Skip to content

Instantly share code, notes, and snippets.

@tonyN7923
Created April 8, 2024 19:58
Show Gist options
  • Save tonyN7923/bf1aaf3b8bc8e70169b85da1cd9a95ec to your computer and use it in GitHub Desktop.
Save tonyN7923/bf1aaf3b8bc8e70169b85da1cd9a95ec to your computer and use it in GitHub Desktop.
Inventory System Plugin - Sample Implementations
// AddItem
FInventoryItemHandle UInventory::AddItem(const FInventoryItemSpecHandle& InSpecHandle, int32 StacksToAdd)
{
const FInventoryItemHandle NewHandle = FInventoryItemHandle::GenerateNewHandle(this);
check(NewHandle.IsValid());
bool bAddedItem = false;
FInventoryItem* Item = nullptr;
const TObjectPtr<const UInventoryItemDefinition> Def = InSpecHandle.GetData()->Def;
// If stackable, we try finding an existing item to stack it
if (Def->bIsStackable)
{
// A stackable item should have a max stack count of at least 1
if (Def->MaxStackCount <= 0)
{
// If you encounter this, you need to fix your item definition's max stack count. This will return an invalid handle.
INVENTORY_SYSTEM_LOG(Error, TEXT("Stackable item def (%s) should have a max stack count of at least 1."), *Def->GetName());
return FInventoryItemHandle();
}
Item = InventoryItems.FindItem(Def->Id);
// If item is found, add stack
if (Item)
{
// Force stacks to add to always be at least 1
StacksToAdd = FMath::Max(StacksToAdd, 1);
*Item += StacksToAdd;
return Item->Handle;
}
}
// If we haven't added item after checking stackable, then we need to create a new inventory item and try adding that
if (!bAddedItem)
{
FInventoryItem NewItem(NewHandle, *InSpecHandle.GetData());
bAddedItem = InventoryItems.AddItem(NewHandle, NewItem);
Item = InventoryItems.FindItem(NewHandle);
}
if (bAddedItem)
{
// We only care about modifying item properties if we are able to add item to inventory.
if (Item)
{
// Set actual first creation date
// The creation date should always be at this point in the execution. The date should not be modified anywhere else.
Item->SetFirstCreationDate(FDateTime::UtcNow().ToIso8601());
// Give the next instance id to this item
Item->SetInstanceId(++LastItemInstanceId);
if (Def->bIsStackable)
{
// Force stacks to add to always be at least 1
StacksToAdd = FMath::Max(StacksToAdd, 1);
*Item += StacksToAdd;
}
}
AddUsage(Def.Get());
INVENTORY_SYSTEM_LOG(Verbose, TEXT("Added inventory item: %s"), *Item->ToString());
// Broadcast inventory item addition
if (OnInventoryItemAddedDelegate.IsBound())
{
OnInventoryItemAddedDelegate.Broadcast(NewHandle);
}
}
else
{
// Broadcast addition failure
if (OnInventoryItemAddFailedDelegate.IsBound())
{
OnInventoryItemAddFailedDelegate.Broadcast(NewHandle);
}
}
return NewHandle;
}
// RemoveItem
bool UInventory::RemoveItem(const FInventoryItemHandle& InItemHandle, int32 StacksToRemove)
{
check(InItemHandle.IsValid());
FInventoryItem* Item = GetInventoryItem(InItemHandle);
if (!Item)
{
INVENTORY_SYSTEM_LOG(Warning, TEXT("Attempting to remove an invalid item."));
return false;
}
if (!Item->Spec.Def->bIsRemovable)
{
INVENTORY_SYSTEM_LOG(Verbose, TEXT("Remove item failed (Item: %s): Item is not removable."), *Item->ToString());
return false;
}
if (Item->IsLocked())
{
INVENTORY_SYSTEM_LOG(Verbose, TEXT("Remove item failed (Item: %s): Item is locked."), *Item->ToString());
return false;
}
if (Item->Spec.Def->bIsStackable)
{
// Force stacks to remove to always be at least 1
StacksToRemove = FMath::Max(StacksToRemove, 1);
// Change stack count if we're not removing the inventory item entirely
if (StacksToRemove < Item->GetStackCount())
{
*Item -= StacksToRemove;
return true;
}
}
// Notify removal before we actually remove the item
if (Item->Events.OnItemRemoved.IsBound())
{
FInventoryItemRemovalInfo RemovalInfo;
RemovalInfo.StackCount = Item->GetStackCount();
Item->Events.OnItemRemoved.Broadcast(RemovalInfo);
}
INVENTORY_SYSTEM_LOG(Verbose, TEXT("Removing item entirely: %s"), *Item->ToString());
// Remove inventory item entirely
return InventoryItems.RemoveItem(InItemHandle);
}
// Wrapper functions for saving and loading inventory data
bool UInventorySystemSaveGame::Save(FString InSlotName, int32 InUserIndex)
{
PrepareGameSlotValues(InSlotName, InUserIndex);
return UGameplayStatics::SaveGameToSlot(this, InSlotName, InUserIndex);
}
void UInventorySystemSaveGame::SaveAsync(FString InSlotName, int32 InUserIndex, FAsyncSaveGameToSlotDelegate InSavedDelegate)
{
PrepareGameSlotValues(InSlotName, InUserIndex);
UGameplayStatics::AsyncSaveGameToSlot(this, InSlotName, InUserIndex, InSavedDelegate);
}
UInventorySystemSaveGame* UInventorySystemSaveGame::Load(bool& bCreatedNew, bool bCreateIfNotExist, FString InSlotName, int32 InUserIndex)
{
PrepareGameSlotValues(InSlotName, InUserIndex);
bCreatedNew = false;
USaveGame* SaveGame = UGameplayStatics::LoadGameFromSlot(InSlotName, InUserIndex);
if (!SaveGame)
{
if (bCreateIfNotExist)
{
SaveGame = UGameplayStatics::CreateSaveGameObject(StaticClass());
bCreatedNew = true;
}
}
return Cast<UInventorySystemSaveGame>(SaveGame);
}
void UInventorySystemSaveGame::LoadAsync(FString InSlotName, int32 InUserIndex, FAsyncLoadGameFromSlotDelegate InLoadedDelegate)
{
PrepareGameSlotValues(InSlotName, InUserIndex);
UGameplayStatics::AsyncLoadGameFromSlot(InSlotName, InUserIndex, InLoadedDelegate);
}
// Helps determine whether to use the save game slot name/user index set on the InventorySystemComponent
// or those set in the Developer Settings
void UInventorySystemSaveGame::PrepareGameSlotValues(FString& InSlotName, int32& InUserIndex)
{
// Return early if slot name is manually provided
if (InSlotName.Len() > 0) return;
// Slot name is not provided, so we try using slot values from developer settings
if (const auto& Settings = GetDefault<UInventorySystemDeveloperSettings>())
{
if (Settings->SlotName.Len() > 0)
{
InSlotName = Settings->SlotName;
InUserIndex = Settings->UserIndex;
}
}
}
// The plugin gives user the ability to add preset items to an inventory upon first-time loading.
// An example use-case could be an archive of all characters/items that can be unlocked in a game.
void UInventorySubsystem::RegisterComponent(UInventorySystemComponent* InComponent)
{
// omitted code...
// Load SaveGame for adding preset items
InComponent->LoadInventories();
for (const auto& Inventory : InComponent->GetInventories())
{
check(Inventory->IsInitialized());
// Add preset items to inventory
if (Inventory->ItemsPreset)
{
INVENTORY_SYSTEM_LOG(Verbose, TEXT("Attempting to add preset items for %s"), *Inventory->ToString());
for (const auto& ItemClass : Inventory->ItemsPreset->GetItemClasses())
{
FInventoryItemSpecHandle SpecHandle = InComponent->MakeOutgoingSpec(ItemClass->GetDefaultObject<UObject>());
// Check if item already exists. If so, we skip adding preset item.
const bool bItemExists = InComponent->GetCachedSaveGame()->SavedInventoryItems.ContainsByPredicate([SpecHandle](const FInventoryItemSaveData& Data)
{
return Data.Id == SpecHandle.GetData()->Def->Id;
});
if (bItemExists) continue;
FInventoryItemHandle ItemHandle = InComponent->AddItem(SpecHandle);
// Calling this BlueprintNativeEvent function allows UInventory blueprint to do any modifications to this added preset item
Inventory->ReceivePresetItemAdded(ItemHandle);
}
}
}
// Save added preset items
InComponent->SaveInventories();
}
// Overloaded += operator (as well as ++, --, -=) for an inventory item handling stacks.
FInventoryItem& operator+=(int32 Amount)
{
const int32 PreviousStackCount = StackCount;
StackCount += Amount;
ClampStackCount();
if (StackCount != PreviousStackCount)
{
INVENTORY_SYSTEM_LOG(VeryVerbose, TEXT("Added %d stack(s) to inventory item: %s"), StackCount - PreviousStackCount, *ToString());
if (Events.OnItemStackChanged.IsBound())
{
Events.OnItemStackChanged.Broadcast(Handle, StackCount, PreviousStackCount);
}
}
// Broadcast limit reached if adding amount surpasses max stack count
if (PreviousStackCount + Amount > Spec.Def->MaxStackCount)
{
INVENTORY_SYSTEM_LOG(VeryVerbose, TEXT("Inventory item reached max stack (%d): %s"), Spec.Def->MaxStackCount, *ToString());
if (Events.OnItemStackLimitReached.IsBound())
{
Events.OnItemStackLimitReached.Broadcast(Handle, StackCount);
}
}
return *this;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment