Files
Project-M/Assets/_Project/Scripts/Client/Building/BuildSendSystem.cs
T
2026-06-04 00:06:18 -07:00

98 lines
4.0 KiB
C#

using ProjectM.Simulation;
using Unity.Entities;
using Unity.Mathematics;
using Unity.NetCode;
using Unity.Transforms;
namespace ProjectM.Client
{
/// <summary>
/// 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.
/// </summary>
[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<PendingBuild> s_PendingBuild =
new System.Collections.Generic.Queue<PendingBuild>();
static int s_PendingUpgrades = 0;
/// <summary>EDITOR / execute_code hook: queue a structure placement at a specific cell.</summary>
public static void PlaceStructure(byte type, int cellX, int cellZ) =>
s_PendingBuild.Enqueue(new PendingBuild { Type = type, CellX = cellX, CellZ = cellZ });
/// <summary>EDITOR / execute_code hook: queue a turret placement at a specific cell.</summary>
public static void PlaceTurret(int cellX, int cellZ) => PlaceStructure(StructureType.Turret, cellX, cellZ);
/// <summary>EDITOR / execute_code hook: queue an ability-damage upgrade.</summary>
public static void UpgradeAbility() => s_PendingUpgrades++;
#endif
protected override void OnCreate()
{
RequireForUpdate<NetworkId>();
}
protected override void OnUpdate()
{
if (!SystemAPI.TryGetSingletonEntity<NetworkId>(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<BaseAnchor>(out var anchor))
return false;
foreach (var xform in SystemAPI.Query<RefRO<LocalTransform>>().WithAll<GhostOwnerIsLocal, PlayerTag>())
{
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 });
}
}
}