From e1e928389cfbe6be70ffffb71e4bc1b7b9f1b480 Mon Sep 17 00:00:00 2001 From: Yogi Wiguna Date: Tue, 10 Mar 2026 12:52:53 +0800 Subject: [PATCH] feat: Implement powerup inventory UI, settings menu, and foundational managers for input and game settings. --- project.godot | 2 +- scenes/lobby.gd | 27 +- scenes/lobby.tscn | 13 + scenes/main.gd | 50 ++-- scenes/ui/settings_menu.tscn | 322 +++++++++++++++++++++++ scripts/managers/player_input_manager.gd | 84 +++--- scripts/managers/settings_manager.gd | 100 +++++++ scripts/managers/settings_manager.gd.uid | 1 + scripts/managers/touch_controls.gd | 87 +++--- scripts/ui/powerup_inventory_ui.gd | 24 +- scripts/ui/settings_menu.gd | 110 ++++++++ scripts/ui/settings_menu.gd.uid | 1 + 12 files changed, 723 insertions(+), 98 deletions(-) create mode 100644 scenes/ui/settings_menu.tscn create mode 100644 scripts/managers/settings_manager.gd create mode 100644 scripts/managers/settings_manager.gd.uid create mode 100644 scripts/ui/settings_menu.gd create mode 100644 scripts/ui/settings_menu.gd.uid diff --git a/project.godot b/project.godot index 2099ab0..05ef364 100644 --- a/project.godot +++ b/project.godot @@ -20,7 +20,7 @@ config/features=PackedStringArray("4.6", "Forward Plus") config/icon="res://icon.svg" [autoload] - +SettingsManager="*uid://c1ouaaqnn0lrc" Nakama="*uid://bueyqhhvxe0tx" NakamaManager="*res://scripts/nakama_manager.gd" AuthManager="*res://scripts/managers/auth_manager.gd" diff --git a/scenes/lobby.gd b/scenes/lobby.gd index d6cf450..1d1aec6 100644 --- a/scenes/lobby.gd +++ b/scenes/lobby.gd @@ -6,6 +6,7 @@ extends Control @onready var create_room_btn = $MainMenuPanel/VBoxContainer/ButtonSection/CreateRoomBtn @onready var browse_rooms_btn = $MainMenuPanel/VBoxContainer/ButtonSection/BrowseRoomsBtn @onready var main_menu_profile_btn = $MainMenuPanel/VBoxContainer/ButtonSection/ProfileBtn +@onready var lobby_settings_btn = $MainMenuPanel/VBoxContainer/ButtonSection/SettingsBtn # UI References - Server Selection @onready var server_option = $MainMenuPanel/VBoxContainer/ServerSelectionSection/ServerOption @@ -34,6 +35,7 @@ extends Control @onready var copy_id_btn = $LobbyPanel/TopBar/MatchIdContainer/CopyIdBtn @onready var duration_option = $LobbyPanel/TopBar/SettingsSection/DurationOption @onready var duration_text_label = $LobbyPanel/TopBar/SettingsSection/DurationTextLabel +@onready var lobby_top_settings_btn = $LobbyPanel/TopBar/ProfileSection/LobbySettingsBtn @onready var random_spawn_check = $LobbyPanel/TopBar/SettingsSection/RandomSpawnCheck @onready var random_spawn_label = $LobbyPanel/TopBar/SettingsSection/RandomSpawnLabel @onready var enable_timer_check = $LobbyPanel/TopBar/SettingsSection/EnableTimerCheck @@ -114,6 +116,8 @@ func _ready(): browse_rooms_btn.pressed.connect(_on_browse_rooms_pressed) if main_menu_profile_btn: main_menu_profile_btn.pressed.connect(_on_profile_btn_pressed) + if lobby_settings_btn: + lobby_settings_btn.pressed.connect(_on_settings_pressed) # Connect Server Selection signals if server_option: @@ -133,7 +137,10 @@ func _ready(): # Connect button signals - Lobby profile_btn.pressed.connect(_on_profile_btn_pressed) - logout_btn.pressed.connect(_on_logout_pressed) + if logout_btn: + logout_btn.pressed.connect(_on_logout_pressed) + if lobby_top_settings_btn: + lobby_top_settings_btn.pressed.connect(_on_settings_pressed) copy_id_btn.pressed.connect(_on_copy_id_pressed) duration_option.item_selected.connect(_on_duration_selected) random_spawn_check.toggled.connect(_on_random_spawn_toggled) @@ -406,6 +413,24 @@ func _on_logout_pressed() -> void: AuthManager.logout() _go_to_login() +func _on_settings_pressed(): + var settings_menu = get_node_or_null("SettingsMenu") + if not settings_menu: + var scene = load("res://scenes/ui/settings_menu.tscn") + if scene: + settings_menu = scene.instantiate() + settings_menu.name = "SettingsMenu" + add_child(settings_menu) + + # Connect close button + var close_btn = settings_menu.get_node_or_null("PanelContainer/VBoxContainer/Header/CloseButton") + if close_btn: + # settings_menu.gd handles basic visibility, but we can override or add to it + pass + + if settings_menu: + settings_menu.open() + func _go_to_login() -> void: if get_tree(): get_tree().change_scene_to_file("res://scenes/ui/login_screen.tscn") diff --git a/scenes/lobby.tscn b/scenes/lobby.tscn index 4140237..887f086 100644 --- a/scenes/lobby.tscn +++ b/scenes/lobby.tscn @@ -138,6 +138,12 @@ layout_mode = 2 theme_override_font_sizes/font_size = 16 text = "BROWSE ROOMS" +[node name="SettingsBtn" type="Button" parent="MainMenuPanel/VBoxContainer/ButtonSection" unique_id=123456789] +custom_minimum_size = Vector2(0, 48) +layout_mode = 2 +theme_override_font_sizes/font_size = 16 +text = "SETTINGS" + [node name="ProfileBtn" type="Button" parent="MainMenuPanel/VBoxContainer/ButtonSection" unique_id=1640960506] custom_minimum_size = Vector2(0, 36) layout_mode = 2 @@ -287,6 +293,13 @@ theme_override_fonts/font = ExtResource("5_pc087") theme_override_font_sizes/font_size = 11 text = "Logout" +[node name="LobbySettingsBtn" type="Button" parent="LobbyPanel/TopBar/ProfileSection" unique_id=987654321] +custom_minimum_size = Vector2(32, 32) +layout_mode = 2 +theme_override_fonts/font = ExtResource("5_pc087") +theme_override_font_sizes/font_size = 14 +text = "⚙" + [node name="Spacer" type="Control" parent="LobbyPanel/TopBar" unique_id=379952578] layout_mode = 2 size_flags_horizontal = 3 diff --git a/scenes/main.gd b/scenes/main.gd index 29d884b..fc53e12 100644 --- a/scenes/main.gd +++ b/scenes/main.gd @@ -60,6 +60,20 @@ func _ready(): # Setup global multiplayer spawners (Stands, etc.) _setup_multiplayer_spawners() + + # Connect HUD Settings button to pause menu toggle + var hud_settings = get_node_or_null("TouchControls/TouchControls/SettingsBtn") + if hud_settings: + if hud_settings.pressed.is_connected(_toggle_pause_menu): + hud_settings.pressed.disconnect(_toggle_pause_menu) + hud_settings.pressed.connect(_toggle_pause_menu) + + # Programmatically connect Pause Menu Settings button to ensure it works + var pause_settings = get_node_or_null("PauseMenu/Panel/VBox/SettingsBtn") + if pause_settings: + if pause_settings.pressed.is_connected(_on_settings_pressed): + pause_settings.pressed.disconnect(_on_settings_pressed) + pause_settings.pressed.connect(_on_settings_pressed) func _setup_multiplayer_spawners(): # Setup MultiplayerSpawner for Static Tekton Stands @@ -2317,24 +2331,26 @@ func _on_how_to_play_back_pressed(): func _on_settings_pressed(): var pause_menu = get_node_or_null("PauseMenu") - var settings_panel = get_node_or_null("SettingsPanel") if pause_menu: pause_menu.visible = false - if settings_panel: - settings_panel.visible = true - # Sync settings UI with current state - var joystick_toggle = settings_panel.get_node_or_null("Panel/VBox/JoystickToggle") - if joystick_toggle and touch_controls: - joystick_toggle.set_pressed_no_signal(touch_controls.joystick_enabled) + var settings_menu = get_node_or_null("SettingsMenu") + if not settings_menu: + var scene = load("res://scenes/ui/settings_menu.tscn") + if scene: + settings_menu = scene.instantiate() + settings_menu.name = "SettingsMenu" + add_child(settings_menu) - var size_slider = settings_panel.get_node_or_null("Panel/VBox/ButtonSizeRow/ButtonSizeSlider") - if size_slider and touch_controls: - size_slider.set_value_no_signal(touch_controls.button_size) - - var opacity_slider = settings_panel.get_node_or_null("Panel/VBox/OpacityRow/OpacitySlider") - if opacity_slider and touch_controls: - opacity_slider.set_value_no_signal(touch_controls.button_opacity) + # Connect close button + var close_btn = settings_menu.get_node_or_null("PanelContainer/VBoxContainer/Header/CloseButton") + if close_btn: + if close_btn.pressed.is_connected(_on_settings_back_pressed): + close_btn.pressed.disconnect(_on_settings_back_pressed) + close_btn.pressed.connect(_on_settings_back_pressed) + + if settings_menu: + settings_menu.open() func _on_quit_match_pressed(): get_tree().paused = false # Ensure unpaused when returning to menu @@ -2345,9 +2361,9 @@ func _on_quit_match_pressed(): func _on_settings_back_pressed(): var pause_menu = get_node_or_null("PauseMenu") - var settings_panel = get_node_or_null("SettingsPanel") - if settings_panel: - settings_panel.visible = false + var settings_menu = get_node_or_null("SettingsMenu") + if settings_menu: + settings_menu.visible = false if pause_menu: pause_menu.visible = true diff --git a/scenes/ui/settings_menu.tscn b/scenes/ui/settings_menu.tscn new file mode 100644 index 0000000..adda038 --- /dev/null +++ b/scenes/ui/settings_menu.tscn @@ -0,0 +1,322 @@ +[gd_scene format=3 uid="uid://b1two2tvv5prx"] + +[ext_resource type="Script" uid="uid://cdege6m8u5cp" path="res://scripts/ui/settings_menu.gd" id="1_script"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bg"] +bg_color = Color(0.05, 0.05, 0.08, 0.85) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(0.2, 0.8, 1, 0.5) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 +shadow_color = Color(0, 0, 0, 0.5) +shadow_size = 20 + +[sub_resource type="LabelSettings" id="LabelSettings_title"] +font_size = 32 +font_color = Color(0.2, 0.9, 1, 1) +outline_size = 8 +outline_color = Color(0, 0, 0, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_tab_bg"] +content_margin_left = 20.0 +content_margin_top = 20.0 +content_margin_right = 20.0 +content_margin_bottom = 20.0 +bg_color = Color(0.1, 0.1, 0.15, 0.6) +corner_radius_top_left = 10 +corner_radius_top_right = 10 +corner_radius_bottom_right = 10 +corner_radius_bottom_left = 10 + +[sub_resource type="LabelSettings" id="LabelSettings_heading"] +font_size = 24 +font_color = Color(0.9, 0.9, 0.9, 1) + +[node name="SettingsMenu" type="CanvasLayer" unique_id=1319114632] +layer = 100 +script = ExtResource("1_script") + +[node name="ColorRect" type="ColorRect" parent="." unique_id=1754757020] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0, 0, 0, 0.4) + +[node name="PanelContainer" type="PanelContainer" parent="." unique_id=1635225045] +custom_minimum_size = Vector2(800, 600) +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -400.0 +offset_top = -300.0 +offset_right = 400.0 +offset_bottom = 300.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_bg") + +[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer" unique_id=1608766484] +layout_mode = 2 +theme_override_constants/separation = 20 + +[node name="Header" type="HBoxContainer" parent="PanelContainer/VBoxContainer" unique_id=1476625293] +custom_minimum_size = Vector2(0, 80) +layout_mode = 2 +theme_override_constants/separation = 20 + +[node name="MarginContainer" type="MarginContainer" parent="PanelContainer/VBoxContainer/Header" unique_id=22122987] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/margin_left = 30 + +[node name="Title" type="Label" parent="PanelContainer/VBoxContainer/Header/MarginContainer" unique_id=1831070364] +layout_mode = 2 +text = "SYSTEM SETTINGS" +label_settings = SubResource("LabelSettings_title") + +[node name="CloseButton" type="Button" parent="PanelContainer/VBoxContainer/Header" unique_id=1345773906] +custom_minimum_size = Vector2(60, 60) +layout_mode = 2 +size_flags_vertical = 4 +theme_override_font_sizes/font_size = 32 +text = "X" +flat = true + +[node name="ContentSection" type="MarginContainer" parent="PanelContainer/VBoxContainer" unique_id=1702280876] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/margin_left = 20 +theme_override_constants/margin_top = 0 +theme_override_constants/margin_right = 20 +theme_override_constants/margin_bottom = 30 + +[node name="TabContainer" type="TabContainer" parent="PanelContainer/VBoxContainer/ContentSection" unique_id=1795678842] +layout_mode = 2 +theme_override_constants/side_margin = 20 +theme_override_styles/panel = SubResource("StyleBoxFlat_tab_bg") +current_tab = 0 + +[node name="Video" type="ScrollContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer" unique_id=964480166] +layout_mode = 2 +metadata/_tab_index = 0 + +[node name="VBox" type="VBoxContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Video" unique_id=1271648419] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 15 + +[node name="Fullscreen" type="HBoxContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Video/VBox" unique_id=683482011] +layout_mode = 2 + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Video/VBox/Fullscreen" unique_id=2085318401] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Fullscreen Mode" +label_settings = SubResource("LabelSettings_heading") + +[node name="FullscreenToggle" type="CheckButton" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Video/VBox/Fullscreen" unique_id=1955016407] +unique_name_in_owner = true +layout_mode = 2 + +[node name="VSync" type="HBoxContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Video/VBox" unique_id=1497653967] +layout_mode = 2 + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Video/VBox/VSync" unique_id=76372424] +layout_mode = 2 +size_flags_horizontal = 3 +text = "V-Sync" +label_settings = SubResource("LabelSettings_heading") + +[node name="VSyncToggle" type="CheckButton" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Video/VBox/VSync" unique_id=412645188] +unique_name_in_owner = true +layout_mode = 2 + +[node name="Audio" type="VBoxContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer" unique_id=1585166620] +visible = false +layout_mode = 2 +theme_override_constants/separation = 20 +metadata/_tab_index = 1 + +[node name="Master" type="VBoxContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Audio" unique_id=2083739941] +layout_mode = 2 + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Audio/Master" unique_id=526445980] +layout_mode = 2 +text = "Master Volume" +label_settings = SubResource("LabelSettings_heading") + +[node name="MasterSlider" type="HSlider" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Audio/Master" unique_id=1787962533] +unique_name_in_owner = true +layout_mode = 2 +max_value = 1.0 +step = 0.05 +value = 0.8 + +[node name="Music" type="VBoxContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Audio" unique_id=81064625] +layout_mode = 2 + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Audio/Music" unique_id=1985407106] +layout_mode = 2 +text = "Music Volume" +label_settings = SubResource("LabelSettings_heading") + +[node name="MusicSlider" type="HSlider" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Audio/Music" unique_id=750703557] +unique_name_in_owner = true +layout_mode = 2 +max_value = 1.0 +step = 0.05 +value = 0.7 + +[node name="SFX" type="VBoxContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Audio" unique_id=1147135370] +layout_mode = 2 + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Audio/SFX" unique_id=1539898557] +layout_mode = 2 +text = "SFX Volume" +label_settings = SubResource("LabelSettings_heading") + +[node name="SFXSlider" type="HSlider" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Audio/SFX" unique_id=1301108449] +unique_name_in_owner = true +layout_mode = 2 +max_value = 1.0 +step = 0.05 +value = 0.9 + +[node name="Controls" type="ScrollContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer" unique_id=1553036843] +visible = false +layout_mode = 2 +metadata/_tab_index = 2 + +[node name="VBox" type="VBoxContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls" unique_id=1592525164] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 10 + +[node name="P1" type="HBoxContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox" unique_id=2065564199] +layout_mode = 2 + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox/P1" unique_id=85468293] +layout_mode = 2 +size_flags_horizontal = 3 +text = "PowerUp 1 (Speed)" +label_settings = SubResource("LabelSettings_heading") + +[node name="Powerup1Btn" type="Button" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox/P1" unique_id=643351652] +unique_name_in_owner = true +custom_minimum_size = Vector2(150, 40) +layout_mode = 2 +text = "1" + +[node name="P2" type="HBoxContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox" unique_id=1529813635] +layout_mode = 2 + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox/P2" unique_id=1645306219] +layout_mode = 2 +size_flags_horizontal = 3 +text = "PowerUp 2 (Freeze)" +label_settings = SubResource("LabelSettings_heading") + +[node name="Powerup2Btn" type="Button" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox/P2" unique_id=937262208] +unique_name_in_owner = true +custom_minimum_size = Vector2(150, 40) +layout_mode = 2 +text = "2" + +[node name="P3" type="HBoxContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox" unique_id=69920143] +layout_mode = 2 + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox/P3" unique_id=448782584] +layout_mode = 2 +size_flags_horizontal = 3 +text = "PowerUp 3 (Wall)" +label_settings = SubResource("LabelSettings_heading") + +[node name="Powerup3Btn" type="Button" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox/P3" unique_id=1817070904] +unique_name_in_owner = true +custom_minimum_size = Vector2(150, 40) +layout_mode = 2 +text = "3" + +[node name="P4" type="HBoxContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox" unique_id=559680686] +layout_mode = 2 + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox/P4" unique_id=1049586584] +layout_mode = 2 +size_flags_horizontal = 3 +text = "PowerUp 4 (Ghost)" +label_settings = SubResource("LabelSettings_heading") + +[node name="Powerup4Btn" type="Button" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox/P4" unique_id=1027617123] +unique_name_in_owner = true +custom_minimum_size = Vector2(150, 40) +layout_mode = 2 +text = "4" + +[node name="Attack" type="HBoxContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox" unique_id=1722218076] +layout_mode = 2 + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox/Attack" unique_id=1040943113] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Attack Mode" +label_settings = SubResource("LabelSettings_heading") + +[node name="AttackModeBtn" type="Button" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox/Attack" unique_id=1913469674] +unique_name_in_owner = true +custom_minimum_size = Vector2(150, 40) +layout_mode = 2 +text = "Q" + +[node name="Grab" type="HBoxContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox" unique_id=951480662] +layout_mode = 2 + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox/Grab" unique_id=1050214223] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Grab" +label_settings = SubResource("LabelSettings_heading") + +[node name="GrabBtn" type="Button" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox/Grab" unique_id=1307913086] +unique_name_in_owner = true +custom_minimum_size = Vector2(150, 40) +layout_mode = 2 +text = "Space" + +[node name="SpawnBoost" type="HBoxContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox" unique_id=1495993475] +layout_mode = 2 + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox/SpawnBoost" unique_id=1933746399] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Spawn Boost" +label_settings = SubResource("LabelSettings_heading") + +[node name="SpawnBoostBtn" type="Button" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox/SpawnBoost" unique_id=1836762532] +unique_name_in_owner = true +custom_minimum_size = Vector2(150, 40) +layout_mode = 2 +text = "E" + +[node name="TektonGrab" type="HBoxContainer" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox" unique_id=263116515] +layout_mode = 2 + +[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox/TektonGrab" unique_id=1041049284] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Grab Tekton" +label_settings = SubResource("LabelSettings_heading") + +[node name="TektonGrabBtn" type="Button" parent="PanelContainer/VBoxContainer/ContentSection/TabContainer/Controls/VBox/TektonGrab" unique_id=1884439337] +unique_name_in_owner = true +custom_minimum_size = Vector2(150, 40) +layout_mode = 2 +text = "G" diff --git a/scripts/managers/player_input_manager.gd b/scripts/managers/player_input_manager.gd index db2be04..47f8806 100644 --- a/scripts/managers/player_input_manager.gd +++ b/scripts/managers/player_input_manager.gd @@ -3,6 +3,7 @@ extends Node var player: Node3D var movement_manager: Node var race_manager: Node +@onready var SettingsManager = get_node_or_null("/root/SettingsManager") func initialize(p_player: Node3D, p_movement_manager: Node, p_race_manager: Node): player = p_player @@ -87,48 +88,49 @@ func handle_unhandled_input(event): var mode = LobbyManager.get_game_mode() var is_sng = mode == GameMode.Mode.STOP_N_GO - match event.keycode: - KEY_KP_1, KEY_1, KEY_KP_2, KEY_2, KEY_KP_3, KEY_3, KEY_KP_4, KEY_4: - if is_sng: - # Stop n Go Mapping: 1-Speed, 2-Ghost - match event.keycode: - KEY_KP_1, KEY_1: - player.activate_powerup(0) # FASTER_SPEED - KEY_KP_2, KEY_2: - player.activate_powerup(3) # INVISIBLE_MODE - else: - # Unified Mapping for all modes: 1-Speed, 2-Wall, 3-Freeze, 4-Ghost - match event.keycode: - KEY_KP_1, KEY_1: - player.activate_powerup(0) # FASTER_SPEED - KEY_KP_2, KEY_2: - player.activate_powerup(2) # BLOCK_FLOOR - KEY_KP_3, KEY_3: - player.activate_powerup(1) # AREA_FREEZE - KEY_KP_4, KEY_4: - player.activate_powerup(3) # INVISIBLE_MODE - # KEY_R: - # player.auto_put_item() - KEY_Q: - if player.powerup_manager: - # Attack Mode (formerly Special) - # Now we want "Straight to Attack Mode" style - player.powerup_manager.use_special_effect() - - # Force visual update / mutual exclusivity manually if powerup manager doesn't do it yet - if player.is_attack_mode and player.has_method("enter_attack_mode"): - # Re-triggering enter_attack_mode might be redundant but safely ensures visuals/Knock=False - player.enter_attack_mode() + # Get dynamic keybinds + var key_p1 = SettingsManager.get_control_keycode("powerup_1") + var key_p2 = SettingsManager.get_control_keycode("powerup_2") + var key_p3 = SettingsManager.get_control_keycode("powerup_3") + var key_p4 = SettingsManager.get_control_keycode("powerup_4") + + var ek = event.keycode + + # Unified check for PowerUp keys + if ek == key_p1: + player.activate_powerup(0) # FASTER_SPEED + elif ek == key_p2: + if is_sng: + player.activate_powerup(3) # Ghost for SNG P2 + else: + player.activate_powerup(2) # Wall for Normal P2 + elif ek == key_p3 and not is_sng: + player.activate_powerup(1) # AREA_FREEZE + elif ek == key_p4 and not is_sng: + player.activate_powerup(3) # INVISIBLE_MODE + + # Non-Remappable variants (Numpad) - Optional fallback + elif ek == KEY_KP_1: + player.activate_powerup(0) + elif ek == KEY_KP_2: + player.activate_powerup(3 if is_sng else 2) + + # Action Buttons (Remappable) + elif ek == SettingsManager.get_control_keycode("attack_mode"): + if player.powerup_manager: + player.powerup_manager.use_special_effect() + if player.is_attack_mode and player.has_method("enter_attack_mode"): + player.enter_attack_mode() - KEY_E: - if player.is_carrying_tekton and player.powerup_manager: - # Spawn Boost (Now uses Tekton) - if player.powerup_manager.has_method("spawn_boost_reward"): - player.powerup_manager.spawn_boost_reward() - KEY_G: - if not player.is_carrying_tekton and player.powerup_manager: - if player.powerup_manager.can_use_special(): - player.grab_tekton() + elif ek == SettingsManager.get_control_keycode("spawn_boost"): + if player.is_carrying_tekton and player.powerup_manager: + if player.powerup_manager.has_method("spawn_boost_reward"): + player.powerup_manager.spawn_boost_reward() + + elif ek == SettingsManager.get_control_keycode("tekton_grab"): + if not player.is_carrying_tekton and player.powerup_manager: + if player.powerup_manager.can_use_special(): + player.grab_tekton() # Handle spawn point selection if not yet selected diff --git a/scripts/managers/settings_manager.gd b/scripts/managers/settings_manager.gd new file mode 100644 index 0000000..a1d4e56 --- /dev/null +++ b/scripts/managers/settings_manager.gd @@ -0,0 +1,100 @@ +extends Node + +# SettingsManager - Global singleton for handling game settings persistence +# Autoloaded as "SettingsManager" + +const SETTINGS_FILE = "user://game_settings.cfg" + +# Default values +var settings = { + "video": { + "fullscreen": true, + "vsync": true, + "resolution_idx": 1 + }, + "audio": { + "master_volume": 0.8, + "music_volume": 0.7, + "sfx_volume": 0.9 + }, + "controls": { + "powerup_1": KEY_1, + "powerup_2": KEY_2, + "powerup_3": KEY_3, + "powerup_4": KEY_4, + "grab": KEY_SPACE, + "put": KEY_R, + "attack_mode": KEY_Q, + "spawn_boost": KEY_E, + "tekton_grab": KEY_G + } +} + +signal settings_applied +signal control_remapped(action: String, key: int) + +func _ready(): + load_settings() + apply_all_settings() + +func load_settings(): + var config = ConfigFile.new() + var err = config.load(SETTINGS_FILE) + + if err == OK: + for section in settings.keys(): + for key in settings[section].keys(): + settings[section][key] = config.get_value(section, key, settings[section][key]) + print("[Settings] Loaded.") + else: + print("[Settings] Using defaults.") + +func save_settings(): + var config = ConfigFile.new() + for section in settings.keys(): + for key in settings[section].keys(): + config.set_value(section, key, settings[section][key]) + config.save(SETTINGS_FILE) + +func apply_all_settings(): + apply_video_settings() + apply_audio_settings() + emit_signal("settings_applied") + +func apply_video_settings(): + var video = settings.video + if video.fullscreen: + DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN) + else: + DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) + + if video.vsync: + DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_ENABLED) + else: + DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED) + +func apply_audio_settings(): + var audio = settings.audio + set_bus_volume("Master", audio.master_volume) + set_bus_volume("Music", audio.music_volume) + set_bus_volume("SFX", audio.sfx_volume) + +func set_bus_volume(bus_name: String, volume_linear: float): + var bus_idx = AudioServer.get_bus_index(bus_name) + if bus_idx != -1: + AudioServer.set_bus_volume_db(bus_idx, linear_to_db(volume_linear)) + AudioServer.set_bus_mute(bus_idx, volume_linear <= 0.001) + +func set_control(action_name: String, keycode: int): + if settings.controls.has(action_name): + settings.controls[action_name] = keycode + emit_signal("control_remapped", action_name, keycode) + save_settings() + +func get_control_keycode(action_name: String) -> int: + return settings.controls.get(action_name, -1) + +func get_control_text(action_name: String) -> String: + var code = get_control_keycode(action_name) + if code == -1: return "Unbound" + return OS.get_keycode_string(code) diff --git a/scripts/managers/settings_manager.gd.uid b/scripts/managers/settings_manager.gd.uid new file mode 100644 index 0000000..0ed0e70 --- /dev/null +++ b/scripts/managers/settings_manager.gd.uid @@ -0,0 +1 @@ +uid://c1ouaaqnn0lrc diff --git a/scripts/managers/touch_controls.gd b/scripts/managers/touch_controls.gd index 45ab6d6..f918cd1 100644 --- a/scripts/managers/touch_controls.gd +++ b/scripts/managers/touch_controls.gd @@ -142,11 +142,26 @@ func _create_touch_ui(): tekton_grab_button = _find_or_create_action_button(actions_container, "TektonGrab", "👋", Vector2(-280, -80)) - # Order: AttackMode, SpawnBoost, Grab - if attack_mode_button: actions_container.move_child(attack_mode_button, 0) - if spawn_boost_button: actions_container.move_child(spawn_boost_button, 1) - if grab_button: actions_container.move_child(grab_button, 2) - if tekton_grab_button: actions_container.move_child(tekton_grab_button, 3) + # Order: AttackMode, SpawnBoost, Grab, TektonGrab + if attack_mode_button: + actions_container.move_child(attack_mode_button, 0) + attack_mode_button.icon = load("res://assets/graphics/touch_control/attack_mode.png") + attack_mode_button.expand_icon = true + + if spawn_boost_button: + actions_container.move_child(spawn_boost_button, 1) + spawn_boost_button.icon = load("res://assets/graphics/touch_control/spawn_tile.png") + spawn_boost_button.expand_icon = true + + if grab_button: + actions_container.move_child(grab_button, 2) + grab_button.icon = load("res://assets/graphics/touch_control/take_tile.png") + grab_button.expand_icon = true + + if tekton_grab_button: + actions_container.move_child(tekton_grab_button, 3) + tekton_grab_button.icon = load("res://assets/graphics/touch_control/grab_tekton.png") + tekton_grab_button.expand_icon = true # Hide Put Button if put_button: @@ -391,6 +406,7 @@ func _load_settings(): var err = config.load(CONFIG_PATH) if err != OK: print("[TouchControls] No saved settings found, using defaults") + _apply_settings() # Apply default settings immediately return # Load settings values @@ -439,46 +455,55 @@ func _apply_settings(): # Apply joystick visibility if virtual_joystick: virtual_joystick.visible = joystick_enabled + + # Apply touch buttons visibility - FORCED ON per request to "just show them" + if actions_container: + actions_container.visible = true + actions_container.set_anchors_preset(Control.PRESET_FULL_RECT) + actions_container.mouse_filter = Control.MOUSE_FILTER_PASS - # Apply touch buttons visibility - dependent on master joystick_enabled switch - # If joystick is disabled, ALL touch controls are hidden - # Note: We ignore touch_buttons_enabled here to ensure "Enable Virtual Joystick" shows EVERYTHING as requested - var buttons_visible = joystick_enabled + var buttons_visible = true - print("[TouchControls] Applying settings: JoystickEnabled=", joystick_enabled, " ButtonsVisible=", buttons_visible) + print("[TouchControls] Applying settings: ButtonsVisible=", buttons_visible) if grab_button: - grab_button.visible = buttons_visible + grab_button.visible = false grab_button.scale = Vector2(button_scale, button_scale) - # Use offsets for anchored controls, not position - # grab_button.offset_left = button_positions.grab.x - # grab_button.offset_top = button_positions.grab.y - # grab_button.offset_right = button_positions.grab.x + button_size - # grab_button.offset_bottom = button_positions.grab.y + button_size + grab_button.set_anchors_preset(Control.PRESET_BOTTOM_RIGHT) + grab_button.offset_left = button_positions.grab.x + grab_button.offset_top = button_positions.grab.y + grab_button.offset_right = button_positions.grab.x + button_size + grab_button.offset_bottom = button_positions.grab.y + button_size if put_button: put_button.visible = false # Always INTENTIONALLY HIDDEN per request - # put_button.scale = Vector2(button_scale, button_scale) - # put_button.offset_left = button_positions.put.x - # put_button.offset_top = button_positions.put.y - # put_button.offset_right = button_positions.put.x + button_size - # put_button.offset_bottom = button_positions.put.y + button_size if attack_mode_button: - attack_mode_button.visible = buttons_visible + attack_mode_button.visible = true attack_mode_button.scale = Vector2(button_scale, button_scale) - # attack_mode_button.offset_left = button_positions.attack_mode.x - # attack_mode_button.offset_top = button_positions.attack_mode.y - # attack_mode_button.offset_right = button_positions.attack_mode.x + button_size - # attack_mode_button.offset_bottom = button_positions.attack_mode.y + button_size + attack_mode_button.set_anchors_preset(Control.PRESET_BOTTOM_RIGHT) + attack_mode_button.offset_left = button_positions.attack_mode.x + attack_mode_button.offset_top = button_positions.attack_mode.y + attack_mode_button.offset_right = button_positions.attack_mode.x + button_size + attack_mode_button.offset_bottom = button_positions.attack_mode.y + button_size if spawn_boost_button: - spawn_boost_button.visible = buttons_visible + spawn_boost_button.visible = true spawn_boost_button.scale = Vector2(button_scale, button_scale) - # spawn_boost_button.offset_left = button_positions.spawn_boost.x - # spawn_boost_button.offset_top = button_positions.spawn_boost.y - # spawn_boost_button.offset_right = button_positions.spawn_boost.x + button_size - # spawn_boost_button.offset_bottom = button_positions.spawn_boost.y + button_size + spawn_boost_button.set_anchors_preset(Control.PRESET_BOTTOM_RIGHT) + spawn_boost_button.offset_left = button_positions.spawn_boost.x + spawn_boost_button.offset_top = button_positions.spawn_boost.y + spawn_boost_button.offset_right = button_positions.spawn_boost.x + button_size + spawn_boost_button.offset_bottom = button_positions.spawn_boost.y + button_size + + if tekton_grab_button: + tekton_grab_button.visible = true + tekton_grab_button.scale = Vector2(button_scale, button_scale) + tekton_grab_button.set_anchors_preset(Control.PRESET_BOTTOM_RIGHT) + tekton_grab_button.offset_left = -280 + tekton_grab_button.offset_top = -80 + tekton_grab_button.offset_right = -280 + button_size + tekton_grab_button.offset_bottom = -80 + button_size # Force layer update visible = true diff --git a/scripts/ui/powerup_inventory_ui.gd b/scripts/ui/powerup_inventory_ui.gd index 91b843d..7e26558 100644 --- a/scripts/ui/powerup_inventory_ui.gd +++ b/scripts/ui/powerup_inventory_ui.gd @@ -8,11 +8,16 @@ var icon_containers: Dictionary = {} # { EffectEnum: Button } # Local State var selected_effect: int = -1 var special_manager_ref: Node = null # Reference to SpecialTilesManager +@onready var SettingsManager = get_node_or_null("/root/SettingsManager") signal effect_selected(effect: int) func _ready(): print("[PowerUpUI] _ready called") + + # Connect to SettingsManager to update labels + if SettingsManager and not SettingsManager.control_remapped.is_connected(_on_control_remapped): + SettingsManager.control_remapped.connect(_on_control_remapped) # Map Effect Enum to UI Nodes (Assumes specific names in main.tscn) # We try to get them immediately. If they are children, they should be accessible. var container = get_node_or_null("Container") @@ -54,8 +59,13 @@ func _ready(): if freeze_btn: freeze_btn.visible = true _update_shortcuts_for_mode(false) + print("[PowerUpUI] UI Initialization Complete. Mapped %d buttons." % icon_containers.size()) +func _on_control_remapped(_action: String, _key: int): + # Refresh all labels + _update_shortcuts_for_mode(LobbyManager.is_game_mode(GameMode.Mode.STOP_N_GO)) + func _setup_btn(effect_id: int, btn: Button): if not btn: print("[PowerUpUI] Warning: Button for effect %d is null" % effect_id) @@ -138,15 +148,15 @@ func _update_btn_shortcut(effect_id: int, btn: Button): var key_text = "" if is_sng: match effect_id: - 0: key_text = "1" # Speed - 3: key_text = "2" # Ghost - _: key_text = "" # Others hidden/disabled + 0: key_text = SettingsManager.get_control_text("powerup_1") + 3: key_text = SettingsManager.get_control_text("powerup_2") # Map P2 to Slot 3 (Ghost) in SNG + _: key_text = "" else: match effect_id: - 0: key_text = "1" # Speed - 2: key_text = "2" # Wall - 1: key_text = "3" # Freeze - 3: key_text = "4" # Ghost + 0: key_text = SettingsManager.get_control_text("powerup_1") + 2: key_text = SettingsManager.get_control_text("powerup_2") + 1: key_text = SettingsManager.get_control_text("powerup_3") + 3: key_text = SettingsManager.get_control_text("powerup_4") sc_lbl.text = key_text diff --git a/scripts/ui/settings_menu.gd b/scripts/ui/settings_menu.gd new file mode 100644 index 0000000..f47b915 --- /dev/null +++ b/scripts/ui/settings_menu.gd @@ -0,0 +1,110 @@ +extends CanvasLayer + +# SettingsMenu - Handles UI logic for the premium settings panel + +@onready var tab_container = $PanelContainer/VBoxContainer/ContentSection/TabContainer +@onready var close_button = $PanelContainer/VBoxContainer/Header/CloseButton + +# Video Controls +@onready var fullscreen_toggle = %FullscreenToggle +@onready var vsync_toggle = %VSyncToggle + +# Audio Controls +@onready var master_slider = %MasterSlider +@onready var music_slider = %MusicSlider +@onready var sfx_slider = %SFXSlider + +# Controls (Keybinds) +@onready var SettingsManager = get_node_or_null("/root/SettingsManager") +var listening_action: String = "" # Set when waiting for a keypress + +func _ready(): + # Theme inheritance is broken by CanvasLayer root, no need for theme = null + _load_ui_values() + _connect_signals() + + # Initial visibility + visible = false + +func _load_ui_values(): + if not SettingsManager: + print("[SettingsMenu] ERROR: SettingsManager not found") + return + + var s = SettingsManager.settings + + # Video + fullscreen_toggle.button_pressed = s.video.fullscreen + vsync_toggle.button_pressed = s.video.vsync + + # Audio + master_slider.value = s.audio.master_volume + music_slider.value = s.audio.music_volume + sfx_slider.value = s.audio.sfx_volume + + # Controls + _update_all_key_labels() + +func _connect_signals(): + # Video + fullscreen_toggle.toggled.connect(_on_video_setting_changed) + vsync_toggle.toggled.connect(_on_video_setting_changed) + + # Audio + master_slider.value_changed.connect(_on_audio_setting_changed.bind("master_volume")) + music_slider.value_changed.connect(_on_audio_setting_changed.bind("music_volume")) + sfx_slider.value_changed.connect(_on_audio_setting_changed.bind("sfx_volume")) + + # Close + close_button.pressed.connect(func(): visible = false) + + # Connect remapping buttons + for action_name in SettingsManager.settings.controls.keys(): + var btn = get_node_or_null("%" + action_name.to_pascal_case() + "Btn") + if btn: + btn.pressed.connect(_on_remap_button_pressed.bind(action_name)) + +func _on_video_setting_changed(_unused = false): + SettingsManager.settings.video.fullscreen = fullscreen_toggle.button_pressed + SettingsManager.settings.video.vsync = vsync_toggle.button_pressed + SettingsManager.save_settings() + SettingsManager.apply_video_settings() + +func _on_audio_setting_changed(key: String, value: float): + SettingsManager.settings.audio[key] = value + SettingsManager.save_settings() + SettingsManager.apply_audio_settings() + +func _on_remap_button_pressed(action_name: String): + listening_action = action_name + var btn = get_node_or_null("%" + action_name.to_pascal_case() + "Btn") + if btn: + btn.text = "..." + btn.modulate = Color(0.5, 1.0, 0.5) # Signal "Listening" + +func _input(event): + if listening_action != "" and event is InputEventKey and event.pressed: + # Capture the key + var keycode = event.keycode + SettingsManager.set_control(listening_action, keycode) + + # Feedback and Reset + _update_key_label(listening_action) + listening_action = "" + + # Consume event to prevent triggers + get_viewport().set_input_as_handled() + +func _update_all_key_labels(): + for action_name in SettingsManager.settings.controls.keys(): + _update_key_label(action_name) + +func _update_key_label(action_name: String): + var btn = get_node_or_null("%" + action_name.to_pascal_case() + "Btn") + if btn: + btn.text = SettingsManager.get_control_text(action_name) + btn.modulate = Color.WHITE + +func open(): + _load_ui_values() + visible = true diff --git a/scripts/ui/settings_menu.gd.uid b/scripts/ui/settings_menu.gd.uid new file mode 100644 index 0000000..0faa5ed --- /dev/null +++ b/scripts/ui/settings_menu.gd.uid @@ -0,0 +1 @@ +uid://cdege6m8u5cp