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 });
}
}
}