Dev tool: switch player class (Warrior/Ranger) at runtime for testing

Editor-only class swap via the existing scalar dev-RPC family (new DebugOp.SetClass): F1/F2 keybind (ClassSwitchHotkeySystem), DebugOverlay '- Class -' buttons, and DebugCommandSendSystem.SetWarrior/SetRanger/SetClass statics. Server (DebugCommandReceiveSystem) swaps class in place on the spawned player: strips+re-seeds the ClassTraits StatModifier seeds, swaps the AbilityRef Fire slot, resets the ability cooldown, and heals a LIVING player to the new max (dead players skip the heal so respawn isn't raced). Server-authoritative + prediction-correct (same buffer-mutation path as GrantUpgrade); wire type unchanged so the RpcCollection hash is unaffected.

ClassTraits gains a shared Seeds core (spawn + swap can't drift), ClassSeedCount, IsClassSeed, a DynamicBuffer AppendSeeds overload, and Reapply. +3 EditMode tests (exact-count round-trip, value-equality fold, boundary/foreign-mod preservation); 351/351 green; Warrior<->Ranger round-trip Play-validated (server+client agree).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-18 21:23:33 -07:00
parent 4ac1ae5a2e
commit a74b761363
8 changed files with 231 additions and 12 deletions
@@ -40,6 +40,10 @@ namespace ProjectM.Client
public static void SetHeat(int heat) => Send(DebugOp.SetHeat, heat);
/// <summary>Set the <see cref="ProjectM.Simulation.TuningKnob"/> knob to value (server-applied, x1000 fixed-point; MC-0).</summary>
public static void SetTuning(byte knob, float value) => Send(DebugOp.SetTuning, knob, Mathf.RoundToInt(value * 1000f));
/// <summary>Swap the sender's class to <paramref name="classId"/> (a <see cref="ProjectM.Simulation.CharacterId"/> byte); server-authoritative (class-switch dev tool).</summary>
public static void SetClass(byte classId) => Send(DebugOp.SetClass, classId);
public static void SetWarrior() => SetClass(ClassTraits.WarriorClass);
public static void SetRanger() => SetClass(ClassTraits.RangerClass);
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
static void ResetOnEnterPlayMode() => s_Pending.Clear();