Files
kronic 599b9b4255 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>
2026-06-08 09:43:31 -07:00

65 lines
2.9 KiB
C#

using ProjectM.Simulation;
using Unity.Entities;
using Unity.NetCode;
namespace ProjectM.Client
{
/// <summary>
/// Client-only sender for <see cref="InventoryDepositRequest"/> RPCs (move the local player's PERSONAL
/// inventory into the shared base stockpile / global ledger). A one-off action (not per-tick predicted
/// input), so it is an RPC: on the deposit key edge (G = deposit ALL) it creates the request entity
/// targeted at the server connection, and the server applies it authoritatively in
/// <see cref="ProjectM.Server.InventoryDepositSystem"/>. Managed SystemBase because it reads the managed
/// Input System; Input System types are fully qualified and <c>using UnityEngine.InputSystem;</c> is
/// intentionally omitted (that namespace defines a PlayerInput type that collides with
/// <see cref="ProjectM.Simulation.PlayerInput"/>). An editor-only static hook (<see cref="Deposit"/>)
/// drives the same path from execute_code for headless validation without a focused Game view. The wire
/// type is unconditional; only this send SYSTEM is build-time managed.
/// </summary>
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
public partial class InventoryDepositSendSystem : SystemBase
{
#if UNITY_EDITOR
struct PendingDeposit { public ushort ItemId; public int Count; }
static readonly System.Collections.Generic.Queue<PendingDeposit> s_Pending =
new System.Collections.Generic.Queue<PendingDeposit>();
/// <summary>EDITOR / execute_code hook: queue a deposit (ItemId 0 = deposit all; Count &lt;= 0 = all of that item).</summary>
public static void Deposit(ushort itemId = 0, int count = 0) =>
s_Pending.Enqueue(new PendingDeposit { ItemId = itemId, Count = count });
#endif
protected override void OnCreate()
{
RequireForUpdate<NetworkId>();
}
protected override void OnUpdate()
{
// Need the server connection to target the RPC; bail (keeping any queued ops) until connected.
if (!SystemAPI.TryGetSingletonEntity<NetworkId>(out var connection))
return;
var keyboard = UnityEngine.InputSystem.Keyboard.current;
if (keyboard != null && keyboard.gKey.wasPressedThisFrame)
Send(connection, 0, 0); // G -> deposit everything to the base stockpile
#if UNITY_EDITOR
while (s_Pending.Count > 0)
{
var d = s_Pending.Dequeue();
Send(connection, d.ItemId, d.Count);
}
#endif
}
void Send(Entity connection, ushort itemId, int count)
{
var request = EntityManager.CreateEntity();
EntityManager.AddComponentData(request, new InventoryDepositRequest { ItemId = itemId, Count = count });
EntityManager.AddComponentData(request, new SendRpcCommandRequest { TargetConnection = connection });
}
}
}