2aebc37115
Cone/SpecialSlam damage lands at its visual contact via ConeContactPending (knob 32, 0=legacy; early-flush + resolve re-validate; death + BOTH class-swap paths drop armed pendings — fixes the shipped melee death-strand in the same stroke). Zones: [GhostField] Caster/Radius/NextTick + ZoneTelegraphSystem (Geyser latch contract on InterpolationTick; rim = true radius; arm grows, persistent phase drains). Cone cues latch to contact (FireStartRaw, C14); TuningConfig.Defaults() fallback at client cue sites (release-build timing fix). G4: SaturationMath ally-FX degrade (living-enemy census, solo-exempt; enemy telegraphs structurally exempt) + CombatStressDebug + overlay saturation rows. Reviews wf_98bf1268 (13 confirmed folded) / wf_9757d214 (5 confirmed fixed). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
176 lines
9.4 KiB
C#
176 lines
9.4 KiB
C#
#if UNITY_EDITOR
|
|
using ProjectM.Simulation;
|
|
using UnityEngine;
|
|
|
|
namespace ProjectM.Client
|
|
{
|
|
/// <summary>
|
|
/// EDITOR-ONLY in-game dev-tools overlay (IMGUI dev overlay). Buttons enqueue
|
|
/// <see cref="DebugCommandRequest"/> RPCs through <see cref="DebugCommandSendSystem"/>, 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
|
|
/// (<see cref="AimPresentation.ForceCursorVisible"/>) so its buttons stay clickable even while aiming.
|
|
/// Stripped from player builds (#if UNITY_EDITOR).
|
|
/// </summary>
|
|
public class DebugOverlay : MonoBehaviour
|
|
{
|
|
bool _open = true;
|
|
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 -");
|
|
if (GUILayout.Button("Force Next Wave")) DebugCommandSendSystem.SpawnWave();
|
|
if (GUILayout.Button("Stop Waves")) DebugCommandSendSystem.StopWaves();
|
|
if (GUILayout.Button("Clear Enemies")) DebugCommandSendSystem.ClearEnemies();
|
|
|
|
GUILayout.Space(6);
|
|
GUILayout.Label("- Saturation (G4) -");
|
|
if (GUILayout.Button("Spawn 12 Drowners")) DebugCommandSendSystem.SpawnEnemy(0, 12); // gym roster kind 0; the standing worst-case load
|
|
CombatStressDebug.StressAllyFx = GUILayout.Toggle(CombatStressDebug.StressAllyFx, "Stress ally FX (3 fake casters)");
|
|
|
|
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("- Class (F1/F2) -");
|
|
GUILayout.BeginHorizontal();
|
|
if (GUILayout.Button("Warrior")) DebugCommandSendSystem.SetWarrior();
|
|
if (GUILayout.Button("Ranger")) DebugCommandSendSystem.SetRanger();
|
|
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.Space(4);
|
|
TuningRow("Melee dmg", TuningKnob.MeleeDamage, 1f, "0.0");
|
|
TuningRow("Melee range", TuningKnob.MeleeRange, 0.2f, "0.0");
|
|
TuningRow("Melee cone rad", TuningKnob.MeleeConeHalfAngleRad, 0.05f, "0.00");
|
|
TuningRow("Melee recover t", TuningKnob.MeleeRecoverTicks, 1f, "0");
|
|
TuningRow("Melee chain t", TuningKnob.MeleeChainGraceTicks, 1f, "0");
|
|
TuningRow("Melee move x", TuningKnob.MeleeSwingMoveScale, 0.05f, "0.00");
|
|
TuningRow("Melee knock spd", TuningKnob.MeleeKnockbackSpeed, 1f, "0.0");
|
|
TuningRow("Melee finish x", TuningKnob.MeleeFinisherMult, 0.1f, "0.0");
|
|
TuningRow("Melee combo len", TuningKnob.MeleeComboLength, 1f, "0");
|
|
// 07-20 feel forks: contact delay (0 = legacy same-tick cleave), input buffer (0 = off), reach-only finisher mult
|
|
TuningRow("Melee contact t", TuningKnob.MeleeContactTicks, 1f, "0");
|
|
TuningRow("Melee buffer t", TuningKnob.MeleeBufferTicks, 1f, "0");
|
|
TuningRow("Melee finish rng x", TuningKnob.MeleeFinisherRangeMult, 0.05f, "0.00");
|
|
GUILayout.Space(4);
|
|
TuningRow("Struct aggro w", TuningKnob.StructureAggroWeight, 0.1f, "0.00"); // EB-1: <1 prefers structures
|
|
// 07-15 facing/underwater feel (0 = no override: authored stat / cast const / authored sharpness)
|
|
TuningRow("Turn rate deg", TuningKnob.TurnRateDeg, 30f, "0");
|
|
TuningRow("Cast turn deg", TuningKnob.CastTurnRateDeg, 60f, "0");
|
|
TuningRow("Move sharp", TuningKnob.MoveSharpness, 0.5f, "0.0");
|
|
|
|
// 07-20 data-driven tuning: whole-gestalt FEEL PROFILES (sim knobs + FeelConfig in one JSON) --
|
|
// A/B a feel with one click instead of dialing 30 scalars; Save captures the hand-dialed state.
|
|
GUILayout.Space(6);
|
|
GUILayout.Label("- Feel Profiles -");
|
|
foreach (var prof in FeelProfileService.ListProfiles())
|
|
if (GUILayout.Button(System.IO.Path.GetFileNameWithoutExtension(prof)))
|
|
FeelProfileService.Apply(prof);
|
|
GUILayout.BeginHorizontal();
|
|
if (GUILayout.Button("Save current")) FeelProfileService.SaveCurrent();
|
|
if (GUILayout.Button("Rescan")) FeelProfileService.Rescan();
|
|
GUILayout.EndHorizontal();
|
|
if (GUILayout.Button(CombatProbe.Running ? "Probe running..." : "Combat probe (6 swings)") && !CombatProbe.Running)
|
|
CombatProbe.Run(6); // measured cadence/contact/DPS -> console
|
|
|
|
|
|
}
|
|
|
|
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
|