Inventory: per-player items backbone (DR-026 Phase 0)
Data-driven ItemDatabase catalog + per-player replicated InventorySlot ([GhostField] OwnerSendType.All, a StatModifier twin). Harvest reroutes to the firing player's personal inventory (optional ComponentLookup<GhostOwner> in ResourceHarvestSystem; remainder/un-owned -> ledger); the G-key InventoryDepositRequest RPC moves the bag into the shared ledger the build economy spends. Catalog asset (Aether/Ore/Biomass + Stone Pickaxe) wired into the Gameplay subscene; read-only HUD inventory panel. ushort ItemId subsumes ResourceId; byte Category/Tier baked for gear-tier progression. Session-only (no SaveData bump). Play-validated host+client: catalog baked into both worlds, the re-baked player ghost carries InventorySlot with a clean handshake, a server write replicates to the client owner, the deposit RPC round-trips, and the HUD renders catalog names. See DR-026. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
using Unity.Entities;
|
||||
|
||||
namespace ProjectM.Simulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Pure, deterministic stacking logic for a player's <see cref="InventorySlot"/> buffer (no RNG /
|
||||
/// wall-clock / singleton access, so server and any future prediction agree). Parallels
|
||||
/// <see cref="StorageMath"/> 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.
|
||||
/// </summary>
|
||||
public static class InventoryMath
|
||||
{
|
||||
/// <summary>
|
||||
/// Add <paramref name="count"/> of <paramref name="itemId"/>: first tops up existing non-full stacks
|
||||
/// of that item, then appends new stacks (each capped at <paramref name="stackMax"/>) while a free slot
|
||||
/// remains (buffer length < <paramref name="maxSlots"/>). 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.
|
||||
/// </summary>
|
||||
public static int Deposit(DynamicBuffer<InventorySlot> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove up to <paramref name="count"/> of <paramref name="itemId"/> 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.
|
||||
/// </summary>
|
||||
public static int Withdraw(DynamicBuffer<InventorySlot> 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;
|
||||
}
|
||||
|
||||
/// <summary>Total quantity of <paramref name="itemId"/> across all stacks (0 if absent).</summary>
|
||||
public static int CountOf(DynamicBuffer<InventorySlot> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user