// Copyright ExByte Studios. Item Interaction Ecosystem. #include "ItemTransaction.h" #include "ItemInventoryLog.h" #include "InventoryComponent.h" FItemTransaction& FItemTransaction::Add(UInventoryComponent* Inventory, const FItemInstanceData& Item) { FItemTransactionStep Step; Step.Type = EItemTransactionStepType::AddItem; Step.Inventory = Inventory; Step.Item = Item; Step.Item.EnsureInstanceId(); Step.InstanceId = Step.Item.InstanceId; Steps.Add(Step); return *this; } FItemTransaction& FItemTransaction::Remove(UInventoryComponent* Inventory, const FGuid& InstanceId) { FItemTransactionStep Step; Step.Type = EItemTransactionStepType::RemoveItem; Step.Inventory = Inventory; Step.InstanceId = InstanceId; Steps.Add(Step); return *this; } FItemTransaction& FItemTransaction::Update(UInventoryComponent* Inventory, const FItemInstanceData& Item) { FItemTransactionStep Step; Step.Type = EItemTransactionStepType::UpdateItem; Step.Inventory = Inventory; Step.Item = Item; Step.InstanceId = Item.InstanceId; Steps.Add(Step); return *this; } FItemTransaction& FItemTransaction::Move(UInventoryComponent* Inventory, const FGuid& InstanceId, const FGuid& ToContainerId, int32 ToSlotIndex) { FItemTransactionStep Step; Step.Type = EItemTransactionStepType::MoveItem; Step.Inventory = Inventory; Step.InstanceId = InstanceId; Step.ToContainer = ToContainerId; Step.ToSlot = ToSlotIndex; Steps.Add(Step); return *this; } bool FItemTransaction::Validate() const { for (const FItemTransactionStep& Step : Steps) { UInventoryComponent* Inv = Step.Inventory.Get(); if (!Inv) { return false; } switch (Step.Type) { case EItemTransactionStepType::AddItem: { // At least one container must accept it. TArray Containers; Inv->GetContainers(Containers); bool bAccepts = false; for (const FInventoryContainer& C : Containers) { if (Inv->CanAcceptItem(Step.Item, C.ContainerId)) { bAccepts = true; break; } } if (!bAccepts) { return false; } break; } case EItemTransactionStepType::RemoveItem: case EItemTransactionStepType::UpdateItem: case EItemTransactionStepType::MoveItem: { FItemInstanceData Found; if (!Inv->FindItem(Step.InstanceId, Found) || Found.Lock.bLocked) { return false; } break; } } } return true; } bool FItemTransaction::Apply() { if (!Validate()) { return false; } for (FItemTransactionStep& Step : Steps) { UInventoryComponent* Inv = Step.Inventory.Get(); bool bOk = false; switch (Step.Type) { case EItemTransactionStepType::AddItem: { FItemInstanceData ToAdd = Step.Item; bOk = Inv->AddItem(ToAdd); break; } case EItemTransactionStepType::RemoveItem: { Inv->FindItem(Step.InstanceId, Step.UndoSnapshot); // capture for rollback bOk = Inv->RemoveItem(Step.InstanceId); break; } case EItemTransactionStepType::UpdateItem: { Inv->FindItem(Step.InstanceId, Step.UndoSnapshot); bOk = Inv->UpdateItem(Step.Item); break; } case EItemTransactionStepType::MoveItem: { Inv->FindItem(Step.InstanceId, Step.UndoSnapshot); Step.UndoFromContainer = Step.UndoSnapshot.CurrentContainerId; Step.UndoFromSlot = Step.UndoSnapshot.SlotIndex; bOk = Inv->MoveItem(Step.InstanceId, Step.UndoFromContainer, Step.ToContainer, Step.ToSlot); break; } } if (!bOk) { UE_LOG(LogItemInventory, Warning, TEXT("[Transaction %s] step failed; rolling back."), *TransactionId.ToString()); Rollback(); return false; } Step.bApplied = true; } return true; } void FItemTransaction::Rollback() { // Undo applied steps in reverse order. for (int32 i = Steps.Num() - 1; i >= 0; --i) { FItemTransactionStep& Step = Steps[i]; if (!Step.bApplied) { continue; } UInventoryComponent* Inv = Step.Inventory.Get(); if (!Inv) { continue; } switch (Step.Type) { case EItemTransactionStepType::AddItem: Inv->RemoveItem(Step.InstanceId); break; case EItemTransactionStepType::RemoveItem: if (Step.UndoSnapshot.IsValid()) { FItemInstanceData Restore = Step.UndoSnapshot; Inv->AddItem(Restore); } break; case EItemTransactionStepType::UpdateItem: if (Step.UndoSnapshot.IsValid()) { Inv->UpdateItem(Step.UndoSnapshot); } break; case EItemTransactionStepType::MoveItem: Inv->MoveItem(Step.InstanceId, Step.ToContainer, Step.UndoFromContainer, Step.UndoFromSlot); break; } Step.bApplied = false; } }