From 71854495d24614541ea7007de96a000595aed77a Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 16 Jun 2026 02:35:13 +0100 Subject: [PATCH] Route inventory drag drop through checkpoints --- .voidbot/state/aetheria.cc | Bin 142922 -> 143398 bytes Aetheria.State.Verify/Program.cs | 34 +++-- Assets/Scripts/Gameplay/ActionGameManager.cs | 138 +++++++++++++++++++ Assets/Scripts/UI/Menu/InventoryPanel.cs | 104 +++++++------- docs/aetheria-perfect-machine-map.md | 19 ++- 5 files changed, 218 insertions(+), 77 deletions(-) diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index c152c0d3abea8df1facac34243ab151901dcf3d8..bf2f2765aeaa342013ce60cc96be00e143565821 100644 GIT binary patch delta 1203 zcmY*Z&yN&E6t-s&mc{4+$cDsZU%(iRGqbZSJFx4CtQ)d;5ZN&%doeX#Z@P==u4?MX z41JnN!a;9j68qwfl^s>@85#iTi^fqX!1?tMtf~T4pCaAbKsgq3tX!Vq#A)%&Y}Su(?=9T#8S)>mKQfa z4~`xFDNLA1p9Uw2|2BgYUrPmvk`Zdqn5rR_g-l1P0Z!soCo%&OGTneT4oZU|+}Fv# zg~re(`KKUkgyAZfgpyB#PB%vQYQs;&gf!qT(81#xE0P4%h+>(> zp}-MMrwBpk4d}B0f(k>IF&WZ%>_vs4FpedrQPy+85*5+GDB^>@i6Oeo5-gY)sktzq z65fEGBkFGViaDA=3!6jC40xG2ST!}K1)F41v&ZsEd?KDHGZtlAiO4A(uVsk{Ne^OA zX2mp4B4h+T44E?sl8GVF*CG;ffg?>SD_R<_kT;&-NV$YlCFS+B8lWuKrUEOmTK8F* zxsiIT;+Jl4aQyF@lGKY-@8v#~Eb_2^dNEzUH*^>Y8^ih#7fAH3Qptcw8>jo^ttnL; zAZE~a*6IB{Lb98NGQcsxh}lY_Isp|g&j&}p@b}cI&r*@(G$^f_0rWor`9n^qq2Q>S zULNk@VYgu=73$W{WGlIF8T;nhTXKiDpR63ZeoJHjNw@fx1@n{V|GYQ3G=BG!)wL38 z-LF3_+J?on{N5f2$!{Cwbcj{qWnb7nd5K9(TA|r3{EYS{s_MFGoz$~=@t6fKb}nAI z(3tCV+tZ(Z>CD!)WfAeocJBW0=AaqKivguK-?FvCsej delta 992 zcmZ9LJxmlq6vy2a69h~oA{2h`3Q$ary-(rpIuiV#AR4?13o^I+Zg_J~5Sz`;o44=%|G&5I<-y>G{lVQGb2wAZ z&y{moE0di|MyJj3)_ZgNIGD@o;N@0)nx6<3x|xFmljhZPdp8CaW|EJG#!zefPh_mM z&etyv3kX>QY{Gdn*&H$^Z@3sDr#5)#N5}`M^6 z&s1w7fe6S#!^04yD!_aPBGUAk5<>-$s=!bJ|wP1K=>({MNQrUzWl_|}`W#A$UQ1WI9@NrC|K)1A6 zC?t<R#WMY#U_&Y%vH)!Q3??qHLI#}63dt)DAmeWx@$=Z z6v>xeV{Gfk?e2Bgwq*;@cOX0#t18-N9N_; new { LineNumber = index + 1, Line = line }) + var hits = new[] { inventoryMenuPath, inventoryPanelPath } + .SelectMany(path => File.ReadLines(path) + .Select((line, index) => new { Path = path, LineNumber = index + 1, Line = line })) .Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal))) - .Select(line => $"{Path.GetRelativePath(root, inventoryMenuPath)}:{line.LineNumber}: {line.Line.Trim()}") + .Select(line => $"{Path.GetRelativePath(root, line.Path)}:{line.LineNumber}: {line.Line.Trim()}") .ToArray(); if (hits.Length > 0) { throw new InvalidOperationException( - "InventoryMenu double-click transfer still owns direct inventory mutation: " + + "Inventory UI still owns direct inventory mutation: " + string.Join("; ", hits)); } @@ -976,17 +987,20 @@ static void RequireInventoryDoubleClickTransferCommitAuthority(string root) { "GameManager.CommitCargoItemTransfer", "GameManager.CommitCargoItemEquip", - "GameManager.CommitEquippedItemStore" + "GameManager.CommitEquippedItemStore", + "GameManager.CommitEquippedItemEquip" }; var missingUiCalls = requiredUiCalls - .Where(symbol => !inventoryMenu.Contains(symbol, StringComparison.Ordinal)) + .Where(symbol => + !inventoryMenu.Contains(symbol, StringComparison.Ordinal) && + !inventoryPanel.Contains(symbol, StringComparison.Ordinal)) .ToArray(); if (missingUiCalls.Length > 0) { throw new InvalidOperationException( - "InventoryMenu no longer routes double-click transfers through ActionGameManager: " + + "Inventory UI no longer routes transfer requests through ActionGameManager: " + string.Join(", ", missingUiCalls)); } } diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs index 99c33b95a..c62f8ed57 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -1493,6 +1493,61 @@ public class ActionGameManager : MonoBehaviour return true; } + public bool CommitCargoItemTransfer( + EquippedCargoBay origin, + EquippedCargoBay destination, + ItemInstance item, + int2 destinationPosition) + { + if (origin == null || + destination == null || + item == null || + !origin.Cargo.ContainsKey(item) || + !destination.ItemFits(item, destinationPosition)) + { + return false; + } + + if (origin == destination) + { + var originalPosition = origin.Cargo[item]; + origin.Remove(item); + if (!destination.TryStore(item, destinationPosition)) + { + origin.TryStore(item, originalPosition); + return false; + } + + QueueRunCheckpoint("cargo-item-transfer"); + return true; + } + + if (item is SimpleCommodity commodity) + { + var quantity = commodity.Quantity; + var transferItem = new SimpleCommodity + { + Reference = commodity.Reference, + Quantity = quantity, + Rotation = commodity.Rotation + }; + + if (!destination.TryStore(transferItem, destinationPosition)) + return false; + + origin.Remove(commodity, quantity); + QueueRunCheckpoint("cargo-item-transfer"); + return true; + } + + if (!destination.TryStore(item, destinationPosition)) + return false; + + origin.Remove(item); + QueueRunCheckpoint("cargo-item-transfer"); + return true; + } + public bool CommitCargoItemEquip(EquippedCargoBay origin, Entity destination, EquippableItem item) { if (origin == null || @@ -1512,6 +1567,29 @@ public class ActionGameManager : MonoBehaviour return true; } + public bool CommitCargoItemEquip( + EquippedCargoBay origin, + Entity destination, + EquippableItem item, + int2 destinationPosition) + { + if (origin == null || + destination == null || + item == null || + !origin.Cargo.ContainsKey(item) || + !destination.ItemFits(item, destinationPosition)) + { + return false; + } + + if (!destination.TryEquip(item, destinationPosition)) + return false; + + origin.Remove(item); + QueueRunCheckpoint("cargo-item-equip"); + return true; + } + public bool CommitEquippedItemStore(Entity origin, EquippedItem equippedItem, EquippedCargoBay destination) { if (origin == null || @@ -1537,6 +1615,66 @@ public class ActionGameManager : MonoBehaviour return true; } + public bool CommitEquippedItemStore( + Entity origin, + EquippedItem equippedItem, + EquippedCargoBay destination, + int2 destinationPosition) + { + if (origin == null || + equippedItem?.EquippableItem == null || + destination == null || + !origin.Equipment.Contains(equippedItem) || + !destination.ItemFits(equippedItem.EquippableItem, destinationPosition)) + { + return false; + } + + var originalPosition = equippedItem.Position; + var item = origin.TryUnequip(equippedItem); + if (item == null) + return false; + + if (!destination.TryStore(item, destinationPosition)) + { + origin.TryEquip(item, originalPosition); + return false; + } + + QueueRunCheckpoint("equipped-item-store"); + return true; + } + + public bool CommitEquippedItemEquip( + Entity origin, + EquippedItem equippedItem, + Entity destination, + int2 destinationPosition) + { + if (origin == null || + equippedItem?.EquippableItem == null || + destination == null || + !origin.Equipment.Contains(equippedItem) || + !destination.ItemFits(equippedItem.EquippableItem, destinationPosition)) + { + return false; + } + + var originalPosition = equippedItem.Position; + var item = origin.TryUnequip(equippedItem); + if (item == null) + return false; + + if (!destination.TryEquip(item, destinationPosition)) + { + origin.TryEquip(item, originalPosition); + return false; + } + + QueueRunCheckpoint("equipped-item-equip"); + return true; + } + public void CommitEquippedItemOverrideShutdown(EquippedItem item, bool enabled) { if (item?.EquippableItem == null) diff --git a/Assets/Scripts/UI/Menu/InventoryPanel.cs b/Assets/Scripts/UI/Menu/InventoryPanel.cs index 417e1a8d4..327fbc70e 100644 --- a/Assets/Scripts/UI/Menu/InventoryPanel.cs +++ b/Assets/Scripts/UI/Menu/InventoryPanel.cs @@ -438,27 +438,10 @@ public class InventoryPanel : MonoBehaviour, IPointerClickHandler GameManager.RegisterDragTarget(drag => { //Debug.Log("Entity Drag Callback"); - if (GameManager.DragObject is EquippedItemDragObject equippedItemDragObject) - { - if(equippedItemDragObject.OriginEntity.TryUnequip(equippedItemDragObject.EquippedItem) == null) - { - // AkSoundEngine.PostEvent("UI_Fail", gameObject); - // TODO: SFX: Fail - Dialog.Clear(); - Dialog.Title.text = "Unable to move item!"; - Dialog.AddProperty("Verify that cargo bays are empty before un-equipping them."); - Dialog.Show(); - Dialog.MoveToCursor(); - } - // else - // AkSoundEngine.PostEvent("Unequip", gameObject); - // TODO: SFX: Unequip - } - else if (GameManager.DragObject is ItemInstanceDragObject itemInstanceDragObject) - itemInstanceDragObject.OriginInventory.Remove(itemInstanceDragObject.Item); - FakeOccupancy = null; - var success = entity.TryEquip(equippableItem, placementPosition); + var success = CommitDraggedItemToEntity(drag, entity, placementPosition); + if (!success) + ShowUnableToMoveItemDialog(); RefreshCells(); // TODO: SFX: Equip return success; @@ -658,26 +641,12 @@ public class InventoryPanel : MonoBehaviour, IPointerClickHandler GameManager.RegisterDragTarget(drag => { //Debug.Log("Inventory Drag Callback"); - if (GameManager.DragObject is EquippedItemDragObject equippedItemDragObject) - { - if(equippedItemDragObject.OriginEntity.TryUnequip(equippedItemDragObject.EquippedItem) == null) - { - // TODO: SFX: Fail - Dialog.Clear(); - Dialog.Title.text = "Unable to move item!"; - Dialog.AddProperty("Verify that cargo bays are empty before un-equipping them."); - Dialog.Show(); - Dialog.MoveToCursor(); - } - //else - // TODO: SFX: Unequip - } - else if (GameManager.DragObject is ItemInstanceDragObject itemInstanceDragObject) - itemInstanceDragObject.OriginInventory.Remove(itemInstanceDragObject.Item); - cargo.TryStore(item, placementPosition); FakeOccupancy = null; + var success = CommitDraggedItemToCargo(drag, cargo, placementPosition); + if (!success) + ShowUnableToMoveItemDialog(); // TODO: SFX: Drop - return true; + return success; }); } }); @@ -937,34 +906,55 @@ public class InventoryPanel : MonoBehaviour, IPointerClickHandler return ActionGameManager.RuntimeCatalog?.FindItem(item?.ItemKey ?? ""); } - public bool CanDropItem(ItemInstance item) + private bool CommitDraggedItemToEntity(DragObject drag, Entity destination, int2 destinationPosition) { - if (_displayedEntity != null && item is EquippableItem equippableItem) + switch (drag) { - return _displayedEntity.TryFindSpace(equippableItem, out _); + case EquippedItemDragObject equippedItemDragObject: + return GameManager.CommitEquippedItemEquip( + equippedItemDragObject.OriginEntity, + equippedItemDragObject.EquippedItem, + destination, + destinationPosition); + case ItemInstanceDragObject itemInstanceDragObject when itemInstanceDragObject.Item is EquippableItem equippableItem: + return GameManager.CommitCargoItemEquip( + itemInstanceDragObject.OriginInventory, + destination, + equippableItem, + destinationPosition); + default: + return false; } - - if (_displayedCargo != null) - { - return _displayedCargo.TryFindSpace(item); - } - - return false; } - public bool DropItem(ItemInstance item) + private bool CommitDraggedItemToCargo(DragObject drag, EquippedCargoBay destination, int2 destinationPosition) { - if (_displayedEntity != null && item is EquippableItem equippableItem) + switch (drag) { - return _displayedEntity.TryEquip(equippableItem); + case EquippedItemDragObject equippedItemDragObject: + return GameManager.CommitEquippedItemStore( + equippedItemDragObject.OriginEntity, + equippedItemDragObject.EquippedItem, + destination, + destinationPosition); + case ItemInstanceDragObject itemInstanceDragObject: + return GameManager.CommitCargoItemTransfer( + itemInstanceDragObject.OriginInventory, + destination, + itemInstanceDragObject.Item, + destinationPosition); + default: + return false; } + } - if(_displayedCargo != null) - { - return _displayedCargo.TryStore(item); - } - - return false; + private void ShowUnableToMoveItemDialog() + { + Dialog.Clear(); + Dialog.Title.text = "Unable to move item!"; + Dialog.AddProperty("Verify that cargo bays are empty before un-equipping them."); + Dialog.Show(); + Dialog.MoveToCursor(); } public Subject OnBackgroundClick = new Subject(); diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index 6593efd84..5c65f3744 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -1389,13 +1389,13 @@ First Aetheria surfaces to publish: remains a uGUI control for now, but it no longer adds/removes items or weapons from `Entity.WeaponGroups` directly; gameplay owns the membership mutation and typed checkpoint. - - Done: route inventory double-click transfer through gameplay-owned - checkpoint commits. `InventoryMenu` no longer drops items, removes cargo, - or unequips gear directly for the double-click path; it asks - `ActionGameManager` to commit cargo-to-cargo, cargo-to-equipment, or - equipment-to-cargo movement and then refreshes display. The deeper - `InventoryPanel` drag/drop path still has direct mutation and is the next - inventory cut. + - Done: route inventory double-click transfer and drag/drop placement through + gameplay-owned checkpoint commits. `InventoryMenu` and `InventoryPanel` no + longer drop items, remove cargo, unequip gear, equip gear, or store cargo + directly for those UI paths; they ask `ActionGameManager` to commit + cargo-to-cargo, cargo-to-equipment, equipment-to-cargo, or + equipment-to-equipment movement and then refresh display. uGUI still owns + the presentation shell for now, not the inventory mutation. - Replace the next concrete uGUI screen with an Eve-owned surface. - Move the staged packages into the Eve repo once its worktree is clean, then import them back into Aetheria from Eve instead of carrying a local copy. @@ -1718,9 +1718,8 @@ the grid mutation and checkpoint. Entity renames follow the same rule: UI collects text, gameplay owns the name mutation and checkpoint. Weapon group assignment follows the same rule: UI requests membership, gameplay owns the group mutation and checkpoint. Inventory double-click transfer follows the same -rule: UI requests cargo/equipment movement, gameplay owns the move and queues -the checkpoint. Drag/drop inventory placement is not pure yet and should be cut -through the same commit primitive family next. Any +rule, and drag/drop placement now shares that commit family: UI requests +cargo/equipment movement, gameplay owns the move and queues the checkpoint. Any predicate that still needs legacy DTO objects must earn that dependency by using behavior objects or simulation-only methods that typed facets do not yet expose.