#if UNITY_EDITOR using System.Collections.Generic; using ProjectM.Simulation; using Unity.Entities; using Unity.NetCode; namespace ProjectM.Server { /// /// Editor-only debug hook (mirrors ProjectM.Client.DebugInputInjectionSystem's static-poke pattern) /// for driving the server-authoritative modifier stack from MCP execute_code. Because modifiers are /// server-authoritative, a client-side append would be stomped by the next snapshot, so this runs in /// the SERVER world: the change flows back through the snapshot and is prediction-correct on the /// client. In-editor single-process only (client + server worlds in one process). Poke from execute_code: /// DebugModifierInjectionSystem.AddModifier((byte)StatTarget.Damage, (byte)ModOp.Flat, 50f); /// DebugModifierInjectionSystem.AddModifier((byte)StatTarget.MoveSpeed, (byte)ModOp.PercentAdd, 0.5f); /// DebugModifierInjectionSystem.CycleAbility(); // Primary -> FastLight -> SlowHeavy -> Primary /// DebugModifierInjectionSystem.ClearModifiers(); /// All applied to the first player on the next server tick. /// [WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)] public partial class DebugModifierInjectionSystem : SystemBase { struct PendingModifier { public byte Target; public byte Op; public float Value; } static readonly List s_Pending = new List(); static bool s_Clear; static bool s_Cycle; /// Queue a modifier to append to the first player on the next server tick. public static void AddModifier(byte target, byte op, float value) { s_Pending.Add(new PendingModifier { Target = target, Op = op, Value = value }); } /// Clear the first player's whole modifier stack on the next server tick. public static void ClearModifiers() => s_Clear = true; /// Cycle the first player's primary ability id on the next server tick. public static void CycleAbility() => s_Cycle = true; protected override void OnUpdate() { if (s_Pending.Count == 0 && !s_Clear && !s_Cycle) return; Entity player = Entity.Null; foreach (var (abilityRef, e) in SystemAPI.Query>().WithAll().WithEntityAccess()) { player = e; break; } if (player == Entity.Null) return; if (s_Clear) { EntityManager.GetBuffer(player).Clear(); s_Clear = false; } if (s_Pending.Count > 0) { var buffer = EntityManager.GetBuffer(player); for (int i = 0; i < s_Pending.Count; i++) { var m = s_Pending[i]; buffer.Add(new StatModifier { Target = m.Target, Op = m.Op, Value = m.Value, SourceId = 0u }); } s_Pending.Clear(); } if (s_Cycle) { var abilityRef = EntityManager.GetComponentData(player); abilityRef.Id = abilityRef.Id switch { (byte)AbilityId.Primary => (byte)AbilityId.FastLight, (byte)AbilityId.FastLight => (byte)AbilityId.SlowHeavy, _ => (byte)AbilityId.Primary, }; EntityManager.SetComponentData(player, abilityRef); s_Cycle = false; } } } } #endif