using ProjectM.Simulation; using Unity.Entities; using Unity.NetCode; namespace ProjectM.Client { /// /// Client-only sender for / RPCs. One-off actions, so /// RPCs (not per-tick input); the server applies them authoritatively in /// . Number keys 1-9 equip the Nth EQUIPPABLE item in the local bag /// (resources are skipped via the catalog); U unequips the weapon. Managed SystemBase because it reads the /// managed Input System; Input System types are fully qualified and using UnityEngine.InputSystem; is /// omitted (it defines a colliding PlayerInput type). An #if UNITY_EDITOR static hook drives the same /// path from execute_code for headless validation. The HUD click-to-equip (HudSystem) is the primary UX; /// these keys are the reachable fallback. Wire types are unconditional; only this send SYSTEM is gated. /// [WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)] public partial class EquipSendSystem : SystemBase { struct PendingEquip { public bool Unequip; public ushort ItemId; public byte Slot; } static readonly System.Collections.Generic.Queue s_Pending = new System.Collections.Generic.Queue(); /// EDITOR / execute_code hook: queue an equip of from the bag. public static void Equip(ushort itemId) => s_Pending.Enqueue(new PendingEquip { Unequip = false, ItemId = itemId }); /// EDITOR / execute_code hook: queue an unequip of (an EquipSlotId). public static void Unequip(byte slot) => s_Pending.Enqueue(new PendingEquip { Unequip = true, Slot = slot }); protected override void OnCreate() { RequireForUpdate(); } protected override void OnUpdate() { if (!SystemAPI.TryGetSingletonEntity(out var connection)) return; var keyboard = UnityEngine.InputSystem.Keyboard.current; if (keyboard != null) { int n = NumberKeyPressed(keyboard); if (n >= 0) { ushort itemId = NthEquippableBagItem(n); if (itemId != 0) SendEquip(connection, itemId); } if (keyboard.uKey.wasPressedThisFrame) SendUnequip(connection, EquipSlotId.Weapon); } while (s_Pending.Count > 0) { var p = s_Pending.Dequeue(); if (p.Unequip) SendUnequip(connection, p.Slot); else SendEquip(connection, p.ItemId); } } static int NumberKeyPressed(UnityEngine.InputSystem.Keyboard kb) { if (kb.digit1Key.wasPressedThisFrame) return 0; if (kb.digit2Key.wasPressedThisFrame) return 1; if (kb.digit3Key.wasPressedThisFrame) return 2; if (kb.digit4Key.wasPressedThisFrame) return 3; if (kb.digit5Key.wasPressedThisFrame) return 4; if (kb.digit6Key.wasPressedThisFrame) return 5; if (kb.digit7Key.wasPressedThisFrame) return 6; if (kb.digit8Key.wasPressedThisFrame) return 7; if (kb.digit9Key.wasPressedThisFrame) return 8; return -1; } /// Resolve the Nth EQUIPPABLE distinct item in the local player's bag (skips resources via the catalog). ushort NthEquippableBagItem(int n) { bool haveDb = SystemAPI.TryGetSingleton(out var db); foreach (var bag in SystemAPI.Query>().WithAll()) { int idx = 0; for (int i = 0; i < bag.Length; i++) { ushort id = bag[i].ItemId; if (id == 0 || bag[i].Count <= 0) continue; if (haveDb && db.Value.IsCreated) { ref var b = ref db.Value.Value; if (!b.TryGetItem(id, out var def) || def.EquipSlot >= EquipSlotId.Count) continue; } if (idx == n) return id; idx++; } break; } return 0; } void SendEquip(Entity connection, ushort itemId) { var req = EntityManager.CreateEntity(); EntityManager.AddComponentData(req, new EquipRequest { ItemId = itemId }); EntityManager.AddComponentData(req, new SendRpcCommandRequest { TargetConnection = connection }); } void SendUnequip(Entity connection, byte slot) { var req = EntityManager.CreateEntity(); EntityManager.AddComponentData(req, new UnequipRequest { Slot = slot }); EntityManager.AddComponentData(req, new SendRpcCommandRequest { TargetConnection = connection }); } } }