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