Files
tekton/scripts/ui/settings_menu.gd
T
2026-04-13 18:15:49 +08:00

372 lines
13 KiB
GDScript

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
@onready var resolution_btn = %ResolutionBtn
@onready var msaa_btn = %MSAABtn
@onready var shadow_btn = %ShadowBtn
@onready var fps_btn = %FPSCapBtn
# Audio Controls
@onready var master_slider = %MasterSlider
@onready var music_slider = %MusicSlider
@onready var sfx_slider = %SFXSlider
# Controls (Keybinds)
@onready var use_controller_btn = %UseControllerBtn
@onready var SettingsManager = get_node_or_null("/root/SettingsManager")
var listening_action: String = "" # Set when waiting for a keypress
# Keys removed from the game - skip them even if stale user config has them
const DEPRECATED_ACTIONS: Array = ["put", "put_alt"]
# Touch Input Controls
@onready var joystick_size_slider = %JoystickSizeSlider
@onready var joystick_size_value = %JoystickSizeValue
@onready var joystick_enabled_toggle = %JoystickEnabledToggle
@onready var button_opacity_slider = %ButtonOpacitySlider
@onready var button_opacity_value = %ButtonOpacityValue
@onready var touch_buttons_enabled_toggle = %TouchButtonsEnabledToggle
const TOUCH_CONFIG_PATH = "user://touch_controls_settings.cfg"
# Controller Rebinding
@onready var ctrl_grab_btn = %CtrlGrabBtn
@onready var ctrl_powerup_btn = %CtrlPowerupBtn
@onready var ctrl_tekton_grab_btn = %CtrlTektonGrabBtn
@onready var ctrl_attack_btn = %CtrlAttackBtn
@onready var ctrl_reset_btn = %CtrlResetBtn
var listening_ctrl_key: String = "" # ctrl_* key being rebound
func _ready():
# Theme inheritance is broken by CanvasLayer root, no need for theme = null
_load_ui_values()
if SettingsManager:
_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
# Populate Resolution dropdown if not already populated
if resolution_btn.item_count == 0:
for i in range(SettingsManager.RESOLUTIONS.size()):
var res = SettingsManager.RESOLUTIONS[i]
resolution_btn.add_item("%dx%d" % [res.x, res.y])
resolution_btn.selected = s.video.resolution_idx
# MSAA
if msaa_btn.item_count == 0:
msaa_btn.add_item("Disabled")
msaa_btn.add_item("2x")
msaa_btn.add_item("4x")
msaa_btn.add_item("8x")
msaa_btn.selected = s.video.msaa
# Shadows
if shadow_btn.item_count == 0:
shadow_btn.add_item("Low")
shadow_btn.add_item("Medium")
shadow_btn.add_item("High")
shadow_btn.add_item("Ultra")
shadow_btn.selected = s.video.shadow_quality
# FPS Cap
if fps_btn.item_count == 0:
fps_btn.add_item("Unlimited")
fps_btn.add_item("30 FPS")
fps_btn.add_item("60 FPS")
fps_btn.add_item("120 FPS")
fps_btn.add_item("144 FPS")
fps_btn.selected = s.video.fps_cap
# Audio
master_slider.value = s.audio.master_volume
music_slider.value = s.audio.music_volume
sfx_slider.value = s.audio.sfx_volume
# Controls
use_controller_btn.button_pressed = s.controls.get("use_controller", false)
_update_all_key_labels()
# Touch Input
_load_touch_ui_values()
# Controller Bindings
_update_all_ctrl_labels()
func _load_touch_ui_values():
"""Load touch control settings from config file."""
var config = ConfigFile.new()
var joystick_size: float = 60.0
var joystick_on: bool = true
var btn_opacity: float = 0.7
var touch_btns_on: bool = true
if config.load(TOUCH_CONFIG_PATH) == OK:
joystick_size = config.get_value("touch_controls", "joystick_size", 60.0)
joystick_on = config.get_value("touch_controls", "joystick_enabled", true)
btn_opacity = config.get_value("touch_controls", "button_opacity", 0.7)
touch_btns_on = config.get_value("touch_controls", "touch_buttons_enabled", true)
joystick_size_slider.value = joystick_size
joystick_size_value.text = str(int(joystick_size))
joystick_enabled_toggle.button_pressed = joystick_on
button_opacity_slider.value = btn_opacity
button_opacity_value.text = str(int(btn_opacity * 100)) + "%"
touch_buttons_enabled_toggle.button_pressed = touch_btns_on
func _connect_signals():
# Video
fullscreen_toggle.toggled.connect(_on_video_setting_changed)
vsync_toggle.toggled.connect(_on_video_setting_changed)
resolution_btn.item_selected.connect(_on_resolution_selected)
msaa_btn.item_selected.connect(_on_video_property_changed.bind("msaa"))
shadow_btn.item_selected.connect(_on_video_property_changed.bind("shadow_quality"))
fps_btn.item_selected.connect(_on_video_property_changed.bind("fps_cap"))
# 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"))
# Controls
use_controller_btn.toggled.connect(_on_control_setting_changed.bind("use_controller"))
# Touch Input
joystick_size_slider.value_changed.connect(_on_joystick_size_changed)
joystick_enabled_toggle.toggled.connect(_on_touch_toggle_changed.bind("joystick_enabled"))
button_opacity_slider.value_changed.connect(_on_button_opacity_changed)
touch_buttons_enabled_toggle.toggled.connect(_on_touch_toggle_changed.bind("touch_buttons_enabled"))
# Controller Rebinding
ctrl_grab_btn.pressed.connect(_on_ctrl_remap_pressed.bind("ctrl_grab"))
ctrl_powerup_btn.pressed.connect(_on_ctrl_remap_pressed.bind("ctrl_use_powerup"))
ctrl_tekton_grab_btn.pressed.connect(_on_ctrl_remap_pressed.bind("ctrl_tekton_grab"))
ctrl_attack_btn.pressed.connect(_on_ctrl_remap_pressed.bind("ctrl_attack_mode"))
ctrl_reset_btn.pressed.connect(_on_ctrl_reset_pressed)
# Close
close_button.pressed.connect(func(): listening_ctrl_key = ""; visible = false)
# Connect remapping buttons
for action_name in SettingsManager.settings.controls.keys():
if action_name == "use_controller" or action_name in DEPRECATED_ACTIONS:
continue
# Check Primary Button
var primary_btn = get_node_or_null("%" + action_name.to_pascal_case() + "Btn")
if primary_btn:
if primary_btn.pressed.is_connected(_on_remap_button_pressed):
primary_btn.pressed.disconnect(_on_remap_button_pressed)
primary_btn.pressed.connect(_on_remap_button_pressed.bind(action_name))
# Check Alt Button
if not action_name.ends_with("_alt"):
var alt_action = action_name + "_alt"
var alt_btn = get_node_or_null("%" + action_name.to_pascal_case() + "AltBtn")
if alt_btn:
if alt_btn.pressed.is_connected(_on_remap_button_pressed):
alt_btn.pressed.disconnect(_on_remap_button_pressed)
alt_btn.pressed.connect(_on_remap_button_pressed.bind(alt_action))
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_resolution_selected(idx: int):
SettingsManager.settings.video.resolution_idx = idx
SettingsManager.save_settings()
SettingsManager.apply_video_settings()
func _on_video_property_changed(idx: int, property_name: String):
SettingsManager.settings.video[property_name] = idx
SettingsManager.save_settings()
SettingsManager.apply_video_settings()
func _on_audio_setting_changed(value: float, key: String):
SettingsManager.settings.audio[key] = value
SettingsManager.save_settings()
SettingsManager.apply_audio_settings()
func _on_control_setting_changed(value: bool, key: String):
SettingsManager.settings.controls[key] = value
SettingsManager.save_settings()
# Emit settings_applied so all UI (touch buttons, prompts) refresh their shortcut labels
if key == "use_controller":
SettingsManager.apply_control_settings()
SettingsManager.emit_signal("settings_applied")
func _on_joystick_size_changed(value: float):
joystick_size_value.text = str(int(value))
_save_touch_value("joystick_size", value)
# Live update the joystick if it's in the scene
var tc = _get_touch_controls()
if tc:
tc.joystick_size = value
if tc.virtual_joystick and tc.virtual_joystick.has_method("set_radius"):
tc.virtual_joystick.set_radius(value)
func _on_button_opacity_changed(value: float):
button_opacity_value.text = str(int(value * 100)) + "%"
_save_touch_value("button_opacity", value)
var tc = _get_touch_controls()
if tc:
tc.button_opacity = value
if tc.has_method("_apply_settings"):
tc._apply_settings()
func _on_touch_toggle_changed(state: bool, key: String):
_save_touch_value(key, state)
var tc = _get_touch_controls()
if tc:
if key == "joystick_enabled":
tc.joystick_enabled = state
elif key == "touch_buttons_enabled":
tc.touch_buttons_enabled = state
if tc.has_method("_apply_settings"):
tc._apply_settings()
func _save_touch_value(key: String, value) -> void:
"""Save a single touch config key without overwriting other keys."""
var config = ConfigFile.new()
config.load(TOUCH_CONFIG_PATH) # Load existing (ignore error if not found)
config.set_value("touch_controls", key, value)
config.save(TOUCH_CONFIG_PATH)
func _get_touch_controls() -> Node:
"""Try to find the TouchControls node in the Main scene."""
var main = get_tree().get_root().get_node_or_null("Main")
if main:
return main.get_node_or_null("TouchLayer/TouchControls")
return null
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):
# Keyboard remapping
if listening_action != "" and event is InputEventKey and event.pressed:
var keycode = event.keycode
# PREVENT DUPLICATES (User Request)
var existing_action = SettingsManager.is_key_used(keycode)
if existing_action != "" and existing_action != listening_action:
var error_btn = get_node_or_null("%" + listening_action.to_pascal_case() + "Btn")
if error_btn:
error_btn.text = "ALREADY USED!"
error_btn.modulate = Color(1.0, 0.3, 0.3)
var sfx = get_node_or_null("/root/SfxManager")
if sfx and sfx.has_method("play_rpc"):
sfx.rpc("play_rpc", "error")
listening_action = ""
await get_tree().create_timer(1.2).timeout
_update_all_key_labels()
get_viewport().set_input_as_handled()
return
SettingsManager.set_control(listening_action, keycode)
_update_key_label(listening_action)
listening_action = ""
get_viewport().set_input_as_handled()
# Controller button remapping
if listening_ctrl_key != "" and event is InputEventJoypadButton and event.pressed:
var btn_idx = event.button_index
# Duplicate check
var existing = SettingsManager.is_controller_button_used(btn_idx)
var ctrl_btn = _get_ctrl_btn(listening_ctrl_key)
if existing != "" and existing != listening_ctrl_key:
if ctrl_btn:
ctrl_btn.text = "ALREADY USED!"
ctrl_btn.modulate = Color(1.0, 0.3, 0.3)
listening_ctrl_key = ""
await get_tree().create_timer(1.2).timeout
_update_all_ctrl_labels()
get_viewport().set_input_as_handled()
return
SettingsManager.set_controller_binding(listening_ctrl_key, btn_idx)
listening_ctrl_key = ""
_update_all_ctrl_labels()
get_viewport().set_input_as_handled()
func _update_all_key_labels():
for action_name in SettingsManager.settings.controls.keys():
if action_name in DEPRECATED_ACTIONS:
continue
_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 and SettingsManager:
btn.text = SettingsManager.get_control_text(action_name)
btn.modulate = Color.WHITE
func _update_all_ctrl_labels():
"""Update all controller button labels from saved settings."""
if not SettingsManager: return
ctrl_grab_btn.text = SettingsManager.get_controller_binding_text("ctrl_grab")
ctrl_grab_btn.modulate = Color.WHITE
ctrl_powerup_btn.text = SettingsManager.get_controller_binding_text("ctrl_use_powerup")
ctrl_powerup_btn.modulate = Color.WHITE
ctrl_tekton_grab_btn.text = SettingsManager.get_controller_binding_text("ctrl_tekton_grab")
ctrl_tekton_grab_btn.modulate = Color.WHITE
ctrl_attack_btn.text = SettingsManager.get_controller_binding_text("ctrl_attack_mode")
ctrl_attack_btn.modulate = Color.WHITE
func _on_ctrl_remap_pressed(ctrl_key: String):
listening_ctrl_key = ctrl_key
var btn = _get_ctrl_btn(ctrl_key)
if btn:
btn.text = "Press a button..."
btn.modulate = Color(0.5, 1.0, 0.5)
func _get_ctrl_btn(ctrl_key: String) -> Button:
match ctrl_key:
"ctrl_grab": return ctrl_grab_btn
"ctrl_use_powerup": return ctrl_powerup_btn
"ctrl_tekton_grab": return ctrl_tekton_grab_btn
"ctrl_attack_mode": return ctrl_attack_btn
return null
func _on_ctrl_reset_pressed():
"""Reset controller bindings to defaults."""
if not SettingsManager: return
SettingsManager.settings.controls["ctrl_grab"] = JOY_BUTTON_A
SettingsManager.settings.controls["ctrl_use_powerup"] = JOY_BUTTON_Y
SettingsManager.settings.controls["ctrl_tekton_grab"] = JOY_BUTTON_RIGHT_SHOULDER
SettingsManager.settings.controls["ctrl_attack_mode"] = JOY_BUTTON_X
SettingsManager.apply_control_settings()
SettingsManager.save_settings()
_update_all_ctrl_labels()
func open():
_load_ui_values()
visible = true