Server-authoritative, data-driven item/inventory/interaction/carry/equipment/ world-slot/placement/crafting framework. 8 modules, compiles against UE 5.7. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
191 lines
5.9 KiB
C++
191 lines
5.9 KiB
C++
// 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<FItemFloatProperty> FloatProperties;
|
||
|
||
UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category = "Item|Runtime")
|
||
TArray<FItemIntProperty> IntProperties;
|
||
|
||
UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category = "Item|Runtime")
|
||
TArray<FItemBoolProperty> BoolProperties;
|
||
|
||
UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category = "Item|Runtime")
|
||
TArray<FItemStringProperty> 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<typename T>
|
||
static bool ArrayEqual(const TArray<T>& A, const TArray<T>& B)
|
||
{
|
||
if (A.Num() != B.Num()) { return false; }
|
||
for (const T& Elem : A) { if (!B.Contains(Elem)) { return false; } }
|
||
return true;
|
||
}
|
||
};
|