From 01bcd7334cc2d3382b38a9e9266f97ed375d5ff8 Mon Sep 17 00:00:00 2001 From: VinPropane Date: Sun, 31 May 2026 17:11:55 -0400 Subject: [PATCH] NEO-105: Consolidate inventory snapshot clone helper. Add PlayerInventorySnapshot.Clone() and use it from craft and encounter completion pre-flight paths (Bugbot PR #144). --- .../NeonSprawl.Server/Game/Crafting/CraftOperations.cs | 9 +-------- .../Game/Encounters/EncounterCompletionOperations.cs | 9 +-------- .../Game/Items/PlayerInventorySnapshot.cs | 8 ++++++++ 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/server/NeonSprawl.Server/Game/Crafting/CraftOperations.cs b/server/NeonSprawl.Server/Game/Crafting/CraftOperations.cs index 903eecd..ad7e117 100644 --- a/server/NeonSprawl.Server/Game/Crafting/CraftOperations.cs +++ b/server/NeonSprawl.Server/Game/Crafting/CraftOperations.cs @@ -56,7 +56,7 @@ public static class CraftOperations } } - var simulated = CloneSnapshot(snapshot); + var simulated = snapshot.Clone(); foreach (var input in scaledInputs) { if (!PlayerInventoryOperations.TrySimulateRemoveStack(ref simulated, input.ItemId, input.Quantity, itemRegistry)) @@ -176,13 +176,6 @@ public static class CraftOperations return scaled; } - private static PlayerInventorySnapshot CloneSnapshot(PlayerInventorySnapshot snapshot) => - new() - { - BagSlots = PlayerInventorySnapshot.CloneSlots(snapshot.BagSlots), - EquipmentSlots = PlayerInventorySnapshot.CloneSlots(snapshot.EquipmentSlots), - }; - private static void CompensatingRestoreInputs( string playerId, IReadOnlyList inputs, diff --git a/server/NeonSprawl.Server/Game/Encounters/EncounterCompletionOperations.cs b/server/NeonSprawl.Server/Game/Encounters/EncounterCompletionOperations.cs index ba8bf72..692b085 100644 --- a/server/NeonSprawl.Server/Game/Encounters/EncounterCompletionOperations.cs +++ b/server/NeonSprawl.Server/Game/Encounters/EncounterCompletionOperations.cs @@ -62,7 +62,7 @@ public static class EncounterCompletionOperations return Deny(EncounterCompletionReasonCodes.InventoryStoreMissing); } - var simulated = CloneSnapshot(snapshot); + var simulated = snapshot.Clone(); foreach (var grant in grants) { if (!PlayerInventoryOperations.TrySimulateAddStack( @@ -139,13 +139,6 @@ public static class EncounterCompletionOperations private static string MakeIdempotencyKey(string normalizedPlayerId, string normalizedEncounterId) => $"{normalizedPlayerId}:{normalizedEncounterId}"; - private static PlayerInventorySnapshot CloneSnapshot(PlayerInventorySnapshot snapshot) => - new() - { - BagSlots = PlayerInventorySnapshot.CloneSlots(snapshot.BagSlots), - EquipmentSlots = PlayerInventorySnapshot.CloneSlots(snapshot.EquipmentSlots), - }; - private static void CompensatingRemoveGrants( string playerId, IReadOnlyList grants, diff --git a/server/NeonSprawl.Server/Game/Items/PlayerInventorySnapshot.cs b/server/NeonSprawl.Server/Game/Items/PlayerInventorySnapshot.cs index 089c9e3..8b16dfb 100644 --- a/server/NeonSprawl.Server/Game/Items/PlayerInventorySnapshot.cs +++ b/server/NeonSprawl.Server/Game/Items/PlayerInventorySnapshot.cs @@ -41,6 +41,14 @@ public sealed class PlayerInventorySnapshot return copy; } + /// Deep-copies bag and equipment slot arrays (NEO-69 craft pre-flight, NEO-105 encounter grant pre-flight). + public PlayerInventorySnapshot Clone() => + new() + { + BagSlots = CloneSlots(BagSlots), + EquipmentSlots = CloneSlots(EquipmentSlots), + }; + public InventorySlotState[] GetSlots(InventoryContainerKind container) => container == InventoryContainerKind.Equipment ? EquipmentSlots : BagSlots;