From 762c8969ab90d94b5cfa6240f6d0e159317f671c Mon Sep 17 00:00:00 2001 From: Frank Tovar Date: Tue, 21 Apr 2026 22:54:14 +0200 Subject: [PATCH] Add hero runtime --- SLICE4.md | 50 ++- godot/project.godot | 40 ++ godot/scenes/bootstrap/GameRoot.tscn | 4 +- godot/scenes/debug/HeroSandbox.tscn | 88 +++++ godot/scenes/hero/Hero.tscn | 17 + godot/scripts/bootstrap/GameRoot.cs | 4 + godot/scripts/debug/DebugBootMode.cs | 3 +- godot/scripts/debug/DebugOverlay.cs | 10 +- godot/scripts/debug/HeroSandboxController.cs | 364 ++++++++++++++++++ .../debug/HeroSandboxController.cs.uid | 1 + .../scripts/debug/commands/DebugCommandId.cs | 17 +- .../debug/commands/DebugCommandService.cs | 18 +- godot/scripts/hero/HeroActor.cs | 51 +++ godot/scripts/hero/HeroActor.cs.uid | 1 + godot/scripts/hero/HeroStateHudController.cs | 75 ++++ .../hero/HeroStateHudController.cs.uid | 1 + godot/scripts/hero/rules/HeroLifeState.cs | 10 + godot/scripts/hero/rules/HeroLifeState.cs.uid | 1 + .../scripts/hero/rules/HeroMissionSnapshot.cs | 7 + .../hero/rules/HeroMissionSnapshot.cs.uid | 1 + godot/scripts/hero/rules/HeroRuleConfig.cs | 38 ++ .../scripts/hero/rules/HeroRuleConfig.cs.uid | 1 + godot/scripts/hero/rules/HeroRuleResult.cs | 16 + .../scripts/hero/rules/HeroRuleResult.cs.uid | 1 + godot/scripts/hero/rules/HeroRunState.cs | 79 ++++ godot/scripts/hero/rules/HeroRunState.cs.uid | 1 + .../scripts/hero/rules/HeroRuntimeService.cs | 248 ++++++++++++ .../hero/rules/HeroRuntimeService.cs.uid | 1 + .../HeroRuntimeServiceTests.cs | 191 +++++++++ 29 files changed, 1327 insertions(+), 12 deletions(-) create mode 100644 godot/scenes/debug/HeroSandbox.tscn create mode 100644 godot/scenes/hero/Hero.tscn create mode 100644 godot/scripts/debug/HeroSandboxController.cs create mode 100644 godot/scripts/debug/HeroSandboxController.cs.uid create mode 100644 godot/scripts/hero/HeroActor.cs create mode 100644 godot/scripts/hero/HeroActor.cs.uid create mode 100644 godot/scripts/hero/HeroStateHudController.cs create mode 100644 godot/scripts/hero/HeroStateHudController.cs.uid create mode 100644 godot/scripts/hero/rules/HeroLifeState.cs create mode 100644 godot/scripts/hero/rules/HeroLifeState.cs.uid create mode 100644 godot/scripts/hero/rules/HeroMissionSnapshot.cs create mode 100644 godot/scripts/hero/rules/HeroMissionSnapshot.cs.uid create mode 100644 godot/scripts/hero/rules/HeroRuleConfig.cs create mode 100644 godot/scripts/hero/rules/HeroRuleConfig.cs.uid create mode 100644 godot/scripts/hero/rules/HeroRuleResult.cs create mode 100644 godot/scripts/hero/rules/HeroRuleResult.cs.uid create mode 100644 godot/scripts/hero/rules/HeroRunState.cs create mode 100644 godot/scripts/hero/rules/HeroRunState.cs.uid create mode 100644 godot/scripts/hero/rules/HeroRuntimeService.cs create mode 100644 godot/scripts/hero/rules/HeroRuntimeService.cs.uid create mode 100644 tests/SideScrollerGame.Content.Tests/HeroRuntimeServiceTests.cs diff --git a/SLICE4.md b/SLICE4.md index 1614355..e1f2f5b 100644 --- a/SLICE4.md +++ b/SLICE4.md @@ -17,12 +17,13 @@ This slice does not implement weapon firing, collectibles as physical pickup act - [x] (2026-04-21 20:22Z) Read repository rules, `PLANS.md`, `DESIGN.md`, `CODE.md`, `SLICE1.MD`, `SLICE2.MD`, `SLICE3.MD`, current debug command files, current sample content, current root boot wiring, and current project file list. - [x] (2026-04-21 20:22Z) Verified that Slice 3 has committed a debug command foundation, a debug sandbox boot mode, content validation tests, and a clean worktree. - [x] (2026-04-21 20:22Z) Created this Slice 4 ExecPlan. -- [ ] Implement pure hero runtime state and rules. -- [ ] Add unit tests for hero movement-independent rules. -- [ ] Add Godot hero actor, hero HUD, and hero sandbox. -- [ ] Add hero sandbox debug commands, keyboard shortcuts, and headless smoke script. -- [ ] Wire `HeroSandbox` into debug boot and validate existing boot modes still work. -- [ ] Run formatting, tests, Godot smokes, update this plan, and commit the completed slice. +- [x] (2026-04-21 20:53Z) Implemented pure hero runtime state and rules. +- [x] (2026-04-21 20:53Z) Added unit tests for hero movement-independent rules. +- [x] (2026-04-21 20:53Z) Added Godot hero actor, hero HUD, and hero sandbox. +- [x] (2026-04-21 20:53Z) Added hero sandbox debug commands, keyboard shortcuts, and headless smoke script. +- [x] (2026-04-21 20:53Z) Wired `HeroSandbox` into debug boot and validated existing boot modes still work. +- [x] (2026-04-21 20:53Z) Ran formatting, tests, Godot smokes, and updated this plan. +- [ ] Commit the completed slice. ## Surprises & Discoveries @@ -38,6 +39,12 @@ This slice does not implement weapon firing, collectibles as physical pickup act - Observation: There is no hero runtime code yet. Evidence: `rg --files godot\scripts godot\scenes tests` lists bootstrap, content, debug, and menu files only; there are no `hero` scene or script folders. +- Observation: Running `dotnet test SideScrollerGame.sln` and `dotnet build SideScrollerGame.sln` in parallel can still race on Godot's generated temp files. + Evidence: the baseline parallel validation reported `MSB3713` because `SideScrollerGame.Godot.AssemblyInfo.cs` was being used by another process. Running `dotnet test` and `dotnet build` serially succeeded. + +- Observation: Godot generated `.cs.uid` files for every new C# hero script during the headless project scan. + Evidence: after `.\godot --headless --path godot --build-solutions --quit`, files such as `godot/scripts/hero/HeroActor.cs.uid`, `godot/scripts/hero/rules/HeroRuntimeService.cs.uid`, and `godot/scripts/debug/HeroSandboxController.cs.uid` appeared. + ## Decision Log - Decision: Implement hero gameplay rules as plain C# before Godot node behavior. @@ -62,7 +69,34 @@ This slice does not implement weapon firing, collectibles as physical pickup act ## Outcomes & Retrospective -No implementation has been performed yet. This plan defines the target result for Slice 4 and must be updated as code, tests, validation, and any course corrections happen. +Completed. Slice 4 added pure hero runtime rules under `godot/scripts/hero/rules/`, a controllable Godot hero actor, a hero state HUD, a dedicated hero sandbox scene, hero debug command ids and scene command handler support, hero-specific keyboard actions, `HeroSandbox` debug boot wiring, and a deterministic headless hero smoke script. + +Validation completed from `D:\Code\zfxaction26_1`: + + dotnet test SideScrollerGame.sln + Passed! - Failed: 0, Passed: 36, Skipped: 0, Total: 36 + + dotnet build SideScrollerGame.sln + Build succeeded. + 0 Warning(s) + 0 Error(s) + + .\godot --headless --path godot --build-solutions --quit + Exited successfully after project scan, .NET build, and script class registration. + + .\godot --headless --path godot -- --debug-boot=hero-sandbox --debug-script=hero-smoke --seed=444 + Hero sandbox smoke succeeded + + .\godot --headless --path godot -- --debug-boot=debug-sandbox --debug-script=foundation-smoke --seed=333 + Debug foundation smoke succeeded + + .\godot --headless --path godot -- --debug-boot=content-browser --content-validate-only + Content validation succeeded + + .\godot --headless --path godot -- --debug-boot=smoke --seed=12345 + Smoke scene loaded + +Remaining risk: hero movement bounds were validated through the implemented actor logic and scene smoke compilation, but were not manually inspected in an editor window during this headless iteration. ## Context and Orientation @@ -496,3 +530,5 @@ In `godot/scripts/bootstrap/GameRoot.cs`, extend boot loading so: loads `res://scenes/debug/HeroSandbox.tscn`. Revision note 2026-04-21: Created this ExecPlan from the current Slice 3 project state. The plan scopes Slice 4 to hero runtime rules, a controllable hero actor, a hero sandbox, hero debug commands, and headless smoke validation while leaving weapons, pickups as actors, squadron actors, enemies, and mission flow to later slices. + +Revision note 2026-04-21: Updated this ExecPlan after implementation. Progress, discoveries, and outcomes now reflect the completed hero runtime, sandbox, smoke validation, serial build requirement, and remaining editor-inspection risk. diff --git a/godot/project.godot b/godot/project.godot index 27643f2..84f85c3 100644 --- a/godot/project.godot +++ b/godot/project.godot @@ -132,3 +132,43 @@ debug_toggle_gameplay_bounds={ "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":55,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } +debug_damage_hero={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":56,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +debug_kill_hero={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":57,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +debug_rebirth_hero={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":48,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +debug_add_points={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":90,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +debug_add_shield={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":88,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +debug_remove_shield={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":67,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +debug_toggle_primary_slot={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":86,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +debug_clear_hero_inventory={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":66,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} diff --git a/godot/scenes/bootstrap/GameRoot.tscn b/godot/scenes/bootstrap/GameRoot.tscn index a5eca27..314ca4e 100644 --- a/godot/scenes/bootstrap/GameRoot.tscn +++ b/godot/scenes/bootstrap/GameRoot.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=8 format=3 uid="uid://b1fxc23gkbqre"] +[gd_scene load_steps=9 format=3 uid="uid://b1fxc23gkbqre"] [ext_resource type="Script" path="res://scripts/bootstrap/GameRoot.cs" id="1_game_root"] [ext_resource type="Script" path="res://scripts/debug/DebugOverlay.cs" id="2_debug_overlay"] @@ -7,6 +7,7 @@ [ext_resource type="PackedScene" path="res://scenes/debug/SmokeScene.tscn" id="4_smoke"] [ext_resource type="PackedScene" path="res://scenes/debug/ContentBrowser.tscn" id="5_content_browser"] [ext_resource type="PackedScene" path="res://scenes/debug/DebugSandbox.tscn" id="6_debug_sandbox"] +[ext_resource type="PackedScene" path="res://scenes/debug/HeroSandbox.tscn" id="7_hero_sandbox"] [node name="GameRoot" type="Node"] script = ExtResource("1_game_root") @@ -14,6 +15,7 @@ MenuScene = ExtResource("3_menu") SmokeScene = ExtResource("4_smoke") ContentBrowserScene = ExtResource("5_content_browser") DebugSandboxScene = ExtResource("6_debug_sandbox") +HeroSandboxScene = ExtResource("7_hero_sandbox") [node name="DebugCommandNode" type="Node" parent="."] process_mode = 3 diff --git a/godot/scenes/debug/HeroSandbox.tscn b/godot/scenes/debug/HeroSandbox.tscn new file mode 100644 index 0000000..6afd481 --- /dev/null +++ b/godot/scenes/debug/HeroSandbox.tscn @@ -0,0 +1,88 @@ +[gd_scene load_steps=4 format=3 uid="uid://dchtr3qrw2omq"] + +[ext_resource type="Script" path="res://scripts/debug/HeroSandboxController.cs" id="1_hero_sandbox"] +[ext_resource type="PackedScene" path="res://scenes/hero/Hero.tscn" id="2_hero"] +[ext_resource type="Script" path="res://scripts/hero/HeroStateHudController.cs" id="3_hero_hud"] + +[node name="HeroSandbox" type="Control"] +process_mode = 3 +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_hero_sandbox") + +[node name="ButtonPanel" type="PanelContainer" parent="."] +process_mode = 3 +layout_mode = 1 +anchors_preset = 10 +anchor_right = 1.0 +offset_left = 16.0 +offset_top = 16.0 +offset_right = -960.0 +offset_bottom = 704.0 +grow_horizontal = 2 +custom_minimum_size = Vector2(220, 0) + +[node name="Scroll" type="ScrollContainer" parent="ButtonPanel"] +layout_mode = 2 + +[node name="Buttons" type="VBoxContainer" parent="ButtonPanel/Scroll"] +layout_mode = 2 + +[node name="Playfield" type="Control" parent="."] +layout_mode = 1 +anchors_preset = 0 +offset_left = 260.0 +offset_top = 64.0 +offset_right = 960.0 +offset_bottom = 560.0 + +[node name="PlayBounds" type="ColorRect" parent="Playfield"] +layout_mode = 0 +offset_left = 280.0 +offset_top = 96.0 +offset_right = 920.0 +offset_bottom = 516.0 +color = Color(0.1, 0.22, 0.28, 0.28) + +[node name="Hero" parent="Playfield" instance=ExtResource("2_hero")] +position = Vector2(600, 306) + +[node name="HudPanel" type="PanelContainer" parent="."] +process_mode = 3 +layout_mode = 1 +anchors_preset = 11 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -348.0 +offset_top = 16.0 +offset_right = -16.0 +offset_bottom = 268.0 +grow_horizontal = 0 +script = ExtResource("3_hero_hud") + +[node name="StateLabel" type="Label" parent="HudPanel"] +layout_mode = 2 +text = "Hero: unbound" + +[node name="LogPanel" type="PanelContainer" parent="."] +process_mode = 3 +layout_mode = 1 +anchors_preset = 11 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -348.0 +offset_top = 288.0 +offset_right = -16.0 +offset_bottom = 704.0 +grow_horizontal = 0 + +[node name="LogScroll" type="ScrollContainer" parent="LogPanel"] +layout_mode = 2 + +[node name="LogLabel" type="Label" parent="LogPanel/LogScroll"] +layout_mode = 2 +text = "" diff --git a/godot/scenes/hero/Hero.tscn b/godot/scenes/hero/Hero.tscn new file mode 100644 index 0000000..9c5da70 --- /dev/null +++ b/godot/scenes/hero/Hero.tscn @@ -0,0 +1,17 @@ +[gd_scene load_steps=2 format=3 uid="uid://b1dwy8sg78kxp"] + +[ext_resource type="Script" path="res://scripts/hero/HeroActor.cs" id="1_hero_actor"] + +[node name="Hero" type="Node2D"] +script = ExtResource("1_hero_actor") + +[node name="Body" type="Polygon2D" parent="."] +color = Color(0.2, 0.85, 1, 1) +polygon = PackedVector2Array(-22, -14, 24, 0, -22, 14, -10, 0) + +[node name="Label" type="Label" parent="."] +offset_left = -22.0 +offset_top = 18.0 +offset_right = 30.0 +offset_bottom = 41.0 +text = "Hero" diff --git a/godot/scripts/bootstrap/GameRoot.cs b/godot/scripts/bootstrap/GameRoot.cs index abc60e2..4c970c9 100644 --- a/godot/scripts/bootstrap/GameRoot.cs +++ b/godot/scripts/bootstrap/GameRoot.cs @@ -28,6 +28,7 @@ public partial class GameRoot : Node DebugBootMode.Smoke => SmokeScene, DebugBootMode.ContentBrowser => ContentBrowserScene, DebugBootMode.DebugSandbox => DebugSandboxScene, + DebugBootMode.HeroSandbox => HeroSandboxScene, _ => MenuScene }; @@ -72,6 +73,9 @@ public partial class GameRoot : Node [Export] public PackedScene? DebugSandboxScene { get; set; } + [Export] + public PackedScene? HeroSandboxScene { get; set; } + private Node? m_LoadedScene; private DebugSettings? m_Settings; private DebugCommandNode? m_CommandNode; diff --git a/godot/scripts/debug/DebugBootMode.cs b/godot/scripts/debug/DebugBootMode.cs index 59824f1..971f104 100644 --- a/godot/scripts/debug/DebugBootMode.cs +++ b/godot/scripts/debug/DebugBootMode.cs @@ -5,5 +5,6 @@ public enum DebugBootMode Menu, Smoke, ContentBrowser, - DebugSandbox + DebugSandbox, + HeroSandbox } \ No newline at end of file diff --git a/godot/scripts/debug/DebugOverlay.cs b/godot/scripts/debug/DebugOverlay.cs index 6d67136..05b8565 100644 --- a/godot/scripts/debug/DebugOverlay.cs +++ b/godot/scripts/debug/DebugOverlay.cs @@ -32,6 +32,12 @@ public partial class DebugOverlay : CanvasLayer Refresh(); } + public void SetHeroSummary(string summary) + { + m_HeroSummary = summary; + Refresh(); + } + private void Refresh() { Label label = EnsureLabel(); @@ -42,7 +48,8 @@ public partial class DebugOverlay : CanvasLayer DebugRuntimeState state = m_Service.State; Visible = state.OverlayVisible; - label.Text = $"Debug boot: {m_Settings.BootMode}\n" + $"Scene: {m_LoadedSceneId}\n" + $"Seed: {state.Seed}\n" + $"Difficulty: {state.ActiveDifficultyId}\n" + $"Paused: {state.IsPaused}\n" + $"Time scale: {state.TimeScale.ToString(CultureInfo.InvariantCulture)}\n" + $"Marker: {DisplayOrNone(state.CurrentMarkerId)}\n" + $"Spawned: {state.SpawnedActorCount} ({DisplayOrNone(state.LastSpawnedActorId)})\n" + $"Flags: invuln={state.Invulnerable}, ammo={state.InfiniteSpecialAmmo}, nofire={state.NoEnemyFire}\n" + $"Debug draw: collisions={state.ShowCollisionShapes}, bounds={state.ShowGameplayBounds}"; + string heroLine = string.IsNullOrWhiteSpace(m_HeroSummary) ? string.Empty : $"\nHero: {m_HeroSummary}"; + label.Text = $"Debug boot: {m_Settings.BootMode}\n" + $"Scene: {m_LoadedSceneId}\n" + $"Seed: {state.Seed}\n" + $"Difficulty: {state.ActiveDifficultyId}\n" + $"Paused: {state.IsPaused}\n" + $"Time scale: {state.TimeScale.ToString(CultureInfo.InvariantCulture)}\n" + $"Marker: {DisplayOrNone(state.CurrentMarkerId)}\n" + $"Spawned: {state.SpawnedActorCount} ({DisplayOrNone(state.LastSpawnedActorId)})\n" + $"Flags: invuln={state.Invulnerable}, ammo={state.InfiniteSpecialAmmo}, nofire={state.NoEnemyFire}\n" + $"Debug draw: collisions={state.ShowCollisionShapes}, bounds={state.ShowGameplayBounds}" + heroLine; } private Label EnsureLabel() @@ -74,4 +81,5 @@ public partial class DebugOverlay : CanvasLayer private DebugCommandService? m_Service; private DebugSettings? m_Settings; private string m_LoadedSceneId = "none"; + private string m_HeroSummary = string.Empty; } \ No newline at end of file diff --git a/godot/scripts/debug/HeroSandboxController.cs b/godot/scripts/debug/HeroSandboxController.cs new file mode 100644 index 0000000..f5f3cb0 --- /dev/null +++ b/godot/scripts/debug/HeroSandboxController.cs @@ -0,0 +1,364 @@ +#nullable enable + +using System; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Godot; +using SideScrollerGame.Content.Samples; +using SideScrollerGame.Debug.Commands; +using SideScrollerGame.Hero; +using SideScrollerGame.Hero.Rules; + +namespace SideScrollerGame.Debug; + +public partial class HeroSandboxController : Control +{ + public override void _Ready() + { + ProcessMode = ProcessModeEnum.Always; + m_CommandNode = GetNodeOrNull("/root/GameRoot/DebugCommandNode"); + m_Overlay = GetNodeOrNull("/root/GameRoot/DebugOverlay"); + if (m_CommandNode is null) + { + GD.PushError("Hero sandbox needs /root/GameRoot/DebugCommandNode."); + return; + } + + BindSceneNodes(); + RegisterHeroCommands(); + CreateHeroRuntime(); + CreateButtons(); + + m_CommandNode.Service.CommandExecuted += HandleCommandExecuted; + m_CommandNode.Service.RegisterRestartHandler(RestartSandbox); + + if (ShouldRunHeroSmoke()) + { + _ = RunHeroSmokeAsync(); + } + } + + public override void _UnhandledInput(InputEvent @event) + { + if (@event.IsActionPressed("debug_damage_hero")) + { + Execute(DebugCommandId.DamageHero); + } + else if (@event.IsActionPressed("debug_kill_hero")) + { + Execute(DebugCommandId.KillHero); + } + else if (@event.IsActionPressed("debug_rebirth_hero")) + { + Execute(DebugCommandId.RebirthHero); + } + else if (@event.IsActionPressed("debug_add_points")) + { + Execute(DebugCommandId.AddHeroPoints, "100"); + } + else if (@event.IsActionPressed("debug_add_shield")) + { + Execute(DebugCommandId.AddHeroShield); + } + else if (@event.IsActionPressed("debug_remove_shield")) + { + Execute(DebugCommandId.RemoveHeroShield); + } + else if (@event.IsActionPressed("debug_toggle_primary_slot")) + { + Execute(DebugCommandId.TogglePrimaryWeaponSlot); + } + else if (@event.IsActionPressed("debug_clear_hero_inventory")) + { + Execute(DebugCommandId.ClearHeroInventory); + } + else if (@event.IsActionPressed("debug_toggle_invulnerability")) + { + Execute(DebugCommandId.ToggleInvulnerability); + } + else if (@event.IsActionPressed("pause_game") || @event.IsActionPressed("debug_pause")) + { + Execute(DebugCommandId.TogglePause); + } + else if (@event.IsActionPressed("debug_frame_step")) + { + Execute(DebugCommandId.FrameStep); + } + else if (@event.IsActionPressed("quick_restart")) + { + Execute(DebugCommandId.RestartMission); + } + } + + private void BindSceneNodes() + { + m_Hero = GetNodeOrNull("Playfield/Hero"); + m_Hud = GetNodeOrNull("HudPanel"); + m_LogLabel = GetNodeOrNull