Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion config_resources/default_settings.tres
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[ext_resource type="PackedScene" uid="uid://dxep4xm1sl8im" path="res://scenes/key_mapping_menu.tscn" id="1_qho2n"]
[ext_resource type="PackedScene" uid="uid://cl4m4q2eurcn0" path="res://scenes/options_menu.tscn" id="2_b4w4u"]
[ext_resource type="Script" uid="uid://cgtk0ximoo5ty" path="res://scripts/game_settings_resource.gd" id="3_qb8m4"]
[ext_resource type="Script" uid="uid://cgtk0ximoo5ty" path="res://scripts/resources/game_settings_resource.gd" id="3_qb8m4"]

[resource]
script = ExtResource("3_qb8m4")
Expand Down
18 changes: 9 additions & 9 deletions files/docs/Development_Guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,15 +118,15 @@ use the following signature:
- `new_value`: **Variant** (The newly assigned, clamped value)

#### 2. Core Files Reference

| Component | File Path | Responsibility |
|----------------------------|-------------------------------------------|-----------------------------------------------------------------------------------|
| **Data Source (Subject)** | `res://scripts/game_settings_resource.gd` | Defines properties (difficulty, log level), performs clamping, and emits signals. |
| **Logic Observer** | `res://scripts/globals.gd` | Connects to the resource to trigger centralized logging and `_save_settings()`. |
| **UI Observer (Gameplay)** | `res://scripts/gameplay_settings.gd` | Syncs sliders and labels with the resource state using `set_value_no_signal`. |
| **UI Observer (Advanced)** | `res://scripts/advanced_settings.gd` | Syncs log level dropdowns and handles web-specific JavaScript callbacks. |
| **Persistence Settings** | `res://scripts/settings.gd` | Manages low-level `InputMap` serialization and legacy migration logic. |

<!-- markdownlint-disable MD033 -->
| Component | File Path | Responsibility |
|-------------------------------------|-----------------------------------------------------|--------------------------------------------------------------------------------------------|
| **Data Source (Subject)** | `res://scripts/resources/game_settings_resource.gd` | Defines properties (difficulty, log level), performs clamping, and emits signals. |
| <del>**Logic Observer** | <del>`res://scripts/globals.gd`</del> | <del>Connects to the resource to trigger centralized logging and `_save_settings()`.</del> |
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
| **UI Observer (Gameplay)** | `res://scripts/ui/menus/gameplay_settings.gd` | Syncs sliders and labels with the resource state using `set_value_no_signal`. |
| **UI Observer (Advanced)** | `res://scripts/ui/menus/advanced_settings.gd` | Syncs log level dropdowns and handles web-specific JavaScript callbacks. |
| <del>**Persistence Settings**</del> | <del>`res://scripts/settings.gd`</del> | <del>Manages low-level `InputMap` serialization and legacy migration logic.</del> |
<!-- markdownlint-enable MD033 -->
#### 3. Connection Example for UI

To prevent infinite recursion, UI handlers should always check for equality or use `no_signal` methods when responding to the resource:
Expand Down
4 changes: 2 additions & 2 deletions project.godot
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ general/default_playback_type.web=0

Globals="*res://scripts/core/globals.gd"
Settings="*res://scripts/core/settings.gd"
AudioConstants="*res://scripts/audio_constants.gd"
AudioConstants="*res://scripts/resources/audio_constants.gd"
AudioManager="*res://scripts/managers/audio_manager.gd"
AudioWebBridge="*res://scripts/audio_web_bridge.gd"
AudioWebBridge="*res://scripts/system/audio_web_bridge.gd"
Comment thread
coderabbitai[bot] marked this conversation as resolved.

[debug]

Expand Down
2 changes: 1 addition & 1 deletion scenes/Player.tscn
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[gd_scene load_steps=7 format=3 uid="uid://37rarq1yywmc"]

[ext_resource type="Script" uid="uid://bcplrymg5tt06" path="res://scripts/player.gd" id="1_tuyoq"]
[ext_resource type="Script" uid="uid://bcplrymg5tt06" path="res://scripts/entities/player.gd" id="1_tuyoq"]
[ext_resource type="PackedScene" uid="uid://bafpwe0qf0vm0" path="res://scenes/weapon.tscn" id="2_fjrip"]
[ext_resource type="PackedScene" uid="uid://blc0ioe12jlnk" path="res://scenes/bullet.tscn" id="3_smehm"]
[ext_resource type="PackedScene" uid="uid://bh47kclxgeeah" path="res://scenes/rotor_left.tscn" id="4_ur7pv"]
Expand Down
2 changes: 1 addition & 1 deletion scenes/bullet.tscn
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[gd_scene load_steps=4 format=3 uid="uid://blc0ioe12jlnk"]

[ext_resource type="Script" uid="uid://uyw8e8i6adxn" path="res://scripts/bullet.gd" id="1_mkf8s"]
[ext_resource type="Script" uid="uid://uyw8e8i6adxn" path="res://scripts/entities/bullet.gd" id="1_mkf8s"]
[ext_resource type="Texture2D" uid="uid://b7utqr70u1mex" path="res://files/sprite/laser_sprites/01.png" id="2_y25gk"]

[sub_resource type="CircleShape2D" id="CircleShape2D_l5glv"]
Expand Down
4 changes: 2 additions & 2 deletions scenes/main_scene.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@
[ext_resource type="Texture2D" uid="uid://btyfigdtk3x88" path="res://files/random_decor/crates_4.png" id="68_f3krf"]
[ext_resource type="Texture2D" uid="uid://bvxu5x1awjrjv" path="res://files/random_decor/dirt_001.png" id="69_7tyuc"]
[ext_resource type="Texture2D" uid="uid://f4hxu68qa4fi" path="res://files/random_decor/dirt_002.png" id="70_isor2"]
[ext_resource type="Script" uid="uid://blu5qujicfa7e" path="res://scripts/hud.gd" id="72_sgkfd"]
[ext_resource type="Script" uid="uid://b5x2ehdthhrla" path="res://scripts/parallax_manager.gd" id="76_qj6t7"]
[ext_resource type="Script" uid="uid://blu5qujicfa7e" path="res://scripts/ui/hud.gd" id="72_sgkfd"]
[ext_resource type="Script" uid="uid://b5x2ehdthhrla" path="res://scripts/managers/parallax_manager.gd" id="76_qj6t7"]

[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_pu3yx"]
bg_color = Color(0.2627451, 0.2627451, 0.2627451, 0.5882353)
Expand Down
2 changes: 1 addition & 1 deletion scenes/weapon.tscn
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://bafpwe0qf0vm0"]

[ext_resource type="Script" uid="uid://dcr6gx3oaboj8" path="res://scripts/weapon.gd" id="1_xasec"]
[ext_resource type="Script" uid="uid://dcr6gx3oaboj8" path="res://scripts/entities/weapon.gd" id="1_xasec"]

[node name="WeaponManager" type="Node2D"]
script = ExtResource("1_xasec")
52 changes: 52 additions & 0 deletions scripts/core/game_paths.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
## Copyright (C) 2026 Egor Kostan
## SPDX-License-Identifier: GPL-3.0-or-later
## game_paths.gd
## Centralized repository for all hardcoded script and scene paths.
## Use this class to reference paths globally to avoid fragility and improve refactoring.

class_name GamePaths
extends RefCounted

# =========================================================
# SCRIPT PATHS
# =========================================================

## Path to the player entity script.
const PLAYER: String = "res://scripts/entities/player.gd"

## Path to the HUD UI script.
const HUD: String = "res://scripts/ui/hud.gd"

## Path to the audio web bridge system script.
const AUDIO_WEB_BRIDGE: String = "res://scripts/system/audio_web_bridge.gd"

## Path to the input remap button component script.
const INPUT_REMAP_BUTTON: String = "res://scripts/ui/components/input_remap_button.gd"

## Path to the gameplay settings menu script.
const GAMEPLAY_SETTINGS: String = "res://scripts/ui/menus/gameplay_settings.gd"

## Path to the core settings singleton/script.
const SETTINGS: String = "res://scripts/core/settings.gd"

# =========================================================
# SCENE PATHS
# =========================================================

## Path to the audio settings menu scene.
const AUDIO_SETTINGS_SCENE: String = "res://scenes/audio_settings.tscn"

## Path to the main game scene.
const MAIN_SCENE: String = "res://scenes/main_scene.tscn"

## Path to the key mapping menu scene.
const KEY_MAPPING_SCENE: String = "res://scenes/key_mapping_menu.tscn"

## Path to the gameplay settings menu scene.
const GAMEPLAY_SETTINGS_SCENE: String = "res://scenes/gameplay_settings.tscn"

## Path to the pause menu scene.
const PAUSE_MENU_SCENE: String = "res://scenes/pause_menu.tscn"

## Path to the options menu scene.
const OPTIONS_MENU_SCENE: String = "res://scenes/options_menu.tscn"
1 change: 1 addition & 0 deletions scripts/core/game_paths.gd.uid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://cj4el7nxnxm07
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
4 changes: 2 additions & 2 deletions test/gut/gut_test_helper.gd
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
## Shared helper functions and mock builders for GUT unit tests.
extends RefCounted

const PLAYER_SCRIPT_PATH: String = "res://scripts/player.gd"
const PLAYER_SCRIPT_PATH: String = GamePaths.PLAYER


## Helper to safely hard-free a node without causing engine crashes.
Expand Down Expand Up @@ -60,7 +60,7 @@ static func build_mock_player_scene() -> Node:
panel.add_child(stats)

# Assign the extracted hud.gd script directly to the mock panel
var hud_script := load("res://scripts/hud.gd")
var hud_script := load(GamePaths.HUD)
if hud_script:
panel.set_script(hud_script)

Expand Down
2 changes: 1 addition & 1 deletion test/gut/test_audio_reset_button.gd
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

extends "res://addons/gut/test.gd"

var audio_scene: PackedScene = load("res://scenes/audio_settings.tscn")
var audio_scene: PackedScene = load(GamePaths.AUDIO_SETTINGS_SCENE)
var audio_instance: Control
var test_config_path: String = "user://test_reset.cfg"

Expand Down
2 changes: 1 addition & 1 deletion test/gut/test_audio_web_bridge.gd
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

extends "res://addons/gut/test.gd"

const AudioWebBridge = preload("res://scripts/audio_web_bridge.gd")
const AudioWebBridge = preload(GamePaths.AUDIO_WEB_BRIDGE)

func before_each() -> void:
# Reset AudioManager to a known clean state before each test
Expand Down
2 changes: 1 addition & 1 deletion test/gut/test_blank_key_labels_on_missing_config.gd
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

extends GutTest

const InputRemapButton := preload("res://scripts/input_remap_button.gd")
const InputRemapButton := preload(GamePaths.INPUT_REMAP_BUTTON)

const TEST_CONFIG_MISSING_PATH: String = "user://test_blank_missing.cfg"
const TEST_CONFIG_PARTIAL_PATH: String = "user://test_blank_partial.cfg"
Expand Down
2 changes: 1 addition & 1 deletion test/gut/test_decor_layer_transformations.gd
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ var viewport_mock: Vector2 = Vector2(1920, 1080)

func before_each() -> void:
await get_tree().process_frame
main_scene = preload("res://scenes/main_scene.tscn").instantiate()
main_scene = preload(GamePaths.MAIN_SCENE).instantiate()
add_child(main_scene)
await get_tree().process_frame

Expand Down
2 changes: 1 addition & 1 deletion test/gut/test_deduplication_on_device_switch.gd
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

extends GutTest

const InputRemapButton: Script = preload("res://scripts/input_remap_button.gd")
const InputRemapButton: Script = preload(GamePaths.INPUT_REMAP_BUTTON)
const TEST_ACTION: String = "speed_up"

var button: InputRemapButton
Expand Down
2 changes: 1 addition & 1 deletion test/gut/test_deduplication_on_rapid_remap.gd
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

extends GutTest

const InputRemapButton: Script = preload("res://scripts/input_remap_button.gd")
const InputRemapButton: Script = preload(GamePaths.INPUT_REMAP_BUTTON)
const TEST_ACTION: String = "speed_up"

var button: InputRemapButton
Expand Down
2 changes: 1 addition & 1 deletion test/gut/test_deduplication_on_remap.gd
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

extends GutTest

const InputRemapButton: Script = preload("res://scripts/input_remap_button.gd")
const InputRemapButton: Script = preload(GamePaths.INPUT_REMAP_BUTTON)
const TEST_ACTION: String = "speed_up"

var button: InputRemapButton
Expand Down
2 changes: 1 addition & 1 deletion test/gut/test_deduplication_on_reset.gd
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func before_each() -> void:
def_ev.physical_keycode = KEY_W
InputMap.action_add_event(TEST_ACTION, def_ev)
InputMap.action_add_event(TEST_ACTION, def_ev.duplicate()) # Duplicate
menu = load("res://scenes/key_mapping_menu.tscn").instantiate()
menu = load(GamePaths.KEY_MAPPING_SCENE).instantiate()
add_child(menu)
menu.keyboard.button_pressed = true # Keyboard mode

Expand Down
2 changes: 1 addition & 1 deletion test/gut/test_fuel_additional_edge_cases.gd
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func test_fuel_consumption_with_scaling() -> void:

# NEW: Instantiate the main scene locally and use GUT's add_child_autoqfree().
# This ensures the scene and all its dynamically generated Sprite2D children are safely queued for deletion.
main_scene = load("res://scenes/main_scene.tscn").instantiate()
main_scene = load(GamePaths.MAIN_SCENE).instantiate()
add_child_autoqfree(main_scene)
player_root = main_scene.get_node("Player")
player_root.fuel_timer.stop()
Expand Down
1 change: 1 addition & 0 deletions test/gut/test_fuel_integration.gd
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
## SPDX-License-Identifier: GPL-3.0-or-later
## test_fuel_integration_gut.gd
## GUT integration tests for Fuel System signals and persistence.

extends "res://addons/gut/test.gd"

const TEST_CONFIG_PATH: String = "user://test_fuel_persistence.cfg"
Expand Down
8 changes: 4 additions & 4 deletions test/gut/test_game_settings_resource.gd
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

extends "res://addons/gut/test.gd"

const GameplaySettings = preload("res://scripts/gameplay_settings.gd")
const GameplaySettings = preload(GamePaths.GAMEPLAY_SETTINGS)
var gameplay_menu: Control
var _resource: GameSettingsResource

Expand All @@ -20,7 +20,7 @@ func before_each() -> void:
Globals.settings = _resource

# Instantiate the menu for initialization tests
gameplay_menu = load("res://scenes/gameplay_settings.tscn").instantiate()
gameplay_menu = load(GamePaths.GAMEPLAY_SETTINGS_SCENE).instantiate()
# Inject mock wrapper to avoid real JS/OS calls during unit tests
gameplay_menu.os_wrapper = OSWrapper.new()

Expand Down Expand Up @@ -74,7 +74,7 @@ func test_gs_ready_01_02_ui_initialization_sync() -> void:
var test_difficulty: float = 1.7
_resource.difficulty = test_difficulty

var new_menu: Variant = load("res://scenes/gameplay_settings.tscn").instantiate()
var new_menu: Variant = load(GamePaths.GAMEPLAY_SETTINGS_SCENE).instantiate()
add_child_autofree(new_menu)
await get_tree().process_frame

Expand Down Expand Up @@ -121,7 +121,7 @@ func test_gs_ready_06_safe_init_non_web() -> void:
stub(mock_os, "has_feature").to_return(false)

# FIX: Instantiate from the SCENE, not just the script
var menu: Control = load("res://scenes/gameplay_settings.tscn").instantiate()
var menu: Control = load(GamePaths.GAMEPLAY_SETTINGS_SCENE).instantiate()
menu.os_wrapper = mock_os

add_child_autofree(menu)
Expand Down
4 changes: 2 additions & 2 deletions test/gut/test_gameplay_settings_js.gd
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@

extends "res://addons/gut/test.gd"

const GameplaySettings = preload("res://scripts/gameplay_settings.gd")
const GameplaySettings = preload(GamePaths.GAMEPLAY_SETTINGS)
var gameplay_menu: Control

func before_each() -> void:
# Fresh resource to isolate state
Globals.settings = GameSettingsResource.new()
gameplay_menu = load("res://scenes/gameplay_settings.tscn").instantiate()
gameplay_menu = load(GamePaths.GAMEPLAY_SETTINGS_SCENE).instantiate()
# Inject mock wrapper to simulate web environment
gameplay_menu.os_wrapper = OSWrapper.new()
add_child_autofree(gameplay_menu)
Expand Down
8 changes: 4 additions & 4 deletions test/gut/test_gameplay_settings_lifecycle.gd
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@

extends "res://addons/gut/test.gd"

const GameplaySettings = preload("res://scripts/gameplay_settings.gd")
const GameplaySettings = preload(GamePaths.GAMEPLAY_SETTINGS)
var gameplay_menu: Control

func before_each() -> void:
Globals.settings = GameSettingsResource.new()
gameplay_menu = load("res://scenes/gameplay_settings.tscn").instantiate()
gameplay_menu = load(GamePaths.GAMEPLAY_SETTINGS_SCENE).instantiate()
gameplay_menu.os_wrapper = OSWrapper.new()
add_child_autofree(gameplay_menu)
await get_tree().process_frame
Expand Down Expand Up @@ -56,7 +56,7 @@ func test_gs_life_02_back_button_restoration() -> void:

## GS-LIFE-08 | Web overlay visibility cleanup
func test_gs_life_08_web_overlay_cleanup() -> void:
var test_menu: Control = load("res://scenes/gameplay_settings.tscn").instantiate()
var test_menu: Control = load(GamePaths.GAMEPLAY_SETTINGS_SCENE).instantiate()
var mock_js_bridge: Variant = double(JavaScriptBridgeWrapper).new()
var mock_os: Variant = double(OSWrapper).new()

Expand Down Expand Up @@ -93,7 +93,7 @@ func test_gs_life_05_null_globals_safety() -> void:
## GS-LIFE-09 | Unexpected removal (unintentional exit) restores previous menu
func test_gs_life_09_unexpected_removal_restoration() -> void:
# 1. Setup: Create fresh instance but do not parent yet
var test_menu: Control = load("res://scenes/gameplay_settings.tscn").instantiate()
var test_menu: Control = load(GamePaths.GAMEPLAY_SETTINGS_SCENE).instantiate()

# 2. Mock a previous menu in the stack
var mock_prev: Control = Control.new()
Expand Down
4 changes: 2 additions & 2 deletions test/gut/test_gameplay_settings_ui.gd
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

extends "res://addons/gut/test.gd"

const GameplaySettings = preload("res://scripts/gameplay_settings.gd")
const GameplaySettings = preload(GamePaths.GAMEPLAY_SETTINGS)
var gameplay_menu: Control
var _resource: GameSettingsResource

Expand All @@ -16,7 +16,7 @@ func before_each() -> void:
_resource = GameSettingsResource.new()
Globals.settings = _resource

gameplay_menu = load("res://scenes/gameplay_settings.tscn").instantiate()
gameplay_menu = load(GamePaths.GAMEPLAY_SETTINGS_SCENE).instantiate()
# Inject mock wrapper to bypass real web/OS calls
gameplay_menu.os_wrapper = OSWrapper.new()

Expand Down
2 changes: 1 addition & 1 deletion test/gut/test_get_pause_binding_label_for_device.gd
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ extends GutTest
var settings: Node

func before_each() -> void:
settings = preload("res://scripts/settings.gd").new()
settings = preload(GamePaths.SETTINGS).new()
add_child_autofree(settings)

# Ensure clean input state
Expand Down
2 changes: 1 addition & 1 deletion test/gut/test_input_remap_button.gd
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

extends "res://addons/gut/test.gd"

const InputRemapButton = preload("res://scripts/input_remap_button.gd")
const InputRemapButton = preload(GamePaths.INPUT_REMAP_BUTTON)

var button: InputRemapButton
const TEST_ACTION: String = "test_action"
Expand Down
2 changes: 1 addition & 1 deletion test/gut/test_input_remap_button_device_aware.gd
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

extends "res://addons/gut/test.gd"

const InputRemapButton: Script = preload("res://scripts/input_remap_button.gd")
const InputRemapButton: Script = preload(GamePaths.INPUT_REMAP_BUTTON)

var button: InputRemapButton
const TEST_ACTION: String = "test_action"
Expand Down
2 changes: 1 addition & 1 deletion test/gut/test_input_remap_ec.gd
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
extends "res://addons/gut/test.gd"

const TEST_ACTION: String = "test_action"
const InputRemapButton = preload("res://scripts/input_remap_button.gd")
const InputRemapButton = preload(GamePaths.INPUT_REMAP_BUTTON)

var original_input_map: Dictionary = {}
var button: InputRemapButton
Expand Down
Loading
Loading