第一次提交
This commit is contained in:
8
Plugins/GIS/Config/BaseGenericInventorySystem.ini
Normal file
8
Plugins/GIS/Config/BaseGenericInventorySystem.ini
Normal file
@@ -0,0 +1,8 @@
|
||||
[CoreRedirects]
|
||||
;GIS 1.1 migration.
|
||||
+FunctionRedirects = (OldName="/Script/GenericInventorySystem.GIS_EquipmentSystemComponent.ServerCycleGroupActiveIndex",NewName="/Script/GenericInventorySystem.GIS_EquipmentSystemComponent.ServerCycleGroupActiveSlot")
|
||||
+FunctionRedirects = (OldName="/Script/GenericInventorySystem.GIS_EquipmentSystemComponent.CycleGroupActiveIndex",NewName="/Script/GenericInventorySystem.GIS_EquipmentSystemComponent.CycleGroupActiveSlot")
|
||||
+FunctionRedirects = (OldName="/Script/GenericInventorySystem.GIS_EquipmentSystemComponent.ServerSetGroupActiveIndex",NewName="/Script/GenericInventorySystem.GIS_EquipmentSystemComponent.ServerSetGroupActiveSlot")
|
||||
+FunctionRedirects = (OldName="/Script/GenericInventorySystem.GIS_EquipmentSystemComponent.SetGroupActiveIndex",NewName="/Script/GenericInventorySystem.GIS_EquipmentSystemComponent.SetGroupActiveSlot")
|
||||
|
||||
|
||||
9
Plugins/GIS/Config/FilterPlugin.ini
Normal file
9
Plugins/GIS/Config/FilterPlugin.ini
Normal file
@@ -0,0 +1,9 @@
|
||||
[FilterPlugin]
|
||||
; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and
|
||||
; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively.
|
||||
;
|
||||
; Examples:
|
||||
; /README.txt
|
||||
; /Extras/...
|
||||
; /Binaries/ThirdParty/*.dll
|
||||
/Config/*
|
||||
42
Plugins/GIS/GenericInventorySystem.uplugin
Normal file
42
Plugins/GIS/GenericInventorySystem.uplugin
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"FileVersion": 3,
|
||||
"Version": 3,
|
||||
"VersionName": "1.1.1",
|
||||
"FriendlyName": "GenericInventorySystem",
|
||||
"Description": "Advanced and Flexible Inventory framework.",
|
||||
"Category": "Gameplay",
|
||||
"CreatedBy": "YuewuDev",
|
||||
"CreatedByURL": "https://yuewu.dev/en",
|
||||
"DocsURL": "https://www.yuewu.dev/en/wiki",
|
||||
"MarketplaceURL": "com.epicgames.launcher://ue/Fab/product/9a9b6f10-4d4c-4897-90ec-809854653402",
|
||||
"SupportURL": "https://discord.com/invite/xMRXAB2",
|
||||
"EngineVersion": "5.7.0",
|
||||
"CanContainContent": false,
|
||||
"Installed": true,
|
||||
"Modules": [
|
||||
{
|
||||
"Name": "GenericInventorySystem",
|
||||
"Type": "Runtime",
|
||||
"LoadingPhase": "Default",
|
||||
"PlatformAllowList": [
|
||||
"Win64",
|
||||
"Android",
|
||||
"Linux"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "GenericInventoryEditor",
|
||||
"Type": "UncookedOnly",
|
||||
"LoadingPhase": "PreDefault",
|
||||
"PlatformAllowList": [
|
||||
"Win64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Plugins": [
|
||||
{
|
||||
"Name": "ModularGameplay",
|
||||
"Enabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
Plugins/GIS/Resources/Icon128.png
Normal file
BIN
Plugins/GIS/Resources/Icon128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,27 @@
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class GenericInventoryEditor : ModuleRules
|
||||
{
|
||||
public GenericInventoryEditor(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
"GenericInventorySystem",
|
||||
}
|
||||
);
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"UnrealEd",
|
||||
"AssetTools",
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Factories/GIS_AssetTypeActions.h"
|
||||
#include "GenericInventoryEditor.h"
|
||||
#include "GIS_CurrencyDefinition.h"
|
||||
#include "GIS_ItemCollection.h"
|
||||
#include "GIS_ItemDefinition.h"
|
||||
#include "GIS_ItemDefinitionSchema.h"
|
||||
#include "GIS_ItemMultiStackCollection.h"
|
||||
#include "GIS_ItemSlotCollection.h"
|
||||
|
||||
|
||||
uint32 FGIS_AssetTypeAction::GetCategories()
|
||||
{
|
||||
return FGenericInventoryEditorModule::GetAssetsCategory();
|
||||
}
|
||||
|
||||
FColor FGIS_AssetTypeAction::GetTypeColor() const
|
||||
{
|
||||
return FColor(114, 40, 199);
|
||||
}
|
||||
|
||||
#define IMPLEMENT_GIS_ASSET_ACTION(ActionName, NameText, DescText) \
|
||||
FText FGIS_AssetTypeAction_##ActionName::GetName() const \
|
||||
{ \
|
||||
return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_" #ActionName "_Name", NameText); \
|
||||
} \
|
||||
FText FGIS_AssetTypeAction_##ActionName::GetAssetDescription(const FAssetData& AssetData) const \
|
||||
{ \
|
||||
return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_" #ActionName "_Description", DescText); \
|
||||
} \
|
||||
UClass* FGIS_AssetTypeAction_##ActionName::GetSupportedClass() const \
|
||||
{ \
|
||||
return UGIS_##ActionName::StaticClass(); \
|
||||
}
|
||||
|
||||
IMPLEMENT_GIS_ASSET_ACTION(ItemDefinition, "Item Definition",
|
||||
"Data Asset that defines your item design.")
|
||||
IMPLEMENT_GIS_ASSET_ACTION(ItemDefinitionSchema, "Item Definition Schema",
|
||||
"Data Asset that defines the validation rules for item definition.")
|
||||
IMPLEMENT_GIS_ASSET_ACTION(ItemCollectionDefinition, "Item Collection Definition(Normal)",
|
||||
"Data Asset that defines the design for normal item collection.")
|
||||
IMPLEMENT_GIS_ASSET_ACTION(ItemSlotCollectionDefinition, "Item Collection Definition(Slot)",
|
||||
"Data Asset that defines the design for slot based item collection.")
|
||||
IMPLEMENT_GIS_ASSET_ACTION(ItemMultiStackCollectionDefinition, "Item Collection Definition(MultiStack)",
|
||||
"Data Asset that defines the design for multi stack based item collection.")
|
||||
IMPLEMENT_GIS_ASSET_ACTION(CurrencyDefinition, "CurrencyDefinition",
|
||||
"Data Asset that defines the in game currency.")
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Factories/GIS_DataAssetsFactories.h"
|
||||
|
||||
#include "GIS_ItemCollection.h"
|
||||
#include "GIS_ItemDefinition.h"
|
||||
#include "GIS_ItemDefinitionSchema.h"
|
||||
#include "GIS_ItemMultiStackCollection.h"
|
||||
#include "GIS_ItemSlotCollection.h"
|
||||
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GIS_DataAssetsFactories)
|
||||
|
||||
UGIS_Factory::UGIS_Factory(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
|
||||
{
|
||||
bEditAfterNew = true;
|
||||
bCreateNew = true;
|
||||
}
|
||||
|
||||
uint32 UGIS_Factory::GetMenuCategories() const
|
||||
{
|
||||
return Super::GetMenuCategories();
|
||||
}
|
||||
|
||||
const TArray<FText>& UGIS_Factory::GetMenuCategorySubMenus() const
|
||||
{
|
||||
return Super::GetMenuCategorySubMenus();
|
||||
}
|
||||
|
||||
#define IMPLEMENT_GIS_FACTORY(FactoryName) \
|
||||
UGIS_Factory_##FactoryName::UGIS_Factory_##FactoryName(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) \
|
||||
{ \
|
||||
SupportedClass = UGIS_##FactoryName::StaticClass(); \
|
||||
} \
|
||||
UObject* UGIS_Factory_##FactoryName::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) \
|
||||
{ \
|
||||
check(Class->IsChildOf(UGIS_##FactoryName::StaticClass())); \
|
||||
return NewObject<UGIS_##FactoryName>(InParent, Class, Name, Flags | RF_Transactional, Context); \
|
||||
}
|
||||
|
||||
IMPLEMENT_GIS_FACTORY(ItemDefinition)
|
||||
|
||||
IMPLEMENT_GIS_FACTORY(ItemDefinitionSchema)
|
||||
|
||||
IMPLEMENT_GIS_FACTORY(ItemCollectionDefinition)
|
||||
|
||||
IMPLEMENT_GIS_FACTORY(ItemSlotCollectionDefinition)
|
||||
|
||||
IMPLEMENT_GIS_FACTORY(ItemMultiStackCollectionDefinition)
|
||||
@@ -0,0 +1,42 @@
|
||||
#include "GenericInventoryEditor.h"
|
||||
|
||||
#include "Factories/GIS_AssetTypeActions.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FGenericInventoryEditorModule"
|
||||
|
||||
TArray<TSharedPtr<IAssetTypeActions>> FGenericInventoryEditorModule::AssetTypeActions = {
|
||||
MakeShared<FGIS_AssetTypeAction_ItemDefinition>(),
|
||||
MakeShared<FGIS_AssetTypeAction_ItemDefinitionSchema>(),
|
||||
MakeShared<FGIS_AssetTypeAction_ItemCollectionDefinition>(),
|
||||
MakeShared<FGIS_AssetTypeAction_ItemSlotCollectionDefinition>(),
|
||||
MakeShared<FGIS_AssetTypeAction_ItemMultiStackCollectionDefinition>()
|
||||
|
||||
};
|
||||
|
||||
EAssetTypeCategories::Type FGenericInventoryEditorModule::AssetsCategory;
|
||||
|
||||
|
||||
void FGenericInventoryEditorModule::StartupModule()
|
||||
{
|
||||
IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
|
||||
AssetsCategory = AssetTools.RegisterAdvancedAssetCategory(FName(TEXT("GenericInventorySystem")), LOCTEXT("GIS_AssetsCategory", "Generic Inventory System"));
|
||||
for (TSharedPtr<IAssetTypeActions>& Action : AssetTypeActions)
|
||||
{
|
||||
AssetTools.RegisterAssetTypeActions(Action.ToSharedRef());
|
||||
}
|
||||
}
|
||||
|
||||
void FGenericInventoryEditorModule::ShutdownModule()
|
||||
{
|
||||
if (const FAssetToolsModule* AssetTools = FModuleManager::GetModulePtr<FAssetToolsModule>("AssetTools"))
|
||||
{
|
||||
for (TSharedPtr<IAssetTypeActions>& Action : AssetTypeActions)
|
||||
{
|
||||
AssetTools->Get().UnregisterAssetTypeActions(Action.ToSharedRef());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FGenericInventoryEditorModule, GenericInventoryEditor)
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AssetTypeActions/AssetTypeActions_DataAsset.h"
|
||||
|
||||
class FGIS_AssetTypeAction : public FAssetTypeActions_DataAsset
|
||||
{
|
||||
public:
|
||||
virtual uint32 GetCategories() override;
|
||||
virtual FColor GetTypeColor() const override;
|
||||
};
|
||||
|
||||
#define DEFINE_GIS_ASSET_ACTION(ActionName) \
|
||||
class FGIS_AssetTypeAction_##ActionName final : public FGIS_AssetTypeAction \
|
||||
{ \
|
||||
public: \
|
||||
virtual FText GetName() const override; \
|
||||
virtual FText GetAssetDescription(const FAssetData& AssetData) const override; \
|
||||
virtual UClass* GetSupportedClass() const override; \
|
||||
};
|
||||
|
||||
DEFINE_GIS_ASSET_ACTION(ItemDefinition)
|
||||
|
||||
DEFINE_GIS_ASSET_ACTION(ItemDefinitionSchema)
|
||||
|
||||
DEFINE_GIS_ASSET_ACTION(ItemCollectionDefinition)
|
||||
|
||||
DEFINE_GIS_ASSET_ACTION(ItemSlotCollectionDefinition)
|
||||
|
||||
DEFINE_GIS_ASSET_ACTION(ItemMultiStackCollectionDefinition)
|
||||
|
||||
DEFINE_GIS_ASSET_ACTION(CurrencyDefinition)
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "UObject/ObjectMacros.h"
|
||||
#include "Templates/SubclassOf.h"
|
||||
#include "Factories/Factory.h"
|
||||
#include "GIS_DataAssetsFactories.generated.h"
|
||||
|
||||
UCLASS(Abstract)
|
||||
class UGIS_Factory : public UFactory
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UGIS_Factory(const FObjectInitializer& ObjectInitializer);
|
||||
virtual uint32 GetMenuCategories() const override;
|
||||
virtual const TArray<FText>& GetMenuCategorySubMenus() const override;
|
||||
};
|
||||
|
||||
UCLASS()
|
||||
class UGIS_Factory_ItemDefinition : public UGIS_Factory
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UGIS_Factory_ItemDefinition(const FObjectInitializer& ObjectInitializer);
|
||||
virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
|
||||
};
|
||||
|
||||
UCLASS()
|
||||
class UGIS_Factory_ItemDefinitionSchema : public UGIS_Factory
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UGIS_Factory_ItemDefinitionSchema(const FObjectInitializer& ObjectInitializer);
|
||||
virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
|
||||
};
|
||||
|
||||
UCLASS()
|
||||
class UGIS_Factory_ItemCollectionDefinition : public UGIS_Factory
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UGIS_Factory_ItemCollectionDefinition(const FObjectInitializer& ObjectInitializer);
|
||||
virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
|
||||
};
|
||||
|
||||
UCLASS()
|
||||
class UGIS_Factory_ItemSlotCollectionDefinition : public UGIS_Factory
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UGIS_Factory_ItemSlotCollectionDefinition(const FObjectInitializer& ObjectInitializer);
|
||||
virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
|
||||
};
|
||||
|
||||
UCLASS()
|
||||
class UGIS_Factory_ItemMultiStackCollectionDefinition : public UGIS_Factory
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UGIS_Factory_ItemMultiStackCollectionDefinition(const FObjectInitializer& ObjectInitializer);
|
||||
virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "AssetTypeCategories.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
class IAssetTypeActions;
|
||||
|
||||
class FGenericInventoryEditorModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
|
||||
static EAssetTypeCategories::Type GetAssetsCategory()
|
||||
{
|
||||
return AssetsCategory;
|
||||
}
|
||||
|
||||
private:
|
||||
static TArray<TSharedPtr<IAssetTypeActions>> AssetTypeActions;
|
||||
static EAssetTypeCategories::Type AssetsCategory;
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
using System.IO;
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class GenericInventorySystem : ModuleRules
|
||||
{
|
||||
public GenericInventorySystem(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
CppCompileWarningSettings.NonInlinedGenCppWarningLevel = WarningLevel.Warning;
|
||||
|
||||
PublicIncludePaths.AddRange(
|
||||
new[]
|
||||
{
|
||||
Path.Combine(ModuleDirectory, "Public/Core"),
|
||||
Path.Combine(ModuleDirectory, "Public/Core/Items"),
|
||||
Path.Combine(ModuleDirectory, "Public/Core/Attributes"),
|
||||
Path.Combine(ModuleDirectory, "Public/Core/Collections"),
|
||||
Path.Combine(ModuleDirectory, "Public/Core/Fragments"),
|
||||
Path.Combine(ModuleDirectory, "Public/Equipping"),
|
||||
Path.Combine(ModuleDirectory, "Public/Crafting"),
|
||||
Path.Combine(ModuleDirectory, "Public/Exchange"),
|
||||
Path.Combine(ModuleDirectory, "Public/Serialization")
|
||||
// ... add public include paths required here ...
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
PrivateIncludePaths.AddRange(
|
||||
new string[]
|
||||
{
|
||||
// ... add other private include paths required here ...
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new[]
|
||||
{
|
||||
"Core",
|
||||
"GameplayTags",
|
||||
"DeveloperSettings",
|
||||
"ModularGameplay"
|
||||
// ... add other public dependencies that you statically link with here ...
|
||||
}
|
||||
);
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"UMG",
|
||||
"SlateCore",
|
||||
"NetCore",
|
||||
"AssetRegistry"
|
||||
// ... add private dependencies that you statically link with here ...
|
||||
}
|
||||
);
|
||||
|
||||
SetupIrisSupport(Target);
|
||||
|
||||
DynamicallyLoadedModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
// ... add any modules that your module loads dynamically here ...
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GIS_LogChannels.h"
|
||||
#include "Engine/TimerHandle.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "Engine/CancellableAsyncAction.h"
|
||||
#include "GIS_AsyncAction_Wait.generated.h"
|
||||
|
||||
/**
|
||||
* Delegate triggered when an async wait action completes or is cancelled.
|
||||
* 异步等待动作完成或取消时触发的委托。
|
||||
*/
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FGIS_AsyncAction_WaitSignature);
|
||||
|
||||
/**
|
||||
* Base class for asynchronous wait actions on an actor.
|
||||
* 在演员上执行异步等待动作的基类。
|
||||
* @details Provides functionality to wait for specific conditions on an actor, with timer-based checks.
|
||||
* @细节 提供在演员上等待特定条件的功能,通过定时器检查。
|
||||
*/
|
||||
UCLASS(Abstract)
|
||||
class GENERICINVENTORYSYSTEM_API UGIS_AsyncAction_Wait : public UCancellableAsyncAction
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor for the async wait action.
|
||||
* 异步等待动作的构造函数。
|
||||
*/
|
||||
UGIS_AsyncAction_Wait();
|
||||
|
||||
/**
|
||||
* Delegate triggered when the wait action completes successfully.
|
||||
* 等待动作成功完成时触发的委托。
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FGIS_AsyncAction_WaitSignature OnCompleted;
|
||||
|
||||
/**
|
||||
* Delegate triggered when the wait action is cancelled.
|
||||
* 等待动作取消时触发的委托。
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FGIS_AsyncAction_WaitSignature OnCancelled;
|
||||
|
||||
/**
|
||||
* Gets the world associated with this action.
|
||||
* 获取与此动作关联的世界。
|
||||
* @return The world object, or nullptr if not set. 世界对象,如果未设置则返回nullptr。
|
||||
*/
|
||||
virtual UWorld* GetWorld() const override;
|
||||
|
||||
/**
|
||||
* Gets the target actor for the wait action.
|
||||
* 获取等待动作的目标演员。
|
||||
* @return The target actor, or nullptr if not set. 目标演员,如果未设置则返回nullptr。
|
||||
*/
|
||||
virtual AActor* GetActor() const;
|
||||
|
||||
/**
|
||||
* Activates the wait action, starting the timer.
|
||||
* 激活等待动作,启动定时器。
|
||||
*/
|
||||
virtual void Activate() override;
|
||||
|
||||
/**
|
||||
* Completes the wait action, triggering the OnCompleted delegate.
|
||||
* 完成等待动作,触发OnCompleted委托。
|
||||
*/
|
||||
virtual void Complete();
|
||||
|
||||
/**
|
||||
* Cancels the wait action, triggering the OnCancelled delegate.
|
||||
* 取消等待动作,触发OnCancelled委托。
|
||||
*/
|
||||
virtual void Cancel() override;
|
||||
|
||||
/**
|
||||
* Determines whether delegates should be broadcast.
|
||||
* 确定是否应广播委托。
|
||||
* @return True if delegates should be broadcast, false otherwise. 如果应广播委托则返回true,否则返回false。
|
||||
*/
|
||||
virtual bool ShouldBroadcastDelegates() const override;
|
||||
|
||||
/**
|
||||
* Called when the target actor is destroyed.
|
||||
* 目标演员销毁时调用。
|
||||
* @param DestroyedActor The actor that was destroyed. 被销毁的演员。
|
||||
*/
|
||||
UFUNCTION()
|
||||
virtual void OnTargetDestroyed(AActor* DestroyedActor);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Creates a new wait action instance.
|
||||
* 创建新的等待动作实例。
|
||||
* @param WorldContext The world context object to get the world reference. 用于获取世界引用的世界上下文对象。
|
||||
* @param TargetActor The target actor to wait for. 要等待的目标演员。
|
||||
* @param WaitInterval The interval between checks (in seconds). 检查间隔(以秒为单位)。
|
||||
* @param MaxWaitTimes The maximum number of checks before timeout (-1 for no limit). 最大检查次数,超时前(-1表示无限制)。
|
||||
* @return The created wait action, or nullptr if invalid parameters. 创建的等待动作,如果参数无效则返回nullptr。
|
||||
* @details Logs warnings if the world context, world, target actor, or wait interval is invalid.
|
||||
* @细节 如果世界上下文、世界、目标演员或等待间隔无效,则记录警告。
|
||||
*/
|
||||
template <typename ActionType = UGIS_AsyncAction_Wait>
|
||||
static ActionType* CreateWaitAction(UObject* WorldContext, AActor* TargetActor, float WaitInterval, int32 MaxWaitTimes)
|
||||
{
|
||||
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(TargetActor))
|
||||
{
|
||||
GIS_LOG(Warning, "invalid target actor.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (WaitInterval <= 0.f)
|
||||
{
|
||||
GIS_LOG(Warning, "WaitInterval %f must be greater than zero!", WaitInterval);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ActionType* NewAction = Cast<ActionType>(NewObject<UGIS_AsyncAction_Wait>(GetTransientPackage(), ActionType::StaticClass()));
|
||||
NewAction->SetWorld(World);
|
||||
NewAction->SetTargetActor(TargetActor);
|
||||
NewAction->SetWaitInterval(WaitInterval);
|
||||
NewAction->SetMaxWaitTimes(MaxWaitTimes);
|
||||
NewAction->RegisterWithGameInstance(World->GetGameInstance());
|
||||
return NewAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the world for the wait action.
|
||||
* 设置等待动作的世界。
|
||||
* @param NewWorld The world to set. 要设置的世界。
|
||||
*/
|
||||
void SetWorld(UWorld* NewWorld);
|
||||
|
||||
/**
|
||||
* Sets the target actor for the wait action.
|
||||
* 设置等待动作的目标演员。
|
||||
* @param NewTargetActor The target actor to set. 要设置的目标演员。
|
||||
*/
|
||||
void SetTargetActor(AActor* NewTargetActor);
|
||||
|
||||
/**
|
||||
* Sets the interval between checks.
|
||||
* 设置检查间隔。
|
||||
* @param NewWaitInterval The interval (in seconds). 间隔(以秒为单位)。
|
||||
*/
|
||||
void SetWaitInterval(float NewWaitInterval);
|
||||
|
||||
/**
|
||||
* Sets the maximum number of checks before timeout.
|
||||
* 设置超时前的最大检查次数。
|
||||
* @param NewMaxWaitTimes The maximum number of checks (-1 for no limit). 最大检查次数(-1表示无限制)。
|
||||
*/
|
||||
void SetMaxWaitTimes(int32 NewMaxWaitTimes);
|
||||
|
||||
/**
|
||||
* Stops the timer for the wait action.
|
||||
* 停止等待动作的定时器。
|
||||
*/
|
||||
void StopWaiting();
|
||||
|
||||
/**
|
||||
* Called when the timer ticks to check the wait condition.
|
||||
* 定时器触发时调用以检查等待条件。
|
||||
*/
|
||||
UFUNCTION()
|
||||
void OnTimer();
|
||||
|
||||
/**
|
||||
* Cleans up resources used by the wait action.
|
||||
* 清理等待动作使用的资源。
|
||||
*/
|
||||
virtual void Cleanup();
|
||||
|
||||
/**
|
||||
* Executes the specific wait condition check.
|
||||
* 执行特定的等待条件检查。
|
||||
*/
|
||||
UFUNCTION()
|
||||
virtual void OnExecutionAction();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Weak reference to the world for the wait action.
|
||||
* 等待动作的世界的弱引用。
|
||||
*/
|
||||
TWeakObjectPtr<UWorld> WorldPtr{nullptr};
|
||||
|
||||
/**
|
||||
* Weak reference to the target actor for the wait action.
|
||||
* 等待动作的目标演员的弱引用。
|
||||
*/
|
||||
UPROPERTY()
|
||||
TWeakObjectPtr<AActor> TargetActorPtr{nullptr};
|
||||
|
||||
/**
|
||||
* Handle for the timer used to check the wait condition.
|
||||
* 用于检查等待条件的定时器句柄。
|
||||
*/
|
||||
UPROPERTY()
|
||||
FTimerHandle TimerHandle;
|
||||
|
||||
/**
|
||||
* Interval between checks (in seconds).
|
||||
* 检查间隔(以秒为单位)。
|
||||
*/
|
||||
float WaitInterval = 0.2f;
|
||||
|
||||
/**
|
||||
* Current number of checks performed.
|
||||
* 当前执行的检查次数。
|
||||
*/
|
||||
int32 WaitTimes = 0;
|
||||
|
||||
/**
|
||||
* Maximum number of checks before timeout (-1 for no limit).
|
||||
* 超时前的最大检查次数(-1表示无限制)。
|
||||
*/
|
||||
int32 MaxWaitTimes{-1};
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GIS_AsyncAction_Wait.h"
|
||||
#include "GIS_AsyncAction_WaitEquipmentSystem.generated.h"
|
||||
|
||||
class UGIS_EquipmentSystemComponent;
|
||||
|
||||
/**
|
||||
* Async action to wait for a valid equipment system component on an actor.
|
||||
* 在演员上等待有效装备系统组件的异步动作。
|
||||
*/
|
||||
UCLASS()
|
||||
class GENERICINVENTORYSYSTEM_API UGIS_AsyncAction_WaitEquipmentSystem : public UGIS_AsyncAction_Wait
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Waits for a valid equipment system component on the target actor.
|
||||
* 在目标演员上等待有效的装备系统组件。
|
||||
* @param WorldContext The world context object to get the world reference. 用于获取世界引用的世界上下文对象。
|
||||
* @param TargetActor The target actor to wait for. 要等待的目标演员。
|
||||
* @return The created wait action. 创建的等待动作。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GIS|Async", meta = (WorldContext = "WorldContext", DefaultToSelf="TargetActor", BlueprintInternalUseOnly = "true"))
|
||||
static UGIS_AsyncAction_WaitEquipmentSystem* WaitEquipmentSystem(UObject* WorldContext, AActor* TargetActor);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Checks for the presence of a valid equipment system component.
|
||||
* 检查是否存在有效的装备系统组件。
|
||||
*/
|
||||
virtual void OnExecutionAction() override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Async action to wait for a valid and initialized equipment system component on an actor.
|
||||
* 在演员上等待有效且已初始化的装备系统组件的异步动作。
|
||||
*/
|
||||
UCLASS()
|
||||
class GENERICINVENTORYSYSTEM_API UGIS_AsyncAction_WaitEquipmentSystemInitialized : public UGIS_AsyncAction_WaitEquipmentSystem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Waits for a valid and initialized equipment system component on the target actor.
|
||||
* 在目标演员上等待有效且已初始化的装备系统组件。
|
||||
* @param WorldContext The world context object to get the world reference. 用于获取世界引用的世界上下文对象。
|
||||
* @param TargetActor The target actor to wait for. 要等待的目标演员。
|
||||
* @return The created wait action. 创建的等待动作。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GIS|Async", meta = (WorldContext = "WorldContext", DefaultToSelf="TargetActor", BlueprintInternalUseOnly = "true"))
|
||||
static UGIS_AsyncAction_WaitEquipmentSystem* WaitEquipmentSystemInitialized(UObject* WorldContext, AActor* TargetActor);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Checks for the presence and initialization of the equipment system component.
|
||||
* 检查装备系统组件的存在和初始化状态。
|
||||
*/
|
||||
virtual void OnExecutionAction() override;
|
||||
|
||||
/**
|
||||
* Cleans up resources and event bindings.
|
||||
* 清理资源和事件绑定。
|
||||
*/
|
||||
virtual void Cleanup() override;
|
||||
|
||||
/**
|
||||
* Called when the equipment system component is initialized.
|
||||
* 装备系统组件初始化时调用。
|
||||
*/
|
||||
UFUNCTION()
|
||||
virtual void OnSystemInitialized();
|
||||
|
||||
/**
|
||||
* Weak reference to the equipment system component being waited for.
|
||||
* 等待的装备系统组件的弱引用。
|
||||
*/
|
||||
TWeakObjectPtr<UGIS_EquipmentSystemComponent> EquipmentSystemPtr;
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GIS_AsyncAction_Wait.h"
|
||||
#include "GIS_InventoryMeesages.h"
|
||||
#include "GIS_AsyncAction_WaitInventorySystem.generated.h"
|
||||
|
||||
/**
|
||||
* Async action to wait for a valid inventory system component on an actor.
|
||||
* 在演员上等待有效库存系统组件的异步动作。
|
||||
*/
|
||||
UCLASS()
|
||||
class GENERICINVENTORYSYSTEM_API UGIS_AsyncAction_WaitInventorySystem : public UGIS_AsyncAction_Wait
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Waits for a valid inventory system component on the target actor.
|
||||
* 在目标演员上等待有效的库存系统组件。
|
||||
* @param WorldContext The world context object to get the world reference. 用于获取世界引用的世界上下文对象。
|
||||
* @param TargetActor The target actor to wait for. 要等待的目标演员。
|
||||
* @return The created wait action. 创建的等待动作。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GIS|Async", meta = (WorldContext = "WorldContext", DefaultToSelf="TargetActor", BlueprintInternalUseOnly = "true"))
|
||||
static UGIS_AsyncAction_WaitInventorySystem* WaitInventorySystem(UObject* WorldContext, AActor* TargetActor);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Checks for the presence of a valid inventory system component.
|
||||
* 检查是否存在有效的库存系统组件。
|
||||
*/
|
||||
virtual void OnExecutionAction() override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Async action to wait for a valid and initialized inventory system component on an actor.
|
||||
* 在演员上等待有效且已初始化的库存系统组件的异步动作。
|
||||
*/
|
||||
UCLASS()
|
||||
class GENERICINVENTORYSYSTEM_API UGIS_AsyncAction_WaitInventorySystemInitialized : public UGIS_AsyncAction_WaitInventorySystem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Waits for a valid and initialized inventory system component on the target actor.
|
||||
* 在目标演员上等待有效且已初始化的库存系统组件。
|
||||
* @param WorldContext The world context object to get the world reference. 用于获取世界引用的世界上下文对象。
|
||||
* @param TargetActor The target actor to wait for. 要等待的目标演员。
|
||||
* @return The created wait action. 创建的等待动作。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GIS|Async", meta = (WorldContext = "WorldContext", DefaultToSelf="TargetActor", BlueprintInternalUseOnly = "true"))
|
||||
static UGIS_AsyncAction_WaitInventorySystem* WaitInventorySystemInitialized(UObject* WorldContext, AActor* TargetActor);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Checks for the presence and initialization of the inventory system component.
|
||||
* 检查库存系统组件的存在和初始化状态。
|
||||
*/
|
||||
virtual void OnExecutionAction() override;
|
||||
|
||||
/**
|
||||
* Cleans up resources and event bindings.
|
||||
* 清理资源和事件绑定。
|
||||
*/
|
||||
virtual void Cleanup() override;
|
||||
|
||||
/**
|
||||
* Called when the inventory system component is initialized.
|
||||
* 库存系统组件初始化时调用。
|
||||
*/
|
||||
UFUNCTION()
|
||||
virtual void OnSystemInitialized();
|
||||
|
||||
/**
|
||||
* Weak reference to the inventory system component being waited for.
|
||||
* 等待的库存系统组件的弱引用。
|
||||
*/
|
||||
TWeakObjectPtr<UGIS_InventorySystemComponent> InventorySysPtr;
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/CancellableAsyncAction.h"
|
||||
#include "Templates/SubclassOf.h"
|
||||
#include "Runtime/Launch/Resources/Version.h"
|
||||
#if ENGINE_MINOR_VERSION < 5
|
||||
#include "InstancedStruct.h"
|
||||
#else
|
||||
#include "StructUtils/InstancedStruct.h"
|
||||
#endif
|
||||
#include "GIS_AsyncAction_WaitItemFragmentDataChanged.generated.h"
|
||||
|
||||
class UGIS_ItemFragment;
|
||||
class UGIS_ItemInstance;
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FGIS_WaitFragmentStateChangedSignature, const UGIS_ItemFragment*, Fragment, const FInstancedStruct&, Data);
|
||||
|
||||
|
||||
/**
|
||||
* Async action to wait for a fragment data changed on an item instance.
|
||||
* 在道具实例上等待指定道具片段的运行时数据变更。
|
||||
*/
|
||||
UCLASS()
|
||||
class GENERICINVENTORYSYSTEM_API UGIS_AsyncAction_WaitItemFragmentDataChanged : public UCancellableAsyncAction
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Wait for a fragment data changed on an item instance.
|
||||
* 在道具实例上等待指定道具片段的运行时数据变更。
|
||||
* @param WorldContext The world context object to get the world reference. 用于获取世界引用的世界上下文对象。
|
||||
* @param ItemInstance The target item instance to wait for. 要等待的目标道具。
|
||||
* @param FragmentClass The fragment type to wait for. 要等待的片段类型。
|
||||
* @return The created wait action. 创建的等待动作。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GIS|Async", meta = (WorldContext = "WorldContext", DefaultToSelf="ItemInstnace", BlueprintInternalUseOnly = "true"))
|
||||
static UGIS_AsyncAction_WaitItemFragmentDataChanged* WaitItemFragmentStateChanged(UObject* WorldContext, UGIS_ItemInstance* ItemInstance, TSoftClassPtr<UGIS_ItemFragment> FragmentClass);
|
||||
|
||||
virtual void Activate() override;
|
||||
virtual void Cancel() override;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category="GIS|Async")
|
||||
FGIS_WaitFragmentStateChangedSignature OnStateChanged;
|
||||
|
||||
protected:
|
||||
UFUNCTION()
|
||||
void OnFragmentStateChanged(const UGIS_ItemFragment* Fragment, const FInstancedStruct& State);
|
||||
|
||||
TWeakObjectPtr<UGIS_ItemInstance> ItemInstance;
|
||||
|
||||
TSubclassOf<UGIS_ItemFragment> FragmentClass;
|
||||
};
|
||||
@@ -0,0 +1,259 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Net/Serialization/FastArraySerializer.h"
|
||||
#include "UObject/Interface.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "GIS_GameplayTagFloat.generated.h"
|
||||
|
||||
struct FGIS_GameplayTagFloatContainer;
|
||||
|
||||
/**
|
||||
* Interface for objects that own a gameplay tag float container.
|
||||
* 拥有游戏标签浮点容器对象的接口。
|
||||
*/
|
||||
UINTERFACE(meta=(CannotImplementInterfaceInBlueprint))
|
||||
class GENERICINVENTORYSYSTEM_API UGIS_GameplayTagFloatContainerOwner : public UInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface class for handling updates to float attributes in a gameplay tag container.
|
||||
* 处理游戏标签容器中浮点属性更新的接口类。
|
||||
*/
|
||||
class GENERICINVENTORYSYSTEM_API IGIS_GameplayTagFloatContainerOwner
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Called when a float attribute associated with a gameplay tag is updated.
|
||||
* 当与游戏标签关联的浮点属性更新时调用。
|
||||
* @param Tag The gameplay tag identifying the attribute. 标识属性的游戏标签。
|
||||
* @param OldValue The previous value of the attribute. 属性之前的值。
|
||||
* @param NewValue The new value of the attribute. 属性的新值。
|
||||
*/
|
||||
virtual void OnTagFloatUpdate(const FGameplayTag& Tag, float OldValue, float NewValue) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a gameplay tag and float value pair.
|
||||
* 表示一个游戏标签和浮点值的键值对。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICINVENTORYSYSTEM_API FGIS_GameplayTagFloat : public FFastArraySerializerItem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Default constructor for the gameplay tag float pair.
|
||||
* 游戏标签浮点对的默认构造函数。
|
||||
*/
|
||||
FGIS_GameplayTagFloat()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for the gameplay tag float pair with initial values.
|
||||
* 使用初始值构造游戏标签浮点对。
|
||||
* @param InTag The gameplay tag for the pair. 键值对的游戏标签。
|
||||
* @param InValue The float value for the pair. 键值对的浮点值。
|
||||
*/
|
||||
FGIS_GameplayTagFloat(FGameplayTag InTag, float InValue)
|
||||
: Tag(InTag)
|
||||
, Value(InValue)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a debug string representation of the tag-value pair.
|
||||
* 获取标签-值对的调试字符串表示。
|
||||
* @return The debug string. 调试字符串。
|
||||
*/
|
||||
FString GetDebugString() const;
|
||||
|
||||
friend FGIS_GameplayTagFloatContainer;
|
||||
|
||||
/**
|
||||
* The gameplay tag identifying the attribute.
|
||||
* 标识属性的游戏标签。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category="GIS")
|
||||
FGameplayTag Tag;
|
||||
|
||||
/**
|
||||
* The float value associated with the tag.
|
||||
* 与标签关联的浮点值。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category="GIS")
|
||||
float Value = 0;
|
||||
|
||||
/**
|
||||
* The previous float value of the attribute (not replicated).
|
||||
* 属性的前一个浮点值(不复制)。
|
||||
*/
|
||||
UPROPERTY(NotReplicated)
|
||||
float PrevValue = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Container for storing gameplay tag float pairs.
|
||||
* 存储游戏标签浮点对的容器。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICINVENTORYSYSTEM_API FGIS_GameplayTagFloatContainer : public FFastArraySerializer
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Default constructor for the float container.
|
||||
* 浮点容器的默认构造函数。
|
||||
*/
|
||||
FGIS_GameplayTagFloatContainer()
|
||||
// : Owner(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for the float container with an owning object.
|
||||
* 使用拥有对象构造浮点容器。
|
||||
* @param InObject The object that owns this container. 拥有此容器的对象。
|
||||
*/
|
||||
FGIS_GameplayTagFloatContainer(UObject* InObject)
|
||||
: ContainerOwner(InObject)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new tag-value pair to the container.
|
||||
* 向容器添加新的标签-值对。
|
||||
* @param Tag The gameplay tag to add. 要添加的游戏标签。
|
||||
* @param Value The float value to associate with the tag. 与标签关联的浮点值。
|
||||
*/
|
||||
void AddItem(FGameplayTag Tag, float Value);
|
||||
|
||||
/**
|
||||
* Sets the value for an existing tag or adds a new tag-value pair.
|
||||
* 为现有标签设置值或添加新的标签-值对。
|
||||
* @param Tag The gameplay tag to set. 要设置的游戏标签。
|
||||
* @param Value The float value to set. 要设置的浮点值。
|
||||
*/
|
||||
void SetItem(FGameplayTag Tag, float Value);
|
||||
|
||||
/**
|
||||
* Removes a specified amount from a tag-value pair.
|
||||
* 从标签-值对中移除指定数量。
|
||||
* @param Tag The gameplay tag to remove value from. 要移除值的游戏标签。
|
||||
* @param Value The amount to remove. 要移除的数量。
|
||||
*/
|
||||
void RemoveItem(FGameplayTag Tag, float Value);
|
||||
|
||||
/**
|
||||
* Sets all items in the container.
|
||||
* 设置容器中的所有条目。
|
||||
* @param NewItems The array of tag-value pairs to set. 要设置的标签-值对数组。
|
||||
*/
|
||||
void SetItems(const TArray<FGIS_GameplayTagFloat>& NewItems);
|
||||
|
||||
/**
|
||||
* Clears all items from the container.
|
||||
* 清空容器中的所有条目。
|
||||
*/
|
||||
void EmptyItems();
|
||||
|
||||
/**
|
||||
* Gets the value associated with a specific tag.
|
||||
* 获取与指定标签关联的值。
|
||||
* @param Tag The gameplay tag to query. 要查询的游戏标签。
|
||||
* @return The float value associated with the tag. 与标签关联的浮点值。
|
||||
*/
|
||||
float GetValue(FGameplayTag Tag) const
|
||||
{
|
||||
return TagToValueMap.FindRef(Tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the container contains a specific tag.
|
||||
* 检查容器是否包含指定标签。
|
||||
* @param Tag The gameplay tag to check. 要检查的游戏标签。
|
||||
* @return True if the tag exists in the container, false otherwise. 如果标签存在于容器中则返回true,否则返回false。
|
||||
*/
|
||||
bool ContainsTag(FGameplayTag Tag) const
|
||||
{
|
||||
return TagToValueMap.Contains(Tag);
|
||||
}
|
||||
|
||||
//~FFastArraySerializer contract
|
||||
/**
|
||||
* Called before items are removed during replication.
|
||||
* 复制期间移除条目前调用。
|
||||
* @param RemovedIndices The indices of items to remove. 要移除的条目索引。
|
||||
* @param FinalSize The final size of the items array after removal. 移除后条目数组的最终大小。
|
||||
*/
|
||||
void PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize);
|
||||
|
||||
/**
|
||||
* Called after items are added during replication.
|
||||
* 复制期间添加条目后调用。
|
||||
* @param AddedIndices The indices of added items. 添加的条目索引。
|
||||
* @param FinalSize The final size of the items array after addition. 添加后条目数组的最终大小。
|
||||
*/
|
||||
void PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize);
|
||||
|
||||
/**
|
||||
* Called after items are changed during replication.
|
||||
* 复制期间条目更改后调用。
|
||||
* @param ChangedIndices The indices of changed items. 更改的条目索引。
|
||||
* @param FinalSize The final size of the items array after change. 更改后条目数组的最终大小。
|
||||
*/
|
||||
void PostReplicatedChange(const TArrayView<int32> ChangedIndices, int32 FinalSize);
|
||||
//~End of FFastArraySerializer contract
|
||||
|
||||
/**
|
||||
* Handles delta serialization for network replication.
|
||||
* 处理网络复制的增量序列化。
|
||||
* @param DeltaParms The serialization parameters. 序列化参数。
|
||||
* @return True if serialization was successful, false otherwise. 如果序列化成功则返回true,否则返回false。
|
||||
*/
|
||||
bool NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParms)
|
||||
{
|
||||
return FastArrayDeltaSerialize<FGIS_GameplayTagFloat, FGIS_GameplayTagFloatContainer>(Items, DeltaParms, *this);
|
||||
}
|
||||
|
||||
/**
|
||||
* The object that owns this container.
|
||||
* 拥有此容器的对象。
|
||||
*/
|
||||
UPROPERTY()
|
||||
TObjectPtr<UObject> ContainerOwner{nullptr};
|
||||
|
||||
/**
|
||||
* Replicated list of gameplay tag float pairs.
|
||||
* 游戏标签浮点对的复制列表。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, SaveGame, BlueprintReadWrite, Category="GIS", meta=(TitleProperty="{Tag}->{Value}"))
|
||||
TArray<FGIS_GameplayTagFloat> Items;
|
||||
|
||||
/**
|
||||
* Accelerated map of tags to values for efficient queries.
|
||||
* 标签到值的加速映射,用于高效查询。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, NotReplicated, SaveGame, Category="GIS", meta=(ForceInlineRow))
|
||||
TMap<FGameplayTag, float> TagToValueMap;
|
||||
};
|
||||
|
||||
/**
|
||||
* Traits for the float container to enable network delta serialization.
|
||||
* 浮点容器的特性,用于启用网络增量序列化。
|
||||
*/
|
||||
template <>
|
||||
struct TStructOpsTypeTraits<FGIS_GameplayTagFloatContainer> : TStructOpsTypeTraitsBase2<FGIS_GameplayTagFloatContainer>
|
||||
{
|
||||
enum
|
||||
{
|
||||
WithNetDeltaSerializer = true,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,248 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Net/Serialization/FastArraySerializer.h"
|
||||
#include "UObject/Interface.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "GIS_GameplayTagInteger.generated.h"
|
||||
|
||||
struct FGIS_GameplayTagIntegerContainer;
|
||||
|
||||
/**
|
||||
* Interface for objects that own a gameplay tag integer container.
|
||||
* 拥有游戏标签整型容器对象的接口。
|
||||
*/
|
||||
UINTERFACE(meta=(CannotImplementInterfaceInBlueprint))
|
||||
class GENERICINVENTORYSYSTEM_API UGIS_GameplayTagIntegerContainerOwner : public UInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface class for handling updates to integer attributes in a gameplay tag container.
|
||||
* 处理游戏标签容器中整型属性更新的接口类。
|
||||
*/
|
||||
class GENERICINVENTORYSYSTEM_API IGIS_GameplayTagIntegerContainerOwner
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Called when an integer attribute associated with a gameplay tag is updated.
|
||||
* 当与游戏标签关联的整型属性更新时调用。
|
||||
* @param Tag The gameplay tag identifying the attribute. 标识属性的游戏标签。
|
||||
* @param OldValue The previous value of the attribute. 属性之前的值。
|
||||
* @param NewValue The new value of the attribute. 属性的新值。
|
||||
*/
|
||||
virtual void OnTagIntegerUpdate(const FGameplayTag& Tag, int32 OldValue, int32 NewValue) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a gameplay tag and integer value pair.
|
||||
* 表示一个游戏标签和整型值的键值对。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICINVENTORYSYSTEM_API FGIS_GameplayTagInteger : public FFastArraySerializerItem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Default constructor for the gameplay tag integer pair.
|
||||
* 游戏标签整型对的默认构造函数。
|
||||
*/
|
||||
FGIS_GameplayTagInteger()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for the gameplay tag integer pair with initial values.
|
||||
* 使用初始值构造游戏标签整型对。
|
||||
* @param InTag The gameplay tag for the pair. 键值对的游戏标签。
|
||||
* @param InValue The integer value for the pair. 键值对的整型值。
|
||||
*/
|
||||
FGIS_GameplayTagInteger(FGameplayTag InTag, int32 InValue)
|
||||
: Tag(InTag)
|
||||
, Value(InValue)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a debug string representation of the tag-value pair.
|
||||
* 获取标签-值对的调试字符串表示。
|
||||
* @return The debug string. 调试字符串。
|
||||
*/
|
||||
FString GetDebugString() const;
|
||||
|
||||
friend FGIS_GameplayTagIntegerContainer;
|
||||
|
||||
/**
|
||||
* The gameplay tag identifying the attribute.
|
||||
* 标识属性的游戏标签。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category="GIS")
|
||||
FGameplayTag Tag;
|
||||
|
||||
/**
|
||||
* The integer value associated with the tag.
|
||||
* 与标签关联的整型值。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category="GIS")
|
||||
int32 Value = 0;
|
||||
|
||||
/**
|
||||
* The previous integer value of the attribute (not replicated).
|
||||
* 属性的前一个整型值(不复制)。
|
||||
* @attention Likely a typo in the original code; should be int32 instead of float.
|
||||
* @注意 原始代码中可能有误,应为int32而非float。
|
||||
*/
|
||||
UPROPERTY(NotReplicated)
|
||||
float PrevValue = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Container for storing gameplay tag integer pairs.
|
||||
* 存储游戏标签整型对的容器。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICINVENTORYSYSTEM_API FGIS_GameplayTagIntegerContainer : public FFastArraySerializer
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Default constructor for the integer container.
|
||||
* 整型容器的默认构造函数。
|
||||
*/
|
||||
FGIS_GameplayTagIntegerContainer()
|
||||
// : Owner(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for the integer container with an owning object.
|
||||
* 使用拥有对象构造整型容器。
|
||||
* @param InObject The object that owns this container. 拥有此容器的对象。
|
||||
*/
|
||||
FGIS_GameplayTagIntegerContainer(UObject* InObject)
|
||||
: ContainerOwner(InObject)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new tag-value pair to the container.
|
||||
* 向容器添加新的标签-值对。
|
||||
* @param Tag The gameplay tag to add. 要添加的游戏标签。
|
||||
* @param Value The integer value to associate with the tag. 与标签关联的整型值。
|
||||
*/
|
||||
void AddItem(FGameplayTag Tag, int32 Value);
|
||||
|
||||
/**
|
||||
* Sets the value for an existing tag or adds a new tag-value pair.
|
||||
* 为现有标签设置值或添加新的标签-值对。
|
||||
* @param Tag The gameplay tag to set. 要设置的游戏标签。
|
||||
* @param Value The integer value to set. 要设置的整型值。
|
||||
*/
|
||||
void SetItem(FGameplayTag Tag, int32 Value);
|
||||
|
||||
/**
|
||||
* Removes a specified amount from a tag-value pair.
|
||||
* 从标签-值对中移除指定数量。
|
||||
* @param Tag The gameplay tag to remove value from. 要移除值的游戏标签。
|
||||
* @param Value The amount to remove. 要移除的数量。
|
||||
*/
|
||||
void RemoveItem(FGameplayTag Tag, int32 Value);
|
||||
|
||||
/**
|
||||
* Gets the value associated with a specific tag.
|
||||
* 获取与指定标签关联的值。
|
||||
* @param Tag The gameplay tag to query. 要查询的游戏标签。
|
||||
* @return The integer value associated with the tag. 与标签关联的整型值。
|
||||
*/
|
||||
int32 GetValue(FGameplayTag Tag) const
|
||||
{
|
||||
return TagToValueMap.FindRef(Tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the container contains a specific tag.
|
||||
* 检查容器是否包含指定标签。
|
||||
* @param Tag The gameplay tag to check. 要检查的游戏标签。
|
||||
* @return True if the tag exists in the container, false otherwise. 如果标签存在于容器中则返回true,否则返回false。
|
||||
*/
|
||||
bool ContainsTag(FGameplayTag Tag) const
|
||||
{
|
||||
return TagToValueMap.Contains(Tag);
|
||||
}
|
||||
|
||||
//~FFastArraySerializer contract
|
||||
/**
|
||||
* Called before items are removed during replication.
|
||||
* 复制期间移除条目前调用。
|
||||
* @param RemovedIndices The indices of items to remove. 要移除的条目索引。
|
||||
* @param FinalSize The final size of the items array after removal. 移除后条目数组的最终大小。
|
||||
*/
|
||||
void PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize);
|
||||
|
||||
/**
|
||||
* Called after items are added during replication.
|
||||
* 复制期间添加条目后调用。
|
||||
* @param AddedIndices The indices of added items. 添加的条目索引。
|
||||
* @param FinalSize The final size of the items array after addition. 添加后条目数组的最终大小。
|
||||
*/
|
||||
void PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize);
|
||||
|
||||
/**
|
||||
* Called after items are changed during replication.
|
||||
* 复制期间条目更改后调用。
|
||||
* @param ChangedIndices The indices of changed items. 更改的条目索引。
|
||||
* @param FinalSize The final size of the items array after change. 更改后条目数组的最终大小。
|
||||
*/
|
||||
void PostReplicatedChange(const TArrayView<int32> ChangedIndices, int32 FinalSize);
|
||||
//~End of FFastArraySerializer contract
|
||||
|
||||
/**
|
||||
* Handles delta serialization for network replication.
|
||||
* 处理网络复制的增量序列化。
|
||||
* @param DeltaParms The serialization parameters. 序列化参数。
|
||||
* @return True if serialization was successful, false otherwise. 如果序列化成功则返回true,否则返回false。
|
||||
*/
|
||||
bool NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParms)
|
||||
{
|
||||
return FastArrayDeltaSerialize<FGIS_GameplayTagInteger, FGIS_GameplayTagIntegerContainer>(Items, DeltaParms, *this);
|
||||
}
|
||||
|
||||
/**
|
||||
* The object that owns this container.
|
||||
* 拥有此容器的对象。
|
||||
*/
|
||||
UPROPERTY()
|
||||
TObjectPtr<UObject> ContainerOwner{nullptr};
|
||||
|
||||
/**
|
||||
* Replicated list of gameplay tag integer pairs.
|
||||
* 游戏标签整型对的复制列表。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, SaveGame, BlueprintReadWrite, Category="GIS", meta=(TitleProperty="{Tag}->{Value}"))
|
||||
TArray<FGIS_GameplayTagInteger> Items;
|
||||
|
||||
/**
|
||||
* Accelerated map of tags to values for efficient queries.
|
||||
* 标签到值的加速映射,用于高效查询。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, NotReplicated, SaveGame, Category="GIS", meta=(ForceInlineRow))
|
||||
TMap<FGameplayTag, int32> TagToValueMap;
|
||||
};
|
||||
|
||||
/**
|
||||
* Traits for the integer container to enable network delta serialization.
|
||||
* 整型容器的特性,用于启用网络增量序列化。
|
||||
*/
|
||||
template <>
|
||||
struct TStructOpsTypeTraits<FGIS_GameplayTagIntegerContainer> : TStructOpsTypeTraitsBase2<FGIS_GameplayTagIntegerContainer>
|
||||
{
|
||||
enum
|
||||
{
|
||||
WithNetDeltaSerializer = true,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,145 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Net/Serialization/FastArraySerializer.h"
|
||||
#include "GIS_CollectionContainer.generated.h"
|
||||
|
||||
class UGIS_ItemCollectionDefinition;
|
||||
class UGIS_InventorySystemComponent;
|
||||
class UGIS_ItemCollection;
|
||||
struct FGIS_CollectionContainer;
|
||||
|
||||
/**
|
||||
* Structure representing an entry in the collection container.
|
||||
* 表示集合容器中条目的结构体。
|
||||
*/
|
||||
USTRUCT()
|
||||
struct GENERICINVENTORYSYSTEM_API FGIS_CollectionEntry : public FFastArraySerializerItem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Default constructor for collection entry.
|
||||
* 集合条目的默认构造函数。
|
||||
*/
|
||||
FGIS_CollectionEntry() : Instance(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Unique ID of the collection entry.
|
||||
* 集合条目的唯一ID。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, Category="GIS")
|
||||
FGuid Id = FGuid();
|
||||
|
||||
/**
|
||||
* The collection definition associated with this entry.
|
||||
* 与此条目关联的集合定义。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, Category="GIS")
|
||||
TObjectPtr<const UGIS_ItemCollectionDefinition> Definition;
|
||||
|
||||
/**
|
||||
* The collection instance associated with this entry.
|
||||
* 与此条目关联的集合实例。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, Category="GIS", meta=(ShowInnerProperties))
|
||||
TObjectPtr<UGIS_ItemCollection> Instance = nullptr;
|
||||
|
||||
/**
|
||||
* Checks if the collection entry is valid.
|
||||
* 检查集合条目是否有效。
|
||||
* @return True if the entry is valid, false otherwise. 如果条目有效则返回true,否则返回false。
|
||||
*/
|
||||
bool IsValidEntry() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Container for storing a list of item collections with replication support.
|
||||
* 用于存储道具集合列表的容器,支持复制。
|
||||
*/
|
||||
USTRUCT()
|
||||
struct GENERICINVENTORYSYSTEM_API FGIS_CollectionContainer : public FFastArraySerializer
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Default constructor for collection container.
|
||||
* 集合容器的默认构造函数。
|
||||
*/
|
||||
FGIS_CollectionContainer()
|
||||
: OwningComponent(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for collection container with an owning inventory component.
|
||||
* 使用所属库存组件构造集合容器。
|
||||
* @param InInventory The owning inventory system component. 所属的库存系统组件。
|
||||
*/
|
||||
FGIS_CollectionContainer(UGIS_InventorySystemComponent* InInventory);
|
||||
|
||||
//~FFastArraySerializer contract
|
||||
/**
|
||||
* Called before collection entries are removed during replication.
|
||||
* 复制期间在移除集合条目前调用。
|
||||
* @param RemovedIndices The indices of removed entries. 移除条目的索引。
|
||||
* @param FinalSize The final size of the array after removal. 移除后数组的最终大小。
|
||||
*/
|
||||
void PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize);
|
||||
|
||||
/**
|
||||
* Called after collection entries are added during replication.
|
||||
* 复制期间在添加集合条目后调用。
|
||||
* @param AddedIndices The indices of added entries. 添加条目的索引。
|
||||
* @param FinalSize The final size of the array after addition. 添加后数组的最终大小。
|
||||
*/
|
||||
void PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize);
|
||||
|
||||
/**
|
||||
* Called after collection entries are changed during replication.
|
||||
* 复制期间在集合条目更改后调用。
|
||||
* @param ChangedIndices The indices of changed entries. 更改条目的索引。
|
||||
* @param FinalSize The final size of the array after changes. 更改后数组的最终大小。
|
||||
*/
|
||||
void PostReplicatedChange(const TArrayView<int32> ChangedIndices, int32 FinalSize);
|
||||
//~End of FFastArraySerializer contract
|
||||
|
||||
/**
|
||||
* Handles delta serialization for the collection container.
|
||||
* 处理集合容器的增量序列化。
|
||||
* @param DeltaParms The serialization parameters. 序列化参数。
|
||||
* @return True if serialization was successful, false otherwise. 如果序列化成功则返回true,否则返回false。
|
||||
*/
|
||||
bool NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParms)
|
||||
{
|
||||
return FastArrayDeltaSerialize<FGIS_CollectionEntry, FGIS_CollectionContainer>(Entries, DeltaParms, *this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replicated list of collection entries.
|
||||
* 复制的集合条目列表。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, Category="InventorySystem", meta=(ShowOnlyInnerProperties, DisplayName="Collections"))
|
||||
TArray<FGIS_CollectionEntry> Entries;
|
||||
|
||||
/**
|
||||
* The inventory system component that owns this container.
|
||||
* 拥有此容器的库存系统组件。
|
||||
*/
|
||||
UPROPERTY()
|
||||
TObjectPtr<UGIS_InventorySystemComponent> OwningComponent;
|
||||
};
|
||||
|
||||
/**
|
||||
* Template specialization to enable network delta serialization for the collection container.
|
||||
* 为集合容器启用网络增量序列化的模板特化。
|
||||
*/
|
||||
template <>
|
||||
struct TStructOpsTypeTraits<FGIS_CollectionContainer> : TStructOpsTypeTraitsBase2<FGIS_CollectionContainer>
|
||||
{
|
||||
enum { WithNetDeltaSerializer = true };
|
||||
};
|
||||
@@ -0,0 +1,651 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "Engine/EngineTypes.h"
|
||||
#include "Engine/DataAsset.h"
|
||||
#include "GIS_CoreStructLibray.h"
|
||||
#include "GIS_InventoryMeesages.h"
|
||||
#include "Items/GIS_ItemInfo.h"
|
||||
#include "Items/GIS_ItemStack.h"
|
||||
#include "GIS_ItemCollection.generated.h"
|
||||
|
||||
class UGIS_ItemRestriction;
|
||||
class UGIS_SerializationFunctionLibrary;
|
||||
class UGIS_ItemRestrictionSet;
|
||||
class UGIS_InventorySubsystem;
|
||||
class UGIS_ItemCollection;
|
||||
class UGIS_InventorySystemComponent;
|
||||
|
||||
/**
|
||||
* Delegate triggered when the item collection is updated.
|
||||
* 道具集合更新时触发的委托。
|
||||
* @param Message The update message containing collection changes. 包含集合变更的更新消息。
|
||||
*/
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FGIS_Collection_UpdateSignature, const FGIS_InventoryCollectionUpdateMessage&, Message);
|
||||
|
||||
/**
|
||||
* Holds static configuration for an item collection.
|
||||
* 存储道具集合的静态配置。
|
||||
*/
|
||||
UCLASS(BlueprintType, NotBlueprintable)
|
||||
class GENERICINVENTORYSYSTEM_API UGIS_ItemCollectionDefinition : public UDataAsset
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Checks if the collection definition supports networking.
|
||||
* 检查集合定义是否支持网络。
|
||||
* @return True if networking is supported, false otherwise. 如果支持网络则返回true,否则返回false。
|
||||
*/
|
||||
virtual bool IsSupportedForNetworking() const override;
|
||||
|
||||
/**
|
||||
* The unique tag of the item collection for querying in the inventory.
|
||||
* 道具集合的唯一标签,用于在库存中查询。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Common", meta=(Categories="GIS.Collection"))
|
||||
FGameplayTag CollectionTag;
|
||||
|
||||
/**
|
||||
* Restrictions applied to the collection for complex use cases.
|
||||
* 为复杂用例应用于集合的限制。
|
||||
* @details Restrictions perform pre-checks to determine if items can be added or removed.
|
||||
* @细节 限制会在道具操作前执行检查,以决定是否可以添加或移除道具。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Instanced, Category="Common")
|
||||
TArray<TObjectPtr<UGIS_ItemRestriction>> Restrictions;
|
||||
|
||||
/**
|
||||
* Options for handling overflow when an item cannot fit in the collection.
|
||||
* 当道具无法放入集合时处理溢出的选项。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category="Common")
|
||||
FGIS_ItemOverflowOptions OverflowOptions;
|
||||
|
||||
/**
|
||||
* Gets the class for instantiating the collection.
|
||||
* 获取用于实例化集合的类。
|
||||
* @return The collection instance class. 集合实例类。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Common")
|
||||
virtual TSubclassOf<UGIS_ItemCollection> GetCollectionInstanceClass() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Base class for a normal item collection.
|
||||
* 常规道具集合的基类。
|
||||
*/
|
||||
UCLASS(BlueprintType, DefaultToInstanced, EditInlineNew, CollapseCategories, DisplayName="GIS Collection (Normal)")
|
||||
class GENERICINVENTORYSYSTEM_API UGIS_ItemCollection : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
friend UGIS_ItemCollectionDefinition;
|
||||
friend UGIS_InventorySystemComponent;
|
||||
friend FGIS_ItemStackContainer;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Helper struct to lock notifications during collection updates.
|
||||
* 用于在集合更新期间锁定通知的辅助结构体。
|
||||
*/
|
||||
struct GIS_CollectionNotifyLocker
|
||||
{
|
||||
/**
|
||||
* Constructor that locks notifications for the collection.
|
||||
* 为集合锁定通知的构造函数。
|
||||
* @param InItemCollection The collection to lock notifications for. 要锁定通知的集合。
|
||||
*/
|
||||
GIS_CollectionNotifyLocker(UGIS_ItemCollection& InItemCollection);
|
||||
|
||||
/**
|
||||
* Destructor that unlocks notifications.
|
||||
* 解锁通知的析构函数。
|
||||
*/
|
||||
~GIS_CollectionNotifyLocker();
|
||||
|
||||
/**
|
||||
* The collection being managed.
|
||||
* 被管理的集合。
|
||||
*/
|
||||
UGIS_ItemCollection& ItemCollection;
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor for the item collection.
|
||||
* 道具集合的构造函数。
|
||||
* @param ObjectInitializer The object initializer. 对象初始化器。
|
||||
*/
|
||||
UGIS_ItemCollection(const FObjectInitializer& ObjectInitializer);
|
||||
|
||||
//~UObject interface
|
||||
/**
|
||||
* Gets the properties that should be replicated for this object.
|
||||
* 获取需要为此对象复制的属性。
|
||||
* @param OutLifetimeProps Array to store the replicated properties. 存储复制属性的数组。
|
||||
*/
|
||||
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
|
||||
|
||||
/**
|
||||
* Calls a remote function on the object.
|
||||
* 在对象上调用远程函数。
|
||||
* @param Function The function to call. 要调用的函数。
|
||||
* @param Parms The function parameters. 函数参数。
|
||||
* @param OutParms The output parameters. 输出参数。
|
||||
* @param Stack The function call stack. 函数调用堆栈。
|
||||
* @return True if the function call was successful, false otherwise. 如果函数调用成功则返回true,否则返回false。
|
||||
*/
|
||||
virtual bool CallRemoteFunction(UFunction* Function, void* Parms, FOutParmRec* OutParms, FFrame* Stack) override;
|
||||
|
||||
/**
|
||||
* Gets the function call space for the object.
|
||||
* 获取对象的函数调用空间。
|
||||
* @param Function The function to query. 要查询的函数。
|
||||
* @param Stack The function call stack. 函数调用堆栈。
|
||||
* @return The call space identifier. 调用空间标识符。
|
||||
*/
|
||||
virtual int32 GetFunctionCallspace(UFunction* Function, FFrame* Stack) override;
|
||||
|
||||
/**
|
||||
* Checks if the object supports networking.
|
||||
* 检查对象是否支持网络。
|
||||
* @return True if networking is supported, false otherwise. 如果支持网络则返回true,否则返回false。
|
||||
*/
|
||||
virtual bool IsSupportedForNetworking() const override { return true; }
|
||||
//~End of UObject interface
|
||||
|
||||
/**
|
||||
* Checks if the collection is initialized.
|
||||
* 检查集合是否已初始化。
|
||||
* @return True if the collection is initialized, false otherwise. 如果集合已初始化则返回true,否则返回false。
|
||||
*/
|
||||
bool IsInitialized() const;
|
||||
|
||||
/**
|
||||
* Gets the inventory that owns this collection.
|
||||
* 获取拥有此集合的库存。
|
||||
* @return The owning inventory, or nullptr if not set. 所属库存,如果未设置则返回nullptr。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GIS|ItemCollection")
|
||||
UGIS_InventorySystemComponent* GetOwningInventory() const { return OwningInventory; };
|
||||
|
||||
/**
|
||||
* Gets the unique tag of this collection.
|
||||
* 获取此集合的唯一标签。
|
||||
* @return The collection tag. 集合标签。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIS|ItemCollection")
|
||||
FGameplayTag GetCollectionTag() const { return CollectionTag; };
|
||||
|
||||
/**
|
||||
* Gets the unique ID of this collection.
|
||||
* 获取此集合的唯一ID。
|
||||
* @return The collection ID. 集合ID。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIS|ItemCollection")
|
||||
FGuid GetCollectionId() const { return CollectionId; };
|
||||
|
||||
/**
|
||||
* Gets the definition from which this collection was created.
|
||||
* 获取此集合的源定义。
|
||||
* @return The collection definition, or nullptr if not set. 集合定义,如果未设置则返回nullptr。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIS|ItemCollection")
|
||||
const UGIS_ItemCollectionDefinition* GetDefinition() const { return Definition; };
|
||||
|
||||
/**
|
||||
* Gets the display name of the collection.
|
||||
* 获取集合的显示名称。
|
||||
* @return The display name. 显示名称。
|
||||
*/
|
||||
FString GetCollectionName() const;
|
||||
|
||||
/**
|
||||
* Gets a debug string representation of the collection.
|
||||
* 获取集合的调试字符串表示。
|
||||
* @return The debug string. 调试字符串。
|
||||
*/
|
||||
FString GetDebugString() const;
|
||||
|
||||
#pragma region HasRegion
|
||||
/**
|
||||
* Checks if the collection contains a specified item.
|
||||
* 检查集合是否包含指定道具。
|
||||
* @param Item The item instance to check. 要检查的道具实例。
|
||||
* @param Amount The amount to check for. 要检查的数量。
|
||||
* @param SimilarItem Whether to check for similar items or exact matches. 是否检查相似道具或精确匹配。
|
||||
* @return True if the collection contains at least the specified amount, false otherwise. 如果集合包含至少指定数量则返回true,否则返回false。
|
||||
*/
|
||||
virtual bool HasItem(const UGIS_ItemInstance* Item, int32 Amount, bool SimilarItem = true) const;
|
||||
|
||||
#pragma endregion HasRegion
|
||||
|
||||
#pragma region Add&Remove
|
||||
/**
|
||||
* Adds an item to the collection.
|
||||
* 将道具添加到集合。
|
||||
* @param ItemInfo The item information to add. 要添加的道具信息。
|
||||
* @return The item that was not added or empty if added successfully. 未添加的道具,如果成功添加则为空。
|
||||
*/
|
||||
virtual FGIS_ItemInfo AddItem(const FGIS_ItemInfo& ItemInfo);
|
||||
|
||||
/**
|
||||
* Adds multiple items to the collection.
|
||||
* 将多个道具添加到集合。
|
||||
* @param ItemInfos The array of item information to add. 要添加的道具信息数组。
|
||||
* @return The number of items added. 添加的道具数量。
|
||||
*/
|
||||
virtual int32 AddItems(const TArray<FGIS_ItemInfo>& ItemInfos);
|
||||
|
||||
/**
|
||||
* Adds a specific amount of an item to the collection.
|
||||
* 将指定数量的道具添加到集合。
|
||||
* @param Item The item instance to add. 要添加的道具实例。
|
||||
* @param Amount The amount to add. 要添加的数量。
|
||||
* @return The item that was actually added. 实际添加的道具。
|
||||
*/
|
||||
FGIS_ItemInfo AddItem(UGIS_ItemInstance* Item, int32 Amount);
|
||||
|
||||
/**
|
||||
* Checks if an item can be added to the collection.
|
||||
* 检查道具是否可以添加到集合。
|
||||
* @param InItemInfo The item information to check. 要检查的道具信息。
|
||||
* @param OutItemInfo The item information that can be added (output). 可添加的道具信息(输出)。
|
||||
* @return True if at least one item can be added, false otherwise. 如果至少可以添加一个道具则返回true,否则返回false。
|
||||
*/
|
||||
virtual bool CanAddItem(const FGIS_ItemInfo& InItemInfo, FGIS_ItemInfo& OutItemInfo);
|
||||
|
||||
/**
|
||||
* Checks if an item can be stacked with an existing stack.
|
||||
* 检查道具是否可以与现有栈堆叠。
|
||||
* @param ItemInfo The item information to check. 要检查的道具信息。
|
||||
* @param ItemStack The item stack to check against. 要检查的道具栈。
|
||||
* @return True if the item can be stacked, false otherwise. 如果道具可以堆叠则返回true,否则返回false。
|
||||
*/
|
||||
virtual bool CanItemStack(const FGIS_ItemInfo& ItemInfo, const FGIS_ItemStack& ItemStack) const;
|
||||
|
||||
/**
|
||||
* Checks conditions for removing an item from the collection.
|
||||
* 检查从集合移除道具的条件。
|
||||
* @param ItemInfo The item information to remove. 要移除的道具信息。
|
||||
* @param OutItemInfo The item information that can be removed (output). 可移除的道具信息(输出)。
|
||||
* @return True if the item can be removed, false otherwise. 如果道具可以移除则返回true,否则返回false。
|
||||
*/
|
||||
virtual bool RemoveItemCondition(const FGIS_ItemInfo& ItemInfo, FGIS_ItemInfo& OutItemInfo);
|
||||
|
||||
/**
|
||||
* Removes an item from the collection.
|
||||
* 从集合中移除道具。
|
||||
* @param ItemInfo The item information to remove. 要移除的道具信息。
|
||||
* @return The item that was removed, or empty if nothing was removed. 移除的道具,如果未移除则为空。
|
||||
*/
|
||||
virtual FGIS_ItemInfo RemoveItem(const FGIS_ItemInfo& ItemInfo);
|
||||
|
||||
/**
|
||||
* Removes all items from the collection.
|
||||
* 从集合中移除所有道具。
|
||||
*/
|
||||
virtual void RemoveAll();
|
||||
|
||||
#pragma endregion Add&Remove
|
||||
|
||||
#pragma region GetReference
|
||||
/**
|
||||
* Gets item information by stack ID.
|
||||
* 通过栈ID获取道具信息。
|
||||
* @param InStackId The stack ID to query. 要查询的栈ID。
|
||||
* @param OutItemInfo The item information (output). 道具信息(输出)。
|
||||
* @return True if item information was found, false otherwise. 如果找到道具信息则返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIS|ItemCollection")
|
||||
bool GetItemInfoByStackId(FGuid InStackId, FGIS_ItemInfo& OutItemInfo) const;
|
||||
|
||||
/**
|
||||
* Finds item information by stack ID.
|
||||
* 通过栈ID查找道具信息。
|
||||
* @param InStackId The stack ID to query. 要查询的栈ID。
|
||||
* @param OutItemInfo The item information (output). 道具信息(输出)。
|
||||
* @return True if item information was found, false otherwise. 如果找到道具信息则返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure=false, Category="GIS|ItemCollection", meta=(ExpandBoolAsExecs="ReturnValue"))
|
||||
bool FindItemInfoByStackId(FGuid InStackId, FGIS_ItemInfo& OutItemInfo) const;
|
||||
|
||||
/**
|
||||
* Retrieves information about a specific item instance.
|
||||
* 获取指定道具实例的信息。
|
||||
* @param Item The item instance to query. 要查询的道具实例。
|
||||
* @param OutItemInfo The item information (output). 道具信息(输出)。
|
||||
* @return True if information was found, false otherwise. 如果找到信息则返回true,否则返回false。
|
||||
*/
|
||||
virtual bool GetItemInfo(const UGIS_ItemInstance* Item, FGIS_ItemInfo& OutItemInfo) const;
|
||||
|
||||
/**
|
||||
* Gets item information by item definition.
|
||||
* 通过道具定义获取道具信息。
|
||||
* @param ItemDefinition The item definition to query. 要查询的道具定义。
|
||||
* @param OutItemInfo The item information (output). 道具信息(输出)。
|
||||
* @return True if information was found, false otherwise. 如果找到信息则返回true,否则返回false。
|
||||
*/
|
||||
bool GetItemInfoByDefinition(const TSoftObjectPtr<UGIS_ItemDefinition>& ItemDefinition, FGIS_ItemInfo& OutItemInfo);
|
||||
|
||||
/**
|
||||
* Gets all item information for a specific item definition.
|
||||
* 获取指定道具定义的所有道具信息。
|
||||
* @param ItemDefinition The item definition to query. 要查询的道具定义。
|
||||
* @param OutItemInfos The array of item information (output). 道具信息数组(输出)。
|
||||
* @return True if information was found, false otherwise. 如果找到信息则返回true,否则返回false。
|
||||
*/
|
||||
bool GetItemInfosByDefinition(const TSoftObjectPtr<UGIS_ItemDefinition>& ItemDefinition, TArray<FGIS_ItemInfo>& OutItemInfos);
|
||||
|
||||
/**
|
||||
* Gets the amount of a specific item in the collection.
|
||||
* 获取集合中指定道具的数量。
|
||||
* @param Item The item instance to query. 要查询的道具实例。
|
||||
* @param SimilarItem Whether to count similar items or exact matches. 是否计数相似道具或精确匹配。
|
||||
* @return The amount of the item in the collection. 集合中的道具数量。
|
||||
*/
|
||||
int32 GetItemAmount(const UGIS_ItemInstance* Item, bool SimilarItem = true) const;
|
||||
|
||||
/**
|
||||
* Gets the amount of items with a specific definition in the collection.
|
||||
* 获取集合中具有指定定义的道具数量。
|
||||
* @param ItemDefinition The item definition to query. 要查询的道具定义。
|
||||
* @param CountStacks Whether to count the number of stacks instead of total amount. 是否计数栈数量而不是总数量。
|
||||
* @return The number of items or stacks in the collection. 集合中的道具或栈数量。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GIS|ItemCollection")
|
||||
int32 GetItemAmount(TSoftObjectPtr<UGIS_ItemDefinition> ItemDefinition, bool CountStacks = false) const;
|
||||
|
||||
/**
|
||||
* Gets all item information in the collection.
|
||||
* 获取集合中的所有道具信息。
|
||||
* @return Array of item information. 道具信息数组。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIS|ItemCollection")
|
||||
TArray<FGIS_ItemInfo> GetAllItemInfos() const;
|
||||
|
||||
/**
|
||||
* Gets all item instances in the collection.
|
||||
* 获取集合中的所有道具实例。
|
||||
* @return Array of item instances. 道具实例数组。
|
||||
*/
|
||||
TArray<UGIS_ItemInstance*> GetAllItems() const;
|
||||
|
||||
#pragma endregion GetReference
|
||||
|
||||
#pragma region Giver
|
||||
/**
|
||||
* Gives an item to another collection.
|
||||
* 将道具给予另一个集合。
|
||||
* @param ItemInfo The item information to give. 要给予的道具信息。
|
||||
* @param ItemCollection The target collection to receive the item. 接收道具的目标集合。
|
||||
* @return The item information that was given. 给予的道具信息。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category="GIS|ItemCollection")
|
||||
virtual FGIS_ItemInfo GiveItem(const FGIS_ItemInfo& ItemInfo, UGIS_ItemCollection* ItemCollection);
|
||||
|
||||
/**
|
||||
* Server function to give an item to another collection.
|
||||
* 服务器函数,将道具给予另一个集合。
|
||||
* @param ItemInfo The item information to give. 要给予的道具信息。
|
||||
* @param ItemCollection The target collection to receive the item. 接收道具的目标集合。
|
||||
*/
|
||||
UFUNCTION(Server, Reliable, BlueprintCallable, Category="GIS|ItemCollection")
|
||||
void ServerGiveItem(const FGIS_ItemInfo& ItemInfo, UGIS_ItemCollection* ItemCollection);
|
||||
|
||||
/**
|
||||
* Implementation of ServerGiveItem.
|
||||
* ServerGiveItem 的实现。
|
||||
* @param ItemInfo The item information to give. 要给予的道具信息。
|
||||
* @param ItemCollection The target collection to receive the item. 接收道具的目标集合。
|
||||
*/
|
||||
virtual void ServerGiveItem_Implementation(const FGIS_ItemInfo& ItemInfo, UGIS_ItemCollection* ItemCollection);
|
||||
|
||||
/**
|
||||
* Gives all items in this collection to another collection.
|
||||
* 将此集合中的所有道具给予另一个集合。
|
||||
* @param OtherItemCollection The target collection to receive the items. 接收道具的目标集合。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category="GIS|ItemCollection")
|
||||
virtual void GiveAllItems(UGIS_ItemCollection* OtherItemCollection);
|
||||
|
||||
/**
|
||||
* Server function to give all items to another collection.
|
||||
* 服务器函数,将所有道具给予另一个集合。
|
||||
* @param OtherItemCollection The target collection to receive the items. 接收道具的目标集合。
|
||||
*/
|
||||
UFUNCTION(Server, Reliable, BlueprintCallable, Category="GIS|ItemCollection")
|
||||
void ServerGiveAllItems(UGIS_ItemCollection* OtherItemCollection);
|
||||
|
||||
/**
|
||||
* Calculates how many items can fit given a limited number of additional stacks.
|
||||
* 计算在有限额外栈数下可以容纳的道具数量。
|
||||
* @param ItemInfo The item information to check. 要检查的道具信息。
|
||||
* @param AvailableAdditionalStacks The number of additional stacks allowed. 允许的额外栈数。
|
||||
* @return The number of items that can fit. 可容纳的道具数量。
|
||||
*/
|
||||
virtual int32 GetItemAmountFittingInLimitedAdditionalStacks(const FGIS_ItemInfo& ItemInfo, int32 AvailableAdditionalStacks) const;
|
||||
|
||||
#pragma endregion Giver
|
||||
|
||||
/**
|
||||
* Event triggered when the item collection is updated.
|
||||
* 道具集合更新时触发的事件。
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FGIS_Collection_UpdateSignature OnItemCollectionUpdate;
|
||||
|
||||
#pragma region ItemStacks
|
||||
/**
|
||||
* Gets all item stacks in the collection.
|
||||
* 获取集合中的所有道具栈。
|
||||
* @return Array of item stacks. 道具栈数组。
|
||||
*/
|
||||
const TArray<FGIS_ItemStack>& GetAllItemStacks() const;
|
||||
|
||||
/**
|
||||
* Gets the number of item stacks in the collection.
|
||||
* 获取集合中的道具栈数量。
|
||||
* @return The number of item stacks. 道具栈数量。
|
||||
*/
|
||||
int32 GetItemStacksNum() const;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Adds an item stack to the collection.
|
||||
* 将道具栈添加到集合。
|
||||
* @param Stack The item stack to add. 要添加的道具栈。
|
||||
*/
|
||||
virtual void AddItemStack(const FGIS_ItemStack& Stack);
|
||||
|
||||
/**
|
||||
* Removes an item stack at a specific index.
|
||||
* 在指定索引移除道具栈。
|
||||
* @param Idx The index of the stack to remove. 要移除的栈索引。
|
||||
* @param bRemoveFromCollection Whether to remove the item instance from the collection. 是否从集合中移除道具实例。
|
||||
*/
|
||||
virtual void RemoveItemStackAtIndex(int32 Idx, bool bRemoveFromCollection = true);
|
||||
|
||||
/**
|
||||
* Updates the amount of an item stack at a specific index.
|
||||
* 更新指定索引的道具栈数量。
|
||||
* @param Idx The index of the stack to update. 要更新的栈索引。
|
||||
* @param NewAmount The new amount for the stack. 栈的新数量。
|
||||
*/
|
||||
virtual void UpdateItemStackAmountAtIndex(int32 Idx, int32 NewAmount);
|
||||
|
||||
/**
|
||||
* Called before an item stack is added (server-side only).
|
||||
* 在添加道具栈之前调用(仅限服务器端)。
|
||||
* @param Stack The item stack to add. 要添加的道具栈。
|
||||
* @param Idx The index where the stack will be added. 栈将添加的索引。
|
||||
*/
|
||||
virtual void OnPreItemStackAdded(const FGIS_ItemStack& Stack, int32 Idx);
|
||||
|
||||
/**
|
||||
* Called when an item stack is added (client and server).
|
||||
* 道具栈添加时调用(客户端和服务器)。
|
||||
* @param Stack The added item stack. 添加的道具栈。
|
||||
*/
|
||||
virtual void OnItemStackAdded(const FGIS_ItemStack& Stack);
|
||||
|
||||
/**
|
||||
* Called when an item stack is removed (client and server).
|
||||
* 道具栈移除时调用(客户端和服务器)。
|
||||
* @param Stack The removed item stack. 移除的道具栈。
|
||||
*/
|
||||
virtual void OnItemStackRemoved(const FGIS_ItemStack& Stack);
|
||||
|
||||
/**
|
||||
* Called when an item stack is updated (client and server).
|
||||
* 道具栈更新时调用(客户端和服务器)。
|
||||
* @param Stack The updated item stack. 更新的道具栈。
|
||||
*/
|
||||
virtual void OnItemStackUpdated(const FGIS_ItemStack& Stack);
|
||||
|
||||
/**
|
||||
* Processes pending item stacks.
|
||||
* 处理待处理的道具栈。
|
||||
*/
|
||||
void ProcessPendingItemStacks();
|
||||
|
||||
/**
|
||||
* Store the stack id and position mapping within this collection.
|
||||
* 存储在此集合中Stack id到位置的映射关系。
|
||||
*/
|
||||
UPROPERTY(VisibleInstanceOnly, Category="ItemCollection", Transient, meta=(ForceInlineRow))
|
||||
TMap<FGuid, int32> StackToIdxMap;
|
||||
|
||||
/**
|
||||
* Store the stack id and position mapping within this collection.
|
||||
* 存储在此集合中Stack id到位置的映射关系。
|
||||
*/
|
||||
// UPROPERTY(VisibleInstanceOnly, Category="ItemCollection", Transient)
|
||||
// TMap<FGuid, int32> IdxToStackMap;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Temporary storage for pending item stacks.
|
||||
* 待处理道具栈的临时存储。
|
||||
*/
|
||||
UPROPERTY(VisibleInstanceOnly, Category="ItemCollection", Transient)
|
||||
TMap<FGuid, FGIS_ItemStack> PendingItemStacks;
|
||||
|
||||
|
||||
#pragma endregion
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Internal function to add an item to the collection.
|
||||
* 内部函数,将道具添加到集合。
|
||||
* @param ItemInfo The item information to add. 要添加的道具信息。
|
||||
* @return The item that was actually added. 实际添加的道具。
|
||||
*/
|
||||
virtual FGIS_ItemInfo AddInternal(const FGIS_ItemInfo& ItemInfo);
|
||||
|
||||
/**
|
||||
* Handles overflow when an item cannot be fully added.
|
||||
* 处理道具无法完全添加时的溢出。
|
||||
* @param OriginalItemInfo The original item information. 原始道具信息。
|
||||
* @param ItemInfoAdded The item information that was added. 已添加的道具信息。
|
||||
*/
|
||||
virtual void HandleItemOverflow(const FGIS_ItemInfo& OriginalItemInfo, const FGIS_ItemInfo& ItemInfoAdded);
|
||||
|
||||
/**
|
||||
* Internal function to remove an item from the collection.
|
||||
* 内部函数,从集合中移除道具。
|
||||
* @param ItemInfo The item information to remove. 要移除的道具信息。
|
||||
* @return The item that was removed. 移除的道具。
|
||||
*/
|
||||
virtual FGIS_ItemInfo RemoveInternal(const FGIS_ItemInfo& ItemInfo);
|
||||
|
||||
/**
|
||||
* Simplifies internal item removal logic.
|
||||
* 简化内部道具移除逻辑。
|
||||
* @param ItemInfo The item information to remove. 要移除的道具信息。
|
||||
* @param AlreadyRemoved The amount already removed (modified). 已移除的数量(可修改)。
|
||||
* @param StackIndex The stack index to remove from. 要移除的栈索引。
|
||||
* @return The item stack was actually removed. 实际被移除的道具栈。
|
||||
*/
|
||||
virtual FGIS_ItemStack SimpleInternalItemRemove(const FGIS_ItemInfo& ItemInfo, int32& AlreadyRemoved, int32 StackIndex);
|
||||
|
||||
/**
|
||||
* Indicates whether the collection is initialized.
|
||||
* 指示集合是否已初始化。
|
||||
*/
|
||||
UPROPERTY(Transient)
|
||||
bool bInitialized = false;
|
||||
|
||||
/**
|
||||
* Counter for locking notifications.
|
||||
* 用于锁定通知的计数器。
|
||||
*/
|
||||
int32 NotifyLocker = 0;
|
||||
|
||||
/**
|
||||
* Container for item stacks.
|
||||
* 道具栈的容器。
|
||||
*/
|
||||
UPROPERTY(VisibleInstanceOnly, Category="ItemCollection", Replicated, meta=(ShowOnlyInnerProperties))
|
||||
FGIS_ItemStackContainer Container;
|
||||
|
||||
/**
|
||||
* The unique tag of the item collection for querying in the inventory.
|
||||
* 道具集合的唯一标签,用于在库存中查询。
|
||||
*/
|
||||
UPROPERTY(Transient)
|
||||
FGameplayTag CollectionTag;
|
||||
|
||||
/**
|
||||
* The collection definition.
|
||||
* 集合定义。
|
||||
*/
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<const UGIS_ItemCollectionDefinition> Definition{nullptr};
|
||||
|
||||
/**
|
||||
* The unique ID of the collection.
|
||||
* 集合的唯一ID。
|
||||
*/
|
||||
UPROPERTY(Transient)
|
||||
FGuid CollectionId;
|
||||
|
||||
/**
|
||||
* The inventory that owns this collection.
|
||||
* 拥有此集合的库存。
|
||||
*/
|
||||
UPROPERTY()
|
||||
TObjectPtr<UGIS_InventorySystemComponent> OwningInventory;
|
||||
|
||||
/**
|
||||
* Sets the collection definition.
|
||||
* 设置集合定义。
|
||||
* @param NewDefinition The new collection definition. 新的集合定义。
|
||||
*/
|
||||
virtual void SetDefinition(const UGIS_ItemCollectionDefinition* NewDefinition);
|
||||
|
||||
/**
|
||||
* Sets the unique tag of the collection.
|
||||
* 设置集合的唯一标签。
|
||||
* @param NewTag The new collection tag. 新的集合标签。
|
||||
*/
|
||||
void SetCollectionTag(FGameplayTag NewTag);
|
||||
|
||||
/**
|
||||
* Sets the unique ID of the collection.
|
||||
* 设置集合的唯一ID。
|
||||
* @param NewId The new collection ID. 新的集合ID。
|
||||
*/
|
||||
void SetCollectionId(FGuid NewId);
|
||||
|
||||
/**
|
||||
* Sets the owning inventory for the collection.
|
||||
* 设置集合的所属库存。
|
||||
* @param NewInventory The new owning inventory. 新的所属库存。
|
||||
*/
|
||||
void SetInventory(UGIS_InventorySystemComponent* NewInventory) { OwningInventory = NewInventory; };
|
||||
};
|
||||
@@ -0,0 +1,133 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GIS_ItemCollection.h"
|
||||
#include "GIS_ItemMultiStackCollection.generated.h"
|
||||
|
||||
class UGIS_ItemMultiStackCollection;
|
||||
|
||||
/**
|
||||
* Definition for a multi-stack item collection.
|
||||
* 多栈道具集合的定义。
|
||||
*/
|
||||
UCLASS(BlueprintType)
|
||||
class UGIS_ItemMultiStackCollectionDefinition : public UGIS_ItemCollectionDefinition
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor for the multi-stack collection definition.
|
||||
* 多栈集合定义的构造函数。
|
||||
*/
|
||||
UGIS_ItemMultiStackCollectionDefinition();
|
||||
|
||||
/**
|
||||
* Gets the class for instantiating the collection.
|
||||
* 获取用于实例化集合的类。
|
||||
* @return The collection instance class. 集合实例类。
|
||||
*/
|
||||
virtual TSubclassOf<UGIS_ItemCollection> GetCollectionInstanceClass() const override;
|
||||
|
||||
/**
|
||||
* Default stack size limit for items without a StackSizeLimitAttribute.
|
||||
* 没有StackSizeLimitAttribute的道具的默认栈大小限制。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="StackSettings")
|
||||
int32 DefaultStackSizeLimit = 99;
|
||||
|
||||
/**
|
||||
* The integer attribute in the item definition to determine stack size (optional).
|
||||
* 道具定义中用于确定栈大小的整型属性(可选)。
|
||||
* @attention This is optional.
|
||||
* @注意 这是可选的。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="StackSettings")
|
||||
FGameplayTag StackSizeLimitAttribute;
|
||||
};
|
||||
|
||||
/**
|
||||
* An item collection that supports multiple stacks of the same item.
|
||||
* 支持相同道具多栈的道具集合。
|
||||
*/
|
||||
UCLASS(DisplayName="GIS Item Collection (Multi Stack)")
|
||||
class GENERICINVENTORYSYSTEM_API UGIS_ItemMultiStackCollection : public UGIS_ItemCollection
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor for the multi-stack item collection.
|
||||
* 多栈道具集合的构造函数。
|
||||
* @param ObjectInitializer The object initializer. 对象初始化器。
|
||||
*/
|
||||
UGIS_ItemMultiStackCollection(const FObjectInitializer& ObjectInitializer);
|
||||
|
||||
/**
|
||||
* Retrieves information about an item instance in the collection.
|
||||
* 获取集合中道具实例的信息。
|
||||
* @param Item The item instance to query. 要查询的道具实例。
|
||||
* @param OutItemInfo The item information (output). 道具信息(输出)。
|
||||
* @return True if information was found, false otherwise. 如果找到信息则返回true,否则返回false。
|
||||
*/
|
||||
virtual bool GetItemInfo(const UGIS_ItemInstance* Item, FGIS_ItemInfo& OutItemInfo) const override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Calculates how many items can fit given a limited number of additional stacks.
|
||||
* 计算在有限额外栈数下可以容纳的道具数量。
|
||||
* @param ItemInfo The item information to check. 要检查的道具信息。
|
||||
* @param AvailableAdditionalStacks The number of additional stacks allowed. 允许的额外栈数。
|
||||
* @return The number of items that can fit. 可容纳的道具数量。
|
||||
*/
|
||||
virtual int32 GetItemAmountFittingInLimitedAdditionalStacks(const FGIS_ItemInfo& ItemInfo, int32 AvailableAdditionalStacks) const override;
|
||||
|
||||
/**
|
||||
* Internal function to add an item to the collection.
|
||||
* 内部函数,将道具添加到集合。
|
||||
* @param ItemInfo The item information to add. 要添加的道具信息。
|
||||
* @return The item that was actually added. 实际添加的道具。
|
||||
*/
|
||||
virtual FGIS_ItemInfo AddInternal(const FGIS_ItemInfo& ItemInfo) override;
|
||||
|
||||
/**
|
||||
* Gets the maximum stack size for an item.
|
||||
* 获取道具的最大栈大小。
|
||||
* @param Item The item instance to query. 要查询的道具实例。
|
||||
* @return The maximum stack size for the item. 道具的最大栈大小。
|
||||
*/
|
||||
int32 GetMaxStackSize(UGIS_ItemInstance* Item) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Internal function to remove an item from the collection.
|
||||
* 内部函数,从集合中移除道具。
|
||||
* @param ItemInfo The item information to remove. 要移除的道具信息。
|
||||
* @return The item that was removed. 移除的道具。
|
||||
*/
|
||||
virtual FGIS_ItemInfo RemoveInternal(const FGIS_ItemInfo& ItemInfo) override;
|
||||
|
||||
/**
|
||||
* Removes items from a specific stack.
|
||||
* 从指定栈移除道具。
|
||||
* @param Index The index of the stack to remove from. 要移除的栈索引。
|
||||
* @param PrevStackIndexWithSameItem The previous stack index with the same item. 具有相同道具的前一个栈索引。
|
||||
* @param MaxStackSize The maximum stack size. 最大栈大小。
|
||||
* @param AmountToRemove The amount to remove (modified). 要移除的数量(可修改)。
|
||||
* @param AlreadyRemoved The amount already removed (modified). 已移除的数量(可修改)。
|
||||
* @return The stack index with the item. 包含道具的栈索引。
|
||||
*/
|
||||
int32 RemoveItemFromStack(int32 Index, int32 PrevStackIndexWithSameItem, int32 MaxStackSize, int32& AmountToRemove, int32& AlreadyRemoved);
|
||||
|
||||
/**
|
||||
* Increases the amount in a specific stack.
|
||||
* 增加指定栈中的数量。
|
||||
* @param StackIdx The stack index to increase. 要增加的栈索引。
|
||||
* @param MaxStackSize The maximum stack size. 最大栈大小。
|
||||
* @param AmountToAdd The amount to add (modified). 要添加的数量(可修改)。
|
||||
* @return The amount added to the stack. 添加到栈的数量。
|
||||
*/
|
||||
int32 IncreaseStackAmount(int32 StackIdx, int32 MaxStackSize, int32& AmountToAdd);
|
||||
};
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Items/GIS_ItemInfo.h"
|
||||
#include "GIS_ItemRestriction.generated.h"
|
||||
|
||||
/**
|
||||
* Base class for item restrictions used to limit item collection operations.
|
||||
* 用于限制道具集合操作的道具限制基类。
|
||||
* @details Subclasses can extend this to implement custom restrictions.
|
||||
* @细节 子类可以扩展此类以实现自定义限制。
|
||||
*/
|
||||
UCLASS(Blueprintable, BlueprintType, DefaultToInstanced, EditInlineNew, CollapseCategories, Abstract, Const)
|
||||
class GENERICINVENTORYSYSTEM_API UGIS_ItemRestriction : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Checks if an item can be added to the collection.
|
||||
* 检查道具是否可以添加到集合。
|
||||
* @param ItemInfo The item information to check (modifiable). 要检查的道具信息(可修改)。
|
||||
* @param ReceivingCollection The collection to add the item to. 要添加到的集合。
|
||||
* @return True if any valid item can be added, false otherwise. 如果可以添加任何有效道具则返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category=ItemRestriction)
|
||||
bool CanAddItem(UPARAM(ref) FGIS_ItemInfo& ItemInfo, UGIS_ItemCollection* ReceivingCollection) const;
|
||||
|
||||
/**
|
||||
* Checks if an item can be removed from the collection.
|
||||
* 检查道具是否可以从集合中移除。
|
||||
* @param ItemInfo The item information to check (modifiable). 要检查的道具信息(可修改)。
|
||||
* @return True if any valid item can be removed, false otherwise. 如果可以移除任何有效道具则返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category=ItemRestriction)
|
||||
bool CanRemoveItem(UPARAM(ref) FGIS_ItemInfo& ItemInfo) const;
|
||||
|
||||
/**
|
||||
* Gets the reason for rejecting an add operation.
|
||||
* 获取拒绝添加操作的原因。
|
||||
* @return The rejection reason text. 拒绝原因文本。
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, BlueprintNativeEvent, Category=ItemRestriction)
|
||||
FText GetRejectAddReason() const;
|
||||
|
||||
/**
|
||||
* Implementation of GetRejectAddReason.
|
||||
* GetRejectAddReason 的实现。
|
||||
* @return The rejection reason text. 拒绝原因文本。
|
||||
*/
|
||||
virtual FText GetRejectAddReason_Implementation() const { return RejectAddReason; }
|
||||
|
||||
/**
|
||||
* Gets the reason for rejecting a remove operation.
|
||||
* 获取拒绝移除操作的原因。
|
||||
* @return The rejection reason text. 拒绝原因文本。
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, BlueprintNativeEvent, Category=ItemRestriction)
|
||||
FText GetRejectRemoveReason() const;
|
||||
|
||||
/**
|
||||
* Implementation of GetRejectRemoveReason.
|
||||
* GetRejectRemoveReason 的实现。
|
||||
* @return The rejection reason text. 拒绝原因文本。
|
||||
*/
|
||||
virtual FText GetRejectRemoveReason_Implementation() const { return RejectAddReason; }
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Internal check for adding an item to the collection.
|
||||
* 检查道具是否可以添加到集合的内部函数。
|
||||
* @param ItemInfo The item information to check (modifiable). 要检查的道具信息(可修改)。
|
||||
* @param ReceivingCollection The collection to add the item to. 要添加到的集合。
|
||||
* @return True if the item can be added, false otherwise. 如果道具可以添加则返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, Category=ItemRestriction, meta=(DisplayName=CanAddItem))
|
||||
bool CanAddItemInternal(UPARAM(ref) FGIS_ItemInfo& ItemInfo, UGIS_ItemCollection* ReceivingCollection) const;
|
||||
|
||||
/**
|
||||
* Implementation of CanAddItemInternal.
|
||||
* CanAddItemInternal 的实现。
|
||||
* @param ItemInfo The item information to check (modifiable). 要检查的道具信息(可修改)。
|
||||
* @param ReceivingCollection The collection to add the item to. 要添加到的集合。
|
||||
* @return True if the item can be added, false otherwise. 如果道具可以添加则返回true,否则返回false。
|
||||
*/
|
||||
virtual bool CanAddItemInternal_Implementation(FGIS_ItemInfo& ItemInfo, UGIS_ItemCollection* ReceivingCollection) const;
|
||||
|
||||
/**
|
||||
* Internal check for removing an item from the collection.
|
||||
* 检查道具是否可以从集合移除的内部函数。
|
||||
* @param ItemInfo The item information to check (modifiable). 要检查的道具信息(可修改)。
|
||||
* @return True if the item can be removed, false otherwise. 如果道具可以移除则返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, Category=ItemRestriction, meta=(DisplayName=CanRemoveItem))
|
||||
bool CanRemoveItemInternal(UPARAM(ref) FGIS_ItemInfo& ItemInfo) const;
|
||||
|
||||
/**
|
||||
* Implementation of CanRemoveItemInternal.
|
||||
* CanRemoveItemInternal 的实现。
|
||||
* @param ItemInfo The item information to check (modifiable). 要检查的道具信息(可修改)。
|
||||
* @return True if the item can be removed, false otherwise. 如果道具可以移除则返回true,否则返回false。
|
||||
*/
|
||||
virtual bool CanRemoveItemInternal_Implementation(FGIS_ItemInfo& ItemInfo) const;
|
||||
|
||||
/**
|
||||
* The reason for rejecting an add operation.
|
||||
* 拒绝添加操作的原因。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=ItemRestriction)
|
||||
FText RejectAddReason;
|
||||
|
||||
/**
|
||||
* The reason for rejecting a remove operation.
|
||||
* 拒绝移除操作的原因。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=ItemRestriction)
|
||||
FText RejectRemoveReason;
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GIS_ItemRestriction.h"
|
||||
#include "GIS_ItemRestriction_StackSizeLimit.generated.h"
|
||||
|
||||
/**
|
||||
* Restricts the maximum amount of an item within a specified collection.
|
||||
* 限制指定集合中道具的最大数量。
|
||||
* @attention Not suitable for MultiStackItemCollection.
|
||||
* @注意 不适用于MultiStackItemCollection。
|
||||
*/
|
||||
UCLASS(NotBlueprintable, DisplayName=ItemRestriction_StackSizeLimit)
|
||||
class GENERICINVENTORYSYSTEM_API UGIS_ItemRestriction_StackSizeLimit final : public UGIS_ItemRestriction
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor for the stack size limit restriction.
|
||||
* 栈大小限制的构造函数。
|
||||
*/
|
||||
UGIS_ItemRestriction_StackSizeLimit();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Internal check for adding an item to the collection.
|
||||
* 检查道具是否可以添加到集合的内部函数。
|
||||
* @param ItemInfo The item information to check (modifiable). 要检查的道具信息(可修改)。
|
||||
* @param ReceivingCollection The collection to add the item to. 要添加到的集合。
|
||||
* @return True if the item can be added within the stack size limit, false otherwise. 如果道具可以在栈大小限制内添加则返回true,否则返回false。
|
||||
*/
|
||||
virtual bool CanAddItemInternal_Implementation(FGIS_ItemInfo& ItemInfo, UGIS_ItemCollection* ReceivingCollection) const override;
|
||||
|
||||
/**
|
||||
* Gets the stack size limit for a specific item.
|
||||
* 获取指定道具的栈大小限制。
|
||||
* @param Item The item instance to query. 要查询的道具实例。
|
||||
* @return The stack size limit for the item. 道具的栈大小限制。
|
||||
*/
|
||||
int32 GetStackSizeLimit(const UGIS_ItemInstance* Item) const;
|
||||
|
||||
/**
|
||||
* The default stack size limit for any items.
|
||||
* 针对所有道具的默认栈大小限制。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=ItemRestriction)
|
||||
int32 DefaultStackSizeLimit;
|
||||
|
||||
/**
|
||||
* The integer attribute in the item definition used to limit the stack size for non-unique items (unique items cannot stack).
|
||||
* 道具定义中用于限制非唯一道具栈大小的整型属性(唯一道具无法堆叠)。
|
||||
* @attention This is optional.
|
||||
* @注意 这是可选的。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=ItemRestriction)
|
||||
FGameplayTag StackSizeLimitAttributeTag;
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GIS_ItemRestriction.h"
|
||||
#include "GIS_ItemRestriction_StacksNumLimit.generated.h"
|
||||
|
||||
/**
|
||||
* Restricts the total number of stacks for an item collection.
|
||||
* 限制道具集合的总堆叠数量。
|
||||
*/
|
||||
UCLASS(NotBlueprintable, DisplayName=ItemRestriction_StacksNumLimit)
|
||||
class GENERICINVENTORYSYSTEM_API UGIS_ItemRestriction_StacksNumLimit final : public UGIS_ItemRestriction
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Checks if an item can be added to the collection based on stack limits.
|
||||
* 检查是否可以根据堆叠限制将道具添加到集合。
|
||||
* @param ItemInfo Information about the item to add. 要添加的道具信息。
|
||||
* @param ReceivingCollection The collection receiving the item. 接收道具的集合。
|
||||
* @return True if the item can be added, false otherwise. 如果可以添加道具则返回true,否则返回false。
|
||||
*/
|
||||
virtual bool CanAddItemInternal_Implementation(FGIS_ItemInfo& ItemInfo, UGIS_ItemCollection* ReceivingCollection) const override;
|
||||
|
||||
/**
|
||||
* Maximum number of stacks allowed in the collection.
|
||||
* 集合中允许的最大堆叠数量。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=ItemRestriction)
|
||||
int32 MaxStacksNum = 30;
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GIS_ItemRestriction.h"
|
||||
#include "GIS_ItemRestriction_TagRequirements.generated.h"
|
||||
|
||||
/**
|
||||
* Restricts items to those that satisfy a specific tag query for addition to a collection.
|
||||
* 限制只有满足特定标签查询的道具可以添加到集合。
|
||||
*/
|
||||
UCLASS(NotBlueprintable, DisplayName=ItemRestriction_TagRequirements)
|
||||
class GENERICINVENTORYSYSTEM_API UGIS_ItemRestriction_TagRequirements final : public UGIS_ItemRestriction
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Tag query that items must satisfy to be added to the collection.
|
||||
* 道具必须满足的标签查询才能添加到集合。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=ItemRestriction)
|
||||
FGameplayTagQuery TagQuery;
|
||||
|
||||
/**
|
||||
* Checks if an item satisfies the tag query for addition to the collection.
|
||||
* 检查道具是否满足标签查询以添加到集合。
|
||||
* @param ItemInfo Information about the item to add. 要添加的道具信息。
|
||||
* @param ReceivingCollection The collection receiving the item. 接收道具的集合。
|
||||
* @return True if the item can be added, false otherwise. 如果可以添加道具则返回true,否则返回false。
|
||||
*/
|
||||
virtual bool CanAddItemInternal_Implementation(FGIS_ItemInfo& ItemInfo, UGIS_ItemCollection* ReceivingCollection) const override;
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GIS_ItemRestriction.h"
|
||||
#include "GIS_ItemRestriction_UniqueOnly.generated.h"
|
||||
|
||||
/**
|
||||
* Restricts a collection to only accept unique items.
|
||||
* 限制集合仅接受唯一道具。
|
||||
* @details Prevents duplicate items from being added to the collection.
|
||||
* @细节 防止重复道具被添加到集合。
|
||||
*/
|
||||
UCLASS(NotBlueprintable, DisplayName=ItemRestriction_UniqueOnly)
|
||||
class GENERICINVENTORYSYSTEM_API UGIS_ItemRestriction_UniqueOnly final : public UGIS_ItemRestriction
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Checks if an item is unique before adding it to the collection.
|
||||
* 在将道具添加到集合之前检查其是否唯一。
|
||||
* @param ItemInfo Information about the item to add. 要添加的道具信息。
|
||||
* @param ReceivingCollection The collection receiving the item. 接收道具的集合。
|
||||
* @return True if the item is unique and can be added, false otherwise. 如果道具唯一且可添加则返回true,否则返回false。
|
||||
*/
|
||||
virtual bool CanAddItemInternal_Implementation(FGIS_ItemInfo& ItemInfo, UGIS_ItemCollection* ReceivingCollection) const override;
|
||||
};
|
||||
@@ -0,0 +1,418 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GIS_ItemCollection.h"
|
||||
#include "GIS_ItemSlotCollection.generated.h"
|
||||
|
||||
/**
|
||||
* A slot-based item collection definition, suitable for equipment or skill bars.
|
||||
* 基于槽的道具集合定义,适合用于装备或技能栏。
|
||||
* @details Stores items in a slot-to-item style, ideal for equipment, skill bars, etc.
|
||||
* @细节 以槽对应道具的方式存储,适合装备、技能栏等场景。
|
||||
*/
|
||||
UCLASS(BlueprintType)
|
||||
class GENERICINVENTORYSYSTEM_API UGIS_ItemSlotCollectionDefinition : public UGIS_ItemCollectionDefinition
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Gets the class for instantiating the collection.
|
||||
* 获取用于实例化集合的类。
|
||||
* @return The collection instance class. 集合实例类。
|
||||
*/
|
||||
virtual TSubclassOf<UGIS_ItemCollection> GetCollectionInstanceClass() const override;
|
||||
|
||||
/**
|
||||
* Checks if the specified slot index is valid within SlotDefinitions.
|
||||
* 检查指定槽索引在SlotDefinitions中是否有效。
|
||||
* @param SlotIndex The slot index to check. 要检查的槽索引。
|
||||
* @return True if the slot index is valid, false otherwise. 如果槽索引有效则返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIS|CollectionDefinition")
|
||||
virtual bool IsValidSlotIndex(int32 SlotIndex) const;
|
||||
|
||||
/**
|
||||
* Gets the index of a slot based on its tag within SlotDefinitions.
|
||||
* 根据槽标签获取SlotDefinitions中的槽索引。
|
||||
* @param SlotName The slot tag to query. 要查询的槽标签。
|
||||
* @return The index of the slot, or INDEX_NONE if not found. 槽的索引,如果未找到则返回INDEX_NONE。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIS|CollectionDefinition")
|
||||
virtual int32 GetIndexOfSlot(UPARAM(meta=(Categories="GIS.Slots"))
|
||||
const FGameplayTag& SlotName) const;
|
||||
|
||||
/**
|
||||
* Gets the slot tag for a given slot index within SlotDefinitions.
|
||||
* 根据槽索引获取SlotDefinitions中的槽标签。
|
||||
* @param SlotIndex The slot index to query. 要查询的槽索引。
|
||||
* @return The slot tag, or invalid tag if not found. 槽标签,如果未找到则返回无效标签。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIS|CollectionDefinition")
|
||||
virtual FGameplayTag GetSlotOfIndex(int32 SlotIndex) const;
|
||||
|
||||
/**
|
||||
* Gets all slot definitions within this collection definition.
|
||||
* 获取此集合定义中的所有槽定义。
|
||||
* @return Array of slot definitions. 槽定义数组。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIS|CollectionDefinition")
|
||||
const TArray<FGIS_ItemSlotDefinition>& GetSlotDefinitions() const;
|
||||
|
||||
/**
|
||||
* Gets the slot definition for a given slot index.
|
||||
* 获取指定槽索引的槽定义。
|
||||
* @param SlotIndex The slot index to query. 要查询的槽索引。
|
||||
* @param OutDefinition The slot definition (output). 槽定义(输出)。
|
||||
* @return True if the slot definition was found, false otherwise. 如果找到槽定义则返回true,否则返回false。
|
||||
*/
|
||||
bool GetSlotDefinition(int32 SlotIndex, FGIS_ItemSlotDefinition& OutDefinition) const;
|
||||
|
||||
/**
|
||||
* Gets the slot definition for a given slot tag.
|
||||
* 获取指定槽标签的槽定义。
|
||||
* @param SlotName The slot tag to query. 要查询的槽标签。
|
||||
* @param OutDefinition The slot definition (output). 槽定义(输出)。
|
||||
* @return True if the slot definition was found, false otherwise. 如果找到槽定义则返回true,否则返回false。
|
||||
*/
|
||||
bool GetSlotDefinition(const FGameplayTag& SlotName, FGIS_ItemSlotDefinition& OutDefinition) const;
|
||||
|
||||
/**
|
||||
* Gets the index of a slot within a specified group.
|
||||
* 获取指定槽在指定槽组内的索引。
|
||||
* @param GroupTag The group tag to query. 要查询的组标签。
|
||||
* @param SlotTag The slot tag to query. 要查询的槽标签。
|
||||
* @return The index of the slot within the group, or -1 if not found. 槽在组内的索引,如果未找到则返回-1。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIS|CollectionDefinition")
|
||||
int32 GetSlotIndexWithinGroup(UPARAM(meta=(Categories="GIS.Slots"))
|
||||
FGameplayTag GroupTag, FGameplayTag SlotTag) const;
|
||||
|
||||
/**
|
||||
* Whether newly added items replace existing items if they are not stackable.
|
||||
* 新添加的道具如果无法堆叠,是否替换现有道具。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Common", meta=(DisplayAfter="OverflowOptions"))
|
||||
bool bNewItemPriority = true;
|
||||
|
||||
/**
|
||||
* Whether replaced items are returned to the source collection of the new item.
|
||||
* 被替换的道具是否返回到新道具的源集合。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Common", meta=(EditCondition="bNewItemPriority"))
|
||||
bool bTryGivePrevItemToNewItemCollection = true;
|
||||
|
||||
/**
|
||||
* Defines all available item slots in the collection.
|
||||
* 定义集合中的所有可用道具槽。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="SlotSettings", meta=(TitleProperty="SlotName:{Name}"))
|
||||
TArray<FGIS_ItemSlotDefinition> SlotDefinitions;
|
||||
|
||||
/**
|
||||
* List of parent slot tags for grouping slots; only one slot per group can be active at a time.
|
||||
* 用于分组的父级槽标签列表,每个组同时只能激活一个槽。
|
||||
* @attention Used by the equipment system.
|
||||
* @注意 由装备系统使用。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="SlotSettings", meta=(AllowPrivateAccess=True, Categories="GIS.Slots"))
|
||||
TArray<FGameplayTag> SlotGroups;
|
||||
|
||||
/**
|
||||
* Cached mapping of slot indices to slot tags for faster access, updated on save in the editor.
|
||||
* 槽索引到槽标签的缓存映射,用于快速访问,在编辑器保存时更新。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Advanced", meta=(ForceInlineRow))
|
||||
TMap<int32, FGameplayTag> IndexToTagMap;
|
||||
|
||||
/**
|
||||
* Cached mapping of slot tags to slot indices for faster access, updated on save in the editor.
|
||||
* 槽标签到槽索引的缓存映射,用于快速访问,在编辑器保存时更新。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Advanced", meta=(ForceInlineRow))
|
||||
TMap<FGameplayTag, int32> TagToIndexMap;
|
||||
|
||||
/**
|
||||
* Cached grouping of slot definitions by slot groups, updated on save in the editor.
|
||||
* 按槽组分组的槽定义缓存,在编辑器保存时更新。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, Category="Advanced", meta=(ForceInlineRow))
|
||||
TMap<FGameplayTag, FGIS_ItemSlotGroup> SlotGroupMap;
|
||||
|
||||
#if WITH_EDITOR
|
||||
/**
|
||||
* Called before saving the object in the editor.
|
||||
* 在编辑器中保存对象前调用。
|
||||
* @param SaveContext The save context. 保存上下文。
|
||||
*/
|
||||
virtual void PreSave(FObjectPreSaveContext SaveContext) override;
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* A slot-based item collection that organizes items in slots, each holding an item stack.
|
||||
* 基于槽的道具集合,按槽组织道具,每个槽可持有道具栈。
|
||||
*/
|
||||
UCLASS(BlueprintType, EditInlineNew, CollapseCategories, DisplayName="GIS Item Collection (Slotted)")
|
||||
class GENERICINVENTORYSYSTEM_API UGIS_ItemSlotCollection : public UGIS_ItemCollection
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
friend UGIS_ItemSlotCollectionDefinition;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor for the slot-based item collection.
|
||||
* 基于槽的道具集合的构造函数。
|
||||
* @param ObjectInitializer The object initializer. 对象初始化器。
|
||||
*/
|
||||
UGIS_ItemSlotCollection(const FObjectInitializer& ObjectInitializer);
|
||||
|
||||
/**
|
||||
* Gets the properties that should be replicated for this object.
|
||||
* 获取需要为此对象复制的属性。
|
||||
* @param OutLifetimeProps Array to store the replicated properties. 存储复制属性的数组。
|
||||
*/
|
||||
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
|
||||
|
||||
/**
|
||||
* Gets the definition of this slot-based collection.
|
||||
* 获取此基于槽的集合的定义。
|
||||
* @return The collection definition, or nullptr if not set. 集合定义,如果未设置则返回nullptr。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIS|ItemCollection")
|
||||
const UGIS_ItemSlotCollectionDefinition* GetMyDefinition() const;
|
||||
|
||||
/**
|
||||
* Adds an item to the collection, typically for unique items.
|
||||
* 将道具添加到集合,通常用于唯一道具。
|
||||
* @param ItemInfo The item information to add. 要添加的道具信息。
|
||||
* @return The item that was not added, the previously equipped item if replaced, or empty if added successfully. 未添加的道具、替换的先前道具或成功添加时为空。
|
||||
*/
|
||||
virtual FGIS_ItemInfo AddItem(const FGIS_ItemInfo& ItemInfo) override;
|
||||
|
||||
/**
|
||||
* Adds an item to the specified slot (authority only).
|
||||
* 将道具添加到指定槽(仅限权限)。
|
||||
* @param ItemInfo The item information to add. 要添加的道具信息。
|
||||
* @param SlotIndex The index of the slot to add the item to. 要添加到的槽索引。
|
||||
* @return The item that was actually added. 实际添加的道具。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category="GIS|ItemCollection")
|
||||
virtual FGIS_ItemInfo AddItem(const FGIS_ItemInfo& ItemInfo, int32 SlotIndex);
|
||||
|
||||
/**
|
||||
* Server function to add an item to the specified slot.
|
||||
* 服务器函数,将道具添加到指定槽。
|
||||
* @param ItemInfo The item information to add. 要添加的道具信息。
|
||||
* @param SlotIndex The index of the slot to add the item to. 要添加到的槽索引。
|
||||
*/
|
||||
UFUNCTION(Server, Reliable, BlueprintCallable, BlueprintAuthorityOnly, Category="GIS|ItemCollection")
|
||||
void ServerAddItem(const FGIS_ItemInfo& ItemInfo, int32 SlotIndex);
|
||||
|
||||
/**
|
||||
* Adds an item to the slot with the specified tag (authority only).
|
||||
* 将道具添加到指定标签的槽(仅限权限)。
|
||||
* @param ItemInfo The item information to add. 要添加的道具信息。
|
||||
* @param SlotName The tag of the slot to add the item to. 要添加到的槽标签。
|
||||
* @return The item that was actually added. 实际添加的道具。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category="GIS|ItemCollection")
|
||||
FGIS_ItemInfo AddItemBySlotName(const FGIS_ItemInfo& ItemInfo, FGameplayTag SlotName);
|
||||
|
||||
/**
|
||||
* Server function to add an item to the slot with the specified tag.
|
||||
* 服务器函数,将道具添加到指定标签的槽。
|
||||
* @param ItemInfo The item information to add. 要添加的道具信息。
|
||||
* @param SlotName The tag of the slot to add the item to. 要添加到的槽标签。
|
||||
*/
|
||||
UFUNCTION(Server, Reliable, BlueprintCallable, BlueprintAuthorityOnly, Category="GIS|ItemCollection")
|
||||
void ServerAddItemBySlotName(const FGIS_ItemInfo& ItemInfo, FGameplayTag SlotName);
|
||||
|
||||
/**
|
||||
* Removes an item from the collection.
|
||||
* 从集合中移除道具。
|
||||
* @param ItemInfo The item information to remove. 要移除的道具信息。
|
||||
* @return The item that was removed, or empty if nothing was removed. 移除的道具,如果未移除则为空。
|
||||
*/
|
||||
virtual FGIS_ItemInfo RemoveItem(const FGIS_ItemInfo& ItemInfo) override;
|
||||
|
||||
/**
|
||||
* Removes an item from the specified slot.
|
||||
* 从指定槽移除道具。
|
||||
* @param SlotIndex The index of the slot to remove the item from. 要移除道具的槽索引。
|
||||
* @param Amount The amount to remove (-1 to remove all). 要移除的数量(-1表示全部移除)。
|
||||
* @return The item information that was removed. 移除的道具信息。
|
||||
*/
|
||||
virtual FGIS_ItemInfo RemoveItem(int32 SlotIndex, int32 Amount = -1);
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIS|ItemCollection")
|
||||
bool IsItemFitWithSlot(const UGIS_ItemInstance* Item, int32 SlotIndex) const;
|
||||
|
||||
/**
|
||||
* Gets a suitable slot index for an incoming item.
|
||||
* 获取适合传入道具的槽索引。
|
||||
* @param Item The item instance to check. 要检查的道具实例。
|
||||
* @return A valid slot index, or INDEX_NONE if none is suitable. 有效槽索引,如果没有合适的槽则返回INDEX_NONE。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIS|ItemCollection")
|
||||
int32 GetTargetSlotIndex(const UGIS_ItemInstance* Item) const;
|
||||
|
||||
/**
|
||||
* Gets the item information at a specific slot by tag.
|
||||
* 通过槽标签获取指定槽的道具信息。
|
||||
* @param SlotTag The slot tag to query. 要查询的槽标签。
|
||||
* @param OutItemInfo The item information (output). 道具信息(输出)。
|
||||
* @return True if item information was found, false otherwise. 如果找到道具信息则返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIS|ItemCollection")
|
||||
virtual bool GetItemInfoAtSlot(FGameplayTag SlotTag, FGIS_ItemInfo& OutItemInfo) const;
|
||||
|
||||
/**
|
||||
* Finds the item information at a specific slot by tag.
|
||||
* 通过槽标签查找指定槽的道具信息。
|
||||
* @param SlotTag The slot tag to query. 要查询的槽标签。
|
||||
* @param OutItemInfo The item information (output). 道具信息(输出)。
|
||||
* @return True if item information was found, false otherwise. 如果找到道具信息则返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure=false, Category="GIS|ItemCollection", meta=(ExpandBoolAsExecs="ReturnValue"))
|
||||
virtual bool FindItemInfoAtSlot(FGameplayTag SlotTag, FGIS_ItemInfo& OutItemInfo) const;
|
||||
|
||||
/**
|
||||
* Gets the item stack at a specific slot by tag (commented out).
|
||||
* 通过槽标签获取指定槽的道具栈(已注释)。
|
||||
*/
|
||||
virtual bool GetItemStackAtSlot(FGameplayTag SlotTag, FGIS_ItemStack& OutItemStack) const;
|
||||
|
||||
/**
|
||||
* Finds the item stack at a specific slot by tag (commented out).
|
||||
* 通过槽标签查找指定槽的道具栈(已注释)。
|
||||
*/
|
||||
virtual bool FindItemStackAtSlot(FGameplayTag SlotTag, FGIS_ItemStack& OutItemStack) const;
|
||||
|
||||
/**
|
||||
* Gets the item information at a specific slot by index.
|
||||
* 通过槽索引获取指定槽的道具信息。
|
||||
* @param SlotIndex The slot index to query. 要查询的槽索引。
|
||||
* @return The item information at the slot. 槽中的道具信息。
|
||||
*/
|
||||
FGIS_ItemInfo GetItemInfoAtSlot(int32 SlotIndex) const;
|
||||
|
||||
/**
|
||||
* Gets the item stack at a specific slot by index.
|
||||
* 通过槽索引获取指定槽的道具栈。
|
||||
* @param SlotIndex The slot index to query. 要查询的槽索引。
|
||||
* @return The item stack at the slot. 槽中的道具栈。
|
||||
*/
|
||||
FGIS_ItemStack GetItemStackAtSlot(int32 SlotIndex) const;
|
||||
|
||||
/**
|
||||
* Gets the slot tag where an item is equipped.
|
||||
* 获取道具装备的槽标签。
|
||||
* @param Item The item instance to query. 要查询的道具实例。
|
||||
* @return The slot tag, or invalid if not equipped. 槽标签,如果未装备则返回无效标签。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIS|ItemCollection")
|
||||
FGameplayTag GetItemSlotName(const UGIS_ItemInstance* Item) const;
|
||||
|
||||
/**
|
||||
* Gets the slot index where an item is equipped.
|
||||
* 获取道具装备的槽索引。
|
||||
* @param Item The item instance to query. 要查询的道具实例。
|
||||
* @return The slot index, or -1 if not equipped. 槽索引,如果未装备则返回-1。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIS|ItemCollection")
|
||||
int32 GetItemSlotIndex(const UGIS_ItemInstance* Item) const;
|
||||
|
||||
/**
|
||||
* Internal function to add an item to a specific slot.
|
||||
* 内部函数,将道具添加到指定槽。
|
||||
* @param ItemInfo The item information to add. 要添加的道具信息。
|
||||
* @param SlotIndex The index of the slot to add the item to. 要添加到的槽索引。
|
||||
* @return The item that was actually added. 实际添加的道具。
|
||||
*/
|
||||
virtual FGIS_ItemInfo AddItemInternal(const FGIS_ItemInfo& ItemInfo, int32 SlotIndex);
|
||||
|
||||
/**
|
||||
* Converts a stack ID to a slot index.
|
||||
* 将栈ID转换为槽索引。
|
||||
* @param InStackId The stack ID to query. 要查询的栈ID。
|
||||
* @return The corresponding slot index, or -1 if not found. 对应的槽索引,如果未找到则返回-1。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIS|ItemCollection")
|
||||
int32 StackIdToSlotIndex(FGuid InStackId) const;
|
||||
|
||||
/**
|
||||
* Converts a slot index to a stack index.
|
||||
* 将槽索引转换为栈索引。
|
||||
* @param InSlotIndex The slot index to query. 要查询的槽索引。
|
||||
* @return The corresponding stack index, or -1 if not found. 对应的栈索引,如果未找到则返回-1。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIS|ItemCollection")
|
||||
int32 SlotIndexToStackIndex(int32 InSlotIndex) const;
|
||||
|
||||
/**
|
||||
* Converts a slot index to a stack ID.
|
||||
* 将槽索引转换为栈ID。
|
||||
* @param InSlotIndex The slot index to query. 要查询的槽索引。
|
||||
* @return The corresponding stack ID, or invalid if not found. 对应的栈ID,如果未找到则返回无效ID。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIS|ItemCollection")
|
||||
FGuid SlotIndexToStackId(int32 InSlotIndex) const;
|
||||
|
||||
/**
|
||||
* Mapping of slot indices to stack IDs for equipped items.
|
||||
* 槽索引到装备道具栈ID的映射。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="ItemCollection", ReplicatedUsing=OnRep_ItemsBySlot)
|
||||
TArray<FGuid> ItemBySlots;
|
||||
|
||||
UPROPERTY(VisibleInstanceOnly, Category="ItemCollection", Transient)
|
||||
TMap<int32, FGuid> SlotToStackMap;
|
||||
|
||||
/**
|
||||
* The definition of this slot-based collection.
|
||||
* 此基于槽的集合的定义。
|
||||
*/
|
||||
UPROPERTY()
|
||||
TObjectPtr<const UGIS_ItemSlotCollectionDefinition> MyDefinition;
|
||||
|
||||
/**
|
||||
* Called when the ItemBySlots array is replicated.
|
||||
* ItemBySlots数组复制时调用。
|
||||
*/
|
||||
UFUNCTION()
|
||||
void OnRep_ItemsBySlot();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Sets the collection definition.
|
||||
* 设置集合定义。
|
||||
* @param NewDefinition The new collection definition. 新的集合定义。
|
||||
*/
|
||||
virtual void SetDefinition(const UGIS_ItemCollectionDefinition* NewDefinition) override;
|
||||
|
||||
/**
|
||||
* Called before an item stack is added to the collection.
|
||||
* 在集合添加道具栈之前调用。
|
||||
* @param Stack The item stack to add. 要添加的道具栈。
|
||||
* @param Idx The index where the stack will be added. 栈将添加的索引。
|
||||
*/
|
||||
virtual void OnPreItemStackAdded(const FGIS_ItemStack& Stack, int32 Idx) override;
|
||||
|
||||
virtual void OnItemStackAdded(const FGIS_ItemStack& Stack) override;
|
||||
virtual void OnItemStackRemoved(const FGIS_ItemStack& Stack) override;
|
||||
|
||||
/**
|
||||
* Sets the amount for an item in a specific slot.
|
||||
* 在指定槽中设置道具数量。
|
||||
* @param ItemInfo The item information to set. 要设置的道具信息。
|
||||
* @param SlotIndex The slot index to set the item in. 要设置的槽索引。
|
||||
* @param RemovePreviousItem Whether to replace the previous item if the slot is at capacity. 如果槽已满,是否替换之前的道具。
|
||||
* @param ItemInfoAdded The item information that was set (output). 已设置的道具信息(输出)。
|
||||
* @return True if the item was set successfully, false otherwise. 如果道具设置成功则返回true,否则返回false。
|
||||
*/
|
||||
virtual bool SetItemAmount(const FGIS_ItemInfo& ItemInfo, int32 SlotIndex, bool RemovePreviousItem, FGIS_ItemInfo& ItemInfoAdded);
|
||||
};
|
||||
@@ -0,0 +1,103 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GIS_MixinTargetInterface.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "GIS_ItemFragment.generated.h"
|
||||
|
||||
class UGIS_ItemInstance;
|
||||
|
||||
/**
|
||||
* Base class for item fragments, which are data objects attached to item definitions.
|
||||
* 道具片段,即道具定义上附加的数据对象的基类。
|
||||
* @details Allows users to create custom item fragment classes for extending item functionality.
|
||||
* @细节 允许用户创建自定义道具片段类以扩展道具功能。
|
||||
*/
|
||||
UCLASS(BlueprintType, Blueprintable, DefaultToInstanced, EditInlineNew, Abstract, CollapseCategories, HideDropdown, Const)
|
||||
class GENERICINVENTORYSYSTEM_API UGIS_ItemFragment : public UObject, public IGIS_MixinTargetInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Called when an item instance is created, allowing fragment-specific initialization.
|
||||
* 在道具实例创建时调用,允许特定片段的初始化。
|
||||
* @param ItemInstance The item instance being created. 被创建的道具实例。
|
||||
*/
|
||||
virtual void OnInstanceCreated(UGIS_ItemInstance* ItemInstance) const
|
||||
{
|
||||
Bp_OnInstancedCrated(ItemInstance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the fragment's runtime data can be serialized.
|
||||
* 确定片段的运行时数据是否可以序列化。
|
||||
* @return True if the data is serializable, false otherwise. 如果数据可序列化则返回true,否则返回false。
|
||||
*/
|
||||
virtual bool IsMixinDataSerializable() const override;
|
||||
|
||||
/**
|
||||
* Retrieves the compatible data structure for serialization or replication.
|
||||
* 获取用于序列化或复制的兼容数据结构。
|
||||
* @return The script struct compatible with this fragment. 与此片段兼容的脚本结构。
|
||||
*/
|
||||
virtual TObjectPtr<const UScriptStruct> GetCompatibleMixinDataType() const override;
|
||||
|
||||
/**
|
||||
* Generate default runtime data for compatible data type.
|
||||
* 生成兼容的混合数据类型的默认值。
|
||||
* @param DefaultState The default value of compatible data. 兼容的数据结构默认值。
|
||||
* @return If has default value. 是否具备默认值?
|
||||
*/
|
||||
virtual bool MakeDefaultMixinData(FInstancedStruct& DefaultState) const override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Blueprint event for handling instance creation logic.
|
||||
* 处理实例创建逻辑的蓝图事件。
|
||||
* @param ItemInstance The item instance being created. 被创建的道具实例。
|
||||
*/
|
||||
UFUNCTION(BlueprintImplementableEvent, Category="ItemFragment", meta=(DisplayName="On Instance Created"))
|
||||
void Bp_OnInstancedCrated(UGIS_ItemInstance* ItemInstance) const;
|
||||
|
||||
/**
|
||||
* Returns the struct type for runtime data serialization or replication.
|
||||
* 返回运行时数据序列化或复制的结构类型。
|
||||
* @return The compatible runtime data struct type. 兼容的运行时数据结构类型。
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, BlueprintPure, Category="ItemFragment")
|
||||
const UScriptStruct* GetCompatibleStateType() const;
|
||||
|
||||
/**
|
||||
* Provide default values for fragment's runtime data type.
|
||||
* 针对运行时数据类型提供默认值。
|
||||
* @param DefaultState The default data. 默认数据
|
||||
* @return If has default value. 是否具备默认值。
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, BlueprintPure, Category="ItemFragment")
|
||||
bool MakeDefaultState(FInstancedStruct& DefaultState) const;
|
||||
|
||||
/**
|
||||
* Determines if the fragment's runtime data supports serialization to disk.
|
||||
* 确定片段的运行时数据是否支持序列化到磁盘。
|
||||
* @details Runtime data is replicated over the network, but serialization to disk is optional.
|
||||
* @细节 运行时数据通过网络复制,但序列化到磁盘是可选的。
|
||||
* @return True if the data supports serialization, false otherwise. 如果数据支持序列化则返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, BlueprintPure, Category="ItemFragment")
|
||||
bool IsStateSerializable() const;
|
||||
|
||||
public:
|
||||
#if WITH_EDITOR
|
||||
/**
|
||||
* Override this to add your custom data validation logic on save.
|
||||
* @note Only called on editor, not runtime.
|
||||
* @param OutMessage The output message.
|
||||
* @return return true if no any data validation errors.
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, BlueprintPure, Category="ItemFragment")
|
||||
bool FragmentDataValidation(FText& OutMessage) const;
|
||||
#endif
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GIS_ItemFragment.h"
|
||||
#include "Attributes/GIS_GameplayTagFloat.h"
|
||||
#include "Attributes/GIS_GameplayTagInteger.h"
|
||||
#include "GIS_ItemFragment_DynamicAttributes.generated.h"
|
||||
|
||||
/**
|
||||
* Item fragment for adding dynamic attributes to an item instance.
|
||||
* 为道具实例添加动态属性的道具片段。
|
||||
* @details Initial attributes are applied to the item instance upon creation.
|
||||
* @细节 初始属性在道具实例创建时应用。
|
||||
* @attention Use static attributes in the item definition for attributes that don't change at runtime.
|
||||
* @注意 对于运行时不更改的属性,请使用道具定义中的静态属性。
|
||||
*/
|
||||
UCLASS(DisplayName="Dynamic Attribute Settings", Category="BuiltIn")
|
||||
class GENERICINVENTORYSYSTEM_API UGIS_ItemFragment_DynamicAttributes : public UGIS_ItemFragment
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Array of initial float attributes applied to–
|
||||
|
||||
System: the item instance.
|
||||
* 应用于道具实例的初始浮点属性数组。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=Attribute, meta=(TitleProperty="{Tag} -> {Value}"))
|
||||
TArray<FGIS_GameplayTagFloat> InitialFloatAttributes;
|
||||
|
||||
/**
|
||||
* Array of initial integer attributes applied to the item instance.
|
||||
* 应用于道具实例的初始整型属性数组。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=Attribute, meta=(TitleProperty="{Tag} -> {Value}"))
|
||||
TArray<FGIS_GameplayTagInteger> InitialIntegerAttributes;
|
||||
|
||||
/**
|
||||
* Cached map for faster access to integer attributes.
|
||||
* 用于快速访问整型属性的缓存映射。
|
||||
*/
|
||||
UPROPERTY()
|
||||
TMap<FGameplayTag, int32> IntegerAttributeMap;
|
||||
|
||||
/**
|
||||
* Cached map for faster access to float attributes.
|
||||
* 用于快速访问浮点属性的缓存映射。
|
||||
*/
|
||||
UPROPERTY()
|
||||
TMap<FGameplayTag, float> FloatAttributeMap;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Initializes the item instance with dynamic attributes upon creation.
|
||||
* 在道具实例创建时使用动态属性进行初始化。
|
||||
* @param Instance The item instance to initialize. 要初始化的道具实例。
|
||||
*/
|
||||
virtual void OnInstanceCreated(UGIS_ItemInstance* Instance) const override;
|
||||
|
||||
/**
|
||||
* Retrieves the default value of a float attribute by tag.
|
||||
* 通过标签获取浮点属性的默认值。
|
||||
* @param AttributeTag The tag identifying the attribute. 标识属性的标签。
|
||||
* @return The default value of the float attribute. 浮点属性的默认值。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIS|ItemFragment|DynamicAttributes")
|
||||
float GetFloatAttributeDefault(FGameplayTag AttributeTag) const;
|
||||
|
||||
/**
|
||||
* Retrieves the default value of an integer attribute by tag.
|
||||
* 通过标签获取整型属性的默认值。
|
||||
* @param AttributeTag The tag identifying the attribute. 标识属性的标签。
|
||||
* @return The default value of the integer attribute. 整型属性的默认值。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIS|ItemFragment|DynamicAttributes")
|
||||
int32 GetIntegerAttributeDefault(FGameplayTag AttributeTag) const;
|
||||
|
||||
#if WITH_EDITOR
|
||||
/**
|
||||
* Called before saving the object to perform editor-specific operations.
|
||||
* 在保存对象之前调用以执行编辑器特定的操作。
|
||||
* @param SaveContext The context for the save operation. 保存操作的上下文。
|
||||
*/
|
||||
virtual void PreSave(FObjectPreSaveContext SaveContext) override;
|
||||
#endif
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GIS_EquipmentStructLibrary.h"
|
||||
#include "GIS_ItemFragment.h"
|
||||
#include "GIS_ItemFragment_Equippable.generated.h"
|
||||
|
||||
class UGIS_EquipmentInstance;
|
||||
|
||||
/**
|
||||
* Item fragment for defining equippable item behavior.
|
||||
* 定义可装备道具行为的道具片段。
|
||||
* @details Configures equipment instance and spawning behavior for equipped items.
|
||||
* @细节 配置装备实例和已装备道具的生成行为。
|
||||
*/
|
||||
UCLASS(DisplayName="Equippable Settings", Category="BuiltIn")
|
||||
class GENERICINVENTORYSYSTEM_API UGIS_ItemFragment_Equippable : public UGIS_ItemFragment
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor for the equippable item fragment.
|
||||
* 可装备道具片段的构造函数。
|
||||
*/
|
||||
UGIS_ItemFragment_Equippable();
|
||||
|
||||
/**
|
||||
* Specifies the equipment instance class handling equipment logic.
|
||||
* 指定处理装备逻辑的装备实例类。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=Equipment, NoClear, meta = (MustImplement = "/Script/GenericInventorySystem.GIS_EquipmentInterface", AllowAbstract = "false"))
|
||||
TSoftClassPtr<UObject> InstanceType;
|
||||
|
||||
/**
|
||||
* Indicates if the equipment instance is actor-based, set automatically based on InstanceType.
|
||||
* 指示装备实例是否基于Actor,根据InstanceType自动设置。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=Equipment, meta=(EditCondition="false"))
|
||||
bool bActorBased{false};
|
||||
|
||||
/**
|
||||
* Determines if the equipment instance is automatically activated upon equipping.
|
||||
* 确定装备实例在装备时是否自动激活。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=Equipment)
|
||||
bool bAutoActivate{false};
|
||||
|
||||
/**
|
||||
* List of actors to spawn on the pawn when the item is equipped, such as weapons or armor.
|
||||
* 在道具装备时在Pawn上生成的Actor列表,如武器或盔甲。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=Equipment, meta=(EditCondition="!bActorBased", EditConditionHides))
|
||||
TArray<FGIS_EquipmentActorToSpawn> ActorsToSpawn;
|
||||
|
||||
#if WITH_EDITOR
|
||||
/**
|
||||
* Called before saving the object to perform editor-specific operations.
|
||||
* 在保存对象之前调用以执行编辑器特定的操作。
|
||||
* @param SaveContext The context for the save operation. 保存操作的上下文。
|
||||
*/
|
||||
virtual void PreSave(FObjectPreSaveContext SaveContext) override;
|
||||
#endif
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GIS_CurrencyEntry.h"
|
||||
#include "GIS_ItemFragment.h"
|
||||
#include "GIS_ItemFragment_Shoppable.generated.h"
|
||||
|
||||
/**
|
||||
* Item fragment for defining shop-related properties of an item.
|
||||
* 定义道具商店相关属性的道具片段。
|
||||
* @details Specifies buy and sell prices for the item in various currencies.
|
||||
* @细节 指定道具在不同货币中的购买和出售价格。
|
||||
*/
|
||||
UCLASS(DisplayName="Shoppable Settings", Category="BuiltIn")
|
||||
class GENERICINVENTORYSYSTEM_API UGIS_ItemFragment_Shoppable : public UGIS_ItemFragment
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Initializes shop-related data for the item instance upon creation.
|
||||
* 在道具实例创建时初始化商店相关数据。
|
||||
* @param Instance The item instance to initialize. 要初始化的道具实例。
|
||||
*/
|
||||
virtual void OnInstanceCreated(UGIS_ItemInstance* Instance) const override;
|
||||
|
||||
/**
|
||||
* List of currencies and amounts required to purchase the item.
|
||||
* 购买道具所需的货币和金额列表。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category=Shoppable, meta=(TitleProperty="Definition"))
|
||||
TArray<FGIS_CurrencyEntry> BuyCurrencyAmounts;
|
||||
|
||||
/**
|
||||
* List of currencies and amounts received when selling the item.
|
||||
* 出售道具时获得的货币和金额列表。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category=Shoppable, meta=(TitleProperty="Definition"))
|
||||
TArray<FGIS_CurrencyEntry> SellCurrencyAmounts;
|
||||
};
|
||||
@@ -0,0 +1,188 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/Texture2D.h"
|
||||
#include "Styling/SlateBrush.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "GIS_CoreStructLibray.generated.h"
|
||||
|
||||
class UGIS_ItemInstance;
|
||||
class UGIS_ItemDefinition;
|
||||
|
||||
/**
|
||||
* Structure representing an item definition with an associated amount.
|
||||
* 表示道具定义及其关联数量的结构体。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICINVENTORYSYSTEM_API FGIS_ItemDefinitionAmount
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
FGIS_ItemDefinitionAmount();
|
||||
|
||||
|
||||
FGIS_ItemDefinitionAmount(TSoftObjectPtr<UGIS_ItemDefinition> InDefinition, int32 InAmount);
|
||||
|
||||
/**
|
||||
* The item definition.
|
||||
* 道具定义。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GIS")
|
||||
TSoftObjectPtr<UGIS_ItemDefinition> Definition;
|
||||
|
||||
/**
|
||||
* The amount of the item.
|
||||
* 道具数量。
|
||||
* @attention Minimum value is 1.
|
||||
* @注意 最小值为1。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GIS", meta=(ClampMin="1"))
|
||||
int32 Amount = 1;
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
|
||||
/**
|
||||
* Friendly name for displaying in the editor.
|
||||
* 在编辑器中显示的友好名称。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, Category=AlwaysHidden, Meta=(EditCondition=False, EditConditionHides))
|
||||
FString EditorFriendlyName;
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* Structure representing a default loadout with a tag and associated items.
|
||||
* 表示默认装备配置的结构体,包含标签和关联的道具。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICINVENTORYSYSTEM_API FGIS_DefaultLoadout
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* The gameplay tag for the loadout.
|
||||
* 装备配置的游戏标签。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GIS")
|
||||
FGameplayTag Tag;
|
||||
|
||||
/**
|
||||
* List of default items for the loadout.
|
||||
* 装备配置的默认道具列表。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GIS", meta=(TitleProperty="EditorFriendlyName"))
|
||||
TArray<FGIS_ItemDefinitionAmount> DefaultItems;
|
||||
};
|
||||
|
||||
/**
|
||||
* Structure defining options for handling item overflow in an inventory.
|
||||
* 定义库存中道具溢出处理选项的结构体。
|
||||
*/
|
||||
USTRUCT()
|
||||
struct GENERICINVENTORYSYSTEM_API FGIS_ItemOverflowOptions
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Whether to return overflow items to their source (if specified) when the inventory is full.
|
||||
* 当库存满时,是否将溢出的道具返回其来源(如果指定)。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category=Overflow)
|
||||
bool bReturnOverflow = true;
|
||||
|
||||
/**
|
||||
* Whether to send a rejection message when items cannot be added due to overflow.
|
||||
* 当道具因溢出无法添加时,是否发送拒绝消息。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category=Overflow)
|
||||
bool bSendRejectedMessage = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Structure representing an item slot definition, primarily used in item slot collections.
|
||||
* 表示道具槽定义的结构体,主要用于道具槽集合。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICINVENTORYSYSTEM_API FGIS_ItemSlotDefinition
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* The gameplay tag for the item slot.
|
||||
* 道具槽的游戏标签。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=ItemSlot, meta=(Categories="GIS.Slots"))
|
||||
FGameplayTag Tag;
|
||||
|
||||
/**
|
||||
* The icon for the item slot.
|
||||
* 道具槽的图标。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=ItemSlot)
|
||||
TSoftObjectPtr<UTexture2D> Icon;
|
||||
|
||||
/**
|
||||
* The Slate brush for the item slot icon.
|
||||
* 道具槽图标的Slate画刷。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=ItemSlot)
|
||||
FSlateBrush IconBrush;
|
||||
|
||||
/**
|
||||
* The display name of the item slot.
|
||||
* 道具槽的显示名称。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=ItemSlot)
|
||||
FText Name;
|
||||
|
||||
/**
|
||||
* The description of the item slot.
|
||||
* 道具槽的描述。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=ItemSlot)
|
||||
FText Desc;
|
||||
|
||||
/**
|
||||
* Gameplay tag query that item tags must match to be equipped in this slot.
|
||||
* 道具标签必须匹配的游戏标签查询,才能装备到此槽。
|
||||
* @attention If empty, no items can be equipped in this slot.
|
||||
* @注意 如果为空,则无法将任何道具装备到此槽。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=ItemSlot)
|
||||
FGameplayTagQuery TagQuery;
|
||||
|
||||
/**
|
||||
* Checks if an item can be equipped in this slot based on its tags.
|
||||
* 检查道具是否可以根据其标签装备到此槽。
|
||||
* @param Item The item instance to check. 要检查的道具实例。
|
||||
* @return True if the item matches the slot's tag query, false otherwise. 如果道具匹配槽的标签查询则返回true,否则返回false。
|
||||
*/
|
||||
bool MatchItem(const UGIS_ItemInstance* Item) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Structure representing a group of item slots with index-to-slot and slot-to-index mappings.
|
||||
* 表示一组道具槽的结构体,包含索引到槽和槽到索引的映射。
|
||||
*/
|
||||
USTRUCT()
|
||||
struct GENERICINVENTORYSYSTEM_API FGIS_ItemSlotGroup
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Mapping of indices to slot tags in the group.
|
||||
* 组内索引到槽标签的映射。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, Category=ItemSlot, meta=(ForceInlineRow))
|
||||
TMap<int32, FGameplayTag> IndexToSlotMap;
|
||||
|
||||
/**
|
||||
* Mapping of slot tags to indices in the group.
|
||||
* 组内槽标签到索引的映射。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, Category=ItemSlot, meta=(ForceInlineRow))
|
||||
TMap<FGameplayTag, int32> SlotToIndexMap;
|
||||
};
|
||||
@@ -0,0 +1,383 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Net/Serialization/FastArraySerializer.h"
|
||||
#include "Templates/SubclassOf.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "Runtime/Launch/Resources/Version.h"
|
||||
#if ENGINE_MINOR_VERSION < 5
|
||||
#include "InstancedStruct.h"
|
||||
#else
|
||||
#include "StructUtils/InstancedStruct.h"
|
||||
#endif
|
||||
#include "GIS_MixinContainer.generated.h"
|
||||
|
||||
/**
|
||||
* Record of mixin for serialization.
|
||||
* 用于序列化的混合数据记录。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICINVENTORYSYSTEM_API FGIS_MixinRecord
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* The asset path of target object.
|
||||
* 目标对象的资产路径。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category="GIS")
|
||||
FString TargetPath;
|
||||
|
||||
/**
|
||||
* The runtime state of this fragment.
|
||||
* 片段的运行时数据。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category="GIS")
|
||||
FInstancedStruct Data;
|
||||
|
||||
/**
|
||||
* Equality operator to compare mixin records.
|
||||
* 比较混合数据记录的相等性运算符。
|
||||
* @param Other The another mixin record to compare with. 要比较的其他Mixin记录。
|
||||
* @return True if the record's target path are equal, false otherwise. 如果目标路径相等则返回true,否则返回false。
|
||||
*/
|
||||
bool operator==(const FGIS_MixinRecord& Other) const;
|
||||
|
||||
/**
|
||||
* Checks if the mixin record is valid.
|
||||
* 检查Mixin记录是否有效。
|
||||
* @return True if the target path and data are valid, false otherwise. 如果片段类型和状态有效则返回true,否则返回false。
|
||||
*/
|
||||
bool IsValid() const;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Stores the mixed-in data of a const target object (attaches additional runtime data to a const object).
|
||||
* 针对常量目标对象存储其混合数据(将额外的运行时数据附加到常量对象)。
|
||||
* @details A wrapper of InstancedStruct, allowing storage of any struct data. For save games, ensure struct fields are serializable (marked with SaveGame).
|
||||
* @细节 这是实例化结构的包装,允许存储任意结构体数据。对于存档,应确保结构体字段标记为SaveGame并受虚幻序列化系统支持。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct FGIS_Mixin : public FFastArraySerializerItem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* The target object to which the data is attached.
|
||||
* 数据附加的目标对象。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Mixin")
|
||||
TObjectPtr<const UObject> Target;
|
||||
|
||||
/**
|
||||
* The mixed-in data attached to the target object.
|
||||
* 附加到目标对象的混合数据。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Mixin")
|
||||
FInstancedStruct Data;
|
||||
|
||||
/**
|
||||
* Timestamp of the last data change.
|
||||
* 数据最后更改的时间戳。
|
||||
*/
|
||||
UPROPERTY()
|
||||
float Timestamp;
|
||||
|
||||
/**
|
||||
* Timestamp of the last replication (not replicated).
|
||||
* 最后一次复制的时间戳(不复制)。
|
||||
*/
|
||||
UPROPERTY(NotReplicated)
|
||||
float LastReplicatedTimestamp;
|
||||
|
||||
/**
|
||||
* Default constructor for mixin.
|
||||
* 混合数据的默认构造函数。
|
||||
*/
|
||||
FGIS_Mixin()
|
||||
: FGIS_Mixin(nullptr, FInstancedStruct())
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for mixin with target class and data.
|
||||
* 使用目标类和数据构造混合数据。
|
||||
* @param InClass The target class for the mixin. 混合数据的目标类。
|
||||
* @param InData The instanced struct data to mix in. 要混合的实例化结构数据。
|
||||
*/
|
||||
FGIS_Mixin(const TSubclassOf<UObject>& InClass, const FInstancedStruct& InData)
|
||||
: Target(InClass), Data(InData)
|
||||
{
|
||||
Timestamp = 0.f;
|
||||
LastReplicatedTimestamp = 0.f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor for mixin.
|
||||
* 混合数据的析构函数。
|
||||
*/
|
||||
~FGIS_Mixin()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the mixin is valid.
|
||||
* 检查混合数据是否有效。
|
||||
* @return True if the mixin is valid (target and data are set), false otherwise. 如果混合数据有效(目标和数据已设置)则返回true,否则返回false。
|
||||
*/
|
||||
bool IsValid() const
|
||||
{
|
||||
return Target != nullptr && Data.IsValid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the mixin to its default state.
|
||||
* 将混合数据重置为默认状态。
|
||||
*/
|
||||
void Reset()
|
||||
{
|
||||
Target = nullptr;
|
||||
Data.Reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the hash value for the mixin entry.
|
||||
* 计算混合数据条目的哈希值。
|
||||
* @param Entry The mixin entry to hash. 要哈希的混合数据条目。
|
||||
* @return The hash value. 哈希值。
|
||||
*/
|
||||
friend uint32 GetTypeHash(const FGIS_Mixin& Entry);
|
||||
};
|
||||
|
||||
/**
|
||||
* Template specialization to enable copying for FGIS_Mixin.
|
||||
* 为 FGIS_Mixin 启用复制的模板特化。
|
||||
*/
|
||||
template <>
|
||||
struct TStructOpsTypeTraits<FGIS_Mixin> : TStructOpsTypeTraitsBase2<FGIS_Mixin>
|
||||
{
|
||||
enum
|
||||
{
|
||||
WithCopy = true
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Container for storing runtime data for various const objects within the owning object.
|
||||
* 用于存储拥有对象中多个常量对象的运行时数据的容器。
|
||||
* @details For example, ItemInstance uses this container to store runtime data for each fragment, as fragments themselves are const (cannot be modified at runtime). Only necessary runtime data is serialized/replicated instead of the entire fragments.
|
||||
* @细节 例如,ItemInstance 使用此容器存储每个片段的运行时数据,因为片段本身是常量(运行时无法修改)。仅序列化/复制必要的运行时数据,而不是整个片段。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICINVENTORYSYSTEM_API FGIS_MixinContainer : public FFastArraySerializer
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Default constructor for mixin container.
|
||||
* 混合数据容器的默认构造函数。
|
||||
*/
|
||||
FGIS_MixinContainer()
|
||||
: OwningObject(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for mixin container with an owning object.
|
||||
* 使用所属对象构造混合数据容器。
|
||||
* @param NewObject The object that owns this container. 拥有此容器的对象。
|
||||
*/
|
||||
explicit FGIS_MixinContainer(UObject* NewObject)
|
||||
: OwningObject(NewObject)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves data for a target class.
|
||||
* 获取目标类的数据。
|
||||
* @param TargetClass The class of the target object. 目标对象的类。
|
||||
* @param OutData The retrieved instanced struct data (output). 检索到的实例化结构数据(输出)。
|
||||
* @return True if data was found, false otherwise. 如果找到数据则返回true,否则返回false。
|
||||
*/
|
||||
// bool GetDataByTargetClass(const TSubclassOf<UObject>& TargetClass, FInstancedStruct& OutData) const;
|
||||
|
||||
/**
|
||||
* Retrieves data for a target object.
|
||||
* 获取目标对象的数据。
|
||||
* @param Target The target object which the data attached to. 数据所附加到的目标对象。
|
||||
* @param OutData The retrieved instanced struct data (output). 检索到的实例化结构数据(输出)。
|
||||
* @return True if data was found, false otherwise. 如果找到数据则返回true,否则返回false。
|
||||
*/
|
||||
bool GetDataByTarget(const UObject* Target, FInstancedStruct& OutData) const;
|
||||
|
||||
/**
|
||||
* Finds the index of a target object in the container.
|
||||
* 查找容器中目标对象的索引。
|
||||
* @param Target The target object to find. 要查找的目标对象。
|
||||
* @return The index of the target object, or INDEX_NONE if not found. 目标对象的索引,如果未找到则返回INDEX_NONE。
|
||||
*/
|
||||
int32 IndexOfTarget(const UObject* Target) const;
|
||||
|
||||
/**
|
||||
* Finds the index of a target class in the container.
|
||||
* 查找容器中目标类的索引。
|
||||
* @param TargetClass The class of the target object. 目标对象的类。
|
||||
* @return The index of the target class, or INDEX_NONE if not found. 目标类的索引,如果未找到则返回INDEX_NONE。
|
||||
*/
|
||||
int32 IndexOfTargetByClass(const TSubclassOf<UObject>& TargetClass) const;
|
||||
|
||||
/**
|
||||
* Sets data for a target object.
|
||||
* 为目标对象设置数据。
|
||||
* @param Target The target object. 目标对象。
|
||||
* @param Data The instanced struct data to set. 要设置的实例化结构数据。
|
||||
* @return The index of the updated or added entry. 更新或添加条目的索引。
|
||||
*/
|
||||
int32 SetDataForTarget(const TObjectPtr<const UObject>& Target, const FInstancedStruct& Data);
|
||||
|
||||
bool IsObjectLoadedFromDisk(const UObject* Object) const;
|
||||
|
||||
/**
|
||||
* Updates data for a target class.
|
||||
* 更新目标类的数据。
|
||||
* @param TargetClass The class of the target object. 目标对象的类。
|
||||
* @param Data The instanced struct data to update. 要更新的实例化结构数据。
|
||||
* @return The index of the updated entry, or INDEX_NONE if not found. 更新条目的索引,如果未找到则返回INDEX_NONE。
|
||||
*/
|
||||
int32 UpdateDataByTargetClass(const TSubclassOf<UObject>& TargetClass, const FInstancedStruct& Data);
|
||||
|
||||
/**
|
||||
* Removes data associated with a target class.
|
||||
* 移除与目标类关联的数据。
|
||||
* @param TargetClass The class of the target object. 目标对象的类。
|
||||
*/
|
||||
void RemoveDataByTargetClass(const TSubclassOf<UObject>& TargetClass);
|
||||
|
||||
/**
|
||||
* Checks if the data is compatible with the target object.
|
||||
* 检查数据是否与目标对象兼容。
|
||||
* @param Target The target object. 目标对象。
|
||||
* @param Data The instanced struct data to check. 要检查的实例化结构数据。
|
||||
* @return True if the data is compatible, false otherwise. 如果数据兼容则返回true,否则返回false。
|
||||
*/
|
||||
bool CheckCompatibility(const UObject* Target, const FInstancedStruct& Data) const;
|
||||
|
||||
/**
|
||||
* Retrieves all data stored in the container.
|
||||
* 获取容器中存储的所有数据。
|
||||
* @return Array of instanced struct data. 实例化结构数据的数组。
|
||||
*/
|
||||
TArray<FInstancedStruct> GetAllData() const;
|
||||
|
||||
/**
|
||||
* Retrieves all serializable mixin in the container.
|
||||
* 获取容器中所有可序列化的mixin
|
||||
* @return Array of serializable mixin. 可序列化的Mixin数组
|
||||
*/
|
||||
TArray<FGIS_Mixin> GetSerializableMixins() const;
|
||||
|
||||
TArray<FGIS_MixinRecord> GetSerializableMixinRecords() const;
|
||||
|
||||
void RestoreFromRecords(const TArray<FGIS_MixinRecord>& Records);
|
||||
|
||||
static TArray<FGIS_Mixin> ConvertRecordsToMixins(const TArray<FGIS_MixinRecord>& Records);
|
||||
|
||||
/**
|
||||
* Retrieves all serializable data stored in the container.
|
||||
* 获取容器中存储的所有可序列化数据。
|
||||
* @return Array of serializable instanced struct data. 可序列化实例化结构数据的数组。
|
||||
*/
|
||||
TArray<FInstancedStruct> GetAllSerializableData() const;
|
||||
|
||||
// -- Begin FFastArraySerializer implementation
|
||||
/**
|
||||
* Called after items are added during replication.
|
||||
* 复制期间在添加项目后调用。
|
||||
* @param AddedIndices The indices of added items. 添加项目的索引。
|
||||
* @param FinalSize The final size of the array after addition. 添加后数组的最终大小。
|
||||
*/
|
||||
void PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize);
|
||||
|
||||
/**
|
||||
* Called after items are changed during replication.
|
||||
* 复制期间在项目更改后调用。
|
||||
* @param ChangedIndices The indices of changed items. 更改项目的索引。
|
||||
* @param FinalSize The final size of the array after changes. 更改后数组的最终大小。
|
||||
*/
|
||||
void PostReplicatedChange(const TArrayView<int32> ChangedIndices, int32 FinalSize);
|
||||
|
||||
/**
|
||||
* Called before items are removed during replication.
|
||||
* 复制期间在移除项目前调用。
|
||||
* @param RemovedIndices The indices of removed items. 移除项目的索引。
|
||||
* @param FinalSize The final size of the array after removal. 移除后数组的最终大小。
|
||||
*/
|
||||
void PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize);
|
||||
|
||||
/**
|
||||
* Handles delta serialization for the mixin container.
|
||||
* 处理混合数据容器的增量序列化。
|
||||
* @param DeltaParams The serialization parameters. 序列化参数。
|
||||
* @return True if serialization was successful, false otherwise. 如果序列化成功则返回true,否则返回false。
|
||||
*/
|
||||
bool NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParams);
|
||||
// -- End FFastArraySerializer implementation
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Caches mixin data for faster access.
|
||||
* 缓存混合数据以加速访问。
|
||||
*/
|
||||
void CacheMixins();
|
||||
|
||||
/**
|
||||
* Updates data at a specific index.
|
||||
* 在指定索引更新数据。
|
||||
* @param Idx The index of the entry to update. 要更新的条目索引。
|
||||
* @param Data The instanced struct data to set. 要设置的实例化结构数据。
|
||||
* @return The index of the updated entry. 更新条目的索引。
|
||||
*/
|
||||
int32 UpdateDataAt(int32 Idx, const FInstancedStruct& Data);
|
||||
|
||||
/**
|
||||
* The object that owns this container.
|
||||
* 拥有此容器的对象。
|
||||
*/
|
||||
UPROPERTY()
|
||||
TObjectPtr<UObject> OwningObject;
|
||||
|
||||
/**
|
||||
* List of a target object to data pairs.
|
||||
* 目标对象到数据的键值对列表。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Mixin")
|
||||
TArray<FGIS_Mixin> Mixins;
|
||||
|
||||
/**
|
||||
* Cached map for faster access to mixin data (index may vary between client and server).
|
||||
* 用于加速访问混合数据的缓存映射(客户端和服务器的索引可能不同)。
|
||||
*/
|
||||
UPROPERTY(NotReplicated)
|
||||
TMap<TObjectPtr<const UObject>, int32> AcceleratedMap;
|
||||
|
||||
/**
|
||||
* Hash of the last cached state.
|
||||
* 最后缓存状态的哈希值。
|
||||
*/
|
||||
UPROPERTY()
|
||||
uint32 LastCachedHash = INDEX_NONE;
|
||||
};
|
||||
|
||||
/**
|
||||
* Template specialization to enable network delta serialization for the mixin container.
|
||||
* 为混合数据容器启用网络增量序列化的模板特化。
|
||||
*/
|
||||
template <>
|
||||
struct TStructOpsTypeTraits<FGIS_MixinContainer> : TStructOpsTypeTraitsBase2<FGIS_MixinContainer>
|
||||
{
|
||||
enum { WithNetDeltaSerializer = true };
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Runtime/Launch/Resources/Version.h"
|
||||
#if ENGINE_MINOR_VERSION < 5
|
||||
#include "InstancedStruct.h"
|
||||
#else
|
||||
#include "StructUtils/InstancedStruct.h"
|
||||
#endif
|
||||
#include "UObject/Interface.h"
|
||||
#include "GIS_MixinOwnerInterface.generated.h"
|
||||
|
||||
/**
|
||||
* Interface class for objects that own a mixin container.
|
||||
* 拥有混合数据容器的对象的接口类。
|
||||
*/
|
||||
UINTERFACE()
|
||||
class GENERICINVENTORYSYSTEM_API UGIS_MixinOwnerInterface : public UInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface for objects that own a mixin container, to be notified of mixin data changes.
|
||||
* 拥有混合数据容器的对象应实现的接口,用于接收混合数据变更通知。
|
||||
* @details For example, an item instance will implement this interface to get notified when data attached to different fragment classes is added, changed, or removed.
|
||||
* @细节 例如,道具实例将实现此接口,以在附加到不同片段类的数据被添加、更改或移除时收到通知。
|
||||
*/
|
||||
class GENERICINVENTORYSYSTEM_API IGIS_MixinOwnerInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Called when mixin data is added to the owning object.
|
||||
* 当混合数据被添加到拥有对象时调用。
|
||||
* @param Target The target object to which the data is attached. 数据附加到的对象。
|
||||
* @param Data The added instanced struct data. 添加的实例化结构数据。
|
||||
*/
|
||||
virtual void OnMixinDataAdded(const TObjectPtr<const UObject>& Target, const FInstancedStruct& Data) = 0;
|
||||
|
||||
/**
|
||||
* Called when mixin data is updated in the owning object.
|
||||
* 当拥有对象中的混合数据被更新时调用。
|
||||
* @param Target The target object to which the data is attached. 数据附加到的对象。
|
||||
* @param Data The updated instanced struct data. 更新的实例化结构数据。
|
||||
*/
|
||||
virtual void OnMixinDataUpdated(const TObjectPtr<const UObject>& Target, const FInstancedStruct& Data) = 0;
|
||||
|
||||
/**
|
||||
* Called when mixin data is removed from the owning object.
|
||||
* 当从拥有对象中移除混合数据时调用。
|
||||
* @param Target The target object to which the data was attached. 数据附加到的对象。
|
||||
* @param Data The removed instanced struct data. 移除的实例化结构数据。
|
||||
*/
|
||||
virtual void OnMixinDataRemoved(const TObjectPtr<const UObject>& Target, const FInstancedStruct& Data) = 0;
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Runtime/Launch/Resources/Version.h"
|
||||
#if ENGINE_MINOR_VERSION < 5
|
||||
#include "InstancedStruct.h"
|
||||
#else
|
||||
#include "StructUtils/InstancedStruct.h"
|
||||
#endif
|
||||
#include "UObject/Interface.h"
|
||||
#include "GIS_MixinTargetInterface.generated.h"
|
||||
|
||||
UINTERFACE(meta=(CannotImplementInterfaceInBlueprint))
|
||||
class UGIS_MixinTargetInterface : public UInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface for objects to handle data mixin functionality.
|
||||
* 处理数据混合功能的接口。
|
||||
* @details Defines methods for serialization and compatibility of mixin data.
|
||||
* @细节 定义了混合数据的序列化和兼容性方法。
|
||||
*/
|
||||
class GENERICINVENTORYSYSTEM_API IGIS_MixinTargetInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Determines if the mixin data can be serialized.
|
||||
* 确定混合数据是否可以序列化。
|
||||
* @return True if the data is serializable, false otherwise. 如果数据可序列化则返回true,否则返回false。
|
||||
*/
|
||||
virtual bool IsMixinDataSerializable() const = 0;
|
||||
|
||||
/**
|
||||
* Retrieves the compatible data structure for the mixin.
|
||||
* 获取混合数据的兼容数据结构。
|
||||
* @return The script struct compatible with the mixin data. 与混合数据兼容的脚本结构。
|
||||
*/
|
||||
virtual TObjectPtr<const UScriptStruct> GetCompatibleMixinDataType() const = 0;
|
||||
|
||||
/**
|
||||
* Generate default runtime data for compatible data type.
|
||||
* 生成兼容的混合数据类型的默认值。
|
||||
* @param DefaultState The default value of compatible data. 兼容的数据结构默认值。
|
||||
* @return If has default value. 是否具备默认值?
|
||||
*/
|
||||
virtual bool MakeDefaultMixinData(FInstancedStruct& DefaultState) const = 0;
|
||||
};
|
||||
@@ -0,0 +1,188 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "GIS_GameplayTagFloat.h"
|
||||
#include "GIS_GameplayTagInteger.h"
|
||||
#include "Engine/DataAsset.h"
|
||||
#include "GIS_ItemDefinition.generated.h"
|
||||
|
||||
class UGIS_ItemDefinitionSchema;
|
||||
class UGIS_ItemFragment;
|
||||
class UGIS_ItemInstance;
|
||||
class UTexture2D;
|
||||
|
||||
/**
|
||||
* An item definition is a data asset. Creating a new item definition is equivalent to creating a new instance of the item definition class.
|
||||
* Essentially, item definitions are static data that can incorporate item data fragments to achieve complex data structures.
|
||||
* 道具定义是一个数据资产,创建一个新道具定义相当于新建一个类型为道具定义的资产。道具定义本质上是静态数据,可以添加道具数据片段,以实现复杂的数据结构。
|
||||
*/
|
||||
UCLASS(BlueprintType, Const)
|
||||
class GENERICINVENTORYSYSTEM_API UGIS_ItemDefinition : public UPrimaryDataAsset
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor for the item definition.
|
||||
* 道具定义的构造函数。
|
||||
* @param ObjectInitializer The object initializer. 对象初始化器。
|
||||
*/
|
||||
UGIS_ItemDefinition(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
|
||||
|
||||
/**
|
||||
* Display name of the item on the UI.
|
||||
* 道具在UI上的显示名称。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=Display)
|
||||
FText DisplayName;
|
||||
|
||||
/**
|
||||
* Description of the item on the UI.
|
||||
* 道具在UI上的描述。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=Display)
|
||||
FText Description;
|
||||
|
||||
/**
|
||||
* Icon of the item on the UI.
|
||||
* 道具在UI上的图标。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=Display)
|
||||
TObjectPtr<UTexture2D> Icon;
|
||||
|
||||
/**
|
||||
* Indicates whether the item is unique, preventing it from being stacked.
|
||||
* 表示道具是否唯一,唯一道具不可堆叠。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=Common)
|
||||
bool bUnique;
|
||||
|
||||
/**
|
||||
* Gameplay tags associated with the item for querying, categorization, and validation.
|
||||
* 与道具关联的游戏标签,用于查询、分类和验证。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=Common, meta=(Categories="GIS.Item"))
|
||||
FGameplayTagContainer ItemTags;
|
||||
|
||||
/**
|
||||
* Static tag-to-float attributes for the item definition.
|
||||
* 道具定义的静态标签到浮点数的属性映射。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=Settings, meta=(TitleProperty="{Tag} -> {Value}", Categories="GIS.Attribute"))
|
||||
TArray<FGIS_GameplayTagFloat> StaticFloatAttributes;
|
||||
|
||||
/**
|
||||
* Static tag-to-integer attributes for the item definition.
|
||||
* 道具定义的静态标签到整数的属性映射。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=Settings, meta=(TitleProperty="{Tag} -> {Value}", Categories="GIS.Attribute"))
|
||||
TArray<FGIS_GameplayTagInteger> StaticIntegerAttributes;
|
||||
|
||||
/**
|
||||
* Array of item data fragments to form simple or complex data structures. No duplicate fragment types are allowed.
|
||||
* 数据片段数组,用于构成简单或复杂的道具数据结构。不允许重复的片段类型。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=Settings, Instanced, meta=(NoElementDuplicate))
|
||||
TArray<TObjectPtr<UGIS_ItemFragment>> Fragments;
|
||||
|
||||
/**
|
||||
* Gets a fragment by its class.
|
||||
* 通过类获取数据片段。
|
||||
* @param FragmentClass The class of the fragment to retrieve. 要检索的片段类。
|
||||
* @return The fragment instance, or nullptr if not found. 片段实例,如果未找到则返回nullptr。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIS|ItemDefinition")
|
||||
const UGIS_ItemFragment* GetFragment(TSubclassOf<UGIS_ItemFragment> FragmentClass) const;
|
||||
|
||||
/**
|
||||
* Checks if the item definition has a specific float attribute.
|
||||
* 检查道具定义是否具有特定的浮点属性。
|
||||
* @param AttributeTag The tag of the attribute to check. 要检查的属性标签。
|
||||
* @return True if the attribute exists, false otherwise. 如果属性存在则返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIS|ItemDefinition")
|
||||
bool HasFloatAttribute(FGameplayTag AttributeTag) const;
|
||||
|
||||
/**
|
||||
* Gets the value of a static float attribute.
|
||||
* 获取静态浮点属性的值。
|
||||
* @param AttributeTag The tag of the attribute to retrieve. 要检索的属性标签。
|
||||
* @return The value of the float attribute, or 0 if not found. 浮点属性的值,如果未找到则返回0。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIS|ItemDefinition")
|
||||
float GetFloatAttribute(FGameplayTag AttributeTag) const;
|
||||
|
||||
/**
|
||||
* Checks if the item definition has a specific integer attribute.
|
||||
* 检查道具定义是否具有特定的整数属性。
|
||||
* @param AttributeTag The tag of the attribute to check. 要检查的属性标签。
|
||||
* @return True if the attribute exists, false otherwise. 如果属性存在则返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIS|ItemDefinition")
|
||||
bool HasIntegerAttribute(FGameplayTag AttributeTag) const;
|
||||
|
||||
/**
|
||||
* Gets the value of a static integer attribute.
|
||||
* 获取静态整数属性的值。
|
||||
* @param AttributeTag The tag of the attribute to retrieve. 要检索的属性标签。
|
||||
* @return The value of the integer attribute, or 0 if not found. 整数属性的值,如果未找到则返回0。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIS|ItemDefinition")
|
||||
int32 GetIntegerAttribute(FGameplayTag AttributeTag) const;
|
||||
|
||||
/**
|
||||
* Template function to find a fragment by its class.
|
||||
* 模板函数,通过类查找数据片段。
|
||||
* @param ResultClass The class of the fragment to find. 要查找的片段类。
|
||||
* @return The fragment instance cast to the specified class, or nullptr if not found. 转换为指定类的片段实例,如果未找到则返回nullptr。
|
||||
*/
|
||||
template <typename ResultClass>
|
||||
const ResultClass* FindFragment() const
|
||||
{
|
||||
return static_cast<const ResultClass*>(GetFragment(ResultClass::StaticClass()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Cached map for faster access to integer attributes.
|
||||
* 用于快速访问整数属性的缓存映射。
|
||||
*/
|
||||
UPROPERTY()
|
||||
TMap<FGameplayTag, int32> IntegerAttributeMap;
|
||||
|
||||
/**
|
||||
* Cached map for faster access to float attributes.
|
||||
* 用于快速访问浮点属性的缓存映射。
|
||||
*/
|
||||
UPROPERTY()
|
||||
TMap<FGameplayTag, float> FloatAttributeMap;
|
||||
|
||||
/**
|
||||
* Finds a fragment in an item definition by its class.
|
||||
* 在道具定义中通过类查找数据片段。
|
||||
* @param ItemDefinition The item definition to query. 要查询的道具定义。
|
||||
* @param FragmentClass The class of the fragment to find. 要查找的片段类。
|
||||
* @return The fragment instance, or nullptr if not found. 片段实例,如果未找到则返回nullptr。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure=false, Category="GIS|ItemDefinition", meta=(DeterminesOutputType=FragmentClass))
|
||||
static const UGIS_ItemFragment* FindFragmentOfItemDefinition(TSoftObjectPtr<UGIS_ItemDefinition> ItemDefinition, TSubclassOf<UGIS_ItemFragment> FragmentClass);
|
||||
|
||||
#if WITH_EDITOR
|
||||
/**
|
||||
* Called before the item definition is saved in the editor.
|
||||
* 编辑器中道具定义保存前调用。
|
||||
* @param SaveContext The save context. 保存上下文。
|
||||
*/
|
||||
virtual void PreSave(FObjectPreSaveContext SaveContext) override;
|
||||
|
||||
/**
|
||||
* Validates the item definition's data in the editor.
|
||||
* 在编辑器中验证道具定义的数据。
|
||||
* @param Context The validation context. 验证上下文。
|
||||
* @return The result of the data validation. 数据验证的结果。
|
||||
*/
|
||||
virtual EDataValidationResult IsDataValid(class FDataValidationContext& Context) const override;
|
||||
#endif
|
||||
};
|
||||
@@ -0,0 +1,171 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "GIS_GameplayTagFloat.h"
|
||||
#include "GIS_GameplayTagInteger.h"
|
||||
#include "Engine/DataAsset.h"
|
||||
#include "GIS_ItemDefinitionSchema.generated.h"
|
||||
|
||||
class UGIS_ItemInstance;
|
||||
class UGIS_ItemDefinition;
|
||||
class UGIS_ItemFragment;
|
||||
|
||||
/**
|
||||
* Structure representing a validation entry for item definitions.
|
||||
* 表示道具定义验证条目的结构体。
|
||||
*/
|
||||
USTRUCT()
|
||||
struct GENERICINVENTORYSYSTEM_API FGIS_ItemDefinitionValidationEntry
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Gameplay tag query to match items for this validation.
|
||||
* 用于匹配此验证的游戏标签查询。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, Category = Schema, meta=(Categories="GIS.Item"))
|
||||
FGameplayTagQuery ItemTagQuery;
|
||||
|
||||
/**
|
||||
* The required item instance class for matching items.
|
||||
* 匹配道具所需的道具实例类。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, Category = Schema)
|
||||
TSoftClassPtr<UGIS_ItemInstance> InstanceClass;
|
||||
|
||||
/**
|
||||
* List of required fragment classes for the item definition.
|
||||
* 道具定义所需的片段类列表。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, Category = Schema)
|
||||
TArray<TSoftClassPtr<UGIS_ItemFragment>> RequiredFragments;
|
||||
|
||||
/**
|
||||
* List of forbidden fragment classes for the item definition.
|
||||
* 道具定义禁止的片段类列表。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, Category = Schema)
|
||||
TArray<TSoftClassPtr<UGIS_ItemFragment>> ForbiddenFragments;
|
||||
|
||||
/**
|
||||
* List of required float attributes with default values.
|
||||
* 必需的浮点属性列表,包含默认值。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, Category = Schema, meta=(TitleProperty="{Tag} -> {Value}"))
|
||||
TArray<FGIS_GameplayTagFloat> RequiredFloatAttributes;
|
||||
|
||||
/**
|
||||
* List of required integer attributes with default values.
|
||||
* 必需的整数属性列表,包含默认值。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, Category = Schema, meta=(TitleProperty="{Tag} -> {Value}"))
|
||||
TArray<FGIS_GameplayTagInteger> RequiredIntegerAttributes;
|
||||
|
||||
/**
|
||||
* Whether to enforce the bUnique property.
|
||||
* 是否强制执行 bUnique 属性。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, Category = Schema)
|
||||
bool bEnforceUnique = false;
|
||||
|
||||
/**
|
||||
* The required value for bUnique if enforced.
|
||||
* 如果强制执行 bUnique,则指定其值。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, Category = Schema, meta=(EditCondition="bEnforceUnique"))
|
||||
bool RequiredUniqueValue = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* The item definition schema is a data asset used in the editor for item definition data validation.
|
||||
* 道具定义模式是一种数据资产,用于在编辑器中进行道具定义的数据验证。
|
||||
*/
|
||||
UCLASS(NotBlueprintable)
|
||||
class GENERICINVENTORYSYSTEM_API UGIS_ItemDefinitionSchema : public UDataAsset
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Validates an item definition against the schema.
|
||||
* 针对模式验证道具定义。
|
||||
* @param Definition The item definition to validate. 要验证的道具定义。
|
||||
* @param OutError The error message if validation fails. 如果验证失败,则返回错误消息。
|
||||
* @return True if the validation passes, false otherwise. 如果验证通过则返回true,否则返回false。
|
||||
*/
|
||||
static bool TryValidateItemDefinition(const UGIS_ItemDefinition* Definition, FText& OutError);
|
||||
|
||||
/**
|
||||
* Performs pre-save validation and auto-fixes for an item definition.
|
||||
* 对道具定义进行保存前验证和自动修复。
|
||||
* @param Definition The item definition to validate and fix. 要验证和修复的道具定义。
|
||||
* @param OutError The error message if validation fails. 如果验证失败,则返回错误消息。
|
||||
*/
|
||||
static void TryPreSaveItemDefinition(UGIS_ItemDefinition* Definition, FText& OutError);
|
||||
|
||||
/**
|
||||
* Validates an item definition against the schema (instance method).
|
||||
* 针对模式验证道具定义(实例方法)。
|
||||
* @param Definition The item definition to validate. 要验证的道具定义。
|
||||
* @param OutError The error message if validation fails. 如果验证失败,则返回错误消息。
|
||||
* @return True if the validation passes, false otherwise. 如果验证通过则返回true,否则返回false。
|
||||
*/
|
||||
virtual bool TryValidate(const UGIS_ItemDefinition* Definition, FText& OutError) const;
|
||||
|
||||
/**
|
||||
* Performs pre-save validation and auto-fixes for an item definition (instance method).
|
||||
* 对道具定义进行保存前验证和自动修复(实例方法)。
|
||||
* @param Definition The item definition to validate and fix. 要验证和修复的道具定义。
|
||||
* @param OutError The error message if validation fails. 如果验证失败,则返回错误消息。
|
||||
*/
|
||||
virtual void TryPreSave(UGIS_ItemDefinition* Definition, FText& OutError) const;
|
||||
|
||||
#if WITH_EDITOR
|
||||
/**
|
||||
* Called before the schema is saved in the editor.
|
||||
* 编辑器中模式保存前调用。
|
||||
* @param SaveContext The save context. 保存上下文。
|
||||
*/
|
||||
virtual void PreSave(FObjectPreSaveContext SaveContext) override;
|
||||
|
||||
/**
|
||||
* Validates the schema's data in the editor.
|
||||
* 在编辑器中验证模式的数据。
|
||||
* @param Context The validation context. 验证上下文。
|
||||
* @return The result of the data validation. 数据验证的结果。
|
||||
*/
|
||||
virtual EDataValidationResult IsDataValid(class FDataValidationContext& Context) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Array of validation entries for the schema.
|
||||
* 模式的验证条目数组。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, Category = Schema)
|
||||
TArray<FGIS_ItemDefinitionValidationEntry> ValidationEntries;
|
||||
|
||||
/**
|
||||
* List of required fragment classes for all item definitions.
|
||||
* 所有道具定义所需的常规片段类列表。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, Category = Schema)
|
||||
TArray<TSoftClassPtr<UGIS_ItemFragment>> CommonRequiredFragments;
|
||||
|
||||
/**
|
||||
* The required parent tag for all item definitions' ItemTags.
|
||||
* 所有道具定义的 ItemTags 必须包含的父级标签。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, Category = Schema)
|
||||
FGameplayTag RequiredParentTag;
|
||||
|
||||
/**
|
||||
* Global fragment order for all item definitions.
|
||||
* 所有道具定义的全局片段排序。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, Category = Schema)
|
||||
TArray<TSoftClassPtr<UGIS_ItemFragment>> FragmentOrder;
|
||||
};
|
||||
@@ -0,0 +1,187 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Items/GIS_ItemStack.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "GIS_ItemInfo.generated.h"
|
||||
|
||||
struct FGIS_ItemStack;
|
||||
class UGIS_ItemCollection;
|
||||
class UGIS_ItemInstance;
|
||||
|
||||
/**
|
||||
* Item information is a temporary structure used to pass item-related information across different operations, such as its belonging collection, amount, and corresponding item instance.
|
||||
* 道具信息是一个临时的结构体,用于在不同的操作中传递道具相关信息,例如其所属集合、数量及对应的道具实例等。
|
||||
*/
|
||||
USTRUCT(BlueprintType, DisplayName="GIS Item Information")
|
||||
struct GENERICINVENTORYSYSTEM_API FGIS_ItemInfo
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Default constructor for item information.
|
||||
* 道具信息的默认构造函数。
|
||||
*/
|
||||
FGIS_ItemInfo();
|
||||
|
||||
/**
|
||||
* Constructor for item information with item, amount, and collection.
|
||||
* 使用道具、数量和集合构造道具信息。
|
||||
* @param InItem The item instance. 道具实例。
|
||||
* @param InAmount The amount of the item. 道具数量。
|
||||
* @param InCollection The item collection where the item originates. 道具来源的集合。
|
||||
*/
|
||||
FGIS_ItemInfo(UGIS_ItemInstance* InItem, int32 InAmount, UGIS_ItemCollection* InCollection);
|
||||
|
||||
/**
|
||||
* Constructor for item information with item, amount, collection, and stack ID.
|
||||
* 使用道具、数量、集合和堆栈ID构造道具信息。
|
||||
* @param InItem The item instance. 道具实例。
|
||||
* @param InAmount The amount of the item. 道具数量。
|
||||
* @param InCollection The item collection where the item originates. 道具来源的集合。
|
||||
* @param InStackId The stack ID associated with the item. 与道具关联的堆栈ID。
|
||||
*/
|
||||
FGIS_ItemInfo(UGIS_ItemInstance* InItem, int32 InAmount, UGIS_ItemCollection* InCollection, FGuid InStackId);
|
||||
|
||||
/**
|
||||
* Constructor for item information with item and amount.
|
||||
* 使用道具和数量构造道具信息。
|
||||
* @param InItem The item instance. 道具实例。
|
||||
* @param InAmount The amount of the item. 道具数量。
|
||||
*/
|
||||
FGIS_ItemInfo(UGIS_ItemInstance* InItem, int32 InAmount);
|
||||
|
||||
/**
|
||||
* Constructor for item information with item, amount, and collection ID.
|
||||
* 使用道具、数量和集合ID构造道具信息。
|
||||
* @param InItem The item instance. 道具实例。
|
||||
* @param InAmount The amount of the item. 道具数量。
|
||||
* @param InCollectionId The collection ID associated with the item. 与道具关联的集合ID。
|
||||
*/
|
||||
FGIS_ItemInfo(UGIS_ItemInstance* InItem, int32 InAmount, FGuid InCollectionId);
|
||||
|
||||
/**
|
||||
* Constructor for item information with item, amount, and collection tag.
|
||||
* 使用道具、数量和集合标签构造道具信息。
|
||||
* @param InItem The item instance. 道具实例。
|
||||
* @param InAmount The amount of the item. 道具数量。
|
||||
* @param InCollectionTag The collection tag associated with the item. 与道具关联的集合标签。
|
||||
*/
|
||||
FGIS_ItemInfo(UGIS_ItemInstance* InItem, int32 InAmount, FGameplayTag InCollectionTag);
|
||||
|
||||
/**
|
||||
* Constructor for item information by copying another info and changing the amount.
|
||||
* 通过复制其他道具信息并更改数量构造道具信息。
|
||||
* @param InAmount The new item amount. 新的道具数量。
|
||||
* @param OtherInfo The item info to copy. 要复制的道具信息。
|
||||
*/
|
||||
FGIS_ItemInfo(int32 InAmount, const FGIS_ItemInfo& OtherInfo);
|
||||
|
||||
FGIS_ItemInfo(int32 InAmount, int32 InIndex, const FGIS_ItemInfo& OtherInfo);
|
||||
|
||||
/**
|
||||
* Constructor for item information by copying another info with a new item and amount.
|
||||
* 通过复制其他道具信息并指定新道具和数量构造道具信息。
|
||||
* @param InItem The new item instance. 新的道具实例。
|
||||
* @param InAmount The new item amount. 新的道具数量。
|
||||
* @param OtherInfo The item info to copy. 要复制的道具信息。
|
||||
*/
|
||||
FGIS_ItemInfo(UGIS_ItemInstance* InItem, int32 InAmount, const FGIS_ItemInfo& OtherInfo);
|
||||
|
||||
/**
|
||||
* Constructor for item information from an item stack.
|
||||
* 从道具堆栈构造道具信息。
|
||||
* @param ItemStack The item stack to convert from. 要转换的道具堆栈。
|
||||
*/
|
||||
FGIS_ItemInfo(const FGIS_ItemStack& ItemStack);
|
||||
|
||||
/**
|
||||
* Gets a debug string representation of the item information.
|
||||
* 获取道具信息的调试字符串表示。
|
||||
* @return The debug string. 调试字符串。
|
||||
*/
|
||||
FString GetDebugString() const;
|
||||
|
||||
/**
|
||||
* Compares this item info with another for equality.
|
||||
* 将此道具信息与另一个比较以判断是否相等。
|
||||
* @param Other The other item info to compare with. 要比较的其他道具信息。
|
||||
* @return True if the item infos are equal, false otherwise. 如果道具信息相等则返回true,否则返回false。
|
||||
*/
|
||||
bool operator==(const FGIS_ItemInfo& Other) const;
|
||||
|
||||
/**
|
||||
* Checks if the item information is valid.
|
||||
* 检查道具信息是否有效。
|
||||
* @return True if the item info is valid, false otherwise. 如果道具信息有效则返回true,否则返回false。
|
||||
*/
|
||||
bool IsValid() const;
|
||||
|
||||
/**
|
||||
* Gets the inventory system component associated with the item collection.
|
||||
* 获取与道具集合关联的库存系统组件。
|
||||
* @return The inventory system component, or nullptr if not found. 库存系统组件,如果未找到则返回nullptr。
|
||||
*/
|
||||
UGIS_InventorySystemComponent* GetInventory() const;
|
||||
|
||||
/**
|
||||
* Static constant representing an invalid or empty item info.
|
||||
* 表示无效或空道具信息的静态常量。
|
||||
*/
|
||||
static FGIS_ItemInfo None;
|
||||
|
||||
/**
|
||||
* The item instance associated with this information, with context-dependent usage.
|
||||
* 与此信息关联的道具实例,用途因上下文而异。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GIS", meta=(DisplayName="Item Instance"))
|
||||
TObjectPtr<UGIS_ItemInstance> Item;
|
||||
|
||||
/**
|
||||
* The amount of the item instance in this information.
|
||||
* 此信息中道具实例的数量。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GIS", meta=(DisplayName="Item Amount"))
|
||||
int32 Amount;
|
||||
|
||||
/**
|
||||
* The collection instance where the information originates, with context-dependent usage.
|
||||
* 此信息的源集合,用途因上下文而异。
|
||||
* @attention For adding items, it specifies the source collection for overflow return; for retrieving items, it indicates the source collection.
|
||||
* @注意 添加道具时,指定溢出返回的源集合;取回道具时,表示来源集合。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GIS")
|
||||
TObjectPtr<UGIS_ItemCollection> ItemCollection;
|
||||
|
||||
/**
|
||||
* The stack ID associated with this information, with context-dependent usage.
|
||||
* 与此信息关联的堆栈ID,用途因上下文而异。
|
||||
* @attention For adding/removing items, it determines the target stack; for retrieving items, it indicates the source stack.
|
||||
* @注意 添加/移除道具时,用于确定目标堆栈;取回道具时,表示来源堆栈。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GIS")
|
||||
FGuid StackId;
|
||||
|
||||
/**
|
||||
* The collection ID associated with this information, with context-dependent usage.
|
||||
* 与此信息关联的集合ID,用途因上下文而异。
|
||||
* @attention For adding/removing items, it determines the target collection; for retrieving items, it is usually null.
|
||||
* @注意 添加/移除道具时,用于确定目标集合;取回道具时,通常为空。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GIS")
|
||||
FGuid CollectionId;
|
||||
|
||||
/**
|
||||
* The collection tag associated with this information, with context-dependent usage.
|
||||
* 与此信息关联的集合标签,用途因上下文而异。
|
||||
* @attention For adding/removing items, it determines the target collection (lower priority than CollectionId); for retrieving items, it is usually null.
|
||||
* @注意 添加/移除道具时,用于确定目标集合(优先级低于CollectionId);取回道具时,通常为空。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GIS", meta=(Categories="GIS.Collection"))
|
||||
FGameplayTag CollectionTag;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GIS")
|
||||
int32 Index{INDEX_NONE};
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user