using ProjectM.Simulation; using Unity.Entities; using Unity.Mathematics; using Unity.NetCode; using Unity.Transforms; namespace ProjectM.Client { /// /// Client-only sender for build + upgrade RPCs. Keyboard: B builds a Turret at the local player's current /// grid cell; U upgrades ability damage. Editor-only statics (PlaceStructure / PlaceTurret / UpgradeAbility) /// drive the same path from execute_code for headless validation (a one-shot key can't be injected on an /// unfocused editor — the StorageOpSendSystem idiom). Managed SystemBase (reads the Input System); /// UnityEngine.InputSystem is fully qualified to avoid the ProjectM.Simulation.PlayerInput name collision. /// The server re-validates legality + cost authoritatively; this only sends a hint. /// [WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)] public partial class BuildSendSystem : SystemBase { #if UNITY_EDITOR struct PendingBuild { public byte Type; public int CellX; public int CellZ; } static readonly System.Collections.Generic.Queue s_PendingBuild = new System.Collections.Generic.Queue(); static int s_PendingUpgrades = 0; /// EDITOR / execute_code hook: queue a structure placement at a specific cell. public static void PlaceStructure(byte type, int cellX, int cellZ) => s_PendingBuild.Enqueue(new PendingBuild { Type = type, CellX = cellX, CellZ = cellZ }); /// EDITOR / execute_code hook: queue a turret placement at a specific cell. public static void PlaceTurret(int cellX, int cellZ) => PlaceStructure(StructureType.Turret, cellX, cellZ); /// EDITOR / execute_code hook: queue an ability-damage upgrade. public static void UpgradeAbility() => s_PendingUpgrades++; #endif 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) { if (keyboard.bKey.wasPressedThisFrame && TryGetLocalPlayerCell(out int2 cell)) SendBuild(connection, StructureType.Turret, cell.x, cell.y); if (keyboard.uKey.wasPressedThisFrame) SendUpgrade(connection); } #if UNITY_EDITOR while (s_PendingBuild.Count > 0) { var b = s_PendingBuild.Dequeue(); SendBuild(connection, b.Type, b.CellX, b.CellZ); } while (s_PendingUpgrades > 0) { s_PendingUpgrades--; SendUpgrade(connection); } #endif } bool TryGetLocalPlayerCell(out int2 cell) { cell = default; if (!SystemAPI.TryGetSingleton(out var anchor)) return false; foreach (var xform in SystemAPI.Query>().WithAll()) { cell = BaseGridMath.WorldToCell(anchor, xform.ValueRO.Position); return true; } return false; } void SendBuild(Entity connection, byte type, int cellX, int cellZ) { var e = EntityManager.CreateEntity(); EntityManager.AddComponentData(e, new BuildPlaceRequest { StructureType = type, CellX = cellX, CellZ = cellZ }); EntityManager.AddComponentData(e, new SendRpcCommandRequest { TargetConnection = connection }); } void SendUpgrade(Entity connection) { var e = EntityManager.CreateEntity(); EntityManager.AddComponentData(e, new AbilityUpgradeRequest()); EntityManager.AddComponentData(e, new SendRpcCommandRequest { TargetConnection = connection }); } } }