#if UNITY_EDITOR using ProjectM.Simulation; using UnityEngine; namespace ProjectM.Client { /// /// EDITOR-ONLY in-game dev-tools overlay (IMGUI dev overlay). Buttons enqueue /// RPCs through , so they drive the REAL /// server-authoritative paths (and work over a live connection too, not just in-editor). Drop it on a /// GameObject in the DevSandbox (or Game) scene. While open it forces the OS cursor visible /// () so its buttons stay clickable even while aiming. /// Stripped from player builds (#if UNITY_EDITOR). /// public class DebugOverlay : MonoBehaviour { bool _open = true; int _siegeSize = 5; int _grantAmount = 50; bool _tuningOpen; Vector2 _scroll; void OnDisable() => AimPresentation.ForceCursorVisible = false; void OnGUI() { if (GUI.Button(new Rect(Screen.width - 96, 10, 86, 24), _open ? "DEV ▲" : "DEV ▼")) _open = !_open; AimPresentation.ForceCursorVisible = _open; if (!_open) return; float panelH = Mathf.Min(760f, Screen.height - 50f); GUILayout.BeginArea(new Rect(Screen.width - 232, 40, 222, panelH), GUI.skin.box); GUILayout.Label("DEV TOOLS"); _scroll = GUILayout.BeginScrollView(_scroll); GUILayout.Label("- World -"); _siegeSize = IntField("Siege size", _siegeSize); if (GUILayout.Button("Spawn Wave / Force Siege")) DebugCommandSendSystem.SpawnWave(_siegeSize); if (GUILayout.Button("End Siege")) DebugCommandSendSystem.EndSiege(); if (GUILayout.Button("Clear Enemies")) DebugCommandSendSystem.ClearEnemies(); if (GUILayout.Button("Force Calm")) DebugCommandSendSystem.SetCalm(); if (GUILayout.Button("Advance Goal +1")) DebugCommandSendSystem.AdvanceGoal(1); GUILayout.Space(6); GUILayout.Label("- Resources -"); _grantAmount = IntField("Amount", _grantAmount); GUILayout.BeginHorizontal(); if (GUILayout.Button("Aether")) DebugCommandSendSystem.GrantResource(ResourceId.Aether, _grantAmount); if (GUILayout.Button("Ore")) DebugCommandSendSystem.GrantResource(ResourceId.Ore, _grantAmount); if (GUILayout.Button("Bio")) DebugCommandSendSystem.GrantResource(ResourceId.Biomass, _grantAmount); GUILayout.EndHorizontal(); if (GUILayout.Button("Grant Damage Upgrade")) DebugCommandSendSystem.GrantUpgrade(); GUILayout.Space(6); GUILayout.Label("- Player -"); GUILayout.BeginHorizontal(); if (GUILayout.Button("Heal")) DebugCommandSendSystem.Heal(); if (GUILayout.Button("Kill")) DebugCommandSendSystem.Kill(); if (GUILayout.Button("God")) DebugCommandSendSystem.ToggleGod(); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); if (GUILayout.Button("Go Base")) DebugCommandSendSystem.Teleport(RegionId.Base); if (GUILayout.Button("Go Expedition")) DebugCommandSendSystem.Teleport(RegionId.Expedition); GUILayout.EndHorizontal(); GUILayout.Space(6); GUILayout.Label("- Telemetry (MC-0) -"); if (DevTelemetryReadout.HasData) { var t = DevTelemetryReadout.Latest; GUILayout.Label($"tick {t.LastSampleTick} husks {t.LiveEnemyCount}"); GUILayout.Label($"dash neg {t.DashIFrameNegatedHits} / wasted {t.DashesWasted}"); GUILayout.Label($"whiff open {t.ChargerWhiffWindowsOpened} / punish {t.ChargerWhiffPunishesLanded}"); } else { GUILayout.Label("(waiting for server telemetry...)"); } GUILayout.Space(6); _tuningOpen = GUILayout.Toggle(_tuningOpen, "- Tuning (MC-0) -"); if (_tuningOpen) { TuningRow("Dash dist", TuningKnob.DashDistance, 0.5f, "0.0"); TuningRow("Dash iframe t", TuningKnob.IFrameWindowTicks, 1f, "0"); TuningRow("Dash recover t", TuningKnob.RecoverTailTicks, 1f, "0"); TuningRow("Dash cd t", TuningKnob.DashCooldownTicks, 1f, "0"); TuningRow("Dash sharp", TuningKnob.DashSharpness, 25f, "0"); TuningRow("Chgr windup t", TuningKnob.ChargerWindupTicks, 1f, "0"); TuningRow("Chgr lunge spd", TuningKnob.ChargerLungeSpeed, 1f, "0.0"); TuningRow("Chgr lunge t", TuningKnob.ChargerLungeDurationTicks, 1f, "0"); TuningRow("Chgr stagger t", TuningKnob.ChargerWhiffStaggerTicks, 1f, "0"); TuningRow("Grunt windup t", TuningKnob.GruntWindupTicks, 1f, "0"); } GUILayout.EndScrollView(); GUILayout.EndArea(); } static int IntField(string label, int value) { GUILayout.BeginHorizontal(); GUILayout.Label(label, GUILayout.Width(70)); string s = GUILayout.TextField(value.ToString(), GUILayout.Width(60)); GUILayout.EndHorizontal(); return int.TryParse(s, out var v) ? v : value; } // One live-tuning row: shows the current value (from the readout) + step buttons. A nudge fires the // authoritative server RPC AND an optimistic local apply (so the tuner's own predicted dash uses it now). static void TuningRow(string label, byte knob, float step, string fmt) { float cur = ProjectM.Simulation.TuningConfig.Get(TuningReadout.Current, knob); GUILayout.BeginHorizontal(); GUILayout.Label(label, GUILayout.Width(92)); GUILayout.Label(cur.ToString(fmt), GUILayout.Width(40)); if (GUILayout.Button("-", GUILayout.Width(26))) Nudge(knob, cur - step); if (GUILayout.Button("+", GUILayout.Width(26))) Nudge(knob, cur + step); GUILayout.EndHorizontal(); } static void Nudge(byte knob, float value) { DebugCommandSendSystem.SetTuning(knob, value); // authoritative (server applies + broadcasts; clamped) TuningReadout.SetLocal(knob, value); // optimistic local (instant feel for the tuner; clamped) } } } #endif