Files
inventory-system-plugin/Source/ItemInventory/Private/ItemFeatureLibrary.cpp
Bonchellon 7f7e043a88 Initial commit: Item Interaction Ecosystem plugin (UE5.7)
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>
2026-06-22 20:49:56 +03:00

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); }
}