Migration

This commit is contained in:
2024-03-20 16:21:19 +01:00
commit 4d87f6e4ab
1847 changed files with 19406 additions and 0 deletions

Binary file not shown.

View File

@@ -0,0 +1,30 @@
{
"FileVersion": 3,
"Version": 1,
"VersionName": "0.1",
"FriendlyName": "SlotBasedInventorySystem",
"Description": "Inventory system with slots",
"Category": "Gameplay",
"CreatedBy": "Amasson",
"CreatedByURL": "",
"DocsURL": "",
"MarketplaceURL": "",
"SupportURL": "",
"CanContainContent": true,
"IsBetaVersion": true,
"IsExperimentalVersion": false,
"Installed": false,
"Modules": [
{
"Name": "SlotBasedInventorySystem",
"Type": "Runtime",
"LoadingPhase": "Default"
}
],
"Plugins": [
{
"Name": "LibAmasson",
"Enabled": true
}
]
}

View File

@@ -0,0 +1,321 @@
// Amasson
#include "Components/SlotInventoryComponent.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/PlayerState.h"
USlotInventoryComponent::USlotInventoryComponent()
{
PrimaryComponentTick.bCanEverTick = true;
SetComponentTickEnabled(false);
}
/** Public Content Management */
const FInventoryContent& USlotInventoryComponent::GetContent() const
{
return Content;
}
int32 USlotInventoryComponent::GetContentCapacity() const
{
return Content.Slots.Num();
}
void USlotInventoryComponent::SetContentCapacity(int32 NewCapacity)
{
if (NewCapacity < 0)
NewCapacity = 0;
const int32 OldCapacity = GetContentCapacity();
Content.Slots.SetNum(NewCapacity, true);
if (NewCapacity > OldCapacity)
{
for (int32 NewSlotIndex = OldCapacity; NewSlotIndex < NewCapacity; NewSlotIndex++)
{
ClearSlotAtIndex(NewSlotIndex);
}
}
OnInventoryCapacityChanged.Broadcast(this, NewCapacity);
}
/** Public Slot Management */
bool USlotInventoryComponent::GetSlotValueAtIndex(int32 Index, FInventorySlot& SlotValue) const
{
if (const FInventorySlot* SlotPtr = Content.GetSlotConstPtrAtIndex(Index))
{
SlotValue = *SlotPtr;
return true;
}
return false;
}
bool USlotInventoryComponent::SetSlotValueAtIndex(int32 Index, const FInventorySlot& NewSlotValue)
{
if (FInventorySlot* SlotPtr = Content.GetSlotPtrAtIndex(Index))
{
*SlotPtr = NewSlotValue;
MarkDirtySlot(Index);
return true;
}
return false;
}
bool USlotInventoryComponent::IsEmptySlotAtIndex(int32 Index) const
{
if (const FInventorySlot* SlotPtr = Content.GetSlotConstPtrAtIndex(Index))
return SlotPtr->IsEmpty();
return false;
}
bool USlotInventoryComponent::ClearSlotAtIndex(int32 Index)
{
if (FInventorySlot* SlotPtr = Content.GetSlotPtrAtIndex(Index))
{
bool bIsEmpty = SlotPtr->IsEmpty();
SlotPtr->Reset();
if (!bIsEmpty)
{
MarkDirtySlot(Index);
return true;
}
}
return false;
}
int32 USlotInventoryComponent::GetEmptySlotCounts() const
{
int32 Total = 0;
for (const FInventorySlot& Slot : Content.Slots)
{
if (Slot.IsEmpty())
++Total;
}
return Total;
}
bool USlotInventoryComponent::ContainsOnlyEmptySlots() const
{
for (const FInventorySlot& Slot : Content.Slots)
{
if (!Slot.IsEmpty())
return false;
}
return true;
}
void USlotInventoryComponent::ModifySlotCountAtIndex(int32 Index, int32 ModifyAmount, bool bAllOrNothing, int32& Overflow)
{
FInventorySlot* SlotPtr = Content.GetSlotPtrAtIndex(Index);
if (!SlotPtr || SlotPtr->IsEmpty())
{
Overflow = ModifyAmount;
return;
}
const uint8 MaxStackSize = GetMaxStackSizeForID(SlotPtr->ID);
if (bAllOrNothing)
{
bool bModified = SlotPtr->TryModifyCountByExact(ModifyAmount, MaxStackSize);
if (bModified)
{
Overflow = 0;
MarkDirtySlot(Index);
}
else
Overflow = ModifyAmount;
}
else
{
SlotPtr->ModifyCountWithOverflow(ModifyAmount, Overflow, MaxStackSize);
if (Overflow != ModifyAmount)
MarkDirtySlot(Index);
}
}
uint8 USlotInventoryComponent::GetMaxStackSizeForID(const FName& ID) const
{
return 255;
}
void USlotInventoryComponent::GetMaxStackSizeForIds(const TSet<FName>& Ids, TMap<FName, uint8>& MaxStackSizes) const
{
for (const FName& Id : Ids)
{
const uint8 MaxStackSize = GetMaxStackSizeForID(Id);
MaxStackSizes.Add(Id, MaxStackSize);
}
}
/** Content Management */
int32 USlotInventoryComponent::GetContentIdCount(FName Id) const
{
int32 Total = 0;
for (const FInventorySlot& Slot : Content.Slots)
{
if (Slot.IsEmpty()) continue;
if (Slot.ID == Id)
Total += Slot.Count;
}
return Total;
}
bool USlotInventoryComponent::ModifyContentWithOverflow(const TMap<FName, int32>& IdsAndCounts, TMap<FName, int32>& Overflows)
{
const TMap<FName, uint8>& MaxStackSizes = GetMaxStackSizesFromIds(IdsAndCounts);
TSet<int32> ModifiedSlots;
FInventoryContent::FContentModificationResult ModificationResult(&ModifiedSlots, &Overflows);
Content.ModifyContentWithValues(IdsAndCounts, MaxStackSizes, ModificationResult);
for (int32 ModifiedSlotIndex : ModifiedSlots)
{
MarkDirtySlot(ModifiedSlotIndex);
}
return true;
}
bool USlotInventoryComponent::TryModifyContentWithoutOverflow(const TMap<FName, int32>& IdsAndCounts)
{
const TMap<FName, uint8>& MaxStackSizes = GetMaxStackSizesFromIds(IdsAndCounts);
TSet<int32> ModifiedSlots;
FInventoryContent TmpContent = Content;
TMap<FName, int32> TmpOverflows;
FInventoryContent::FContentModificationResult ModificationResult(&ModifiedSlots, &TmpOverflows);
TmpContent.ModifyContentWithValues(IdsAndCounts, MaxStackSizes, ModificationResult);
if (!TmpOverflows.IsEmpty())
return false;
Content = TmpContent;
for (int32 ModifiedSlotIndex : ModifiedSlots)
MarkDirtySlot(ModifiedSlotIndex);
return true;
}
bool USlotInventoryComponent::DropSlotTowardOtherInventoryAtIndex(int32 SourceIndex, USlotInventoryComponent* DestinationInventory, int32 DestinationIndex, uint8 MaxAmount)
{
if (!IsValid(DestinationInventory)) return false;
FInventorySlot* SourceSlot = Content.GetSlotPtrAtIndex(SourceIndex);
if (!SourceSlot)
return false;
const uint8 MaxStackSize = DestinationInventory->GetMaxStackSizeForID(SourceSlot->ID);
if (DestinationInventory->Content.ReceiveSlotAtIndex(*SourceSlot, DestinationIndex, MaxStackSize, MaxAmount))
{
MarkDirtySlot(SourceIndex);
DestinationInventory->MarkDirtySlot(DestinationIndex);
return true;
}
return false;
}
bool USlotInventoryComponent::DropSlotTowardOtherInventory(int32 SourceIndex, USlotInventoryComponent* Destination)
{
if (!IsValid(Destination)) return false;
FInventorySlot SourceSlot;
if (!GetSlotValueAtIndex(SourceIndex, SourceSlot))
return false;
if (SourceSlot.IsEmpty())
return false;
TMap<FName, int32> Modifications;
Modifications.Add(SourceSlot.ID, SourceSlot.Count);
TMap<FName, int32> Overflows;
if (!Destination->ModifyContentWithOverflow(Modifications, Overflows))
return false;
if (Overflows.IsEmpty())
{
ClearSlotAtIndex(SourceIndex);
return true;
}
checkf(Overflows.Contains(SourceSlot.ID), TEXT("Overflow is not empty but does not contains SourceSlot.ID"));
const int32 NewCount = Overflows[SourceSlot.ID];
if (SourceSlot.Count == NewCount)
return false;
SourceSlot.Count = NewCount;
return SetSlotValueAtIndex(SourceIndex, SourceSlot);
}
void USlotInventoryComponent::RegroupSlotAtIndexWithSimilarIds(int32 Index)
{
TSet<int32> ModifiedSlots;
FInventoryContent::FContentModificationResult ModificationResult(&ModifiedSlots, nullptr);
FInventorySlot* Slot = Content.GetSlotPtrAtIndex(Index);
if (!Slot) return;
const uint8 MaxStackSize = GetMaxStackSizeForID(Slot->ID);
Content.RegroupSlotsWithSimilarIdsAtIndex(Index, ModificationResult, MaxStackSize, Slot);
for (const int32 ModifiedSlotIndex : ModifiedSlots)
MarkDirtySlot(ModifiedSlotIndex);
}
/** Private Content Management */
const TMap<FName, uint8> USlotInventoryComponent::GetMaxStackSizesFromIds(const TMap<FName, int32>& IdsAndCounts) const
{
TSet<FName> Ids;
IdsAndCounts.GetKeys(Ids);
TMap<FName, uint8> MaxStackSizes;
GetMaxStackSizeForIds(Ids, MaxStackSizes);
return MaxStackSizes;
}
/** Slot Updating */
void USlotInventoryComponent::BroadcastContentUpdate()
{
OnInventoryContentChanged.Broadcast(this, DirtySlots.Array());
DirtySlots.Reset();
}
void USlotInventoryComponent::MarkDirtySlot(int32 SlotIndex)
{
DirtySlots.Add(SlotIndex);
MarkSlotsHaveBeenModified();
}
void USlotInventoryComponent::MarkSlotsHaveBeenModified()
{
SetComponentTickEnabled(true);
}

View File

@@ -0,0 +1,214 @@
// Amasson
#include "Components/SlotInventoryComponent_Networked.h"
#include "Net/UnrealNetwork.h"
USlotInventoryComponent_Networked::USlotInventoryComponent_Networked()
{
SetIsReplicatedByDefault(true);
bHasAuthority = GetOwner() ? GetOwner()->HasAuthority() : false;
}
void USlotInventoryComponent_Networked::BeginPlay()
{
Super::BeginPlay();
if (bHasAuthority)
{
OnInventoryCapacityChanged.AddDynamic(this, &ThisClass::OnCapacityChanged);
}
}
void USlotInventoryComponent_Networked::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
}
void USlotInventoryComponent_Networked::Server_BroadcastFullInventory_Implementation(bool bOwnerOnly)
{
TArray<int32> AllIndices;
AllIndices.Reserve(GetContentCapacity() + 1); // Reserve space for N+1 elements
for (int32 i = 0; i <= GetContentCapacity(); i++)
AllIndices.Add(i);
if (bOwnerOnly)
Client_UpdateSlotsValues_Implementation(AllIndices, Content.Slots);
else
NetMulticast_UpdateSlotsValues_Implementation(AllIndices, Content.Slots);
}
/** Client Request */
void USlotInventoryComponent_Networked::Server_RequestSetContentCapacity_Implementation(int32 NewCapacity)
{
SetContentCapacity(NewCapacity);
}
void USlotInventoryComponent_Networked::Server_RequestSetSlotValueAtIndex_Implementation(int32 Index, const FInventorySlot& NewSlotValue)
{
SetSlotValueAtIndex(Index, NewSlotValue);
}
void USlotInventoryComponent_Networked::Server_RequestClearSlotAtIndex_Implementation(int32 Index)
{
ClearSlotAtIndex(Index);
}
void USlotInventoryComponent_Networked::Server_RequestDropSlotTowardOtherInventoryAtIndex_Implementation(int32 SourceIndex, USlotInventoryComponent* DestinationInventory, int32 DestinationIndex, uint8 MaxAmount)
{
DropSlotTowardOtherInventoryAtIndex(SourceIndex, DestinationInventory, DestinationIndex, MaxAmount);
}
void USlotInventoryComponent_Networked::Server_RequestDropSlotTowardOtherInventory_Implementation(int32 SourceIndex, USlotInventoryComponent* DestinationInventory)
{
DropSlotTowardOtherInventory(SourceIndex, DestinationInventory);
}
void USlotInventoryComponent_Networked::Server_RequestDropSlotFromOtherInventoryAtIndex_Implementation(int32 DestinationIndex, USlotInventoryComponent* SourceInventory, int32 SourceIndex, uint8 MaxAmount)
{
if (IsValid(SourceInventory))
{
SourceInventory->DropSlotTowardOtherInventoryAtIndex(SourceIndex, this, DestinationIndex, MaxAmount);
}
}
void USlotInventoryComponent_Networked::Server_RequestDropSlotFromOtherInventory_Implementation(USlotInventoryComponent* SourceInventory, int32 SourceIndex)
{
if (IsValid(SourceInventory))
{
SourceInventory->DropSlotTowardOtherInventory(SourceIndex, this);
}
}
static AActor* GetLastValidOwner(AActor* Actor)
{
if (IsValid(Actor))
{
AActor* Owner = Actor->GetOwner();
AActor* ValidOwner = GetLastValidOwner(Owner);
return IsValid(ValidOwner) ? ValidOwner : Actor;
}
return nullptr;
}
static bool IsValidAndCanCallRPC(UActorComponent* Component)
{
if (IsValid(Component))
{
AActor* LastOwner = GetLastValidOwner(Component->GetOwner());
if (IsValid(LastOwner))
{
ENetRole NetRole = LastOwner->GetLocalRole();
bool bCanCallRPC = NetRole >= ENetRole::ROLE_AutonomousProxy;
return bCanCallRPC;
}
}
return false;
}
void USlotInventoryComponent_Networked::DropInventorySlotFromSourceToDestinationAtIndex(USlotInventoryComponent_Networked* SourceInventory, int32 SourceIndex, USlotInventoryComponent_Networked* DestinationInventory, int32 DestinationIndex, uint8 MaxAmount)
{
if (IsValidAndCanCallRPC(SourceInventory))
{
SourceInventory->Server_RequestDropSlotTowardOtherInventoryAtIndex(SourceIndex, DestinationInventory, DestinationIndex, MaxAmount);
}
else if (IsValidAndCanCallRPC(DestinationInventory))
{
DestinationInventory->Server_RequestDropSlotFromOtherInventoryAtIndex(DestinationIndex, SourceInventory, SourceIndex, MaxAmount);
}
}
void USlotInventoryComponent_Networked::DropInventorySlotFromSourceToDestination(USlotInventoryComponent_Networked* SourceInventory, int32 SourceIndex, USlotInventoryComponent_Networked* DestinationInventory)
{
if (IsValidAndCanCallRPC(SourceInventory))
{
SourceInventory->Server_RequestDropSlotTowardOtherInventory(SourceIndex, DestinationInventory);
}
else if (IsValidAndCanCallRPC(DestinationInventory))
{
DestinationInventory->Server_RequestDropSlotFromOtherInventory(SourceInventory, SourceIndex);
}
}
void USlotInventoryComponent_Networked::Server_RequestRegroupSlotAtIndexWithSimilarIds_Implementation(int32 Index)
{
RegroupSlotAtIndexWithSimilarIds(Index);
}
/** Slot Update */
void USlotInventoryComponent_Networked::NetMulticast_UpdateSlotsValues_Implementation(const TArray<int32>& Indices, const TArray<FInventorySlot>& Values)
{
ReceievedUpdateSlotsValues(Indices, Values);
}
void USlotInventoryComponent_Networked::Client_UpdateSlotsValues_Implementation(const TArray<int32>& Indices, const TArray<FInventorySlot>& Values)
{
ReceievedUpdateSlotsValues(Indices, Values);
}
void USlotInventoryComponent_Networked::ReceievedUpdateSlotsValues(const TArray<int32>& Indices, const TArray<FInventorySlot>& Values)
{
checkf(Indices.Num() == Values.Num(), TEXT("SlotInventoryComponent_Networked::ReceievedUpdateSlotsValues: Received miss matching arrays"));
if (bHasAuthority)
return;
for (int32 i = 0; i < Indices.Num(); i++)
{
SetSlotValueAtIndex(Indices[i], Values[i]);
}
}
/** Capacity Update */
void USlotInventoryComponent_Networked::OnCapacityChanged(USlotInventoryComponent* SlotInventoryComponent, int32 NewCapacity)
{
if (SlotInventoryComponent == this)
{
NetMulticast_UpdateCapacity(NewCapacity);
}
}
void USlotInventoryComponent_Networked::NetMulticast_UpdateCapacity_Implementation(int32 NewCapacity)
{
if (bHasAuthority)
return;
SetContentCapacity(NewCapacity);
}
/** Content Update */
void USlotInventoryComponent_Networked::BroadcastContentUpdate()
{
if (bHasAuthority)
BroadcastModifiedSlotsToClients();
Super::BroadcastContentUpdate();
}
void USlotInventoryComponent_Networked::BroadcastModifiedSlotsToClients()
{
TArray<int32> Indices;
TArray<FInventorySlot> Values;
for (int32 DirtySlotIndex : DirtySlots)
{
FInventorySlot SlotValue;
if (GetSlotValueAtIndex(DirtySlotIndex, SlotValue))
{
Indices.Add(DirtySlotIndex);
Values.Add(SlotValue);
}
}
NetMulticast_UpdateSlotsValues(Indices, Values);
}

View File

@@ -0,0 +1,20 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "SlotBasedInventorySystem.h"
#define LOCTEXT_NAMESPACE "FSlotBasedInventorySystemModule"
void FSlotBasedInventorySystemModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
}
void FSlotBasedInventorySystemModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FSlotBasedInventorySystemModule, SlotBasedInventorySystem)

View File

@@ -0,0 +1,15 @@
// Amasson
#include "SlotInventoryBlueprintLibrary.h"
bool USlotInventoryBlueprintLibrary::IsValidIndex(const FInventoryContent& Content, int32 Index)
{
return Content.IsValidIndex(Index);
}
bool USlotInventoryBlueprintLibrary::IsEmptySlot(const FInventorySlot& Slot)
{
return Slot.IsEmpty();
}

View File

@@ -0,0 +1,258 @@
// Amasson
#include "Structures/SlotInventorySystemStructs.h"
#include "Math/UnrealMathUtility.h"
#include "Templates/UnrealTemplate.h"
bool FInventorySlot::IsEmpty() const
{
return ID == NAME_None || Count == 0;
}
void FInventorySlot::Reset()
{
ID = NAME_None;
Count = 0;
}
void FInventorySlot::ModifyCountWithOverflow(int32 ModifyAmount, int32& Overflow, uint8 MaxStackSize)
{
int32 NewCount = Count + ModifyAmount;
if (NewCount < 0)
{
Overflow = Count + ModifyAmount;
Count = 0;
}
else if (NewCount > MaxStackSize)
{
Overflow = Count + ModifyAmount - MaxStackSize;
Count = MaxStackSize;
}
else
{
Overflow = 0;
Count = NewCount;
}
}
bool FInventorySlot::TryModifyCountByExact(int32 ModifyAmount, uint8 MaxStackSize)
{
int32 NewCount = Count + ModifyAmount;
if (NewCount < 0 || NewCount > MaxStackSize)
return false;
Count = NewCount;
return true;
}
bool FInventorySlot::AddIdAndCount(const FName& SlotId, int32 ModifyAmount, int32& Overflow, uint8 MaxStackSize)
{
if (IsEmpty())
{
if (ModifyAmount < 0)
{
Overflow = ModifyAmount;
return false;
}
ID = SlotId;
Count = FMath::Min(ModifyAmount, int32(MaxStackSize));
Overflow = ModifyAmount - Count;
return Overflow != ModifyAmount;
}
if (ID != SlotId)
{
Overflow = ModifyAmount;
return false;
}
ModifyCountWithOverflow(ModifyAmount, Overflow, MaxStackSize);
return Overflow != ModifyAmount;
}
/** Inventory Content */
bool FInventoryContent::IsValidIndex(int32 Index) const
{
return Index >= 0 && Index < Slots.Num();
}
FInventorySlot* FInventoryContent::GetSlotPtrAtIndex(int32 Index)
{
if (!IsValidIndex(Index))
return nullptr;
return &(Slots[Index]);
}
const FInventorySlot* FInventoryContent::GetSlotConstPtrAtIndex(int32 Index) const
{
if (!IsValidIndex(Index))
return nullptr;
return &(Slots[Index]);
}
FInventoryContent::FContentModificationResult::FContentModificationResult(TSet<int32>* InModifiedSlots, TMap<FName, int32>* InOverflows)
: bModifiedSomething(false), bCreatedEmptySlot(false),
ModifiedSlots(InModifiedSlots), Overflows(InOverflows)
{}
void FInventoryContent::ModifyContentWithValues(const TMap<FName, int32>& IdsAndCounts, const TMap<FName, uint8>& MaxStackSizes, FContentModificationResult& ModificationResult)
{
bool bModified = false;
bool bHasPositiveOverflow = false;
bool bHasNewEmptySlots = false;
for (const TPair<FName, int32>& IdAndCount : IdsAndCounts)
{
const FName& SlotId = IdAndCount.Key;
int32 ModifyAmount = IdAndCount.Value;
const uint8 MaxStackSize = MaxStackSizes[SlotId];
FContentModificationResult Result(ModificationResult.ModifiedSlots, nullptr);
ReceiveSlotOverflow(SlotId, ModifyAmount, MaxStackSize, false, Result);
ReceiveSlotOverflow(SlotId, ModifyAmount, MaxStackSize, true, Result);
bModified = Result.bModifiedSomething;
bHasNewEmptySlots = Result.bCreatedEmptySlot;
if (ModifyAmount != 0)
{
if (ModificationResult.Overflows)
ModificationResult.Overflows->Add(SlotId, ModifyAmount);
if (ModifyAmount > 0)
bHasPositiveOverflow = true;
}
}
if (bModified && bHasPositiveOverflow && bHasNewEmptySlots && ModificationResult.Overflows)
{
if (ModificationResult.Overflows)
{
const TMap<FName, int32> NewModifications = *ModificationResult.Overflows;
ModificationResult.Overflows->Reset();
ModifyContentWithValues(NewModifications, MaxStackSizes, ModificationResult);
}
}
}
void FInventoryContent::ReceiveSlotOverflow(const FName& SlotId, int32& InoutOverflow, uint8 MaxStackSize, bool bTargetEmptySlots, FContentModificationResult& ModificationResult)
{
for (int32 i = 0; i < Slots.Num() && InoutOverflow != 0; i++)
{
FInventorySlot& Slot(Slots[i]);
if (Slot.IsEmpty() != bTargetEmptySlots)
continue;
if (Slot.AddIdAndCount(SlotId, InoutOverflow, InoutOverflow, MaxStackSize))
{
ModificationResult.bModifiedSomething = true;
if (Slot.IsEmpty())
ModificationResult.bCreatedEmptySlot = true;
if (ModificationResult.ModifiedSlots)
ModificationResult.ModifiedSlots->Add(i);
}
}
}
bool FInventoryContent::ReceiveSlotAtIndex(FInventorySlot& InoutSlot, int32 Index, uint8 MaxStackSize, uint8 MaxTransferAmount)
{
FInventorySlot* LocalSlot = GetSlotPtrAtIndex(Index);
if (!LocalSlot)
return false;
if (LocalSlot->IsEmpty())
{
if (InoutSlot.IsEmpty())
return false;
LocalSlot->ID = InoutSlot.ID;
LocalSlot->Count = 0;
}
if (LocalSlot->ID == InoutSlot.ID)
{
return MergeSlotsWithSimilarIds(*LocalSlot, InoutSlot, MaxStackSize, MaxTransferAmount);
}
else
{
bool bCanReceiveSlot = InoutSlot.Count <= MaxTransferAmount && InoutSlot.Count <= MaxStackSize;
if (!bCanReceiveSlot)
return false;
SwapSlots(InoutSlot, *LocalSlot);
}
return true;
}
void FInventoryContent::RegroupSlotsWithSimilarIdsAtIndex(int32 Index, FContentModificationResult& ModificationResult, uint8 MaxStackSize, FInventorySlot* CachedSlotPtr)
{
FInventorySlot* TargetSlot = CachedSlotPtr ? CachedSlotPtr : GetSlotPtrAtIndex(Index);
if (!TargetSlot)
return;
if (TargetSlot->IsEmpty())
return;
for (int32 SlotIndex = 0; SlotIndex < Slots.Num() && TargetSlot->Count < MaxStackSize; SlotIndex++)
{
if (TargetSlot->Count >= MaxStackSize)
break;
if (SlotIndex == Index)
continue;
FInventorySlot& Slot = Slots[SlotIndex];
if (!Slot.IsEmpty() && Slot.ID == TargetSlot->ID)
{
bool bMerged = MergeSlotsWithSimilarIds(*TargetSlot, Slot, MaxStackSize);
if (bMerged)
{
ModificationResult.bModifiedSomething = true;
if (ModificationResult.ModifiedSlots)
ModificationResult.ModifiedSlots->Add(SlotIndex);
if (Slot.IsEmpty())
ModificationResult.bCreatedEmptySlot = true;
}
}
}
if (ModificationResult.bModifiedSomething)
if (ModificationResult.ModifiedSlots)
ModificationResult.ModifiedSlots->Add(Index);
}
bool FInventoryContent::MergeSlotsWithSimilarIds(FInventorySlot& DestinationSlot, FInventorySlot& SourceSlot, uint8 MaxStackSize, uint8 MaxTransferAmount)
{
checkf(SourceSlot.ID == DestinationSlot.ID, TEXT("Tried to merge slots with different Ids (%s!=%s)"), *SourceSlot.ID.ToString(), *DestinationSlot.ID.ToString());
const int32 LocalCount = DestinationSlot.Count;
const int32 ReceiveCount = SourceSlot.Count;
const int32 TotalCount = LocalCount + ReceiveCount;
const int32 StackCount = FMath::Min(TotalCount, int32(MaxStackSize));
const int32 TotalTransferAmount = StackCount - LocalCount;
const int32 TransferAmount = FMath::Min(TotalTransferAmount, int32(MaxTransferAmount));
if (TransferAmount == 0)
return false;
SourceSlot.Count -= TransferAmount;
DestinationSlot.Count += TransferAmount;
return true;
}
void FInventoryContent::SwapSlots(FInventorySlot& FirstSlot, FInventorySlot& SecondSlot)
{
Swap(FirstSlot, SecondSlot);
}

View File

@@ -0,0 +1,131 @@
// Amasson
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Structures/SlotInventorySystemStructs.h"
#include "SlotInventoryComponent.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnInventoryCapacityChangedSignature, USlotInventoryComponent*, SlotInventoryComponent, int32, NewCapacity);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnInventoryContentChangedSignature, USlotInventoryComponent*, SlotInventoryComponent, const TArray<int32>&, ChangedSlots);
UCLASS( Blueprintable, ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class SLOTBASEDINVENTORYSYSTEM_API USlotInventoryComponent : public UActorComponent
{
GENERATED_BODY()
public:
USlotInventoryComponent();
UPROPERTY(BlueprintAssignable)
FOnInventoryCapacityChangedSignature OnInventoryCapacityChanged;
UPROPERTY(BlueprintAssignable)
FOnInventoryContentChangedSignature OnInventoryContentChanged;
/** Content Management */
UFUNCTION(BlueprintCallable, Category = "Content")
const FInventoryContent& GetContent() const;
UFUNCTION(BlueprintCallable, Category = "Content|Capacity")
int32 GetContentCapacity() const;
UFUNCTION(BlueprintCallable, Category = "Content|Capacity")
void SetContentCapacity(int32 NewCapacity);
/** Slot Management */
UFUNCTION(BlueprintCallable, Category = "Content|Slot")
bool GetSlotValueAtIndex(int32 Index, FInventorySlot& SlotValue) const;
UFUNCTION(BlueprintCallable, Category = "Content|Slot")
bool SetSlotValueAtIndex(int32 Index, const FInventorySlot& NewSlotValue);
UFUNCTION(BlueprintCallable, Category = "Content|Slot|Count")
bool IsEmptySlotAtIndex(int32 Index) const;
UFUNCTION(BlueprintCallable, Category = "Content|Slot|Count")
bool ClearSlotAtIndex(int32 Index);
UFUNCTION(BlueprintCallable, Category = "Content|Slot|Count")
int32 GetEmptySlotCounts() const;
UFUNCTION(BlueprintCallable, Category = "Content|Slot|Count")
bool ContainsOnlyEmptySlots() const;
UFUNCTION(BlueprintCallable, Category = "Content|Slot|Count")
void ModifySlotCountAtIndex(int32 Index, int32 ModifyAmount, bool bAllOrNothing, int32& Overflow);
UFUNCTION(BlueprintCallable, Category = "Content|Slot|Count")
virtual uint8 GetMaxStackSizeForID(const FName& ID) const;
UFUNCTION(BlueprintCallable, Category = "Content|Slot|Count")
void GetMaxStackSizeForIds(const TSet<FName>& Ids, TMap<FName, uint8>& MaxStackSizes) const;
/** Content Management */
UFUNCTION(BlueprintCallable, Category = "Content|Count")
int32 GetContentIdCount(FName Id) const;
UFUNCTION(BlueprintCallable, Category = "Content|Modify")
bool ModifyContentWithOverflow(const TMap<FName, int32>& IdsAndCounts, TMap<FName, int32>& Overflows);
UFUNCTION(BlueprintCallable, Category = "Content|Modify")
bool TryModifyContentWithoutOverflow(const TMap<FName, int32>& IdsAndCounts);
UFUNCTION(BlueprintCallable, Category = "Content|Action")
bool DropSlotTowardOtherInventoryAtIndex(int32 SourceIndex, USlotInventoryComponent* Destination, int32 DestinationIndex, uint8 MaxAmount = 255);
UFUNCTION(BlueprintCallable, Category = "Content|Action")
bool DropSlotTowardOtherInventory(int32 SourceIndex, USlotInventoryComponent* Destination);
UFUNCTION(BlueprintCallable, Category = "Content|Action")
void RegroupSlotAtIndexWithSimilarIds(int32 Index);
protected:
/** Content Management */
const TMap<FName, uint8> GetMaxStackSizesFromIds(const TMap<FName, int32>& IdsAndCounts) const;
/** Slot Updating */
virtual void BroadcastContentUpdate();
void MarkDirtySlot(int32 SlotIndex);
void MarkSlotsHaveBeenModified();
public:
/**
* The purpose of the tick function is to trigger an update broadcast only once.
* We can use mark modified content multiple times in a same tick but they will
* be cached and only broadcast in the next tick all at once.
*/
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
SetComponentTickEnabled(false);
BroadcastContentUpdate();
}
protected:
UPROPERTY(EditAnywhere, Category = "Content", meta = (AllowPrivateAccess = true))
FInventoryContent Content;
TSet<int32> DirtySlots;
};

View File

@@ -0,0 +1,93 @@
// Amasson
#pragma once
#include "CoreMinimal.h"
#include "Components/SlotInventoryComponent.h"
#include "SlotInventoryComponent_Networked.generated.h"
/**
*
*/
UCLASS( Blueprintable, ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class SLOTBASEDINVENTORYSYSTEM_API USlotInventoryComponent_Networked : public USlotInventoryComponent
{
GENERATED_BODY()
public:
USlotInventoryComponent_Networked();
virtual void BeginPlay() override;
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
UFUNCTION(Server, Reliable, BlueprintCallable, Category = "ClientRequest|Update")
void Server_BroadcastFullInventory(bool bOwnerOnly = true);
/** Client Request */
UFUNCTION(Server, Reliable, BlueprintCallable, Category = "ClientRequest|Content|Capacity")
void Server_RequestSetContentCapacity(int32 NewCapacity);
UFUNCTION(Server, Reliable, BlueprintCallable, Category = "ClientRequest|Content|Slot")
void Server_RequestSetSlotValueAtIndex(int32 Index, const FInventorySlot& NewSlotValue);
UFUNCTION(Server, Reliable, BlueprintCallable, Category = "ClientRequest|Content|Slot")
void Server_RequestClearSlotAtIndex(int32 Index);
UFUNCTION(Server, Reliable, BlueprintCallable, Category = "ClientRequest|Action|Drop")
void Server_RequestDropSlotTowardOtherInventoryAtIndex(int32 SourceIndex, USlotInventoryComponent* DestinationInventory, int32 DestinationIndex, uint8 MaxAmount = 255);
UFUNCTION(Server, Reliable, BlueprintCallable, Category = "ClientRequest|Action|Drop")
void Server_RequestDropSlotTowardOtherInventory(int32 SourceIndex, USlotInventoryComponent* DestinationInventory);
UFUNCTION(Server, Reliable, BlueprintCallable, Category = "ClientRequest|Action|Drop")
void Server_RequestDropSlotFromOtherInventoryAtIndex(int32 DestinationIndex, USlotInventoryComponent* SourceInventory, int32 SourceIndex, uint8 MaxAmount = 255);
UFUNCTION(Server, Reliable, BlueprintCallable, Category = "ClientRequest|Action|Drop")
void Server_RequestDropSlotFromOtherInventory(USlotInventoryComponent* SourceInventory, int32 SourceIndex);
UFUNCTION(BlueprintCallable, Category = "ClientRequest|Action|Drop")
static void DropInventorySlotFromSourceToDestinationAtIndex(USlotInventoryComponent_Networked* SourceInventory, int32 SourceIndex, USlotInventoryComponent_Networked* DestinationInventory, int32 DestinationIndex, uint8 MaxAmount = 255);
UFUNCTION(BlueprintCallable, Category = "ClientRequest|Action|Drop")
static void DropInventorySlotFromSourceToDestination(USlotInventoryComponent_Networked* SourceInventory, int32 SourceIndex, USlotInventoryComponent_Networked* DestinationInventory);
UFUNCTION(Server, Reliable, BlueprintCallable, Category = "ClientRequest|Action")
void Server_RequestRegroupSlotAtIndexWithSimilarIds(int32 Index);
protected:
/** Slot Update */
UFUNCTION(NetMulticast, Reliable)
void NetMulticast_UpdateSlotsValues(const TArray<int32>& Indices, const TArray<FInventorySlot>& Values);
UFUNCTION(Client, Reliable)
void Client_UpdateSlotsValues(const TArray<int32>& Indices, const TArray<FInventorySlot>& Values);
void ReceievedUpdateSlotsValues(const TArray<int32>& Indices, const TArray<FInventorySlot>& Values);
/** Capacity Update */
UFUNCTION()
void OnCapacityChanged(USlotInventoryComponent* SlotInventoryComponent, int32 NewCapacity);
UFUNCTION(NetMulticast, Reliable)
void NetMulticast_UpdateCapacity(int32 NewCapacity);
/** Content Update */
virtual void BroadcastContentUpdate() override;
void BroadcastModifiedSlotsToClients();
bool bHasAuthority;
};

View File

@@ -0,0 +1,15 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
class FSlotBasedInventorySystemModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};

View File

@@ -0,0 +1,27 @@
// Amasson
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Structures/SlotInventorySystemStructs.h"
#include "SlotInventoryBlueprintLibrary.generated.h"
/**
*
*/
UCLASS()
class SLOTBASEDINVENTORYSYSTEM_API USlotInventoryBlueprintLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
/** Inventory Content */
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SlotInventory|Content")
static bool IsValidIndex(const FInventoryContent& Content, int32 Index);
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SlotInventory|Slot")
static bool IsEmptySlot(const FInventorySlot& Slot);
};

View File

@@ -0,0 +1,68 @@
// Amasson
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataTable.h"
#include "SlotInventorySystemStructs.generated.h"
USTRUCT(BlueprintType)
struct FInventorySlot
{
GENERATED_USTRUCT_BODY()
UPROPERTY(BlueprintReadWrite, EditAnywhere, SaveGame)
FName ID;
UPROPERTY(BlueprintReadWrite, EditAnywhere, SaveGame)
uint8 Count = 0;
bool IsEmpty() const;
void Reset();
void ModifyCountWithOverflow(int32 ModifyAmount, int32& Overflow, uint8 MaxStackSize = 255);
bool TryModifyCountByExact(int32 ModifyAmount, uint8 MaxStackSize = 255);
bool AddIdAndCount(const FName& SlotId, int32 ModifyAmount, int32& Overflow, uint8 MaxStackSize = 255);
};
USTRUCT(BlueprintType)
struct SLOTBASEDINVENTORYSYSTEM_API FInventoryContent
{
GENERATED_USTRUCT_BODY()
UPROPERTY(BlueprintReadWrite, EditAnywhere, SaveGame)
TArray<FInventorySlot> Slots;
bool IsValidIndex(int32 Index) const;
FInventorySlot* GetSlotPtrAtIndex(int32 Index);
const FInventorySlot* GetSlotConstPtrAtIndex(int32 Index) const;
struct FContentModificationResult
{
bool bModifiedSomething;
bool bCreatedEmptySlot;
TSet<int32>* ModifiedSlots;
TMap<FName, int32>* Overflows;
FContentModificationResult(TSet<int32>* InModifiedSlots, TMap<FName, int32>* Overflows);
};
void ModifyContentWithValues(const TMap<FName, int32>& IdsAndCounts, const TMap<FName, uint8>& MaxStackSizes, FContentModificationResult& ModificationResult);
void ReceiveSlotOverflow(const FName& SlotId, int32& InoutOverflow, uint8 MaxStackSize, bool bTargetEmptySlots, FContentModificationResult& ModificationResult);
bool ReceiveSlotAtIndex(FInventorySlot& InoutSlot, int32 Index, uint8 MaxStackSize = 255, uint8 MaxTransferAmount = 255);
void RegroupSlotsWithSimilarIdsAtIndex(int32 Index, FContentModificationResult& ModificationResult, uint8 MaxStackSize = 255, FInventorySlot* CachedSlotPtr = nullptr);
static bool MergeSlotsWithSimilarIds(FInventorySlot& DestinationSlot, FInventorySlot& SourceSlot, uint8 MaxStackSize = 255, uint8 MaxTransferAmount = 255);
static void SwapSlots(FInventorySlot& FirstSlot, FInventorySlot& SecondSlot);
};

View File

@@ -0,0 +1,53 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class SlotBasedInventorySystem : ModuleRules
{
public SlotBasedInventorySystem(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.AddRange(
new string[] {
// ... add public include paths required here ...
}
);
PrivateIncludePaths.AddRange(
new string[] {
// ... add other private include paths required here ...
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
// ... add private dependencies that you statically link with here ...
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
);
}
}