Files
Papay-Shooter/ui/pause_menu.gd
T

941 lines
30 KiB
GDScript

extends CanvasLayer
var bg: ColorRect
var main_vbox: VBoxContainer
var resume_btn: Button
var respawn_btn: Button
var loadouts_btn: Button
var settings_btn: Button
var return_btn: Button
var exit_btn: Button
# Loadout UI
var loadout_editor: Control
var loadout_list_vbox: VBoxContainer
var edit_panel: PanelContainer
var name_edit: LineEdit
var primary1_opt: OptionButton
var primary2_opt: OptionButton
var special_opt: OptionButton
var melee_opt: OptionButton
var save_loadout_btn: Button
var back_to_main_btn: Button
var editing_index: int = -1
# Settings UI
var settings_editor: Control
var settings_back_btn: Button
var _world_fov_slider: HSlider
var _world_fov_input: LineEdit
var _debug_ui_checkbox: CheckButton
var _mouse_sens_slider: HSlider
var _mouse_sens_input: LineEdit
var _ads_sens_slider: HSlider
var _ads_sens_input: LineEdit
var _display_mode_opt: OptionButton
var _resolution_opt: OptionButton
var _fps_slider: HSlider
var _fps_input: LineEdit
var _show_fps_checkbox: CheckButton
var _show_movement_speed_checkbox: CheckButton
var _vol_master_slider: HSlider
var _vol_master_input: LineEdit
var _vol_sfx_slider: HSlider
var _vol_sfx_input: LineEdit
var _vol_music_slider: HSlider
var _vol_music_input: LineEdit
var _vol_wind_slider: HSlider
var _vol_wind_input: LineEdit
var keybind_vbox: VBoxContainer
var rebind_overlay: ColorRect
var rebind_label: Label
var waiting_for_input_action: String = ""
var quit_dialog: ConfirmationDialog
var return_dialog: ConfirmationDialog
func _ready() -> void:
process_mode = Node.PROCESS_MODE_ALWAYS
layer = 100 # Ensure it's on top
visible = false
_build_ui()
_connect_signals()
func _input(event: InputEvent) -> void:
if waiting_for_input_action != "":
if event is InputEventKey or event is InputEventMouseButton:
if event.is_pressed():
SettingsManager.rebind_action(waiting_for_input_action, event)
waiting_for_input_action = ""
rebind_overlay.visible = false
_populate_keybindings()
# Consume event so it doesn't trigger anything else
get_viewport().set_input_as_handled()
return
if event.is_action_pressed("ui_cancel"):
# Don't open the pause menu if we are already in the main menu
if get_tree().current_scene and get_tree().current_scene.name == "MainMenu":
return
if visible:
_resume()
else:
_pause()
func _pause() -> void:
visible = true
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
_show_main_menu()
func _resume() -> void:
visible = false
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
func _respawn() -> void:
_resume() # Unpause
# Find local player using their unique multiplayer ID
var local_player: Node3D = null
var pid = multiplayer.get_unique_id()
if get_tree().current_scene:
local_player = get_tree().current_scene.get_node_or_null(str(pid))
if not local_player:
var spawner = get_tree().current_scene.get_node_or_null("PlayerSpawner")
if spawner:
local_player = spawner.get_node_or_null(str(pid))
if local_player and local_player.has_method("rpc_request_respawn"):
local_player.rpc_request_respawn.rpc_id(1)
func _build_ui() -> void:
bg = ColorRect.new()
bg.color = Color(0, 0, 0, 0.7)
bg.set_anchors_preset(Control.PRESET_FULL_RECT)
add_child(bg)
# ── Main Menu ──
var main_center = CenterContainer.new()
main_center.set_anchors_preset(Control.PRESET_FULL_RECT)
bg.add_child(main_center)
main_vbox = VBoxContainer.new()
main_vbox.add_theme_constant_override("separation", 20)
main_center.add_child(main_vbox)
var title = Label.new()
title.text = "MENU"
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
title.add_theme_font_size_override("font_size", 48)
main_vbox.add_child(title)
resume_btn = Button.new()
resume_btn.text = "Resume"
resume_btn.custom_minimum_size = Vector2(200, 50)
main_vbox.add_child(resume_btn)
respawn_btn = Button.new()
respawn_btn.text = "Respawn"
respawn_btn.custom_minimum_size = Vector2(200, 50)
main_vbox.add_child(respawn_btn)
loadouts_btn = Button.new()
loadouts_btn.text = "Loadouts"
loadouts_btn.custom_minimum_size = Vector2(200, 50)
main_vbox.add_child(loadouts_btn)
settings_btn = Button.new()
settings_btn.text = "Settings"
settings_btn.custom_minimum_size = Vector2(200, 50)
main_vbox.add_child(settings_btn)
return_btn = Button.new()
return_btn.text = "Main Menu"
return_btn.custom_minimum_size = Vector2(200, 50)
main_vbox.add_child(return_btn)
exit_btn = Button.new()
exit_btn.text = "Exit Game"
exit_btn.custom_minimum_size = Vector2(200, 50)
main_vbox.add_child(exit_btn)
# ── Loadout Editor Root ──
loadout_editor = Control.new()
loadout_editor.set_anchors_preset(Control.PRESET_FULL_RECT)
bg.add_child(loadout_editor)
var editor_center = CenterContainer.new()
editor_center.set_anchors_preset(Control.PRESET_FULL_RECT)
loadout_editor.add_child(editor_center)
var editor_hbox = HBoxContainer.new()
editor_hbox.add_theme_constant_override("separation", 40)
editor_center.add_child(editor_hbox)
var left_vbox = VBoxContainer.new()
editor_hbox.add_child(left_vbox)
var list_title = Label.new()
list_title.text = "Your Loadouts"
list_title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
left_vbox.add_child(list_title)
loadout_list_vbox = VBoxContainer.new()
left_vbox.add_child(loadout_list_vbox)
back_to_main_btn = Button.new()
back_to_main_btn.text = "Back to Menu"
back_to_main_btn.custom_minimum_size = Vector2(0, 40)
left_vbox.add_child(back_to_main_btn)
edit_panel = PanelContainer.new()
editor_hbox.add_child(edit_panel)
var edit_vbox = VBoxContainer.new()
edit_vbox.add_theme_constant_override("separation", 10)
edit_panel.add_child(edit_vbox)
var edit_title = Label.new()
edit_title.text = "Edit Loadout"
edit_title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
edit_vbox.add_child(edit_title)
name_edit = LineEdit.new()
name_edit.placeholder_text = "Loadout Name"
name_edit.custom_minimum_size = Vector2(250, 0)
edit_vbox.add_child(name_edit)
edit_vbox.add_child(_create_label("Primary 1:"))
primary1_opt = OptionButton.new()
edit_vbox.add_child(primary1_opt)
edit_vbox.add_child(_create_label("Primary 2:"))
primary2_opt = OptionButton.new()
edit_vbox.add_child(primary2_opt)
edit_vbox.add_child(_create_label("Special:"))
special_opt = OptionButton.new()
edit_vbox.add_child(special_opt)
var melee_lbl = Label.new()
melee_lbl.text = "Melee"
edit_vbox.add_child(melee_lbl)
melee_opt = OptionButton.new()
edit_vbox.add_child(melee_opt)
save_loadout_btn = Button.new()
save_loadout_btn.text = "Save Loadout"
save_loadout_btn.custom_minimum_size = Vector2(0, 40)
edit_vbox.add_child(save_loadout_btn)
# ── Settings Editor Root ──
settings_editor = Control.new()
settings_editor.set_anchors_preset(Control.PRESET_FULL_RECT)
bg.add_child(settings_editor)
var settings_center = CenterContainer.new()
settings_center.set_anchors_preset(Control.PRESET_FULL_RECT)
settings_editor.add_child(settings_center)
var settings_vbox = VBoxContainer.new()
settings_vbox.custom_minimum_size = Vector2(600, 400)
settings_center.add_child(settings_vbox)
settings_back_btn = Button.new()
settings_back_btn.text = "Back to Menu"
settings_vbox.add_child(settings_back_btn)
var tab_container = TabContainer.new()
tab_container.size_flags_vertical = Control.SIZE_EXPAND_FILL
settings_vbox.add_child(tab_container)
# General Tab
var general_tab = VBoxContainer.new()
general_tab.name = "General"
tab_container.add_child(general_tab)
var user_hbox = HBoxContainer.new()
general_tab.add_child(user_hbox)
var user_lbl = Label.new()
user_lbl.text = "Username:"
user_lbl.custom_minimum_size = Vector2(150, 0)
user_hbox.add_child(user_lbl)
var user_input = LineEdit.new()
user_input.text = SettingsManager.username
user_input.custom_minimum_size = Vector2(200, 0)
user_hbox.add_child(user_input)
var user_save = Button.new()
user_save.text = "Save"
user_save.pressed.connect(func():
var final_name = user_input.text.strip_edges()
user_input.text = final_name
SettingsManager.username = final_name
SettingsManager._save_settings()
var nm = get_node_or_null("/root/NetworkManager")
if nm:
if nm.multiplayer.has_multiplayer_peer() and not nm.multiplayer.multiplayer_peer is OfflineMultiplayerPeer:
nm.update_username.rpc_id(1, final_name)
else:
nm.update_username(final_name)
)
user_hbox.add_child(user_save)
var fov_hbox = HBoxContainer.new()
general_tab.add_child(fov_hbox)
var fov_lbl = Label.new()
fov_lbl.text = "World FOV"
fov_lbl.custom_minimum_size = Vector2(150, 0)
fov_hbox.add_child(fov_lbl)
_world_fov_slider = HSlider.new()
_world_fov_slider.min_value = 70.0
_world_fov_slider.max_value = 120.0
_world_fov_slider.step = 1.0
_world_fov_slider.value = SettingsManager.world_fov
_world_fov_slider.custom_minimum_size = Vector2(200, 0)
_world_fov_slider.size_flags_vertical = Control.SIZE_SHRINK_CENTER
fov_hbox.add_child(_world_fov_slider)
_world_fov_input = LineEdit.new()
_world_fov_input.text = str(round(SettingsManager.world_fov))
_world_fov_input.custom_minimum_size = Vector2(60, 0)
fov_hbox.add_child(_world_fov_input)
_world_fov_slider.value_changed.connect(func(value: float):
_world_fov_input.text = str(round(value))
SettingsManager.world_fov = value
SettingsManager._save_settings()
)
_world_fov_input.text_submitted.connect(func(new_text: String):
var val = new_text.to_float()
val = clampf(val, 70.0, 120.0)
_world_fov_slider.value = val
_world_fov_input.text = str(round(val))
SettingsManager.world_fov = val
SettingsManager._save_settings()
)
var spacer_gen = Control.new()
spacer_gen.size_flags_vertical = Control.SIZE_EXPAND_FILL
general_tab.add_child(spacer_gen)
var reset_gen_btn = Button.new()
reset_gen_btn.text = "Reset to Defaults"
reset_gen_btn.pressed.connect(func():
_world_fov_slider.value = 90.0
SettingsManager.world_fov = 90.0
SettingsManager._save_settings()
)
general_tab.add_child(reset_gen_btn)
# Video Tab
var video_tab = VBoxContainer.new()
video_tab.name = "Video"
video_tab.add_theme_constant_override("separation", 10)
tab_container.add_child(video_tab)
var mode_hbox = HBoxContainer.new()
video_tab.add_child(mode_hbox)
var mode_lbl = Label.new()
mode_lbl.text = "Display Mode"
mode_lbl.custom_minimum_size = Vector2(150, 0)
mode_hbox.add_child(mode_lbl)
_display_mode_opt = OptionButton.new()
_display_mode_opt.add_item("Windowed", DisplayServer.WINDOW_MODE_WINDOWED)
_display_mode_opt.add_item("Borderless Fullscreen", DisplayServer.WINDOW_MODE_FULLSCREEN)
_display_mode_opt.add_item("Exclusive Fullscreen", DisplayServer.WINDOW_MODE_EXCLUSIVE_FULLSCREEN)
_display_mode_opt.item_selected.connect(func(index: int):
SettingsManager.display_mode = _display_mode_opt.get_item_id(index)
SettingsManager._save_settings()
SettingsManager.apply_video_settings()
)
mode_hbox.add_child(_display_mode_opt)
var res_hbox = HBoxContainer.new()
video_tab.add_child(res_hbox)
var res_lbl = Label.new()
res_lbl.text = "Resolution"
res_lbl.custom_minimum_size = Vector2(150, 0)
res_hbox.add_child(res_lbl)
_resolution_opt = OptionButton.new()
var res_names = ["640x480 (480p)", "1280x720 (720p)", "1920x1080 (1080p)", "2560x1080 (UW-1080p)", "2560x1440 (1440p)", "3440x1440 (UW-1440p)", "3840x2160 (4K)"]
for i in range(res_names.size()):
_resolution_opt.add_item(res_names[i], i)
_resolution_opt.item_selected.connect(func(index: int):
SettingsManager.resolution_index = index
SettingsManager._save_settings()
SettingsManager.apply_video_settings()
)
res_hbox.add_child(_resolution_opt)
var fps_hbox = HBoxContainer.new()
video_tab.add_child(fps_hbox)
var fps_lbl = Label.new()
fps_lbl.text = "FPS Cap"
fps_lbl.custom_minimum_size = Vector2(150, 0)
fps_hbox.add_child(fps_lbl)
_fps_slider = HSlider.new()
_fps_slider.min_value = 30.0
_fps_slider.max_value = 361.0
_fps_slider.step = 1.0
_fps_slider.custom_minimum_size = Vector2(200, 0)
_fps_slider.size_flags_vertical = Control.SIZE_SHRINK_CENTER
fps_hbox.add_child(_fps_slider)
_fps_input = LineEdit.new()
_fps_input.custom_minimum_size = Vector2(80, 0)
fps_hbox.add_child(_fps_input)
_fps_slider.value_changed.connect(func(value: float):
if value > 360.0:
_fps_input.text = "Uncapped"
SettingsManager.fps_cap = 0
else:
_fps_input.text = str(int(value))
SettingsManager.fps_cap = int(value)
SettingsManager._save_settings()
SettingsManager.apply_video_settings()
)
_fps_input.text_submitted.connect(func(new_text: String):
var val = new_text.to_int()
if val > 360 or val <= 0:
_fps_slider.value = 361.0
_fps_input.text = "Uncapped"
SettingsManager.fps_cap = 0
else:
val = max(30, val)
_fps_slider.value = float(val)
_fps_input.text = str(val)
SettingsManager.fps_cap = val
SettingsManager._save_settings()
SettingsManager.apply_video_settings()
)
var spacer_vid = Control.new()
spacer_vid.size_flags_vertical = Control.SIZE_EXPAND_FILL
video_tab.add_child(spacer_vid)
var reset_vid_btn = Button.new()
reset_vid_btn.text = "Reset to Defaults"
reset_vid_btn.pressed.connect(func():
SettingsManager.display_mode = DisplayServer.WINDOW_MODE_WINDOWED
SettingsManager.resolution_index = 2
SettingsManager.fps_cap = 0
SettingsManager._save_settings()
SettingsManager.apply_video_settings()
_populate_video_ui()
)
video_tab.add_child(reset_vid_btn)
# Audio Tab
var audio_tab = VBoxContainer.new()
audio_tab.name = "Audio"
audio_tab.add_theme_constant_override("separation", 10)
tab_container.add_child(audio_tab)
# Master
var master_arr = _build_vol_control(audio_tab, "Master Volume")
_vol_master_slider = master_arr[0]
_vol_master_input = master_arr[1]
_vol_master_slider.value_changed.connect(func(v): _on_vol_changed("vol_master", v, _vol_master_input))
_vol_master_input.text_submitted.connect(func(t): _on_vol_text_changed("vol_master", t, _vol_master_slider, _vol_master_input))
# SFX
var sfx_arr = _build_vol_control(audio_tab, "Sound Effects")
_vol_sfx_slider = sfx_arr[0]
_vol_sfx_input = sfx_arr[1]
_vol_sfx_slider.value_changed.connect(func(v): _on_vol_changed("vol_sfx", v, _vol_sfx_input))
_vol_sfx_input.text_submitted.connect(func(t): _on_vol_text_changed("vol_sfx", t, _vol_sfx_slider, _vol_sfx_input))
# Music
var music_arr = _build_vol_control(audio_tab, "Music")
_vol_music_slider = music_arr[0]
_vol_music_input = music_arr[1]
_vol_music_slider.value_changed.connect(func(v): _on_vol_changed("vol_music", v, _vol_music_input))
_vol_music_input.text_submitted.connect(func(t): _on_vol_text_changed("vol_music", t, _vol_music_slider, _vol_music_input))
# Wind
var wind_arr = _build_vol_control(audio_tab, "Wind")
_vol_wind_slider = wind_arr[0]
_vol_wind_input = wind_arr[1]
_vol_wind_slider.value_changed.connect(func(v): _on_vol_changed("vol_wind", v, _vol_wind_input))
_vol_wind_input.text_submitted.connect(func(t): _on_vol_text_changed("vol_wind", t, _vol_wind_slider, _vol_wind_input))
var spacer_aud = Control.new()
spacer_aud.size_flags_vertical = Control.SIZE_EXPAND_FILL
audio_tab.add_child(spacer_aud)
var reset_aud_btn = Button.new()
reset_aud_btn.text = "Reset to Defaults"
reset_aud_btn.pressed.connect(func():
SettingsManager.vol_master = 100.0
SettingsManager.vol_sfx = 100.0
SettingsManager.vol_music = 100.0
SettingsManager.vol_wind = 100.0
SettingsManager._save_settings()
SettingsManager.apply_audio_settings()
_populate_audio_ui()
)
audio_tab.add_child(reset_aud_btn)
# Mouse Tab
var mouse_tab = VBoxContainer.new()
mouse_tab.name = "Mouse"
mouse_tab.add_theme_constant_override("separation", 10)
tab_container.add_child(mouse_tab)
var sens_hbox = HBoxContainer.new()
mouse_tab.add_child(sens_hbox)
var sens_title = Label.new()
sens_title.text = "Sensitivity"
sens_title.custom_minimum_size = Vector2(150, 0)
sens_hbox.add_child(sens_title)
_mouse_sens_slider = HSlider.new()
_mouse_sens_slider.min_value = 1.0
_mouse_sens_slider.max_value = 100.0
_mouse_sens_slider.step = 1.0
_mouse_sens_slider.value = SettingsManager.mouse_sensitivity * 10000.0
_mouse_sens_slider.custom_minimum_size = Vector2(200, 0)
_mouse_sens_slider.size_flags_vertical = Control.SIZE_SHRINK_CENTER
sens_hbox.add_child(_mouse_sens_slider)
_mouse_sens_input = LineEdit.new()
_mouse_sens_input.text = str(round(SettingsManager.mouse_sensitivity * 10000.0))
_mouse_sens_input.custom_minimum_size = Vector2(60, 0)
sens_hbox.add_child(_mouse_sens_input)
_mouse_sens_slider.value_changed.connect(func(value: float):
_mouse_sens_input.text = str(round(value))
SettingsManager.mouse_sensitivity = value / 10000.0
SettingsManager._save_settings()
)
_mouse_sens_input.text_submitted.connect(func(new_text: String):
var val = new_text.to_float()
val = clampf(val, 1.0, 100.0)
_mouse_sens_slider.value = val
_mouse_sens_input.text = str(round(val))
SettingsManager.mouse_sensitivity = val / 10000.0
SettingsManager._save_settings()
)
var ads_sens_hbox = HBoxContainer.new()
mouse_tab.add_child(ads_sens_hbox)
var ads_sens_title = Label.new()
ads_sens_title.text = "ADS Sensitivity"
ads_sens_title.custom_minimum_size = Vector2(150, 0)
ads_sens_hbox.add_child(ads_sens_title)
_ads_sens_slider = HSlider.new()
_ads_sens_slider.min_value = 1.0
_ads_sens_slider.max_value = 100.0
_ads_sens_slider.step = 1.0
_ads_sens_slider.value = SettingsManager.ads_sensitivity * 10000.0
_ads_sens_slider.custom_minimum_size = Vector2(200, 0)
_ads_sens_slider.size_flags_vertical = Control.SIZE_SHRINK_CENTER
ads_sens_hbox.add_child(_ads_sens_slider)
_ads_sens_input = LineEdit.new()
_ads_sens_input.text = str(round(SettingsManager.ads_sensitivity * 10000.0))
_ads_sens_input.custom_minimum_size = Vector2(60, 0)
ads_sens_hbox.add_child(_ads_sens_input)
_ads_sens_slider.value_changed.connect(func(value: float):
_ads_sens_input.text = str(round(value))
SettingsManager.ads_sensitivity = value / 10000.0
SettingsManager._save_settings()
)
_ads_sens_input.text_submitted.connect(func(new_text: String):
var val = new_text.to_float()
val = clampf(val, 1.0, 100.0)
_ads_sens_slider.value = val
_ads_sens_input.text = str(round(val))
SettingsManager.ads_sensitivity = val / 10000.0
SettingsManager._save_settings()
)
var spacer_mouse = Control.new()
spacer_mouse.size_flags_vertical = Control.SIZE_EXPAND_FILL
mouse_tab.add_child(spacer_mouse)
var reset_mouse_btn = Button.new()
reset_mouse_btn.text = "Reset to Defaults"
reset_mouse_btn.pressed.connect(func():
_mouse_sens_slider.value = 20.0
_ads_sens_slider.value = 10.0
SettingsManager.mouse_sensitivity = 0.002
SettingsManager.ads_sensitivity = 0.001
SettingsManager._save_settings()
)
mouse_tab.add_child(reset_mouse_btn)
# Keybindings Tab
var key_tab = VBoxContainer.new()
key_tab.name = "Keybindings"
tab_container.add_child(key_tab)
var scroll = ScrollContainer.new()
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
key_tab.add_child(scroll)
keybind_vbox = VBoxContainer.new()
keybind_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
scroll.add_child(keybind_vbox)
var reset_key_btn = Button.new()
reset_key_btn.text = "Reset to Defaults"
reset_key_btn.pressed.connect(func():
SettingsManager.reset_to_defaults()
_populate_keybindings()
)
key_tab.add_child(reset_key_btn)
# Advanced Tab
var advanced_tab = VBoxContainer.new()
advanced_tab.name = "Advanced"
advanced_tab.add_theme_constant_override("separation", 10)
tab_container.add_child(advanced_tab)
var debug_label = Label.new()
debug_label.text = "--- Debug ---"
debug_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
advanced_tab.add_child(debug_label)
_debug_ui_checkbox = CheckButton.new()
_debug_ui_checkbox.text = "Show Debug UI (Speed, State, Buffs)"
_debug_ui_checkbox.button_pressed = SettingsManager.show_debug_ui
_debug_ui_checkbox.toggled.connect(func(toggled_on: bool):
SettingsManager.show_debug_ui = toggled_on
SettingsManager._save_settings()
)
advanced_tab.add_child(_debug_ui_checkbox)
_show_fps_checkbox = CheckButton.new()
_show_fps_checkbox.text = "Show FPS Counter"
_show_fps_checkbox.button_pressed = SettingsManager.show_fps
_show_fps_checkbox.toggled.connect(func(toggled_on: bool):
SettingsManager.show_fps = toggled_on
SettingsManager._save_settings()
)
advanced_tab.add_child(_show_fps_checkbox)
_show_movement_speed_checkbox = CheckButton.new()
_show_movement_speed_checkbox.text = "Show Movement Speed Indicator"
_show_movement_speed_checkbox.button_pressed = SettingsManager.show_movement_speed
_show_movement_speed_checkbox.toggled.connect(func(toggled_on: bool):
SettingsManager.show_movement_speed = toggled_on
SettingsManager._save_settings()
)
advanced_tab.add_child(_show_movement_speed_checkbox)
var spacer_adv = Control.new()
spacer_adv.size_flags_vertical = Control.SIZE_EXPAND_FILL
advanced_tab.add_child(spacer_adv)
var reset_adv_btn = Button.new()
reset_adv_btn.text = "Reset to Defaults"
reset_adv_btn.pressed.connect(func():
_debug_ui_checkbox.button_pressed = false
SettingsManager.show_debug_ui = false
_show_fps_checkbox.button_pressed = false
SettingsManager.show_fps = false
_show_movement_speed_checkbox.button_pressed = false
SettingsManager.show_movement_speed = false
SettingsManager._save_settings()
)
advanced_tab.add_child(reset_adv_btn)
# Rebind Overlay
rebind_overlay = ColorRect.new()
rebind_overlay.color = Color(0, 0, 0, 0.85)
rebind_overlay.set_anchors_preset(Control.PRESET_FULL_RECT)
rebind_overlay.visible = false
add_child(rebind_overlay)
var rebind_center = CenterContainer.new()
rebind_center.set_anchors_preset(Control.PRESET_FULL_RECT)
rebind_overlay.add_child(rebind_center)
rebind_label = Label.new()
rebind_label.text = "Press any key..."
rebind_label.add_theme_font_size_override("font_size", 32)
rebind_center.add_child(rebind_label)
# ── Quit Dialog ──
quit_dialog = ConfirmationDialog.new()
quit_dialog.title = "Quit Game"
quit_dialog.dialog_text = "Are you sure you want to close the game?"
add_child(quit_dialog)
# ── Return Dialog ──
return_dialog = ConfirmationDialog.new()
return_dialog.title = "Main Menu"
return_dialog.dialog_text = "Are you sure you want to return to the main menu?"
add_child(return_dialog)
func _build_vol_control(parent: Node, label_name: String) -> Array:
var hbox = HBoxContainer.new()
parent.add_child(hbox)
var lbl = Label.new()
lbl.text = label_name
lbl.custom_minimum_size = Vector2(150, 0)
hbox.add_child(lbl)
var slider = HSlider.new()
slider.min_value = 0.0
slider.max_value = 100.0
slider.step = 1.0
slider.size_flags_horizontal = Control.SIZE_EXPAND_FILL
slider.custom_minimum_size = Vector2(150, 0)
hbox.add_child(slider)
var input = LineEdit.new()
input.custom_minimum_size = Vector2(60, 0)
hbox.add_child(input)
return [slider, input]
func _on_vol_changed(setting_name: String, value: float, input_node: LineEdit) -> void:
SettingsManager.set(setting_name, value)
input_node.text = str(value)
SettingsManager._save_settings()
SettingsManager.apply_audio_settings()
func _on_vol_text_changed(setting_name: String, text: String, slider_node: HSlider, input_node: LineEdit) -> void:
var val = clampf(text.to_float(), 0.0, 100.0)
SettingsManager.set(setting_name, val)
slider_node.value = val
input_node.text = str(val)
SettingsManager._save_settings()
SettingsManager.apply_audio_settings()
func _create_label(text: String) -> Label:
var l = Label.new()
l.text = text
return l
func _connect_signals() -> void:
resume_btn.pressed.connect(_resume)
respawn_btn.pressed.connect(_respawn)
loadouts_btn.pressed.connect(_show_loadouts)
settings_btn.pressed.connect(_show_settings)
return_btn.pressed.connect(_show_return_dialog)
exit_btn.pressed.connect(_show_exit_dialog)
back_to_main_btn.pressed.connect(_show_main_menu)
settings_back_btn.pressed.connect(_show_main_menu)
save_loadout_btn.pressed.connect(_save_current_loadout)
quit_dialog.confirmed.connect(_quit_game)
return_dialog.confirmed.connect(_return_to_main_menu)
func _show_return_dialog() -> void:
return_dialog.popup_centered()
func _return_to_main_menu() -> void:
_resume() # Reset pause state and cursor mode
var nm = get_node_or_null("/root/NetworkManager")
if nm:
nm.disconnect_game()
get_tree().change_scene_to_file("res://ui/main_menu/main_menu.tscn")
func _show_main_menu() -> void:
if get_tree().current_scene and get_tree().current_scene.name == "MainMenu":
visible = false
return
main_vbox.visible = true
loadout_editor.visible = false
settings_editor.visible = false
func _show_loadouts() -> void:
main_vbox.visible = false
settings_editor.visible = false
loadout_editor.visible = true
edit_panel.visible = false
_populate_loadout_list()
func _populate_video_ui() -> void:
if _display_mode_opt:
for i in range(_display_mode_opt.item_count):
if _display_mode_opt.get_item_id(i) == SettingsManager.display_mode:
_display_mode_opt.select(i)
break
if _resolution_opt:
_resolution_opt.select(SettingsManager.resolution_index)
if _fps_slider and _fps_input:
if SettingsManager.fps_cap == 0:
_fps_slider.value = 361.0
_fps_input.text = "Uncapped"
else:
_fps_slider.value = float(SettingsManager.fps_cap)
_fps_input.text = str(SettingsManager.fps_cap)
func _populate_audio_ui() -> void:
if _vol_master_slider: _vol_master_slider.value = SettingsManager.vol_master
if _vol_master_input: _vol_master_input.text = str(SettingsManager.vol_master)
if _vol_sfx_slider: _vol_sfx_slider.value = SettingsManager.vol_sfx
if _vol_sfx_input: _vol_sfx_input.text = str(SettingsManager.vol_sfx)
if _vol_music_slider: _vol_music_slider.value = SettingsManager.vol_music
if _vol_music_input: _vol_music_input.text = str(SettingsManager.vol_music)
if _vol_wind_slider: _vol_wind_slider.value = SettingsManager.vol_wind
if _vol_wind_input: _vol_wind_input.text = str(SettingsManager.vol_wind)
func _show_settings() -> void:
main_vbox.visible = false
loadout_editor.visible = false
settings_editor.visible = true
_populate_keybindings()
_populate_video_ui()
_populate_audio_ui()
if _debug_ui_checkbox:
_debug_ui_checkbox.button_pressed = SettingsManager.show_debug_ui
if _show_fps_checkbox:
_show_fps_checkbox.button_pressed = SettingsManager.show_fps
if _show_movement_speed_checkbox:
_show_movement_speed_checkbox.button_pressed = SettingsManager.show_movement_speed
if _world_fov_slider:
_world_fov_slider.value = SettingsManager.world_fov
_world_fov_input.text = str(round(SettingsManager.world_fov))
if _mouse_sens_slider:
var display_val = round(SettingsManager.mouse_sensitivity * 10000.0)
_mouse_sens_slider.value = display_val
_mouse_sens_input.text = str(display_val)
var ads_display_val = round(SettingsManager.ads_sensitivity * 10000.0)
_ads_sens_slider.value = ads_display_val
_ads_sens_input.text = str(ads_display_val)
_mouse_sens_slider.value = display_val
if _mouse_sens_input:
_mouse_sens_input.text = str(display_val)
func _populate_keybindings() -> void:
for c in keybind_vbox.get_children():
c.queue_free()
var actions = SettingsManager.default_bindings.keys()
for action in actions:
var hbox = HBoxContainer.new()
var lbl = Label.new()
lbl.text = action.capitalize()
lbl.custom_minimum_size = Vector2(200, 0)
hbox.add_child(lbl)
var btn = Button.new()
btn.text = _get_key_name(action)
btn.custom_minimum_size = Vector2(150, 0)
btn.pressed.connect(func(): _start_rebind(action))
hbox.add_child(btn)
keybind_vbox.add_child(hbox)
func _start_rebind(action: String) -> void:
waiting_for_input_action = action
rebind_label.text = "Press any key for: " + action.capitalize()
rebind_overlay.visible = true
func _get_key_name(action: String) -> String:
if not InputMap.has_action(action):
return "?"
var events = InputMap.action_get_events(action)
for e in events:
if e is InputEventKey:
var code = e.physical_keycode if e.physical_keycode != 0 else e.keycode
return OS.get_keycode_string(code)
elif e is InputEventMouseButton:
if e.button_index == MOUSE_BUTTON_LEFT: return "LClick"
elif e.button_index == MOUSE_BUTTON_RIGHT: return "RClick"
elif e.button_index == MOUSE_BUTTON_MIDDLE: return "MClick"
elif e.button_index == MOUSE_BUTTON_WHEEL_UP: return "MWheel Up"
elif e.button_index == MOUSE_BUTTON_WHEEL_DOWN: return "MWheel Down"
return "?"
func _populate_loadout_list() -> void:
for c in loadout_list_vbox.get_children():
c.queue_free()
for i in range(LoadoutManager.loadouts.size()):
var l = LoadoutManager.loadouts[i]
var hbox = HBoxContainer.new()
var btn = Button.new()
var is_active = (i == LoadoutManager.active_loadout_index)
btn.text = l["name"] + (" (Active)" if is_active else "")
btn.custom_minimum_size = Vector2(200, 40)
btn.pressed.connect(func(): _edit_loadout(i))
hbox.add_child(btn)
var equip_btn = Button.new()
equip_btn.text = "Equip"
equip_btn.disabled = is_active
equip_btn.pressed.connect(func(): _equip_loadout(i))
hbox.add_child(equip_btn)
loadout_list_vbox.add_child(hbox)
func _equip_loadout(idx: int) -> void:
LoadoutManager.active_loadout_index = idx
LoadoutManager.save_loadouts()
_populate_loadout_list()
func _edit_loadout(idx: int) -> void:
editing_index = idx
var l = LoadoutManager.loadouts[idx]
name_edit.text = l["name"]
_populate_options(primary1_opt, "primary", l["primary_1"])
_populate_options(primary2_opt, "primary", l["primary_2"])
_populate_options(special_opt, "special", l["special"])
if l.has("melee"):
_populate_options(melee_opt, "melee", l["melee"])
edit_panel.visible = true
func _populate_options(opt: OptionButton, category: String, current_id: String) -> void:
opt.clear()
var idx = 0
var select_idx = 0
opt.add_item("None", 0)
opt.set_item_metadata(0, "none")
if current_id == "none": select_idx = 0
idx += 1
for key in LoadoutManager.weapon_db:
if key == "none": continue
var w = LoadoutManager.weapon_db[key]
if w["category"] == category:
opt.add_item(w["name"], idx)
opt.set_item_metadata(idx, key)
if key == current_id:
select_idx = idx
idx += 1
opt.select(select_idx)
func _save_current_loadout() -> void:
if editing_index >= 0 and editing_index < LoadoutManager.loadouts.size():
var l = LoadoutManager.loadouts[editing_index]
if name_edit.text.strip_edges() != "":
l["name"] = name_edit.text.strip_edges()
name_edit.text = l["name"]
l["primary_1"] = primary1_opt.get_item_metadata(primary1_opt.get_selected_id())
l["primary_2"] = primary2_opt.get_item_metadata(primary2_opt.get_selected_id())
l["special"] = special_opt.get_item_metadata(special_opt.get_selected_id())
l["melee"] = melee_opt.get_item_metadata(melee_opt.get_selected_id())
LoadoutManager.save_loadouts()
_populate_loadout_list()
edit_panel.visible = false
func _show_exit_dialog() -> void:
quit_dialog.popup_centered()
func _quit_game() -> void:
get_tree().quit()