372 lines
13 KiB
GDScript
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
|