#if UNITY_EDITOR
using ProjectM.Simulation;
using Unity.Entities;
using Unity.Mathematics;
using Unity.NetCode;
namespace ProjectM.Client
{
///
/// EDITOR-ONLY validation hook for driving the local player's without a
/// real input device or a focused Game view. The Unity Input System ignores injected/real device
/// input while the Game view is unfocused, which makes headless (MCP execute_code) or
/// automated fire/move validation impossible through alone.
///
/// This system runs in immediately AFTER the real gather and,
/// when is set, overwrites the locally-owned player's input from static fields
/// you can poke from a debugger / execute_code / an editor button. Because it writes the same
/// the gather does, it drives the authentic command → prediction →
/// AbilityFireSystem pipeline (not a shortcut), so it validates the real fire/move/auto-target path.
///
///
/// Entirely wrapped in #if UNITY_EDITOR: it does not exist in player builds. Pair with
/// Application.runInBackground = true so the unfocused editor keeps ticking. Usage from
/// execute_code: ProjectM.Client.DebugInputInjectionSystem.Fire(); (one shot),
/// ...SetMove(0f, 1f); (hold a move heading), ...SetAim(1f, 0f);, ...Stop();.
///
///
[UpdateInGroup(typeof(GhostInputSystemGroup))]
[UpdateAfter(typeof(PlayerInputGatherSystem))]
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
public partial class DebugInputInjectionSystem : SystemBase
{
/// While true, this system overrides the local player's gathered input each frame.
public static bool Active;
/// Movement heading applied to PlayerInput.Move while .
public static float2 Move;
/// Aim vector applied to PlayerInput.Aim while (zero = face movement).
public static float2 Aim;
/// Frames remaining to hold the Fire event. Each held frame raises Fire.Set(); holding
/// across several frames spans multiple network ticks so the one-shot event reliably reaches the
/// command buffer (a single-frame pulse can be lost across the frame→tick boundary). 0 = idle.
public static int FireFrames;
/// Convenience: hold Fire for the next frames (also enables
/// override). The ability cooldown still gates how many shots actually result.
public static void Fire(int frames = 10) { Active = true; FireFrames = math.max(FireFrames, frames); }
/// Convenience: hold a planar move heading (also enables override).
public static void SetMove(float x, float z) { Active = true; Move = new float2(x, z); }
/// Convenience: hold an aim direction (also enables override).
public static void SetAim(float x, float z) { Active = true; Aim = new float2(x, z); }
/// Convenience: stop overriding and clear all injected input.
public static void Stop() { Active = false; Move = default; Aim = default; FireFrames = 0; }
protected override void OnCreate()
{
RequireForUpdate();
}
protected override void OnUpdate()
{
if (!Active)
return;
bool fire = FireFrames > 0;
if (FireFrames > 0) FireFrames--;
float2 move = Move;
float2 aim = Aim;
foreach (var input in SystemAPI.Query>().WithAll())
{
input.ValueRW.Move = move;
input.ValueRW.Aim = aim;
if (fire)
input.ValueRW.Fire.Set();
}
}
}
}
#endif