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>
166 lines
5.5 KiB
C++
166 lines
5.5 KiB
C++
// Copyright ExByte Studios. Item Interaction Ecosystem.
|
|
|
|
#include "ItemFeatureLibrary.h"
|
|
#include "ItemInventoryLog.h"
|
|
#include "ItemNativeTags.h"
|
|
#include "ItemDatabaseSubsystem.h"
|
|
#include "ItemAuxiliaryRows.h"
|
|
#include "InventoryComponent.h"
|
|
#include "Engine/World.h"
|
|
|
|
namespace
|
|
{
|
|
float GetWorldSeconds(const UObject* WorldContext)
|
|
{
|
|
const UWorld* World = GEngine ? GEngine->GetWorldFromContextObject(WorldContext, EGetWorldErrorMode::ReturnNull) : nullptr;
|
|
return World ? World->GetTimeSeconds() : 0.f;
|
|
}
|
|
}
|
|
|
|
bool UItemFeatureLibrary::UpdateLazyFeatures(const UObject* WorldContext, FItemInstanceData& Item, FName& OutTransformItemId)
|
|
{
|
|
OutTransformItemId = NAME_None;
|
|
|
|
const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(WorldContext);
|
|
if (!DB)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TArray<FItemFeatureRow> RottingRows;
|
|
DB->GetFeatureRows(Item.ItemId, ItemEcosystemTags::Feature_Rotting, RottingRows);
|
|
if (RottingRows.Num() == 0)
|
|
{
|
|
return false; // not perishable
|
|
}
|
|
|
|
float DecayPerHour = 0.f;
|
|
FName RottenItemId = NAME_None;
|
|
for (const FItemFeatureRow& Row : RottingRows)
|
|
{
|
|
if (Row.Param == FName("DecayRatePerHour")) { DecayPerHour = Row.GetFloat(); }
|
|
else if (Row.Param == FName("RottenItemId")) { RottenItemId = FName(*Row.Value); }
|
|
}
|
|
if (DecayPerHour <= 0.f)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const float Now = GetWorldSeconds(WorldContext);
|
|
const float Last = Item.RuntimeProperties.GetFloat(ItemEcosystemTags::Property_LastUpdateTime, Now);
|
|
float Freshness = Item.RuntimeProperties.GetFloat(ItemEcosystemTags::Property_Freshness, 1.f);
|
|
|
|
const float ElapsedHours = FMath::Max(0.f, Now - Last) / 3600.f;
|
|
Freshness = FMath::Clamp(Freshness - DecayPerHour * ElapsedHours, 0.f, 1.f);
|
|
|
|
Item.RuntimeProperties.SetFloat(ItemEcosystemTags::Property_Freshness, Freshness);
|
|
Item.RuntimeProperties.SetFloat(ItemEcosystemTags::Property_LastUpdateTime, Now);
|
|
|
|
if (Freshness <= 0.f)
|
|
{
|
|
Item.StateTags.AddTag(ItemEcosystemTags::State_Rotten);
|
|
if (!RottenItemId.IsNone())
|
|
{
|
|
OutTransformItemId = RottenItemId;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void UItemFeatureLibrary::UpdateInventoryFeatures(const UObject* WorldContext, UInventoryComponent* Inventory)
|
|
{
|
|
if (!Inventory || !Inventory->GetOwner() || !Inventory->GetOwner()->HasAuthority())
|
|
{
|
|
return;
|
|
}
|
|
|
|
TArray<FItemInstanceData> Items;
|
|
Inventory->GetAllItems(Items);
|
|
for (FItemInstanceData& Item : Items)
|
|
{
|
|
FName TransformId;
|
|
if (!UpdateLazyFeatures(WorldContext, Item, TransformId))
|
|
{
|
|
continue;
|
|
}
|
|
if (!TransformId.IsNone())
|
|
{
|
|
// Transform into the rotten variant, preserving quantity.
|
|
const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(WorldContext);
|
|
FItemInstanceData NewItem = DB ? DB->MakeItemInstance(TransformId, Item.Quantity) : FItemInstanceData(TransformId, Item.Quantity);
|
|
Inventory->RemoveItem(Item.InstanceId);
|
|
Inventory->AddItem(NewItem);
|
|
}
|
|
else
|
|
{
|
|
Inventory->UpdateItem(Item);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UItemFeatureLibrary::ForceRot(const UObject* WorldContext, FItemInstanceData& Item, FName& OutTransformItemId)
|
|
{
|
|
Item.RuntimeProperties.SetFloat(ItemEcosystemTags::Property_Freshness, 0.f);
|
|
Item.RuntimeProperties.SetFloat(ItemEcosystemTags::Property_LastUpdateTime, GetWorldSeconds(WorldContext));
|
|
Item.StateTags.AddTag(ItemEcosystemTags::State_Rotten);
|
|
|
|
OutTransformItemId = NAME_None;
|
|
if (const UItemDatabaseSubsystem* DB = UItemDatabaseSubsystem::Get(WorldContext))
|
|
{
|
|
TArray<FItemFeatureRow> Rows;
|
|
DB->GetFeatureRows(Item.ItemId, ItemEcosystemTags::Feature_Rotting, Rows);
|
|
for (const FItemFeatureRow& Row : Rows)
|
|
{
|
|
if (Row.Param == FName("RottenItemId") && !Row.Value.IsEmpty())
|
|
{
|
|
OutTransformItemId = FName(*Row.Value);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UItemFeatureLibrary::BreakItem(FItemInstanceData& Item)
|
|
{
|
|
Item.RuntimeProperties.SetFloat(ItemEcosystemTags::Property_Durability, 0.f);
|
|
Item.StateTags.AddTag(ItemEcosystemTags::State_Broken);
|
|
}
|
|
|
|
void UItemFeatureLibrary::DrainBattery(FItemInstanceData& Item, float DrainPerSecond, float Seconds)
|
|
{
|
|
const float Charge = Item.RuntimeProperties.GetFloat(ItemEcosystemTags::Property_BatteryCharge, 1.f);
|
|
Item.RuntimeProperties.SetFloat(ItemEcosystemTags::Property_BatteryCharge,
|
|
FMath::Clamp(Charge - DrainPerSecond * Seconds, 0.f, 1.f));
|
|
}
|
|
|
|
float UItemFeatureLibrary::AddFuel(FItemInstanceData& Item, float Amount, float MaxFuel)
|
|
{
|
|
const float Current = Item.RuntimeProperties.GetFloat(ItemEcosystemTags::Property_FuelAmount, 0.f);
|
|
const float NewValue = FMath::Clamp(Current + Amount, 0.f, MaxFuel);
|
|
Item.RuntimeProperties.SetFloat(ItemEcosystemTags::Property_FuelAmount, NewValue);
|
|
return NewValue - Current;
|
|
}
|
|
|
|
float UItemFeatureLibrary::TransferFuel(FItemInstanceData& From, FItemInstanceData& To, float Amount, float ToMaxFuel)
|
|
{
|
|
const float Available = From.RuntimeProperties.GetFloat(ItemEcosystemTags::Property_FuelAmount, 0.f);
|
|
const float ToCurrent = To.RuntimeProperties.GetFloat(ItemEcosystemTags::Property_FuelAmount, 0.f);
|
|
const float Room = FMath::Max(0.f, ToMaxFuel - ToCurrent);
|
|
const float Moved = FMath::Min3(Amount, Available, Room);
|
|
if (Moved <= 0.f)
|
|
{
|
|
return 0.f;
|
|
}
|
|
From.RuntimeProperties.SetFloat(ItemEcosystemTags::Property_FuelAmount, Available - Moved);
|
|
To.RuntimeProperties.SetFloat(ItemEcosystemTags::Property_FuelAmount, ToCurrent + Moved);
|
|
return Moved;
|
|
}
|
|
|
|
void UItemFeatureLibrary::SetWetness(FItemInstanceData& Item, float Wetness)
|
|
{
|
|
const float Clamped = FMath::Clamp(Wetness, 0.f, 1.f);
|
|
Item.RuntimeProperties.SetFloat(ItemEcosystemTags::Property_Wetness, Clamped);
|
|
if (Clamped > 0.5f) { Item.StateTags.AddTag(ItemEcosystemTags::State_Wet); }
|
|
else { Item.StateTags.RemoveTag(ItemEcosystemTags::State_Wet); }
|
|
}
|