第一次提交
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Async/GIS_AsyncAction_Wait.h"
|
||||
#include "TimerManager.h"
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_AsyncAction_Wait)
|
||||
|
||||
|
||||
UGIS_AsyncAction_Wait::UGIS_AsyncAction_Wait()
|
||||
{
|
||||
}
|
||||
|
||||
bool UGIS_AsyncAction_Wait::ShouldBroadcastDelegates() const
|
||||
{
|
||||
return Super::ShouldBroadcastDelegates() && IsValid(GetActor());
|
||||
}
|
||||
|
||||
void UGIS_AsyncAction_Wait::StopWaiting()
|
||||
{
|
||||
const UWorld* World = GetWorld();
|
||||
if (TimerHandle.IsValid() && IsValid(World))
|
||||
{
|
||||
FTimerManager& TimerManager = World->GetTimerManager();
|
||||
TimerManager.ClearTimer(TimerHandle);
|
||||
}
|
||||
}
|
||||
|
||||
void UGIS_AsyncAction_Wait::Cleanup()
|
||||
{
|
||||
AActor* Actor = GetActor();
|
||||
|
||||
if (IsValid(Actor))
|
||||
{
|
||||
Actor->OnDestroyed.RemoveDynamic(this, &ThisClass::OnTargetDestroyed);
|
||||
}
|
||||
|
||||
StopWaiting();
|
||||
}
|
||||
|
||||
void UGIS_AsyncAction_Wait::Activate()
|
||||
{
|
||||
const UWorld* World = GetWorld();
|
||||
AActor* Actor = GetActor();
|
||||
if (IsValid(World) && IsValid(Actor))
|
||||
{
|
||||
FTimerManager& TimerManager = World->GetTimerManager();
|
||||
TimerManager.SetTimer(TimerHandle, this, &ThisClass::OnTimer, WaitInterval, true, 0);
|
||||
Actor->OnDestroyed.AddDynamic(this, &ThisClass::OnTargetDestroyed);
|
||||
}
|
||||
else
|
||||
{
|
||||
Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
UWorld* UGIS_AsyncAction_Wait::GetWorld() const
|
||||
{
|
||||
if (WorldPtr.IsValid() && WorldPtr->IsValidLowLevelFast())
|
||||
{
|
||||
return WorldPtr.Get();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AActor* UGIS_AsyncAction_Wait::GetActor() const
|
||||
{
|
||||
if (TargetActorPtr.IsValid() && TargetActorPtr->IsValidLowLevelFast())
|
||||
{
|
||||
return TargetActorPtr.Get();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void UGIS_AsyncAction_Wait::Cancel()
|
||||
{
|
||||
Super::Cancel();
|
||||
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
void UGIS_AsyncAction_Wait::OnTargetDestroyed(AActor* DestroyedActor)
|
||||
{
|
||||
Cancel();
|
||||
}
|
||||
|
||||
void UGIS_AsyncAction_Wait::SetWorld(UWorld* NewWorld)
|
||||
{
|
||||
WorldPtr = NewWorld;
|
||||
}
|
||||
|
||||
void UGIS_AsyncAction_Wait::SetTargetActor(AActor* NewTargetActor)
|
||||
{
|
||||
TargetActorPtr = NewTargetActor;
|
||||
}
|
||||
|
||||
void UGIS_AsyncAction_Wait::SetWaitInterval(float NewWaitInterval)
|
||||
{
|
||||
WaitInterval = NewWaitInterval;
|
||||
}
|
||||
|
||||
void UGIS_AsyncAction_Wait::SetMaxWaitTimes(int32 NewMaxWaitTimes)
|
||||
{
|
||||
MaxWaitTimes = NewMaxWaitTimes;
|
||||
}
|
||||
|
||||
void UGIS_AsyncAction_Wait::OnTimer()
|
||||
{
|
||||
AActor* Actor = GetActor();
|
||||
if (!IsValid(Actor))
|
||||
{
|
||||
Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
OnExecutionAction();
|
||||
|
||||
if (MaxWaitTimes > 0)
|
||||
{
|
||||
WaitTimes++;
|
||||
if (WaitTimes > MaxWaitTimes)
|
||||
{
|
||||
Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGIS_AsyncAction_Wait::OnExecutionAction()
|
||||
{
|
||||
}
|
||||
|
||||
void UGIS_AsyncAction_Wait::Complete()
|
||||
{
|
||||
Super::Cancel();
|
||||
OnCompleted.Broadcast();
|
||||
Cleanup();
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Async/GIS_AsyncAction_WaitEquipmentSystem.h"
|
||||
|
||||
#include "GIS_EquipmentSystemComponent.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_AsyncAction_WaitEquipmentSystem)
|
||||
|
||||
UGIS_AsyncAction_WaitEquipmentSystem* UGIS_AsyncAction_WaitEquipmentSystem::WaitEquipmentSystem(UObject* WorldContext, AActor* TargetActor)
|
||||
{
|
||||
return CreateWaitAction<UGIS_AsyncAction_WaitEquipmentSystem>(WorldContext, TargetActor, 0.5, -1);
|
||||
}
|
||||
|
||||
void UGIS_AsyncAction_WaitEquipmentSystem::OnExecutionAction()
|
||||
{
|
||||
AActor* Actor = GetActor();
|
||||
|
||||
if (UGIS_EquipmentSystemComponent* EquipmentSystem = UGIS_EquipmentSystemComponent::GetEquipmentSystemComponent(Actor))
|
||||
{
|
||||
Complete();
|
||||
}
|
||||
}
|
||||
|
||||
UGIS_AsyncAction_WaitEquipmentSystem* UGIS_AsyncAction_WaitEquipmentSystemInitialized::WaitEquipmentSystemInitialized(UObject* WorldContext, AActor* TargetActor)
|
||||
{
|
||||
return CreateWaitAction<UGIS_AsyncAction_WaitEquipmentSystemInitialized>(WorldContext, TargetActor, 0.5, -1);
|
||||
}
|
||||
|
||||
void UGIS_AsyncAction_WaitEquipmentSystemInitialized::OnExecutionAction()
|
||||
{
|
||||
// Already found.
|
||||
UGIS_EquipmentSystemComponent* ExistingOne = EquipmentSystemPtr.IsValid() ? EquipmentSystemPtr.Get() : nullptr;
|
||||
if (IsValid(ExistingOne))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AActor* Actor = GetActor();
|
||||
if (UGIS_EquipmentSystemComponent* EquipmentSys = UGIS_EquipmentSystemComponent::GetEquipmentSystemComponent(Actor))
|
||||
{
|
||||
if (EquipmentSys->IsEquipmentSystemInitialized())
|
||||
{
|
||||
Complete();
|
||||
return;
|
||||
}
|
||||
EquipmentSystemPtr = EquipmentSys;
|
||||
EquipmentSystemPtr->OnEquipmentSystemInitializedEvent.AddDynamic(this, &ThisClass::OnSystemInitialized);
|
||||
}
|
||||
}
|
||||
|
||||
void UGIS_AsyncAction_WaitEquipmentSystemInitialized::Cleanup()
|
||||
{
|
||||
Super::Cleanup();
|
||||
UGIS_EquipmentSystemComponent* ExistingOne = EquipmentSystemPtr.IsValid() ? EquipmentSystemPtr.Get() : nullptr;
|
||||
|
||||
if (IsValid(ExistingOne))
|
||||
{
|
||||
ExistingOne->OnEquipmentSystemInitializedEvent.RemoveAll(this);
|
||||
}
|
||||
}
|
||||
|
||||
void UGIS_AsyncAction_WaitEquipmentSystemInitialized::OnSystemInitialized()
|
||||
{
|
||||
Complete();
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Async/GIS_AsyncAction_WaitInventorySystem.h"
|
||||
|
||||
#include "GIS_InventorySystemComponent.h"
|
||||
#include "GameFramework/PlayerState.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_AsyncAction_WaitInventorySystem)
|
||||
|
||||
UGIS_AsyncAction_WaitInventorySystem* UGIS_AsyncAction_WaitInventorySystem::WaitInventorySystem(UObject* WorldContext, AActor* TargetActor)
|
||||
{
|
||||
return CreateWaitAction<UGIS_AsyncAction_WaitInventorySystem>(WorldContext, TargetActor, 0.5, -1);
|
||||
}
|
||||
|
||||
void UGIS_AsyncAction_WaitInventorySystem::OnExecutionAction()
|
||||
{
|
||||
AActor* Actor = GetActor();
|
||||
|
||||
if (UGIS_InventorySystemComponent* Inventory = UGIS_InventorySystemComponent::GetInventorySystemComponent(Actor))
|
||||
{
|
||||
Complete();
|
||||
}
|
||||
}
|
||||
|
||||
UGIS_AsyncAction_WaitInventorySystem* UGIS_AsyncAction_WaitInventorySystemInitialized::WaitInventorySystemInitialized(UObject* WorldContext, AActor* TargetActor)
|
||||
{
|
||||
return CreateWaitAction<UGIS_AsyncAction_WaitInventorySystemInitialized>(WorldContext, TargetActor, 0.5, -1);
|
||||
}
|
||||
|
||||
void UGIS_AsyncAction_WaitInventorySystemInitialized::OnExecutionAction()
|
||||
{
|
||||
// Already found.
|
||||
UGIS_InventorySystemComponent* ExistingOne = InventorySysPtr.IsValid() ? InventorySysPtr.Get() : nullptr;
|
||||
if (IsValid(ExistingOne))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AActor* Actor = GetActor();
|
||||
if (UGIS_InventorySystemComponent* InventorySys = UGIS_InventorySystemComponent::GetInventorySystemComponent(Actor))
|
||||
{
|
||||
if (InventorySys->IsInventoryInitialized())
|
||||
{
|
||||
Complete();
|
||||
return;
|
||||
}
|
||||
InventorySysPtr = InventorySys;
|
||||
InventorySysPtr->OnInventorySystemInitializedEvent.AddDynamic(this, &ThisClass::OnSystemInitialized);
|
||||
}
|
||||
}
|
||||
|
||||
void UGIS_AsyncAction_WaitInventorySystemInitialized::Cleanup()
|
||||
{
|
||||
Super::Cleanup();
|
||||
UGIS_InventorySystemComponent* ExistingOne = InventorySysPtr.IsValid() ? InventorySysPtr.Get() : nullptr;
|
||||
if (IsValid(ExistingOne))
|
||||
{
|
||||
ExistingOne->OnInventorySystemInitializedEvent.RemoveAll(this);
|
||||
}
|
||||
}
|
||||
|
||||
void UGIS_AsyncAction_WaitInventorySystemInitialized::OnSystemInitialized()
|
||||
{
|
||||
Complete();
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Async/GIS_AsyncAction_WaitItemFragmentDataChanged.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "GIS_ItemFragment.h"
|
||||
#include "GIS_ItemInstance.h"
|
||||
#include "GIS_LogChannels.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_AsyncAction_WaitItemFragmentDataChanged)
|
||||
|
||||
UGIS_AsyncAction_WaitItemFragmentDataChanged* UGIS_AsyncAction_WaitItemFragmentDataChanged::WaitItemFragmentStateChanged(UObject* WorldContext, UGIS_ItemInstance* ItemInstance,
|
||||
TSoftClassPtr<UGIS_ItemFragment> FragmentClass)
|
||||
{
|
||||
if (!IsValid(WorldContext))
|
||||
{
|
||||
GIS_LOG(Warning, "invalid world context!")
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UWorld* World = GEngine->GetWorldFromContextObject(WorldContext, EGetWorldErrorMode::LogAndReturnNull);
|
||||
if (!IsValid(World))
|
||||
{
|
||||
GIS_LOG(Warning, "can't get world from context:%s", *GetNameSafe(WorldContext));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!IsValid(ItemInstance))
|
||||
{
|
||||
GIS_LOG(Warning, "invalid item instance");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TSubclassOf<UGIS_ItemFragment> Class = FragmentClass.LoadSynchronous();
|
||||
if (Class == nullptr)
|
||||
{
|
||||
GIS_LOG(Warning, "invalid fragment class");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UGIS_AsyncAction_WaitItemFragmentDataChanged* NewAction = NewObject<UGIS_AsyncAction_WaitItemFragmentDataChanged>(GetTransientPackage(), StaticClass());
|
||||
NewAction->ItemInstance = ItemInstance;
|
||||
NewAction->FragmentClass = Class;
|
||||
NewAction->RegisterWithGameInstance(World->GetGameInstance());
|
||||
return NewAction;
|
||||
}
|
||||
|
||||
void UGIS_AsyncAction_WaitItemFragmentDataChanged::Activate()
|
||||
{
|
||||
UGIS_ItemInstance* Item = ItemInstance.IsValid() ? ItemInstance.Get() : nullptr;
|
||||
if (IsValid(Item))
|
||||
{
|
||||
Item->OnFragmentStateAddedEvent.AddDynamic(this, &ThisClass::OnFragmentStateChanged);
|
||||
Item->OnFragmentStateUpdatedEvent.AddDynamic(this, &ThisClass::OnFragmentStateChanged);
|
||||
}
|
||||
}
|
||||
|
||||
void UGIS_AsyncAction_WaitItemFragmentDataChanged::Cancel()
|
||||
{
|
||||
Super::Cancel();
|
||||
UGIS_ItemInstance* Item = ItemInstance.IsValid() ? ItemInstance.Get() : nullptr;
|
||||
if (IsValid(Item))
|
||||
{
|
||||
Item->OnFragmentStateAddedEvent.RemoveAll(this);
|
||||
Item->OnFragmentStateUpdatedEvent.RemoveAll(this);
|
||||
ItemInstance.Reset();
|
||||
FragmentClass = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void UGIS_AsyncAction_WaitItemFragmentDataChanged::OnFragmentStateChanged(const UGIS_ItemFragment* Fragment, const FInstancedStruct& State)
|
||||
{
|
||||
if (ShouldBroadcastDelegates())
|
||||
{
|
||||
if (Fragment != nullptr && Fragment->GetClass() == FragmentClass)
|
||||
{
|
||||
OnStateChanged.Broadcast(Fragment, State);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "Attributes/GIS_GameplayTagFloat.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_GameplayTagFloat)
|
||||
|
||||
|
||||
FString FGIS_GameplayTagFloat::GetDebugString() const
|
||||
{
|
||||
return FString::Printf(TEXT("%sx%f"), *Tag.ToString(), Value);
|
||||
}
|
||||
|
||||
void FGIS_GameplayTagFloatContainer::AddItem(FGameplayTag Tag, float Value)
|
||||
{
|
||||
if (!Tag.IsValid())
|
||||
{
|
||||
FFrame::KismetExecutionMessage(TEXT("An invalid tag was passed to AddItem"), ELogVerbosity::Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Value > 0)
|
||||
{
|
||||
for (FGIS_GameplayTagFloat& Item : Items)
|
||||
{
|
||||
// handle adding to existing value.
|
||||
if (Item.Tag == Tag)
|
||||
{
|
||||
const float OldValue = Item.Value;
|
||||
const float NewValue = Item.Value + Value;
|
||||
Item.Value = NewValue;
|
||||
TagToValueMap[Tag] = NewValue;
|
||||
MarkItemDirty(Item);
|
||||
if (IGIS_GameplayTagFloatContainerOwner* Interface = Cast<IGIS_GameplayTagFloatContainerOwner>(ContainerOwner))
|
||||
{
|
||||
Interface->OnTagFloatUpdate(Tag, OldValue, NewValue);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// handle adding new item.
|
||||
FGIS_GameplayTagFloat& NewItem = Items.Emplace_GetRef(Tag, Value);
|
||||
TagToValueMap.Add(Tag, Value);
|
||||
MarkItemDirty(NewItem);
|
||||
if (IGIS_GameplayTagFloatContainerOwner* Interface = Cast<IGIS_GameplayTagFloatContainerOwner>(ContainerOwner))
|
||||
{
|
||||
Interface->OnTagFloatUpdate(Tag, 0, Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FGIS_GameplayTagFloatContainer::SetItem(FGameplayTag Tag, float Value)
|
||||
{
|
||||
if (!Tag.IsValid())
|
||||
{
|
||||
FFrame::KismetExecutionMessage(TEXT("An invalid tag was passed to SetItem"), ELogVerbosity::Warning);
|
||||
return;
|
||||
}
|
||||
for (FGIS_GameplayTagFloat& Item : Items)
|
||||
{
|
||||
if (Item.Tag == Tag)
|
||||
{
|
||||
const float OldValue = Item.Value;
|
||||
Item.Value = Value;
|
||||
TagToValueMap[Tag] = Value;
|
||||
MarkItemDirty(Item);
|
||||
if (IGIS_GameplayTagFloatContainerOwner* Interface = Cast<IGIS_GameplayTagFloatContainerOwner>(ContainerOwner))
|
||||
{
|
||||
Interface->OnTagFloatUpdate(Tag, OldValue, Value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
FGIS_GameplayTagFloat& NewItem = Items.Emplace_GetRef(Tag, Value);
|
||||
MarkItemDirty(NewItem);
|
||||
TagToValueMap.Add(Tag, Value);
|
||||
if (IGIS_GameplayTagFloatContainerOwner* Interface = Cast<IGIS_GameplayTagFloatContainerOwner>(ContainerOwner))
|
||||
{
|
||||
Interface->OnTagFloatUpdate(Tag, 0, Value);
|
||||
}
|
||||
}
|
||||
|
||||
void FGIS_GameplayTagFloatContainer::SetItems(const TArray<FGIS_GameplayTagFloat>& NewItems)
|
||||
{
|
||||
Items = NewItems;
|
||||
TagToValueMap.Empty();
|
||||
for (const FGIS_GameplayTagFloat& NewItem : NewItems)
|
||||
{
|
||||
TagToValueMap.Add(NewItem.Tag, NewItem.Value);
|
||||
}
|
||||
MarkArrayDirty();
|
||||
}
|
||||
|
||||
void FGIS_GameplayTagFloatContainer::EmptyItems()
|
||||
{
|
||||
Items.Empty();
|
||||
TagToValueMap.Empty();
|
||||
MarkArrayDirty();
|
||||
}
|
||||
|
||||
void FGIS_GameplayTagFloatContainer::RemoveItem(FGameplayTag Tag, float Value)
|
||||
{
|
||||
if (!Tag.IsValid())
|
||||
{
|
||||
FFrame::KismetExecutionMessage(TEXT("An invalid tag was passed to RemoveItem"), ELogVerbosity::Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
//@TODO: Should we error if you try to remove a Item that doesn't exist or has a smaller count?
|
||||
if (Value > 0)
|
||||
{
|
||||
for (auto It = Items.CreateIterator(); It; ++It)
|
||||
{
|
||||
FGIS_GameplayTagFloat& Item = *It;
|
||||
if (Item.Tag == Tag)
|
||||
{
|
||||
if (Item.Value <= Value)
|
||||
{
|
||||
const float OldValue = Item.Value;
|
||||
It.RemoveCurrent();
|
||||
TagToValueMap.Remove(Tag);
|
||||
MarkArrayDirty();
|
||||
if (IGIS_GameplayTagFloatContainerOwner* Interface = Cast<IGIS_GameplayTagFloatContainerOwner>(ContainerOwner))
|
||||
{
|
||||
Interface->OnTagFloatUpdate(Tag, OldValue, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const float OldValue = Item.Value;
|
||||
const float NewValue = Item.Value - Value;
|
||||
Item.Value = NewValue;
|
||||
TagToValueMap[Tag] = NewValue;
|
||||
MarkItemDirty(Item);
|
||||
if (IGIS_GameplayTagFloatContainerOwner* Interface = Cast<IGIS_GameplayTagFloatContainerOwner>(ContainerOwner))
|
||||
{
|
||||
Interface->OnTagFloatUpdate(Tag, OldValue, NewValue);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FGIS_GameplayTagFloatContainer::PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize)
|
||||
{
|
||||
for (int32 Index : RemovedIndices)
|
||||
{
|
||||
FGIS_GameplayTagFloat& Item = Items[Index];
|
||||
TagToValueMap.Remove(Item.Tag);
|
||||
if (IGIS_GameplayTagFloatContainerOwner* Interface = Cast<IGIS_GameplayTagFloatContainerOwner>(ContainerOwner))
|
||||
{
|
||||
Interface->OnTagFloatUpdate(Item.Tag, Item.PrevValue, 0);
|
||||
}
|
||||
Item.PrevValue = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void FGIS_GameplayTagFloatContainer::PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize)
|
||||
{
|
||||
for (int32 Index : AddedIndices)
|
||||
{
|
||||
FGIS_GameplayTagFloat& Item = Items[Index];
|
||||
TagToValueMap.Add(Item.Tag, Item.Value);
|
||||
if (IGIS_GameplayTagFloatContainerOwner* Interface = Cast<IGIS_GameplayTagFloatContainerOwner>(ContainerOwner))
|
||||
{
|
||||
Interface->OnTagFloatUpdate(Item.Tag, 0, Item.Value);
|
||||
}
|
||||
Item.PrevValue = Item.Value;
|
||||
}
|
||||
}
|
||||
|
||||
void FGIS_GameplayTagFloatContainer::PostReplicatedChange(const TArrayView<int32> ChangedIndices, int32 FinalSize)
|
||||
{
|
||||
for (int32 Index : ChangedIndices)
|
||||
{
|
||||
FGIS_GameplayTagFloat& Item = Items[Index];
|
||||
TagToValueMap[Item.Tag] = Item.Value;
|
||||
if (IGIS_GameplayTagFloatContainerOwner* Interface = Cast<IGIS_GameplayTagFloatContainerOwner>(ContainerOwner))
|
||||
{
|
||||
Interface->OnTagFloatUpdate(Item.Tag, Item.PrevValue, Item.Value);
|
||||
}
|
||||
Item.PrevValue = Item.Value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "Attributes/GIS_GameplayTagInteger.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_GameplayTagInteger)
|
||||
|
||||
FString FGIS_GameplayTagInteger::GetDebugString() const
|
||||
{
|
||||
return FString::Printf(TEXT("%sx%d"), *Tag.ToString(), Value);
|
||||
}
|
||||
|
||||
void FGIS_GameplayTagIntegerContainer::AddItem(FGameplayTag Tag, int32 Value)
|
||||
{
|
||||
if (!Tag.IsValid())
|
||||
{
|
||||
FFrame::KismetExecutionMessage(TEXT("An invalid tag was passed to AddItem"), ELogVerbosity::Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Value > 0)
|
||||
{
|
||||
for (FGIS_GameplayTagInteger& Item : Items)
|
||||
{
|
||||
if (Item.Tag == Tag)
|
||||
{
|
||||
const int32 OldValue = Item.Value;
|
||||
const int32 NewValue = Item.Value + Value;
|
||||
Item.Value = NewValue;
|
||||
TagToValueMap[Tag] = NewValue;
|
||||
MarkItemDirty(Item);
|
||||
if (IGIS_GameplayTagIntegerContainerOwner* Interface = Cast<IGIS_GameplayTagIntegerContainerOwner>(ContainerOwner))
|
||||
{
|
||||
Interface->OnTagIntegerUpdate(Tag, OldValue, NewValue);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
FGIS_GameplayTagInteger& NewItem = Items.Emplace_GetRef(Tag, Value);
|
||||
TagToValueMap.Add(Tag, Value);
|
||||
MarkItemDirty(NewItem);
|
||||
if (IGIS_GameplayTagIntegerContainerOwner* Interface = Cast<IGIS_GameplayTagIntegerContainerOwner>(ContainerOwner))
|
||||
{
|
||||
Interface->OnTagIntegerUpdate(Tag, 0, Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FGIS_GameplayTagIntegerContainer::SetItem(FGameplayTag Tag, int32 Value)
|
||||
{
|
||||
if (!Tag.IsValid())
|
||||
{
|
||||
FFrame::KismetExecutionMessage(TEXT("An invalid tag was passed to SetItem"), ELogVerbosity::Warning);
|
||||
return;
|
||||
}
|
||||
for (FGIS_GameplayTagInteger& Item : Items)
|
||||
{
|
||||
if (Item.Tag == Tag)
|
||||
{
|
||||
int32 OldValue = Item.Value;
|
||||
Item.Value = Value;
|
||||
TagToValueMap[Tag] = Value;
|
||||
MarkItemDirty(Item);
|
||||
if (IGIS_GameplayTagIntegerContainerOwner* Interface = Cast<IGIS_GameplayTagIntegerContainerOwner>(ContainerOwner))
|
||||
{
|
||||
Interface->OnTagIntegerUpdate(Tag, OldValue, Value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
FGIS_GameplayTagInteger& NewItem = Items.Emplace_GetRef(Tag, Value);
|
||||
MarkItemDirty(NewItem);
|
||||
TagToValueMap.Add(Tag, Value);
|
||||
if (IGIS_GameplayTagIntegerContainerOwner* Interface = Cast<IGIS_GameplayTagIntegerContainerOwner>(ContainerOwner))
|
||||
{
|
||||
Interface->OnTagIntegerUpdate(Tag, 0, Value);
|
||||
}
|
||||
}
|
||||
|
||||
void FGIS_GameplayTagIntegerContainer::RemoveItem(FGameplayTag Tag, int32 Value)
|
||||
{
|
||||
if (!Tag.IsValid())
|
||||
{
|
||||
FFrame::KismetExecutionMessage(TEXT("An invalid tag was passed to RemoveItem"), ELogVerbosity::Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
//@TODO: Should we error if you try to remove a Item that doesn't exist or has a smaller count?
|
||||
if (Value > 0)
|
||||
{
|
||||
for (auto It = Items.CreateIterator(); It; ++It)
|
||||
{
|
||||
FGIS_GameplayTagInteger& Item = *It;
|
||||
if (Item.Tag == Tag)
|
||||
{
|
||||
if (Item.Value <= Value)
|
||||
{
|
||||
const int32 OldValue = Item.Value;
|
||||
It.RemoveCurrent();
|
||||
TagToValueMap.Remove(Tag);
|
||||
MarkArrayDirty();
|
||||
if (IGIS_GameplayTagIntegerContainerOwner* Interface = Cast<IGIS_GameplayTagIntegerContainerOwner>(ContainerOwner))
|
||||
{
|
||||
Interface->OnTagIntegerUpdate(Tag, OldValue, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const int32 OldValue = Item.Value;
|
||||
const int32 NewValue = Item.Value - Value;
|
||||
Item.Value = NewValue;
|
||||
TagToValueMap[Tag] = NewValue;
|
||||
MarkItemDirty(Item);
|
||||
if (IGIS_GameplayTagIntegerContainerOwner* Interface = Cast<IGIS_GameplayTagIntegerContainerOwner>(ContainerOwner))
|
||||
{
|
||||
Interface->OnTagIntegerUpdate(Tag, OldValue, NewValue);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FGIS_GameplayTagIntegerContainer::PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize)
|
||||
{
|
||||
for (int32 Index : RemovedIndices)
|
||||
{
|
||||
FGIS_GameplayTagInteger& Item = Items[Index];
|
||||
|
||||
TagToValueMap.Remove(Item.Tag);
|
||||
if (IGIS_GameplayTagIntegerContainerOwner* Interface = Cast<IGIS_GameplayTagIntegerContainerOwner>(ContainerOwner))
|
||||
{
|
||||
Interface->OnTagIntegerUpdate(Item.Tag, Item.PrevValue, 0);
|
||||
}
|
||||
Item.PrevValue = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void FGIS_GameplayTagIntegerContainer::PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize)
|
||||
{
|
||||
for (int32 Index : AddedIndices)
|
||||
{
|
||||
FGIS_GameplayTagInteger& Item = Items[Index];
|
||||
TagToValueMap.Add(Item.Tag, Item.Value);
|
||||
if (IGIS_GameplayTagIntegerContainerOwner* Interface = Cast<IGIS_GameplayTagIntegerContainerOwner>(ContainerOwner))
|
||||
{
|
||||
Interface->OnTagIntegerUpdate(Item.Tag, 0, Item.Value);
|
||||
}
|
||||
Item.PrevValue = Item.Value;
|
||||
}
|
||||
}
|
||||
|
||||
void FGIS_GameplayTagIntegerContainer::PostReplicatedChange(const TArrayView<int32> ChangedIndices, int32 FinalSize)
|
||||
{
|
||||
for (int32 Index : ChangedIndices)
|
||||
{
|
||||
FGIS_GameplayTagInteger& Item = Items[Index];
|
||||
TagToValueMap[Item.Tag] = Item.Value;
|
||||
if (IGIS_GameplayTagIntegerContainerOwner* Interface = Cast<IGIS_GameplayTagIntegerContainerOwner>(ContainerOwner))
|
||||
{
|
||||
Interface->OnTagIntegerUpdate(Item.Tag, Item.PrevValue, Item.Value);
|
||||
}
|
||||
Item.PrevValue = Item.Value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "GIS_CollectionContainer.h"
|
||||
#include "GIS_InventorySystemComponent.h"
|
||||
#include "GIS_ItemCollection.h"
|
||||
#include "GIS_LogChannels.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_CollectionContainer)
|
||||
|
||||
|
||||
bool FGIS_CollectionEntry::IsValidEntry() const
|
||||
{
|
||||
return Id.IsValid() && IsValid(Instance) && IsValid(Definition);
|
||||
}
|
||||
|
||||
FGIS_CollectionContainer::FGIS_CollectionContainer(UGIS_InventorySystemComponent* InInventory)
|
||||
{
|
||||
OwningComponent = InInventory;
|
||||
}
|
||||
|
||||
void FGIS_CollectionContainer::PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize)
|
||||
{
|
||||
for (int32 Index : RemovedIndices)
|
||||
{
|
||||
const FGIS_CollectionEntry& Entry = Entries[Index];
|
||||
|
||||
// already in the list.
|
||||
if (Entry.IsValidEntry() && OwningComponent->CollectionIdToInstanceMap.Contains(Entry.Instance->GetCollectionId()))
|
||||
{
|
||||
OwningComponent->OnCollectionRemoved(Entry);
|
||||
}
|
||||
else if (OwningComponent->PendingCollections.Contains(Entry.Id))
|
||||
{
|
||||
GIS_OWNED_CLOG(OwningComponent, Warning, "Discard pending collection(%s).", *GetNameSafe(OwningComponent->PendingCollections[Entry.Id].Definition))
|
||||
OwningComponent->PendingCollections.Remove(Entry.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FGIS_CollectionContainer::PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize)
|
||||
{
|
||||
for (int32 Index : AddedIndices)
|
||||
{
|
||||
const FGIS_CollectionEntry& Entry = Entries[Index];
|
||||
|
||||
if (OwningComponent->GetOwner() && Entry.IsValidEntry())
|
||||
{
|
||||
OwningComponent->OnCollectionAdded(Entry);
|
||||
}
|
||||
else
|
||||
{
|
||||
OwningComponent->PendingCollections.Add(Entry.Id, Entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FGIS_CollectionContainer::PostReplicatedChange(const TArrayView<int32> ChangedIndices, int32 FinalSize)
|
||||
{
|
||||
for (int32 Index : ChangedIndices)
|
||||
{
|
||||
const FGIS_CollectionEntry& Entry = Entries[Index];
|
||||
|
||||
if (Entry.IsValidEntry() && OwningComponent->CollectionIdToInstanceMap.Contains(Entry.Instance->GetCollectionId())) //Already Added.
|
||||
{
|
||||
OwningComponent->OnCollectionUpdated(Entry);
|
||||
}
|
||||
else if (OwningComponent->PendingCollections.Contains(Entry.Id)) //In pending list.
|
||||
{
|
||||
OwningComponent->PendingCollections.Emplace(Entry.Id, Entry); // Updated to pending.
|
||||
}
|
||||
else
|
||||
{
|
||||
OwningComponent->PendingCollections.Add(Entry.Id, Entry); //Add to pending list.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,861 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GIS_ItemCollection.h"
|
||||
#include "GIS_InventorySystemComponent.h"
|
||||
#include "GIS_InventoryMeesages.h"
|
||||
#include "GIS_InventorySubsystem.h"
|
||||
#include "GIS_InventoryTags.h"
|
||||
#include "Items/GIS_ItemInstance.h"
|
||||
#include "Items/GIS_ItemDefinition.h"
|
||||
#include "Engine/ActorChannel.h"
|
||||
#include "Net/UnrealNetwork.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "Engine/NetDriver.h"
|
||||
#include "Engine/BlueprintGeneratedClass.h"
|
||||
#include "GIS_ItemRestriction.h"
|
||||
#include "GIS_LogChannels.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_ItemCollection)
|
||||
|
||||
bool UGIS_ItemCollectionDefinition::IsSupportedForNetworking() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
TSubclassOf<UGIS_ItemCollection> UGIS_ItemCollectionDefinition::GetCollectionInstanceClass() const
|
||||
{
|
||||
return UGIS_ItemCollection::StaticClass();
|
||||
}
|
||||
|
||||
UGIS_ItemCollection::GIS_CollectionNotifyLocker::GIS_CollectionNotifyLocker(UGIS_ItemCollection& InItemCollection): ItemCollection(InItemCollection)
|
||||
{
|
||||
ItemCollection.NotifyLocker++;
|
||||
}
|
||||
|
||||
UGIS_ItemCollection::GIS_CollectionNotifyLocker::~GIS_CollectionNotifyLocker()
|
||||
{
|
||||
ItemCollection.NotifyLocker--;
|
||||
}
|
||||
|
||||
UGIS_ItemCollection::UGIS_ItemCollection(const FObjectInitializer& ObjectInitializer): Super(ObjectInitializer), Container(this)
|
||||
{
|
||||
}
|
||||
|
||||
void UGIS_ItemCollection::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||
{
|
||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||
|
||||
DOREPLIFETIME(ThisClass, Container);
|
||||
|
||||
//fix: https://forums.unrealengine.com/t/subobject-replication-for-blueprint-child-class/106205/4
|
||||
UBlueprintGeneratedClass* bpClass = Cast<UBlueprintGeneratedClass>(this->GetClass());
|
||||
if (bpClass != nullptr)
|
||||
{
|
||||
bpClass->GetLifetimeBlueprintReplicationList(OutLifetimeProps);
|
||||
}
|
||||
}
|
||||
|
||||
bool UGIS_ItemCollection::CallRemoteFunction(UFunction* Function, void* Parms, FOutParmRec* OutParms, FFrame* Stack)
|
||||
{
|
||||
check(!HasAnyFlags(RF_ClassDefaultObject));
|
||||
check(GetOuter() != nullptr);
|
||||
|
||||
AActor* Owner = CastChecked<AActor>(GetOuter());
|
||||
|
||||
bool bProcessed = false;
|
||||
|
||||
FWorldContext* const Context = GEngine->GetWorldContextFromWorld(GetWorld());
|
||||
if (Context != nullptr)
|
||||
{
|
||||
for (FNamedNetDriver& Driver : Context->ActiveNetDrivers)
|
||||
{
|
||||
if (Driver.NetDriver != nullptr && Driver.NetDriver->ShouldReplicateFunction(Owner, Function))
|
||||
{
|
||||
Driver.NetDriver->ProcessRemoteFunction(Owner, Function, Parms, OutParms, Stack, this);
|
||||
bProcessed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bProcessed;
|
||||
}
|
||||
|
||||
int32 UGIS_ItemCollection::GetFunctionCallspace(UFunction* Function, FFrame* Stack)
|
||||
{
|
||||
if (HasAnyFlags(RF_ClassDefaultObject) || !IsSupportedForNetworking())
|
||||
{
|
||||
// This handles absorbing authority/cosmetic
|
||||
return GEngine->GetGlobalFunctionCallspace(Function, this, Stack);
|
||||
}
|
||||
check(GetOuter() != nullptr);
|
||||
return GetOuter()->GetFunctionCallspace(Function, Stack);
|
||||
}
|
||||
|
||||
bool UGIS_ItemCollection::IsInitialized() const
|
||||
{
|
||||
return bInitialized;
|
||||
}
|
||||
|
||||
FString UGIS_ItemCollection::GetCollectionName() const
|
||||
{
|
||||
if (Definition)
|
||||
{
|
||||
if (Definition->CollectionTag.IsValid())
|
||||
{
|
||||
TArray<FName> TagNames;
|
||||
UGameplayTagsManager::Get().SplitGameplayTagFName(Definition->CollectionTag, TagNames);
|
||||
if (!TagNames.IsEmpty())
|
||||
{
|
||||
return FString::Format(TEXT("{0} Collection"), {TagNames.Last().ToString()});
|
||||
}
|
||||
}
|
||||
return FString::Format(TEXT("{0}"), {GetNameSafe(this)});
|
||||
}
|
||||
return TEXT("Invalid Collection!!!");
|
||||
}
|
||||
|
||||
FString UGIS_ItemCollection::GetDebugString() const
|
||||
{
|
||||
return FString::Format(TEXT("{0}({1})"), {GetCollectionName(), GetNameSafe(OwningInventory->GetOwner())});
|
||||
}
|
||||
|
||||
bool UGIS_ItemCollection::HasItem(const UGIS_ItemInstance* Item, int32 Amount, bool SimilarItem) const
|
||||
{
|
||||
if (Item == nullptr) { return false; }
|
||||
|
||||
return GetItemAmount(Item, SimilarItem) >= Amount;
|
||||
}
|
||||
|
||||
FGIS_ItemInfo UGIS_ItemCollection::AddItem(const FGIS_ItemInfo& ItemInfo)
|
||||
{
|
||||
//The actually added item info; 实际添加的道具信息
|
||||
FGIS_ItemInfo ItemInfoAdded = FGIS_ItemInfo(ItemInfo.Item, 0, this);
|
||||
|
||||
FGIS_ItemInfo CanAddItemInfo;
|
||||
if (CanAddItem(ItemInfo, CanAddItemInfo))
|
||||
{
|
||||
//非唯一道具或数量为一,直接加.
|
||||
if (!CanAddItemInfo.Item->IsUnique() || CanAddItemInfo.Amount <= 1)
|
||||
{
|
||||
//要添加的信息
|
||||
FGIS_ItemInfo ItemInfoToAdd(CanAddItemInfo.Item, CanAddItemInfo.Amount, ItemInfo);
|
||||
ItemInfoAdded = AddInternal(ItemInfoToAdd);
|
||||
}
|
||||
else //unique item can stack.
|
||||
{
|
||||
//先加1个
|
||||
FGIS_ItemInfo OriginalResult = AddItem(FGIS_ItemInfo(CanAddItemInfo.Item, 1, ItemInfo));
|
||||
// 遍历加入数量为1的道具, 每个都有不同的GUID
|
||||
for (int32 i = 1; i < CanAddItemInfo.Amount; i++)
|
||||
{
|
||||
UGIS_ItemInstance* DuplicatedItem = UGIS_InventorySubsystem::Get(GetWorld())->DuplicateItem(OwningInventory->GetOwner(), CanAddItemInfo.Item);
|
||||
check(DuplicatedItem && DuplicatedItem->IsItemValid())
|
||||
AddItem(FGIS_ItemInfo(DuplicatedItem, 1, ItemInfo));
|
||||
}
|
||||
ItemInfoAdded = FGIS_ItemInfo(CanAddItemInfo.Amount, OriginalResult);
|
||||
}
|
||||
}
|
||||
|
||||
// overflow
|
||||
if (ItemInfoAdded.Amount < ItemInfo.Amount)
|
||||
{
|
||||
HandleItemOverflow(ItemInfo, ItemInfoAdded);
|
||||
}
|
||||
|
||||
return ItemInfoAdded;
|
||||
}
|
||||
|
||||
int32 UGIS_ItemCollection::AddItems(const TArray<FGIS_ItemInfo>& ItemInfos)
|
||||
{
|
||||
int32 TotalAdded = 0;
|
||||
for (int32 i = 0; i < ItemInfos.Num(); i++)
|
||||
{
|
||||
TotalAdded += AddItem(ItemInfos[i]).Amount;
|
||||
}
|
||||
return TotalAdded;
|
||||
}
|
||||
|
||||
FGIS_ItemInfo UGIS_ItemCollection::AddItem(UGIS_ItemInstance* Item, int32 Amount)
|
||||
{
|
||||
return AddItem(FGIS_ItemInfo(Item, Amount));
|
||||
}
|
||||
|
||||
bool UGIS_ItemCollection::CanAddItem(const FGIS_ItemInfo& Input, FGIS_ItemInfo& Output)
|
||||
{
|
||||
if (!bInitialized || Input.Item == nullptr || !Input.Item->IsItemValid() || Input.Amount < 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FGIS_ItemInfo ModifiedInput = Input;
|
||||
|
||||
/*
|
||||
* Documentation: For none-unique item(means stackable), if you want to add 2 apples with id x, there are following case:
|
||||
* 1.There are already 10 apples with id x in your inventory,this will turn your "add 2 apples of id x" request into "add 2 apples of id y", resulting 12 apples with id y in your inventory.
|
||||
* 2.There's no any apples in your inventory, resulting in 2 apples with id x in your inventory.
|
||||
* 文档: 道具不唯一. 举例: 你要加2个苹果(ID是X)到库存,且苹果定义上它不是唯一的(即可叠加)。
|
||||
* 如果1:库存里如果已经有10个苹果(ID是Y)。就把 “你要加2个ID为X的苹果” 变成“你要加2个ID为Y的苹果”.
|
||||
* 如果2:库存里如果没有任何苹果。那么就不变,其结果就是往库存里加2个ID是X的苹果.
|
||||
*/
|
||||
if (!ModifiedInput.Item->IsUnique())
|
||||
{
|
||||
FGIS_ItemInfo SimilarItemInfo;
|
||||
if (GetItemInfo(ModifiedInput.Item, SimilarItemInfo))
|
||||
{
|
||||
//修改要加入的道具信息为相似道具、但保留Amount.
|
||||
ModifiedInput = FGIS_ItemInfo(Input.Amount, SimilarItemInfo);
|
||||
}
|
||||
}
|
||||
|
||||
if (ModifiedInput.Item->GetOwningCollection() != nullptr && ModifiedInput.Item->GetOwningCollection() != this)
|
||||
{
|
||||
UGIS_ItemInstance* DuplicatedItem = UGIS_InventorySubsystem::Get(GetWorld())->DuplicateItem(OwningInventory->GetOwner(), ModifiedInput.Item);
|
||||
check(DuplicatedItem && DuplicatedItem->IsItemValid())
|
||||
GIS_LOG(Verbose, "The Item(%s) was duplicated, and the duplicated one was added because the item is already part of another collection on actor %s.", *Input.GetDebugString(),
|
||||
*GetNameSafe(Input.Item->GetOuter()))
|
||||
ModifiedInput = FGIS_ItemInfo(DuplicatedItem, Input.Amount, Input);
|
||||
}
|
||||
|
||||
if (ModifiedInput.Item->GetOuter() != nullptr && ModifiedInput.Item->GetOuter() != OwningInventory->GetOwner())
|
||||
{
|
||||
UGIS_ItemInstance* DuplicatedItem = UGIS_InventorySubsystem::Get(GetWorld())->DuplicateItem(OwningInventory->GetOwner(), ModifiedInput.Item, false);
|
||||
check(DuplicatedItem && DuplicatedItem->IsItemValid())
|
||||
GIS_LOG(Verbose, "The Item(%s) was duplicated, and the duplicated one was added because the item was created by another actor %s.", *Input.GetDebugString(),
|
||||
*GetNameSafe(Input.Item->GetOuter()))
|
||||
ModifiedInput = FGIS_ItemInfo(DuplicatedItem, Input.Amount, Input);
|
||||
}
|
||||
|
||||
for (int32 i = 0; i < Definition->Restrictions.Num(); i++)
|
||||
{
|
||||
auto Restriction = Definition->Restrictions[i];
|
||||
if (Restriction && !Restriction->CanAddItem(ModifiedInput, this))
|
||||
{
|
||||
GIS_CLOG(Warning, "can't add item(%s) due to restriction:%s", *Input.GetDebugString(), *Restriction->GetClass()->GetName())
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Output = ModifiedInput;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UGIS_ItemCollection::CanItemStack(const FGIS_ItemInfo& ItemInfo, const FGIS_ItemStack& ItemStack) const
|
||||
{
|
||||
return !ItemInfo.Item->IsUnique() && ItemStack.Item->StackableEquivalentTo(ItemInfo.Item);
|
||||
}
|
||||
|
||||
bool UGIS_ItemCollection::RemoveItemCondition(const FGIS_ItemInfo& ItemInfo, FGIS_ItemInfo& OutItemInfo)
|
||||
{
|
||||
if (!ItemInfo.Item || !ItemInfo.Item->IsItemValid() || ItemInfo.Amount < 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
OutItemInfo = ItemInfo;
|
||||
|
||||
if (!ItemInfo.Item->IsUnique())
|
||||
{
|
||||
FGIS_ItemInfo SimilarItemInfo;
|
||||
if (GetItemInfo(ItemInfo.Item, SimilarItemInfo))
|
||||
{
|
||||
OutItemInfo = FGIS_ItemInfo(SimilarItemInfo.Item, ItemInfo.Amount);
|
||||
}
|
||||
}
|
||||
|
||||
// make sure is this collection.
|
||||
if (OutItemInfo.ItemCollection != this)
|
||||
{
|
||||
OutItemInfo.ItemCollection = this;
|
||||
}
|
||||
|
||||
for (int32 i = 0; i < Definition->Restrictions.Num(); i++)
|
||||
{
|
||||
// Check against src item info in restriction.
|
||||
if (!Definition->Restrictions[i]->CanRemoveItem(OutItemInfo))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FGIS_ItemInfo UGIS_ItemCollection::RemoveItem(const FGIS_ItemInfo& ItemInfo)
|
||||
{
|
||||
FGIS_ItemInfo ItemInfoToRemove;
|
||||
if (!RemoveItemCondition(ItemInfo, ItemInfoToRemove))
|
||||
{
|
||||
return FGIS_ItemInfo(ItemInfo.Item, 0, this);
|
||||
}
|
||||
return RemoveInternal(ItemInfoToRemove);
|
||||
}
|
||||
|
||||
void UGIS_ItemCollection::RemoveAll()
|
||||
{
|
||||
for (int32 i = Container.Stacks.Num() - 1; i >= 0; i--)
|
||||
{
|
||||
FGIS_ItemStack& ItemStack = Container.Stacks[i];
|
||||
RemoveItem(FGIS_ItemInfo(ItemStack.Item, ItemStack.Amount));
|
||||
}
|
||||
}
|
||||
|
||||
bool UGIS_ItemCollection::GetItemInfoByStackId(FGuid InStackId, FGIS_ItemInfo& OutItemInfo) const
|
||||
{
|
||||
if (const FGIS_ItemStack* Stack = Container.Stacks.FindByKey(InStackId))
|
||||
{
|
||||
OutItemInfo = *Stack;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UGIS_ItemCollection::FindItemInfoByStackId(FGuid InStackId, FGIS_ItemInfo& OutItemInfo) const
|
||||
{
|
||||
return GetItemInfoByStackId(InStackId, OutItemInfo);
|
||||
}
|
||||
|
||||
bool UGIS_ItemCollection::GetItemInfo(const UGIS_ItemInstance* Item, FGIS_ItemInfo& OutItemInfo) const
|
||||
{
|
||||
if (Item == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool bFoundSimilar = false;
|
||||
FGIS_ItemInfo SimilarItemInfo;
|
||||
for (int32 i = 0; i < Container.Stacks.Num(); i++)
|
||||
{
|
||||
const FGIS_ItemStack& ItemStack = Container.Stacks[i];
|
||||
//Is not unique but has same definition.
|
||||
if (!Item->IsUnique() && ItemStack.Item->GetDefinition() == Item->GetDefinition())
|
||||
{
|
||||
SimilarItemInfo = FGIS_ItemInfo(ItemStack.Item, ItemStack.Amount, ItemStack.Collection);
|
||||
bFoundSimilar = true;
|
||||
}
|
||||
|
||||
if (ItemStack.Item->GetItemId() != Item->GetItemId())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//Found unique item with same id.
|
||||
OutItemInfo = FGIS_ItemInfo(ItemStack.Item, ItemStack.Amount, ItemStack.Collection);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (bFoundSimilar)
|
||||
{
|
||||
OutItemInfo = SimilarItemInfo;
|
||||
}
|
||||
return bFoundSimilar;
|
||||
}
|
||||
|
||||
bool UGIS_ItemCollection::GetItemInfoByDefinition(const TSoftObjectPtr<UGIS_ItemDefinition>& ItemDefinition, FGIS_ItemInfo& OutItemInfo)
|
||||
{
|
||||
if (ItemDefinition.IsNull())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UGIS_ItemDefinition* LoadedItemDefinition = ItemDefinition.LoadSynchronous();
|
||||
|
||||
if (LoadedItemDefinition == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool bFoundSimilar = false;
|
||||
FGIS_ItemInfo SimilarItemInfo;
|
||||
for (int32 i = 0; i < Container.Stacks.Num(); i++)
|
||||
{
|
||||
FGIS_ItemStack& ItemStack = Container.Stacks[i];
|
||||
if (ItemStack.Item->GetDefinition() == LoadedItemDefinition)
|
||||
{
|
||||
SimilarItemInfo = FGIS_ItemInfo(ItemStack.Item, ItemStack.Amount, ItemStack.Collection);
|
||||
bFoundSimilar = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (bFoundSimilar && SimilarItemInfo.IsValid())
|
||||
{
|
||||
OutItemInfo = SimilarItemInfo;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UGIS_ItemCollection::GetItemInfosByDefinition(const TSoftObjectPtr<UGIS_ItemDefinition>& ItemDefinition, TArray<FGIS_ItemInfo>& OutItemInfos)
|
||||
{
|
||||
if (ItemDefinition.IsNull())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UGIS_ItemDefinition* LoadedItemDefinition = ItemDefinition.LoadSynchronous();
|
||||
|
||||
if (LoadedItemDefinition == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int32 i = 0; i < Container.Stacks.Num(); i++)
|
||||
{
|
||||
FGIS_ItemStack& ItemStack = Container.Stacks[i];
|
||||
if (ItemStack.Item->GetDefinition() == LoadedItemDefinition)
|
||||
{
|
||||
FGIS_ItemInfo SimilarItemInfo = FGIS_ItemInfo(ItemStack.Item, ItemStack.Amount, ItemStack.Collection);
|
||||
OutItemInfos.Add(SimilarItemInfo);
|
||||
}
|
||||
}
|
||||
return OutItemInfos.Num() > 0;
|
||||
}
|
||||
|
||||
int32 UGIS_ItemCollection::GetItemAmount(const UGIS_ItemInstance* Item, bool SimilarItem) const
|
||||
{
|
||||
if (Item == nullptr) { return 0; }
|
||||
|
||||
int32 Count = 0;
|
||||
|
||||
for (int i = 0; i < Container.Stacks.Num(); i++)
|
||||
{
|
||||
if (Container.Stacks[i].Item && Container.Stacks[i].Item->SimilarTo(Item))
|
||||
{
|
||||
Count += Container.Stacks[i].Amount;
|
||||
}
|
||||
}
|
||||
|
||||
return Count;
|
||||
}
|
||||
|
||||
int32 UGIS_ItemCollection::GetItemAmount(TSoftObjectPtr<UGIS_ItemDefinition> ItemDefinition, bool CountStacks) const
|
||||
{
|
||||
if (ItemDefinition.IsNull())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
UGIS_ItemDefinition* LoadedDefinition = ItemDefinition.LoadSynchronous();
|
||||
if (LoadedDefinition == nullptr)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int32 Count = 0;
|
||||
for (int32 i = 0; i < Container.Stacks.Num(); i++)
|
||||
{
|
||||
if (Container.Stacks[i].Item == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (LoadedDefinition != Container.Stacks[i].Item->GetDefinition())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Count += CountStacks ? 1 : Container.Stacks[i].Amount;
|
||||
}
|
||||
|
||||
return Count;
|
||||
}
|
||||
|
||||
TArray<FGIS_ItemInfo> UGIS_ItemCollection::GetAllItemInfos() const
|
||||
{
|
||||
TArray<FGIS_ItemInfo> Infos;
|
||||
for (int32 i = 0; i < Container.Stacks.Num(); i++)
|
||||
{
|
||||
const FGIS_ItemStack& ItemStack = Container.Stacks[i];
|
||||
Infos.Add(FGIS_ItemInfo(ItemStack));
|
||||
}
|
||||
return Infos;
|
||||
}
|
||||
|
||||
TArray<UGIS_ItemInstance*> UGIS_ItemCollection::GetAllItems() const
|
||||
{
|
||||
TArray<UGIS_ItemInstance*> Rets;
|
||||
for (int32 i = 0; i < Container.Stacks.Num(); i++)
|
||||
{
|
||||
if (Container.Stacks[i].IsValidStack())
|
||||
{
|
||||
Rets.AddUnique(Container.Stacks[i].Item);
|
||||
}
|
||||
}
|
||||
return Rets;
|
||||
}
|
||||
|
||||
const TArray<FGIS_ItemStack>& UGIS_ItemCollection::GetAllItemStacks() const
|
||||
{
|
||||
return Container.Stacks;
|
||||
}
|
||||
|
||||
int32 UGIS_ItemCollection::GetItemStacksNum() const
|
||||
{
|
||||
return Container.Stacks.Num();
|
||||
}
|
||||
|
||||
void UGIS_ItemCollection::AddItemStack(const FGIS_ItemStack& Stack)
|
||||
{
|
||||
check(Stack.IsValidStack())
|
||||
int32 Idx = Container.Stacks.AddDefaulted();
|
||||
Container.Stacks[Idx] = Stack;
|
||||
FGIS_ItemStack& AddedStack = Container.Stacks[Idx];
|
||||
|
||||
OnPreItemStackAdded(Stack, Idx);
|
||||
OnItemStackAdded(AddedStack);
|
||||
|
||||
Container.MarkItemDirty(AddedStack);
|
||||
}
|
||||
|
||||
void UGIS_ItemCollection::RemoveItemStackAtIndex(int32 Idx, bool bRemoveFromCollection)
|
||||
{
|
||||
check(Container.Stacks.IsValidIndex(Idx))
|
||||
const FGIS_ItemStack RemovedStack = Container.Stacks[Idx];
|
||||
OnItemStackRemoved(RemovedStack);
|
||||
Container.Stacks.RemoveAt(Idx);
|
||||
if (bRemoveFromCollection)
|
||||
{
|
||||
RemovedStack.Item->UnassignCollection(this);
|
||||
if (OwningInventory->IsReplicatedSubObjectRegistered(RemovedStack.Item))
|
||||
{
|
||||
OwningInventory->RemoveReplicatedSubObject(RemovedStack.Item);
|
||||
}
|
||||
}
|
||||
Container.MarkArrayDirty();
|
||||
}
|
||||
|
||||
void UGIS_ItemCollection::UpdateItemStackAmountAtIndex(int32 Idx, int32 NewAmount)
|
||||
{
|
||||
check(Container.Stacks.IsValidIndex(Idx))
|
||||
FGIS_ItemStack& StackToUpdate = Container.Stacks[Idx];
|
||||
StackToUpdate.Amount = NewAmount;
|
||||
OnItemStackUpdated(StackToUpdate);
|
||||
Container.MarkItemDirty(StackToUpdate);
|
||||
}
|
||||
|
||||
void UGIS_ItemCollection::OnPreItemStackAdded(const FGIS_ItemStack& Stack, int32 Idx)
|
||||
{
|
||||
if (IsValid(Stack.Item))
|
||||
{
|
||||
if (Stack.Item->GetOwningCollection() != this)
|
||||
{
|
||||
Stack.Item->AssignCollection(this);
|
||||
}
|
||||
|
||||
if (!OwningInventory->IsReplicatedSubObjectRegistered(Stack.Item))
|
||||
{
|
||||
OwningInventory->AddReplicatedSubObject(Stack.Item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGIS_ItemCollection::OnItemStackAdded(const FGIS_ItemStack& Stack)
|
||||
{
|
||||
check(Stack.IsValidStack())
|
||||
if (OwningInventory)
|
||||
{
|
||||
StackToIdxMap.Add(Stack.Id, Stack.Index);
|
||||
FGIS_InventoryStackUpdateMessage Message;
|
||||
Message.Inventory = OwningInventory;
|
||||
Message.ChangeType = EGIS_ItemStackChangeType::WasAdded;
|
||||
Message.CollectionId = CollectionId;
|
||||
Message.Instance = Stack.Item;
|
||||
Message.StackId = Stack.Id;
|
||||
Message.NewCount = Stack.Amount;
|
||||
Message.Delta = Stack.Amount;
|
||||
OwningInventory->OnInventoryStackUpdate.Broadcast(Message);
|
||||
|
||||
GIS_CLOG(Verbose, "added item stack:item(%s),amount(%d)", *Stack.Item->GetDefinition()->GetName(), Stack.Amount)
|
||||
}
|
||||
}
|
||||
|
||||
void UGIS_ItemCollection::OnItemStackRemoved(const FGIS_ItemStack& Stack)
|
||||
{
|
||||
if (OwningInventory)
|
||||
{
|
||||
StackToIdxMap.Remove(Stack.Id);
|
||||
FGIS_InventoryStackUpdateMessage Message;
|
||||
Message.Inventory = OwningInventory;
|
||||
Message.ChangeType = EGIS_ItemStackChangeType::WasRemoved;
|
||||
Message.CollectionId = CollectionId;
|
||||
Message.Instance = Stack.Item;
|
||||
Message.StackId = Stack.Id;
|
||||
Message.NewCount = 0;
|
||||
Message.Delta = -Stack.LastObservedAmount;
|
||||
OwningInventory->OnInventoryStackUpdate.Broadcast(Message);
|
||||
|
||||
GIS_CLOG(Verbose, "removed item stack:item(%s),amount(%d)", *Stack.Item->GetDefinition()->GetName(), Stack.Amount)
|
||||
}
|
||||
}
|
||||
|
||||
void UGIS_ItemCollection::OnItemStackUpdated(const FGIS_ItemStack& Stack)
|
||||
{
|
||||
if (OwningInventory)
|
||||
{
|
||||
if (StackToIdxMap.Contains(Stack.Id))
|
||||
{
|
||||
StackToIdxMap[Stack.Id] = Stack.Index;
|
||||
}
|
||||
FGIS_InventoryStackUpdateMessage Message;
|
||||
Message.Inventory = OwningInventory;
|
||||
Message.ChangeType = EGIS_ItemStackChangeType::Changed;
|
||||
Message.CollectionId = CollectionId;
|
||||
Message.Instance = Stack.Item;
|
||||
Message.StackId = Stack.Id;
|
||||
Message.NewCount = Stack.Amount;
|
||||
Message.Delta = Stack.Amount - Stack.LastObservedAmount;
|
||||
OwningInventory->OnInventoryStackUpdate.Broadcast(Message);
|
||||
GIS_CLOG(Verbose, "updated item stack:item(%s),amount(%d)", *Stack.Item->GetDefinition()->GetName(), Stack.Amount)
|
||||
}
|
||||
}
|
||||
|
||||
void UGIS_ItemCollection::ProcessPendingItemStacks()
|
||||
{
|
||||
if (bInitialized)
|
||||
{
|
||||
TArray<FGuid> Added;
|
||||
for (const TPair<FGuid, FGIS_ItemStack>& Pending : PendingItemStacks)
|
||||
{
|
||||
if (Pending.Value.IsValidStack())
|
||||
{
|
||||
Added.AddUnique(Pending.Key);
|
||||
}
|
||||
}
|
||||
for (int32 i = 0; i < Added.Num(); i++)
|
||||
{
|
||||
FGuid AddedStackId = Added[i];
|
||||
const FGIS_ItemStack& AddedStack = PendingItemStacks[AddedStackId];
|
||||
OnItemStackAdded(AddedStack);
|
||||
GIS_CLOG(Verbose, "added item stack:item(%s),amount(%d) from pending list.", *AddedStack.Item->GetDefinition()->GetName(), AddedStack.Amount)
|
||||
PendingItemStacks.Remove(AddedStackId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGIS_ItemCollection::SetDefinition(const UGIS_ItemCollectionDefinition* NewDefinition)
|
||||
{
|
||||
check(OwningInventory != nullptr);
|
||||
check(NewDefinition != nullptr);
|
||||
if (!bInitialized)
|
||||
{
|
||||
Definition = NewDefinition;
|
||||
CollectionTag = Definition->CollectionTag;
|
||||
bInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
void UGIS_ItemCollection::SetCollectionTag(FGameplayTag NewTag)
|
||||
{
|
||||
CollectionTag = NewTag;
|
||||
}
|
||||
|
||||
void UGIS_ItemCollection::SetCollectionId(FGuid NewId)
|
||||
{
|
||||
if (!CollectionId.IsValid())
|
||||
{
|
||||
CollectionId = NewId;
|
||||
}
|
||||
}
|
||||
|
||||
FGIS_ItemInfo UGIS_ItemCollection::AddInternal(const FGIS_ItemInfo& ItemInfo)
|
||||
{
|
||||
bool bFound = false;
|
||||
FGIS_ItemStack AddedItemStack;
|
||||
|
||||
if (!ItemInfo.Item->IsUnique())
|
||||
{
|
||||
// First, trying to add to existing stack.
|
||||
int32 TargetStackIdx = Container.IndexOfById(ItemInfo.StackId);
|
||||
if (TargetStackIdx != INDEX_NONE)
|
||||
{
|
||||
check(Container.Stacks.IsValidIndex(TargetStackIdx))
|
||||
const FGIS_ItemStack& ExistingStack = Container.Stacks[TargetStackIdx];
|
||||
if (ExistingStack.Collection == this && ExistingStack.Item == ItemInfo.Item)
|
||||
{
|
||||
UpdateItemStackAmountAtIndex(TargetStackIdx, ItemInfo.Amount + ExistingStack.Amount);
|
||||
AddedItemStack = ExistingStack;
|
||||
bFound = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bFound)
|
||||
{
|
||||
for (int32 i = 0; i < Container.Stacks.Num(); i++)
|
||||
{
|
||||
if (CanItemStack(ItemInfo, Container.Stacks[i]) == false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
UpdateItemStackAmountAtIndex(i, ItemInfo.Amount + Container.Stacks[i].Amount);
|
||||
AddedItemStack = Container.Stacks[i];
|
||||
bFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//新增.
|
||||
if (!bFound)
|
||||
{
|
||||
AddedItemStack.Initialize(FGuid::NewGuid(), ItemInfo.Item, ItemInfo.Amount, this, ItemInfo.Index);
|
||||
AddItemStack(AddedItemStack);
|
||||
}
|
||||
FGIS_ItemInfo AddedItemInfo = FGIS_ItemInfo(ItemInfo.Item, ItemInfo.Amount, this, AddedItemStack.Id);
|
||||
return AddedItemInfo;
|
||||
}
|
||||
|
||||
void UGIS_ItemCollection::HandleItemOverflow(const FGIS_ItemInfo& OriginalItemInfo, const FGIS_ItemInfo& ItemInfoAdded)
|
||||
{
|
||||
FGIS_ItemInfo RejectedItemInfo = FGIS_ItemInfo(OriginalItemInfo.Amount - ItemInfoAdded.Amount, OriginalItemInfo);
|
||||
|
||||
FGIS_ItemInfo ReturnedItemInfo;
|
||||
if (Definition->OverflowOptions.bReturnOverflow)
|
||||
{
|
||||
if (OriginalItemInfo.ItemCollection != nullptr && OriginalItemInfo.ItemCollection != this)
|
||||
{
|
||||
ReturnedItemInfo = OriginalItemInfo.ItemCollection->AddItem(RejectedItemInfo);
|
||||
}
|
||||
}
|
||||
|
||||
if (Definition->OverflowOptions.bSendRejectedMessage)
|
||||
{
|
||||
FGIS_InventoryAddItemInfoRejectedMessage Message;
|
||||
Message.Inventory = OwningInventory;
|
||||
Message.Collection = this;
|
||||
Message.OriginalItemInfo = OriginalItemInfo;
|
||||
Message.ItemInfoAdded = ItemInfoAdded;
|
||||
Message.RejectedItemInfo = RejectedItemInfo;
|
||||
Message.ReturnedItemInfo = ReturnedItemInfo;
|
||||
OwningInventory->OnInventoryAddItemInfo_Rejected.Broadcast(Message);
|
||||
}
|
||||
GIS_CLOG(Warning, "try add %s, added %d, rejected %d, returned:%d", *OriginalItemInfo.GetDebugString(), ItemInfoAdded.Amount, RejectedItemInfo.Amount, ReturnedItemInfo.Amount);
|
||||
}
|
||||
|
||||
FGIS_ItemInfo UGIS_ItemCollection::RemoveInternal(const FGIS_ItemInfo& ItemInfo)
|
||||
{
|
||||
int32 Removed = 0;
|
||||
|
||||
FGIS_ItemStack ItemStackToRemove;
|
||||
|
||||
{
|
||||
int32 Idx = Container.IndexOfByIds(ItemInfo.StackId, ItemInfo.Item->GetItemId());
|
||||
|
||||
if (Idx != INDEX_NONE)
|
||||
{
|
||||
ItemStackToRemove = SimpleInternalItemRemove(ItemInfo, Removed, Idx);
|
||||
}
|
||||
}
|
||||
|
||||
//如果已经移除的数量未达到指定移除的数量,就继续移除。
|
||||
if (Removed < ItemInfo.Amount)
|
||||
{
|
||||
TArray<FGIS_ItemStack> TempStacks = Container.Stacks;
|
||||
for (int32 i = TempStacks.Num() - 1; i >= 0; i--)
|
||||
{
|
||||
if (TempStacks[i].Item == nullptr || TempStacks[i].Item->GetItemId() != ItemInfo.Item->GetItemId())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
ItemStackToRemove = SimpleInternalItemRemove(ItemInfo, Removed, i);
|
||||
if (Removed >= ItemInfo.Amount)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const FGIS_ItemInfo RemovedItemInfo = FGIS_ItemInfo(ItemInfo.Item, Removed, this, ItemStackToRemove.Id);
|
||||
|
||||
if (Removed == 0)
|
||||
{
|
||||
return RemovedItemInfo;
|
||||
}
|
||||
|
||||
return RemovedItemInfo;
|
||||
}
|
||||
|
||||
FGIS_ItemStack UGIS_ItemCollection::SimpleInternalItemRemove(const FGIS_ItemInfo& ItemInfo, int32& AlreadyRemoved, int32 StackIndex)
|
||||
{
|
||||
check(Container.Stacks.IsValidIndex(StackIndex));
|
||||
const FGIS_ItemStack FoundStack = Container.Stacks[StackIndex];
|
||||
int32 RemainingToRemove = ItemInfo.Amount - AlreadyRemoved; //我要减10个,已经减了4个,还要减6个
|
||||
int32 NewAmount = FoundStack.Amount - RemainingToRemove;
|
||||
if (NewAmount <= 0) //如果栈里还有3个,不够减,那么已经减去的数量就成了7个,然后栈被移除。
|
||||
{
|
||||
AlreadyRemoved += FoundStack.Amount;
|
||||
RemoveItemStackAtIndex(StackIndex);
|
||||
}
|
||||
else //如果栈里还有7个,够减,那么已经减去的数量为10,栈还剩下7-6=1个。
|
||||
{
|
||||
AlreadyRemoved += RemainingToRemove;
|
||||
UpdateItemStackAmountAtIndex(StackIndex, NewAmount);
|
||||
}
|
||||
return FoundStack;
|
||||
}
|
||||
|
||||
FGIS_ItemInfo UGIS_ItemCollection::GiveItem(const FGIS_ItemInfo& ItemInfo, UGIS_ItemCollection* ItemCollection)
|
||||
{
|
||||
if (OwningInventory->GetOwnerRole() != ROLE_Authority)
|
||||
{
|
||||
GIS_CLOG(Warning, "Has no authority")
|
||||
return FGIS_ItemInfo::None;
|
||||
}
|
||||
if (!ItemInfo.IsValid())
|
||||
{
|
||||
GIS_CLOG(Warning, "invalid ItemInfo to give.")
|
||||
return FGIS_ItemInfo::None;
|
||||
}
|
||||
|
||||
if (ItemInfo.Item->GetOwningInventory() == ItemCollection->GetOwningInventory())
|
||||
{
|
||||
GIS_CLOG(Warning, "Item:%s already belongs to this inventory.", *ItemInfo.GetDebugString());
|
||||
return FGIS_ItemInfo::None;
|
||||
}
|
||||
|
||||
FGIS_ItemInfo RemovedItemInfo = RemoveItem(ItemInfo);
|
||||
|
||||
FGIS_ItemInfo ItemInfoToAdd = FGIS_ItemInfo(ItemInfo.Item, RemovedItemInfo.Amount, this);
|
||||
FGIS_ItemInfo GivenItemInfo = ItemCollection->AddItem(ItemInfoToAdd);
|
||||
if (GivenItemInfo.Amount != RemovedItemInfo.Amount)
|
||||
{
|
||||
// Failed to add so add it back to the previous collection.
|
||||
//ItemInfoToAdd.Amount = ItemInfoToAdd.Amount - GivenItemInfo.Amount;
|
||||
//if (ItemInfoToAdd.IsValid())
|
||||
//{
|
||||
// AddItem(FGIS_ItemInfo(ItemInfoToAdd));
|
||||
//}
|
||||
}
|
||||
return GivenItemInfo;
|
||||
}
|
||||
|
||||
void UGIS_ItemCollection::ServerGiveItem_Implementation(const FGIS_ItemInfo& ItemInfo, UGIS_ItemCollection* ItemCollection)
|
||||
{
|
||||
GiveItem(ItemInfo, ItemCollection);
|
||||
}
|
||||
|
||||
void UGIS_ItemCollection::GiveAllItems(UGIS_ItemCollection* OtherItemCollection)
|
||||
{
|
||||
for (int i = Container.Stacks.Num() - 1; i >= 0; i--)
|
||||
{
|
||||
auto& itemStack = Container.Stacks[i];
|
||||
GiveItem(FGIS_ItemInfo(itemStack), OtherItemCollection);
|
||||
}
|
||||
}
|
||||
|
||||
void UGIS_ItemCollection::ServerGiveAllItems_Implementation(UGIS_ItemCollection* OtherItemCollection)
|
||||
{
|
||||
GiveAllItems(OtherItemCollection);
|
||||
}
|
||||
|
||||
int32 UGIS_ItemCollection::GetItemAmountFittingInLimitedAdditionalStacks(const FGIS_ItemInfo& ItemInfo, int32 AvailableAdditionalStacks) const
|
||||
{
|
||||
if (ItemInfo.Item->IsUnique())
|
||||
{
|
||||
//预计还有5个,而要添加10个,那么就只能5个。
|
||||
//预计还有10个,而要添加6个,那么就能放6个。
|
||||
return FMath::Min(ItemInfo.Amount, AvailableAdditionalStacks);
|
||||
}
|
||||
|
||||
//满了,且没有已经存在的Stack可以叠上去。
|
||||
if (AvailableAdditionalStacks == 0 && !HasItem(ItemInfo.Item, 1))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return ItemInfo.Amount;
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GIS_ItemMultiStackCollection.h"
|
||||
|
||||
#include "GIS_InventorySystemComponent.h"
|
||||
#include "GIS_InventoryTags.h"
|
||||
#include "GIS_ItemDefinition.h"
|
||||
#include "Items/GIS_ItemInstance.h"
|
||||
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_ItemMultiStackCollection)
|
||||
|
||||
UGIS_ItemMultiStackCollectionDefinition::UGIS_ItemMultiStackCollectionDefinition()
|
||||
{
|
||||
StackSizeLimitAttribute = GIS_AttributeTags::StackSizeLimit;
|
||||
}
|
||||
|
||||
TSubclassOf<UGIS_ItemCollection> UGIS_ItemMultiStackCollectionDefinition::GetCollectionInstanceClass() const
|
||||
{
|
||||
return UGIS_ItemMultiStackCollection::StaticClass();
|
||||
}
|
||||
|
||||
UGIS_ItemMultiStackCollection::UGIS_ItemMultiStackCollection(const FObjectInitializer& ObjectInitializer): Super(ObjectInitializer)
|
||||
{
|
||||
}
|
||||
|
||||
bool UGIS_ItemMultiStackCollection::GetItemInfo(const UGIS_ItemInstance* Item, FGIS_ItemInfo& OutItemInfo) const
|
||||
{
|
||||
if (Item == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool bFoundSimilar = false;
|
||||
FGIS_ItemInfo SimilarItemInfo;
|
||||
|
||||
for (int32 i = Container.Stacks.Num() - 1; i >= 0; i--)
|
||||
{
|
||||
const FGIS_ItemStack& ItemStack = Container.Stacks[i];
|
||||
|
||||
if (!ItemStack.IsValidStack())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Item->IsUnique() && ItemStack.Item->GetDefinition() == Item->GetDefinition())
|
||||
{
|
||||
SimilarItemInfo = FGIS_ItemInfo(ItemStack);
|
||||
bFoundSimilar = true;
|
||||
}
|
||||
if (Container.Stacks[i].Item->GetItemId() != Item->GetItemId())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
OutItemInfo = FGIS_ItemInfo(Container.Stacks[i]);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (bFoundSimilar)
|
||||
{
|
||||
OutItemInfo = SimilarItemInfo;
|
||||
}
|
||||
return bFoundSimilar;
|
||||
}
|
||||
|
||||
int32 UGIS_ItemMultiStackCollection::GetItemAmountFittingInLimitedAdditionalStacks(const FGIS_ItemInfo& ItemInfo, int32 AvailableAdditionalStacks) const
|
||||
{
|
||||
int32 AmountToAdd = ItemInfo.Amount;
|
||||
|
||||
int32 MaxStackSize = GetMaxStackSize(ItemInfo.Item);
|
||||
|
||||
for (int32 i = 0; i < Container.Stacks.Num(); i++)
|
||||
{
|
||||
const FGIS_ItemStack& itemStack = Container.Stacks[i];
|
||||
if (CanItemStack(ItemInfo, itemStack) == false) { continue; }
|
||||
|
||||
if (itemStack.Amount == MaxStackSize) { continue; }
|
||||
|
||||
int32 TotalToSet = itemStack.Amount + AmountToAdd;
|
||||
int32 SizeDifference = TotalToSet - MaxStackSize;
|
||||
|
||||
if (SizeDifference <= 0)
|
||||
{
|
||||
AmountToAdd = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
AmountToAdd = SizeDifference;
|
||||
}
|
||||
|
||||
int32 StacksToAdd = AmountToAdd / MaxStackSize;
|
||||
int32 RemainderStack = AmountToAdd % MaxStackSize;
|
||||
|
||||
|
||||
if (AvailableAdditionalStacks > StacksToAdd)
|
||||
{
|
||||
return ItemInfo.Amount;
|
||||
}
|
||||
if (AvailableAdditionalStacks == StacksToAdd)
|
||||
{
|
||||
return ItemInfo.Amount - RemainderStack;
|
||||
}
|
||||
|
||||
return ItemInfo.Amount - RemainderStack - MaxStackSize * (StacksToAdd - AvailableAdditionalStacks);
|
||||
}
|
||||
|
||||
FGIS_ItemInfo UGIS_ItemMultiStackCollection::AddInternal(const FGIS_ItemInfo& ItemInfo)
|
||||
{
|
||||
// total amounts of item need to add.
|
||||
int32 AmountToAdd = ItemInfo.Amount;
|
||||
int32 MaxStackSize = GetMaxStackSize(ItemInfo.Item);
|
||||
|
||||
// temp struct to record added stack.
|
||||
FGIS_ItemStack AddedItemStack;
|
||||
|
||||
{
|
||||
//try adding item amount on top of the existing stack. 尝试在指定栈上新增数量。
|
||||
int32 TargetStackIdx = Container.IndexOfById(ItemInfo.StackId);
|
||||
if (TargetStackIdx != INDEX_NONE)
|
||||
{
|
||||
check(Container.Stacks.IsValidIndex(TargetStackIdx))
|
||||
const FGIS_ItemStack& TargetStack = Container.Stacks[TargetStackIdx];
|
||||
|
||||
//Make sure the target stack is valid and can be stacked with new item. 确保指定栈有效且可与请求的道具信息堆叠。
|
||||
if (TargetStack.IsValidStack() && CanItemStack(ItemInfo, TargetStack))
|
||||
{
|
||||
AddedItemStack = TargetStack;
|
||||
IncreaseStackAmount(TargetStackIdx, MaxStackSize, AmountToAdd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//尝试在已经存在的兼容栈上新增数量。
|
||||
for (int32 i = 0; i < Container.Stacks.Num(); i++)
|
||||
{
|
||||
const FGIS_ItemStack& ItemStack = Container.Stacks[i];
|
||||
if (CanItemStack(ItemInfo, ItemStack) == false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
AddedItemStack = ItemStack;
|
||||
|
||||
int32 AmountAdded = IncreaseStackAmount(i, MaxStackSize, AmountToAdd);
|
||||
}
|
||||
|
||||
/**
|
||||
* 30/10=3,30%10=0 need 3 stacks.
|
||||
* 25/10=2,25%10=1 need 3 stacks.
|
||||
* 77/21=3,77%21=1 need 4 stacks. (4*21 = 84) > 77
|
||||
*/
|
||||
int32 StacksToAdd = AmountToAdd / MaxStackSize;
|
||||
int32 RemainderStack = AmountToAdd % MaxStackSize;
|
||||
|
||||
for (int32 i = 0; i < StacksToAdd; i++)
|
||||
{
|
||||
FGIS_ItemStack NewItemStack;
|
||||
NewItemStack.Initialize(FGuid::NewGuid(), ItemInfo.Item, MaxStackSize, this);
|
||||
AddedItemStack = NewItemStack;
|
||||
AddItemStack(NewItemStack);
|
||||
}
|
||||
|
||||
if (RemainderStack != 0)
|
||||
{
|
||||
FGIS_ItemStack NewItemStack;
|
||||
NewItemStack.Initialize(FGuid::NewGuid(), ItemInfo.Item, RemainderStack, this);
|
||||
AddedItemStack = NewItemStack;
|
||||
AddItemStack(NewItemStack);
|
||||
}
|
||||
|
||||
return FGIS_ItemInfo(ItemInfo.Item, AddedItemStack.Amount, this);
|
||||
}
|
||||
|
||||
int32 UGIS_ItemMultiStackCollection::GetMaxStackSize(UGIS_ItemInstance* Item) const
|
||||
{
|
||||
const UGIS_ItemMultiStackCollectionDefinition* MyDefinition = CastChecked<UGIS_ItemMultiStackCollectionDefinition>(Definition);
|
||||
|
||||
if (!Item->GetDefinition()->HasIntegerAttribute(MyDefinition->StackSizeLimitAttribute))
|
||||
{
|
||||
return MyDefinition->DefaultStackSizeLimit;
|
||||
}
|
||||
return Item->GetDefinition()->GetIntegerAttribute(MyDefinition->StackSizeLimitAttribute);
|
||||
|
||||
// if (!Item->HasIntegerAttribute(MyDefinition->StackSizeLimitAttribute))
|
||||
// {
|
||||
// return MyDefinition->DefaultStackSizeLimit;
|
||||
// }
|
||||
// return Item->GetIntegerAttribute(MyDefinition->StackSizeLimitAttribute);
|
||||
}
|
||||
|
||||
/**
|
||||
* 案例: 每个栈最多放10个,现在有35个苹果,占用4个栈,这两个栈的ID分别是A,B,C,D。它们的分布是[(A:10),(B:10)(C:10)(D:5)]
|
||||
* 假设传入的ItemInfo是栈A里删除11个苹果:那么先从栈A删除10个,这时候栈A空了,还需要在栈B里移除1个。最后的栈分布是[(B:9),(C:10),(D:5)]
|
||||
* 假设传入的ItemInfo是栈A里删除34个苹果:那么先从栈ABC都删除10个,ABC清空,然后剩下栈D里删除4个,最后的栈分布是[(D:1)]
|
||||
*/
|
||||
FGIS_ItemInfo UGIS_ItemMultiStackCollection::RemoveInternal(const FGIS_ItemInfo& ItemInfo)
|
||||
{
|
||||
int32 AlreadyRemoved = 0;
|
||||
int32 AmountToRemove = ItemInfo.Amount;
|
||||
//上一次移除的StackIndex
|
||||
int32 PreviousStackIndexWithSameItem = INDEX_NONE;
|
||||
int32 MaxStackSize = GetMaxStackSize(ItemInfo.Item);
|
||||
FGIS_ItemStack RemovedItemStack;
|
||||
|
||||
{
|
||||
// Try remove from existing stack first.
|
||||
int32 StackIdx = Container.IndexOfById(ItemInfo.StackId);
|
||||
if (StackIdx != INDEX_NONE)
|
||||
{
|
||||
check(Container.Stacks.IsValidIndex(StackIdx))
|
||||
RemovedItemStack = Container.Stacks[StackIdx];
|
||||
PreviousStackIndexWithSameItem = RemoveItemFromStack(StackIdx, PreviousStackIndexWithSameItem, MaxStackSize, AmountToRemove, AlreadyRemoved);
|
||||
}
|
||||
}
|
||||
|
||||
// 继续从其他栈中移除这个道具
|
||||
TArray<FGIS_ItemStack> TempStacks = Container.Stacks;
|
||||
for (int i = TempStacks.Num() - 1; i >= 0; i--)
|
||||
{
|
||||
if (AmountToRemove <= 0) { break; }
|
||||
|
||||
if (TempStacks[i].Item == nullptr || TempStacks[i].Item->GetItemId() != ItemInfo.Item->GetItemId()) { continue; }
|
||||
//忽略前面移除的Stack.
|
||||
if (RemovedItemStack == TempStacks[i]) { continue; }
|
||||
|
||||
RemovedItemStack = TempStacks[i];
|
||||
|
||||
PreviousStackIndexWithSameItem = RemoveItemFromStack(i, PreviousStackIndexWithSameItem, MaxStackSize, AmountToRemove, AlreadyRemoved);
|
||||
}
|
||||
|
||||
//No any stacks contain this item instance; 无任何包含此道具实例的道具栈,因此可以从该集合完全移除。
|
||||
if (PreviousStackIndexWithSameItem == INDEX_NONE)
|
||||
{
|
||||
ItemInfo.Item->UnassignCollection(this);
|
||||
if (OwningInventory->IsReplicatedSubObjectRegistered(ItemInfo.Item))
|
||||
{
|
||||
OwningInventory->RemoveReplicatedSubObject(ItemInfo.Item);
|
||||
}
|
||||
}
|
||||
|
||||
if (AlreadyRemoved == 0)
|
||||
{
|
||||
return FGIS_ItemInfo(ItemInfo.Item, AlreadyRemoved, this);
|
||||
}
|
||||
return FGIS_ItemInfo(ItemInfo.Item, AlreadyRemoved, this);
|
||||
}
|
||||
|
||||
int32 UGIS_ItemMultiStackCollection::RemoveItemFromStack(int32 Index, int32 PrevStackIndexWithSameItem, int32 MaxStackSize, int32& AmountToRemove, int32& AlreadyRemoved)
|
||||
{
|
||||
check(Container.Stacks.IsValidIndex(Index));
|
||||
|
||||
int32 AmountInStack = Container.Stacks[Index].Amount;
|
||||
|
||||
if (PrevStackIndexWithSameItem != INDEX_NONE)
|
||||
{
|
||||
check(Container.Stacks.IsValidIndex(PrevStackIndexWithSameItem))
|
||||
|
||||
int32 MergedAmount = AmountInStack + Container.Stacks[PrevStackIndexWithSameItem].Amount;
|
||||
|
||||
if (MergedAmount > MaxStackSize) //假设每个栈最大100,当前的栈有70,之前的栈有60,即一共130,就超了30.
|
||||
{
|
||||
UpdateItemStackAmountAtIndex(PrevStackIndexWithSameItem, MaxStackSize); //让之前的栈满,即60+40=100;
|
||||
AmountInStack = MergedAmount - MaxStackSize; //再让当前栈变成 130-100=30(即少了70-40=30)
|
||||
}
|
||||
else //the total size of 2 stacks doesn't reach max stack size. 假设每个栈最大100,当前的栈有70,之前的栈有10,即一共80,没有超过100.
|
||||
{
|
||||
// merge current stack's amount into prev stack. //让之前栈为10+70=80.
|
||||
UpdateItemStackAmountAtIndex(PrevStackIndexWithSameItem, MergedAmount);
|
||||
// and empty this one. 当前栈归0.
|
||||
AmountInStack = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (AmountToRemove == 0) { return PrevStackIndexWithSameItem; }
|
||||
|
||||
int32 NewAmount = AmountInStack - AmountToRemove;
|
||||
if (NewAmount <= 0)
|
||||
{
|
||||
AmountToRemove = -NewAmount;
|
||||
AlreadyRemoved += AmountInStack;
|
||||
|
||||
//Item can be stored within multiple stacks, we don't need to remove it from this collection now. Item在此集合可以属于多个栈,不在这里移除。
|
||||
RemoveItemStackAtIndex(Index, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
AlreadyRemoved += AmountToRemove;
|
||||
AmountToRemove = 0;
|
||||
UpdateItemStackAmountAtIndex(Index, NewAmount);
|
||||
PrevStackIndexWithSameItem = Index;
|
||||
}
|
||||
return PrevStackIndexWithSameItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* 代入: Stack(item,70);MaxStackSize(100),AmountToAdd(50) 结果:Stack(item,100), AmountToAdd(20), 返回实际添加30
|
||||
* 代入: Stack(item,40);MaxStackSize(100),AmountToAdd(50) 结果:Stack(item,90), AmountToAdd(0), 返回实际添加50
|
||||
*/
|
||||
int32 UGIS_ItemMultiStackCollection::IncreaseStackAmount(int32 StackIdx, int32 MaxStackSize, int32& AmountToAdd)
|
||||
{
|
||||
check(Container.Stacks.IsValidIndex(StackIdx))
|
||||
|
||||
const FGIS_ItemStack& ItemStack = Container.Stacks[StackIdx];
|
||||
if (ItemStack.Amount == MaxStackSize)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int32 OriginAmountToAdd = AmountToAdd;
|
||||
int32 NewAmount = ItemStack.Amount + AmountToAdd;
|
||||
|
||||
int32 OverflowedAmount = NewAmount - MaxStackSize; // <=0 no overflow >0 means overflow.
|
||||
|
||||
// This stack can hold the new amount. 这个栈装得下。
|
||||
if (OverflowedAmount <= 0)
|
||||
{
|
||||
AmountToAdd = 0; //all amounts have been added. 无需再添加。
|
||||
}
|
||||
else //This stack overflow. 这个栈溢出了。
|
||||
{
|
||||
NewAmount = MaxStackSize;
|
||||
//Still need to add overflowed amount. 超过最大尺寸后的剩余要增加的数量
|
||||
AmountToAdd = OverflowedAmount;
|
||||
}
|
||||
|
||||
UpdateItemStackAmountAtIndex(StackIdx, NewAmount);
|
||||
//实际增加的数量
|
||||
return OriginAmountToAdd - AmountToAdd;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GIS_ItemRestriction.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_ItemRestriction)
|
||||
|
||||
bool UGIS_ItemRestriction::CanAddItem(FGIS_ItemInfo& ItemInfo, UGIS_ItemCollection* ReceivingCollection) const
|
||||
{
|
||||
return CanAddItemInternal(ItemInfo, ReceivingCollection);
|
||||
}
|
||||
|
||||
bool UGIS_ItemRestriction::CanRemoveItem(FGIS_ItemInfo& ItemInfo) const
|
||||
{
|
||||
return CanRemoveItemInternal(ItemInfo);
|
||||
}
|
||||
|
||||
bool UGIS_ItemRestriction::CanAddItemInternal_Implementation(FGIS_ItemInfo& ItemInfo, UGIS_ItemCollection* ReceivingCollection) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UGIS_ItemRestriction::CanRemoveItemInternal_Implementation(FGIS_ItemInfo& ItemInfo) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GIS_ItemRestriction_StackSizeLimit.h"
|
||||
|
||||
#include "GIS_ItemCollection.h"
|
||||
#include "GIS_ItemDefinition.h"
|
||||
#include "Items/GIS_ItemInstance.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_ItemRestriction_StackSizeLimit)
|
||||
|
||||
UGIS_ItemRestriction_StackSizeLimit::UGIS_ItemRestriction_StackSizeLimit()
|
||||
{
|
||||
DefaultStackSizeLimit = 99;
|
||||
}
|
||||
|
||||
bool UGIS_ItemRestriction_StackSizeLimit::CanAddItemInternal_Implementation(FGIS_ItemInfo& ItemInfo, UGIS_ItemCollection* ReceivingCollection) const
|
||||
{
|
||||
int32 MaxStackSize = GetStackSizeLimit(ItemInfo.Item);
|
||||
|
||||
FGIS_ItemInfo ExistingItemInfoResult;
|
||||
if (!ReceivingCollection->GetItemInfo(ItemInfo.Item, ExistingItemInfoResult))
|
||||
{
|
||||
ItemInfo.Amount = FMath::Min(ItemInfo.Amount, MaxStackSize);
|
||||
return true;
|
||||
}
|
||||
|
||||
int32 ItemAmountsThatFit = FMath::Min(MaxStackSize - ExistingItemInfoResult.Amount, ItemInfo.Amount);
|
||||
ItemAmountsThatFit = FMath::Max(0, ItemAmountsThatFit);
|
||||
|
||||
if (ItemAmountsThatFit == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ItemInfo.Amount = ItemAmountsThatFit;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
int32 UGIS_ItemRestriction_StackSizeLimit::GetStackSizeLimit(const UGIS_ItemInstance* Item) const
|
||||
{
|
||||
if (Item == nullptr)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (StackSizeLimitAttributeTag.IsValid() && Item->GetDefinition()->HasIntegerAttribute(StackSizeLimitAttributeTag))
|
||||
{
|
||||
return Item->GetDefinition()->GetIntegerAttribute(StackSizeLimitAttributeTag);
|
||||
}
|
||||
// if (StackSizeLimitAttributeTag.IsValid() && Item->HasIntegerAttribute(StackSizeLimitAttributeTag))
|
||||
// {
|
||||
// return Item->GetIntegerAttribute(StackSizeLimitAttributeTag);
|
||||
// }
|
||||
|
||||
return DefaultStackSizeLimit;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GIS_ItemRestriction_StacksNumLimit.h"
|
||||
|
||||
#include "GIS_InventorySystemComponent.h"
|
||||
#include "GIS_ItemCollection.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_ItemRestriction_StacksNumLimit)
|
||||
|
||||
bool UGIS_ItemRestriction_StacksNumLimit::CanAddItemInternal_Implementation(FGIS_ItemInfo& ItemInfo, UGIS_ItemCollection* ReceivingCollection) const
|
||||
{
|
||||
int32 ExistingNum = ReceivingCollection->GetItemStacksNum();
|
||||
|
||||
int32 AvailableAdditionalStacks = MaxStacksNum - ExistingNum;
|
||||
|
||||
int32 ItemAmountsThatFit = ReceivingCollection->GetItemAmountFittingInLimitedAdditionalStacks(ItemInfo, AvailableAdditionalStacks);
|
||||
|
||||
if (ItemAmountsThatFit == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ItemInfo = FGIS_ItemInfo(ItemAmountsThatFit, ItemInfo);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GIS_ItemRestriction_TagRequirements.h"
|
||||
|
||||
#include "Items/GIS_ItemInstance.h"
|
||||
#include "GIS_ItemCollection.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_ItemRestriction_TagRequirements)
|
||||
|
||||
bool UGIS_ItemRestriction_TagRequirements::CanAddItemInternal_Implementation(FGIS_ItemInfo& ItemInfo, UGIS_ItemCollection* ReceivingCollection) const
|
||||
{
|
||||
if (TagQuery.IsEmpty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return TagQuery.Matches(ItemInfo.Item->GetItemTags());
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GIS_ItemRestriction_UniqueOnly.h"
|
||||
|
||||
#include "Items/GIS_ItemInstance.h"
|
||||
#include "GIS_ItemCollection.h"
|
||||
#include "Items/GIS_ItemDefinition.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_ItemRestriction_UniqueOnly)
|
||||
|
||||
bool UGIS_ItemRestriction_UniqueOnly::CanAddItemInternal_Implementation(FGIS_ItemInfo& ItemInfo, UGIS_ItemCollection* ReceivingCollection) const
|
||||
{
|
||||
// this restriction will not modify the item info will be added, so just set it to OutItemInfo
|
||||
return ItemInfo.Item->IsUnique();
|
||||
}
|
||||
@@ -0,0 +1,583 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GIS_ItemSlotCollection.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "GIS_InventorySystemComponent.h"
|
||||
#include "Items/GIS_ItemInstance.h"
|
||||
#include "GIS_LogChannels.h"
|
||||
#include "Misc/DataValidation.h"
|
||||
#include "UObject/ObjectSaveContext.h"
|
||||
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_ItemSlotCollection)
|
||||
|
||||
TSubclassOf<UGIS_ItemCollection> UGIS_ItemSlotCollectionDefinition::GetCollectionInstanceClass() const
|
||||
{
|
||||
return UGIS_ItemSlotCollection::StaticClass();
|
||||
}
|
||||
|
||||
bool UGIS_ItemSlotCollectionDefinition::IsValidSlotIndex(int32 SlotIndex) const
|
||||
{
|
||||
return SlotDefinitions.IsValidIndex(SlotIndex);
|
||||
}
|
||||
|
||||
int32 UGIS_ItemSlotCollectionDefinition::GetIndexOfSlot(const FGameplayTag& SlotName) const
|
||||
{
|
||||
return TagToIndexMap.Contains(SlotName) ? TagToIndexMap[SlotName] : INDEX_NONE;
|
||||
}
|
||||
|
||||
FGameplayTag UGIS_ItemSlotCollectionDefinition::GetSlotOfIndex(int32 SlotIndex) const
|
||||
{
|
||||
return SlotDefinitions.IsValidIndex(SlotIndex) ? SlotDefinitions[SlotIndex].Tag : FGameplayTag::EmptyTag;
|
||||
}
|
||||
|
||||
const TArray<FGIS_ItemSlotDefinition>& UGIS_ItemSlotCollectionDefinition::GetSlotDefinitions() const
|
||||
{
|
||||
return SlotDefinitions;
|
||||
}
|
||||
|
||||
bool UGIS_ItemSlotCollectionDefinition::GetSlotDefinition(int32 SlotIndex, FGIS_ItemSlotDefinition& OutDefinition) const
|
||||
{
|
||||
if (SlotDefinitions.IsValidIndex(SlotIndex))
|
||||
{
|
||||
OutDefinition = SlotDefinitions[SlotIndex];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UGIS_ItemSlotCollectionDefinition::GetSlotDefinition(const FGameplayTag& SlotName, FGIS_ItemSlotDefinition& OutDefinition) const
|
||||
{
|
||||
if (TagToIndexMap.Contains(SlotName))
|
||||
{
|
||||
check(SlotDefinitions.IsValidIndex(TagToIndexMap[SlotName]));
|
||||
OutDefinition = SlotDefinitions[TagToIndexMap[SlotName]];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 UGIS_ItemSlotCollectionDefinition::GetSlotIndexWithinGroup(FGameplayTag GroupTag, FGameplayTag SlotTag) const
|
||||
{
|
||||
if (SlotGroupMap.Contains(GroupTag))
|
||||
{
|
||||
if (SlotGroupMap[GroupTag].SlotToIndexMap.Contains(SlotTag))
|
||||
{
|
||||
return SlotGroupMap[GroupTag].SlotToIndexMap[SlotTag];
|
||||
}
|
||||
}
|
||||
return INDEX_NONE;
|
||||
}
|
||||
|
||||
UGIS_ItemSlotCollection::UGIS_ItemSlotCollection(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
|
||||
{
|
||||
}
|
||||
|
||||
void UGIS_ItemSlotCollection::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||
{
|
||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||
|
||||
// DOREPLIFETIME(ThisClass, ItemBySlots)
|
||||
}
|
||||
|
||||
const UGIS_ItemSlotCollectionDefinition* UGIS_ItemSlotCollection::GetMyDefinition() const
|
||||
{
|
||||
return MyDefinition;
|
||||
}
|
||||
|
||||
FGIS_ItemInfo UGIS_ItemSlotCollection::AddItem(const FGIS_ItemInfo& ItemInfo)
|
||||
{
|
||||
if (!ItemInfo.IsValid())
|
||||
{
|
||||
return FGIS_ItemInfo(ItemInfo.Item, 0, this);
|
||||
}
|
||||
return AddItem(ItemInfo, GetTargetSlotIndex(ItemInfo.Item));
|
||||
}
|
||||
|
||||
FGIS_ItemInfo UGIS_ItemSlotCollection::AddItem(const FGIS_ItemInfo& ItemInfo, int32 SlotIndex)
|
||||
{
|
||||
FGIS_ItemInfo ItemInfoAdded = AddItemInternal(ItemInfo, SlotIndex);
|
||||
|
||||
if (ItemInfoAdded.Amount < ItemInfo.Amount)
|
||||
{
|
||||
HandleItemOverflow(ItemInfo, ItemInfoAdded);
|
||||
}
|
||||
return ItemInfoAdded;
|
||||
}
|
||||
|
||||
void UGIS_ItemSlotCollection::ServerAddItem_Implementation(const FGIS_ItemInfo& ItemInfo, int32 SlotIndex)
|
||||
{
|
||||
AddItem(ItemInfo, SlotIndex);
|
||||
}
|
||||
|
||||
FGIS_ItemInfo UGIS_ItemSlotCollection::AddItemBySlotName(const FGIS_ItemInfo& ItemInfo, FGameplayTag SlotName)
|
||||
{
|
||||
int32 Index = MyDefinition->GetIndexOfSlot(SlotName);
|
||||
if (Index <= INDEX_NONE)
|
||||
{
|
||||
return FGIS_ItemInfo(ItemInfo.Item, 0, this);
|
||||
}
|
||||
|
||||
return AddItem(ItemInfo, Index);
|
||||
}
|
||||
|
||||
void UGIS_ItemSlotCollection::ServerAddItemBySlotName_Implementation(const FGIS_ItemInfo& ItemInfo, FGameplayTag SlotName)
|
||||
{
|
||||
AddItemBySlotName(ItemInfo, SlotName);
|
||||
}
|
||||
|
||||
FGIS_ItemInfo UGIS_ItemSlotCollection::RemoveItem(const FGIS_ItemInfo& ItemInfo)
|
||||
{
|
||||
int32 SlotIndex = GetItemSlotIndex(ItemInfo.Item);
|
||||
if (SlotIndex == INDEX_NONE)
|
||||
{
|
||||
return FGIS_ItemInfo(ItemInfo.Item, 0, this);
|
||||
}
|
||||
|
||||
return RemoveItem(SlotIndex, ItemInfo.Amount);
|
||||
}
|
||||
|
||||
FGIS_ItemInfo UGIS_ItemSlotCollection::RemoveItem(int32 SlotIndex, int32 Amount)
|
||||
{
|
||||
if (!SlotToStackMap.Contains(SlotIndex))
|
||||
{
|
||||
return FGIS_ItemInfo(nullptr, 0, this);
|
||||
}
|
||||
|
||||
const FGIS_ItemStack* ItemToRemove = Container.FindById(SlotToStackMap[SlotIndex]);
|
||||
|
||||
if (ItemToRemove == nullptr)
|
||||
{
|
||||
return FGIS_ItemInfo(nullptr, 0, this);
|
||||
}
|
||||
|
||||
int32 AmountToRemove = Amount > 0 ? Amount : ItemToRemove->Amount;
|
||||
FGIS_ItemInfo Removed = RemoveInternal(FGIS_ItemInfo(ItemToRemove->Item, AmountToRemove, this, ItemToRemove->Id));
|
||||
|
||||
return Removed;
|
||||
}
|
||||
|
||||
bool UGIS_ItemSlotCollection::IsItemFitWithSlot(const UGIS_ItemInstance* Item, int32 SlotIndex) const
|
||||
{
|
||||
if (!IsValid(Item))
|
||||
{
|
||||
GIS_CLOG(Error, "invalid item")
|
||||
return false;
|
||||
}
|
||||
const TArray<FGIS_ItemSlotDefinition>& SlotDefinitions = MyDefinition->SlotDefinitions;
|
||||
if (SlotDefinitions.IsEmpty() || !SlotDefinitions.IsValidIndex(SlotIndex))
|
||||
{
|
||||
GIS_CLOG(Error, "has empty slots!")
|
||||
return false;
|
||||
}
|
||||
|
||||
const FGIS_ItemSlotDefinition& SlotDefinition = SlotDefinitions[SlotIndex];
|
||||
|
||||
// doesn't match definition.
|
||||
if (!SlotDefinition.MatchItem(Item))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// slot is empty.
|
||||
if (!SlotToStackMap.Contains(SlotIndex))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// slot is not empty, check if it can stack.
|
||||
if (!Item->IsUnique())
|
||||
{
|
||||
if (const FGIS_ItemStack* ExistingStack = Container.FindById(SlotToStackMap[SlotIndex]))
|
||||
{
|
||||
if (ExistingStack->Item->StackableEquivalentTo(Item))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// can't stack, check new item can replace existing item.
|
||||
return MyDefinition->bNewItemPriority;
|
||||
}
|
||||
|
||||
int32 UGIS_ItemSlotCollection::GetTargetSlotIndex(const UGIS_ItemInstance* Item) const
|
||||
{
|
||||
const TArray<FGIS_ItemSlotDefinition>& SlotDefinitions = MyDefinition->SlotDefinitions;
|
||||
if (SlotDefinitions.IsEmpty())
|
||||
{
|
||||
GIS_CLOG(Error, "has empty slots!")
|
||||
return INDEX_NONE;
|
||||
}
|
||||
|
||||
int32 FoundUsedSlot = INDEX_NONE;
|
||||
int32 FoundEmptySlot = INDEX_NONE;
|
||||
|
||||
for (int32 i = 0; i < SlotDefinitions.Num(); i++)
|
||||
{
|
||||
const FGIS_ItemSlotDefinition& SlotDefinition = SlotDefinitions[i];
|
||||
if (!SlotDefinition.MatchItem(Item))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
FoundUsedSlot = i;
|
||||
|
||||
// i对应的槽为空
|
||||
if (!SlotToStackMap.Contains(i))
|
||||
{
|
||||
if (FoundEmptySlot != INDEX_NONE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
FoundEmptySlot = i;
|
||||
continue;
|
||||
}
|
||||
|
||||
//非唯一且可堆叠.
|
||||
if (!Item->IsUnique())
|
||||
{
|
||||
if (const FGIS_ItemStack* ExistingStack = Container.FindById(SlotToStackMap[i]))
|
||||
{
|
||||
if (ExistingStack->Item->StackableEquivalentTo(Item))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (FoundEmptySlot != INDEX_NONE)
|
||||
{
|
||||
return FoundEmptySlot;
|
||||
}
|
||||
return FoundUsedSlot;
|
||||
}
|
||||
|
||||
bool UGIS_ItemSlotCollection::GetItemInfoAtSlot(FGameplayTag SlotTag, FGIS_ItemInfo& OutItemInfo) const
|
||||
{
|
||||
int32 Index = MyDefinition->GetIndexOfSlot(SlotTag);
|
||||
|
||||
if (Index != INDEX_NONE)
|
||||
{
|
||||
OutItemInfo = GetItemInfoAtSlot(Index);
|
||||
|
||||
return OutItemInfo.IsValid();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UGIS_ItemSlotCollection::FindItemInfoAtSlot(FGameplayTag SlotTag, FGIS_ItemInfo& OutItemInfo) const
|
||||
{
|
||||
return GetItemInfoAtSlot(SlotTag, OutItemInfo);
|
||||
}
|
||||
|
||||
bool UGIS_ItemSlotCollection::GetItemStackAtSlot(FGameplayTag SlotTag, FGIS_ItemStack& OutItemStack) const
|
||||
{
|
||||
int32 Index = MyDefinition->GetIndexOfSlot(SlotTag);
|
||||
|
||||
if (Index != INDEX_NONE)
|
||||
{
|
||||
OutItemStack = GetItemStackAtSlot(Index);
|
||||
return OutItemStack.IsValidStack();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UGIS_ItemSlotCollection::FindItemStackAtSlot(FGameplayTag SlotTag, FGIS_ItemStack& OutItemStack) const
|
||||
{
|
||||
return GetItemStackAtSlot(SlotTag, OutItemStack);
|
||||
}
|
||||
|
||||
FGIS_ItemInfo UGIS_ItemSlotCollection::GetItemInfoAtSlot(int32 SlotIndex) const
|
||||
{
|
||||
if (SlotIndex < 0) { return FGIS_ItemInfo::None; }
|
||||
|
||||
FGIS_ItemStack ItemStack = GetItemStackAtSlot(SlotIndex);
|
||||
if (ItemStack.IsValidStack())
|
||||
{
|
||||
return FGIS_ItemInfo(ItemStack.Item, ItemStack.Amount, ItemStack.Collection);
|
||||
}
|
||||
return FGIS_ItemInfo::None;
|
||||
}
|
||||
|
||||
FGIS_ItemStack UGIS_ItemSlotCollection::GetItemStackAtSlot(int32 SlotIndex) const
|
||||
{
|
||||
if (SlotToStackMap.Contains(SlotIndex))
|
||||
{
|
||||
if (const FGIS_ItemStack* Stack = Container.FindById(SlotToStackMap[SlotIndex]))
|
||||
{
|
||||
return *Stack;
|
||||
}
|
||||
}
|
||||
return FGIS_ItemStack();
|
||||
}
|
||||
|
||||
FGameplayTag UGIS_ItemSlotCollection::GetItemSlotName(const UGIS_ItemInstance* Item) const
|
||||
{
|
||||
int32 SlotIndex = GetItemSlotIndex(Item);
|
||||
return MyDefinition->GetSlotOfIndex(SlotIndex);
|
||||
}
|
||||
|
||||
int32 UGIS_ItemSlotCollection::GetItemSlotIndex(const UGIS_ItemInstance* Item) const
|
||||
{
|
||||
const TArray<FGIS_ItemSlotDefinition>& SlotDefinitions = MyDefinition->SlotDefinitions;
|
||||
|
||||
int32 StackableEquivalentItemIndex = INDEX_NONE;
|
||||
for (int i = 0; i < SlotDefinitions.Num(); i++)
|
||||
{
|
||||
const FGIS_ItemSlotDefinition& SlotDefinition = SlotDefinitions[i];
|
||||
if (!SlotDefinition.MatchItem(Item))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!SlotToStackMap.Contains(i) || !SlotToStackMap[i].IsValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (const FGIS_ItemStack* Stack = Container.FindById(SlotToStackMap[i]))
|
||||
{
|
||||
if (Stack->Item == Item)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
|
||||
if (Stack->Item->StackableEquivalentTo(Item))
|
||||
{
|
||||
StackableEquivalentItemIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return StackableEquivalentItemIndex;
|
||||
}
|
||||
|
||||
FGIS_ItemInfo UGIS_ItemSlotCollection::AddItemInternal(const FGIS_ItemInfo& ItemInfo, int32 SlotIndex)
|
||||
{
|
||||
FGIS_ItemInfo CanAddItemInfo;
|
||||
const bool CanAddResult = CanAddItem(ItemInfo, CanAddItemInfo);
|
||||
if (!CanAddResult)
|
||||
{
|
||||
return FGIS_ItemInfo(ItemInfo.Item, 0, this);
|
||||
}
|
||||
|
||||
if (SlotIndex == INDEX_NONE)
|
||||
{
|
||||
GIS_CLOG(Warning, "invalid valid target slot(%d) to put item:%s", SlotIndex, *ItemInfo.GetDebugString())
|
||||
return FGIS_ItemInfo(ItemInfo.Item, 0, this);
|
||||
}
|
||||
|
||||
FGIS_ItemSlotDefinition SlotDefinition;
|
||||
if (!MyDefinition->GetSlotDefinition(SlotIndex, SlotDefinition))
|
||||
{
|
||||
GIS_CLOG(Verbose, "No slot definition found for slot index:%d", SlotIndex)
|
||||
return FGIS_ItemInfo(ItemInfo.Item, 0, this);
|
||||
}
|
||||
|
||||
if (!MyDefinition->bNewItemPriority && CanAddItemInfo.Item->IsUnique() && SlotToStackMap.Contains(SlotIndex) && SlotToStackMap[SlotIndex].IsValid())
|
||||
{
|
||||
GIS_CLOG(Verbose, "Can't add item info because the target slot:%s was occupied. ItemInfo:%s.", *SlotDefinition.Tag.ToString(), *ItemInfo.GetDebugString())
|
||||
return FGIS_ItemInfo(ItemInfo.Item, 0, this);
|
||||
}
|
||||
|
||||
// similar item amount for this item.
|
||||
int32 CurrentAmount = GetItemAmount(CanAddItemInfo.Item.Get());
|
||||
|
||||
FGIS_ItemInfo SetItemInfo;
|
||||
if (SetItemAmount(FGIS_ItemInfo(CanAddItemInfo.Amount + CurrentAmount, CanAddItemInfo), SlotIndex, true, SetItemInfo))
|
||||
{
|
||||
return SetItemInfo;
|
||||
}
|
||||
return FGIS_ItemInfo(ItemInfo.Item, 0, this);
|
||||
}
|
||||
|
||||
int32 UGIS_ItemSlotCollection::StackIdToSlotIndex(FGuid InStackId) const
|
||||
{
|
||||
if (!InStackId.IsValid())
|
||||
{
|
||||
return INDEX_NONE;
|
||||
}
|
||||
if (StackToIdxMap.Contains(InStackId))
|
||||
{
|
||||
return StackToIdxMap[InStackId];
|
||||
}
|
||||
return INDEX_NONE;
|
||||
}
|
||||
|
||||
int32 UGIS_ItemSlotCollection::SlotIndexToStackIndex(int32 InSlotIndex) const
|
||||
{
|
||||
if (SlotToStackMap.Contains(InSlotIndex))
|
||||
{
|
||||
return Container.IndexOfById(SlotToStackMap[InSlotIndex]);
|
||||
}
|
||||
return INDEX_NONE;
|
||||
}
|
||||
|
||||
FGuid UGIS_ItemSlotCollection::SlotIndexToStackId(int32 InSlotIndex) const
|
||||
{
|
||||
return SlotToStackMap.Contains(InSlotIndex) && SlotToStackMap[InSlotIndex].IsValid() ? SlotToStackMap[InSlotIndex] : FGuid();
|
||||
}
|
||||
|
||||
void UGIS_ItemSlotCollection::OnRep_ItemsBySlot()
|
||||
{
|
||||
}
|
||||
|
||||
void UGIS_ItemSlotCollection::SetDefinition(const UGIS_ItemCollectionDefinition* NewDefinition)
|
||||
{
|
||||
Super::SetDefinition(NewDefinition);
|
||||
check(OwningInventory != nullptr)
|
||||
check(Definition != nullptr)
|
||||
if (bInitialized)
|
||||
{
|
||||
MyDefinition = CastChecked<UGIS_ItemSlotCollectionDefinition>(Definition);
|
||||
if (OwningInventory->GetOwnerRole() >= ROLE_Authority)
|
||||
{
|
||||
if (MyDefinition->SlotDefinitions.Num() == 0)
|
||||
{
|
||||
GIS_CLOG(Error, "has empty slots")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGIS_ItemSlotCollection::OnPreItemStackAdded(const FGIS_ItemStack& Stack, int32 Idx)
|
||||
{
|
||||
Super::OnPreItemStackAdded(Stack, Idx);
|
||||
}
|
||||
|
||||
void UGIS_ItemSlotCollection::OnItemStackAdded(const FGIS_ItemStack& Stack)
|
||||
{
|
||||
check(Stack.IsValidStack())
|
||||
if (OwningInventory)
|
||||
{
|
||||
SlotToStackMap.Add(Stack.Index, Stack.Id);
|
||||
}
|
||||
Super::OnItemStackAdded(Stack);
|
||||
}
|
||||
|
||||
void UGIS_ItemSlotCollection::OnItemStackRemoved(const FGIS_ItemStack& Stack)
|
||||
{
|
||||
if (OwningInventory)
|
||||
{
|
||||
SlotToStackMap.Remove(Stack.Index);
|
||||
}
|
||||
Super::OnItemStackRemoved(Stack);
|
||||
}
|
||||
|
||||
bool UGIS_ItemSlotCollection::SetItemAmount(const FGIS_ItemInfo& ItemInfo, int32 SlotIndex, bool RemovePreviousItem, FGIS_ItemInfo& ItemInfoAdded)
|
||||
{
|
||||
if (!OwningInventory->GetOwner()->HasAuthority())
|
||||
{
|
||||
GIS_CLOG(Warning, "has no authority!");
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 Amount = ItemInfo.Amount;
|
||||
|
||||
int32 StackIdx = SlotIndexToStackIndex(SlotIndex);
|
||||
|
||||
// Found valid stack
|
||||
if (StackIdx != INDEX_NONE)
|
||||
{
|
||||
const FGIS_ItemStack& CurrentStack = Container.Stacks[StackIdx];
|
||||
|
||||
if (ItemInfo.Item->StackableEquivalentTo(CurrentStack.Item))
|
||||
{
|
||||
// reduce existing amount to get the amount needed to add.
|
||||
Amount -= CurrentStack.Amount;
|
||||
}
|
||||
else if (RemovePreviousItem)
|
||||
{
|
||||
FGIS_ItemInfo RemovedItem = RemoveItem(FGIS_ItemInfo(CurrentStack));
|
||||
if (RemovedItem.Amount > 0)
|
||||
{
|
||||
FGIS_ItemInfo CanAddItemInfo;
|
||||
if (MyDefinition->bTryGivePrevItemToNewItemCollection && ItemInfo.ItemCollection != nullptr && ItemInfo.ItemCollection->CanAddItem(RemovedItem, CanAddItemInfo))
|
||||
{
|
||||
GIS_CLOG(Verbose, "An existing item has been replaced by a new one, and the old one has been added to the collection where the new item coming from. prev:%s new:%s",
|
||||
*RemovedItem.GetDebugString(), *ItemInfo.GetDebugString())
|
||||
ItemInfo.ItemCollection->AddItem(CanAddItemInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
GIS_CLOG(Verbose, "An item has been replaced by a new one, and the old one has gone forever! prev: %s new:%s",
|
||||
*RemovedItem.GetDebugString(), *ItemInfo.GetDebugString())
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Not found, add new item.
|
||||
ItemInfoAdded = AddInternal(FGIS_ItemInfo(Amount, SlotIndex, ItemInfo));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
void UGIS_ItemSlotCollectionDefinition::PreSave(FObjectPreSaveContext SaveContext)
|
||||
{
|
||||
// remove repeated slot by name.
|
||||
{
|
||||
TArray<FGameplayTag> SlotNames;
|
||||
TArray<FGIS_ItemSlotDefinition> Slots;
|
||||
for (int32 i = 0; i < SlotDefinitions.Num(); ++i)
|
||||
{
|
||||
if (!SlotNames.Contains(SlotDefinitions[i].Tag))
|
||||
{
|
||||
Slots.Add(SlotDefinitions[i]);
|
||||
SlotNames.Add(SlotDefinitions[i].Tag);
|
||||
}
|
||||
}
|
||||
SlotDefinitions = Slots;
|
||||
}
|
||||
|
||||
IndexToTagMap.Empty();
|
||||
TagToIndexMap.Empty();
|
||||
|
||||
for (int32 i = 0; i < SlotDefinitions.Num(); ++i)
|
||||
{
|
||||
if (!SlotDefinitions[i].Tag.IsValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!IndexToTagMap.Contains(i))
|
||||
{
|
||||
IndexToTagMap.Add(i, SlotDefinitions[i].Tag);
|
||||
}
|
||||
if (!TagToIndexMap.Contains(SlotDefinitions[i].Tag))
|
||||
{
|
||||
TagToIndexMap.Add(SlotDefinitions[i].Tag, i);
|
||||
}
|
||||
}
|
||||
|
||||
TArray<FGIS_ItemSlotGroup> Groups;
|
||||
SlotGroupMap.Empty();
|
||||
for (int32 i = 0; i < SlotGroups.Num(); i++)
|
||||
{
|
||||
FGIS_ItemSlotGroup Group;
|
||||
int32 Idx = 0;
|
||||
for (int32 j = 0; j < SlotDefinitions.Num(); j++)
|
||||
{
|
||||
if (SlotDefinitions[j].Tag.MatchesTag(SlotGroups[i]))
|
||||
{
|
||||
Group.IndexToSlotMap.Add(Idx, SlotDefinitions[j].Tag);
|
||||
Group.SlotToIndexMap.Add(SlotDefinitions[j].Tag, Idx);
|
||||
Idx++;
|
||||
}
|
||||
}
|
||||
if (!Group.IndexToSlotMap.IsEmpty())
|
||||
{
|
||||
SlotGroupMap.Add(SlotGroups[i], Group);
|
||||
}
|
||||
}
|
||||
|
||||
Super::PreSave(SaveContext);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Fragments/GIS_ItemFragment.h"
|
||||
|
||||
#include "Items/GIS_ItemStack.h"
|
||||
#include "Misc/DataValidation.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_ItemFragment)
|
||||
|
||||
|
||||
bool UGIS_ItemFragment::IsMixinDataSerializable() const
|
||||
{
|
||||
return IsStateSerializable();
|
||||
}
|
||||
|
||||
TObjectPtr<const UScriptStruct> UGIS_ItemFragment::GetCompatibleMixinDataType() const
|
||||
{
|
||||
return GetCompatibleStateType();
|
||||
}
|
||||
|
||||
bool UGIS_ItemFragment::MakeDefaultMixinData(FInstancedStruct& DefaultState) const
|
||||
{
|
||||
return MakeDefaultState(DefaultState);
|
||||
}
|
||||
|
||||
|
||||
bool UGIS_ItemFragment::MakeDefaultState_Implementation(FInstancedStruct& DefaultState) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const UScriptStruct* UGIS_ItemFragment::GetCompatibleStateType_Implementation() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool UGIS_ItemFragment::IsStateSerializable_Implementation() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
bool UGIS_ItemFragment::FragmentDataValidation_Implementation(FText& OutMessage) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GIS_ItemFragment_DynamicAttributes.h"
|
||||
|
||||
#include "Items/GIS_ItemInstance.h"
|
||||
#include "UObject/ObjectSaveContext.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_ItemFragment_DynamicAttributes)
|
||||
|
||||
void UGIS_ItemFragment_DynamicAttributes::OnInstanceCreated(UGIS_ItemInstance* Instance) const
|
||||
{
|
||||
for (int32 i = 0; i < InitialFloatAttributes.Num(); i++)
|
||||
{
|
||||
if (InitialFloatAttributes[i].Tag.IsValid())
|
||||
{
|
||||
Instance->SetFloatAttribute(InitialFloatAttributes[i].Tag, InitialFloatAttributes[i].Value);
|
||||
}
|
||||
}
|
||||
for (int32 i = 0; i < InitialIntegerAttributes.Num(); i++)
|
||||
{
|
||||
if (InitialIntegerAttributes[i].Tag.IsValid())
|
||||
{
|
||||
Instance->SetIntegerAttribute(InitialIntegerAttributes[i].Tag, InitialIntegerAttributes[i].Value);
|
||||
}
|
||||
}
|
||||
// for (int32 i=0;i<InitialBoolAttributes.Num();i++)
|
||||
// {
|
||||
// Instance->SetBoolAttribute(InitialBoolAttributes[i].Tag,InitialBoolAttributes[i].Value);
|
||||
// }
|
||||
}
|
||||
|
||||
float UGIS_ItemFragment_DynamicAttributes::GetFloatAttributeDefault(FGameplayTag AttributeTag) const
|
||||
{
|
||||
return FloatAttributeMap.Contains(AttributeTag) ? FloatAttributeMap[AttributeTag] : 0;
|
||||
}
|
||||
|
||||
int32 UGIS_ItemFragment_DynamicAttributes::GetIntegerAttributeDefault(FGameplayTag AttributeTag) const
|
||||
{
|
||||
return IntegerAttributeMap.Contains(AttributeTag) ? IntegerAttributeMap[AttributeTag] : 0;
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
void UGIS_ItemFragment_DynamicAttributes::PreSave(FObjectPreSaveContext SaveContext)
|
||||
{
|
||||
FloatAttributeMap.Empty();
|
||||
for (const FGIS_GameplayTagFloat& Attribute : InitialFloatAttributes)
|
||||
{
|
||||
if (FloatAttributeMap.Contains(Attribute.Tag))
|
||||
{
|
||||
FloatAttributeMap.Add(Attribute.Tag, Attribute.Value);
|
||||
}
|
||||
}
|
||||
|
||||
IntegerAttributeMap.Empty();
|
||||
for (const FGIS_GameplayTagInteger& Attribute : InitialIntegerAttributes)
|
||||
{
|
||||
if (IntegerAttributeMap.Contains(Attribute.Tag))
|
||||
{
|
||||
IntegerAttributeMap.Add(Attribute.Tag, Attribute.Value);
|
||||
}
|
||||
}
|
||||
|
||||
Super::PreSave(SaveContext);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GIS_ItemFragment_Equippable.h"
|
||||
|
||||
#include "GIS_EquipmentInstance.h"
|
||||
#include "UObject/ObjectSaveContext.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_ItemFragment_Equippable)
|
||||
|
||||
|
||||
UGIS_ItemFragment_Equippable::UGIS_ItemFragment_Equippable()
|
||||
{
|
||||
InstanceType = UGIS_EquipmentInstance::StaticClass();
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
void UGIS_ItemFragment_Equippable::PreSave(FObjectPreSaveContext SaveContext)
|
||||
{
|
||||
if (InstanceType.IsNull())
|
||||
{
|
||||
bActorBased = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
UClass* InstanceClass = InstanceType.LoadSynchronous();
|
||||
if (InstanceClass != nullptr)
|
||||
{
|
||||
bActorBased = InstanceClass->IsChildOf(AActor::StaticClass());
|
||||
}
|
||||
}
|
||||
Super::PreSave(SaveContext);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GIS_ItemFragment_Shoppable.h"
|
||||
#include "GIS_InventoryTags.h"
|
||||
#include "Items/GIS_ItemInstance.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_ItemFragment_Shoppable)
|
||||
|
||||
void UGIS_ItemFragment_Shoppable::OnInstanceCreated(UGIS_ItemInstance* Instance) const
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GIS_CoreStructLibray.h"
|
||||
|
||||
#include "Items/GIS_ItemDefinition.h"
|
||||
#include "Items/GIS_ItemInstance.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_CoreStructLibray)
|
||||
|
||||
|
||||
FGIS_ItemDefinitionAmount::FGIS_ItemDefinitionAmount()
|
||||
{
|
||||
Definition = nullptr;
|
||||
Amount = 1;
|
||||
}
|
||||
|
||||
FGIS_ItemDefinitionAmount::FGIS_ItemDefinitionAmount(TSoftObjectPtr<UGIS_ItemDefinition> InDefinition, int32 InAmount)
|
||||
{
|
||||
Definition = InDefinition;
|
||||
Amount = InAmount;
|
||||
}
|
||||
|
||||
bool FGIS_ItemSlotDefinition::MatchItem(const UGIS_ItemInstance* Item) const
|
||||
{
|
||||
if (!IsValid(Item))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TagQuery.IsEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TagQuery.Matches(Item->GetDefinition()->ItemTags))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "GIS_MixinContainer.h"
|
||||
#include "Engine/World.h"
|
||||
#include "AssetRegistry/AssetData.h"
|
||||
#include "GIS_ItemFragment.h"
|
||||
#include "GIS_LogChannels.h"
|
||||
#include "GIS_MixinOwnerInterface.h"
|
||||
#include "AssetRegistry/AssetRegistryModule.h"
|
||||
#include "Items/GIS_ItemInstance.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_MixinContainer)
|
||||
|
||||
uint32 GetTypeHash(const FGIS_Mixin& Entry)
|
||||
{
|
||||
return HashCombine(GetTypeHash(Entry.Target), Entry.Timestamp);
|
||||
}
|
||||
|
||||
int32 FGIS_MixinContainer::IndexOfTarget(const UObject* Target) const
|
||||
{
|
||||
if (!IsValid(Target))
|
||||
{
|
||||
return INDEX_NONE;
|
||||
}
|
||||
return AcceleratedMap.Contains(Target->GetClass()) ? AcceleratedMap[Target->GetClass()] : INDEX_NONE;
|
||||
}
|
||||
|
||||
int32 FGIS_MixinContainer::IndexOfTargetByClass(const TSubclassOf<UObject>& TargetClass) const
|
||||
{
|
||||
check(IsValid(TargetClass));
|
||||
|
||||
const int32* Idx = AcceleratedMap.Find(TargetClass);
|
||||
if (Idx != nullptr)
|
||||
{
|
||||
return *Idx;
|
||||
}
|
||||
|
||||
return INDEX_NONE;
|
||||
}
|
||||
|
||||
// bool FGIS_MixinContainer::GetDataByTargetClass(const TSubclassOf<UObject>& TargetClass, FInstancedStruct& OutData) const
|
||||
// {
|
||||
// if (AcceleratedMap.Contains(TargetClass) && Mixins.IsValidIndex(AcceleratedMap[TargetClass]))
|
||||
// {
|
||||
// OutData = Mixins[AcceleratedMap[TargetClass]].Data;
|
||||
// return true;
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
|
||||
bool FGIS_MixinRecord::operator==(const FGIS_MixinRecord& Other) const
|
||||
{
|
||||
return TargetPath == Other.TargetPath && Data.GetScriptStruct() == Other.Data.GetScriptStruct();
|
||||
}
|
||||
|
||||
bool FGIS_MixinRecord::IsValid() const
|
||||
{
|
||||
return !TargetPath.IsEmpty() && Data.IsValid();
|
||||
}
|
||||
|
||||
bool FGIS_MixinContainer::GetDataByTarget(const UObject* Target, FInstancedStruct& OutData) const
|
||||
{
|
||||
if (AcceleratedMap.Contains(Target) && Mixins.IsValidIndex(AcceleratedMap[Target]))
|
||||
{
|
||||
OutData = Mixins[AcceleratedMap[Target]].Data;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 FGIS_MixinContainer::SetDataForTarget(const TObjectPtr<const UObject>& Target, const FInstancedStruct& Data)
|
||||
{
|
||||
if (Target == nullptr || !Data.IsValid())
|
||||
{
|
||||
return INDEX_NONE;
|
||||
}
|
||||
|
||||
if (!IsObjectLoadedFromDisk(Target))
|
||||
{
|
||||
return INDEX_NONE;
|
||||
}
|
||||
|
||||
if (AcceleratedMap.Contains(Target))
|
||||
{
|
||||
return UpdateDataAt(AcceleratedMap[Target], Data);
|
||||
}
|
||||
|
||||
// is not valid class -> data pair.
|
||||
if (!CheckCompatibility(Target, Data))
|
||||
{
|
||||
return INDEX_NONE;
|
||||
}
|
||||
|
||||
int32 Idx = Mixins.AddDefaulted();
|
||||
FGIS_Mixin& NewMixin = Mixins[Idx];
|
||||
NewMixin.Target = Target;
|
||||
NewMixin.Data = Data;
|
||||
NewMixin.Timestamp = OwningObject->GetWorld()->GetTimeSeconds();
|
||||
if (IGIS_MixinOwnerInterface* MixinOwner = Cast<IGIS_MixinOwnerInterface>(OwningObject))
|
||||
{
|
||||
MixinOwner->OnMixinDataAdded(NewMixin.Target, NewMixin.Data);
|
||||
}
|
||||
MarkItemDirty(NewMixin);
|
||||
CacheMixins();
|
||||
|
||||
return Idx;
|
||||
}
|
||||
|
||||
bool FGIS_MixinContainer::IsObjectLoadedFromDisk(const UObject* Object) const
|
||||
{
|
||||
if (!IsValid(Object))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取资源路径
|
||||
FSoftObjectPath AssetPath(Object);
|
||||
if (AssetPath.IsNull())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查资产注册表
|
||||
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
|
||||
IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
|
||||
|
||||
FAssetData AssetData = AssetRegistry.GetAssetByObjectPath(AssetPath);
|
||||
return AssetData.IsValid();
|
||||
}
|
||||
|
||||
int32 FGIS_MixinContainer::UpdateDataByTargetClass(const TSubclassOf<UObject>& TargetClass, const FInstancedStruct& Data)
|
||||
{
|
||||
if (AcceleratedMap.Contains(TargetClass))
|
||||
{
|
||||
return UpdateDataAt(AcceleratedMap[TargetClass], Data);
|
||||
}
|
||||
return INDEX_NONE;
|
||||
}
|
||||
|
||||
int32 FGIS_MixinContainer::UpdateDataAt(const int32 Idx, const FInstancedStruct& Data)
|
||||
{
|
||||
if (Idx == INDEX_NONE || !Mixins.IsValidIndex(Idx))
|
||||
{
|
||||
return INDEX_NONE;
|
||||
}
|
||||
|
||||
FGIS_Mixin& Entry = Mixins[Idx];
|
||||
|
||||
if (!CheckCompatibility(Entry.Target, Data))
|
||||
{
|
||||
return INDEX_NONE;
|
||||
}
|
||||
|
||||
Entry.Data = Data;
|
||||
Entry.Timestamp = OwningObject->GetWorld()->GetTimeSeconds();
|
||||
if (IGIS_MixinOwnerInterface* MixinOwner = Cast<IGIS_MixinOwnerInterface>(OwningObject))
|
||||
{
|
||||
MixinOwner->OnMixinDataUpdated(Entry.Target, Entry.Data);
|
||||
}
|
||||
MarkItemDirty(Entry);
|
||||
CacheMixins();
|
||||
|
||||
return Idx;
|
||||
}
|
||||
|
||||
void FGIS_MixinContainer::RemoveDataByTargetClass(const TSubclassOf<UObject>& TargetClass)
|
||||
{
|
||||
const int32 Idx = AcceleratedMap.Contains(TargetClass) ? AcceleratedMap[TargetClass] : INDEX_NONE;
|
||||
|
||||
if (Idx != INDEX_NONE)
|
||||
{
|
||||
const FGIS_Mixin& Entry = Mixins[Idx];
|
||||
if (IGIS_MixinOwnerInterface* MixinOwner = Cast<IGIS_MixinOwnerInterface>(OwningObject))
|
||||
{
|
||||
MixinOwner->OnMixinDataRemoved(Entry.Target, Entry.Data);
|
||||
}
|
||||
Mixins.RemoveAt(Idx);
|
||||
MarkArrayDirty();
|
||||
}
|
||||
|
||||
CacheMixins();
|
||||
}
|
||||
|
||||
bool FGIS_MixinContainer::CheckCompatibility(const UObject* Target, const FInstancedStruct& Data) const
|
||||
{
|
||||
if (!IsValid(Target) || !Data.IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (const IGIS_MixinTargetInterface* MixinTarget = Cast<IGIS_MixinTargetInterface>(Target))
|
||||
{
|
||||
return MixinTarget->GetCompatibleMixinDataType() == Data.GetScriptStruct();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
TArray<FInstancedStruct> FGIS_MixinContainer::GetAllData() const
|
||||
{
|
||||
TArray<FInstancedStruct> AllData;
|
||||
AllData.Reserve(Mixins.Num());
|
||||
|
||||
for (const FGIS_Mixin& Mixin : Mixins)
|
||||
{
|
||||
AllData.Add(Mixin.Data);
|
||||
}
|
||||
|
||||
return AllData;
|
||||
}
|
||||
|
||||
TArray<FGIS_Mixin> FGIS_MixinContainer::GetSerializableMixins() const
|
||||
{
|
||||
return Mixins.FilterByPredicate([this](const FGIS_Mixin& Mixin)
|
||||
{
|
||||
if (const IGIS_MixinTargetInterface* MixinTarget = Cast<IGIS_MixinTargetInterface>(Mixin.Target))
|
||||
{
|
||||
return MixinTarget->IsMixinDataSerializable() && Mixin.Data.IsValid() && MixinTarget->GetCompatibleMixinDataType() == Mixin.Data.GetScriptStruct() && IsObjectLoadedFromDisk(Mixin.Target);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
TArray<FGIS_MixinRecord> FGIS_MixinContainer::GetSerializableMixinRecords() const
|
||||
{
|
||||
TArray<FGIS_MixinRecord> Records;
|
||||
TArray<FGIS_Mixin> FilteredMixins = GetSerializableMixins();
|
||||
|
||||
for (const FGIS_Mixin& FilteredMixin : FilteredMixins)
|
||||
{
|
||||
FGIS_MixinRecord Record;
|
||||
const FSoftObjectPath AssetPath = FSoftObjectPath(FilteredMixin.Target);
|
||||
Record.TargetPath = AssetPath.ToString();
|
||||
Record.Data = FilteredMixin.Data;
|
||||
Records.Add(Record);
|
||||
}
|
||||
|
||||
return Records;
|
||||
}
|
||||
|
||||
void FGIS_MixinContainer::RestoreFromRecords(const TArray<FGIS_MixinRecord>& Records)
|
||||
{
|
||||
TArray<FGIS_Mixin> ConvertedMixins = ConvertRecordsToMixins(Records);
|
||||
for (const FGIS_Mixin& ConvertedMixin : ConvertedMixins)
|
||||
{
|
||||
SetDataForTarget(ConvertedMixin.Target, ConvertedMixin.Data);
|
||||
}
|
||||
}
|
||||
|
||||
TArray<FGIS_Mixin> FGIS_MixinContainer::ConvertRecordsToMixins(const TArray<FGIS_MixinRecord>& Records)
|
||||
{
|
||||
TArray<FGIS_Mixin> Ret;
|
||||
for (const FGIS_MixinRecord& Record : Records)
|
||||
{
|
||||
if (!Record.IsValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const FSoftObjectPath TargetPath = FSoftObjectPath(Record.TargetPath);
|
||||
const TSoftObjectPtr<UObject> TargetObjectSoftPtr = TSoftObjectPtr<UObject>(TargetPath);
|
||||
const TObjectPtr<const UObject> TargetObject = !TargetObjectSoftPtr.IsNull() ? TargetObjectSoftPtr.LoadSynchronous() : nullptr;
|
||||
if (!IsValid(TargetObject))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const IGIS_MixinTargetInterface* TargetInterface = Cast<IGIS_MixinTargetInterface>(TargetObject);
|
||||
if (TargetInterface == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!TargetInterface->IsMixinDataSerializable())
|
||||
{
|
||||
GIS_LOG(Warning, "Skip restoring mixin's data, as target(%s,class:%s) existed in record no longer considered serializable!",
|
||||
*GetNameSafe(TargetObject), *GetNameSafe(TargetObject->GetClass()));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TargetInterface->GetCompatibleMixinDataType() != Record.Data.GetScriptStruct())
|
||||
{
|
||||
GIS_LOG(Warning,
|
||||
"Skip restoring mixin's data, as target(%s,class:%s)'s data type(%s) in record no longer compatible with the new type(%s).",
|
||||
*GetNameSafe(TargetObject), *GetNameSafe(TargetObject->GetClass()),
|
||||
*GetNameSafe(Record.Data.GetScriptStruct()), *GetNameSafe(TargetInterface->GetCompatibleMixinDataType()));
|
||||
continue;
|
||||
}
|
||||
FGIS_Mixin Mixin;
|
||||
Mixin.Target = TargetObject;
|
||||
Mixin.Data = Record.Data;
|
||||
Ret.Add(Mixin);
|
||||
}
|
||||
return Ret;
|
||||
}
|
||||
|
||||
TArray<FInstancedStruct> FGIS_MixinContainer::GetAllSerializableData() const
|
||||
{
|
||||
TArray<FInstancedStruct> AllData;
|
||||
AllData.Reserve(Mixins.Num());
|
||||
|
||||
for (const FGIS_Mixin& Mixin : Mixins)
|
||||
{
|
||||
if (const IGIS_MixinTargetInterface* MixinTarget = Cast<IGIS_MixinTargetInterface>(Mixin.Target))
|
||||
{
|
||||
if (MixinTarget->IsMixinDataSerializable())
|
||||
{
|
||||
AllData.Add(Mixin.Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return AllData;
|
||||
}
|
||||
|
||||
void FGIS_MixinContainer::PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize)
|
||||
{
|
||||
for (const int32 Index : AddedIndices)
|
||||
{
|
||||
FGIS_Mixin& Mixin = Mixins[Index];
|
||||
if (Mixin.Timestamp != Mixin.LastReplicatedTimestamp)
|
||||
{
|
||||
Mixin.LastReplicatedTimestamp = Mixin.Timestamp;
|
||||
if (IGIS_MixinOwnerInterface* MixinOwner = Cast<IGIS_MixinOwnerInterface>(OwningObject))
|
||||
{
|
||||
MixinOwner->OnMixinDataAdded(Mixin.Target, Mixin.Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CacheMixins();
|
||||
}
|
||||
|
||||
void FGIS_MixinContainer::PostReplicatedChange(const TArrayView<int32> ChangedIndices, int32 FinalSize)
|
||||
{
|
||||
for (const int32 Index : ChangedIndices)
|
||||
{
|
||||
FGIS_Mixin& Mixin = Mixins[Index];
|
||||
if (Mixin.Timestamp != Mixin.LastReplicatedTimestamp)
|
||||
{
|
||||
Mixin.LastReplicatedTimestamp = Mixin.Timestamp;
|
||||
if (IGIS_MixinOwnerInterface* MixinOwner = Cast<IGIS_MixinOwnerInterface>(OwningObject))
|
||||
{
|
||||
MixinOwner->OnMixinDataUpdated(Mixin.Target, Mixin.Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CacheMixins();
|
||||
}
|
||||
|
||||
void FGIS_MixinContainer::PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize)
|
||||
{
|
||||
for (const int32 Index : RemovedIndices)
|
||||
{
|
||||
const FGIS_Mixin& Mixin = Mixins[Index];
|
||||
if (IGIS_MixinOwnerInterface* MixinOwner = Cast<IGIS_MixinOwnerInterface>(OwningObject))
|
||||
{
|
||||
MixinOwner->OnMixinDataRemoved(Mixin.Target, Mixin.Data);
|
||||
}
|
||||
}
|
||||
|
||||
CacheMixins();
|
||||
}
|
||||
|
||||
void FGIS_MixinContainer::CacheMixins()
|
||||
{
|
||||
const uint32 MixinsHash = GetTypeHash(Mixins);
|
||||
if (MixinsHash == LastCachedHash)
|
||||
{
|
||||
// Same hash, no need to cache things again.
|
||||
return;
|
||||
}
|
||||
|
||||
const int32 Size = Mixins.Num();
|
||||
AcceleratedMap.Empty(Size);
|
||||
|
||||
for (int Idx = 0; Idx < Size; ++Idx)
|
||||
{
|
||||
const UObject* Target = Mixins[Idx].Target;
|
||||
AcceleratedMap.Add(Target, Idx);
|
||||
}
|
||||
|
||||
LastCachedHash = GetTypeHash(Mixins);
|
||||
}
|
||||
|
||||
bool FGIS_MixinContainer::NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParams)
|
||||
{
|
||||
return FastArrayDeltaSerialize<FGIS_Mixin, FGIS_MixinContainer>(Mixins, DeltaParams, *this);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GIS_MixinOwnerInterface.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_MixinOwnerInterface)
|
||||
// Add default functionality here for any IGIS_MixinOwnerInterface functions that are not pure virtual.
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GIS_MixinTargetInterface.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_MixinTargetInterface)
|
||||
// Add default functionality here for any IGIS_MixinTargetInterface functions that are not pure virtual.
|
||||
@@ -0,0 +1,139 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "Items/GIS_ItemDefinition.h"
|
||||
#include "Items/GIS_ItemInstance.h"
|
||||
#include "Engine/Texture2D.h"
|
||||
#include "Items/GIS_ItemDefinitionSchema.h"
|
||||
#include "Fragments/GIS_ItemFragment.h"
|
||||
#include "Misc/DataValidation.h"
|
||||
#include "UObject/ObjectSaveContext.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_ItemDefinition)
|
||||
|
||||
UGIS_ItemDefinition::UGIS_ItemDefinition(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
|
||||
{
|
||||
bUnique = false;
|
||||
}
|
||||
|
||||
const UGIS_ItemFragment* UGIS_ItemDefinition::GetFragment(TSubclassOf<UGIS_ItemFragment> FragmentClass) const
|
||||
{
|
||||
if (FragmentClass != nullptr)
|
||||
{
|
||||
for (UGIS_ItemFragment* Fragment : Fragments)
|
||||
{
|
||||
if (Fragment && Fragment->IsA(FragmentClass))
|
||||
{
|
||||
return Fragment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool UGIS_ItemDefinition::HasFloatAttribute(FGameplayTag AttributeTag) const
|
||||
{
|
||||
return FloatAttributeMap.Contains(AttributeTag);
|
||||
}
|
||||
|
||||
bool UGIS_ItemDefinition::HasIntegerAttribute(FGameplayTag AttributeTag) const
|
||||
{
|
||||
return IntegerAttributeMap.Contains(AttributeTag);
|
||||
}
|
||||
|
||||
float UGIS_ItemDefinition::GetFloatAttribute(FGameplayTag AttributeTag) const
|
||||
{
|
||||
return FloatAttributeMap.Contains(AttributeTag) ? FloatAttributeMap[AttributeTag] : 0;
|
||||
}
|
||||
|
||||
int32 UGIS_ItemDefinition::GetIntegerAttribute(FGameplayTag AttributeTag) const
|
||||
{
|
||||
return IntegerAttributeMap.Contains(AttributeTag) ? IntegerAttributeMap[AttributeTag] : 0;
|
||||
}
|
||||
|
||||
const UGIS_ItemFragment* UGIS_ItemDefinition::FindFragmentOfItemDefinition(TSoftObjectPtr<UGIS_ItemDefinition> ItemDefinition, TSubclassOf<UGIS_ItemFragment> FragmentClass)
|
||||
{
|
||||
if (!ItemDefinition.IsNull())
|
||||
{
|
||||
const UGIS_ItemDefinition* LoadedDefinition = ItemDefinition.LoadSynchronous();
|
||||
if (LoadedDefinition != nullptr)
|
||||
{
|
||||
return LoadedDefinition->GetFragment(FragmentClass);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
void UGIS_ItemDefinition::PreSave(FObjectPreSaveContext SaveContext)
|
||||
{
|
||||
FloatAttributeMap.Empty();
|
||||
for (const FGIS_GameplayTagFloat& Attribute : StaticFloatAttributes)
|
||||
{
|
||||
FloatAttributeMap.Add(Attribute.Tag, Attribute.Value);
|
||||
}
|
||||
|
||||
IntegerAttributeMap.Empty();
|
||||
for (const FGIS_GameplayTagInteger& Attribute : StaticIntegerAttributes)
|
||||
{
|
||||
IntegerAttributeMap.Add(Attribute.Tag, Attribute.Value);
|
||||
}
|
||||
|
||||
FText SchemaError;
|
||||
UGIS_ItemDefinitionSchema::TryPreSaveItemDefinition(this, SchemaError);
|
||||
if (!SchemaError.IsEmpty())
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("ItemDefinition PreSave validation warning for %s: %s"), *GetPathName(), *SchemaError.ToString());
|
||||
}
|
||||
|
||||
Super::PreSave(SaveContext);
|
||||
}
|
||||
|
||||
EDataValidationResult UGIS_ItemDefinition::IsDataValid(class FDataValidationContext& Context) const
|
||||
{
|
||||
if (ItemTags.IsEmpty())
|
||||
{
|
||||
Context.AddWarning(FText::FromString(FString::Format(TEXT("Item tags should not be empty for %s, or it can't be queried by external system."), {GetPathName()})));
|
||||
}
|
||||
|
||||
// Check for each fragment's data validation.
|
||||
for (int32 i = 0; i < Fragments.Num(); i++)
|
||||
{
|
||||
const UGIS_ItemFragment* Fragment = Fragments[i];
|
||||
if (Fragment != nullptr)
|
||||
{
|
||||
FText Message;
|
||||
if (!Fragment->FragmentDataValidation(Message))
|
||||
{
|
||||
Context.AddWarning(FText::FromString(FString::Format(TEXT("DataValidation failed for fragment at index:{0},message:{1}"), {i, Message.ToString()})));
|
||||
return EDataValidationResult::Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for duplicate fragment types
|
||||
TSet<TSubclassOf<UGIS_ItemFragment>> FragmentClassSet;
|
||||
for (const UGIS_ItemFragment* Fragment : Fragments)
|
||||
{
|
||||
if (Fragment != nullptr)
|
||||
{
|
||||
TSubclassOf<UGIS_ItemFragment> FragmentClass = Fragment->GetClass();
|
||||
if (FragmentClassSet.Contains(FragmentClass))
|
||||
{
|
||||
Context.AddError(FText::FromString(FString::Format(TEXT("Duplicate fragment type {0} found in Fragments array for %s."), {FragmentClass->GetName(), GetPathName()})));
|
||||
return EDataValidationResult::Invalid;
|
||||
}
|
||||
FragmentClassSet.Add(FragmentClass);
|
||||
}
|
||||
}
|
||||
|
||||
FText SchemaError;
|
||||
if (!UGIS_ItemDefinitionSchema::TryValidateItemDefinition(this, SchemaError))
|
||||
{
|
||||
Context.AddError(SchemaError);
|
||||
return EDataValidationResult::Invalid;
|
||||
}
|
||||
|
||||
return EDataValidationResult::Valid;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,576 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "Items/GIS_ItemDefinitionSchema.h"
|
||||
#include "GIS_InventorySystemSettings.h"
|
||||
#include "GIS_LogChannels.h"
|
||||
#include "Items/GIS_ItemDefinition.h"
|
||||
#include "Fragments/GIS_ItemFragment.h"
|
||||
#include "Misc/DataValidation.h"
|
||||
#include "UObject/ObjectSaveContext.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_ItemDefinitionSchema)
|
||||
|
||||
bool UGIS_ItemDefinitionSchema::TryValidateItemDefinition(const UGIS_ItemDefinition* Definition, FText& OutError)
|
||||
{
|
||||
if (Definition == nullptr)
|
||||
{
|
||||
OutError = FText::FromString(TEXT("Item definition is null."));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (const UGIS_InventorySystemSettings* Settings = UGIS_InventorySystemSettings::Get())
|
||||
{
|
||||
FString AssetPath = Definition->GetPathName();
|
||||
const UGIS_ItemDefinitionSchema* Schema = Settings->GetItemDefinitionSchemaForAsset(AssetPath);
|
||||
if (Schema)
|
||||
{
|
||||
return Schema->TryValidate(Definition, OutError);
|
||||
}
|
||||
// OutError = FText::FromString(FString::Format(TEXT("No valid schema found for item definition at path: {0}."), {AssetPath}));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void UGIS_ItemDefinitionSchema::TryPreSaveItemDefinition(UGIS_ItemDefinition* Definition, FText& OutError)
|
||||
{
|
||||
if (Definition == nullptr)
|
||||
{
|
||||
OutError = FText::FromString(TEXT("Item definition is null."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (const UGIS_InventorySystemSettings* Settings = UGIS_InventorySystemSettings::Get())
|
||||
{
|
||||
FString AssetPath = Definition->GetPathName();
|
||||
const UGIS_ItemDefinitionSchema* Schema = Settings->GetItemDefinitionSchemaForAsset(AssetPath);
|
||||
if (Schema)
|
||||
{
|
||||
Schema->TryPreSave(Definition, OutError);
|
||||
}
|
||||
// OutError = FText::FromString(FString::Format(TEXT("No valid schema found for item definition at path: {0}."), {AssetPath}));
|
||||
}
|
||||
}
|
||||
|
||||
bool UGIS_ItemDefinitionSchema::TryValidate(const UGIS_ItemDefinition* Definition, FText& OutError) const
|
||||
{
|
||||
if (Definition == nullptr)
|
||||
{
|
||||
OutError = FText::FromString(TEXT("Item definition is null."));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate parent tag: all ItemTags must be children of RequiredParentTag
|
||||
if (RequiredParentTag.IsValid())
|
||||
{
|
||||
for (const FGameplayTag& Tag : Definition->ItemTags.GetGameplayTagArray())
|
||||
{
|
||||
if (!Tag.MatchesTag(RequiredParentTag))
|
||||
{
|
||||
OutError = FText::FromString(FString::Format(TEXT("Tag {0} is not a child of required parent tag {1}."), {Tag.ToString(), RequiredParentTag.ToString()}));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FGIS_ItemDefinitionValidationEntry FoundEntry;
|
||||
bool bFoundEntry = false;
|
||||
int32 ValidationEntryIndex = -1;
|
||||
|
||||
// Find matching validation entry
|
||||
for (int32 Index = 0; Index < ValidationEntries.Num(); ++Index)
|
||||
{
|
||||
const FGIS_ItemDefinitionValidationEntry& Entry = ValidationEntries[Index];
|
||||
if (!Entry.ItemTagQuery.IsEmpty() && Definition->ItemTags.MatchesQuery(Entry.ItemTagQuery))
|
||||
{
|
||||
FoundEntry = Entry;
|
||||
bFoundEntry = true;
|
||||
ValidationEntryIndex = Index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bFoundEntry)
|
||||
{
|
||||
return true; // No matching entry
|
||||
}
|
||||
|
||||
TMap<TSubclassOf<UGIS_ItemFragment>, int32> FragmentClassMap;
|
||||
TArray<TSubclassOf<UGIS_ItemFragment>> FragmentClasses;
|
||||
|
||||
// Build fragment class map and check for duplicate fragment types
|
||||
for (const UGIS_ItemFragment* Fragment : Definition->Fragments)
|
||||
{
|
||||
if (Fragment != nullptr)
|
||||
{
|
||||
TSubclassOf<UGIS_ItemFragment> FragmentClass = Fragment->GetClass();
|
||||
int32& Count = FragmentClassMap.FindOrAdd(FragmentClass);
|
||||
Count++;
|
||||
if (Count > 1)
|
||||
{
|
||||
OutError = FText::FromString(FString::Format(TEXT("Duplicate fragment type {0} found in Fragments array for schema {1}."), {FragmentClass->GetName(), GetPathName()}));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FragmentClassMap.GetKeys(FragmentClasses);
|
||||
|
||||
// Validate common required fragments
|
||||
for (const TSoftClassPtr<UGIS_ItemFragment>& CommonFragment : CommonRequiredFragments)
|
||||
{
|
||||
if (UClass* LoadedClass = CommonFragment.LoadSynchronous())
|
||||
{
|
||||
if (!FragmentClasses.Contains(LoadedClass))
|
||||
{
|
||||
OutError = FText::FromString(
|
||||
FString::Format(TEXT("Missing common required fragment of type {0} for ValidationEntry {1} in schema {2}."), {LoadedClass->GetName(), ValidationEntryIndex, GetPathName()}));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate forbidden fragments, excluding common required fragments and required fragments
|
||||
TSet<TSoftClassPtr<UGIS_ItemFragment>> EffectiveRequiredFragments;
|
||||
for (const TSoftClassPtr<UGIS_ItemFragment>& RequiredFragment : FoundEntry.RequiredFragments)
|
||||
{
|
||||
if (!CommonRequiredFragments.Contains(RequiredFragment))
|
||||
{
|
||||
EffectiveRequiredFragments.Add(RequiredFragment);
|
||||
}
|
||||
}
|
||||
for (const TSoftClassPtr<UGIS_ItemFragment>& ForbiddenFragment : FoundEntry.ForbiddenFragments)
|
||||
{
|
||||
if (UClass* LoadedClass = ForbiddenFragment.LoadSynchronous())
|
||||
{
|
||||
if (CommonRequiredFragments.Contains(ForbiddenFragment))
|
||||
{
|
||||
OutError = FText::FromString(FString::Format(
|
||||
TEXT("Forbidden fragment {0} in ValidationEntry {1} is a common required fragment in schema {2}."), {LoadedClass->GetName(), ValidationEntryIndex, GetPathName()}));
|
||||
return false;
|
||||
}
|
||||
if (EffectiveRequiredFragments.Contains(ForbiddenFragment))
|
||||
{
|
||||
OutError = FText::FromString(
|
||||
FString::Format(TEXT("Forbidden fragment {0} in ValidationEntry {1} is a required fragment in schema {2}."), {LoadedClass->GetName(), ValidationEntryIndex, GetPathName()}));
|
||||
return false;
|
||||
}
|
||||
if (FragmentClasses.Contains(LoadedClass))
|
||||
{
|
||||
OutError = FText::FromString(FString::Format(
|
||||
TEXT("Forbidden fragment of type {0} found in definition for ValidationEntry {1} in schema {2}."), {LoadedClass->GetName(), ValidationEntryIndex, GetPathName()}));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate required fragments, excluding those already in CommonRequiredFragments
|
||||
for (const TSoftClassPtr<UGIS_ItemFragment>& RequiredFragment : EffectiveRequiredFragments)
|
||||
{
|
||||
if (UClass* LoadedClass = RequiredFragment.LoadSynchronous())
|
||||
{
|
||||
if (!FragmentClasses.Contains(LoadedClass))
|
||||
{
|
||||
OutError = FText::FromString(
|
||||
FString::Format(TEXT("Missing required fragment of type {0} for ValidationEntry {1} in schema {2}."), {LoadedClass->GetName(), ValidationEntryIndex, GetPathName()}));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate required float attributes
|
||||
for (const FGIS_GameplayTagFloat& RequiredFloat : FoundEntry.RequiredFloatAttributes)
|
||||
{
|
||||
bool bFound = false;
|
||||
for (const FGIS_GameplayTagFloat& FloatAttr : Definition->StaticFloatAttributes)
|
||||
{
|
||||
if (FloatAttr.Tag == RequiredFloat.Tag)
|
||||
{
|
||||
bFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!bFound)
|
||||
{
|
||||
OutError = FText::FromString(
|
||||
FString::Format(TEXT("Missing required float attribute {0} for ValidationEntry {1} in schema {2}."), {RequiredFloat.Tag.ToString(), ValidationEntryIndex, GetPathName()}));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate required integer attributes
|
||||
for (const FGIS_GameplayTagInteger& RequiredInteger : FoundEntry.RequiredIntegerAttributes)
|
||||
{
|
||||
bool bFound = false;
|
||||
for (const FGIS_GameplayTagInteger& IntegerAttr : Definition->StaticIntegerAttributes)
|
||||
{
|
||||
if (IntegerAttr.Tag == RequiredInteger.Tag)
|
||||
{
|
||||
bFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!bFound)
|
||||
{
|
||||
OutError = FText::FromString(
|
||||
FString::Format(TEXT("Missing required integer attribute {0} for ValidationEntry {1} in schema {2}."), {RequiredInteger.Tag.ToString(), ValidationEntryIndex, GetPathName()}));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate bUnique
|
||||
if (FoundEntry.bEnforceUnique && Definition->bUnique != FoundEntry.RequiredUniqueValue)
|
||||
{
|
||||
OutError = FText::FromString(FString::Format(
|
||||
TEXT("bUnique must be {0} for ValidationEntry {1} in schema {2}."), {FoundEntry.RequiredUniqueValue ? TEXT("true") : TEXT("false"), ValidationEntryIndex, GetPathName()}));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UGIS_ItemDefinitionSchema::TryPreSave(UGIS_ItemDefinition* Definition, FText& OutError) const
|
||||
{
|
||||
if (Definition == nullptr)
|
||||
{
|
||||
OutError = FText::FromString(TEXT("Item definition is null."));
|
||||
return;
|
||||
}
|
||||
|
||||
FGIS_ItemDefinitionValidationEntry FoundEntry;
|
||||
bool bFoundEntry = false;
|
||||
|
||||
// Find matching validation entry
|
||||
for (const FGIS_ItemDefinitionValidationEntry& Entry : ValidationEntries)
|
||||
{
|
||||
if (!Entry.ItemTagQuery.IsEmpty() && Definition->ItemTags.MatchesQuery(Entry.ItemTagQuery))
|
||||
{
|
||||
FoundEntry = Entry;
|
||||
bFoundEntry = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Collect required fragments
|
||||
TSet<TSoftClassPtr<UGIS_ItemFragment>> RequiredFragmentSet;
|
||||
for (const TSoftClassPtr<UGIS_ItemFragment>& CommonFragment : CommonRequiredFragments)
|
||||
{
|
||||
RequiredFragmentSet.Add(CommonFragment);
|
||||
}
|
||||
if (bFoundEntry)
|
||||
{
|
||||
for (const TSoftClassPtr<UGIS_ItemFragment>& RequiredFragment : FoundEntry.RequiredFragments)
|
||||
{
|
||||
if (!CommonRequiredFragments.Contains(RequiredFragment))
|
||||
{
|
||||
RequiredFragmentSet.Add(RequiredFragment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect forbidden fragments
|
||||
TSet<TSoftClassPtr<UGIS_ItemFragment>> ForbiddenFragmentSet;
|
||||
if (bFoundEntry)
|
||||
{
|
||||
for (const TSoftClassPtr<UGIS_ItemFragment>& ForbiddenFragment : FoundEntry.ForbiddenFragments)
|
||||
{
|
||||
if (!CommonRequiredFragments.Contains(ForbiddenFragment) && !RequiredFragmentSet.Contains(ForbiddenFragment))
|
||||
{
|
||||
ForbiddenFragmentSet.Add(ForbiddenFragment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build map of existing fragments, keeping only the first instance of each type
|
||||
TMap<TSubclassOf<UGIS_ItemFragment>, TObjectPtr<UGIS_ItemFragment>> ExistingFragmentMap;
|
||||
for (TObjectPtr<UGIS_ItemFragment> Fragment : Definition->Fragments)
|
||||
{
|
||||
if (Fragment != nullptr)
|
||||
{
|
||||
TSubclassOf<UGIS_ItemFragment> FragmentClass = Fragment->GetClass();
|
||||
if (!ExistingFragmentMap.Contains(FragmentClass))
|
||||
{
|
||||
ExistingFragmentMap.Add(FragmentClass, Fragment);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogGIS, Warning, TEXT("Removed duplicate fragment of type %s from ItemDefinition(%s) by schema(%s)."), *FragmentClass->GetName(), *GetNameSafe(Definition), *GetPathName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove forbidden fragments
|
||||
for (const TSoftClassPtr<UGIS_ItemFragment>& ForbiddenFragment : ForbiddenFragmentSet)
|
||||
{
|
||||
if (UClass* ForbiddenClass = ForbiddenFragment.LoadSynchronous())
|
||||
{
|
||||
if (ExistingFragmentMap.Remove(ForbiddenClass))
|
||||
{
|
||||
UE_LOG(LogGIS, Warning, TEXT("Removed forbidden fragment of type %s from ItemDefinition(%s) by schema(%s)."), *ForbiddenClass->GetName(), *GetNameSafe(Definition), *GetPathName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add missing required fragments
|
||||
for (const TSoftClassPtr<UGIS_ItemFragment>& RequiredFragment : RequiredFragmentSet)
|
||||
{
|
||||
if (UClass* FragmentClass = RequiredFragment.LoadSynchronous())
|
||||
{
|
||||
if (!ExistingFragmentMap.Contains(FragmentClass))
|
||||
{
|
||||
UGIS_ItemFragment* NewFragment = NewObject<UGIS_ItemFragment>(Definition, FragmentClass);
|
||||
if (NewFragment)
|
||||
{
|
||||
ExistingFragmentMap.Add(FragmentClass, NewFragment);
|
||||
UE_LOG(LogGIS, Warning, TEXT("Added missing required fragment of type %s to ItemDefinition(%s) by schema(%s)."), *FragmentClass->GetName(), *GetNameSafe(Definition),
|
||||
*GetPathName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort fragments according to FragmentOrder
|
||||
TArray<TObjectPtr<UGIS_ItemFragment>> NewFragments;
|
||||
TArray<TObjectPtr<UGIS_ItemFragment>> NonRequiredFragments;
|
||||
|
||||
// Cache loaded fragment classes to avoid repeated LoadSynchronous calls
|
||||
TMap<TSoftClassPtr<UGIS_ItemFragment>, UClass*> FragmentClassCache;
|
||||
for (const TSoftClassPtr<UGIS_ItemFragment>& OrderedFragment : FragmentOrder)
|
||||
{
|
||||
UClass* FragmentClass = FragmentClassCache.FindOrAdd(OrderedFragment, OrderedFragment.LoadSynchronous());
|
||||
if (FragmentClass)
|
||||
{
|
||||
if (TObjectPtr<UGIS_ItemFragment>* ExistingFragment = ExistingFragmentMap.Find(FragmentClass))
|
||||
{
|
||||
NewFragments.Add(*ExistingFragment);
|
||||
ExistingFragmentMap.Remove(FragmentClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Append remaining non-required fragments
|
||||
ExistingFragmentMap.GenerateValueArray(NonRequiredFragments);
|
||||
for (const TObjectPtr<UGIS_ItemFragment>& Fragment : NonRequiredFragments)
|
||||
{
|
||||
if (Fragment && !FragmentOrder.Contains(Fragment->GetClass()))
|
||||
{
|
||||
UE_LOG(LogGIS, Warning, TEXT("Fragment type %s is not included in FragmentOrder, appended to end of fragments with ItemDefinition(%s) by schema(%s)."), *Fragment->GetClass()->GetName(),
|
||||
*GetNameSafe(Definition), *GetPathName());
|
||||
}
|
||||
}
|
||||
NewFragments.Append(NonRequiredFragments);
|
||||
|
||||
// Update the definition's fragment array
|
||||
Definition->Fragments = MoveTemp(NewFragments);
|
||||
|
||||
// Auto-fix required attributes and bUnique if a matching entry was found
|
||||
if (bFoundEntry)
|
||||
{
|
||||
// Auto-fix float attributes
|
||||
for (const FGIS_GameplayTagFloat& RequiredFloat : FoundEntry.RequiredFloatAttributes)
|
||||
{
|
||||
bool bFound = false;
|
||||
for (FGIS_GameplayTagFloat& FloatAttr : Definition->StaticFloatAttributes)
|
||||
{
|
||||
if (FloatAttr.Tag == RequiredFloat.Tag)
|
||||
{
|
||||
bFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!bFound)
|
||||
{
|
||||
Definition->StaticFloatAttributes.Add(RequiredFloat);
|
||||
UE_LOG(LogGIS, Warning, TEXT("Added missing required float attribute %s to ItemDefinition(%s) by schema(%s)."), *RequiredFloat.Tag.ToString(), *GetNameSafe(Definition),
|
||||
*GetPathName());
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-fix integer attributes
|
||||
for (const FGIS_GameplayTagInteger& RequiredInteger : FoundEntry.RequiredIntegerAttributes)
|
||||
{
|
||||
bool bFound = false;
|
||||
for (FGIS_GameplayTagInteger& IntegerAttr : Definition->StaticIntegerAttributes)
|
||||
{
|
||||
if (IntegerAttr.Tag == RequiredInteger.Tag)
|
||||
{
|
||||
bFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!bFound)
|
||||
{
|
||||
Definition->StaticIntegerAttributes.Add(RequiredInteger);
|
||||
UE_LOG(LogGIS, Warning, TEXT("Added missing required integer attribute %s to ItemDefinition(%s) by schema(%s)."), *RequiredInteger.Tag.ToString(), *GetNameSafe(Definition),
|
||||
*GetPathName());
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-fix bUnique
|
||||
if (FoundEntry.bEnforceUnique)
|
||||
{
|
||||
if (Definition->bUnique != FoundEntry.RequiredUniqueValue)
|
||||
{
|
||||
Definition->bUnique = FoundEntry.RequiredUniqueValue;
|
||||
UE_LOG(LogGIS, Warning, TEXT("Set bUnique to %s for ItemDefinition(%s) by schema(%s)."), FoundEntry.RequiredUniqueValue ? TEXT("true") : TEXT("false"), *GetNameSafe(Definition),
|
||||
*GetPathName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the definition as modified
|
||||
Definition->Modify();
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
void UGIS_ItemDefinitionSchema::PreSave(FObjectPreSaveContext SaveContext)
|
||||
{
|
||||
// Remove RequiredFragments and ForbiddenFragments that overlap with CommonRequiredFragments
|
||||
for (int32 EntryIndex = 0; EntryIndex < ValidationEntries.Num(); ++EntryIndex)
|
||||
{
|
||||
FGIS_ItemDefinitionValidationEntry& Entry = ValidationEntries[EntryIndex];
|
||||
int32 InitialRequiredCount = Entry.RequiredFragments.Num();
|
||||
int32 InitialForbiddenCount = Entry.ForbiddenFragments.Num();
|
||||
|
||||
Entry.RequiredFragments.RemoveAll([&](const TSoftClassPtr<UGIS_ItemFragment>& Fragment)
|
||||
{
|
||||
if (CommonRequiredFragments.Contains(Fragment))
|
||||
{
|
||||
if (UClass* LoadedClass = Fragment.LoadSynchronous())
|
||||
{
|
||||
UE_LOG(LogGIS, Warning, TEXT("Removed fragment %s from ValidationEntry %d RequiredFragments as it is already in CommonRequiredFragments for schema(%s)."), *LoadedClass->GetName(),
|
||||
EntryIndex, *GetPathName());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
Entry.ForbiddenFragments.RemoveAll([&](const TSoftClassPtr<UGIS_ItemFragment>& Fragment)
|
||||
{
|
||||
if (CommonRequiredFragments.Contains(Fragment))
|
||||
{
|
||||
if (UClass* LoadedClass = Fragment.LoadSynchronous())
|
||||
{
|
||||
UE_LOG(LogGIS, Warning, TEXT("Removed fragment %s from ValidationEntry %d ForbiddenFragments as it is in CommonRequiredFragments for schema(%s)."), *LoadedClass->GetName(),
|
||||
EntryIndex, *GetPathName());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (Entry.RequiredFragments.Num() < InitialRequiredCount)
|
||||
{
|
||||
UE_LOG(LogGIS, Warning, TEXT("Removed %d fragment(s) from ValidationEntry %d RequiredFragments for schema(%s)."), InitialRequiredCount - Entry.RequiredFragments.Num(), EntryIndex,
|
||||
*GetPathName());
|
||||
}
|
||||
if (Entry.ForbiddenFragments.Num() < InitialForbiddenCount)
|
||||
{
|
||||
UE_LOG(LogGIS, Warning, TEXT("Removed %d fragment(s) from ValidationEntry %d ForbiddenFragments for schema(%s)."), InitialForbiddenCount - Entry.ForbiddenFragments.Num(), EntryIndex,
|
||||
*GetPathName());
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the schema as modified if changes were made
|
||||
Modify();
|
||||
|
||||
Super::PreSave(SaveContext);
|
||||
}
|
||||
|
||||
EDataValidationResult UGIS_ItemDefinitionSchema::IsDataValid(class FDataValidationContext& Context) const
|
||||
{
|
||||
// Validate that RequiredFragments do not overlap with CommonRequiredFragments
|
||||
for (int32 EntryIndex = 0; EntryIndex < ValidationEntries.Num(); ++EntryIndex)
|
||||
{
|
||||
const FGIS_ItemDefinitionValidationEntry& Entry = ValidationEntries[EntryIndex];
|
||||
for (const TSoftClassPtr<UGIS_ItemFragment>& RequiredFragment : Entry.RequiredFragments)
|
||||
{
|
||||
if (CommonRequiredFragments.Contains(RequiredFragment))
|
||||
{
|
||||
if (UClass* LoadedClass = RequiredFragment.LoadSynchronous())
|
||||
{
|
||||
Context.AddWarning(FText::FromString(
|
||||
FString::Format(
|
||||
TEXT("ValidationEntry {0} contains fragment {1} in RequiredFragments, which is already in CommonRequiredFragments for schema {2}."),
|
||||
{EntryIndex, LoadedClass->GetName(), GetPathName()})));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that ForbiddenFragments do not overlap with CommonRequiredFragments or RequiredFragments
|
||||
TSet<TSoftClassPtr<UGIS_ItemFragment>> EffectiveRequiredFragments;
|
||||
for (const TSoftClassPtr<UGIS_ItemFragment>& RequiredFragment : Entry.RequiredFragments)
|
||||
{
|
||||
if (!CommonRequiredFragments.Contains(RequiredFragment))
|
||||
{
|
||||
EffectiveRequiredFragments.Add(RequiredFragment);
|
||||
}
|
||||
}
|
||||
for (const TSoftClassPtr<UGIS_ItemFragment>& ForbiddenFragment : Entry.ForbiddenFragments)
|
||||
{
|
||||
if (CommonRequiredFragments.Contains(ForbiddenFragment))
|
||||
{
|
||||
if (UClass* LoadedClass = ForbiddenFragment.LoadSynchronous())
|
||||
{
|
||||
Context.AddError(FText::FromString(
|
||||
FString::Format(
|
||||
TEXT("ValidationEntry {0} contains fragment {1} in ForbiddenFragments, which is in CommonRequiredFragments for schema {2}."),
|
||||
{EntryIndex, LoadedClass->GetName(), GetPathName()})));
|
||||
return EDataValidationResult::Invalid;
|
||||
}
|
||||
}
|
||||
if (EffectiveRequiredFragments.Contains(ForbiddenFragment))
|
||||
{
|
||||
if (UClass* LoadedClass = ForbiddenFragment.LoadSynchronous())
|
||||
{
|
||||
Context.AddError(FText::FromString(
|
||||
FString::Format(
|
||||
TEXT("ValidationEntry {0} contains fragment {1} in ForbiddenFragments, which is in RequiredFragments for schema {2}."),
|
||||
{EntryIndex, LoadedClass->GetName(), GetPathName()})));
|
||||
return EDataValidationResult::Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate FragmentOrder contains no duplicates
|
||||
TSet<TSoftClassPtr<UGIS_ItemFragment>> FragmentOrderSet;
|
||||
for (const TSoftClassPtr<UGIS_ItemFragment>& Fragment : FragmentOrder)
|
||||
{
|
||||
if (FragmentOrderSet.Contains(Fragment))
|
||||
{
|
||||
if (UClass* LoadedClass = Fragment.LoadSynchronous())
|
||||
{
|
||||
Context.AddError(FText::FromString(FString::Format(TEXT("FragmentOrder contains duplicate fragment {0} for schema {1}."), {LoadedClass->GetName(), GetPathName()})));
|
||||
return EDataValidationResult::Invalid;
|
||||
}
|
||||
}
|
||||
FragmentOrderSet.Add(Fragment);
|
||||
}
|
||||
|
||||
// Suggest including all CommonRequiredFragments and RequiredFragments in FragmentOrder
|
||||
TSet<TSoftClassPtr<UGIS_ItemFragment>> AllRequiredFragments = TSet<TSoftClassPtr<UGIS_ItemFragment>>(CommonRequiredFragments);
|
||||
for (const FGIS_ItemDefinitionValidationEntry& Entry : ValidationEntries)
|
||||
{
|
||||
for (const TSoftClassPtr<UGIS_ItemFragment>& RequiredFragment : Entry.RequiredFragments)
|
||||
{
|
||||
if (!CommonRequiredFragments.Contains(RequiredFragment))
|
||||
{
|
||||
AllRequiredFragments.Add(RequiredFragment);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const TSoftClassPtr<UGIS_ItemFragment>& RequiredFragment : AllRequiredFragments)
|
||||
{
|
||||
if (!FragmentOrder.Contains(RequiredFragment))
|
||||
{
|
||||
if (UClass* LoadedClass = RequiredFragment.LoadSynchronous())
|
||||
{
|
||||
Context.AddWarning(FText::FromString(FString::Format(TEXT("Required fragment {0} is not included in FragmentOrder for schema {1}."), {LoadedClass->GetName(), GetPathName()})));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Context.GetNumErrors() > 0 ? EDataValidationResult::Invalid : EDataValidationResult::Valid;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "Items/GIS_ItemInfo.h"
|
||||
#include "Items/GIS_ItemInstance.h"
|
||||
#include "GIS_ItemCollection.h"
|
||||
#include "Items/GIS_ItemDefinition.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_ItemInfo)
|
||||
|
||||
FGIS_ItemInfo FGIS_ItemInfo::None = FGIS_ItemInfo();
|
||||
|
||||
FGIS_ItemInfo::FGIS_ItemInfo()
|
||||
{
|
||||
Item = nullptr;
|
||||
Amount = 0;
|
||||
ItemCollection = nullptr;
|
||||
StackId = FGIS_ItemStack::InvalidId;
|
||||
CollectionTag = FGameplayTag::EmptyTag;
|
||||
CollectionId = FGIS_ItemStack::InvalidId;
|
||||
}
|
||||
|
||||
FGIS_ItemInfo::FGIS_ItemInfo(UGIS_ItemInstance* InItem, int32 InAmount, UGIS_ItemCollection* InCollection)
|
||||
{
|
||||
Item = InItem;
|
||||
Amount = InAmount;
|
||||
ItemCollection = InCollection;
|
||||
StackId = FGIS_ItemStack::InvalidId;
|
||||
}
|
||||
|
||||
FGIS_ItemInfo::FGIS_ItemInfo(UGIS_ItemInstance* InItem, int32 InAmount, UGIS_ItemCollection* InCollection, FGuid InStackId)
|
||||
{
|
||||
Item = InItem;
|
||||
Amount = InAmount;
|
||||
ItemCollection = InCollection;
|
||||
CollectionId = InCollection->GetCollectionId();
|
||||
StackId = InStackId;
|
||||
}
|
||||
|
||||
FGIS_ItemInfo::FGIS_ItemInfo(UGIS_ItemInstance* InItem, int32 InAmount)
|
||||
{
|
||||
Item = InItem;
|
||||
Amount = InAmount;
|
||||
}
|
||||
|
||||
FGIS_ItemInfo::FGIS_ItemInfo(UGIS_ItemInstance* InItem, int32 InAmount, FGuid InCollectionId)
|
||||
{
|
||||
Item = InItem;
|
||||
Amount = InAmount;
|
||||
CollectionId = InCollectionId;
|
||||
}
|
||||
|
||||
FGIS_ItemInfo::FGIS_ItemInfo(UGIS_ItemInstance* InItem, int32 InAmount, FGameplayTag InCollectionTag)
|
||||
{
|
||||
Item = InItem;
|
||||
Amount = InAmount;
|
||||
CollectionTag = InCollectionTag;
|
||||
}
|
||||
|
||||
FGIS_ItemInfo::FGIS_ItemInfo(int32 InAmount, const FGIS_ItemInfo& OtherInfo)
|
||||
{
|
||||
Amount = InAmount;
|
||||
Item = OtherInfo.Item;
|
||||
ItemCollection = OtherInfo.ItemCollection;
|
||||
StackId = OtherInfo.StackId;
|
||||
CollectionId = OtherInfo.CollectionId;
|
||||
CollectionTag = OtherInfo.CollectionTag;
|
||||
}
|
||||
|
||||
FGIS_ItemInfo::FGIS_ItemInfo(int32 InAmount, int32 InIndex, const FGIS_ItemInfo& OtherInfo)
|
||||
{
|
||||
Amount = InAmount;
|
||||
Index = InIndex;
|
||||
Item = OtherInfo.Item;
|
||||
ItemCollection = OtherInfo.ItemCollection;
|
||||
StackId = OtherInfo.StackId;
|
||||
CollectionId = OtherInfo.CollectionId;
|
||||
CollectionTag = OtherInfo.CollectionTag;
|
||||
}
|
||||
|
||||
FGIS_ItemInfo::FGIS_ItemInfo(UGIS_ItemInstance* InItem, int32 InAmount, const FGIS_ItemInfo& OtherInfo)
|
||||
{
|
||||
Item = InItem;
|
||||
Amount = InAmount;
|
||||
ItemCollection = OtherInfo.ItemCollection;
|
||||
StackId = OtherInfo.StackId;
|
||||
CollectionId = OtherInfo.CollectionId;
|
||||
}
|
||||
|
||||
FGIS_ItemInfo::FGIS_ItemInfo(const FGIS_ItemStack& ItemStack)
|
||||
{
|
||||
Item = ItemStack.Item;
|
||||
ItemCollection = ItemStack.Collection;
|
||||
Amount = ItemStack.Amount;
|
||||
StackId = ItemStack.Id;
|
||||
}
|
||||
|
||||
FString FGIS_ItemInfo::GetDebugString() const
|
||||
{
|
||||
if (!IsValid())
|
||||
{
|
||||
return TEXT("Invalid item info");
|
||||
}
|
||||
|
||||
return FString::Format(TEXT("{0}({1})"), {Item->GetDefinition()->GetName(), Amount});
|
||||
}
|
||||
|
||||
bool FGIS_ItemInfo::operator==(const FGIS_ItemInfo& Other) const
|
||||
{
|
||||
return Other.Amount == Amount && Other.Item == Item && Other.ItemCollection == ItemCollection;
|
||||
}
|
||||
|
||||
bool FGIS_ItemInfo::IsValid() const
|
||||
{
|
||||
return Item != nullptr && Amount > 0;
|
||||
}
|
||||
|
||||
UGIS_InventorySystemComponent* FGIS_ItemInfo::GetInventory() const
|
||||
{
|
||||
return ItemCollection != nullptr ? ItemCollection->GetOwningInventory() : nullptr;
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Items/GIS_ItemInstance.h"
|
||||
#include "GIS_InventorySystemComponent.h"
|
||||
#include "GIS_ItemCollection.h"
|
||||
#include "Items/GIS_ItemDefinition.h"
|
||||
#include "GIS_ItemFragment.h"
|
||||
#include "Net/UnrealNetwork.h"
|
||||
#include "Engine/BlueprintGeneratedClass.h"
|
||||
|
||||
#if UE_WITH_IRIS
|
||||
#include "Iris/ReplicationSystem/ReplicationFragmentUtil.h"
|
||||
#endif // UE_WITH_IRIS
|
||||
|
||||
#include "GIS_LogChannels.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_ItemInstance)
|
||||
|
||||
|
||||
UGIS_ItemInstance::UGIS_ItemInstance(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer), IntegerAttributes(this), FloatAttributes(this), FragmentStates(this)
|
||||
{
|
||||
OwningCollection = nullptr;
|
||||
}
|
||||
|
||||
void UGIS_ItemInstance::GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const
|
||||
{
|
||||
TagContainer = GetItemTags();
|
||||
}
|
||||
|
||||
void UGIS_ItemInstance::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||
{
|
||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||
|
||||
|
||||
// FastArray don't need push model.
|
||||
DOREPLIFETIME(ThisClass, IntegerAttributes);
|
||||
DOREPLIFETIME(ThisClass, FloatAttributes);
|
||||
DOREPLIFETIME(ThisClass, FragmentStates);
|
||||
|
||||
FDoRepLifetimeParams SharedParams;
|
||||
SharedParams.bIsPushBased = true;
|
||||
|
||||
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, ItemId, SharedParams);
|
||||
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, Definition, SharedParams);
|
||||
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, OwningCollection, SharedParams);
|
||||
|
||||
SharedParams.Condition = COND_InitialOrOwner;
|
||||
|
||||
//fix: https://forums.unrealengine.com/t/subobject-replication-for-blueprint-child-class/106205/4
|
||||
UBlueprintGeneratedClass* bpClass = Cast<UBlueprintGeneratedClass>(this->GetClass());
|
||||
if (bpClass != nullptr)
|
||||
{
|
||||
bpClass->GetLifetimeBlueprintReplicationList(OutLifetimeProps);
|
||||
}
|
||||
}
|
||||
|
||||
FGuid UGIS_ItemInstance::GetItemId() const
|
||||
{
|
||||
return ItemId;
|
||||
}
|
||||
|
||||
void UGIS_ItemInstance::SetItemId(FGuid NewId)
|
||||
{
|
||||
ItemId = NewId;
|
||||
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, ItemId, this);
|
||||
}
|
||||
|
||||
bool UGIS_ItemInstance::IsUnique() const
|
||||
{
|
||||
return Definition->bUnique;
|
||||
}
|
||||
|
||||
FText UGIS_ItemInstance::GetItemName() const
|
||||
{
|
||||
return Definition->DisplayName;
|
||||
}
|
||||
|
||||
const UGIS_ItemDefinition* UGIS_ItemInstance::GetDefinition() const
|
||||
{
|
||||
return Definition;
|
||||
}
|
||||
|
||||
void UGIS_ItemInstance::SetDefinition(const UGIS_ItemDefinition* NewDefinition)
|
||||
{
|
||||
Definition = NewDefinition;
|
||||
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, Definition, this);
|
||||
}
|
||||
|
||||
FGameplayTagContainer UGIS_ItemInstance::GetItemTags() const
|
||||
{
|
||||
return Definition->ItemTags;
|
||||
}
|
||||
|
||||
const UGIS_ItemFragment* UGIS_ItemInstance::GetFragment(TSubclassOf<UGIS_ItemFragment> FragmentClass) const
|
||||
{
|
||||
if (Definition != nullptr && FragmentClass != nullptr)
|
||||
{
|
||||
return Definition->GetFragment(FragmentClass);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const UGIS_ItemFragment* UGIS_ItemInstance::FindFragment(TSubclassOf<UGIS_ItemFragment> FragmentClass, bool& bValid) const
|
||||
{
|
||||
bValid = false;
|
||||
if (const UGIS_ItemFragment* Fragment = GetFragment(FragmentClass))
|
||||
{
|
||||
bValid = true;
|
||||
return Fragment;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool UGIS_ItemInstance::HasAnyAttribute(FGameplayTag AttributeTag) const
|
||||
{
|
||||
return HasFloatAttribute(AttributeTag) || HasIntegerAttribute(AttributeTag); // || HasBoolAttribute(AttributeTag);
|
||||
}
|
||||
|
||||
bool UGIS_ItemInstance::HasFloatAttribute(FGameplayTag AttributeTag) const
|
||||
{
|
||||
if (FloatAttributes.ContainsTag(AttributeTag))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
FText UGIS_ItemInstance::GetItemDescription() const
|
||||
{
|
||||
return Definition->Description;
|
||||
}
|
||||
|
||||
float UGIS_ItemInstance::GetFloatAttribute(FGameplayTag AttributeTag) const
|
||||
{
|
||||
if (FloatAttributes.ContainsTag(AttributeTag))
|
||||
{
|
||||
return FloatAttributes.GetValue(AttributeTag);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32 UGIS_ItemInstance::GetIntegerAttribute(FGameplayTag AttributeTag) const
|
||||
{
|
||||
if (IntegerAttributes.ContainsTag(AttributeTag))
|
||||
{
|
||||
return IntegerAttributes.GetValue(AttributeTag);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void UGIS_ItemInstance::SetFloatAttribute(FGameplayTag AttributeTag, float NewValue)
|
||||
{
|
||||
FloatAttributes.SetItem(AttributeTag, NewValue);
|
||||
}
|
||||
|
||||
void UGIS_ItemInstance::AddFloatAttribute(FGameplayTag AttributeTag, float Value)
|
||||
{
|
||||
FloatAttributes.AddItem(AttributeTag, Value);
|
||||
}
|
||||
|
||||
void UGIS_ItemInstance::RemoveFloatAttribute(FGameplayTag AttributeTag, float Value)
|
||||
{
|
||||
FloatAttributes.RemoveItem(AttributeTag, Value);
|
||||
}
|
||||
|
||||
bool UGIS_ItemInstance::HasIntegerAttribute(FGameplayTag AttributeTag) const
|
||||
{
|
||||
if (IntegerAttributes.ContainsTag(AttributeTag))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void UGIS_ItemInstance::SetIntegerAttribute(FGameplayTag AttributeTag, int32 NewValue)
|
||||
{
|
||||
IntegerAttributes.SetItem(AttributeTag, NewValue);
|
||||
}
|
||||
|
||||
void UGIS_ItemInstance::AddIntegerAttribute(FGameplayTag AttributeTag, int32 NewValue)
|
||||
{
|
||||
IntegerAttributes.AddItem(AttributeTag, NewValue);
|
||||
}
|
||||
|
||||
void UGIS_ItemInstance::RemoveIntegerAttribute(FGameplayTag AttributeTag, int32 Value)
|
||||
{
|
||||
IntegerAttributes.RemoveItem(AttributeTag, Value);
|
||||
}
|
||||
|
||||
UGIS_ItemCollection* UGIS_ItemInstance::GetOwningCollection() const
|
||||
{
|
||||
return OwningCollection;
|
||||
}
|
||||
|
||||
UGIS_InventorySystemComponent* UGIS_ItemInstance::GetOwningInventory() const
|
||||
{
|
||||
return IsValid(OwningCollection) ? OwningCollection->GetOwningInventory() : nullptr;
|
||||
}
|
||||
|
||||
bool UGIS_ItemInstance::FindFragmentStateByClass(TSubclassOf<UGIS_ItemFragment> FragmentClass, FInstancedStruct& OutState) const
|
||||
{
|
||||
return FragmentStates.GetDataByTarget(GetFragment(FragmentClass), OutState);
|
||||
}
|
||||
|
||||
void UGIS_ItemInstance::SetFragmentStateByClass(TSubclassOf<UGIS_ItemFragment> FragmentClass, const FInstancedStruct& NewState)
|
||||
{
|
||||
if (const UGIS_ItemFragment* Fragment = Definition->GetFragment(FragmentClass))
|
||||
{
|
||||
FragmentStates.SetDataForTarget(Fragment, NewState);
|
||||
}
|
||||
}
|
||||
|
||||
void UGIS_ItemInstance::AssignCollection(UGIS_ItemCollection* NewItemCollection)
|
||||
{
|
||||
if (NewItemCollection == nullptr || OwningCollection == NewItemCollection)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (OwningCollection != nullptr && OwningCollection->GetOwningInventory() != nullptr && OwningCollection != NewItemCollection)
|
||||
{
|
||||
GIS_CLOG(Error, "is unable to be added to a new item collection:%s when it is already a member of an existing item collection.", *OwningCollection->GetDebugString());
|
||||
return;
|
||||
}
|
||||
OwningCollection = NewItemCollection;
|
||||
GIS_CLOG(Verbose, "item(%s) assigned new collection(%s)", *GetNameSafe(this), *OwningCollection->GetDebugString());
|
||||
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, OwningCollection, this);
|
||||
}
|
||||
|
||||
void UGIS_ItemInstance::UnassignCollection(UGIS_ItemCollection* ItemCollection)
|
||||
{
|
||||
if (OwningCollection != nullptr && ItemCollection != nullptr && OwningCollection != ItemCollection)
|
||||
{
|
||||
GIS_CLOG(Warning, "belong to %s, but trying to remove from %s.", *OwningCollection->GetDebugString(), *ItemCollection->GetDebugString());
|
||||
return;
|
||||
}
|
||||
GIS_CLOG(Verbose, "item(%s) removed from collection(%s)", *GetNameSafe(this), *OwningCollection->GetDebugString());
|
||||
OwningCollection = nullptr;
|
||||
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, OwningCollection, this);
|
||||
}
|
||||
|
||||
// void UGIS_ItemInstance::ResetCollection()
|
||||
// {
|
||||
// OwningCollection = nullptr;
|
||||
// MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, OwningCollection, this);
|
||||
// }
|
||||
|
||||
bool UGIS_ItemInstance::IsItemValid() const
|
||||
{
|
||||
return ItemId.IsValid() && Definition != nullptr;
|
||||
}
|
||||
|
||||
bool UGIS_ItemInstance::StackableEquivalentTo(const UGIS_ItemInstance* OtherItem) const
|
||||
{
|
||||
return AreStackableEquivalent(this, OtherItem);
|
||||
}
|
||||
|
||||
bool UGIS_ItemInstance::SimilarTo(const UGIS_ItemInstance* OtherItem) const
|
||||
{
|
||||
return AreSimilar(this, OtherItem);
|
||||
}
|
||||
|
||||
bool UGIS_ItemInstance::AreStackableEquivalent(const UGIS_ItemInstance* Lhs, const UGIS_ItemInstance* Rhs)
|
||||
{
|
||||
if (Lhs == nullptr || Rhs == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// 有必要比较同一个指针吗?
|
||||
if (Lhs == Rhs)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (Lhs->GetClass() != Rhs->GetClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (Lhs->GetDefinition() != nullptr && Rhs->GetDefinition() != nullptr)
|
||||
{
|
||||
if (Lhs->GetDefinition() != Rhs->GetDefinition())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (Lhs->IsUnique())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
//return AreValueEquivalent(Lhs, Rhs);
|
||||
}
|
||||
|
||||
// bool UGIS_ItemInstance::AreValueEquivalent(const UGIS_ItemInstance* Lhs, const UGIS_ItemInstance* Rhs)
|
||||
// {
|
||||
// if (Lhs == nullptr || Rhs == nullptr)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
// if (Lhs == Rhs)
|
||||
// {
|
||||
// return true;
|
||||
// }
|
||||
// if (Lhs->GetClass() != Rhs->GetClass())
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
// // if (Lhs->GetItemTags() != Rhs->GetItemTags())
|
||||
// // return false;
|
||||
// if (Lhs->GetDefinition() != Rhs->GetDefinition())
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
// // if (!Lhs->GetDefinitionTag().IsValid() || !Rhs->GetDefinitionTag().IsValid())
|
||||
// // {
|
||||
// // return false;
|
||||
// // }
|
||||
// return true;
|
||||
// }
|
||||
|
||||
bool UGIS_ItemInstance::AreSimilar(const UGIS_ItemInstance* Lhs, const UGIS_ItemInstance* Rhs)
|
||||
{
|
||||
if (Lhs == nullptr || Rhs == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (Lhs == Rhs)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (Lhs->GetClass() != Rhs->GetClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (Lhs->GetDefinition() != Rhs->GetDefinition())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (Lhs->IsUnique())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (Lhs->GetItemId() == Rhs->GetItemId())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void UGIS_ItemInstance::OnItemDuplicated(const UGIS_ItemInstance* SrcItem)
|
||||
{
|
||||
// FloatAttributes.ContainerOwner = this;
|
||||
// IntegerAttributes.ContainerOwner = this;
|
||||
}
|
||||
|
||||
const FGIS_MixinContainer& UGIS_ItemInstance::GetFragmentStates() const
|
||||
{
|
||||
return FragmentStates;
|
||||
}
|
||||
|
||||
void UGIS_ItemInstance::OnMixinDataAdded(const TObjectPtr<const UObject>& Target, const FInstancedStruct& Data)
|
||||
{
|
||||
const UGIS_ItemFragment* Fragment = CastChecked<UGIS_ItemFragment>(Target);
|
||||
OnFragmentStateAdded(Fragment, Data);
|
||||
OnFragmentStateAddedEvent.Broadcast(Fragment, Data);
|
||||
}
|
||||
|
||||
void UGIS_ItemInstance::OnMixinDataUpdated(const TObjectPtr<const UObject>& Target, const FInstancedStruct& Data)
|
||||
{
|
||||
const UGIS_ItemFragment* Fragment = CastChecked<UGIS_ItemFragment>(Target);
|
||||
OnFragmentStateUpdated(Fragment, Data);
|
||||
OnFragmentStateUpdatedEvent.Broadcast(Fragment, Data);
|
||||
}
|
||||
|
||||
void UGIS_ItemInstance::OnMixinDataRemoved(const TObjectPtr<const UObject>& Target, const FInstancedStruct& Data)
|
||||
{
|
||||
const UGIS_ItemFragment* Fragment = CastChecked<UGIS_ItemFragment>(Target);
|
||||
OnFragmentStateRemoved(Fragment, Data);
|
||||
OnFragmentStateRemovedEvent.Broadcast(Fragment, Data);
|
||||
}
|
||||
|
||||
void UGIS_ItemInstance::OnFragmentStateAdded(const UGIS_ItemFragment* Fragment, const FInstancedStruct& Data)
|
||||
{
|
||||
}
|
||||
|
||||
void UGIS_ItemInstance::OnFragmentStateUpdated(const UGIS_ItemFragment* Fragment, const FInstancedStruct& Data)
|
||||
{
|
||||
}
|
||||
|
||||
void UGIS_ItemInstance::OnFragmentStateRemoved(const UGIS_ItemFragment* Fragment, const FInstancedStruct& Data)
|
||||
{
|
||||
}
|
||||
|
||||
void UGIS_ItemInstance::OnTagFloatUpdate(const FGameplayTag& Tag, float OldValue, float NewValue)
|
||||
{
|
||||
OnFloatAttributeChanged(Tag, OldValue, NewValue);
|
||||
OnFloatAttributeChangedEvent.Broadcast(Tag, OldValue, NewValue);
|
||||
}
|
||||
|
||||
void UGIS_ItemInstance::OnTagIntegerUpdate(const FGameplayTag& Tag, int32 OldValue, int32 NewValue)
|
||||
{
|
||||
OnIntegerAttributeChanged(Tag, OldValue, NewValue);
|
||||
OnIntegerAttributeChangedEvent.Broadcast(Tag, OldValue, NewValue);
|
||||
}
|
||||
|
||||
void UGIS_ItemInstance::OnFloatAttributeChanged_Implementation(const FGameplayTag& Tag, float OldValue, float NewValue)
|
||||
{
|
||||
}
|
||||
|
||||
void UGIS_ItemInstance::OnIntegerAttributeChanged_Implementation(const FGameplayTag& Tag, int32 OldValue, int32 NewValue)
|
||||
{
|
||||
}
|
||||
|
||||
#if UE_WITH_IRIS
|
||||
void UGIS_ItemInstance::RegisterReplicationFragments(UE::Net::FFragmentRegistrationContext& Context, UE::Net::EFragmentRegistrationFlags RegistrationFlags)
|
||||
{
|
||||
using namespace UE::Net;
|
||||
UObject::RegisterReplicationFragments(Context, RegistrationFlags);
|
||||
// Build descriptors and allocate PropertyReplicationFragments for this object
|
||||
FReplicationFragmentUtil::CreateAndRegisterFragmentsForObject(this, Context, RegistrationFlags);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,7 @@
|
||||
// // Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
//
|
||||
//
|
||||
// #include "Items/GIS_ItemInterface.h"
|
||||
//
|
||||
// #include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_ItemInterface)
|
||||
//
|
||||
@@ -0,0 +1,195 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "Items/GIS_ItemStack.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "GIS_InventoryMeesages.h"
|
||||
#include "GIS_ItemCollection.h"
|
||||
#include "Items/GIS_ItemDefinition.h"
|
||||
#include "GIS_InventorySystemComponent.h"
|
||||
#include "Items/GIS_ItemInstance.h"
|
||||
#include "GIS_LogChannels.h"
|
||||
|
||||
#include "GIS_ItemSlotCollection.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_ItemStack)
|
||||
|
||||
FGuid FGIS_ItemStack::InvalidId = FGuid(0, 0, 0, 0);
|
||||
|
||||
FGIS_ItemStack::FGIS_ItemStack()
|
||||
{
|
||||
Item = nullptr;
|
||||
Id = InvalidId;
|
||||
Amount = 0;
|
||||
Collection = nullptr;
|
||||
LastObservedAmount = -1;
|
||||
}
|
||||
|
||||
FString FGIS_ItemStack::GetDebugString()
|
||||
{
|
||||
return FString::Format(TEXT("Item({0}),Amount({1}),ID({2})"), {Item && Item->GetDefinition() ? GetNameSafe(Item->GetDefinition()) : TEXT("None"), Amount, Id.ToString()});
|
||||
}
|
||||
|
||||
void FGIS_ItemStack::Initialize(FGuid InStackId, UGIS_ItemInstance* InItem, int32 InAmount, UGIS_ItemCollection* InCollection, int32 InIndex)
|
||||
{
|
||||
Id = InStackId;
|
||||
Item = InItem;
|
||||
Amount = InAmount;
|
||||
Collection = InCollection;
|
||||
Index = InIndex;
|
||||
LastObservedAmount = InAmount;
|
||||
}
|
||||
|
||||
bool FGIS_ItemStack::IsValidStack() const
|
||||
{
|
||||
return Id.IsValid() && IsValid(Item) && IsValid(Collection) && Amount > 0;
|
||||
}
|
||||
|
||||
void FGIS_ItemStack::Reset()
|
||||
{
|
||||
Id.Invalidate();
|
||||
Item = nullptr;
|
||||
Amount = 0;
|
||||
Collection = nullptr;
|
||||
LastObservedAmount = INDEX_NONE;
|
||||
}
|
||||
|
||||
bool FGIS_ItemStack::operator==(const FGIS_ItemStack& Other) const
|
||||
{
|
||||
return Id == Other.Id && Item->GetItemId() == Other.Item->GetItemId();
|
||||
}
|
||||
|
||||
bool FGIS_ItemStack::operator!=(const FGIS_ItemStack& Other) const
|
||||
{
|
||||
return !(operator==(Other));
|
||||
}
|
||||
|
||||
bool FGIS_ItemStack::operator==(const FGuid& OtherId) const
|
||||
{
|
||||
return Id == OtherId;
|
||||
}
|
||||
|
||||
bool FGIS_ItemStack::operator!=(const FGuid& OtherId) const
|
||||
{
|
||||
return Id != OtherId;
|
||||
}
|
||||
|
||||
void FGIS_ItemStackContainer::PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize)
|
||||
{
|
||||
for (int32 Index : RemovedIndices)
|
||||
{
|
||||
FGIS_ItemStack& Stack = Stacks[Index];
|
||||
|
||||
if (OwningCollection->StackToIdxMap.Contains(Stack.Id))
|
||||
{
|
||||
OwningCollection->OnItemStackRemoved(Stack);
|
||||
}
|
||||
else if (OwningCollection->PendingItemStacks.Contains(Stack.Id))
|
||||
{
|
||||
GIS_OWNED_CLOG(OwningCollection, Warning, "Discard pending item stack(%s).", *OwningCollection->PendingItemStacks[Stack.Id].GetDebugString())
|
||||
OwningCollection->PendingItemStacks.Remove(Stack.Id);
|
||||
}
|
||||
|
||||
Stack.LastObservedAmount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void FGIS_ItemStackContainer::PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize)
|
||||
{
|
||||
for (int32 Index : AddedIndices)
|
||||
{
|
||||
FGIS_ItemStack& Stack = Stacks[Index];
|
||||
|
||||
if (OwningCollection && OwningCollection->IsInitialized() && Stack.IsValidStack())
|
||||
{
|
||||
OwningCollection->OnItemStackAdded(Stack);
|
||||
}
|
||||
else if (OwningCollection->PendingItemStacks.Contains(Stack.Id))
|
||||
{
|
||||
OwningCollection->PendingItemStacks[Stack.Id] = Stack;
|
||||
}
|
||||
else
|
||||
{
|
||||
OwningCollection->PendingItemStacks.Add(Stack.Id, Stack);
|
||||
}
|
||||
Stack.LastObservedAmount = Stack.Amount;
|
||||
}
|
||||
}
|
||||
|
||||
void FGIS_ItemStackContainer::PostReplicatedChange(const TArrayView<int32> ChangedIndices, int32 FinalSize)
|
||||
{
|
||||
for (int32 Index : ChangedIndices)
|
||||
{
|
||||
FGIS_ItemStack& Stack = Stacks[Index];
|
||||
check(Stack.LastObservedAmount != INDEX_NONE);
|
||||
|
||||
if (OwningCollection->StackToIdxMap.Contains(Stack.Id)) //Already Added.
|
||||
{
|
||||
OwningCollection->OnItemStackUpdated(Stack);
|
||||
}
|
||||
else if (OwningCollection->PendingItemStacks.Contains(Stack.Id)) //In pending list.
|
||||
{
|
||||
OwningCollection->PendingItemStacks.Emplace(Stack.Id, Stack); // Updated to pending.
|
||||
}
|
||||
else
|
||||
{
|
||||
OwningCollection->PendingItemStacks.Add(Stack.Id, Stack); //Add to pending list.
|
||||
}
|
||||
Stack.LastObservedAmount = Stack.Amount;
|
||||
}
|
||||
}
|
||||
|
||||
const FGIS_ItemStack* FGIS_ItemStackContainer::FindById(const FGuid& StackId) const
|
||||
{
|
||||
if (!StackId.IsValid())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return Stacks.FindByPredicate([StackId](const FGIS_ItemStack& Stack)
|
||||
{
|
||||
return Stack.Id == StackId;
|
||||
});
|
||||
}
|
||||
|
||||
const FGIS_ItemStack* FGIS_ItemStackContainer::FindByItemId(const FGuid& ItemId) const
|
||||
{
|
||||
if (!ItemId.IsValid())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return Stacks.FindByPredicate([ItemId](const FGIS_ItemStack& Stack)
|
||||
{
|
||||
return Stack.Item->GetItemId() == ItemId;
|
||||
});
|
||||
}
|
||||
|
||||
int32 FGIS_ItemStackContainer::IndexOfById(const FGuid& StackId) const
|
||||
{
|
||||
if (!StackId.IsValid())
|
||||
{
|
||||
return INDEX_NONE;
|
||||
}
|
||||
return Stacks.IndexOfByPredicate([StackId](const FGIS_ItemStack& Stack)
|
||||
{
|
||||
return Stack.Id == StackId;
|
||||
});
|
||||
}
|
||||
|
||||
int32 FGIS_ItemStackContainer::IndexOfByItemId(const FGuid& ItemId) const
|
||||
{
|
||||
return Stacks.IndexOfByPredicate([ItemId](const FGIS_ItemStack& Stack)
|
||||
{
|
||||
return Stack.Item->GetItemId() == ItemId;
|
||||
});
|
||||
}
|
||||
|
||||
int32 FGIS_ItemStackContainer::IndexOfByIds(const FGuid& StackId, const FGuid& ItemId) const
|
||||
{
|
||||
if (!StackId.IsValid() || !ItemId.IsValid())
|
||||
{
|
||||
return INDEX_NONE;
|
||||
}
|
||||
return Stacks.IndexOfByPredicate([StackId,ItemId](const FGIS_ItemStack& Stack)
|
||||
{
|
||||
return Stack.Id == StackId && Stack.Item->GetItemId() == ItemId;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GIS_CraftingStructLibrary.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_CraftingStructLibrary)
|
||||
@@ -0,0 +1,252 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GIS_CraftingSystemComponent.h"
|
||||
#include "Engine/World.h"
|
||||
#include "GIS_InventorySystemComponent.h"
|
||||
#include "GIS_CurrencySystemComponent.h"
|
||||
#include "GIS_InventoryFunctionLibrary.h"
|
||||
#include "GIS_InventorySubsystem.h"
|
||||
#include "GIS_ItemCollection.h"
|
||||
#include "Items/GIS_ItemDefinition.h"
|
||||
#include "GIS_ItemFragment_CraftingRecipe.h"
|
||||
#include "Items/GIS_ItemInstance.h"
|
||||
#include "Kismet/KismetMathLibrary.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_CraftingSystemComponent)
|
||||
|
||||
// Sets default values for this component's properties
|
||||
UGIS_CraftingSystemComponent::UGIS_CraftingSystemComponent()
|
||||
{
|
||||
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
|
||||
// off to improve performance if you don't need them.
|
||||
PrimaryComponentTick.bCanEverTick = false;
|
||||
NumOfSelectedItemsCache = 0;
|
||||
// ...
|
||||
}
|
||||
|
||||
bool UGIS_CraftingSystemComponent::Craft(const UGIS_ItemDefinition* Recipe, UGIS_InventorySystemComponent* CostInventory, int32 Quantity)
|
||||
{
|
||||
const UGIS_ItemFragment_CraftingRecipe* RecipeFragment = GetRecipeFragment(Recipe);
|
||||
if (RecipeFragment == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return CraftInternal(Recipe, CostInventory, Quantity);
|
||||
}
|
||||
|
||||
bool UGIS_CraftingSystemComponent::CanCraft(const UGIS_ItemDefinition* RecipeDefinition, UGIS_InventorySystemComponent* Inventory, int32 Quantity)
|
||||
{
|
||||
return CanCraftInternal(RecipeDefinition, Inventory, Quantity);
|
||||
}
|
||||
|
||||
bool UGIS_CraftingSystemComponent::RemoveItemIngredients(UGIS_InventorySystemComponent* Inventory, const TArray<FGIS_ItemInfo>& ItemIngredients)
|
||||
{
|
||||
for (int32 i = 0; i < ItemIngredients.Num(); i++)
|
||||
{
|
||||
FGIS_ItemInfo ItemInfoToRemove;
|
||||
ItemInfoToRemove.Item = ItemIngredients[i].Item;
|
||||
ItemInfoToRemove.Amount = ItemIngredients[i].Amount;
|
||||
if (Inventory->GetDefaultCollection()->RemoveItem(ItemInfoToRemove).Amount == ItemInfoToRemove.Amount) { continue; }
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UGIS_CraftingSystemComponent::IsValidRecipe_Implementation(const UGIS_ItemDefinition* RecipeDefinition) const
|
||||
{
|
||||
if (const UGIS_ItemFragment_CraftingRecipe* Fragment = GetRecipeFragment(RecipeDefinition))
|
||||
{
|
||||
return !Fragment->InputItems.IsEmpty() && !Fragment->OutputItems.IsEmpty();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UGIS_CraftingSystemComponent::CanCraftInternal(const UGIS_ItemDefinition* RecipeDefinition, UGIS_InventorySystemComponent* Inventory,
|
||||
int32 Quantity)
|
||||
{
|
||||
if (!IsValidRecipe(RecipeDefinition))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const UGIS_ItemFragment_CraftingRecipe* RecipeFragment = GetRecipeFragment(RecipeDefinition);
|
||||
check(RecipeFragment != nullptr);
|
||||
|
||||
if (!SelectItemForIngredients(Inventory, RecipeFragment->InputItems, Quantity))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
TArray<FGIS_ItemInfo> Selected;
|
||||
int32 SelectedCount = 0;
|
||||
// Has enough items? 有足够的道具?
|
||||
if (!CheckIfEnoughItemIngredients(RecipeFragment->InputItems, Quantity, SelectedItemsCache, Selected, SelectedCount))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UGIS_CurrencySystemComponent* CurrencySystem = Inventory->GetCurrencySystem();
|
||||
if (CurrencySystem == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Has enough currencies? 有足够货币?
|
||||
if (!CurrencySystem->HasCurrencies(UGIS_InventoryFunctionLibrary::MultiplyCurrencies(RecipeFragment->InputCurrencies, Quantity)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UGIS_CraftingSystemComponent::CraftInternal(const UGIS_ItemDefinition* RecipeDefinition, UGIS_InventorySystemComponent* Inventory, int32 Quantity)
|
||||
{
|
||||
const UGIS_ItemFragment_CraftingRecipe* RecipeFragment = GetRecipeFragment(RecipeDefinition);
|
||||
if (RecipeFragment == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SelectItemForIngredients(Inventory, RecipeFragment->InputItems, Quantity))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CanCraftInternal(RecipeDefinition, Inventory, Quantity))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UGIS_CurrencySystemComponent* CurrencySystem = Inventory->GetCurrencySystem();
|
||||
|
||||
bool bCurrencyRemoveSuccess = CurrencySystem->RemoveCurrencies(UGIS_InventoryFunctionLibrary::MultiplyCurrencies(RecipeFragment->InputCurrencies, Quantity));
|
||||
|
||||
bool bItemRemoveSuccess = RemoveItemIngredients(Inventory, SelectedItemsCache);
|
||||
|
||||
if (bCurrencyRemoveSuccess && bItemRemoveSuccess)
|
||||
{
|
||||
ProduceCraftingOutput(RecipeDefinition, Inventory, Quantity);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void UGIS_CraftingSystemComponent::ProduceCraftingOutput(const UGIS_ItemDefinition* RecipeDefinition, UGIS_InventorySystemComponent* Inventory, int32 Quantity)
|
||||
{
|
||||
const UGIS_ItemFragment_CraftingRecipe* RecipeFragment = GetRecipeFragment(RecipeDefinition);
|
||||
if (RecipeFragment == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TArray<FGIS_ItemDefinitionAmount> ItemAmounts = UGIS_InventoryFunctionLibrary::MultiplyItemAmounts(RecipeFragment->OutputItems, Quantity);
|
||||
|
||||
for (int32 i = 0; i < ItemAmounts.Num(); i++)
|
||||
{
|
||||
FGIS_ItemInfo ItemInfoToAdd;
|
||||
ItemInfoToAdd.Item = UGIS_InventorySubsystem::Get(GetWorld())->CreateItem(Inventory->GetOwner(), ItemAmounts[i].Definition);
|
||||
ItemInfoToAdd.Amount = ItemAmounts[i].Amount;
|
||||
Inventory->AddItem(ItemInfoToAdd);
|
||||
}
|
||||
}
|
||||
|
||||
const UGIS_ItemFragment_CraftingRecipe* UGIS_CraftingSystemComponent::GetRecipeFragment(const UGIS_ItemDefinition* RecipeDefinition) const
|
||||
{
|
||||
return RecipeDefinition ? RecipeDefinition->FindFragment<UGIS_ItemFragment_CraftingRecipe>() : nullptr;
|
||||
}
|
||||
|
||||
bool UGIS_CraftingSystemComponent::SelectItemForIngredients(const UGIS_InventorySystemComponent* Inventory, const TArray<FGIS_ItemDefinitionAmount>& ItemIngredients, int32 Quantity)
|
||||
{
|
||||
SelectedItemsCache.Empty();
|
||||
NumOfSelectedItemsCache = 0;
|
||||
for (int32 i = 0; i < ItemIngredients.Num(); i++)
|
||||
{
|
||||
auto RequiredItem = ItemIngredients[i];
|
||||
int32 NeededAmount = RequiredItem.Amount * Quantity;
|
||||
|
||||
TArray<FGIS_ItemInfo> FilteredItemInfos;
|
||||
if (Inventory->GetItemInfosByDefinition(RequiredItem.Definition, FilteredItemInfos))
|
||||
{
|
||||
for (int32 j = 0; j < FilteredItemInfos.Num(); j++)
|
||||
{
|
||||
const FGIS_ItemInfo& itemInfo = FilteredItemInfos[j];
|
||||
|
||||
int32 HaveAmount = itemInfo.Amount;
|
||||
|
||||
bool FoundSelectedItem = false;
|
||||
for (int k = 0; k < NumOfSelectedItemsCache; k++)
|
||||
{
|
||||
const FGIS_ItemInfo& SelectedItemInfo = SelectedItemsCache[k];
|
||||
if (itemInfo.Item != SelectedItemInfo.Item) { continue; }
|
||||
if (itemInfo.StackId != SelectedItemInfo.StackId) { continue; }
|
||||
|
||||
FoundSelectedItem = true;
|
||||
|
||||
HaveAmount = FMath::Max(0, HaveAmount - SelectedItemInfo.Amount);
|
||||
int32 additionalIgnoreAmount = FMath::Clamp(HaveAmount, 0, NeededAmount);
|
||||
//更新已经选择道具信息。
|
||||
SelectedItemsCache[k] = FGIS_ItemInfo(SelectedItemInfo.Amount + additionalIgnoreAmount, SelectedItemInfo);
|
||||
}
|
||||
|
||||
int32 SelectedAmount = FMath::Clamp(HaveAmount, 0, NeededAmount);
|
||||
|
||||
//记录已选择道具信息。
|
||||
if (FoundSelectedItem == false)
|
||||
{
|
||||
SelectedItemsCache.SetNum(NumOfSelectedItemsCache + 1);
|
||||
SelectedItemsCache[NumOfSelectedItemsCache] = FGIS_ItemInfo(SelectedAmount, itemInfo);
|
||||
NumOfSelectedItemsCache++;
|
||||
}
|
||||
|
||||
NeededAmount -= SelectedAmount;
|
||||
if (NeededAmount <= 0) { break; }
|
||||
}
|
||||
}
|
||||
if (NeededAmount <= 0) { continue; }
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UGIS_CraftingSystemComponent::CheckIfEnoughItemIngredients(const TArray<FGIS_ItemDefinitionAmount>& ItemIngredients, int32 Quantity, const TArray<FGIS_ItemInfo>& SelectedItems,
|
||||
TArray<FGIS_ItemInfo>& ItemsToIgnore, int32& NumOfItemsToIgnore)
|
||||
{
|
||||
for (int32 i = 0; i < ItemIngredients.Num(); i++)
|
||||
{
|
||||
const FGIS_ItemDefinitionAmount& ingredientAmount = ItemIngredients[i];
|
||||
|
||||
int32 neededAmount = Quantity * ingredientAmount.Amount;
|
||||
|
||||
for (int32 j = 0; j < SelectedItems.Num(); j++)
|
||||
{
|
||||
const FGIS_ItemInfo& itemInfo = SelectedItems[j];
|
||||
if (ingredientAmount.Definition != itemInfo.Item->GetDefinition()) { continue; }
|
||||
|
||||
int32 haveAmount = itemInfo.Amount;
|
||||
bool foundMatch = false;
|
||||
|
||||
|
||||
int32 selectedAmount = FMath::Clamp(haveAmount, 0, neededAmount);
|
||||
|
||||
if (foundMatch == false)
|
||||
{
|
||||
ItemsToIgnore.SetNum(NumOfItemsToIgnore + 1);
|
||||
ItemsToIgnore[NumOfItemsToIgnore] = FGIS_ItemInfo(selectedAmount, itemInfo);
|
||||
NumOfItemsToIgnore++;
|
||||
}
|
||||
|
||||
neededAmount -= selectedAmount;
|
||||
if (neededAmount <= 0) { break; }
|
||||
}
|
||||
|
||||
if (neededAmount > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GIS_ItemFragment_CraftingRecipe.h"
|
||||
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_ItemFragment_CraftingRecipe)
|
||||
|
||||
#if WITH_EDITOR
|
||||
#include "UObject/ObjectSaveContext.h"
|
||||
|
||||
void UGIS_ItemFragment_CraftingRecipe::PreSave(FObjectPreSaveContext SaveContext)
|
||||
{
|
||||
Super::PreSave(SaveContext);
|
||||
for (FGIS_ItemDefinitionAmount& DefaultItem : OutputItems)
|
||||
{
|
||||
DefaultItem.EditorFriendlyName = FString::Format(TEXT("{0}x {1}"), {DefaultItem.Amount, DefaultItem.Definition.IsNull() ? TEXT("Invalid Item") : DefaultItem.Definition.GetAssetName()});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Drops/GIS_CurrencyDropper.h"
|
||||
|
||||
#include "GIS_CurrencySystemComponent.h"
|
||||
#include "GIS_LogChannels.h"
|
||||
#include "Misc/DataValidation.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_CurrencyDropper)
|
||||
|
||||
void UGIS_CurrencyDropper::BeginPlay()
|
||||
{
|
||||
MyCurrency = UGIS_CurrencySystemComponent::GetCurrencySystemComponent(GetOwner());
|
||||
if (MyCurrency == nullptr)
|
||||
{
|
||||
GIS_CLOG(Warning, "Mising currency system component!");
|
||||
}
|
||||
Super::BeginPlay();
|
||||
}
|
||||
|
||||
void UGIS_CurrencyDropper::Drop()
|
||||
{
|
||||
if (AActor* PickupActor = CreatePickupActorInstance())
|
||||
{
|
||||
if (UGIS_CurrencySystemComponent* CurrencySys = UGIS_CurrencySystemComponent::GetCurrencySystemComponent(PickupActor))
|
||||
{
|
||||
CurrencySys->SetCurrencies(MyCurrency->GetAllCurrencies());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if WITH_EDITOR
|
||||
EDataValidationResult UGIS_CurrencyDropper::IsDataValid(FDataValidationContext& Context) const
|
||||
{
|
||||
if (PickupActorClass.IsNull())
|
||||
{
|
||||
Context.AddError(FText::FromString(FString::Format(TEXT("%s has no pickup actor class.!"), {*GetName()})));
|
||||
return EDataValidationResult::Invalid;
|
||||
}
|
||||
return Super::IsDataValid(Context);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Drops/GIS_DropperComponent.h"
|
||||
#include "Engine/World.h"
|
||||
#include "GIS_LogChannels.h"
|
||||
#include "Components/CapsuleComponent.h"
|
||||
#include "GameFramework/Character.h"
|
||||
#include "Kismet/KismetMathLibrary.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_DropperComponent)
|
||||
|
||||
|
||||
UGIS_DropperComponent::UGIS_DropperComponent()
|
||||
{
|
||||
PrimaryComponentTick.bStartWithTickEnabled = false;
|
||||
PrimaryComponentTick.bCanEverTick = false;
|
||||
SetIsReplicatedByDefault(true);
|
||||
}
|
||||
|
||||
void UGIS_DropperComponent::Drop()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
AActor* UGIS_DropperComponent::CreatePickupActorInstance_Implementation()
|
||||
{
|
||||
UWorld* World = GetWorld();
|
||||
check(World);
|
||||
if (PickupActorClass.IsNull())
|
||||
{
|
||||
GIS_CLOG(Error, "missing PickupActorClass!");
|
||||
return nullptr;
|
||||
}
|
||||
UClass* PickupClass = PickupActorClass.LoadSynchronous();
|
||||
if (PickupClass == nullptr)
|
||||
{
|
||||
GIS_CLOG(Error, "failed to load PickupActorClass!");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FVector Origin = CalcDropOrigin();
|
||||
AActor* Pickup = World->SpawnActor<AActor>(PickupClass, FTransform(Origin + CalcDropOffset()));
|
||||
if (Pickup == nullptr)
|
||||
{
|
||||
GIS_CLOG(Error, "failed to spawn pickup actor from PickupActorClass(%s)!", *PickupClass->GetName());
|
||||
return nullptr;
|
||||
}
|
||||
return Pickup;
|
||||
}
|
||||
|
||||
FVector UGIS_DropperComponent::CalcDropOrigin_Implementation() const
|
||||
{
|
||||
if (IsValid(DropTransform))
|
||||
{
|
||||
return DropTransform->GetActorLocation();
|
||||
}
|
||||
|
||||
FVector OriginLocation = GetOwner()->GetActorLocation();
|
||||
|
||||
if (const ACharacter* Character = Cast<ACharacter>(GetOwner()))
|
||||
{
|
||||
OriginLocation.Z -= Character->GetCapsuleComponent()->GetScaledCapsuleHalfHeight();
|
||||
}
|
||||
else if (const UCapsuleComponent* CapsuleComponent = Cast<UCapsuleComponent>(GetOwner()->GetRootComponent()))
|
||||
{
|
||||
OriginLocation.Z -= CapsuleComponent->GetScaledCapsuleHalfHeight();
|
||||
}
|
||||
return OriginLocation;
|
||||
}
|
||||
|
||||
FVector UGIS_DropperComponent::CalcDropOffset_Implementation() const
|
||||
{
|
||||
const float RandomX = UKismetMathLibrary::RandomFloatInRange(-DropRadius, DropRadius);
|
||||
const float RandomY = UKismetMathLibrary::RandomFloatInRange(-DropRadius, DropRadius);
|
||||
|
||||
return FVector(RandomX, RandomY, 0);
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Drops/GIS_ItemDropperComponent.h"
|
||||
#include "GIS_InventoryTags.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "GIS_InventorySystemComponent.h"
|
||||
#include "GIS_ItemCollection.h"
|
||||
#include "GIS_LogChannels.h"
|
||||
#include "Pickups/GIS_InventoryPickupComponent.h"
|
||||
#include "Pickups/GIS_ItemPickupComponent.h"
|
||||
#include "Pickups/GIS_WorldItemComponent.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_ItemDropperComponent)
|
||||
|
||||
void UGIS_ItemDropperComponent::Drop()
|
||||
{
|
||||
TArray<FGIS_ItemInfo> ItemsToDrop = GetItemsToDrop();
|
||||
DropItemsInternal(ItemsToDrop);
|
||||
}
|
||||
|
||||
void UGIS_ItemDropperComponent::BeginPlay()
|
||||
{
|
||||
if (!CollectionTag.IsValid())
|
||||
{
|
||||
CollectionTag = GIS_CollectionTags::Main;
|
||||
}
|
||||
|
||||
Super::BeginPlay();
|
||||
}
|
||||
|
||||
TArray<FGIS_ItemInfo> UGIS_ItemDropperComponent::GetItemsToDrop() const
|
||||
{
|
||||
return GetItemsToDropInternal();
|
||||
}
|
||||
|
||||
TArray<FGIS_ItemInfo> UGIS_ItemDropperComponent::GetItemsToDropInternal() const
|
||||
{
|
||||
TArray<FGIS_ItemInfo> Items;
|
||||
|
||||
UGIS_InventorySystemComponent* Inventory = UGIS_InventorySystemComponent::FindInventorySystemComponent(GetOwner());
|
||||
if (Inventory == nullptr)
|
||||
{
|
||||
GIS_CLOG(Error, "requires inventory system component to drop items.")
|
||||
return Items;
|
||||
}
|
||||
UGIS_ItemCollection* Collection = Inventory->GetCollectionByTag(CollectionTag);
|
||||
if (Collection == nullptr)
|
||||
{
|
||||
GIS_CLOG(Error, " inventory missing collection with tag:%s'", *CollectionTag.ToString())
|
||||
return Items;
|
||||
}
|
||||
|
||||
Items = Collection->GetAllItemInfos();
|
||||
return Items;
|
||||
}
|
||||
|
||||
void UGIS_ItemDropperComponent::DropItemsInternal(const TArray<FGIS_ItemInfo>& ItemInfos)
|
||||
{
|
||||
if (bDropAsInventory)
|
||||
{
|
||||
DropInventoryPickup(ItemInfos);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int32 i = 0; i < ItemInfos.Num(); i++)
|
||||
{
|
||||
DropItemPickup(ItemInfos[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGIS_ItemDropperComponent::DropInventoryPickup(const TArray<FGIS_ItemInfo>& ItemInfos)
|
||||
{
|
||||
if (AActor* PickupActor = CreatePickupActorInstance())
|
||||
{
|
||||
UGIS_InventorySystemComponent* Inventory = PickupActor->FindComponentByClass<UGIS_InventorySystemComponent>();
|
||||
UGIS_InventoryPickupComponent* Pickup = PickupActor->FindComponentByClass<UGIS_InventoryPickupComponent>();
|
||||
if (Inventory == nullptr || Pickup == nullptr)
|
||||
{
|
||||
GIS_CLOG(Error, "Spawned pickup(%s) missing either inventory component or inventory pickup component.", *PickupActor->GetName());
|
||||
return;
|
||||
}
|
||||
|
||||
UGIS_ItemCollection* Collection = Inventory->GetDefaultCollection();
|
||||
if (Collection == nullptr)
|
||||
{
|
||||
GIS_CLOG(Error, "Spawned pickup(%s)'s inventory doesn't have default collection.", *PickupActor->GetName());
|
||||
return;
|
||||
}
|
||||
Collection->RemoveAll();
|
||||
Collection->AddItems(ItemInfos);
|
||||
}
|
||||
}
|
||||
|
||||
void UGIS_ItemDropperComponent::DropItemPickup(const FGIS_ItemInfo& ItemInfo)
|
||||
{
|
||||
if (AActor* Pickup = CreatePickupActorInstance())
|
||||
{
|
||||
UGIS_ItemPickupComponent* ItemPickup = Pickup->FindComponentByClass<UGIS_ItemPickupComponent>();
|
||||
UGIS_WorldItemComponent* WorldItem = Pickup->FindComponentByClass<UGIS_WorldItemComponent>();
|
||||
if (ItemPickup == nullptr || WorldItem == nullptr)
|
||||
{
|
||||
GIS_CLOG(Error, "Spawned pickup(%s) missing either ItemPickup component or WorldItem component.", *Pickup->GetName());
|
||||
}
|
||||
WorldItem->SetItemInfo(ItemInfo.Item, ItemInfo.Amount);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Drops/GIS_RandomItemDropperComponent.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "GIS_InventorySystemComponent.h"
|
||||
#include "GIS_ItemCollection.h"
|
||||
#include "GIS_LogChannels.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_RandomItemDropperComponent)
|
||||
|
||||
TArray<FGIS_ItemInfo> UGIS_RandomItemDropperComponent::GetItemsToDropInternal() const
|
||||
{
|
||||
TArray<FGIS_ItemInfo> Results;
|
||||
|
||||
UGIS_InventorySystemComponent* Inventory = UGIS_InventorySystemComponent::FindInventorySystemComponent(GetOwner());
|
||||
if (Inventory == nullptr)
|
||||
{
|
||||
GIS_CLOG(Error, "requires inventory system component to drop items.")
|
||||
return Results;
|
||||
}
|
||||
UGIS_ItemCollection* Collection = Inventory->GetCollectionByTag(CollectionTag);
|
||||
if (Collection == nullptr)
|
||||
{
|
||||
GIS_CLOG(Error, " inventory missing collection with tag:%s'", *CollectionTag.ToString())
|
||||
return Results;
|
||||
}
|
||||
|
||||
const TArray<FGIS_ItemStack>& ItemStacks = Collection->GetAllItemStacks();
|
||||
|
||||
TArray<FGIS_ItemInfo> ItemInfos;
|
||||
ItemInfos.Reserve(ItemStacks.Num());
|
||||
int32 ProbabilitySum = 0;
|
||||
|
||||
for (int32 i = 0; i < ItemStacks.Num(); ++i)
|
||||
{
|
||||
//加权
|
||||
ProbabilitySum += ItemStacks[i].Amount;
|
||||
ItemInfos[i] = FGIS_ItemInfo(ProbabilitySum, ItemStacks[i]);
|
||||
}
|
||||
|
||||
int32 RandomAmount = FMath::RandRange(MinAmount, MaxAmount + 1);
|
||||
Results.Empty();
|
||||
|
||||
for (int i = 0; i < RandomAmount; i++)
|
||||
{
|
||||
auto& selectedItemInfo = GetRandomItemInfo(ItemInfos, ProbabilitySum);
|
||||
bool foundMatch = false;
|
||||
//去重,多个栈可能指向同一个道具实例,若发现重复
|
||||
for (int j = 0; j < Results.Num(); j++)
|
||||
{
|
||||
if (Results[j].Item == selectedItemInfo.Item)
|
||||
{
|
||||
Results[j] = FGIS_ItemInfo(Results[j].Amount + 1, Results[j]);
|
||||
foundMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundMatch) { Results.Add(FGIS_ItemInfo(1, selectedItemInfo)); }
|
||||
}
|
||||
|
||||
return Results;
|
||||
}
|
||||
|
||||
const FGIS_ItemInfo& UGIS_RandomItemDropperComponent::GetRandomItemInfo(const TArray<FGIS_ItemInfo>& ItemInfos, int32 ProbabilitySum) const
|
||||
{
|
||||
int32 RandomProbabilityIdx = FMath::RandRange(0, ProbabilitySum);
|
||||
|
||||
int32 min = 0;
|
||||
int32 max = ItemInfos.Num() - 1;
|
||||
int32 mid = 0;
|
||||
|
||||
while (min <= max)
|
||||
{
|
||||
mid = (min + max) / 2;
|
||||
if (ItemInfos[mid].Amount == RandomProbabilityIdx)
|
||||
{
|
||||
++mid;
|
||||
break;
|
||||
}
|
||||
|
||||
if (RandomProbabilityIdx < ItemInfos[mid].Amount
|
||||
&& (mid == 0 || RandomProbabilityIdx > ItemInfos[mid - 1].Amount)) { break; }
|
||||
|
||||
if (RandomProbabilityIdx < ItemInfos[mid].Amount) { max = mid - 1; }
|
||||
else
|
||||
{
|
||||
min = mid + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return ItemInfos[mid];
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GIS_EquipmentActorInterface.h"
|
||||
#include "GIS_EquipmentInstance.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_EquipmentActorInterface)
|
||||
|
||||
// Add default functionality here for any IGIS_EquipmentActorInterface functions that are not pure virtual.
|
||||
void IGIS_EquipmentActorInterface::ReceiveSourceEquipment_Implementation(UGIS_EquipmentInstance* NewEquipmentInstance, int32 Idx)
|
||||
{
|
||||
}
|
||||
|
||||
UGIS_EquipmentInstance* IGIS_EquipmentActorInterface::GetSourceEquipment_Implementation() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void IGIS_EquipmentActorInterface::ReceiveEquipmentBeginPlay_Implementation()
|
||||
{
|
||||
}
|
||||
|
||||
void IGIS_EquipmentActorInterface::ReceiveEquipmentEndPlay_Implementation()
|
||||
{
|
||||
}
|
||||
|
||||
UPrimitiveComponent* IGIS_EquipmentActorInterface::GetPrimitiveComponent_Implementation() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "GIS_EquipmentInstance.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "Engine/BlueprintGeneratedClass.h"
|
||||
#include "GameFramework/Character.h"
|
||||
#include "Net/UnrealNetwork.h"
|
||||
#include "TimerManager.h"
|
||||
#if UE_WITH_IRIS
|
||||
#include "Iris/ReplicationSystem/ReplicationFragmentUtil.h"
|
||||
#endif // UE_WITH_IRIS
|
||||
#include "GIS_EquipmentActorInterface.h"
|
||||
#include "GIS_EquipmentSystemComponent.h"
|
||||
#include "Items/GIS_ItemInstance.h"
|
||||
#include "GIS_ItemFragment_Equippable.h"
|
||||
#include "GIS_LogChannels.h"
|
||||
#include "Pickups/GIS_WorldItemComponent.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_EquipmentInstance)
|
||||
|
||||
class FLifetimeProperty;
|
||||
class UClass;
|
||||
class USceneComponent;
|
||||
|
||||
UGIS_EquipmentInstance::UGIS_EquipmentInstance(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
bIsActive = false;
|
||||
}
|
||||
|
||||
bool UGIS_EquipmentInstance::IsSupportedForNetworking() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
UWorld* UGIS_EquipmentInstance::GetWorld() const
|
||||
{
|
||||
if (OwningPawn)
|
||||
{
|
||||
return OwningPawn->GetWorld();
|
||||
}
|
||||
if (UObject* Outer = GetOuter())
|
||||
{
|
||||
return Outer->GetWorld();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void UGIS_EquipmentInstance::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||
{
|
||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||
|
||||
// DOREPLIFETIME(ThisClass, SourceItem);
|
||||
|
||||
// fix: https://forums.unrealengine.com/t/subobject-replication-for-blueprint-child-class/106205/4
|
||||
UBlueprintGeneratedClass* bpClass = Cast<UBlueprintGeneratedClass>(this->GetClass());
|
||||
if (bpClass != nullptr)
|
||||
{
|
||||
bpClass->GetLifetimeBlueprintReplicationList(OutLifetimeProps);
|
||||
}
|
||||
DOREPLIFETIME(ThisClass, EquipmentActors);
|
||||
}
|
||||
|
||||
void UGIS_EquipmentInstance::ReceiveOwningPawn_Implementation(APawn* NewPawn)
|
||||
{
|
||||
OwningPawn = NewPawn;
|
||||
}
|
||||
|
||||
APawn* UGIS_EquipmentInstance::GetOwningPawn_Implementation() const
|
||||
{
|
||||
return OwningPawn;
|
||||
}
|
||||
|
||||
void UGIS_EquipmentInstance::ReceiveSourceItem_Implementation(UGIS_ItemInstance* NewItem)
|
||||
{
|
||||
SourceItem = NewItem;
|
||||
}
|
||||
|
||||
UGIS_ItemInstance* UGIS_EquipmentInstance::GetSourceItem_Implementation() const
|
||||
{
|
||||
return SourceItem;
|
||||
}
|
||||
|
||||
void UGIS_EquipmentInstance::OnEquipmentBeginPlay_Implementation()
|
||||
{
|
||||
if (OwningPawn->HasAuthority())
|
||||
{
|
||||
SpawnAndSetupEquipmentActors(SourceItem->FindFragmentByClass<UGIS_ItemFragment_Equippable>()->ActorsToSpawn);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetupEquipmentActors(EquipmentActors);
|
||||
}
|
||||
}
|
||||
|
||||
void UGIS_EquipmentInstance::OnEquipmentTick_Implementation(float DeltaSeconds)
|
||||
{
|
||||
}
|
||||
|
||||
void UGIS_EquipmentInstance::OnEquipmentEndPlay_Implementation()
|
||||
{
|
||||
DestroyEquipmentActors();
|
||||
}
|
||||
|
||||
#if UE_WITH_IRIS
|
||||
void UGIS_EquipmentInstance::RegisterReplicationFragments(UE::Net::FFragmentRegistrationContext& Context, UE::Net::EFragmentRegistrationFlags RegistrationFlags)
|
||||
{
|
||||
using namespace UE::Net;
|
||||
|
||||
// Build descriptors and allocate PropertyReplicationFragments for this object
|
||||
FReplicationFragmentUtil::CreateAndRegisterFragmentsForObject(this, Context, RegistrationFlags);
|
||||
}
|
||||
|
||||
#endif // UE_WITH_IRIS
|
||||
|
||||
bool UGIS_EquipmentInstance::IsEquipmentActive_Implementation() const
|
||||
{
|
||||
return bIsActive;
|
||||
}
|
||||
|
||||
void UGIS_EquipmentInstance::OnActiveStateChanged_Implementation(bool NewActiveState)
|
||||
{
|
||||
bIsActive = NewActiveState;
|
||||
SetupActiveStateForEquipmentActors(EquipmentActors);
|
||||
OnActiveStateChangedEvent.Broadcast(NewActiveState);
|
||||
}
|
||||
|
||||
APawn* UGIS_EquipmentInstance::GetTypedOwningPawn(TSubclassOf<APawn> PawnType) const
|
||||
{
|
||||
APawn* Result = nullptr;
|
||||
if (UClass* ActualPawnType = PawnType)
|
||||
{
|
||||
if (APawn* Pawn = Execute_GetOwningPawn(this))
|
||||
{
|
||||
if (Pawn->IsA(ActualPawnType))
|
||||
{
|
||||
Result = Pawn;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
bool UGIS_EquipmentInstance::CanActivate_Implementation() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
int32 UGIS_EquipmentInstance::GetIndexOfEquipmentActor(const AActor* InEquipmentActor) const
|
||||
{
|
||||
if (IsValid(InEquipmentActor) && EquipmentActors.Contains(InEquipmentActor))
|
||||
{
|
||||
for (int32 i = 0; i < EquipmentActors.Num(); i++)
|
||||
{
|
||||
if (EquipmentActors[i] == InEquipmentActor)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return INDEX_NONE;
|
||||
}
|
||||
|
||||
AActor* UGIS_EquipmentInstance::GetTypedEquipmentActor(TSubclassOf<AActor> DesiredClass) const
|
||||
{
|
||||
if (UClass* RealClass = DesiredClass)
|
||||
{
|
||||
for (const TObjectPtr<AActor>& SpawnedActor : EquipmentActors)
|
||||
{
|
||||
if (SpawnedActor && SpawnedActor->GetClass()->IsChildOf(RealClass))
|
||||
{
|
||||
return SpawnedActor;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void UGIS_EquipmentInstance::SpawnAndSetupEquipmentActors(const TArray<FGIS_EquipmentActorToSpawn>& ActorsToSpawn)
|
||||
{
|
||||
if (OwningPawn == nullptr || !OwningPawn->HasAuthority())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
USceneComponent* AttachParent = GetAttachParentForSpawnedActors(OwningPawn);
|
||||
|
||||
for (int32 i = 0; i < ActorsToSpawn.Num(); ++i)
|
||||
{
|
||||
const FGIS_EquipmentActorToSpawn& SpawnInfo = ActorsToSpawn[i];
|
||||
if (SpawnInfo.ActorToSpawn.IsNull())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
TSubclassOf<AActor> ActorClass = SpawnInfo.ActorToSpawn.LoadSynchronous();
|
||||
if (!ActorClass)
|
||||
{
|
||||
GIS_CLOG(Warning, "%s Failed to load actor class at index: %d ", *GetName(), i);
|
||||
continue;
|
||||
}
|
||||
if (!ActorClass->ImplementsInterface(UGIS_EquipmentActorInterface::StaticClass()))
|
||||
{
|
||||
GIS_CLOG(Warning, "actor class(%s) doesn't implements:%s", *ActorClass->GetName(), *UGIS_EquipmentActorInterface::StaticClass()->GetName());
|
||||
continue;
|
||||
}
|
||||
AActor* NewActor = GetWorld()->SpawnActorDeferred<AActor>(ActorClass, FTransform::Identity, OwningPawn);
|
||||
if (NewActor == nullptr)
|
||||
{
|
||||
GIS_CLOG(Warning, "%s Failed to spawn actor of class: %s at index: %d ", *GetName(), *ActorClass->GetName(), i);
|
||||
continue;
|
||||
}
|
||||
BeforeSpawningActor(NewActor);
|
||||
NewActor->FinishSpawning(FTransform::Identity, /*bIsDefaultTransform=*/true);
|
||||
if (SpawnInfo.bShouldAttach)
|
||||
{
|
||||
NewActor->SetActorRelativeTransform(SpawnInfo.AttachTransform);
|
||||
NewActor->AttachToComponent(AttachParent, FAttachmentTransformRules::KeepRelativeTransform, SpawnInfo.AttachSocket);
|
||||
}
|
||||
EquipmentActors.Add(NewActor);
|
||||
}
|
||||
SetupEquipmentActors(EquipmentActors);
|
||||
}
|
||||
|
||||
void UGIS_EquipmentInstance::DestroyEquipmentActors()
|
||||
{
|
||||
if (OwningPawn == nullptr || !OwningPawn->HasAuthority())
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (AActor* Actor : EquipmentActors)
|
||||
{
|
||||
if (!IsValid(Actor))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsValid(Actor) && Actor->GetClass()->ImplementsInterface(UGIS_EquipmentActorInterface::StaticClass()))
|
||||
{
|
||||
IGIS_EquipmentActorInterface::Execute_ReceiveEquipmentEndPlay(Actor);
|
||||
}
|
||||
|
||||
if (IsValid(Actor))
|
||||
{
|
||||
Actor->Destroy();
|
||||
}
|
||||
}
|
||||
EquipmentActors.Empty();
|
||||
}
|
||||
|
||||
USceneComponent* UGIS_EquipmentInstance::GetAttachParentForSpawnedActors_Implementation(APawn* Pawn) const
|
||||
{
|
||||
if (ACharacter* Char = Cast<ACharacter>(Pawn))
|
||||
{
|
||||
return Char->GetMesh();
|
||||
}
|
||||
if (Pawn)
|
||||
{
|
||||
return Pawn->FindComponentByClass<USkeletalMeshComponent>();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void UGIS_EquipmentInstance::BeforeSpawningActor_Implementation(AActor* SpawningActor) const
|
||||
{
|
||||
}
|
||||
|
||||
void UGIS_EquipmentInstance::SetupEquipmentActors_Implementation(const TArray<AActor*>& InActors)
|
||||
{
|
||||
if (!OwningPawn)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (OwningPawn->HasAuthority())
|
||||
{
|
||||
for (int32 i = 0; i < InActors.Num(); i++)
|
||||
{
|
||||
UGIS_WorldItemComponent* WorldItem = InActors[i]->FindComponentByClass<UGIS_WorldItemComponent>();
|
||||
if (WorldItem != nullptr)
|
||||
{
|
||||
WorldItem->SetItemInfo(SourceItem, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int32 i = 0; i < InActors.Num(); i++)
|
||||
{
|
||||
AActor* Actor = InActors[i];
|
||||
if (IsValid(Actor))
|
||||
{
|
||||
if (Actor->GetClass()->ImplementsInterface(UGIS_EquipmentActorInterface::StaticClass()))
|
||||
{
|
||||
IGIS_EquipmentActorInterface::Execute_ReceiveSourceEquipment(Actor, this, i);
|
||||
IGIS_EquipmentActorInterface::Execute_ReceiveEquipmentBeginPlay(Actor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGIS_EquipmentInstance::OnRep_EquipmentActors()
|
||||
{
|
||||
}
|
||||
|
||||
bool UGIS_EquipmentInstance::IsEquipmentActorsValid(int32 Num) const
|
||||
{
|
||||
if (EquipmentActors.Num() == Num)
|
||||
{
|
||||
bool bValid = true;
|
||||
for (int32 i = 0; i < EquipmentActors.Num(); i++)
|
||||
{
|
||||
if (!IsValid(EquipmentActors[i]))
|
||||
{
|
||||
bValid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return bValid;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void UGIS_EquipmentInstance::SetupInitialStateForEquipmentActors(const TArray<AActor*>& InActors)
|
||||
{
|
||||
}
|
||||
|
||||
void UGIS_EquipmentInstance::SetupActiveStateForEquipmentActors(const TArray<AActor*>& InActors) const
|
||||
{
|
||||
for (int32 i = 0; i < InActors.Num(); i++)
|
||||
{
|
||||
AActor* Actor = InActors[i];
|
||||
if (IsValid(Actor) && Actor->GetClass()->ImplementsInterface(UGIS_EquipmentInterface::StaticClass()))
|
||||
{
|
||||
Execute_OnActiveStateChanged(Actor, bIsActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "GIS_EquipmentInterface.h"
|
||||
#include "GameFramework/Pawn.h"
|
||||
#include "GIS_EquipmentInstance.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_EquipmentInterface)
|
||||
|
||||
void IGIS_EquipmentInterface::ReceiveOwningPawn_Implementation(APawn* NewPawn)
|
||||
{
|
||||
}
|
||||
|
||||
APawn* IGIS_EquipmentInterface::GetOwningPawn_Implementation() const
|
||||
{
|
||||
APawn* ReturnPawn = Cast<APawn>(_getUObject()->GetOuter());
|
||||
return ReturnPawn;
|
||||
}
|
||||
|
||||
void IGIS_EquipmentInterface::ReceiveSourceItem_Implementation(UGIS_ItemInstance* NewItem)
|
||||
{
|
||||
}
|
||||
|
||||
UGIS_ItemInstance* IGIS_EquipmentInterface::GetSourceItem_Implementation() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void IGIS_EquipmentInterface::OnActiveStateChanged_Implementation(bool bNewActiveState)
|
||||
{
|
||||
}
|
||||
|
||||
bool IGIS_EquipmentInterface::IsEquipmentActive_Implementation() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void IGIS_EquipmentInterface::OnEquipmentBeginPlay_Implementation()
|
||||
{
|
||||
}
|
||||
|
||||
void IGIS_EquipmentInterface::OnOnEquipmentTick_Implementation(float DeltaSeconds)
|
||||
{
|
||||
}
|
||||
|
||||
void IGIS_EquipmentInterface::OnEquipmentEndPlay_Implementation()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
// Add default functionality here for any IGIS_EquipmentInterface functions that are not pure virtual.
|
||||
|
||||
bool IGIS_EquipmentInterface::IsReplicationManaged_Implementation()
|
||||
{
|
||||
return _getUObject() && _getUObject()->GetClass()->IsChildOf(UGIS_EquipmentInstance::StaticClass());
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "GIS_EquipmentStructLibrary.h"
|
||||
|
||||
#include "GIS_EquipmentInstance.h"
|
||||
#include "GIS_EquipmentSystemComponent.h"
|
||||
#include "GIS_ItemFragment_Equippable.h"
|
||||
#include "GIS_ItemInstance.h"
|
||||
#include "GIS_LogChannels.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_EquipmentStructLibrary)
|
||||
|
||||
FString FGIS_EquipmentEntry::GetDebugString() const
|
||||
{
|
||||
return FString::Printf(TEXT("%s"), *GetNameSafe(Instance));
|
||||
}
|
||||
|
||||
bool FGIS_EquipmentEntry::CheckClientDataReady() const
|
||||
{
|
||||
bool bDataReady = Instance != nullptr && ItemInstance != nullptr && EquippedSlot.IsValid();
|
||||
if (!bDataReady)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
UGIS_EquipmentInstance* EquipmentInstance = Cast<UGIS_EquipmentInstance>(Instance);
|
||||
if (!EquipmentInstance)
|
||||
return true;
|
||||
|
||||
const UGIS_ItemFragment_Equippable* Equippable = ItemInstance->FindFragmentByClass<UGIS_ItemFragment_Equippable>();
|
||||
|
||||
if (!Equippable || Equippable->bActorBased || Equippable->ActorsToSpawn.IsEmpty())
|
||||
return true;
|
||||
|
||||
TArray<AActor*> EquipmentActors = EquipmentInstance->GetEquipmentActors();
|
||||
// actor num doesn't match.
|
||||
if (EquipmentActors.Num() != Equippable->ActorsToSpawn.Num())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool bValid = true;
|
||||
for (int32 i = 0; i < EquipmentActors.Num(); i++)
|
||||
{
|
||||
if (!::IsValid(EquipmentActors[i]))
|
||||
{
|
||||
bValid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return bValid;
|
||||
}
|
||||
|
||||
bool FGIS_EquipmentEntry::IsValidEntry() const
|
||||
{
|
||||
return Instance != nullptr && ItemInstance != nullptr && EquippedSlot.IsValid();
|
||||
}
|
||||
|
||||
void FGIS_EquipmentContainer::PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize)
|
||||
{
|
||||
for (int32 Index : RemovedIndices)
|
||||
{
|
||||
FGIS_EquipmentEntry& Entry = Entries[Index];
|
||||
|
||||
// already in the list.
|
||||
if (OwningComponent->SlotToInstanceMap.Contains(Entry.EquippedSlot))
|
||||
{
|
||||
OwningComponent->OnEquipmentEntryRemoved(Entry, Index);
|
||||
}
|
||||
else if (OwningComponent->PendingEquipmentEntries.Contains(Index))
|
||||
{
|
||||
GIS_OWNED_CLOG(OwningComponent, Warning, "Discard pending equipment(%s).", *OwningComponent->PendingEquipmentEntries[Index].GetDebugString())
|
||||
OwningComponent->PendingEquipmentEntries.Remove(Index);
|
||||
}
|
||||
Entry.bPrevActive = Entry.bActive;
|
||||
Entry.PrevEquippedGroup = Entry.EquippedGroup;
|
||||
}
|
||||
}
|
||||
|
||||
void FGIS_EquipmentContainer::PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize)
|
||||
{
|
||||
for (int32 Index : AddedIndices)
|
||||
{
|
||||
FGIS_EquipmentEntry& Entry = Entries[Index];
|
||||
if (OwningComponent)
|
||||
{
|
||||
if (OwningComponent->GetOwner() && OwningComponent->IsEquipmentSystemInitialized() && Entry.CheckClientDataReady())
|
||||
{
|
||||
OwningComponent->OnEquipmentEntryAdded(Entry, Index);
|
||||
}
|
||||
else
|
||||
{
|
||||
OwningComponent->PendingEquipmentEntries.Add(Index, Entry);
|
||||
}
|
||||
}
|
||||
Entry.bPrevActive = Entry.bActive;
|
||||
}
|
||||
}
|
||||
|
||||
void FGIS_EquipmentContainer::PostReplicatedChange(const TArrayView<int32> ChangedIndices, int32 FinalSize)
|
||||
{
|
||||
for (int32 Index : ChangedIndices)
|
||||
{
|
||||
FGIS_EquipmentEntry& Entry = Entries[Index];
|
||||
if (OwningComponent->SlotToInstanceMap.Contains(Entry.EquippedSlot)) // Already Added.
|
||||
{
|
||||
OwningComponent->OnEquipmentEntryChanged(Entry, Index);
|
||||
}
|
||||
else if (OwningComponent->PendingEquipmentEntries.Contains(Index)) // In pending list.
|
||||
{
|
||||
OwningComponent->PendingEquipmentEntries.Emplace(Index, Entry);
|
||||
}
|
||||
else
|
||||
{
|
||||
OwningComponent->PendingEquipmentEntries.Add(Index, Entry); // Add to pending list.
|
||||
}
|
||||
Entry.bPrevActive = Entry.bActive;
|
||||
Entry.PrevEquippedGroup = Entry.EquippedGroup;
|
||||
}
|
||||
}
|
||||
|
||||
int32 FGIS_EquipmentContainer::IndexOfBySlot(const FGameplayTag& Slot) const
|
||||
{
|
||||
if (!Slot.IsValid())
|
||||
{
|
||||
return INDEX_NONE;
|
||||
}
|
||||
return Entries.IndexOfByPredicate([Slot](const FGIS_EquipmentEntry& Entry)
|
||||
{
|
||||
return Entry.EquippedSlot == Slot;
|
||||
});
|
||||
}
|
||||
|
||||
int32 FGIS_EquipmentContainer::IndexOfByGroup(const FGameplayTag& Group) const
|
||||
{
|
||||
if (!Group.IsValid())
|
||||
{
|
||||
return INDEX_NONE;
|
||||
}
|
||||
return Entries.IndexOfByPredicate([Group](const FGIS_EquipmentEntry& Entry)
|
||||
{
|
||||
return Entry.EquippedGroup.IsValid() && Entry.EquippedGroup == Group;
|
||||
});
|
||||
}
|
||||
|
||||
int32 FGIS_EquipmentContainer::IndexOfByInstance(const UObject* Instance) const
|
||||
{
|
||||
if (!IsValid(Instance))
|
||||
{
|
||||
return INDEX_NONE;
|
||||
}
|
||||
return Entries.IndexOfByPredicate([Instance](const FGIS_EquipmentEntry& Entry)
|
||||
{
|
||||
return Entry.Instance && Entry.Instance == Instance;
|
||||
});
|
||||
}
|
||||
|
||||
int32 FGIS_EquipmentContainer::IndexOfByItem(const UGIS_ItemInstance* Item) const
|
||||
{
|
||||
if (!IsValid(Item))
|
||||
{
|
||||
return INDEX_NONE;
|
||||
}
|
||||
return Entries.IndexOfByPredicate([Item](const FGIS_EquipmentEntry& Entry)
|
||||
{
|
||||
return Entry.ItemInstance && Entry.ItemInstance == Item;
|
||||
});
|
||||
}
|
||||
|
||||
int32 FGIS_EquipmentContainer::IndexOfByItemId(const FGuid& ItemId) const
|
||||
{
|
||||
if (!ItemId.IsValid())
|
||||
{
|
||||
return INDEX_NONE;
|
||||
}
|
||||
return Entries.IndexOfByPredicate([ItemId](const FGIS_EquipmentEntry& Entry)
|
||||
{
|
||||
return Entry.ItemInstance && Entry.ItemInstance->GetItemId() == ItemId;
|
||||
});
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,46 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "GIS_CurrencyContainer.h"
|
||||
|
||||
#include "GIS_CurrencySystemComponent.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_CurrencyContainer)
|
||||
|
||||
void FGIS_CurrencyContainer::PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize)
|
||||
{
|
||||
for (int32 Index : RemovedIndices)
|
||||
{
|
||||
FGIS_CurrencyEntry& Entry = Entries[Index];
|
||||
if (OwningComponent)
|
||||
{
|
||||
OwningComponent->OnCurrencyEntryRemoved(Entry, Index);
|
||||
}
|
||||
Entry.PrevAmount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void FGIS_CurrencyContainer::PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize)
|
||||
{
|
||||
for (int32 Index : AddedIndices)
|
||||
{
|
||||
FGIS_CurrencyEntry& Entry = Entries[Index];
|
||||
if (OwningComponent)
|
||||
{
|
||||
OwningComponent->OnCurrencyEntryAdded(Entry, Index);
|
||||
}
|
||||
Entry.PrevAmount = Entry.Amount;
|
||||
}
|
||||
}
|
||||
|
||||
void FGIS_CurrencyContainer::PostReplicatedChange(const TArrayView<int32> ChangedIndices, int32 FinalSize)
|
||||
{
|
||||
for (int32 Index : ChangedIndices)
|
||||
{
|
||||
FGIS_CurrencyEntry& Entry = Entries[Index];
|
||||
if (OwningComponent)
|
||||
{
|
||||
OwningComponent->OnCurrencyEntryUpdated(Entry, Index, Entry.PrevAmount, Entry.Amount);
|
||||
}
|
||||
Entry.PrevAmount = Entry.Amount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Exchange/GIS_CurrencyDefinition.h"
|
||||
|
||||
#include "GIS_InventorySubsystem.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_CurrencyDefinition)
|
||||
|
||||
FGIS_CurrencyExchangeRate::FGIS_CurrencyExchangeRate(const UGIS_CurrencyDefinition* InCurrency, float InExchangeRate)
|
||||
{
|
||||
Currency = InCurrency;
|
||||
ExchangeRate = InExchangeRate;
|
||||
}
|
||||
|
||||
|
||||
bool UGIS_CurrencyDefinition::TryGetExchangeRateTo(const UGIS_CurrencyDefinition* OtherCurrency, double& ExchangeRate) const
|
||||
{
|
||||
if (OtherCurrency == nullptr)
|
||||
{
|
||||
ExchangeRate = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
//same currency
|
||||
if (OtherCurrency == this)
|
||||
{
|
||||
ExchangeRate = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
const FGIS_CurrencyExchangeRate RootExchangeRate = GetRootExchangeRate();
|
||||
const FGIS_CurrencyExchangeRate OtherRootExchangeRate = OtherCurrency->GetRootExchangeRate();
|
||||
if (RootExchangeRate.Currency != OtherRootExchangeRate.Currency)
|
||||
{
|
||||
ExchangeRate = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
ExchangeRate = RootExchangeRate.ExchangeRate / OtherRootExchangeRate.ExchangeRate;
|
||||
return true;
|
||||
}
|
||||
|
||||
FGIS_CurrencyExchangeRate UGIS_CurrencyDefinition::GetRootExchangeRate(double AdditionalExchangeRate) const
|
||||
{
|
||||
if (ParentCurrency)
|
||||
{
|
||||
return ParentCurrency->GetRootExchangeRate(AdditionalExchangeRate * ExchangeRateToParent);
|
||||
}
|
||||
return FGIS_CurrencyExchangeRate(this, AdditionalExchangeRate);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GIS_CurrencyEntry.h"
|
||||
#include "GIS_CurrencyDefinition.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_CurrencyEntry)
|
||||
FGIS_CurrencyEntry::FGIS_CurrencyEntry()
|
||||
{
|
||||
Definition = nullptr;
|
||||
Amount = 0;
|
||||
}
|
||||
|
||||
FGIS_CurrencyEntry::FGIS_CurrencyEntry(const TObjectPtr<const UGIS_CurrencyDefinition>& InDefinition, float InAmount)
|
||||
{
|
||||
Definition = InDefinition;
|
||||
Amount = InAmount;
|
||||
}
|
||||
|
||||
FGIS_CurrencyEntry::FGIS_CurrencyEntry(float InAmount, const TObjectPtr<const UGIS_CurrencyDefinition>& InDefinition)
|
||||
{
|
||||
Definition = InDefinition;
|
||||
Amount = InAmount;
|
||||
}
|
||||
|
||||
bool FGIS_CurrencyEntry::Equals(const FGIS_CurrencyEntry& Other) const
|
||||
{
|
||||
return Amount == Other.Amount && Definition == Other.Definition;
|
||||
}
|
||||
|
||||
FString FGIS_CurrencyEntry::ToString() const
|
||||
{
|
||||
return FString::Format(TEXT("{0} {1}"), {Definition ? Definition->GetName() : TEXT("None"), Amount});
|
||||
}
|
||||
|
||||
bool FGIS_CurrencyEntry::operator==(const FGIS_CurrencyEntry& Rhs) const
|
||||
{
|
||||
return Equals(Rhs);
|
||||
}
|
||||
|
||||
bool FGIS_CurrencyEntry::operator!=(const FGIS_CurrencyEntry& Rhs) const
|
||||
{
|
||||
return !Equals(Rhs);
|
||||
}
|
||||
@@ -0,0 +1,550 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Exchange/GIS_CurrencySystemComponent.h"
|
||||
#include "Engine/World.h"
|
||||
#include "GIS_InventorySubsystem.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "GIS_InventoryTags.h"
|
||||
#include "GIS_LogChannels.h"
|
||||
#include "Net/UnrealNetwork.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_CurrencySystemComponent)
|
||||
|
||||
UGIS_CurrencySystemComponent::UGIS_CurrencySystemComponent() : Container(this)
|
||||
{
|
||||
PrimaryComponentTick.bCanEverTick = false;
|
||||
SetIsReplicatedByDefault(true);
|
||||
bReplicateUsingRegisteredSubObjectList = true;
|
||||
bWantsInitializeComponent = true;
|
||||
}
|
||||
|
||||
UGIS_CurrencySystemComponent* UGIS_CurrencySystemComponent::GetCurrencySystemComponent(const AActor* Actor)
|
||||
{
|
||||
return IsValid(Actor) ? Actor->FindComponentByClass<UGIS_CurrencySystemComponent>() : nullptr;
|
||||
}
|
||||
|
||||
void UGIS_CurrencySystemComponent::GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const
|
||||
{
|
||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||
|
||||
DOREPLIFETIME(ThisClass, Container);
|
||||
}
|
||||
|
||||
void UGIS_CurrencySystemComponent::InitializeComponent()
|
||||
{
|
||||
Super::InitializeComponent();
|
||||
if (GetWorld() && !GetWorld()->IsGameWorld())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Container.OwningComponent = this;
|
||||
}
|
||||
|
||||
TArray<FGIS_CurrencyEntry> UGIS_CurrencySystemComponent::GetAllCurrencies() const
|
||||
{
|
||||
TArray<FGIS_CurrencyEntry> Ret;
|
||||
for (const FGIS_CurrencyEntry& Item : Container.Entries)
|
||||
{
|
||||
Ret.Add(FGIS_CurrencyEntry(Item.Definition, Item.Amount));
|
||||
}
|
||||
return Ret;
|
||||
}
|
||||
|
||||
void UGIS_CurrencySystemComponent::SetCurrencies(const TArray<FGIS_CurrencyEntry>& InCurrencyInfos)
|
||||
{
|
||||
TArray<FGIS_CurrencyEntry> NewEntries = InCurrencyInfos.FilterByPredicate([](const FGIS_CurrencyEntry& Item)
|
||||
{
|
||||
return Item.Definition != nullptr && Item.Amount > 0;
|
||||
});
|
||||
|
||||
Container.Entries.Empty();
|
||||
CurrencyMap.Empty();
|
||||
Container.Entries = NewEntries;
|
||||
for (const FGIS_CurrencyEntry& NewItem : NewEntries)
|
||||
{
|
||||
CurrencyMap.Add(NewItem.Definition, NewItem.Amount);
|
||||
}
|
||||
Container.MarkArrayDirty();
|
||||
}
|
||||
|
||||
void UGIS_CurrencySystemComponent::EmptyCurrencies()
|
||||
{
|
||||
Container.Entries.Empty();
|
||||
CurrencyMap.Empty();
|
||||
Container.MarkArrayDirty();
|
||||
}
|
||||
|
||||
bool UGIS_CurrencySystemComponent::GetCurrency(TSoftObjectPtr<const UGIS_CurrencyDefinition> CurrencyDefinition, FGIS_CurrencyEntry& OutCurrencyInfo) const
|
||||
{
|
||||
if (CurrencyDefinition.IsNull())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const UGIS_CurrencyDefinition* Definition = CurrencyDefinition.LoadSynchronous();
|
||||
|
||||
return GetCurrencyInternal(Definition, OutCurrencyInfo);
|
||||
}
|
||||
|
||||
bool UGIS_CurrencySystemComponent::GetCurrencies(TArray<TSoftObjectPtr<UGIS_CurrencyDefinition>> CurrencyDefinitions, TArray<FGIS_CurrencyEntry>& OutCurrencyInfos) const
|
||||
{
|
||||
TArray<TObjectPtr<const UGIS_CurrencyDefinition>> Definitions;
|
||||
for (TSoftObjectPtr<const UGIS_CurrencyDefinition> Currency : CurrencyDefinitions)
|
||||
{
|
||||
if (const UGIS_CurrencyDefinition* Definition = !Currency.IsNull() ? Currency.LoadSynchronous() : nullptr)
|
||||
{
|
||||
Definitions.AddUnique(Definition);
|
||||
}
|
||||
}
|
||||
return GetCurrenciesInternal(Definitions, OutCurrencyInfos);
|
||||
}
|
||||
|
||||
bool UGIS_CurrencySystemComponent::AddCurrency(FGIS_CurrencyEntry CurrencyInfo)
|
||||
{
|
||||
return AddCurrencyInternal(CurrencyInfo);
|
||||
}
|
||||
|
||||
bool UGIS_CurrencySystemComponent::RemoveCurrency(FGIS_CurrencyEntry CurrencyInfo)
|
||||
{
|
||||
return RemoveCurrencyInternal(CurrencyInfo);
|
||||
}
|
||||
|
||||
bool UGIS_CurrencySystemComponent::HasCurrency(FGIS_CurrencyEntry CurrencyInfo) const
|
||||
{
|
||||
return HasCurrencyInternal(CurrencyInfo);
|
||||
}
|
||||
|
||||
bool UGIS_CurrencySystemComponent::HasCurrencies(const TArray<FGIS_CurrencyEntry>& CurrencyInfos)
|
||||
{
|
||||
bool bOk = true;
|
||||
for (auto& Currency : CurrencyInfos)
|
||||
{
|
||||
if (!HasCurrencyInternal(Currency))
|
||||
{
|
||||
bOk = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return bOk;
|
||||
}
|
||||
|
||||
bool UGIS_CurrencySystemComponent::AddCurrencies(const TArray<FGIS_CurrencyEntry>& CurrencyInfos)
|
||||
{
|
||||
for (const FGIS_CurrencyEntry& Currency : CurrencyInfos)
|
||||
{
|
||||
AddCurrencyInternal(Currency);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UGIS_CurrencySystemComponent::RemoveCurrencies(const TArray<FGIS_CurrencyEntry>& CurrencyInfos)
|
||||
{
|
||||
for (const FGIS_CurrencyEntry& Currency : CurrencyInfos)
|
||||
{
|
||||
RemoveCurrencyInternal(Currency);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void UGIS_CurrencySystemComponent::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
AddInitialCurrencies();
|
||||
}
|
||||
|
||||
void UGIS_CurrencySystemComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
Super::EndPlay(EndPlayReason);
|
||||
}
|
||||
|
||||
#pragma region Static Calculations (To be continued)
|
||||
#if 0
|
||||
bool UGIS_CurrencySystemComponent::AddCurrencyInternal(const FGIS_CurrencyEntry& CurrencyInfo, bool bNotify)
|
||||
{
|
||||
if (!CurrencyInfo.Definition || CurrencyInfo.Amount <= 0)
|
||||
{
|
||||
FFrame::KismetExecutionMessage(TEXT("An invalid currency definition was passed."), ELogVerbosity::Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto discreteCurrencyAmounts = ConvertToDiscrete(CurrencyInfo.Definition, CurrencyInfo.Amount);
|
||||
|
||||
|
||||
auto currencyAmountCopy = Container.Entries;
|
||||
|
||||
auto added = DiscreteAddition(currencyAmountCopy, discreteCurrencyAmounts);
|
||||
|
||||
SetCurrencyInfos(added);
|
||||
return true;
|
||||
}
|
||||
|
||||
int32 UGIS_CurrencySystemComponent::FindIndexWithCurrency(const UGIS_CurrencyDefinition* Currency, const TArray<FGIS_CurrencyEntry>& List)
|
||||
{
|
||||
for (int32 i = 0; i < List.Num(); i++)
|
||||
{
|
||||
if (List[i].Definition == Currency)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return INDEX_NONE;
|
||||
}
|
||||
|
||||
int32 UGIS_CurrencySystemComponent::FindOrCreateCurrencyIndex(const UGIS_CurrencyDefinition* Currency, TArray<FGIS_CurrencyEntry>& Result)
|
||||
{
|
||||
int32 Index = FindIndexWithCurrency(Currency, Result);
|
||||
if (Index != INDEX_NONE)
|
||||
{
|
||||
return Index;
|
||||
}
|
||||
|
||||
Result.Add(FGIS_CurrencyEntry(0.0f, Currency));
|
||||
return Result.Num() - 1;
|
||||
}
|
||||
|
||||
TArray<FGIS_CurrencyEntry> UGIS_CurrencySystemComponent::DiscreteAddition(const TArray<FGIS_CurrencyEntry>& Lhs, const TArray<FGIS_CurrencyEntry>& Rhs)
|
||||
{
|
||||
TArray<FGIS_CurrencyEntry> Result;
|
||||
TArray<FGIS_CurrencyEntry> TempArray = Lhs; // 复制 Lhs 作为初始结果
|
||||
|
||||
for (int32 i = 0; i < Rhs.Num(); i++)
|
||||
{
|
||||
// 交替使用 Result 和 TempArray 避免重复分配
|
||||
TArray<FGIS_CurrencyEntry>& Target = (i % 2 == 0) ? Result : TempArray;
|
||||
TempArray = DiscreteAddition(TempArray, Rhs[i].Definition, Rhs[i].Amount);
|
||||
}
|
||||
|
||||
// 确保最终结果存储在 Result 中
|
||||
if (TempArray != Result)
|
||||
{
|
||||
Result = TempArray;
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
TArray<FGIS_CurrencyEntry> UGIS_CurrencySystemComponent::DiscreteAddition(const TArray<FGIS_CurrencyEntry>& CurrencyAmounts, const UGIS_CurrencyDefinition* Currency, float Amount)
|
||||
{
|
||||
TArray<FGIS_CurrencyEntry> Result = CurrencyAmounts; // 复制输入数组
|
||||
while (true)
|
||||
{
|
||||
int32 Index = FindOrCreateCurrencyIndex(Currency, Result);
|
||||
|
||||
float Sum = Amount + Result[Index].Amount;
|
||||
float Mod = FMath::Fmod(Sum, Currency->MaxAmount + 1.0f);
|
||||
Result[Index] = FGIS_CurrencyEntry(Mod, Currency);
|
||||
|
||||
float Overflow = Sum - Mod;
|
||||
if (Overflow <= 0.0f)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
const UGIS_CurrencyDefinition* OverflowCurrency = Currency->OverflowCurrency;
|
||||
double Rate;
|
||||
if (!OverflowCurrency || !Currency->TryGetExchangeRateTo(OverflowCurrency, Rate))
|
||||
{
|
||||
return SetAllFractionToMax(CurrencyAmounts, Currency);
|
||||
}
|
||||
|
||||
double OverflowDouble = Overflow * Rate; // 使用 Rate 而非 Amount
|
||||
int32 OverflowInt = FMath::TruncToInt(OverflowDouble);
|
||||
|
||||
double Diff = OverflowDouble - OverflowInt;
|
||||
if (Diff > 0.0)
|
||||
{
|
||||
TArray<FGIS_CurrencyEntry> DiscreteDiff = ConvertToDiscrete(OverflowCurrency, Diff);
|
||||
Result = DiscreteAddition(Result, DiscreteDiff);
|
||||
}
|
||||
|
||||
Currency = OverflowCurrency;
|
||||
Amount = static_cast<float>(OverflowInt);
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
TArray<FGIS_CurrencyEntry> UGIS_CurrencySystemComponent::SetAllFractionToMax(const TArray<FGIS_CurrencyEntry>& CurrencyAmounts, const UGIS_CurrencyDefinition* Currency)
|
||||
{
|
||||
TArray<FGIS_CurrencyEntry> Result = CurrencyAmounts;
|
||||
|
||||
while (Currency != nullptr)
|
||||
{
|
||||
int32 Index = FindOrCreateCurrencyIndex(Currency, Result);
|
||||
Result[Index] = FGIS_CurrencyEntry(Currency->MaxAmount, Currency);
|
||||
Currency = Currency->FractionCurrency;
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
TArray<FGIS_CurrencyEntry> UGIS_CurrencySystemComponent::MaxedOutAmount(const UGIS_CurrencyDefinition* Currency)
|
||||
{
|
||||
TArray<FGIS_CurrencyEntry> Result;
|
||||
int32 Index = 0;
|
||||
|
||||
while (Currency != nullptr)
|
||||
{
|
||||
Result.SetNum(Index + 1);
|
||||
Result[Index] = FGIS_CurrencyEntry(Currency->MaxAmount, Currency);
|
||||
Index++;
|
||||
Currency = Currency->FractionCurrency;
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
TArray<FGIS_CurrencyEntry> UGIS_CurrencySystemComponent::ConvertToDiscrete(const UGIS_CurrencyDefinition* Currency, double Amount)
|
||||
{
|
||||
TArray<FGIS_CurrencyEntry> Result;
|
||||
int32 Index = 0;
|
||||
|
||||
TArray<FGIS_CurrencyEntry> OverflowCurrencies = ConvertOverflow(Currency, Amount);
|
||||
|
||||
bool bMaxed = false;
|
||||
for (int32 i = OverflowCurrencies.Num() - 1; i >= 0; i--)
|
||||
{
|
||||
Result.SetNum(Index + 1);
|
||||
if (Currency->FractionCurrency == OverflowCurrencies[i].Definition)
|
||||
{
|
||||
bMaxed = true;
|
||||
}
|
||||
Result[Index] = OverflowCurrencies[i];
|
||||
Index++;
|
||||
}
|
||||
|
||||
if (!bMaxed)
|
||||
{
|
||||
TArray<FGIS_CurrencyEntry> FractionCurrencies = ConvertFraction(Currency, Amount);
|
||||
for (int32 i = 0; i < FractionCurrencies.Num(); i++)
|
||||
{
|
||||
Result.SetNum(Index + 1);
|
||||
Result[Index] = FractionCurrencies[i];
|
||||
Index++;
|
||||
}
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
TArray<FGIS_CurrencyEntry> UGIS_CurrencySystemComponent::ConvertOverflow(const UGIS_CurrencyDefinition* Currency, double Amount)
|
||||
{
|
||||
TArray<FGIS_CurrencyEntry> Result;
|
||||
int32 Index = 0;
|
||||
const UGIS_CurrencyDefinition* NextCurrency = Currency;
|
||||
|
||||
Amount = FMath::TruncToDouble(Amount);
|
||||
|
||||
while (NextCurrency != nullptr && Amount > 0.0)
|
||||
{
|
||||
double Mod = fmod(Amount, NextCurrency->MaxAmount + 1.0);
|
||||
float IntMod = static_cast<float>(Mod);
|
||||
if (IntMod > 0.0f)
|
||||
{
|
||||
Result.SetNum(Index + 1);
|
||||
Result[Index] = FGIS_CurrencyEntry(IntMod, NextCurrency);
|
||||
Index++;
|
||||
}
|
||||
|
||||
if (Amount - IntMod <= 0.0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
double Rate;
|
||||
if (!NextCurrency->OverflowCurrency || !NextCurrency->TryGetExchangeRateTo(NextCurrency->OverflowCurrency, Rate))
|
||||
{
|
||||
return MaxedOutAmount(NextCurrency);
|
||||
}
|
||||
|
||||
Amount -= Mod;
|
||||
Amount *= Rate;
|
||||
NextCurrency = NextCurrency->OverflowCurrency;
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
TArray<FGIS_CurrencyEntry> UGIS_CurrencySystemComponent::ConvertFraction(const UGIS_CurrencyDefinition* Currency, double Amount)
|
||||
{
|
||||
TArray<FGIS_CurrencyEntry> Result;
|
||||
if (FMath::IsNearlyZero(fmod(Amount, 1.0)))
|
||||
{
|
||||
return Result;
|
||||
}
|
||||
|
||||
int32 Index = 0;
|
||||
const UGIS_CurrencyDefinition* NextCurrency = Currency;
|
||||
double DecimalAmount = fmod(Amount, 1.0);
|
||||
|
||||
while (NextCurrency != nullptr && DecimalAmount > 0.0)
|
||||
{
|
||||
if (!NextCurrency->FractionCurrency)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
double Rate;
|
||||
if (!NextCurrency->TryGetExchangeRateTo(NextCurrency->FractionCurrency, Rate))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
NextCurrency = NextCurrency->FractionCurrency;
|
||||
DecimalAmount *= Rate;
|
||||
int32 Floor = static_cast<int32>(FMath::Floor(DecimalAmount));
|
||||
|
||||
if (Floor > 0)
|
||||
{
|
||||
Result.SetNum(Index + 1);
|
||||
Result[Index] = FGIS_CurrencyEntry(static_cast<float>(Floor), NextCurrency);
|
||||
Index++;
|
||||
}
|
||||
|
||||
DecimalAmount -= Floor;
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
#endif
|
||||
#pragma endregion
|
||||
void UGIS_CurrencySystemComponent::OnCurrencyEntryAdded(const FGIS_CurrencyEntry& Entry, int32 Idx)
|
||||
{
|
||||
CurrencyMap.Add(Entry.Definition, Entry.Amount);
|
||||
OnCurrencyChangedEvent.Broadcast(Entry.Definition, 0, Entry.Amount);
|
||||
}
|
||||
|
||||
void UGIS_CurrencySystemComponent::OnCurrencyEntryRemoved(const FGIS_CurrencyEntry& Entry, int32 Idx)
|
||||
{
|
||||
CurrencyMap.Remove(Entry.Definition);
|
||||
OnCurrencyChangedEvent.Broadcast(Entry.Definition, Entry.PrevAmount, 0);
|
||||
}
|
||||
|
||||
void UGIS_CurrencySystemComponent::OnCurrencyEntryUpdated(const FGIS_CurrencyEntry& Entry, int32 Idx, float OldAmount, float NewAmount)
|
||||
{
|
||||
CurrencyMap.Emplace(Entry.Definition, NewAmount);
|
||||
OnCurrencyChangedEvent.Broadcast(Entry.Definition, OldAmount, NewAmount);
|
||||
}
|
||||
|
||||
|
||||
void UGIS_CurrencySystemComponent::OnCurrencyChanged(TObjectPtr<const UGIS_CurrencyDefinition> Currency, float OldValue, float NewValue)
|
||||
{
|
||||
OnCurrencyChangedEvent.Broadcast(Currency, OldValue, NewValue);
|
||||
}
|
||||
|
||||
|
||||
void UGIS_CurrencySystemComponent::AddInitialCurrencies_Implementation()
|
||||
{
|
||||
if (GetOwner()->HasAuthority())
|
||||
{
|
||||
//TODO check records from save game.
|
||||
if (!DefaultCurrencies.IsEmpty())
|
||||
{
|
||||
AddCurrencies(DefaultCurrencies);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UGIS_CurrencySystemComponent::GetCurrencyInternal(const TObjectPtr<const UGIS_CurrencyDefinition>& Currency, FGIS_CurrencyEntry& OutCurrencyInfo) const
|
||||
{
|
||||
if (CurrencyMap.Contains(Currency))
|
||||
{
|
||||
OutCurrencyInfo = FGIS_CurrencyEntry(Currency, CurrencyMap[Currency]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UGIS_CurrencySystemComponent::GetCurrenciesInternal(const TArray<TObjectPtr<const UGIS_CurrencyDefinition>>& Currencies, TArray<FGIS_CurrencyEntry>& OutCurrencies) const
|
||||
{
|
||||
TArray<FGIS_CurrencyEntry> Result;
|
||||
for (int32 i = 0; i < Currencies.Num(); i++)
|
||||
{
|
||||
FGIS_CurrencyEntry Info;
|
||||
if (GetCurrencyInternal(Currencies[i], Info))
|
||||
{
|
||||
Result.Add(Info);
|
||||
}
|
||||
}
|
||||
|
||||
OutCurrencies = Result;
|
||||
|
||||
return !OutCurrencies.IsEmpty();
|
||||
}
|
||||
|
||||
bool UGIS_CurrencySystemComponent::AddCurrencyInternal(const FGIS_CurrencyEntry& CurrencyInfo, bool bNotify)
|
||||
{
|
||||
if (!CurrencyInfo.Definition || CurrencyInfo.Amount <= 0)
|
||||
{
|
||||
GIS_CLOG(Warning, "An invalid currency definition was passed.")
|
||||
// FFrame::KismetExecutionMessage(TEXT("An invalid tag was passed."), ELogVerbosity::Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int32 i = 0; i < Container.Entries.Num(); i++)
|
||||
{
|
||||
// handle adding to existing value.
|
||||
FGIS_CurrencyEntry& Entry = Container.Entries[i];
|
||||
if (Entry.Definition == CurrencyInfo.Definition)
|
||||
{
|
||||
const float OldValue = Entry.Amount;
|
||||
const float NewValue = Entry.Amount + CurrencyInfo.Amount;
|
||||
Entry.Amount = NewValue;
|
||||
OnCurrencyEntryUpdated(Entry, i, OldValue, NewValue);
|
||||
Container.MarkItemDirty(Entry);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int32 Idx = Container.Entries.AddDefaulted();
|
||||
Container.Entries[Idx] = CurrencyInfo;
|
||||
OnCurrencyEntryAdded(Container.Entries[Idx], Idx);
|
||||
Container.MarkItemDirty(Container.Entries[Idx]);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UGIS_CurrencySystemComponent::RemoveCurrencyInternal(const FGIS_CurrencyEntry& CurrencyInfo, bool bNotify)
|
||||
{
|
||||
if (!CurrencyInfo.Definition || CurrencyInfo.Amount <= 0)
|
||||
{
|
||||
GIS_CLOG(Warning, "An invalid tag was passed to RemoveItem")
|
||||
// FFrame::KismetExecutionMessage(TEXT("An invalid tag was passed to RemoveItem"), ELogVerbosity::Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int32 i = 0; i < Container.Entries.Num(); i++)
|
||||
{
|
||||
FGIS_CurrencyEntry& Entry = Container.Entries[i];
|
||||
if (Entry.Definition == CurrencyInfo.Definition)
|
||||
{
|
||||
if (Entry.Amount <= CurrencyInfo.Amount)
|
||||
{
|
||||
OnCurrencyEntryRemoved(Entry, i);
|
||||
Container.Entries.RemoveAt(i);
|
||||
Container.MarkArrayDirty();
|
||||
}
|
||||
else
|
||||
{
|
||||
const float OldValue = Entry.Amount;
|
||||
const float NewValue = Entry.Amount - CurrencyInfo.Amount;
|
||||
Entry.Amount = NewValue;
|
||||
OnCurrencyEntryUpdated(Entry, i, OldValue, NewValue);
|
||||
Container.MarkItemDirty(Entry);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UGIS_CurrencySystemComponent::HasCurrencyInternal(const FGIS_CurrencyEntry& CurrencyInfo) const
|
||||
{
|
||||
if (CurrencyMap.Contains(CurrencyInfo.Definition) && CurrencyInfo.Amount > 0)
|
||||
{
|
||||
return CurrencyMap[CurrencyInfo.Definition] >= CurrencyInfo.Amount;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Exchange/Shops/GIS_ShopCondition.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_ShopCondition)
|
||||
// Add default functionality here for any IGShopCondition functions that are not pure virtual.
|
||||
@@ -0,0 +1,365 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Exchange/Shops/GIS_ShopSystemComponent.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "GIS_CurrencySystemComponent.h"
|
||||
#include "GIS_InventoryFunctionLibrary.h"
|
||||
#include "GIS_InventorySubsystem.h"
|
||||
#include "GIS_InventorySystemComponent.h"
|
||||
#include "GIS_ItemCollection.h"
|
||||
#include "GIS_ItemDefinition.h"
|
||||
#include "GIS_ItemFragment_Shoppable.h"
|
||||
#include "Items/GIS_ItemInstance.h"
|
||||
#include "GIS_LogChannels.h"
|
||||
#include "Exchange/Shops/GIS_ShopCondition.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_ShopSystemComponent)
|
||||
|
||||
|
||||
UGIS_ShopSystemComponent::UGIS_ShopSystemComponent()
|
||||
{
|
||||
PrimaryComponentTick.bStartWithTickEnabled = false;
|
||||
PrimaryComponentTick.bCanEverTick = false;
|
||||
}
|
||||
|
||||
UGIS_ShopSystemComponent* UGIS_ShopSystemComponent::GetShopSystemComponent(const AActor* Actor)
|
||||
{
|
||||
return IsValid(Actor) ? Actor->FindComponentByClass<UGIS_ShopSystemComponent>() : nullptr;
|
||||
}
|
||||
|
||||
UGIS_InventorySystemComponent* UGIS_ShopSystemComponent::GetInventory() const
|
||||
{
|
||||
return OwningInventory;
|
||||
}
|
||||
|
||||
bool UGIS_ShopSystemComponent::BuyItem(UGIS_InventorySystemComponent* BuyerInventory, UGIS_CurrencySystemComponent* CurrencySystem, const FGIS_ItemInfo& ItemInfo)
|
||||
{
|
||||
return BuyItemInternal(BuyerInventory, CurrencySystem, ItemInfo);
|
||||
}
|
||||
|
||||
bool UGIS_ShopSystemComponent::SellItem(UGIS_InventorySystemComponent* SellerInventory, UGIS_CurrencySystemComponent* CurrencySystem, const FGIS_ItemInfo& ItemInfo)
|
||||
{
|
||||
return SellItemInternal(SellerInventory, CurrencySystem, ItemInfo);
|
||||
}
|
||||
|
||||
bool UGIS_ShopSystemComponent::CanBuyerBuyItem(UGIS_InventorySystemComponent* BuyerInventory, UGIS_CurrencySystemComponent* CurrencySystem, const FGIS_ItemInfo& ItemInfo) const
|
||||
{
|
||||
UGIS_ItemCollection* TargetCollection = BuyerInventory->GetCollectionByTag(TargetItemCollectionToAddOnBuy);
|
||||
if (TargetCollection == nullptr)
|
||||
{
|
||||
GIS_CLOG(Warning, "buyer's inventory missing collection named:%s", *TargetItemCollectionToAddOnBuy.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
FGIS_ItemInfo CanAddItemInfo;
|
||||
if (!TargetCollection->CanAddItem(ItemInfo, CanAddItemInfo))
|
||||
{
|
||||
GIS_CLOG(Warning, "buyer's collection can't add this item(%s)", *ItemInfo.GetDebugString());
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < BuyConditions.Num(); i++)
|
||||
{
|
||||
if (BuyConditions[i]->CanBuy(this, BuyerInventory, CurrencySystem, ItemInfo)) { continue; }
|
||||
GIS_CLOG(Warning, "buy collection(%s) reject buying item(%s)", *BuyConditions[i].GetObject()->GetClass()->GetName(), *ItemInfo.GetDebugString());
|
||||
return false;
|
||||
}
|
||||
|
||||
return CanBuyerBuyItemInternal(BuyerInventory, CurrencySystem, ItemInfo);
|
||||
}
|
||||
|
||||
bool UGIS_ShopSystemComponent::CanSellerSellItem(UGIS_InventorySystemComponent* SellerInventory, UGIS_CurrencySystemComponent* CurrencySystem, const FGIS_ItemInfo& ItemInfo) const
|
||||
{
|
||||
for (int i = 0; i < SellConditions.Num(); i++)
|
||||
{
|
||||
if (SellConditions[i]->CanSell(this, SellerInventory, CurrencySystem, ItemInfo)) { continue; }
|
||||
GIS_CLOG(Warning, "sell collection(%s) reject selling item(%s)", *SellConditions[i].GetObject()->GetClass()->GetName(), *ItemInfo.GetDebugString());
|
||||
return false;
|
||||
}
|
||||
return CanSellerSellItemInternal(SellerInventory, CurrencySystem, ItemInfo);
|
||||
}
|
||||
|
||||
bool UGIS_ShopSystemComponent::IsItemBuyable(const FGIS_ItemInfo& ItemInfo) const
|
||||
{
|
||||
if (OwningInventory == nullptr) { return false; }
|
||||
|
||||
if (!ItemInfo.IsValid())
|
||||
{
|
||||
GIS_CLOG(Warning, "invalid item to buy.");
|
||||
return false;
|
||||
}
|
||||
|
||||
UGIS_ItemCollection* ItemCollection = ItemInfo.Item->GetOwningCollection();
|
||||
if (ItemCollection == nullptr) { ItemCollection = OwningInventory->GetDefaultCollection(); }
|
||||
|
||||
if (!ItemCollection->HasItem(ItemInfo.Item, 1))
|
||||
{
|
||||
GIS_CLOG(Warning, "shop's inventory doesn't have item:%s", *ItemInfo.Item->GetDefinition()->GetName());
|
||||
return false;
|
||||
}
|
||||
const UGIS_ItemFragment_Shoppable* Shoppable = ItemInfo.Item->FindFragmentByClass<UGIS_ItemFragment_Shoppable>();
|
||||
if (Shoppable == nullptr)
|
||||
{
|
||||
GIS_CLOG(Warning, "item(%s) is not buyable, missing Shoppable fragment!", *ItemInfo.GetDebugString());
|
||||
return false;
|
||||
}
|
||||
if (Shoppable->BuyCurrencyAmounts.IsEmpty())
|
||||
{
|
||||
GIS_CLOG(Warning, "item(%s) is not buyable, missing BuyCurrencyAmounts in shoppable fragment!", *ItemInfo.GetDebugString());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UGIS_ShopSystemComponent::IsItemSellable(const FGIS_ItemInfo& ItemInfo) const
|
||||
{
|
||||
if (!ItemInfo.IsValid())
|
||||
{
|
||||
GIS_CLOG(Warning, "invalid item to sell.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const UGIS_ItemFragment_Shoppable* Shoppable = ItemInfo.Item->FindFragmentByClass<UGIS_ItemFragment_Shoppable>();
|
||||
|
||||
if (Shoppable == nullptr)
|
||||
{
|
||||
GIS_CLOG(Warning, "item(%s) is not sellable, missing Shoppable fragment!", *ItemInfo.GetDebugString());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Shoppable->SellCurrencyAmounts.IsEmpty())
|
||||
{
|
||||
GIS_CLOG(Warning, "item(%s) is not sellable, missing SellCurrencyAmounts in shoppable fragment!", *ItemInfo.GetDebugString());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
float UGIS_ShopSystemComponent::GetBuyModifierForBuyer_Implementation(UGIS_InventorySystemComponent* BuyerInventory) const
|
||||
{
|
||||
return 1 + BuyPriceModifier;
|
||||
}
|
||||
|
||||
// float UGIS_ShopSystemComponent::GetBuyModifierForItem(UGIS_InventorySystemComponent* BuyerInventory, FGIS_ItemInfo ItemInfo) const
|
||||
// {
|
||||
// return 1;
|
||||
// }
|
||||
|
||||
float UGIS_ShopSystemComponent::GetSellModifierForSeller_Implementation(UGIS_InventorySystemComponent* SellerInventory) const
|
||||
{
|
||||
return 1 + SellPriceModifer;
|
||||
}
|
||||
|
||||
// float UGIS_ShopSystemComponent::GetSellModifierForItem(UGIS_InventorySystemComponent* SellerInventory, const FGIS_ItemInfo& ItemInfo) const
|
||||
// {
|
||||
// return 1;
|
||||
// }
|
||||
|
||||
bool UGIS_ShopSystemComponent::TryGetBuyValueForBuyer_Implementation(UGIS_InventorySystemComponent* Buyer, const FGIS_ItemInfo& ItemInfo, TArray<FGIS_CurrencyEntry>& BuyValue) const
|
||||
{
|
||||
if (!IsValid(ItemInfo.Item))
|
||||
{
|
||||
GIS_CLOG(Warning, "invalid item to buy.");
|
||||
return false;
|
||||
}
|
||||
const UGIS_ItemFragment_Shoppable* Shoppable = ItemInfo.Item->FindFragmentByClass<UGIS_ItemFragment_Shoppable>();
|
||||
if (!IsValid(Shoppable))
|
||||
{
|
||||
GIS_CLOG(Warning, "missing Shoppable fragment for item:%s!", *GetNameSafe(ItemInfo.Item->GetDefinition()));
|
||||
return false;
|
||||
}
|
||||
|
||||
float Modifier = GetBuyModifierForBuyer(Buyer);
|
||||
BuyValue = UGIS_InventoryFunctionLibrary::MultiplyCurrencies(Shoppable->BuyCurrencyAmounts, Modifier * ItemInfo.Amount);
|
||||
|
||||
return BuyValue.IsEmpty() == false;
|
||||
}
|
||||
|
||||
bool UGIS_ShopSystemComponent::TryGetSellValueForSeller_Implementation(UGIS_InventorySystemComponent* Seller, const FGIS_ItemInfo& ItemInfo, TArray<FGIS_CurrencyEntry>& SellValue) const
|
||||
{
|
||||
if (!IsValid(ItemInfo.Item))
|
||||
{
|
||||
GIS_CLOG(Warning, "invalid item to sell.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const UGIS_ItemFragment_Shoppable* Shoppable = ItemInfo.Item->FindFragmentByClass<UGIS_ItemFragment_Shoppable>();
|
||||
if (!IsValid(Shoppable))
|
||||
{
|
||||
GIS_CLOG(Warning, "missing Shoppable fragment for item:%s!", *GetNameSafe(ItemInfo.Item->GetDefinition()));
|
||||
return false;
|
||||
}
|
||||
|
||||
float Modifier = GetSellModifierForSeller(Seller);
|
||||
|
||||
SellValue = UGIS_InventoryFunctionLibrary::MultiplyCurrencies(Shoppable->SellCurrencyAmounts, Modifier * ItemInfo.Amount);
|
||||
|
||||
return SellValue.IsEmpty() == false;
|
||||
}
|
||||
|
||||
void UGIS_ShopSystemComponent::BeginPlay()
|
||||
{
|
||||
OwningInventory = UGIS_InventorySystemComponent::FindInventorySystemComponent(GetOwner());
|
||||
if (!OwningInventory)
|
||||
{
|
||||
GIS_CLOG(Error, "Requires inventory system component!");
|
||||
}
|
||||
{
|
||||
TArray<UActorComponent*> Components = GetOwner()->GetComponentsByInterface(UGIS_ShopBuyCondition::StaticClass());
|
||||
BuyConditions.Empty();
|
||||
for (const auto Component : Components)
|
||||
{
|
||||
BuyConditions.Add(Component);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
TArray<UActorComponent*> Components = GetOwner()->GetComponentsByInterface(UGIS_ShopSellCondition::StaticClass());
|
||||
SellConditions.Empty();
|
||||
for (const auto Component : Components)
|
||||
{
|
||||
SellConditions.Add(Component);
|
||||
}
|
||||
}
|
||||
|
||||
Super::BeginPlay();
|
||||
}
|
||||
|
||||
void UGIS_ShopSystemComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
Super::EndPlay(EndPlayReason);
|
||||
}
|
||||
|
||||
bool UGIS_ShopSystemComponent::CanSellerSellItemInternal_Implementation(UGIS_InventorySystemComponent* SellerInventory, UGIS_CurrencySystemComponent* CurrencySystem,
|
||||
const FGIS_ItemInfo& ItemInfo) const
|
||||
{
|
||||
UGIS_ItemCollection* ItemCollection = ItemInfo.ItemCollection;
|
||||
if (ItemCollection == nullptr || ItemCollection->GetOwningInventory() == SellerInventory)
|
||||
{
|
||||
ItemCollection = SellerInventory->GetDefaultCollection();
|
||||
}
|
||||
|
||||
if (ItemCollection == nullptr)
|
||||
{
|
||||
GIS_CLOG(Warning, "seller:%s doesn't have valid default collection.", *GetNameSafe(SellerInventory));
|
||||
return false;
|
||||
}
|
||||
|
||||
//atleast has one.
|
||||
if (!ItemCollection->HasItem(ItemInfo.Item, 1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UGIS_ShopSystemComponent::CanBuyerBuyItemInternal_Implementation(UGIS_InventorySystemComponent* BuyerInventory, UGIS_CurrencySystemComponent* CurrencySystem, const FGIS_ItemInfo& ItemInfo) const
|
||||
{
|
||||
TArray<FGIS_CurrencyEntry> BuyPrice;
|
||||
if (TryGetBuyValueForBuyer(BuyerInventory, ItemInfo, BuyPrice))
|
||||
{
|
||||
return CurrencySystem->HasCurrencies(BuyPrice);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UGIS_ShopSystemComponent::SellItemInternal_Implementation(UGIS_InventorySystemComponent* Seller, UGIS_CurrencySystemComponent* CurrencySystem, const FGIS_ItemInfo& ItemInfo)
|
||||
{
|
||||
if (!IsValid(Seller) || !ItemInfo.IsValid() || !IsValid(CurrencySystem))
|
||||
{
|
||||
GIS_CLOG(Warning, "passed invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
if (!IsItemSellable(ItemInfo))
|
||||
{
|
||||
GIS_CLOG(Warning, "item:%s is not sellable", *ItemInfo.GetDebugString());
|
||||
return false;
|
||||
}
|
||||
if (!CanSellerSellItem(Seller, CurrencySystem, ItemInfo))
|
||||
{
|
||||
GIS_CLOG(Warning, "seller can sell this item:%s", *ItemInfo.GetDebugString());
|
||||
return false;
|
||||
}
|
||||
UGIS_ItemCollection* ItemCollection = ItemInfo.ItemCollection;
|
||||
if (ItemCollection == nullptr || ItemCollection->GetOwningInventory() == Seller)
|
||||
{
|
||||
ItemCollection = Seller->GetDefaultCollection();
|
||||
}
|
||||
if (ItemCollection->RemoveItem(ItemInfo).Amount != ItemInfo.Amount)
|
||||
{
|
||||
GIS_CLOG(Error, "Failed to remove item(%s) from inventory!", *ItemInfo.GetDebugString());
|
||||
return false;
|
||||
}
|
||||
|
||||
TArray<FGIS_CurrencyEntry> SellCurrencyAmount;
|
||||
if (!TryGetSellValueForSeller(Seller, ItemInfo, SellCurrencyAmount))
|
||||
{
|
||||
GIS_CLOG(Error, "can't get sell value for item:%s", *ItemInfo.GetDebugString());
|
||||
return false;
|
||||
}
|
||||
|
||||
CurrencySystem->AddCurrencies(SellCurrencyAmount);
|
||||
|
||||
OwningInventory->AddItem(ItemInfo);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UGIS_ShopSystemComponent::BuyItemInternal_Implementation(UGIS_InventorySystemComponent* BuyerInventory, UGIS_CurrencySystemComponent* CurrencySystem, const FGIS_ItemInfo& ItemInfo)
|
||||
{
|
||||
if (!IsValid(BuyerInventory) || !ItemInfo.IsValid() || !IsValid(CurrencySystem))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!IsItemBuyable(ItemInfo))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!CanBuyerBuyItem(BuyerInventory, CurrencySystem, ItemInfo))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UGIS_ItemCollection* TargetCollection = BuyerInventory->GetCollectionByTag(TargetItemCollectionToAddOnBuy);
|
||||
if (TargetCollection == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//remove currency from buyer's currency system.
|
||||
TArray<FGIS_CurrencyEntry> BuyPrice;
|
||||
if (TryGetBuyValueForBuyer(BuyerInventory, ItemInfo, BuyPrice))
|
||||
{
|
||||
CurrencySystem->RemoveCurrencies(BuyPrice);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ItemInfo.Item->IsUnique())
|
||||
{
|
||||
for (int32 i = 0; i < ItemInfo.Amount; ++i)
|
||||
{
|
||||
UGIS_ItemInstance* NewItem = UGIS_InventorySubsystem::Get(this)->CreateItem(BuyerInventory->GetOwner(), ItemInfo.Item->GetDefinition());
|
||||
TargetCollection->AddItem(NewItem, 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TargetCollection->AddItem(ItemInfo);
|
||||
}
|
||||
|
||||
// Remove item from shop's inventory.
|
||||
OwningInventory->RemoveItem(ItemInfo);
|
||||
|
||||
// Add currency to shop's currency system.
|
||||
if (UGIS_CurrencySystemComponent* OwningCurrencySystem = UGIS_CurrencySystemComponent::GetCurrencySystemComponent(GetOwner()))
|
||||
{
|
||||
OwningCurrencySystem->AddCurrencies(BuyPrice);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,400 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GIS_InventoryFactory.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "Serialization/MemoryReader.h"
|
||||
#include "Serialization/MemoryWriter.h"
|
||||
#include "GIS_CollectionContainer.h"
|
||||
#include "GIS_InventorySystemComponent.h"
|
||||
#include "GIS_ItemCollection.h"
|
||||
#include "Items/GIS_ItemDefinition.h"
|
||||
#include "GIS_ItemFragment.h"
|
||||
#include "GIS_LogChannels.h"
|
||||
#include "Items/GIS_ItemInstance.h"
|
||||
#include "Misc/DataValidation.h"
|
||||
#include "Serialization/ObjectAndNameAsStringProxyArchive.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_InventoryFactory)
|
||||
|
||||
UGIS_ItemInstance* UGIS_InventoryFactory::DuplicateItem_Implementation(AActor* Owner, UGIS_ItemInstance* SrcItem, bool bGenerateNewId)
|
||||
{
|
||||
if (!IsValid(SrcItem) || SrcItem->GetDefinition() == nullptr)
|
||||
{
|
||||
GIS_LOG(Error, "Missing src item or src item doesn't have valid definition.");
|
||||
return nullptr;
|
||||
}
|
||||
UGIS_ItemInstance* NewItem = DuplicateObject(SrcItem, Owner);
|
||||
if (bGenerateNewId)
|
||||
{
|
||||
NewItem->SetItemId(FGuid::NewGuid());
|
||||
}
|
||||
NewItem->OnItemDuplicated(SrcItem);
|
||||
return NewItem;
|
||||
}
|
||||
|
||||
UGIS_ItemCollection* UGIS_InventoryFactory::CreateCollection_Implementation(AActor* Owner, const UGIS_ItemCollectionDefinition* Definition)
|
||||
{
|
||||
if (!IsValid(Owner))
|
||||
{
|
||||
GIS_LOG(Error, "Missing owner.");
|
||||
return nullptr;
|
||||
}
|
||||
if (!IsValid(Definition))
|
||||
{
|
||||
GIS_LOG(Error, "Cannot create collection with null collection Definition.");
|
||||
return nullptr;
|
||||
}
|
||||
TSubclassOf<UGIS_ItemCollection> CollectionClass = Definition->GetCollectionInstanceClass();
|
||||
if (CollectionClass == nullptr)
|
||||
{
|
||||
GIS_LOG(Error, "definition(%s) doesn't specify valid item collection class.", *Definition->GetName())
|
||||
return nullptr;
|
||||
}
|
||||
UGIS_ItemCollection* NewCollection = NewObject<UGIS_ItemCollection>(Owner, CollectionClass);
|
||||
if (NewCollection == nullptr)
|
||||
{
|
||||
GIS_LOG(Error, "failed to create instance of %s", *GetNameSafe(Definition))
|
||||
return nullptr;
|
||||
}
|
||||
return NewCollection;
|
||||
}
|
||||
|
||||
UGIS_InventoryFactory::UGIS_InventoryFactory()
|
||||
{
|
||||
DefaultItemInstanceClass = UGIS_ItemInstance::StaticClass();
|
||||
}
|
||||
|
||||
UGIS_ItemInstance* UGIS_InventoryFactory::CreateItem_Implementation(AActor* Owner, const UGIS_ItemDefinition* ItemDefinition)
|
||||
{
|
||||
if (!IsValid(Owner))
|
||||
{
|
||||
GIS_LOG(Error, "Missing owner.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (ItemDefinition == nullptr)
|
||||
{
|
||||
GIS_LOG(Error, "Cannot create Item with null Item Definition.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TSubclassOf<UGIS_ItemInstance> ItemInstanceClass = DefaultItemInstanceClass.LoadSynchronous();
|
||||
|
||||
if (ItemInstanceClass == nullptr)
|
||||
{
|
||||
GIS_LOG(Error, "ItemDefinition: %s has invalid InstanceType.", *ItemDefinition->GetName());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UGIS_ItemInstance* Item = NewObject<UGIS_ItemInstance>(Owner, ItemInstanceClass);
|
||||
if (Item == nullptr)
|
||||
{
|
||||
GIS_LOG(Error, "ItemInstanceClass: %s create failed.", *ItemInstanceClass->GetName());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Item->SetItemId(FGuid::NewGuid());
|
||||
Item->SetDefinition(ItemDefinition);
|
||||
|
||||
for (const UGIS_ItemFragment* Fragment : ItemDefinition->Fragments)
|
||||
{
|
||||
if (Fragment == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (Fragment->GetCompatibleMixinDataType() != nullptr)
|
||||
{
|
||||
FInstancedStruct FragmentState;
|
||||
if (Fragment->MakeDefaultMixinData(FragmentState))
|
||||
{
|
||||
if (FragmentState.IsValid() && FragmentState.GetScriptStruct() == Fragment->GetCompatibleMixinDataType())
|
||||
{
|
||||
Item->SetFragmentStateByClass(Fragment->GetClass(), FragmentState);
|
||||
}
|
||||
}
|
||||
}
|
||||
Fragment->OnInstanceCreated(Item);
|
||||
}
|
||||
return Item;
|
||||
}
|
||||
|
||||
UGIS_ItemInstance* UGIS_InventoryFactory::DeserializeItem_Implementation(AActor* Owner, const FGIS_ItemRecord& Record)
|
||||
{
|
||||
if (!IsValid(Owner))
|
||||
{
|
||||
GIS_LOG(Error, "Missing owner.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const FSoftObjectPath ItemDefinitionAssetPath = FSoftObjectPath(Record.DefinitionAssetPath);
|
||||
const TSoftObjectPtr<UGIS_ItemDefinition> ItemDefinitionReference = TSoftObjectPtr<UGIS_ItemDefinition>(ItemDefinitionAssetPath);
|
||||
|
||||
UGIS_ItemDefinition* ItemDefinition = !ItemDefinitionReference.IsNull() ? ItemDefinitionReference.LoadSynchronous() : nullptr;
|
||||
if (!IsValid(ItemDefinition))
|
||||
{
|
||||
GIS_LOG(Warning, "invalid item definition on path:%s", *ItemDefinitionAssetPath.ToString());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UGIS_ItemInstance* ItemInstance = CreateItem(Owner, ItemDefinition);
|
||||
if (!IsValid(ItemInstance))
|
||||
{
|
||||
GIS_LOG(Warning, "failed to create item instance from definition:%s", *GetNameSafe(ItemDefinition));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ItemInstance->SetItemId(Record.ItemId);
|
||||
ItemInstance->SetDefinition(ItemDefinition);
|
||||
|
||||
TArray<FGIS_Mixin> ConvertedMixins = FGIS_MixinContainer::ConvertRecordsToMixins(Record.FragmentStateRecords);
|
||||
ConvertedMixins = ConvertedMixins.FilterByPredicate([ItemDefinition](const FGIS_Mixin& Mixin)
|
||||
{
|
||||
return ItemDefinition->Fragments.Contains(Mixin.Target);
|
||||
});
|
||||
|
||||
for (const FGIS_Mixin& ConvertedMixin : ConvertedMixins)
|
||||
{
|
||||
ItemInstance->SetFragmentStateByClass(ConvertedMixin.Target->GetClass(), ConvertedMixin.Data);
|
||||
}
|
||||
|
||||
FMemoryReader Reader(Record.ByteData);
|
||||
FObjectAndNameAsStringProxyArchive Ar2(Reader, true);
|
||||
Ar2.ArIsSaveGame = true;
|
||||
ItemInstance->Serialize(Ar2);
|
||||
return ItemInstance;
|
||||
}
|
||||
|
||||
bool UGIS_InventoryFactory::SerializeItem_Implementation(UGIS_ItemInstance* Item, FGIS_ItemRecord& Record)
|
||||
{
|
||||
if (!IsValid(Item))
|
||||
{
|
||||
GIS_LOG(Error, "Missing item.");
|
||||
return false;
|
||||
}
|
||||
|
||||
Record.ItemId = Item->GetItemId();
|
||||
const FSoftObjectPath AssetPath = FSoftObjectPath(Item->GetDefinition());
|
||||
Record.DefinitionAssetPath = AssetPath.ToString();
|
||||
Record.FragmentStateRecords = Item->GetFragmentStates().GetSerializableMixinRecords();
|
||||
|
||||
|
||||
FMemoryWriter Writer(Record.ByteData);
|
||||
FObjectAndNameAsStringProxyArchive Ar(Writer, true);
|
||||
Ar.ArIsSaveGame = true;
|
||||
Item->Serialize(Ar);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UGIS_InventoryFactory::SerializeCollection_Implementation(UGIS_ItemCollection* Collection, FGIS_CollectionRecord& Record)
|
||||
{
|
||||
if (!IsValid(Collection))
|
||||
{
|
||||
GIS_LOG(Error, "Missing collection.");
|
||||
return false;
|
||||
}
|
||||
|
||||
Record.Tag = Collection->GetCollectionTag();
|
||||
Record.Id = Collection->GetCollectionId();
|
||||
const FSoftObjectPath AssetPath = FSoftObjectPath(Collection->GetDefinition());
|
||||
Record.DefinitionAssetPath = AssetPath.ToString();
|
||||
const TArray<FGIS_ItemStack>& ValidStacks = Collection->GetAllItemStacks().FilterByPredicate([](const FGIS_ItemStack& ItemStack)
|
||||
{
|
||||
return ItemStack.IsValidStack();
|
||||
});
|
||||
|
||||
for (const FGIS_ItemStack& Stack : ValidStacks)
|
||||
{
|
||||
FGIS_StackRecord StackRecord;
|
||||
StackRecord.Id = Stack.Id;
|
||||
StackRecord.CollectionId = Stack.Collection->GetCollectionId();
|
||||
StackRecord.ItemId = Stack.Item->GetItemId();
|
||||
StackRecord.Amount = Stack.Amount;
|
||||
Record.StackRecords.Add(StackRecord);
|
||||
}
|
||||
|
||||
return Record.IsValid();
|
||||
}
|
||||
|
||||
void UGIS_InventoryFactory::DeserializeCollection_Implementation(UGIS_InventorySystemComponent* InventorySystem, const FGIS_CollectionRecord& Record, TMap<FGuid, UGIS_ItemInstance*>& ItemsMap)
|
||||
{
|
||||
if (!IsValid(InventorySystem))
|
||||
{
|
||||
GIS_LOG(Error, "Missing inventory system.");
|
||||
return;
|
||||
}
|
||||
const FSoftObjectPath DefinitionAssetPath = FSoftObjectPath(Record.DefinitionAssetPath);
|
||||
const TSoftObjectPtr<UGIS_ItemCollectionDefinition> DefinitionReference = TSoftObjectPtr<UGIS_ItemCollectionDefinition>(DefinitionAssetPath);
|
||||
UGIS_ItemCollectionDefinition* Definition = DefinitionReference.LoadSynchronous();
|
||||
|
||||
if (Definition == nullptr)
|
||||
{
|
||||
GIS_LOG(Error, "failed to load definition from path:%s", *DefinitionAssetPath.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
UGIS_ItemCollection* NewCollection = CreateCollection(InventorySystem->GetOwner(), Definition);
|
||||
if (NewCollection == nullptr)
|
||||
{
|
||||
GIS_LOG(Error, "failed to create collection from definition:%s", *GetNameSafe(Definition));
|
||||
return;
|
||||
}
|
||||
|
||||
FGIS_CollectionEntry NewEntry;
|
||||
NewEntry.Id = Record.Id;
|
||||
NewEntry.Instance = NewCollection;
|
||||
NewEntry.Definition = Definition;
|
||||
InventorySystem->AddCollectionEntry(NewEntry);
|
||||
|
||||
for (const FGIS_StackRecord& StackRecord : Record.StackRecords)
|
||||
{
|
||||
FGIS_ItemInfo Info;
|
||||
Info.Item = ItemsMap[StackRecord.ItemId];
|
||||
Info.Amount = StackRecord.Amount;
|
||||
Info.ItemCollection = NewCollection;
|
||||
InventorySystem->AddItem(Info);
|
||||
}
|
||||
}
|
||||
|
||||
bool UGIS_InventoryFactory::SerializeInventory_Implementation(UGIS_InventorySystemComponent* InventorySystem, FGIS_InventoryRecord& Record)
|
||||
{
|
||||
if (!IsValid(InventorySystem))
|
||||
{
|
||||
GIS_LOG(Error, "Missing inventory system.");
|
||||
return false;
|
||||
}
|
||||
|
||||
TArray<FGIS_CollectionRecord> CollectionRecords;
|
||||
TArray<FGIS_ItemRecord> ItemRecords;
|
||||
|
||||
TArray<UGIS_ItemCollection*> Collections = InventorySystem->GetItemCollections();
|
||||
TArray<UGIS_ItemInstance*> Items;
|
||||
|
||||
// build collection records.
|
||||
for (UGIS_ItemCollection* Collection : Collections)
|
||||
{
|
||||
if (Collection->IsInitialized())
|
||||
{
|
||||
FGIS_CollectionRecord CollectionRecord;
|
||||
if (SerializeCollection(Collection, CollectionRecord))
|
||||
{
|
||||
CollectionRecords.Add(CollectionRecord);
|
||||
}
|
||||
Items.Append(Collection->GetAllItems());
|
||||
}
|
||||
}
|
||||
|
||||
ItemRecords.Reserve(Items.Num());
|
||||
for (UGIS_ItemInstance* Item : Items)
|
||||
{
|
||||
FGIS_ItemRecord ItemRecord;
|
||||
if (SerializeItem(Item, ItemRecord))
|
||||
{
|
||||
ItemRecords.Add(ItemRecord);
|
||||
}
|
||||
}
|
||||
|
||||
Record.ItemRecords = ItemRecords;
|
||||
Record.CollectionRecords = CollectionRecords;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UGIS_InventoryFactory::DeserializeInventory_Implementation(UGIS_InventorySystemComponent* InventorySystem, const FGIS_InventoryRecord& InRecord)
|
||||
{
|
||||
if (!IsValid(InventorySystem))
|
||||
{
|
||||
GIS_LOG(Error, "Missing inventory system.");
|
||||
return;
|
||||
}
|
||||
|
||||
TMap<FGuid, UGIS_ItemInstance*> ItemsMap;
|
||||
for (const FGIS_ItemRecord& ItemRecord : InRecord.ItemRecords)
|
||||
{
|
||||
if (UGIS_ItemInstance* Instance = DeserializeItem(InventorySystem->GetOwner(), ItemRecord))
|
||||
{
|
||||
ItemsMap.Emplace(Instance->GetItemId(), Instance);
|
||||
}
|
||||
}
|
||||
|
||||
for (const FGIS_CollectionRecord& CollectionRecord : InRecord.CollectionRecords)
|
||||
{
|
||||
DeserializeCollection(InventorySystem, CollectionRecord, ItemsMap);
|
||||
}
|
||||
|
||||
if (!InventorySystem->IsDefaultCollectionCreated())
|
||||
{
|
||||
GIS_OWNED_CLOG(InventorySystem, Warning,
|
||||
"The default collection definitions is not match with collections restored from inventory record. That may be a problem as you changed the layout of inventory.")
|
||||
}
|
||||
}
|
||||
|
||||
// TArray<FGIS_ItemFragmentStateRecord> UGIS_InventoryFactory::FilterSerializableFragmentStates(const UGIS_ItemInstance* ItemInstance)
|
||||
// {
|
||||
// TArray<FGIS_Mixin> Mixins = ItemInstance->GetFragmentStates().GetSerializableMixins();
|
||||
// TArray<FGIS_ItemFragmentStateRecord> Records;
|
||||
// for (const FGIS_Mixin& Mixin : Mixins)
|
||||
// {
|
||||
// if (Mixin.Target->IsA<UGIS_ItemFragment>())
|
||||
// {
|
||||
// FGIS_ItemFragmentStateRecord Record;
|
||||
// Record.FragmentClass = Mixin.Target->GetClass();
|
||||
// Record.FragmentState = Mixin.Data;
|
||||
// Records.Add(Record);
|
||||
// }
|
||||
// }
|
||||
// return Records;
|
||||
// }
|
||||
|
||||
// TArray<FGIS_ItemFragmentStateRecord> UGIS_InventoryFactory::FilterCompatibleFragmentStateRecords(const UGIS_ItemDefinition* ItemDefinition, const FGIS_ItemRecord& Record)
|
||||
// {
|
||||
// TArray<FGIS_Mixin> ConvertedMixins = FGIS_MixinContainer::ConvertRecordsToMixins(Record.FragmentStateRecords);
|
||||
//
|
||||
// TArray<FGIS_ItemFragmentStateRecord> CompatibleRecords;
|
||||
// for (const FGIS_ItemFragmentStateRecord& StateRecord : Record.FragmentStateRecords)
|
||||
// {
|
||||
// if (StateRecord.FragmentClass == nullptr || !StateRecord.FragmentState.IsValid())
|
||||
// {
|
||||
// GIS_LOG(Warning, "Skip restoring invalid fragment state for item:%s", *ItemDefinition->GetName());
|
||||
// continue;
|
||||
// }
|
||||
// const UGIS_ItemFragment* Fragment = ItemDefinition->GetFragment(StateRecord.FragmentClass);
|
||||
//
|
||||
// if (Fragment == nullptr)
|
||||
// {
|
||||
// GIS_LOG(Warning, "Skip restoring fragment's state, as fragment(%s) existed in record no longer exists on item(%s).",
|
||||
// *GetNameSafe(StateRecord.FragmentClass), *ItemDefinition->GetName());
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// if (!Fragment->IsMixinDataSerializable())
|
||||
// {
|
||||
// GIS_LOG(Warning, "Skip restoring fragment's state, as fragment(%s) existed in record no longer considered serializable on item(%s).",
|
||||
// *GetNameSafe(StateRecord.FragmentClass), *ItemDefinition->GetName());
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// if (Fragment->GetCompatibleMixinDataType() != StateRecord.FragmentState.GetScriptStruct())
|
||||
// {
|
||||
// GIS_LOG(Warning,
|
||||
// "Skip restoring fragment's state, as fragment(%s)'s state type(%s) in record no longer compatible with the new type(%s) on item(%s).",
|
||||
// *GetNameSafe(StateRecord.FragmentClass), *GetNameSafe(StateRecord.FragmentState.GetScriptStruct()), *GetNameSafe(Fragment->GetCompatibleMixinDataType()),
|
||||
// *ItemDefinition->GetName());
|
||||
// }
|
||||
// CompatibleRecords.Add(StateRecord);
|
||||
// }
|
||||
// return CompatibleRecords;
|
||||
// }
|
||||
|
||||
#if WITH_EDITOR
|
||||
EDataValidationResult UGIS_InventoryFactory::IsDataValid(class FDataValidationContext& Context) const
|
||||
{
|
||||
if (DefaultItemInstanceClass.IsNull())
|
||||
{
|
||||
Context.AddError(FText::FromString(TEXT("Missing Default Item Instance Class")));
|
||||
return EDataValidationResult::Invalid;
|
||||
}
|
||||
return Super::IsDataValid(Context);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GIS_InventoryFunctionLibrary.h"
|
||||
|
||||
#include "GIS_ItemCollection.h"
|
||||
#include "Items/GIS_ItemInstance.h"
|
||||
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_InventoryFunctionLibrary)
|
||||
|
||||
TArray<FGIS_ItemDefinitionAmount> UGIS_InventoryFunctionLibrary::MultiplyItemAmounts(const TArray<FGIS_ItemDefinitionAmount>& ItemAmounts, int32 Multiplier)
|
||||
{
|
||||
TArray<FGIS_ItemDefinitionAmount> Results;
|
||||
for (int32 i = 0; i < ItemAmounts.Num(); i++)
|
||||
{
|
||||
Results.Add(FGIS_ItemDefinitionAmount(ItemAmounts[i].Definition, ItemAmounts[i].Amount * Multiplier));
|
||||
}
|
||||
return Results;
|
||||
}
|
||||
|
||||
TArray<FGIS_CurrencyEntry> UGIS_InventoryFunctionLibrary::MultiplyCurrencies(const TArray<FGIS_CurrencyEntry>& Currencies, float Multiplier)
|
||||
{
|
||||
TArray<FGIS_CurrencyEntry> Results;
|
||||
for (int32 i = 0; i < Currencies.Num(); i++)
|
||||
{
|
||||
Results.Add(FGIS_CurrencyEntry(Currencies[i].Definition, Currencies[i].Amount * Multiplier));
|
||||
}
|
||||
return Results;
|
||||
}
|
||||
|
||||
TArray<FGIS_ItemInfo> UGIS_InventoryFunctionLibrary::FilterItemInfosByTagQuery(const TArray<FGIS_ItemInfo>& ItemInfos, const FGameplayTagQuery& Query)
|
||||
{
|
||||
return ItemInfos.FilterByPredicate([&](const FGIS_ItemInfo& ItemInfo)
|
||||
{
|
||||
return ItemInfo.Item != nullptr && ItemInfo.Item->GetItemTags().MatchesQuery(Query);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
TArray<FGIS_ItemStack> UGIS_InventoryFunctionLibrary::FilterItemStacksByTagQuery(const TArray<FGIS_ItemStack>& ItemStacks, const FGameplayTagQuery& TagQuery)
|
||||
{
|
||||
return ItemStacks.FilterByPredicate([TagQuery](const FGIS_ItemStack& Stack)
|
||||
{
|
||||
return TagQuery.Matches(Stack.Item->GetItemTags());
|
||||
});
|
||||
}
|
||||
|
||||
TArray<FGIS_ItemStack> UGIS_InventoryFunctionLibrary::FilterItemStacksByDefinition(const TArray<FGIS_ItemStack>& ItemStacks, const UGIS_ItemDefinition* Definition)
|
||||
{
|
||||
return ItemStacks.FilterByPredicate([Definition](const FGIS_ItemStack& Stack)
|
||||
{
|
||||
return Stack.Item->GetDefinition() == Definition;
|
||||
});
|
||||
}
|
||||
|
||||
TArray<FGIS_ItemStack> UGIS_InventoryFunctionLibrary::FilterItemStacksByCollectionTags(const TArray<FGIS_ItemStack>& ItemStacks, const FGameplayTagContainer& CollectionTags)
|
||||
{
|
||||
return ItemStacks.FilterByPredicate([CollectionTags](const FGIS_ItemStack& Stack)
|
||||
{
|
||||
return Stack.Collection->GetCollectionTag().MatchesAnyExact(CollectionTags);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GIS_InventorySubsystem.h"
|
||||
#include "Engine/World.h"
|
||||
#include "GIS_InventorySystemSettings.h"
|
||||
#include "GIS_InventoryFactory.h"
|
||||
#include "GIS_LogChannels.h"
|
||||
#include "Items/GIS_ItemDefinition.h"
|
||||
#include "Items/GIS_ItemInstance.h"
|
||||
#include "Items/GIS_ItemInterface.h"
|
||||
#include "Kismet/KismetMathLibrary.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_InventorySubsystem)
|
||||
|
||||
UGIS_InventorySubsystem* UGIS_InventorySubsystem::Get(const UObject* WorldContextObject)
|
||||
{
|
||||
if (WorldContextObject)
|
||||
{
|
||||
return WorldContextObject->GetWorld()->GetGameInstance()->GetSubsystem<UGIS_InventorySubsystem>();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void UGIS_InventorySubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||
{
|
||||
InitializeFactory();
|
||||
}
|
||||
|
||||
void UGIS_InventorySubsystem::Deinitialize()
|
||||
{
|
||||
Super::Deinitialize();
|
||||
Factory = nullptr;
|
||||
}
|
||||
|
||||
UGIS_ItemInstance* UGIS_InventorySubsystem::CreateItem(AActor* Owner, TSoftObjectPtr<UGIS_ItemDefinition> ItemDefinition)
|
||||
{
|
||||
if (Factory && !ItemDefinition.IsNull())
|
||||
{
|
||||
UGIS_ItemDefinition* LoadedDefinition = ItemDefinition.LoadSynchronous();
|
||||
|
||||
if (LoadedDefinition == nullptr)
|
||||
{
|
||||
GIS_LOG(Error, "Cannot create Item with invalid Item Definition.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return Factory->CreateItem(Owner, LoadedDefinition);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UGIS_ItemInstance* UGIS_InventorySubsystem::CreateItem(AActor* Owner, const UGIS_ItemDefinition* ItemDefinition)
|
||||
{
|
||||
if (Factory && ItemDefinition != nullptr)
|
||||
{
|
||||
return Factory->CreateItem(Owner, ItemDefinition);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UGIS_ItemInstance* UGIS_InventorySubsystem::DuplicateItem(AActor* Owner, UGIS_ItemInstance* FromItem, bool bGenerateNewId)
|
||||
{
|
||||
if (Factory)
|
||||
{
|
||||
return Factory->DuplicateItem(Owner, FromItem, bGenerateNewId);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool UGIS_InventorySubsystem::SerializeItem(UGIS_ItemInstance* Item, FGIS_ItemRecord& Record)
|
||||
{
|
||||
if (Factory)
|
||||
{
|
||||
return Factory->SerializeItem(Item, Record);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
UGIS_ItemInstance* UGIS_InventorySubsystem::DeserializeItem(AActor* Owner, const FGIS_ItemRecord& Record)
|
||||
{
|
||||
if (Factory)
|
||||
{
|
||||
return Factory->DeserializeItem(Owner, Record);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool UGIS_InventorySubsystem::SerializeCollection(UGIS_ItemCollection* ItemCollection, FGIS_CollectionRecord& Record)
|
||||
{
|
||||
if (Factory)
|
||||
{
|
||||
return Factory->SerializeCollection(ItemCollection, Record);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void UGIS_InventorySubsystem::DeserializeCollection(UGIS_InventorySystemComponent* InventorySystem, const FGIS_CollectionRecord& Record, TMap<FGuid, UGIS_ItemInstance*>& ItemsMap)
|
||||
{
|
||||
if (Factory)
|
||||
{
|
||||
return Factory->DeserializeCollection(InventorySystem, Record, ItemsMap);
|
||||
}
|
||||
}
|
||||
|
||||
bool UGIS_InventorySubsystem::SerializeInventory(UGIS_InventorySystemComponent* InventorySystem, FGIS_InventoryRecord& Record)
|
||||
{
|
||||
if (Factory)
|
||||
{
|
||||
return Factory->SerializeInventory(InventorySystem, Record);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void UGIS_InventorySubsystem::DeserializeInventory(UGIS_InventorySystemComponent* InventorySystem, const FGIS_InventoryRecord& Record)
|
||||
{
|
||||
if (Factory)
|
||||
{
|
||||
return Factory->DeserializeInventory(InventorySystem, Record);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void UGIS_InventorySubsystem::InitializeFactory()
|
||||
{
|
||||
if (UGIS_InventorySystemSettings::Get() == nullptr || UGIS_InventorySystemSettings::Get()->InventoryFactoryClass.IsNull())
|
||||
{
|
||||
GIS_LOG(Error, "Missing ItemFactoryClass in inventory system settings.");
|
||||
return;
|
||||
}
|
||||
const UClass* FactoryClass = UGIS_InventorySystemSettings::Get()->InventoryFactoryClass.LoadSynchronous();
|
||||
|
||||
if (FactoryClass == nullptr)
|
||||
{
|
||||
GIS_LOG(Error, "invalid ItemFactoryClass found inventory system settings.");
|
||||
return;
|
||||
}
|
||||
|
||||
UGIS_InventoryFactory* TempFactory = NewObject<UGIS_InventoryFactory>(this, FactoryClass);
|
||||
if (TempFactory == nullptr)
|
||||
{
|
||||
GIS_LOG(Error, "Failed to create item factory instance.Class:%s", *FactoryClass->GetName());
|
||||
return;
|
||||
}
|
||||
Factory = TempFactory;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,55 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "GIS_InventorySystemSettings.h"
|
||||
#include "GIS_InventoryFactory.h"
|
||||
#include "Items/GIS_ItemDefinitionSchema.h"
|
||||
#include "Misc/DataValidation.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_InventorySystemSettings)
|
||||
|
||||
UGIS_InventorySystemSettings::UGIS_InventorySystemSettings()
|
||||
{
|
||||
InventoryFactoryClass = UGIS_InventoryFactory::StaticClass();
|
||||
}
|
||||
|
||||
FName UGIS_InventorySystemSettings::GetCategoryName() const
|
||||
{
|
||||
return TEXT("Game");
|
||||
}
|
||||
|
||||
const UGIS_InventorySystemSettings* UGIS_InventorySystemSettings::Get()
|
||||
{
|
||||
return GetDefault<UGIS_InventorySystemSettings>();
|
||||
}
|
||||
|
||||
const UGIS_ItemDefinitionSchema* UGIS_InventorySystemSettings::GetItemDefinitionSchemaForAsset(const FString& AssetPath) const
|
||||
{
|
||||
// Check path-specific schemas first
|
||||
for (const FGIS_ItemDefinitionSchemaEntry& Entry : ItemDefinitionSchemaMap)
|
||||
{
|
||||
if (!Entry.PathPrefix.IsEmpty() && AssetPath.StartsWith(Entry.PathPrefix))
|
||||
{
|
||||
if (Entry.Schema.IsValid())
|
||||
{
|
||||
if (UGIS_ItemDefinitionSchema* Schema = Cast<UGIS_ItemDefinitionSchema>(Entry.Schema.TryLoad()))
|
||||
{
|
||||
UE_LOG(LogTemp, Log, TEXT("Using path-specific schema %s for asset %s"), *Entry.Schema.ToString(), *AssetPath);
|
||||
return Schema;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to default schema
|
||||
if (DefaultItemDefinitionSchema.IsValid())
|
||||
{
|
||||
if (UGIS_ItemDefinitionSchema* Schema = Cast<UGIS_ItemDefinitionSchema>(DefaultItemDefinitionSchema.TryLoad()))
|
||||
{
|
||||
UE_LOG(LogTemp, Log, TEXT("Using default schema %s for asset %s"), *DefaultItemDefinitionSchema.ToString(), *AssetPath);
|
||||
return Schema;
|
||||
}
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Warning, TEXT("No valid schema found for asset %s"), *AssetPath);
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GIS_InventoryTags.h"
|
||||
|
||||
namespace GIS_CollectionTags
|
||||
{
|
||||
UE_DEFINE_GAMEPLAY_TAG(Main, "GIS.Collection.Main");
|
||||
UE_DEFINE_GAMEPLAY_TAG(Equipped, "GIS.Collection.Equipped");
|
||||
UE_DEFINE_GAMEPLAY_TAG(Hidden, "GIS.Collection.Hidden");
|
||||
UE_DEFINE_GAMEPLAY_TAG(QuickBar, "GIS.Collection.QuickBar");
|
||||
}
|
||||
|
||||
namespace GIS_InventoryInitState
|
||||
{
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Spawned, "GIS.InitState.Spawned", "1: Actor/component has initially spawned and can be extended");
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(DataAvailable, "GIS.InitState.DataAvailable", "2: All required data has been loaded/replicated and is ready for initialization");
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(DataInitialized, "GIS.InitState.DataInitialized", "3: The available data has been initialized for this actor/component, but it is not ready for full gameplay");
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(GameplayReady, "GIS.InitState.GameplayReady", "4: The actor/component is fully ready for active gameplay");
|
||||
}
|
||||
|
||||
// namespace GIS_MessageTags
|
||||
// {
|
||||
// UE_DEFINE_GAMEPLAY_TAG(ItemStackUpdate, "GIS.Message.StackUpdate")
|
||||
// UE_DEFINE_GAMEPLAY_TAG(InventoryUpdate, "GIS.Message.Inventory.Update")
|
||||
// UE_DEFINE_GAMEPLAY_TAG(CollectionUpdate, "GIS.Message.Inventory.Collection.Update")
|
||||
// UE_DEFINE_GAMEPLAY_TAG(InventoryAddItemInfo, "GIS.Message.Inventory.AddItemInfo")
|
||||
// UE_DEFINE_GAMEPLAY_TAG(InventoryAddItemInfoRejected, "GIS.Message.Inventory.AddItemInfo.Rejected")
|
||||
// UE_DEFINE_GAMEPLAY_TAG(InventoryRemoveItemInfo, "GIS.Message.Inventory.RemoveItemInfo")
|
||||
// UE_DEFINE_GAMEPLAY_TAG(QuickBarSlotsChanged, "GIS.Message.QuickBar.SlotsChanged")
|
||||
// UE_DEFINE_GAMEPLAY_TAG(QuickBarActiveIndexChanged, "GIS.Message.QuickBar.ActiveIndexChanged")
|
||||
// }
|
||||
|
||||
namespace GIS_AttributeTags
|
||||
{
|
||||
UE_DEFINE_GAMEPLAY_TAG(Dummy, "GIS.Attribute.Dummy");
|
||||
// UE_DEFINE_GAMEPLAY_TAG(EnhancedLevel, "GIS.Attribute.EnhancedLevel");
|
||||
// UE_DEFINE_GAMEPLAY_TAG(MaxEnhancedLevel, "GIS.Attribute.MaxEnhancedLevel");
|
||||
UE_DEFINE_GAMEPLAY_TAG(StackSizeLimit, "GIS.Attribute.StackSizeLimit");
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GIS_LogChannels.h"
|
||||
|
||||
#include "GIS_EquipmentInstance.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "GIS_ItemInstance.h"
|
||||
#include "GIS_ItemCollection.h"
|
||||
#include "GIS_ItemDefinition.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogGIS)
|
||||
|
||||
|
||||
FString GetGISLogContextString(const UObject* ContextObject)
|
||||
{
|
||||
ENetRole Role = ROLE_None;
|
||||
FString RoleName = TEXT("None");
|
||||
FString Name = "None";
|
||||
|
||||
if (const AActor* Actor = Cast<AActor>(ContextObject))
|
||||
{
|
||||
Role = Actor->GetLocalRole();
|
||||
Name = Actor->GetName();
|
||||
}
|
||||
else if (const UActorComponent* Component = Cast<UActorComponent>(ContextObject))
|
||||
{
|
||||
if (AActor* ActorOwner = Cast<AActor>(Component->GetOuter()))
|
||||
{
|
||||
Role = ActorOwner->GetLocalRole();
|
||||
Name = ActorOwner->GetName();
|
||||
}
|
||||
else
|
||||
{
|
||||
const AActor* Owner = Component->GetOwner();
|
||||
Role = IsValid(Owner) ? Owner->GetLocalRole() : ROLE_None;
|
||||
Name = IsValid(Owner) ? Owner->GetName() : TEXT("None");
|
||||
}
|
||||
}
|
||||
else if (const UGIS_ItemInstance* ItemInstance = Cast<UGIS_ItemInstance>(ContextObject))
|
||||
{
|
||||
if (AActor* ActorOwner = Cast<AActor>(ItemInstance->GetOuter()))
|
||||
{
|
||||
Role = ActorOwner->GetLocalRole();
|
||||
Name = ActorOwner->GetName();
|
||||
}
|
||||
else
|
||||
{
|
||||
return FString::Printf(TEXT("(%s)'s instance(%s) "), *ItemInstance->GetDefinition()->GetName(), *ItemInstance->GetName());
|
||||
}
|
||||
}
|
||||
else if (const UGIS_ItemCollection* Collection = Cast<UGIS_ItemCollection>(ContextObject))
|
||||
{
|
||||
if (AActor* ActorOwner = Cast<AActor>(Collection->GetOuter()))
|
||||
{
|
||||
Role = ActorOwner->GetLocalRole();
|
||||
Name = ActorOwner->GetName();
|
||||
}
|
||||
if (Role != ROLE_None)
|
||||
{
|
||||
RoleName = (Role == ROLE_Authority) ? TEXT("Server") : TEXT("Client");
|
||||
}
|
||||
return FString::Printf(TEXT("[%s] (%s)'s %s"), *RoleName, *Name, *Collection->GetCollectionName());
|
||||
}
|
||||
else if (IsValid(ContextObject))
|
||||
{
|
||||
Name = ContextObject->GetName();
|
||||
}
|
||||
|
||||
if (Role != ROLE_None)
|
||||
{
|
||||
RoleName = (Role == ROLE_Authority) ? TEXT("Server") : TEXT("Client");
|
||||
}
|
||||
return FString::Printf(TEXT("[%s] (%s)"), *RoleName, *Name);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "GenericInventorySystem.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FGenericInventorySystemModule"
|
||||
|
||||
void FGenericInventorySystemModule::StartupModule()
|
||||
{
|
||||
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
|
||||
|
||||
}
|
||||
|
||||
void FGenericInventorySystemModule::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(FGenericInventorySystemModule, GenericInventorySystem)
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Pickups/GIS_CurrencyPickupComponent.h"
|
||||
#include "GIS_CurrencySystemComponent.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "GIS_InventorySystemComponent.h"
|
||||
#include "GIS_LogChannels.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_CurrencyPickupComponent)
|
||||
|
||||
void UGIS_CurrencyPickupComponent::BeginPlay()
|
||||
{
|
||||
OwningCurrencySystem = UGIS_CurrencySystemComponent::GetCurrencySystemComponent(GetOwner());
|
||||
if (OwningCurrencySystem == nullptr)
|
||||
{
|
||||
GIS_CLOG(Warning, "Mising CurrencySystemComponent!");
|
||||
}
|
||||
Super::BeginPlay();
|
||||
}
|
||||
|
||||
bool UGIS_CurrencyPickupComponent::Pickup(UGIS_InventorySystemComponent* Picker)
|
||||
{
|
||||
if (!GetOwner()->HasAuthority())
|
||||
{
|
||||
GIS_CLOG(Warning, "has no authority!");
|
||||
return false;
|
||||
}
|
||||
if (OwningCurrencySystem == nullptr || !IsValid(OwningCurrencySystem))
|
||||
{
|
||||
GIS_CLOG(Warning, "mising CurrencySystemComponent!");
|
||||
return false;
|
||||
}
|
||||
if (Picker == nullptr || !IsValid(Picker))
|
||||
{
|
||||
GIS_CLOG(Warning, "passed-in invalid picker.");
|
||||
return false;
|
||||
}
|
||||
|
||||
UGIS_CurrencySystemComponent* PickerCurrencySystem = Picker->GetCurrencySystem();
|
||||
if (PickerCurrencySystem == nullptr)
|
||||
{
|
||||
GIS_CLOG(Warning, "Picker:%s has no CurrencySystem!", *Picker->GetOwner()->GetName());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (PickerCurrencySystem->AddCurrencies(OwningCurrencySystem->GetAllCurrencies()))
|
||||
{
|
||||
OwningCurrencySystem->EmptyCurrencies();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Pickups/GIS_InventoryPickupComponent.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "GIS_InventorySystemComponent.h"
|
||||
#include "GIS_InventoryTags.h"
|
||||
#include "GIS_ItemCollection.h"
|
||||
#include "GIS_LogChannels.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_InventoryPickupComponent)
|
||||
|
||||
void UGIS_InventoryPickupComponent::BeginPlay()
|
||||
{
|
||||
if (!CollectionTag.IsValid())
|
||||
{
|
||||
CollectionTag = GIS_CollectionTags::Main;
|
||||
}
|
||||
|
||||
Inventory = UGIS_InventorySystemComponent::FindInventorySystemComponent(GetOwner());
|
||||
if (!Inventory)
|
||||
{
|
||||
GIS_CLOG(Warning, "InventoryPickup requries an inventory system component on the same actor!")
|
||||
}
|
||||
|
||||
Super::BeginPlay();
|
||||
}
|
||||
|
||||
bool UGIS_InventoryPickupComponent::Pickup(UGIS_InventorySystemComponent* Picker)
|
||||
{
|
||||
if (!GetOwner()->HasAuthority())
|
||||
{
|
||||
GIS_CLOG(Warning, "has no authority!");
|
||||
return false;
|
||||
}
|
||||
if (Inventory == nullptr || !IsValid(Inventory))
|
||||
{
|
||||
GIS_CLOG(Warning, "doesn't have an inventory system component to function.")
|
||||
return false;
|
||||
}
|
||||
if (!CollectionTag.IsValid() || !IsValid(Picker))
|
||||
{
|
||||
GIS_CLOG(Warning, "doesn't have valid picker to function.")
|
||||
return false;
|
||||
}
|
||||
|
||||
UGIS_ItemCollection* DestCollection = Picker->GetCollectionByTag(CollectionTag);
|
||||
if (DestCollection == nullptr)
|
||||
{
|
||||
GIS_CLOG(Warning, "picker(%s) doesn't have valid collection named:%s", *Picker->GetOwner()->GetName(), *CollectionTag.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
return AddPickupToCollection(DestCollection);
|
||||
}
|
||||
|
||||
UGIS_InventorySystemComponent* UGIS_InventoryPickupComponent::GetOwningInventory() const
|
||||
{
|
||||
return Inventory;
|
||||
}
|
||||
|
||||
bool UGIS_InventoryPickupComponent::AddPickupToCollection(UGIS_ItemCollection* DestCollection)
|
||||
{
|
||||
TArray<FGIS_ItemInfo> PickupItems = Inventory->GetDefaultCollection()->GetAllItemInfos();
|
||||
bool bAtLeastOneCanBeAdded = false;
|
||||
for (int32 i = 0; i < PickupItems.Num(); i++)
|
||||
{
|
||||
FGIS_ItemInfo ItemInfo = PickupItems[i];
|
||||
FGIS_ItemInfo CanAddedItemInfo;
|
||||
if (DestCollection->CanAddItem(ItemInfo, CanAddedItemInfo))
|
||||
{
|
||||
if (CanAddedItemInfo.Amount != 0)
|
||||
{
|
||||
bAtLeastOneCanBeAdded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bAtLeastOneCanBeAdded == false)
|
||||
{
|
||||
NotifyPickupFailed();
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int32 i = 0; i < PickupItems.Num(); i++)
|
||||
{
|
||||
DestCollection->AddItem(PickupItems[i]);
|
||||
}
|
||||
Inventory->GetDefaultCollection()->RemoveAll();
|
||||
NotifyPickupSuccess();
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Pickups/GIS_ItemPickupComponent.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "GIS_InventoryTags.h"
|
||||
#include "GIS_InventorySystemComponent.h"
|
||||
#include "GIS_ItemCollection.h"
|
||||
#include "Items/GIS_ItemInstance.h"
|
||||
#include "GIS_LogChannels.h"
|
||||
#include "Pickups/GIS_WorldItemComponent.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_ItemPickupComponent)
|
||||
|
||||
bool UGIS_ItemPickupComponent::Pickup(UGIS_InventorySystemComponent* Picker)
|
||||
{
|
||||
if (!GetOwner()->HasAuthority())
|
||||
{
|
||||
GIS_CLOG(Warning, "has no authority!");
|
||||
return false;
|
||||
}
|
||||
if (!CollectionTag.IsValid() || !IsValid(Picker))
|
||||
{
|
||||
GIS_CLOG(Warning, "passed-in invalid picker.");
|
||||
return false;
|
||||
}
|
||||
if (WorldItemComponent == nullptr && WorldItemComponent->GetItemInstance()->IsItemValid())
|
||||
{
|
||||
GIS_CLOG(Warning, "doesn't have valid WordItem component attached or it has invalid item instance reference.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryAddToCollection(Picker);
|
||||
}
|
||||
|
||||
UGIS_WorldItemComponent* UGIS_ItemPickupComponent::GetWorldItem() const
|
||||
{
|
||||
return WorldItemComponent;
|
||||
}
|
||||
|
||||
// Called when the game starts
|
||||
void UGIS_ItemPickupComponent::BeginPlay()
|
||||
{
|
||||
if (!CollectionTag.IsValid())
|
||||
{
|
||||
CollectionTag = GIS_CollectionTags::Main;
|
||||
}
|
||||
|
||||
Super::BeginPlay();
|
||||
WorldItemComponent = GetOwner()->FindComponentByClass<UGIS_WorldItemComponent>();
|
||||
|
||||
if (WorldItemComponent == nullptr)
|
||||
{
|
||||
GIS_CLOG(Error, "requires GIS_WorldItemComponent to function!")
|
||||
}
|
||||
}
|
||||
|
||||
bool UGIS_ItemPickupComponent::TryAddToCollection(UGIS_InventorySystemComponent* Picker)
|
||||
{
|
||||
UGIS_ItemInstance* NewItemInstance = WorldItemComponent->GetDuplicatedItemInstance(Picker->GetOwner());
|
||||
|
||||
if (NewItemInstance == nullptr)
|
||||
{
|
||||
GIS_CLOG(Error, "referenced invalid item! Pickup failed!");
|
||||
NotifyPickupFailed();
|
||||
return false;
|
||||
}
|
||||
|
||||
FGIS_ItemInfo NewItemInfo;
|
||||
NewItemInfo.Item = NewItemInstance;
|
||||
NewItemInfo.Amount = WorldItemComponent->GetItemAmount();
|
||||
const FGameplayTag TargetCollection = CollectionTag.IsValid() ? CollectionTag : GIS_CollectionTags::Main;
|
||||
NewItemInfo.CollectionTag = TargetCollection;
|
||||
|
||||
FGIS_ItemInfo CanAddedItemInfo;
|
||||
const bool bResult = Picker->CanAddItem(NewItemInfo, CanAddedItemInfo);
|
||||
|
||||
if (!bResult || CanAddedItemInfo.Amount == 0 || (bFailIfFullAmountNotFit && CanAddedItemInfo.Amount != NewItemInfo.Amount))
|
||||
{
|
||||
NotifyPickupFailed();
|
||||
return false;
|
||||
}
|
||||
|
||||
Picker->AddItem(NewItemInfo);
|
||||
NotifyPickupSuccess();
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Pickups/GIS_PickupActorInterface.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_PickupActorInterface)
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Pickups/GIS_PickupComponent.h"
|
||||
#include "Sound/SoundBase.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_PickupComponent)
|
||||
|
||||
// Sets default values for this component's properties
|
||||
UGIS_PickupComponent::UGIS_PickupComponent()
|
||||
{
|
||||
PrimaryComponentTick.bStartWithTickEnabled = false;
|
||||
PrimaryComponentTick.bCanEverTick = false;
|
||||
SetIsReplicatedByDefault(true);
|
||||
}
|
||||
|
||||
bool UGIS_PickupComponent::Pickup(UGIS_InventorySystemComponent* Picker)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void UGIS_PickupComponent::NotifyPickupSuccess()
|
||||
{
|
||||
OnPickupSuccess.Broadcast();
|
||||
}
|
||||
|
||||
void UGIS_PickupComponent::NotifyPickupFailed()
|
||||
{
|
||||
OnPickupFail.Broadcast();
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "Pickups/GIS_WorldItemComponent.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "Engine/World.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "GIS_InventorySubsystem.h"
|
||||
#include "GIS_InventorySystemComponent.h"
|
||||
#include "Items/GIS_ItemDefinition.h"
|
||||
#include "Items/GIS_ItemInstance.h"
|
||||
#include "GIS_LogChannels.h"
|
||||
#include "Net/UnrealNetwork.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_WorldItemComponent)
|
||||
|
||||
UGIS_WorldItemComponent::UGIS_WorldItemComponent(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
PrimaryComponentTick.bStartWithTickEnabled = false;
|
||||
PrimaryComponentTick.bCanEverTick = false;
|
||||
|
||||
SetIsReplicatedByDefault(true);
|
||||
|
||||
bReplicateUsingRegisteredSubObjectList = true;
|
||||
}
|
||||
|
||||
void UGIS_WorldItemComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||
{
|
||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||
|
||||
FDoRepLifetimeParams Parameters;
|
||||
Parameters.bIsPushBased = true;
|
||||
|
||||
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, ItemInfo, Parameters)
|
||||
}
|
||||
|
||||
UGIS_WorldItemComponent* UGIS_WorldItemComponent::GetWorldItemComponent(const AActor* Actor)
|
||||
{
|
||||
if (IsValid(Actor))
|
||||
{
|
||||
return Actor->FindComponentByClass<UGIS_WorldItemComponent>();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void UGIS_WorldItemComponent::CreateItemFromDefinition(FGIS_ItemDefinitionAmount ItemDefinition)
|
||||
{
|
||||
if (!ItemDefinition.Definition.IsNull() && ItemDefinition.Amount >= 1)
|
||||
{
|
||||
if (!ItemInfo.IsValid())
|
||||
{
|
||||
UGIS_ItemInstance* NewItemInstance = UGIS_InventorySubsystem::Get(GetWorld())->CreateItem(GetOwner(), ItemDefinition.Definition.LoadSynchronous());
|
||||
if (NewItemInstance == nullptr)
|
||||
{
|
||||
GIS_CLOG(Error, "failed to create item instance from definition!");
|
||||
}
|
||||
else
|
||||
{
|
||||
SetItemInfo(NewItemInstance, ItemDefinition.Amount);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GIS_CLOG(Warning, "Already have valid item info, skip creation.")
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GIS_CLOG(Error, "passed invalid definition setup,skip item instance creating!");
|
||||
}
|
||||
}
|
||||
|
||||
bool UGIS_WorldItemComponent::HasValidDefinition() const
|
||||
{
|
||||
return !Definition.Definition.IsNull() && Definition.Amount >= 1;
|
||||
}
|
||||
|
||||
|
||||
void UGIS_WorldItemComponent::SetItemInfo(UGIS_ItemInstance* InItem, int32 InAmount)
|
||||
{
|
||||
if (InItem == nullptr || InAmount <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ItemInfo.IsValid())
|
||||
{
|
||||
GIS_CLOG(Warning, "Already have valid item info.")
|
||||
return;
|
||||
}
|
||||
|
||||
ItemInfo = FGIS_ItemInfo(InItem, InAmount);
|
||||
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, ItemInfo, this)
|
||||
|
||||
// add to ReplicatedSubObject list only if it was created by this component.
|
||||
if (bReplicateUsingRegisteredSubObjectList && InItem->GetOuter() == GetOwner())
|
||||
{
|
||||
AddReplicatedSubObject(ItemInfo.Item);
|
||||
}
|
||||
}
|
||||
|
||||
void UGIS_WorldItemComponent::ResetItemInfo()
|
||||
{
|
||||
if (ItemInfo.IsValid())
|
||||
{
|
||||
// remove from replicated sub object list only if it was created by this component.
|
||||
if (bReplicateUsingRegisteredSubObjectList && ItemInfo.Item->GetOuter() == GetOwner())
|
||||
{
|
||||
RemoveReplicatedSubObject(ItemInfo.Item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UGIS_ItemInstance* UGIS_WorldItemComponent::GetItemInstance()
|
||||
{
|
||||
return ItemInfo.Item;
|
||||
}
|
||||
|
||||
UGIS_ItemInstance* UGIS_WorldItemComponent::GetDuplicatedItemInstance(AActor* NewOwner)
|
||||
{
|
||||
if (ItemInfo.IsValid())
|
||||
{
|
||||
return UGIS_InventorySubsystem::Get(GetWorld())->DuplicateItem(NewOwner, ItemInfo.Item);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FGIS_ItemInfo UGIS_WorldItemComponent::GetItemInfo() const
|
||||
{
|
||||
return ItemInfo;
|
||||
}
|
||||
|
||||
int32 UGIS_WorldItemComponent::GetItemAmount() const
|
||||
{
|
||||
return ItemInfo.Amount;
|
||||
}
|
||||
|
||||
void UGIS_WorldItemComponent::BeginPlay()
|
||||
{
|
||||
if (HasValidDefinition() && GetOwner()->HasAuthority())
|
||||
{
|
||||
CreateItemFromDefinition(Definition);
|
||||
}
|
||||
Super::BeginPlay();
|
||||
}
|
||||
|
||||
void UGIS_WorldItemComponent::OnRep_ItemInfo()
|
||||
{
|
||||
if (ItemInfo.IsValid())
|
||||
{
|
||||
GIS_CLOG(Verbose, "item:%s replicated!", *ItemInfo.Item->GetDefinition()->GetName());
|
||||
ItemInfoSetEvent.Broadcast(ItemInfo);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GIS_SerializationStructLibrary.h"
|
||||
#include "GIS_ItemFragment.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_SerializationStructLibrary)
|
||||
|
||||
bool FGIS_ItemRecord::operator==(const FGIS_ItemRecord& Other) const
|
||||
{
|
||||
return ItemId == Other.ItemId && DefinitionAssetPath == Other.DefinitionAssetPath;
|
||||
}
|
||||
|
||||
bool FGIS_ItemRecord::IsValid() const
|
||||
{
|
||||
return ItemId.IsValid() && !DefinitionAssetPath.IsEmpty();
|
||||
}
|
||||
|
||||
// bool FGIS_ItemFragmentStateRecord::operator==(const FGIS_ItemFragmentStateRecord& Other) const
|
||||
// {
|
||||
// return FragmentClass == Other.FragmentClass;
|
||||
// }
|
||||
//
|
||||
// bool FGIS_ItemFragmentStateRecord::IsValid() const
|
||||
// {
|
||||
// return FragmentClass != nullptr && FragmentState.IsValid();
|
||||
// }
|
||||
|
||||
bool FGIS_StackRecord::IsValid() const
|
||||
{
|
||||
return ItemId.IsValid() && Id.IsValid() && CollectionId.IsValid();
|
||||
}
|
||||
|
||||
bool FGIS_CollectionRecord::IsValid() const
|
||||
{
|
||||
return Id.IsValid() && !DefinitionAssetPath.IsEmpty();
|
||||
}
|
||||
|
||||
FGIS_CurrencyRecord::FGIS_CurrencyRecord()
|
||||
{
|
||||
Key = NAME_None;
|
||||
}
|
||||
Reference in New Issue
Block a user