// Copyright Epic Games, Inc. All Rights Reserved. #include "LyraInventoryManagerComponent.h" #include "Engine/ActorChannel.h" #include "Engine/World.h" #include "GameFramework/Actor.h" #include "GameFramework/GameplayMessageSubsystem.h" #include "LyraInventoryItemDefinition.h" #include "LyraInventoryItemInstance.h" #include "Misc/AssertionMacros.h" #include "NativeGameplayTags.h" #include "Net/UnrealNetwork.h" #include "UObject/NameTypes.h" #include "UObject/Object.h" #include "UObject/ObjectPtr.h" #include "UObject/UObjectBaseUtility.h" class FLifetimeProperty; struct FReplicationFlags; UE_DEFINE_GAMEPLAY_TAG_STATIC(TAG_Lyra_Inventory_Message_StackChanged, "Lyra.Inventory.Message.StackChanged"); ////////////////////////////////////////////////////////////////////// // FLyraInventoryEntry FString FLyraInventoryEntry::GetDebugString() const { TSubclassOf ItemDef; if (Instance != nullptr) { ItemDef = Instance->GetItemDef(); } return FString::Printf(TEXT("%s (%d x %s)"), *GetNameSafe(Instance), StackCount, *GetNameSafe(ItemDef)); } ////////////////////////////////////////////////////////////////////// // FLyraInventoryList void FLyraInventoryList::PreReplicatedRemove(const TArrayView RemovedIndices, int32 FinalSize) { for (int32 Index : RemovedIndices) { FLyraInventoryEntry& Stack = Entries[Index]; BroadcastChangeMessage(Stack, /*OldCount=*/ Stack.StackCount, /*NewCount=*/ 0); Stack.LastObservedCount = 0; } } void FLyraInventoryList::PostReplicatedAdd(const TArrayView AddedIndices, int32 FinalSize) { for (int32 Index : AddedIndices) { FLyraInventoryEntry& Stack = Entries[Index]; BroadcastChangeMessage(Stack, /*OldCount=*/ 0, /*NewCount=*/ Stack.StackCount); Stack.LastObservedCount = Stack.StackCount; } } void FLyraInventoryList::PostReplicatedChange(const TArrayView ChangedIndices, int32 FinalSize) { for (int32 Index : ChangedIndices) { FLyraInventoryEntry& Stack = Entries[Index]; check(Stack.LastObservedCount != INDEX_NONE); BroadcastChangeMessage(Stack, /*OldCount=*/ Stack.LastObservedCount, /*NewCount=*/ Stack.StackCount); Stack.LastObservedCount = Stack.StackCount; } } void FLyraInventoryList::BroadcastChangeMessage(FLyraInventoryEntry& Entry, int32 OldCount, int32 NewCount) { FLyraInventoryChangeMessage Message; Message.InventoryOwner = OwnerComponent; Message.Instance = Entry.Instance; Message.NewCount = NewCount; Message.Delta = NewCount - OldCount; UGameplayMessageSubsystem& MessageSystem = UGameplayMessageSubsystem::Get(OwnerComponent->GetWorld()); MessageSystem.BroadcastMessage(TAG_Lyra_Inventory_Message_StackChanged, Message); } ULyraInventoryItemInstance* FLyraInventoryList::AddEntry(TSubclassOf ItemDef, int32 StackCount) { ULyraInventoryItemInstance* Result = nullptr; check(ItemDef != nullptr); check(OwnerComponent); AActor* OwningActor = OwnerComponent->GetOwner(); check(OwningActor->HasAuthority()); FLyraInventoryEntry& NewEntry = Entries.AddDefaulted_GetRef(); NewEntry.Instance = NewObject(OwnerComponent->GetOwner()); //@TODO: Using the actor instead of component as the outer due to UE-127172 NewEntry.Instance->SetItemDef(ItemDef); for (ULyraInventoryItemFragment* Fragment : GetDefault(ItemDef)->Fragments) { if (Fragment != nullptr) { Fragment->OnInstanceCreated(NewEntry.Instance); } } NewEntry.StackCount = StackCount; Result = NewEntry.Instance; //const ULyraInventoryItemDefinition* ItemCDO = GetDefault(ItemDef); MarkItemDirty(NewEntry); return Result; } void FLyraInventoryList::AddEntry(ULyraInventoryItemInstance* Instance) { unimplemented(); } void FLyraInventoryList::RemoveEntry(ULyraInventoryItemInstance* Instance) { for (auto EntryIt = Entries.CreateIterator(); EntryIt; ++EntryIt) { FLyraInventoryEntry& Entry = *EntryIt; if (Entry.Instance == Instance) { EntryIt.RemoveCurrent(); MarkArrayDirty(); } } } TArray FLyraInventoryList::GetAllItems() const { TArray Results; Results.Reserve(Entries.Num()); for (const FLyraInventoryEntry& Entry : Entries) { if (Entry.Instance != nullptr) //@TODO: Would prefer to not deal with this here and hide it further? { Results.Add(Entry.Instance); } } return Results; } ////////////////////////////////////////////////////////////////////// // ULyraInventoryManagerComponent ULyraInventoryManagerComponent::ULyraInventoryManagerComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) , InventoryList(this) { SetIsReplicatedByDefault(true); } void ULyraInventoryManagerComponent::GetLifetimeReplicatedProps(TArray< FLifetimeProperty >& OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); DOREPLIFETIME(ThisClass, InventoryList); } bool ULyraInventoryManagerComponent::CanAddItemDefinition(TSubclassOf ItemDef, int32 StackCount) { //@TODO: Add support for stack limit / uniqueness checks / etc... return true; } ULyraInventoryItemInstance* ULyraInventoryManagerComponent::AddItemDefinition(TSubclassOf ItemDef, int32 StackCount) { ULyraInventoryItemInstance* Result = nullptr; if (ItemDef != nullptr) { Result = InventoryList.AddEntry(ItemDef, StackCount); if (IsUsingRegisteredSubObjectList() && IsReadyForReplication() && Result) { AddReplicatedSubObject(Result); } } return Result; } void ULyraInventoryManagerComponent::AddItemInstance(ULyraInventoryItemInstance* ItemInstance) { InventoryList.AddEntry(ItemInstance); if (IsUsingRegisteredSubObjectList() && IsReadyForReplication() && ItemInstance) { AddReplicatedSubObject(ItemInstance); } } void ULyraInventoryManagerComponent::RemoveItemInstance(ULyraInventoryItemInstance* ItemInstance) { InventoryList.RemoveEntry(ItemInstance); if (ItemInstance && IsUsingRegisteredSubObjectList()) { RemoveReplicatedSubObject(ItemInstance); } } TArray ULyraInventoryManagerComponent::GetAllItems() const { return InventoryList.GetAllItems(); } ULyraInventoryItemInstance* ULyraInventoryManagerComponent::FindFirstItemStackByDefinition(TSubclassOf ItemDef) const { for (const FLyraInventoryEntry& Entry : InventoryList.Entries) { ULyraInventoryItemInstance* Instance = Entry.Instance; if (IsValid(Instance)) { if (Instance->GetItemDef() == ItemDef) { return Instance; } } } return nullptr; } int32 ULyraInventoryManagerComponent::GetTotalItemCountByDefinition(TSubclassOf ItemDef) const { int32 TotalCount = 0; for (const FLyraInventoryEntry& Entry : InventoryList.Entries) { ULyraInventoryItemInstance* Instance = Entry.Instance; if (IsValid(Instance)) { if (Instance->GetItemDef() == ItemDef) { ++TotalCount; } } } return TotalCount; } bool ULyraInventoryManagerComponent::ConsumeItemsByDefinition(TSubclassOf ItemDef, int32 NumToConsume) { AActor* OwningActor = GetOwner(); if (!OwningActor || !OwningActor->HasAuthority()) { return false; } //@TODO: N squared right now as there's no acceleration structure int32 TotalConsumed = 0; while (TotalConsumed < NumToConsume) { if (ULyraInventoryItemInstance* Instance = ULyraInventoryManagerComponent::FindFirstItemStackByDefinition(ItemDef)) { InventoryList.RemoveEntry(Instance); ++TotalConsumed; } else { return false; } } return TotalConsumed == NumToConsume; } void ULyraInventoryManagerComponent::ReadyForReplication() { Super::ReadyForReplication(); // Register existing ULyraInventoryItemInstance if (IsUsingRegisteredSubObjectList()) { for (const FLyraInventoryEntry& Entry : InventoryList.Entries) { ULyraInventoryItemInstance* Instance = Entry.Instance; if (IsValid(Instance)) { AddReplicatedSubObject(Instance); } } } } bool ULyraInventoryManagerComponent::ReplicateSubobjects(UActorChannel* Channel, class FOutBunch* Bunch, FReplicationFlags* RepFlags) { bool WroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags); for (FLyraInventoryEntry& Entry : InventoryList.Entries) { ULyraInventoryItemInstance* Instance = Entry.Instance; if (Instance && IsValid(Instance)) { WroteSomething |= Channel->ReplicateSubobject(Instance, *Bunch, *RepFlags); } } return WroteSomething; } ////////////////////////////////////////////////////////////////////// // // UCLASS(Abstract) // class ULyraInventoryFilter : public UObject // { // public: // virtual bool PassesFilter(ULyraInventoryItemInstance* Instance) const { return true; } // }; // UCLASS() // class ULyraInventoryFilter_HasTag : public ULyraInventoryFilter // { // public: // virtual bool PassesFilter(ULyraInventoryItemInstance* Instance) const { return true; } // };