diff --git a/docs/superpowers/plans/2026-04-05-f1-debug-menu.md b/docs/superpowers/plans/2026-04-05-f1-debug-menu.md new file mode 100644 index 0000000..54fac5e --- /dev/null +++ b/docs/superpowers/plans/2026-04-05-f1-debug-menu.md @@ -0,0 +1,389 @@ +# F1 Debug Menu Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add an F1-toggled debug overlay with scene swapping and a command console that works regardless of the active scene. + +**Architecture:** A new `game.tscn` root scene hosts a `DebugMenu` CanvasLayer and an `ActiveSceneContainer` node. Scenes are loaded as children of the container. The command console uses a command pattern with explicit registration and an Expression eval fallback. + +**Tech Stack:** Godot 4.6, GDScript + +**Spec:** `docs/superpowers/specs/2026-04-05-f1-debug-menu-design.md` + +--- + +### Task 1: ConsoleCommand Base Class + +**Files:** +- Create: `resources/resource_definitions/console_command.gd` + +- [ ] **Step 1: Create the base class** + +```gdscript +class_name ConsoleCommand extends RefCounted + +func get_command_name() -> String: + return "" + +func get_help_text() -> String: + return "" + +func run(args: Array, context: Dictionary) -> String: + return "" +``` + +- [ ] **Step 2: Commit** + +--- + +### Task 2: HelpCommand + +**Files:** +- Create: `resources/console_commands/help_command.gd` + +- [ ] **Step 1: Create the command** + +`context["commands"]` will be the `Array[ConsoleCommand]` from DebugMenu. + +```gdscript +class_name HelpCommand extends ConsoleCommand + +func get_command_name() -> String: + return "help" + +func get_help_text() -> String: + return "Lists all available commands" + +func run(args: Array, context: Dictionary) -> String: + var commands: Array = context["commands"] + var lines: PackedStringArray = [] + for command: ConsoleCommand in commands: + lines.append("%s - %s" % [command.get_command_name(), command.get_help_text()]) + lines.append("Any other input is evaluated as a GDScript expression.") + return "\n".join(lines) +``` + +- [ ] **Step 2: Commit** + +--- + +### Task 3: ListScenesCommand + +**Files:** +- Create: `resources/console_commands/list_scenes_command.gd` + +- [ ] **Step 1: Create the command** + +`context["scene_registry"]` will be the scene registry array from DebugMenu. + +```gdscript +class_name ListScenesCommand extends ConsoleCommand + +func get_command_name() -> String: + return "list_scenes" + +func get_help_text() -> String: + return "Lists available scenes for swapping" + +func run(args: Array, context: Dictionary) -> String: + var registry: Array = context["scene_registry"] + var lines: PackedStringArray = [] + for entry: Dictionary in registry: + lines.append(entry["name"]) + return "\n".join(lines) +``` + +- [ ] **Step 2: Commit** + +--- + +### Task 4: SwapCommand + +**Files:** +- Create: `resources/console_commands/swap_command.gd` + +- [ ] **Step 1: Create the command** + +`context["debug_menu"]` will be the DebugMenu node reference, which has the `swap_scene()` method. + +```gdscript +class_name SwapCommand extends ConsoleCommand + +func get_command_name() -> String: + return "swap" + +func get_help_text() -> String: + return "swap - Swap to a scene by name (use list_scenes to see options)" + +func run(args: Array, context: Dictionary) -> String: + if args.size() == 0: + return "Usage: swap " + + var search_name := " ".join(args).to_lower() + var registry: Array = context["scene_registry"] + for entry: Dictionary in registry: + if entry["name"].to_lower() == search_name: + var debug_menu: Node = context["debug_menu"] + debug_menu.call_deferred("swap_scene", entry) + return "Swapping to: %s" % entry["name"] + + return "Scene not found: %s" % search_name +``` + +- [ ] **Step 2: Commit** + +--- + +### Task 5: DebugMenu Script + +**Files:** +- Create: `nodes/debug_menu.gd` + +- [ ] **Step 1: Create the script** + +```gdscript +class_name DebugMenu extends CanvasLayer + +signal scene_swapped +signal close_requested + +var active_scene_container: Node + +var scene_registry: Array = [ + { "name": "Strategy Phase", "path": "res://scenes/strategy_phase.tscn" }, + { "name": "Main Menu", "path": "res://scenes/main_menu.tscn" }, + { "name": "Visual Novel", "path": "res://scenes/vn_scene.tscn" }, + { "name": "Dialogue", "path": "res://scenes/dialogue_scene.tscn" }, +] + +var commands: Array[ConsoleCommand] = [] + +@onready var panel: PanelContainer = %Panel +@onready var scene_list: VBoxContainer = %SceneList +@onready var command_input: LineEdit = %CommandInput +@onready var result_label: Label = %ResultLabel + +func _ready() -> void: + commands = [ + HelpCommand.new(), + ListScenesCommand.new(), + SwapCommand.new(), + ] + _build_scene_buttons() + command_input.text_submitted.connect(_on_command_submitted) + result_label.text = "" + +func _build_scene_buttons() -> void: + for child in scene_list.get_children(): + child.queue_free() + for entry: Dictionary in scene_registry: + var button := Button.new() + button.text = entry["name"] + button.pressed.connect(swap_scene.bind(entry)) + scene_list.add_child(button) + +func swap_scene(entry: Dictionary) -> void: + for child in active_scene_container.get_children(): + child.queue_free() + var scene: PackedScene = load(entry["path"]) + var instance := scene.instantiate() + active_scene_container.add_child(instance) + if entry.has("setup"): + _apply_setup(entry["setup"], instance) + close_requested.emit() + +func _apply_setup(setup_key: String, scene_instance: Node) -> void: + match setup_key: + _: + push_warning("Unknown setup key: %s" % setup_key) + +func _on_command_submitted(text: String) -> void: + command_input.text = "" + if text.strip_edges().is_empty(): + return + var result := _execute_command(text.strip_edges()) + _show_result(result) + +func _execute_command(input: String) -> String: + var parts := input.split(" ", false) + var command_name := parts[0] + var args: Array = [] + if parts.size() > 1: + args = Array(parts.slice(1)) + + var context := { + "commands": commands, + "scene_registry": scene_registry, + "debug_menu": self, + "active_scene_container": active_scene_container, + } + + for command: ConsoleCommand in commands: + if command.get_command_name() == command_name: + return command.run(args, context) + + return _eval_expression(input) + +func _eval_expression(input: String) -> String: + var expression := Expression.new() + var error := expression.parse(input) + if error != OK: + return "Parse error: %s" % expression.get_error_text() + + var active_scene: Node = null + if active_scene_container.get_child_count() > 0: + active_scene = active_scene_container.get_child(0) + + var result = expression.execute([], active_scene) + if expression.has_execute_failed(): + return "Error: %s" % expression.get_error_text() + + return str(result) + +func _show_result(text: String) -> void: + result_label.text = text + var tween := create_tween() + tween.tween_interval(2.0) + tween.tween_callback(func(): result_label.text = "") +``` + +- [ ] **Step 2: Commit** + +--- + +### Task 6: DebugMenu Scene (Prefab) + +**Files:** +- Create: `prefabs/debug_menu.tscn` + +- [ ] **Step 1: Build the scene in the Godot editor** + +Create `prefabs/debug_menu.tscn` with this node tree: + +``` +DebugMenu (CanvasLayer, layer = 100, script = res://nodes/debug_menu.gd) +└── Panel (PanelContainer, unique name ✓) + ├── anchors: full rect (anchor_left=0, anchor_top=0, anchor_right=1, anchor_bottom=1) + └── MarginContainer + └── VBoxContainer + ├── Label (text = "Debug Menu", horizontal_alignment = CENTER) + ├── HSeparator + ├── Label (text = "Scenes:") + ├── SceneList (VBoxContainer, unique name ✓) + ├── HSeparator + ├── Label (text = "Console:") + ├── CommandInput (LineEdit, unique name ✓, placeholder_text = "Enter command or expression...") + └── ResultLabel (Label, unique name ✓, text = "") +``` + +Make sure to: +- Set `DebugMenu` CanvasLayer `layer` property to `100` +- Attach `res://nodes/debug_menu.gd` as the script +- Use "Access as Unique Name" (%) on: `Panel`, `SceneList`, `CommandInput`, `ResultLabel` +- Set `Panel`'s layout to "Full Rect" so it covers the viewport +- Set `CommandInput.placeholder_text` to `"Enter command or expression..."` + +- [ ] **Step 2: Commit** + +--- + +### Task 7: Game Script + +**Files:** +- Create: `nodes/game.gd` + +- [ ] **Step 1: Create the script** + +```gdscript +class_name Game extends Node + +@onready var debug_menu: DebugMenu = $DebugMenu +@onready var active_scene_container: Node = $ActiveSceneContainer + +var _default_scene: PackedScene = preload("res://scenes/strategy_phase.tscn") + +func _ready() -> void: + debug_menu.active_scene_container = active_scene_container + debug_menu.close_requested.connect(_close_debug_menu) + debug_menu.visible = false + var instance := _default_scene.instantiate() + active_scene_container.add_child(instance) + +func _unhandled_input(event: InputEvent) -> void: + if event.is_action_pressed("debug_toggle"): + _toggle_debug_menu() + +func _toggle_debug_menu() -> void: + if debug_menu.visible: + _close_debug_menu() + else: + _open_debug_menu() + +func _open_debug_menu() -> void: + debug_menu.visible = true + active_scene_container.process_mode = Node.PROCESS_MODE_DISABLED + +func _close_debug_menu() -> void: + debug_menu.visible = false + active_scene_container.process_mode = Node.PROCESS_MODE_INHERIT +``` + +- [ ] **Step 2: Commit** + +--- + +### Task 8: Game Scene and Project Config + +**Files:** +- Create: `scenes/game.tscn` +- Modify: `project.godot` + +- [ ] **Step 1: Register the F1 input action** + +In Godot editor: Project > Project Settings > Input Map. Add a new action `debug_toggle` mapped to the F1 key. + +Alternatively, add this to `project.godot` under a new `[input]` section: + +```ini +[input] + +debug_toggle={ +"deadzone": 0.2, +"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":0,"physical_keycode":4194332,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +``` + +(Physical keycode `4194332` is F1 in Godot.) + +- [ ] **Step 2: Build the game scene in the Godot editor** + +Create `scenes/game.tscn` with this tree: + +``` +Game (Node, script = res://nodes/game.gd, process_mode = PROCESS_MODE_ALWAYS) +├── DebugMenu (instance of res://prefabs/debug_menu.tscn) +└── ActiveSceneContainer (Node) +``` + +- Set `Game`'s `process_mode` to `PROCESS_MODE_ALWAYS` in the inspector. +- Instance `prefabs/debug_menu.tscn` as a child named `DebugMenu`. +- Add a plain `Node` child named `ActiveSceneContainer`. + +- [ ] **Step 3: Update project.godot main scene** + +Change `run/main_scene` in `project.godot` to point to `scenes/game.tscn` (use Project > Project Settings > General > Application > Run > Main Scene in the editor, or update the UID reference manually). + +- [ ] **Step 4: Smoke test** + +Run the project. Expected: +1. Strategy phase loads as before inside ActiveSceneContainer. +2. Press F1 — debug menu overlay appears, game underneath freezes. +3. Press F1 again — debug menu hides, game resumes. +4. Click a scene button — scene swaps, debug menu closes. +5. Type `help` in command input, press Enter — lists commands in result label. +6. Type `list_scenes` — shows scene names. +7. Type `swap main menu` — swaps to main menu scene. +8. Type an expression like `2 + 2` — shows `4`. + +- [ ] **Step 5: Commit** diff --git a/docs/superpowers/specs/2026-04-05-f1-debug-menu-design.md b/docs/superpowers/specs/2026-04-05-f1-debug-menu-design.md new file mode 100644 index 0000000..14b54cd --- /dev/null +++ b/docs/superpowers/specs/2026-04-05-f1-debug-menu-design.md @@ -0,0 +1,145 @@ +# F1 Debug Menu Design + +## Overview + +An F1-toggled debug menu overlay that works regardless of the current scene. Introduces a new root scene (`game.tscn`) that hosts both the debug menu and the active game scene as children. The game pauses underneath while the debug menu is open. + +## Scene Tree Structure + +``` +Game (Node, process_mode = PROCESS_MODE_ALWAYS) +├── DebugMenu (CanvasLayer, layer 100) +│ ├── Panel (PanelContainer) +│ │ ├── SceneList (VBoxContainer of Buttons) +│ │ └── CommandInput (LineEdit) +│ └── ResultLabel (Label, brief flash for command output) +└── ActiveSceneContainer (Node) + └── +``` + +- `Game` is the new main scene, replacing `strategy_phase.tscn` as the entry point in `project.godot`. +- `DebugMenu` uses a high `CanvasLayer` (layer 100) so it always renders above the active scene. +- `ActiveSceneContainer` is a plain `Node` whose child gets swapped when changing scenes. +- `Game` has `process_mode = PROCESS_MODE_ALWAYS` so it can receive F1 input even while the tree is paused. + +## Responsibilities + +### Game (`game.gd`) + +Thin shell only: + +- F1 input toggles `DebugMenu` visibility. +- When debug menu opens: sets `ActiveSceneContainer.process_mode = PROCESS_MODE_DISABLED`. +- When debug menu closes: sets `ActiveSceneContainer.process_mode = PROCESS_MODE_INHERIT`. +- Loads the default scene (strategy_phase) into `ActiveSceneContainer` at startup. +- Passes a reference to `ActiveSceneContainer` to `DebugMenu` at `_ready()`. + +### DebugMenu (`debug_menu.gd`) + +Owns all debug functionality: + +- **Scene registry**: hardcoded array of scene entries. +- **Scene swapping**: frees old child of `ActiveSceneContainer`, instantiates new scene, applies setup if present. +- **Command console**: parses input, dispatches to registered commands or falls back to expression eval. +- **Command registration**: holds an explicit `Array[ConsoleCommand]` populated at `_ready()`. + +## Scene Swapping + +### Registry Format + +A hardcoded array in `DebugMenu`: + +```gdscript +var scene_registry: Array = [ + { "name": "Strategy Phase", "path": "res://scenes/strategy_phase.tscn" }, + { "name": "Strategy Phase (Test Data)", "path": "res://scenes/strategy_phase.tscn", "setup": "test_data" }, + { "name": "Main Menu", "path": "res://scenes/main_menu.tscn" }, + { "name": "Visual Novel", "path": "res://scenes/vn_scene.tscn" }, + { "name": "Dialogue", "path": "res://scenes/dialogue_scene.tscn" }, +] +``` + +### Swap Flow + +1. Free the current child of `ActiveSceneContainer`. +2. Instantiate the new scene from `path` and add as child. +3. If the entry has a `"setup"` key, call a setup method on `DebugMenu` that applies that configuration to the new scene instance. +4. Close the debug menu and signal `Game` to unpause. + +### Setup Hooks + +Setup functions live in `DebugMenu`, dispatched by matching the `"setup"` string key. This keeps test configurations self-contained within the debug system. + +## Command Console + +### Input + +A `LineEdit` at the bottom of the debug panel. On Enter: + +1. Split input text — first token is the command name, rest are arguments. +2. Check against registered commands by matching `get_command_name()`. +3. If found, call `run()` with args and context. +4. If no match, fall back to Godot's `Expression` class (evaluated with the active scene as base instance). +5. Show the result (or error) in `ResultLabel`, then hide it after 2 seconds using a `Timer` or `create_tween()`. + +### Command Pattern + +Base class: + +```gdscript +class_name ConsoleCommand + +func get_command_name() -> String: + return "" + +func get_help_text() -> String: + return "" + +func run(args: Array, context: Dictionary) -> String: + return "" +``` + +Each command extends `ConsoleCommand` and overrides these three methods. The `context` dictionary provides access to the active scene container, debug menu reference, and other shared state without coupling commands to specific nodes. + +Commands are explicitly registered in an `Array[ConsoleCommand]` in `DebugMenu._ready()`. + +### Starter Commands + +- **`help`** — lists all registered commands with their help text. +- **`swap `** — scene swap by name (matches against registry entry names). +- **`list_scenes`** — prints available scene registry entries. + +### Expression Eval Fallback + +Uses Godot's `Expression` class. The expression executes with the current active scene as the base instance, allowing direct access to nodes and properties in the loaded scene. Errors are displayed in `ResultLabel`. + +## File Organization + +``` +nodes/ +├── game.gd # Thin shell +├── debug_menu.gd # Scene registry, command console, swap logic + +resources/resource_definitions/ +├── console_command.gd # Base class + +resources/console_commands/ +├── swap_command.gd +├── help_command.gd +├── list_scenes_command.gd + +scenes/ +├── game.tscn # New root scene (new main_scene in project.godot) + +prefabs/ +├── debug_menu.tscn # CanvasLayer with panel, buttons, input, result label +``` + +Existing scenes are unchanged — they are loaded as children of `ActiveSceneContainer` instead of being the root. + +## Key Decisions + +- **No autoload**: The debug menu is a UI node, not a headless service. The `XXXServer` autoload pattern is reserved for game services like a future `SceneManagerServer`. +- **Explicit command registration**: Commands are listed in an array rather than auto-discovered from a folder. Simpler and more readable for a small number of commands. Can be swapped to auto-discovery later if needed. +- **Single-line output**: `ResultLabel` shows one result at a time with a brief display. No scrollable history for now. +- **Hardcoded scene list**: Entries can carry setup data/context, which auto-discovery from `.tscn` files wouldn't support. diff --git a/nodes/debug_menu.gd b/nodes/debug_menu.gd new file mode 100644 index 0000000..5f29ab5 --- /dev/null +++ b/nodes/debug_menu.gd @@ -0,0 +1,105 @@ +class_name DebugMenu extends CanvasLayer + +signal close_requested + +var active_scene_container: Node + +var scene_registry: Array = [ + { "name": "Battle Test", "path": "res://scenes/strategy_phase.tscn" }, + { "name": "Main Menu", "path": "res://scenes/main_menu.tscn" }, + { "name": "Dialogue Test", "path": "res://scenes/dialogue_scene.tscn" }, +] + +var commands: Array[ConsoleCommand] = [] +var _result_tween: Tween + +@onready var panel: PanelContainer = %Panel +@onready var scene_list: VBoxContainer = %SceneList +@onready var command_input: LineEdit = %CommandInput +@onready var result_label: Label = %ResultLabel + +func _ready() -> void: + commands = [ + HelpCommand.new(), + ListScenesCommand.new(), + SwapCommand.new(), + ] + _build_scene_buttons() + command_input.text_submitted.connect(_on_command_submitted) + result_label.text = "" + +func _build_scene_buttons() -> void: + for child in scene_list.get_children(): + child.queue_free() + for entry: Dictionary in scene_registry: + var button := Button.new() + button.text = entry["name"] + button.pressed.connect(swap_scene.bind(entry)) + scene_list.add_child(button) + +func swap_scene(entry: Dictionary) -> void: + for child in active_scene_container.get_children(): + active_scene_container.remove_child(child) + child.queue_free() + var scene: PackedScene = load(entry["path"]) + var instance := scene.instantiate() + active_scene_container.add_child(instance) + if entry.has("setup"): + _apply_setup(entry["setup"], instance) + close_requested.emit() + +func _apply_setup(setup_key: String, scene_instance: Node) -> void: + match setup_key: + _: + push_warning("Unknown setup key: %s" % setup_key) + +func _on_command_submitted(text: String) -> void: + command_input.text = "" + if text.strip_edges().is_empty(): + return + var result := _execute_command(text.strip_edges()) + _show_result(result) + +func _execute_command(input: String) -> String: + var parts := input.split(" ", false) + var command_name := parts[0] + var args: Array = [] + if parts.size() > 1: + args = Array(parts.slice(1)) + + var context := { + "commands": commands, + "scene_registry": scene_registry, + "debug_menu": self, + "active_scene_container": active_scene_container, + } + + for command: ConsoleCommand in commands: + if command.get_command_name() == command_name: + return command.run(args, context) + + return _eval_expression(input) + +func _eval_expression(input: String) -> String: + var expression := Expression.new() + var error := expression.parse(input) + if error != OK: + return "Parse error: %s" % expression.get_error_text() + + var active_scene: Node = null + if active_scene_container.get_child_count() > 0: + active_scene = active_scene_container.get_child(0) + + var result = expression.execute([], active_scene) + if expression.has_execute_failed(): + return "Error: %s" % expression.get_error_text() + + return str(result) + +func _show_result(text: String) -> void: + result_label.text = text + if _result_tween and _result_tween.is_valid(): + _result_tween.kill() + _result_tween = create_tween() + _result_tween.tween_interval(2.0) + _result_tween.tween_callback(func(): result_label.text = "") diff --git a/nodes/debug_menu.gd.uid b/nodes/debug_menu.gd.uid new file mode 100644 index 0000000..2cd7471 --- /dev/null +++ b/nodes/debug_menu.gd.uid @@ -0,0 +1 @@ +uid://c64yr8xvkb5cw diff --git a/nodes/game.gd b/nodes/game.gd new file mode 100644 index 0000000..9ed7efa --- /dev/null +++ b/nodes/game.gd @@ -0,0 +1,31 @@ +class_name Game extends Node + +@onready var debug_menu: DebugMenu = $DebugMenu +@onready var active_scene_container: Node = $ActiveSceneContainer + +var _default_scene: PackedScene = preload("res://scenes/main_menu.tscn") + +func _ready() -> void: + debug_menu.active_scene_container = active_scene_container + debug_menu.close_requested.connect(_close_debug_menu) + debug_menu.visible = false + var instance := _default_scene.instantiate() + active_scene_container.add_child(instance) + +func _unhandled_input(event: InputEvent) -> void: + if event.is_action_pressed("debug_toggle"): + _toggle_debug_menu() + +func _toggle_debug_menu() -> void: + if debug_menu.visible: + _close_debug_menu() + else: + _open_debug_menu() + +func _open_debug_menu() -> void: + debug_menu.visible = true + active_scene_container.process_mode = Node.PROCESS_MODE_DISABLED + +func _close_debug_menu() -> void: + debug_menu.visible = false + active_scene_container.process_mode = Node.PROCESS_MODE_INHERIT diff --git a/nodes/game.gd.uid b/nodes/game.gd.uid new file mode 100644 index 0000000..157f2ab --- /dev/null +++ b/nodes/game.gd.uid @@ -0,0 +1 @@ +uid://ifv6cww6fk6c diff --git a/prefabs/debug_menu.tscn b/prefabs/debug_menu.tscn new file mode 100644 index 0000000..ea4e2d1 --- /dev/null +++ b/prefabs/debug_menu.tscn @@ -0,0 +1,59 @@ +[gd_scene format=3] + +[ext_resource type="Script" path="res://nodes/debug_menu.gd" id="1_script"] + +[node name="DebugMenu" type="CanvasLayer"] +layer = 100 +script = ExtResource("1_script") + +[node name="Panel" type="PanelContainer" parent="."] +unique_name_in_owner = true +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="MarginContainer" type="MarginContainer" parent="Panel"] +layout_mode = 2 +theme_override_constants/margin_left = 12 +theme_override_constants/margin_top = 12 +theme_override_constants/margin_right = 12 +theme_override_constants/margin_bottom = 12 + +[node name="VBoxContainer" type="VBoxContainer" parent="Panel/MarginContainer"] +layout_mode = 2 + +[node name="TitleLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer"] +layout_mode = 2 +text = "Debug Menu" +horizontal_alignment = 1 + +[node name="HSeparator" type="HSeparator" parent="Panel/MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="ScenesLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer"] +layout_mode = 2 +text = "Scenes:" + +[node name="SceneList" type="VBoxContainer" parent="Panel/MarginContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="HSeparator2" type="HSeparator" parent="Panel/MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="ConsoleLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer"] +layout_mode = 2 +text = "Console:" + +[node name="CommandInput" type="LineEdit" parent="Panel/MarginContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +placeholder_text = "Enter command or expression..." + +[node name="ResultLabel" type="Label" parent="Panel/MarginContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "" +autowrap_mode = 3 diff --git a/project.godot b/project.godot index e92048c..cf89579 100644 --- a/project.godot +++ b/project.godot @@ -11,7 +11,7 @@ config_version=5 [application] config/name="Dungeon Lords" -run/main_scene="uid://c35md0oc82je2" +run/main_scene="res://scenes/game.tscn" config/features=PackedStringArray("4.6", "Mobile") config/icon="res://icon.svg" @@ -20,6 +20,14 @@ config/icon="res://icon.svg" window/size/viewport_width=800 window/size/viewport_height=600 +[input] + +debug_toggle={ +"deadzone": 0.2, +"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":0,"physical_keycode":4194332,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} + [physics] 3d/physics_engine="Jolt Physics" diff --git a/resources/console_commands/help_command.gd b/resources/console_commands/help_command.gd new file mode 100644 index 0000000..79537b3 --- /dev/null +++ b/resources/console_commands/help_command.gd @@ -0,0 +1,15 @@ +class_name HelpCommand extends ConsoleCommand + +func get_command_name() -> String: + return "help" + +func get_help_text() -> String: + return "Lists all available commands" + +func run(args: Array, context: Dictionary) -> String: + var commands: Array = context["commands"] + var lines: PackedStringArray = [] + for command: ConsoleCommand in commands: + lines.append("%s - %s" % [command.get_command_name(), command.get_help_text()]) + lines.append("Any other input is evaluated as a GDScript expression.") + return "\n".join(lines) diff --git a/resources/console_commands/help_command.gd.uid b/resources/console_commands/help_command.gd.uid new file mode 100644 index 0000000..a843af3 --- /dev/null +++ b/resources/console_commands/help_command.gd.uid @@ -0,0 +1 @@ +uid://bvat5xgudptct diff --git a/resources/console_commands/list_scenes_command.gd b/resources/console_commands/list_scenes_command.gd new file mode 100644 index 0000000..9e8b056 --- /dev/null +++ b/resources/console_commands/list_scenes_command.gd @@ -0,0 +1,14 @@ +class_name ListScenesCommand extends ConsoleCommand + +func get_command_name() -> String: + return "list_scenes" + +func get_help_text() -> String: + return "Lists available scenes for swapping" + +func run(args: Array, context: Dictionary) -> String: + var registry: Array = context["scene_registry"] + var lines: PackedStringArray = [] + for entry: Dictionary in registry: + lines.append(entry["name"]) + return "\n".join(lines) diff --git a/resources/console_commands/list_scenes_command.gd.uid b/resources/console_commands/list_scenes_command.gd.uid new file mode 100644 index 0000000..e5c8a32 --- /dev/null +++ b/resources/console_commands/list_scenes_command.gd.uid @@ -0,0 +1 @@ +uid://b51b3np7lxd3v diff --git a/resources/console_commands/swap_command.gd b/resources/console_commands/swap_command.gd new file mode 100644 index 0000000..85b7b2e --- /dev/null +++ b/resources/console_commands/swap_command.gd @@ -0,0 +1,21 @@ +class_name SwapCommand extends ConsoleCommand + +func get_command_name() -> String: + return "swap" + +func get_help_text() -> String: + return "swap - Swap to a scene by name (use list_scenes to see options)" + +func run(args: Array, context: Dictionary) -> String: + if args.size() == 0: + return "Usage: swap " + + var search_name := " ".join(args).to_lower() + var registry: Array = context["scene_registry"] + for entry: Dictionary in registry: + if entry["name"].to_lower() == search_name: + var debug_menu: Node = context["debug_menu"] + debug_menu.swap_scene(entry) + return "" + + return "Scene not found: %s" % search_name diff --git a/resources/console_commands/swap_command.gd.uid b/resources/console_commands/swap_command.gd.uid new file mode 100644 index 0000000..43b8897 --- /dev/null +++ b/resources/console_commands/swap_command.gd.uid @@ -0,0 +1 @@ +uid://b56j4uyjiaku1 diff --git a/resources/resource_definitions/console_command.gd b/resources/resource_definitions/console_command.gd new file mode 100644 index 0000000..81f6d68 --- /dev/null +++ b/resources/resource_definitions/console_command.gd @@ -0,0 +1,10 @@ +class_name ConsoleCommand extends RefCounted + +func get_command_name() -> String: + return "" + +func get_help_text() -> String: + return "" + +func run(args: Array, context: Dictionary) -> String: + return "" diff --git a/resources/resource_definitions/console_command.gd.uid b/resources/resource_definitions/console_command.gd.uid new file mode 100644 index 0000000..008eda9 --- /dev/null +++ b/resources/resource_definitions/console_command.gd.uid @@ -0,0 +1 @@ +uid://b2kk8l3kumxpr diff --git a/scenes/game.tscn b/scenes/game.tscn new file mode 100644 index 0000000..d8e5d87 --- /dev/null +++ b/scenes/game.tscn @@ -0,0 +1,12 @@ +[gd_scene format=3 uid="uid://gfrxev22t0bc"] + +[ext_resource type="Script" uid="uid://ifv6cww6fk6c" path="res://nodes/game.gd" id="1_script"] +[ext_resource type="PackedScene" path="res://prefabs/debug_menu.tscn" id="2_debug_menu"] + +[node name="Game" type="Node" unique_id=906681388] +process_mode = 1 +script = ExtResource("1_script") + +[node name="DebugMenu" parent="." unique_id=486799252 instance=ExtResource("2_debug_menu")] + +[node name="ActiveSceneContainer" type="Node" parent="." unique_id=2133680613]