using Unity.Entities; namespace ProjectM.Simulation { /// /// Pure, deterministic stacking logic for a player's buffer (no RNG / /// wall-clock / singleton access, so server and any future prediction agree). Parallels /// in spirit but does NOT collapse into it: StorageMath is an unbounded /// single-row merge, whereas this enforces a per-item stack cap and a max slot count and supports multiple /// stacks of the same item once a stack fills. DynamicBuffer is a handle, so mutations apply to the /// underlying entity buffer; growing it (buffer.Add) is a resize, NOT a structural change, so it is safe to /// call while iterating a different query. Unit-tested in EditMode via a plain Entities world. /// public static class InventoryMath { /// /// Add of : first tops up existing non-full stacks /// of that item, then appends new stacks (each capped at ) while a free slot /// remains (buffer length < ). Returns the REMAINDER that did not fit /// (0 if everything was deposited). No-op (returns 0) for count <= 0; a positive count of itemId 0 /// returns the full count (nothing deposited — never writes a 0-id row). stackMax < 1 = unbounded. /// public static int Deposit(DynamicBuffer buffer, ushort itemId, int count, int stackMax, int maxSlots) { if (count <= 0) return 0; if (itemId == 0) return count; if (stackMax < 1) stackMax = int.MaxValue; // Top up existing stacks of this item. for (int i = 0; i < buffer.Length && count > 0; i++) { if (buffer[i].ItemId != itemId) continue; var e = buffer[i]; int space = stackMax - e.Count; if (space <= 0) continue; int add = space < count ? space : count; e.Count += add; buffer[i] = e; count -= add; } // Append new stacks while a slot is free. while (count > 0 && buffer.Length < maxSlots) { int add = stackMax < count ? stackMax : count; buffer.Add(new InventorySlot { ItemId = itemId, Count = add }); count -= add; } return count; } /// /// Remove up to of across all its stacks, clamped to /// what is available; drops a stack that reaches zero. Returns the amount actually withdrawn (0 if none). /// Iterates back-to-front so RemoveAt does not skip a stack. No-op for count <= 0 or itemId 0. /// public static int Withdraw(DynamicBuffer buffer, ushort itemId, int count) { if (count <= 0 || itemId == 0) return 0; int taken = 0; for (int i = buffer.Length - 1; i >= 0 && count > 0; i--) { if (buffer[i].ItemId != itemId) continue; var e = buffer[i]; int t = e.Count < count ? e.Count : count; e.Count -= t; taken += t; count -= t; if (e.Count <= 0) buffer.RemoveAt(i); else buffer[i] = e; } return taken; } /// Total quantity of across all stacks (0 if absent). public static int CountOf(DynamicBuffer buffer, ushort itemId) { int total = 0; for (int i = 0; i < buffer.Length; i++) if (buffer[i].ItemId == itemId) total += buffer[i].Count; return total; } } }