// Copyright ExByte Studios. Item Interaction Ecosystem. #pragma once #include "CoreMinimal.h" #include "GameplayTagContainer.h" #include "ItemRuntimeProperties.generated.h" /** * Tag->value pairs (section 4.4). * * The TЗ sketched these as TMaps, but UHT forbids TMap inside replicated structs, * and runtime properties must replicate (they live in FItemInstanceData). So they * are stored as small replicable arrays instead; the typed accessors below hide * that and behave like a map. N is tiny per item, so linear lookup is fine. */ USTRUCT(BlueprintType) struct FItemFloatProperty { GENERATED_BODY() UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category = "Item|Runtime") FGameplayTag Tag; UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category = "Item|Runtime") float Value = 0.f; bool operator==(const FItemFloatProperty& O) const { return Tag == O.Tag && Value == O.Value; } }; USTRUCT(BlueprintType) struct FItemIntProperty { GENERATED_BODY() UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category = "Item|Runtime") FGameplayTag Tag; UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category = "Item|Runtime") int32 Value = 0; bool operator==(const FItemIntProperty& O) const { return Tag == O.Tag && Value == O.Value; } }; USTRUCT(BlueprintType) struct FItemBoolProperty { GENERATED_BODY() UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category = "Item|Runtime") FGameplayTag Tag; UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category = "Item|Runtime") bool Value = false; bool operator==(const FItemBoolProperty& O) const { return Tag == O.Tag && Value == O.Value; } }; USTRUCT(BlueprintType) struct FItemStringProperty { GENERATED_BODY() UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category = "Item|Runtime") FGameplayTag Tag; UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category = "Item|Runtime") FString Value; bool operator==(const FItemStringProperty& O) const { return Tag == O.Tag && Value == O.Value; } }; /** * Dynamic, per-instance properties keyed by gameplay tag (section 4.4). * Only data differing from the definition defaults should be stored, to keep * replication/save payloads small. Typed accessors fall back when a key is absent. */ USTRUCT(BlueprintType) struct ITEMCORE_API FItemRuntimeProperties { GENERATED_BODY() UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category = "Item|Runtime") TArray FloatProperties; UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category = "Item|Runtime") TArray IntProperties; UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category = "Item|Runtime") TArray BoolProperties; UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category = "Item|Runtime") TArray StringProperties; bool IsEmpty() const { return FloatProperties.Num() == 0 && IntProperties.Num() == 0 && BoolProperties.Num() == 0 && StringProperties.Num() == 0; } // --- Float --- float GetFloat(const FGameplayTag& Tag, float Fallback = 0.f) const { for (const FItemFloatProperty& P : FloatProperties) { if (P.Tag == Tag) { return P.Value; } } return Fallback; } bool HasFloat(const FGameplayTag& Tag) const { for (const FItemFloatProperty& P : FloatProperties) { if (P.Tag == Tag) { return true; } } return false; } void SetFloat(const FGameplayTag& Tag, float Value) { for (FItemFloatProperty& P : FloatProperties) { if (P.Tag == Tag) { P.Value = Value; return; } } FloatProperties.Add({ Tag, Value }); } void RemoveFloat(const FGameplayTag& Tag) { FloatProperties.RemoveAll([&Tag](const FItemFloatProperty& P) { return P.Tag == Tag; }); } // --- Int --- int32 GetInt(const FGameplayTag& Tag, int32 Fallback = 0) const { for (const FItemIntProperty& P : IntProperties) { if (P.Tag == Tag) { return P.Value; } } return Fallback; } bool HasInt(const FGameplayTag& Tag) const { for (const FItemIntProperty& P : IntProperties) { if (P.Tag == Tag) { return true; } } return false; } void SetInt(const FGameplayTag& Tag, int32 Value) { for (FItemIntProperty& P : IntProperties) { if (P.Tag == Tag) { P.Value = Value; return; } } IntProperties.Add({ Tag, Value }); } // --- Bool --- bool GetBool(const FGameplayTag& Tag, bool Fallback = false) const { for (const FItemBoolProperty& P : BoolProperties) { if (P.Tag == Tag) { return P.Value; } } return Fallback; } bool HasBool(const FGameplayTag& Tag) const { for (const FItemBoolProperty& P : BoolProperties) { if (P.Tag == Tag) { return true; } } return false; } void SetBool(const FGameplayTag& Tag, bool Value) { for (FItemBoolProperty& P : BoolProperties) { if (P.Tag == Tag) { P.Value = Value; return; } } BoolProperties.Add({ Tag, Value }); } // --- String --- FString GetString(const FGameplayTag& Tag, const FString& Fallback = FString()) const { for (const FItemStringProperty& P : StringProperties) { if (P.Tag == Tag) { return P.Value; } } return Fallback; } bool HasString(const FGameplayTag& Tag) const { for (const FItemStringProperty& P : StringProperties) { if (P.Tag == Tag) { return true; } } return false; } void SetString(const FGameplayTag& Tag, const FString& Value) { for (FItemStringProperty& P : StringProperties) { if (P.Tag == Tag) { P.Value = Value; return; } } StringProperties.Add({ Tag, Value }); } bool operator==(const FItemRuntimeProperties& Other) const { return ArrayEqual(FloatProperties, Other.FloatProperties) && ArrayEqual(IntProperties, Other.IntProperties) && ArrayEqual(BoolProperties, Other.BoolProperties) && ArrayEqual(StringProperties, Other.StringProperties); } bool operator!=(const FItemRuntimeProperties& Other) const { return !(*this == Other); } private: template static bool ArrayEqual(const TArray& A, const TArray& B) { if (A.Num() != B.Num()) { return false; } for (const T& Elem : A) { if (!B.Contains(Elem)) { return false; } } return true; } };