diff --git a/assets/text/text.csv b/assets/text/text.csv new file mode 100644 index 0000000..84de66a --- /dev/null +++ b/assets/text/text.csv @@ -0,0 +1,58 @@ +keys,en +UI_UNSET,unset +UI_LISTEN,listening... +UI_CANCEL,Cancel +UI_ACCEPT,Accept +UI_START,Start +UI_ON,On +UI_OFF,Off +UI_BACK,"⏎ Back" +UI_LOCKED,Locked +UI_QUIT,Quit +, +PAUSE_HEADING,Paused +PAUSE_RESUME,Resume +PAUSE_SETTINGS,Settings +PAUSE_END,"Quit to Title" +PAUSE_END_MSG,"End the game and return to the title screen?\nUnsaved progress will be lost." +PAUSE_QUIT,"Quit to Desktop" +PAUSE_QUIT_MSG,"Quit to desktop?\nUnsaved progress will be lost." +, +SETTINGS_GAME,Game +SETTINGS_GAME_HEADING,"Game Configuration" +SETTINGS_GAME_ACCESSIBILITY_HEADING,Accessibility +SETTINGS_GAME_CAMERA_HEADING,Camera +SETTINGS_SCREEN_SHAKE,"Enable Screen Shake" +SETTINGS_HEAD_BOB,"Enable Head Bob" +SETTINGS_SENSITIVITY_X,"Sensitivity, Horizontal" +SETTINGS_SENSITIVITY_Y,"Sensitivity, Vertical" +SETTINGS_MOUSE_ACCELERATION,"Mouse Acceleration" +SETTINGS_INVERT_PITCH,"Invert Pitch" +SETTINGS_GRAPHICS,Graphics +SETTINGS_GRAPHICS_HEADING,"Graphics & Display" +SETTINGS_GRAPHICS_DISPLAY_HEADING,Display +SETTINGS_GRAPHICS_FULLSCREEN,Fullscreen +SETTINGS_GRAPHICS_VSYNC,V-Sync +SETTINGS_AUDIO,Audio +SETTINGS_AUDIO_HEADING,"Audio Settings" +SETTINGS_CONTROLS,Controls +SETTINGS_CONTROLS_HEADING,"Control Bindings" +, +ACTION_move_forward,"Move forward" +ACTION_move_left,"Move left" +ACTION_move_back,"Move back" +ACTION_move_right,"Move right" +ACTION_jump,Jump +ACTION_sneak,Sneak +ACTION_sprint,Sprint +ACTION_fire,"Fire equipped tool" +ACTION_interact,Interact +ACTION_switch_mode,"Tool mode switch" +ACTION_select_next_tool,"Select next tool" +ACTION_select_prev_tool,"Select previous tool" +ACTION_select_point_spray,"Select spot spray" +ACTION_select_wide_spray,"Select wide spray" +ACTION_select_brush,"Select toothbrush" +ACTION_select_mp3_player,"Select MP3 player" +ACTION_select_manual,"Select manual" +ACTION_pause,Pause diff --git a/assets/text/text.csv.import b/assets/text/text.csv.import new file mode 100644 index 0000000..503c686 --- /dev/null +++ b/assets/text/text.csv.import @@ -0,0 +1,17 @@ +[remap] + +importer="csv_translation" +type="Translation" +uid="uid://o71c6t5pv7hy" + +[deps] + +files=["res://assets/text/text.en.translation"] + +source_file="res://assets/text/text.csv" +dest_files=["res://assets/text/text.en.translation"] + +[params] + +compress=true +delimiter=0 diff --git a/project.godot b/project.godot index ec36a2e..48f4d69 100644 --- a/project.godot +++ b/project.godot @@ -17,12 +17,15 @@ warnings/threads/thread_model=2 config/name="Grunk" run/main_scene="uid://884jqafhtrv0" +config/project_settings_override="user://settings.godot" config/features=PackedStringArray("4.4", "Forward Plus") run/max_fps=60 [autoload] +GameRuntime="*res://src/game/game_runtime.gd" ItemCatalog="*res://src/items/item_catalog.tscn" +GameSettings="*res://src/game/game_settings.gd" GameManager="*res://src/game/game_manager.tscn" [debug] @@ -65,6 +68,9 @@ config/input/mouse_sensitivity_x=0.45 config/input/mouse_sensitivity_y=0.45 config/input/invert_pitch=false config/input/mouse_acceleration=30.0 +audio/buses/override_bus_layout="user://audio_bus_layout.tres" +config/accessibility/enable_screen_shake=true +config/accessibility/enable_head_bob=true [input] @@ -118,6 +124,16 @@ switch_mode={ "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194306,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } +select_next_tool={ +"deadzone": 0.2, +"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":4,"canceled":false,"pressed":false,"double_click":false,"script":null) +] +} +select_prev_tool={ +"deadzone": 0.2, +"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":5,"canceled":false,"pressed":false,"double_click":false,"script":null) +] +} select_point_spray={ "deadzone": 0.2, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":49,"key_label":0,"unicode":49,"location":0,"echo":false,"script":null) @@ -133,14 +149,14 @@ select_brush={ "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":51,"key_label":0,"unicode":51,"location":0,"echo":false,"script":null) ] } -select_next_tool={ +select_mp3_player={ "deadzone": 0.2, -"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":4,"canceled":false,"pressed":false,"double_click":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":52,"key_label":0,"unicode":52,"location":0,"echo":false,"script":null) ] } -select_prev_tool={ +select_manual={ "deadzone": 0.2, -"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":5,"canceled":false,"pressed":false,"double_click":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":53,"key_label":0,"unicode":53,"location":0,"echo":false,"script":null) ] } pause={ @@ -149,6 +165,10 @@ pause={ ] } +[internationalization] + +locale/translations=PackedStringArray("res://assets/text/text.en.translation") + [layer_names] 3d_render/layer_1="World" diff --git a/src/game/game.gd b/src/game/game.gd index 1ad8cea..4cf0637 100644 --- a/src/game/game.gd +++ b/src/game/game.gd @@ -5,6 +5,12 @@ class_name Game extends Node static var manager: GameManagerType: get(): return GameManager +static var settings: GameSettingsType: + get(): + return GameSettings +static var runtime: GameRuntimeType: + get(): + return GameRuntime ## Global static access to Game singleton static var instance: Game diff --git a/src/game/game_runtime.gd b/src/game/game_runtime.gd new file mode 100644 index 0000000..b6178ca --- /dev/null +++ b/src/game/game_runtime.gd @@ -0,0 +1,9 @@ +class_name GameRuntimeType extends Node +## Autoloaded runtime manager + + +func _notification(what: int) -> void: + if what == NOTIFICATION_WM_CLOSE_REQUEST: + # Handle close request here + # TODO: save? + get_tree().quit() diff --git a/src/game/game_runtime.gd.uid b/src/game/game_runtime.gd.uid new file mode 100644 index 0000000..7bae18d --- /dev/null +++ b/src/game/game_runtime.gd.uid @@ -0,0 +1 @@ +uid://dcg434tx4r288 diff --git a/src/game/game_settings.gd b/src/game/game_settings.gd new file mode 100644 index 0000000..f37324e --- /dev/null +++ b/src/game/game_settings.gd @@ -0,0 +1,53 @@ +class_name GameSettingsType extends Node +## Autoloaded container for project settings, for quick runtime access. + +var version: String + +var settings_file: String +var audio_bus_file: String + +var mouse_sensitivity_x: float +var mouse_sensitivity_y: float +var mouse_acceleration: float +var invert_pitch: bool + +var enable_screen_shake: bool +var enable_head_bob: bool + + +func _init() -> void: + ProjectSettings.settings_changed.connect(_read_settings) + _read_settings() + _load_audio_bus_override() + + +func _read_settings() -> void: + version = ProjectSettings.get_setting("application/config/version") + + settings_file = ProjectSettings.get_setting("application/config/project_settings_override") + audio_bus_file = ProjectSettings.get_setting("game/audio/buses/override_bus_layout") + + mouse_sensitivity_x = ProjectSettings.get_setting("game/config/input/mouse_sensitivity_x") + mouse_sensitivity_y = ProjectSettings.get_setting("game/config/input/mouse_sensitivity_y") + mouse_acceleration = ProjectSettings.get_setting("game/config/input/mouse_acceleration") + invert_pitch = ProjectSettings.get_setting("game/config/input/invert_pitch") + + enable_screen_shake = ProjectSettings.get_setting( + "game/config/accessibility/enable_screen_shake" + ) + enable_head_bob = ProjectSettings.get_setting("game/config/accessibility/enable_head_bob") + + +func _load_audio_bus_override() -> void: + # Load override audio bus file + if FileAccess.file_exists(audio_bus_file): + print_debug("Loading audio bus override from ", audio_bus_file) + var bus_layout: AudioBusLayout = load(audio_bus_file) + AudioServer.set_bus_layout(bus_layout) + + +func write() -> void: + print_debug("Writing settings to ", settings_file) + ProjectSettings.save_custom(settings_file) + print_debug("Writing audio bus levels to ", audio_bus_file) + ResourceSaver.save(AudioServer.generate_bus_layout(), audio_bus_file) diff --git a/src/game/game_settings.gd.uid b/src/game/game_settings.gd.uid new file mode 100644 index 0000000..4386b04 --- /dev/null +++ b/src/game/game_settings.gd.uid @@ -0,0 +1 @@ +uid://bo00uxdf81beu diff --git a/src/ui/elements/checker_container.gd b/src/ui/elements/checker_container.gd new file mode 100644 index 0000000..1df47f5 --- /dev/null +++ b/src/ui/elements/checker_container.gd @@ -0,0 +1,19 @@ +@tool +class_name CheckerContainer extends PanelContainer +## PanelContainer that sets its theme based on its parent parity + +@export var even_variation := "CheckerContainerEven" +@export var odd_variation := "CheckerContainerOdd" + + +func _ready() -> void: + get_parent().child_order_changed.connect(_recompute) + + +func _is_even_child() -> bool: + var parent := get_parent() + return parent.get_children().find(self) % 2 == 0 if parent else false + + +func _recompute() -> void: + theme_type_variation = even_variation if _is_even_child() else odd_variation diff --git a/src/ui/elements/checker_container.gd.uid b/src/ui/elements/checker_container.gd.uid new file mode 100644 index 0000000..4a7cbe3 --- /dev/null +++ b/src/ui/elements/checker_container.gd.uid @@ -0,0 +1 @@ +uid://csuuvuagv6lpf diff --git a/src/ui/elements/input_prompt/input_prompt.gd b/src/ui/elements/input_prompt/input_prompt.gd new file mode 100644 index 0000000..358e827 --- /dev/null +++ b/src/ui/elements/input_prompt/input_prompt.gd @@ -0,0 +1,63 @@ +@tool +extends Label +## A prompt with the input symbol bound to the given action. + +const PROMPT_FORMAT := "{0} - {1}" +const HOLD_FORMAT := "{0} {1}" + +const UNKNOWN_LABEL_SYM := "[unknown]" + +const ACTION_HOLD_KEY := "ACTION_HOLD_MODIFIER" + +@export var action: StringName: + set(value): + action = value + _update() + +@export var hold := false: + set(value): + hold = value + _update() + +@export var show_name := true: + set(value): + show_name = value + _update() + +@export var event_index := 0: + set(value): + event_index = value + _update() + + +func _ready() -> void: + _update() + + +func _update() -> void: + var input_symbol: String + var actions := InputMap.action_get_events(action) + if actions and event_index < len(actions): + var primary := actions[event_index] + input_symbol = PromptMap.from_event(primary) + + var loc_action := tr(ControlBinding.ACTION_KEY_FMT.format([action])) + + if not input_symbol: + input_symbol = str(PromptMap.UNKNOWN_INPUT_SYMBOL) + + if hold: + input_symbol = HOLD_FORMAT.format([input_symbol, tr(ACTION_HOLD_KEY)]) + + if show_name: + text = ( + PROMPT_FORMAT + . format( + [ + input_symbol, + loc_action if loc_action else str(action), + ] + ) + ) + else: + text = input_symbol diff --git a/src/ui/elements/input_prompt/input_prompt.gd.uid b/src/ui/elements/input_prompt/input_prompt.gd.uid new file mode 100644 index 0000000..7d5ff3a --- /dev/null +++ b/src/ui/elements/input_prompt/input_prompt.gd.uid @@ -0,0 +1 @@ +uid://dn50ip335p7oh diff --git a/src/ui/elements/input_prompt/input_prompt.tscn b/src/ui/elements/input_prompt/input_prompt.tscn new file mode 100644 index 0000000..b7d4945 --- /dev/null +++ b/src/ui/elements/input_prompt/input_prompt.tscn @@ -0,0 +1,14 @@ +[gd_scene load_steps=2 format=3 uid="uid://b47goj32i6sdh"] + +[ext_resource type="Script" path="res://src/ui/elements/input_prompt/input_prompt.gd" id="1_qq6w5"] + +[node name="InputPrompt" type="Label"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_type_variation = &"InputPrompt" +text = "❓ - ACTION_" +script = ExtResource("1_qq6w5") +hold = null diff --git a/src/ui/elements/numeric_slider/numeric_slider.gd b/src/ui/elements/numeric_slider/numeric_slider.gd new file mode 100644 index 0000000..7611d64 --- /dev/null +++ b/src/ui/elements/numeric_slider/numeric_slider.gd @@ -0,0 +1,97 @@ +@tool +class_name NumericSlider extends HBoxContainer +## HSlider with an attached SpinBox + +signal value_changed(value: float) + +@export_category("Shared Range Properties") + +@export var min_value := 0.0: + set(v): + _set_shared("min_value", v) + min_value = v + +@export var max_value := 100.0: + set(v): + _set_shared("max_value", v) + max_value = v + +@export var step := 1.0: + set(v): + _set_shared("step", v) + step = v + +@export var page := 0: + set(v): + _set_shared("page", v) + page = v + +@export var value: float: + get: + return _value + set(v): + _value = v + value_changed.emit(v) + +@export var exp_edit := false: + set(v): + _set_shared("exp_edit", v) + exp_edit = v + +@export var rounded := false: + set(v): + _set_shared("rounded", v) + rounded = v + +@export var allow_greater := false: + set(v): + _set_shared("allow_greater", v) + allow_greater = v + +@export var allow_lesser := false: + set(v): + _set_shared("allow_lesser", v) + allow_lesser = v + +var _value := 0.0: + set(v): + if slider: + slider.set_value_no_signal(v) + if spin_box: + spin_box.set_value_no_signal(v) + _value = v + +@onready var slider: HSlider = $HSlider +@onready var spin_box: SpinBox = $SpinBox + + +func _ready() -> void: + # Force propagation of properties to shared range elements + min_value = min_value + max_value = max_value + step = step + page = page + _value = _value + exp_edit = exp_edit + rounded = rounded + allow_greater = allow_greater + allow_lesser = allow_lesser + + +func set_value_no_signal(new_value: float) -> void: + _value = new_value + + +func _set_shared(property: String, new_value: Variant) -> void: + if slider: + slider.set(property, new_value) + if spin_box: + spin_box.set(property, new_value) + + +func _on_spin_box_value_changed(new_value: float) -> void: + value = new_value + + +func _on_slider_value_changed(new_value: float) -> void: + value = new_value diff --git a/src/ui/elements/numeric_slider/numeric_slider.gd.uid b/src/ui/elements/numeric_slider/numeric_slider.gd.uid new file mode 100644 index 0000000..eea504a --- /dev/null +++ b/src/ui/elements/numeric_slider/numeric_slider.gd.uid @@ -0,0 +1 @@ +uid://ei2iyprpp4rt diff --git a/src/ui/elements/numeric_slider/numeric_slider.tscn b/src/ui/elements/numeric_slider/numeric_slider.tscn new file mode 100644 index 0000000..7b16079 --- /dev/null +++ b/src/ui/elements/numeric_slider/numeric_slider.tscn @@ -0,0 +1,27 @@ +[gd_scene load_steps=2 format=3 uid="uid://dqqcyi26d3bpg"] + +[ext_resource type="Script" uid="uid://ei2iyprpp4rt" path="res://src/ui/elements/numeric_slider/numeric_slider.gd" id="1_kcr4o"] + +[node name="NumericSlider" type="HBoxContainer"] +anchors_preset = 14 +anchor_top = 0.5 +anchor_right = 1.0 +anchor_bottom = 0.5 +offset_top = -8.0 +offset_bottom = 10.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_kcr4o") + +[node name="HSlider" type="HSlider" parent="."] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 4 + +[node name="SpinBox" type="SpinBox" parent="."] +custom_minimum_size = Vector2(120, 0) +layout_mode = 2 +alignment = 2 + +[connection signal="value_changed" from="HSlider" to="." method="_on_slider_value_changed"] +[connection signal="value_changed" from="SpinBox" to="." method="_on_spin_box_value_changed"] diff --git a/src/ui/elements/text_checkbox/text_checkbox.gd b/src/ui/elements/text_checkbox/text_checkbox.gd new file mode 100644 index 0000000..63ff98c --- /dev/null +++ b/src/ui/elements/text_checkbox/text_checkbox.gd @@ -0,0 +1,29 @@ +@tool +extends CheckBox +## Checkbox which updates its label based on check state. + +@export var true_text := "UI_ON": + set(value): + true_text = value + _refresh() + +@export var false_text := "UI_OFF": + set(value): + false_text = value + _refresh() + + +func _ready() -> void: + _refresh() + + +func _set_text(value: bool) -> void: + text = true_text if value else false_text + + +func _refresh() -> void: + _set_text(button_pressed) + + +func _on_toggled(toggled_on: bool) -> void: + _set_text(toggled_on) diff --git a/src/ui/elements/text_checkbox/text_checkbox.gd.uid b/src/ui/elements/text_checkbox/text_checkbox.gd.uid new file mode 100644 index 0000000..7c63adf --- /dev/null +++ b/src/ui/elements/text_checkbox/text_checkbox.gd.uid @@ -0,0 +1 @@ +uid://da0um27cm4eiv diff --git a/src/ui/elements/text_checkbox/text_checkbox.tscn b/src/ui/elements/text_checkbox/text_checkbox.tscn new file mode 100644 index 0000000..815684d --- /dev/null +++ b/src/ui/elements/text_checkbox/text_checkbox.tscn @@ -0,0 +1,10 @@ +[gd_scene load_steps=2 format=3 uid="uid://b7ce38k7rx466"] + +[ext_resource type="Script" path="res://src/ui/elements/text_checkbox/text_checkbox.gd" id="1_6mma0"] + +[node name="TextCheckbox" type="CheckBox"] +size_flags_horizontal = 0 +text = "UI_OFF" +script = ExtResource("1_6mma0") + +[connection signal="toggled" from="." to="." method="_on_toggled"] diff --git a/src/ui/menus/menu.theme b/src/ui/menus/menu.theme index 6972fbd..fc5b560 100644 Binary files a/src/ui/menus/menu.theme and b/src/ui/menus/menu.theme differ diff --git a/src/ui/menus/pause_menu/pause_menu.gd b/src/ui/menus/pause_menu/pause_menu.gd index 3363daf..cca7ac5 100644 --- a/src/ui/menus/pause_menu/pause_menu.gd +++ b/src/ui/menus/pause_menu/pause_menu.gd @@ -5,14 +5,13 @@ extends Control var _freeze_input := false -@onready var menu_list: VBoxContainer = %MenuList -@onready var quit_confirm: CenterContainer = %QuitConfirm -@onready var end_game_confirm: CenterContainer = %EndGameConfirm -@onready var settings_container: MarginContainer = %SettingsContainer +@onready var menu_list: Control = %MenuList +@onready var quit_confirm: Control = %QuitConfirm +@onready var end_game_confirm: Control = %EndGameConfirm +@onready var settings_container: Container = %SettingsContainer func _ready() -> void: - print_debug("PAUSING") Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) Engine.time_scale = 1.0 diff --git a/src/ui/menus/pause_menu/pause_menu.tscn b/src/ui/menus/pause_menu/pause_menu.tscn index e66b83f..5c5fd81 100644 --- a/src/ui/menus/pause_menu/pause_menu.tscn +++ b/src/ui/menus/pause_menu/pause_menu.tscn @@ -2,9 +2,7 @@ [ext_resource type="Theme" uid="uid://doq7ay6f7dgfo" path="res://src/ui/menus/menu.theme" id="1_b4t8b"] [ext_resource type="Script" uid="uid://cllx5glqld8wn" path="res://src/ui/menus/pause_menu/pause_menu.gd" id="1_rd0j2"] - -[sub_resource type="Resource" id="Resource_6sp1a"] -metadata/__load_path__ = "res://src/ui/menus/settings_menu/settings_menu.tscn" +[ext_resource type="PackedScene" uid="uid://d3eaqw2rdurct" path="res://src/ui/menus/settings_menu/settings_menu.tscn" id="3_dowgp"] [node name="PauseMenu" type="Control"] process_mode = 3 @@ -16,7 +14,7 @@ grow_horizontal = 2 grow_vertical = 2 theme = ExtResource("1_b4t8b") script = ExtResource("1_rd0j2") -settings_scene = SubResource("Resource_6sp1a") +settings_scene = ExtResource("3_dowgp") [node name="Shade" type="ColorRect" parent="."] layout_mode = 1 @@ -57,7 +55,7 @@ layout_mode = 2 theme_override_colors/font_color = Color(1, 1, 1, 1) theme_override_colors/font_outline_color = Color(0, 0, 0, 1) theme_override_constants/outline_size = 8 -text = "paused" +text = "PAUSE_HEADING" horizontal_alignment = 1 [node name="HSeparator" type="HSeparator" parent="MenuList"] @@ -71,34 +69,35 @@ alignment = 1 [node name="ResumeButton" type="Button" parent="MenuList/VBoxContainer"] layout_mode = 2 -text = "Resume" +text = "PAUSE_RESUME" [node name="SettingsButton" type="Button" parent="MenuList/VBoxContainer"] layout_mode = 2 -text = "Settings" +text = "PAUSE_SETTINGS" [node name="EndGameButton" type="Button" parent="MenuList/VBoxContainer"] layout_mode = 2 theme_type_variation = &"DangerButton" -text = "Quit to Title" +text = "PAUSE_END" [node name="QuitButton" type="Button" parent="MenuList/VBoxContainer"] layout_mode = 2 theme_type_variation = &"DangerButton" -text = "Quit to Desktop" +text = "PAUSE_QUIT" [node name="SettingsContainer" type="MarginContainer" parent="."] unique_name_in_owner = true +custom_minimum_size = Vector2(1000, 600) layout_mode = 1 anchors_preset = 8 anchor_left = 0.5 anchor_top = 0.5 anchor_right = 0.5 anchor_bottom = 0.5 -offset_left = -640.0 -offset_top = -360.0 -offset_right = 640.0 -offset_bottom = 360.0 +offset_left = -400.0 +offset_top = -300.0 +offset_right = 430.0 +offset_bottom = 300.0 grow_horizontal = 2 grow_vertical = 2 mouse_filter = 2 @@ -130,8 +129,7 @@ theme_override_constants/separation = 16 [node name="Label" type="Label" parent="QuitConfirm/PanelContainer/MarginContainer/VBoxContainer"] layout_mode = 2 theme_override_font_sizes/font_size = 24 -text = "End the game and return to the title screen? -Unsaved progress will be lost." +text = "PAUSE_QUIT_MSG" horizontal_alignment = 1 [node name="HBoxContainer" type="HBoxContainer" parent="QuitConfirm/PanelContainer/MarginContainer/VBoxContainer"] @@ -141,13 +139,13 @@ theme_override_constants/separation = 16 [node name="CancelButton" type="Button" parent="QuitConfirm/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"] layout_mode = 2 size_flags_horizontal = 3 -text = "Cancel" +text = "UI_CANCEL" [node name="ConfirmQuitButton" type="Button" parent="QuitConfirm/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"] layout_mode = 2 size_flags_horizontal = 3 theme_type_variation = &"DangerButton" -text = "Quit" +text = "UI_QUIT" [node name="EndGameConfirm" type="CenterContainer" parent="."] unique_name_in_owner = true @@ -176,8 +174,7 @@ theme_override_constants/separation = 16 [node name="Label" type="Label" parent="EndGameConfirm/PanelContainer/MarginContainer/VBoxContainer"] layout_mode = 2 theme_override_font_sizes/font_size = 24 -text = "End the game and return to the desktop? -Unsaved progress will be lost." +text = "PAUSE_END_MSG" horizontal_alignment = 1 [node name="HBoxContainer" type="HBoxContainer" parent="EndGameConfirm/PanelContainer/MarginContainer/VBoxContainer"] @@ -187,13 +184,13 @@ theme_override_constants/separation = 16 [node name="CancelButton" type="Button" parent="EndGameConfirm/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"] layout_mode = 2 size_flags_horizontal = 3 -text = "Cancel" +text = "UI_CANCEL" [node name="ConfirmEndGameButton" type="Button" parent="EndGameConfirm/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"] layout_mode = 2 size_flags_horizontal = 3 theme_type_variation = &"DangerButton" -text = "Quit" +text = "UI_QUIT" [connection signal="pressed" from="MenuList/VBoxContainer/ResumeButton" to="." method="resume"] [connection signal="pressed" from="MenuList/VBoxContainer/SettingsButton" to="." method="settings"] diff --git a/src/ui/menus/settings_menu/control_binding/control_binding.gd b/src/ui/menus/settings_menu/control_binding/control_binding.gd new file mode 100644 index 0000000..cad228e --- /dev/null +++ b/src/ui/menus/settings_menu/control_binding/control_binding.gd @@ -0,0 +1,108 @@ +class_name ControlBinding extends CheckerContainer +## Input for rebinding an action. + +const ACTION_KEY_FMT := "ACTION_{0}" +const LISTENING_TEXT := "UI_LISTEN" + +const MOTION_THRESHOLD := 0.5 + +@export var key: StringName + +var listening: bool = false: + set(value): + if button: + button.disabled = value + listening = value + +@onready var action: Label = %Action +@onready var button: Button = %Button + + +func _get_settings_key() -> String: + return "input/%s" % key + + +func _ready() -> void: + # gdlint:ignore = private-method-call + super._ready() + + # Set action label text + var loc_action := tr(ACTION_KEY_FMT.format([key])) + # Fall back to just the key if no localization exists + @warning_ignore("incompatible_ternary") + action.text = loc_action if loc_action else key + + # Set the binding label + _set_label_from_binding() + + +func _set_label_from_binding() -> void: + var actions := InputMap.action_get_events(key) + if actions: + var primary := actions[0] + button.text = PromptMap.from_event(primary) + if button.text == PromptMap.UNKNOWN_INPUT_SYMBOL: + print_debug("No mapping for input event: ", primary) + # Special case: Can't rebind things bound to ESC + if primary is InputEventKey and (primary as InputEventKey).physical_keycode == KEY_ESCAPE: + button.disabled = true + + +func _input(event: InputEvent) -> void: + if not listening: + return + + if event is InputEventKey: + var key_event: InputEventKey = event + if ( + key_event.physical_keycode in [KEY_CTRL, KEY_ALT, KEY_SHIFT, KEY_META] + and key_event.pressed + ): + # Ignore modifier key until release + return + if key_event.physical_keycode == KEY_ESCAPE: + get_viewport().set_input_as_handled() + cancel_rebinding() + return + + if event is InputEventJoypadMotion: + var motion_event: InputEventJoypadMotion = event + if abs(motion_event.axis_value) < MOTION_THRESHOLD: + # Ignore axis motion unless it's over our threshold + return + + if ( + event is InputEventKey + or event is InputEventMouseButton + or event is InputEventJoypadButton + or event is InputEventJoypadMotion + ): + get_viewport().set_input_as_handled() + rebind(event) + + +func start_listening() -> void: + button.text = LISTENING_TEXT + listening = true + + +func cancel_rebinding() -> void: + _set_label_from_binding() + listening = false + + +func rebind(event: InputEvent) -> void: + # Clear previous binding + InputMap.action_erase_events(key) + # Add new binding + InputMap.action_add_event(key, event) + # Update label + _set_label_from_binding() + + listening = false + + +func apply() -> void: + var setting: Dictionary = ProjectSettings.get_setting(_get_settings_key()) + setting["events"] = InputMap.action_get_events(key) + ProjectSettings.set_setting(_get_settings_key(), setting) diff --git a/src/ui/menus/settings_menu/control_binding/control_binding.gd.uid b/src/ui/menus/settings_menu/control_binding/control_binding.gd.uid new file mode 100644 index 0000000..bd92c2d --- /dev/null +++ b/src/ui/menus/settings_menu/control_binding/control_binding.gd.uid @@ -0,0 +1 @@ +uid://y0kwpyt3w5bt diff --git a/src/ui/menus/settings_menu/control_binding/control_binding.tscn b/src/ui/menus/settings_menu/control_binding/control_binding.tscn new file mode 100644 index 0000000..1b2aee8 --- /dev/null +++ b/src/ui/menus/settings_menu/control_binding/control_binding.tscn @@ -0,0 +1,36 @@ +[gd_scene load_steps=3 format=3 uid="uid://dwvpddd7id1h"] + +[ext_resource type="Script" uid="uid://y0kwpyt3w5bt" path="res://src/ui/menus/settings_menu/control_binding/control_binding.gd" id="1_7mwhu"] +[ext_resource type="Theme" uid="uid://doq7ay6f7dgfo" path="res://src/ui/menus/menu.theme" id="1_82ajm"] + +[node name="ControlBinding" type="PanelContainer" groups=["ControlBindings"]] +theme = ExtResource("1_82ajm") +theme_type_variation = &"CheckerContainerOdd" +script = ExtResource("1_7mwhu") + +[node name="MarginContainer" type="MarginContainer" parent="."] +layout_mode = 2 +theme_override_constants/margin_left = 8 +theme_override_constants/margin_top = 8 +theme_override_constants/margin_right = 8 +theme_override_constants/margin_bottom = 8 + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer"] +layout_mode = 2 +theme_override_constants/separation = 48 +alignment = 2 + +[node name="Action" type="Label" parent="MarginContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +theme_type_variation = &"SettingsInputLabel" +text = "Action" + +[node name="Button" type="Button" parent="MarginContainer/HBoxContainer"] +unique_name_in_owner = true +custom_minimum_size = Vector2(300, 42) +layout_mode = 2 +theme_type_variation = &"InputBindButton" +text = "UI_UNSET" + +[connection signal="pressed" from="MarginContainer/HBoxContainer/Button" to="." method="start_listening"] diff --git a/src/ui/menus/settings_menu/settings/checkbox_setting/checkbox_setting.gd b/src/ui/menus/settings_menu/settings/checkbox_setting/checkbox_setting.gd new file mode 100644 index 0000000..f669165 --- /dev/null +++ b/src/ui/menus/settings_menu/settings/checkbox_setting/checkbox_setting.gd @@ -0,0 +1,13 @@ +extends Setting +## Setting with checkbox representing a boolean value. + +@onready var checkbox: CheckBox = %TextCheckbox + + +func initialize_value(value: Variant) -> void: + @warning_ignore("unsafe_cast") + checkbox.button_pressed = value as bool + + +func get_value() -> Variant: + return checkbox.button_pressed diff --git a/src/ui/menus/settings_menu/settings/checkbox_setting/checkbox_setting.gd.uid b/src/ui/menus/settings_menu/settings/checkbox_setting/checkbox_setting.gd.uid new file mode 100644 index 0000000..4182fb7 --- /dev/null +++ b/src/ui/menus/settings_menu/settings/checkbox_setting/checkbox_setting.gd.uid @@ -0,0 +1 @@ +uid://dv8n1d4g4awyc diff --git a/src/ui/menus/settings_menu/settings/checkbox_setting/checkbox_setting.tscn b/src/ui/menus/settings_menu/settings/checkbox_setting/checkbox_setting.tscn new file mode 100644 index 0000000..bbdc1da --- /dev/null +++ b/src/ui/menus/settings_menu/settings/checkbox_setting/checkbox_setting.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=4 format=3 uid="uid://bpmpj4n6xp17l"] + +[ext_resource type="PackedScene" uid="uid://dcah6r3ku60g6" path="res://src/ui/menus/settings_menu/settings/setting/setting.tscn" id="1_5t42f"] +[ext_resource type="Script" uid="uid://dv8n1d4g4awyc" path="res://src/ui/menus/settings_menu/settings/checkbox_setting/checkbox_setting.gd" id="2_mwq55"] +[ext_resource type="PackedScene" uid="uid://b7ce38k7rx466" path="res://src/ui/elements/text_checkbox/text_checkbox.tscn" id="3_fosy2"] + +[node name="CheckboxSetting" instance=ExtResource("1_5t42f")] +script = ExtResource("2_mwq55") + +[node name="TextCheckbox" parent="PanelContainer/MarginContainer" index="0" instance=ExtResource("3_fosy2")] +unique_name_in_owner = true +layout_mode = 2 diff --git a/src/ui/menus/settings_menu/settings/checkbox_setting/fullscreen_setting.gd b/src/ui/menus/settings_menu/settings/checkbox_setting/fullscreen_setting.gd new file mode 100644 index 0000000..69f1024 --- /dev/null +++ b/src/ui/menus/settings_menu/settings/checkbox_setting/fullscreen_setting.gd @@ -0,0 +1,11 @@ +extends "res://src/ui/menus/settings_menu/settings/checkbox_setting/checkbox_setting.gd" +## Special case setting for fullscreen mode + + +func initialize_value(value: Variant) -> void: + @warning_ignore("unsafe_cast") + checkbox.button_pressed = (value as Window.Mode) == Window.MODE_FULLSCREEN + + +func get_value() -> Variant: + return Window.MODE_FULLSCREEN if checkbox.button_pressed else Window.MODE_WINDOWED diff --git a/src/ui/menus/settings_menu/settings/checkbox_setting/fullscreen_setting.gd.uid b/src/ui/menus/settings_menu/settings/checkbox_setting/fullscreen_setting.gd.uid new file mode 100644 index 0000000..e70f87d --- /dev/null +++ b/src/ui/menus/settings_menu/settings/checkbox_setting/fullscreen_setting.gd.uid @@ -0,0 +1 @@ +uid://wlo8hxahp1x7 diff --git a/src/ui/menus/settings_menu/settings/dropdown_setting/dropdown_setting.gd b/src/ui/menus/settings_menu/settings/dropdown_setting/dropdown_setting.gd new file mode 100644 index 0000000..ad401ff --- /dev/null +++ b/src/ui/menus/settings_menu/settings/dropdown_setting/dropdown_setting.gd @@ -0,0 +1,13 @@ +extends Setting +## Setting with a dropdown menu representing an enum value + +@onready var dropdown: OptionButton = %Dropdown + + +func initialize_value(value: Variant) -> void: + @warning_ignore("unsafe_cast") + dropdown.selected = dropdown.get_item_index(value as int) + + +func get_value() -> Variant: + return dropdown.get_item_id(dropdown.selected) diff --git a/src/ui/menus/settings_menu/settings/dropdown_setting/dropdown_setting.gd.uid b/src/ui/menus/settings_menu/settings/dropdown_setting/dropdown_setting.gd.uid new file mode 100644 index 0000000..b08e3ef --- /dev/null +++ b/src/ui/menus/settings_menu/settings/dropdown_setting/dropdown_setting.gd.uid @@ -0,0 +1 @@ +uid://cfsf8k44t38g7 diff --git a/src/ui/menus/settings_menu/settings/dropdown_setting/dropdown_setting.tscn b/src/ui/menus/settings_menu/settings/dropdown_setting/dropdown_setting.tscn new file mode 100644 index 0000000..81700ed --- /dev/null +++ b/src/ui/menus/settings_menu/settings/dropdown_setting/dropdown_setting.tscn @@ -0,0 +1,13 @@ +[gd_scene load_steps=3 format=3 uid="uid://dpry41u0ctikn"] + +[ext_resource type="PackedScene" uid="uid://dcah6r3ku60g6" path="res://src/ui/menus/settings_menu/settings/setting/setting.tscn" id="1_km84n"] +[ext_resource type="Script" path="res://src/ui/menus/settings_menu/settings/dropdown_setting/dropdown_setting.gd" id="2_ubo13"] + +[node name="DropdownSetting" instance=ExtResource("1_km84n")] +script = ExtResource("2_ubo13") + +[node name="Dropdown" type="OptionButton" parent="PanelContainer/MarginContainer" index="0"] +unique_name_in_owner = true +layout_mode = 2 +selected = 0 +item_count = 1 diff --git a/src/ui/menus/settings_menu/settings/numeric_setting/numeric_setting.gd b/src/ui/menus/settings_menu/settings/numeric_setting/numeric_setting.gd new file mode 100644 index 0000000..e1c1360 --- /dev/null +++ b/src/ui/menus/settings_menu/settings/numeric_setting/numeric_setting.gd @@ -0,0 +1,13 @@ +extends Setting +## Setting with numeric slider representing a floating-point value + +@onready var slider: NumericSlider = %NumericSlider + + +func initialize_value(value: Variant) -> void: + @warning_ignore("unsafe_cast") + slider.value = value as float + + +func get_value() -> Variant: + return slider.value diff --git a/src/ui/menus/settings_menu/settings/numeric_setting/numeric_setting.gd.uid b/src/ui/menus/settings_menu/settings/numeric_setting/numeric_setting.gd.uid new file mode 100644 index 0000000..20b3847 --- /dev/null +++ b/src/ui/menus/settings_menu/settings/numeric_setting/numeric_setting.gd.uid @@ -0,0 +1 @@ +uid://dpxpgs6iw7b7x diff --git a/src/ui/menus/settings_menu/settings/numeric_setting/numeric_setting.tscn b/src/ui/menus/settings_menu/settings/numeric_setting/numeric_setting.tscn new file mode 100644 index 0000000..1982db6 --- /dev/null +++ b/src/ui/menus/settings_menu/settings/numeric_setting/numeric_setting.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=4 format=3 uid="uid://dut1lj8ju37sq"] + +[ext_resource type="PackedScene" uid="uid://dcah6r3ku60g6" path="res://src/ui/menus/settings_menu/settings/setting/setting.tscn" id="1_t2sut"] +[ext_resource type="Script" uid="uid://dpxpgs6iw7b7x" path="res://src/ui/menus/settings_menu/settings/numeric_setting/numeric_setting.gd" id="2_3nkup"] +[ext_resource type="PackedScene" uid="uid://dqqcyi26d3bpg" path="res://src/ui/elements/numeric_slider/numeric_slider.tscn" id="2_piwkl"] + +[node name="NumericSetting" instance=ExtResource("1_t2sut")] +script = ExtResource("2_3nkup") + +[node name="NumericSlider" parent="PanelContainer/MarginContainer" index="0" instance=ExtResource("2_piwkl")] +unique_name_in_owner = true +layout_mode = 2 diff --git a/src/ui/menus/settings_menu/settings/setting/setting.gd b/src/ui/menus/settings_menu/settings/setting/setting.gd new file mode 100644 index 0000000..ee3a900 --- /dev/null +++ b/src/ui/menus/settings_menu/settings/setting/setting.gd @@ -0,0 +1,25 @@ +class_name Setting extends HBoxContainer +## Base class for settings menu inputs + +@export var key: StringName + + +func _ready() -> void: + if not Engine.is_editor_hint(): + initialize_value(ProjectSettings.get_setting(key)) + + +## Initialize the value of this setting from project settings. +func initialize_value(_value: Variant) -> void: + pass # Implemented in derived type + + +## Apply the set value of this setting to project settings. +func apply() -> void: + ProjectSettings.set_setting(key, get_value()) + + +## Get the raw value of this setting. +func get_value() -> Variant: + # Implemented in derived type + return null diff --git a/src/ui/menus/settings_menu/settings/setting/setting.gd.uid b/src/ui/menus/settings_menu/settings/setting/setting.gd.uid new file mode 100644 index 0000000..8b1903e --- /dev/null +++ b/src/ui/menus/settings_menu/settings/setting/setting.gd.uid @@ -0,0 +1 @@ +uid://d2ajb1x2wpm05 diff --git a/src/ui/menus/settings_menu/settings/setting/setting.tscn b/src/ui/menus/settings_menu/settings/setting/setting.tscn new file mode 100644 index 0000000..302a57d --- /dev/null +++ b/src/ui/menus/settings_menu/settings/setting/setting.tscn @@ -0,0 +1,26 @@ +[gd_scene load_steps=2 format=3 uid="uid://dcah6r3ku60g6"] + +[ext_resource type="Script" uid="uid://d2ajb1x2wpm05" path="res://src/ui/menus/settings_menu/settings/setting/setting.gd" id="1_rrash"] + +[node name="Setting" type="HBoxContainer"] +script = ExtResource("1_rrash") + +[node name="Spacer" type="HSeparator" parent="."] +custom_minimum_size = Vector2(32, 0) +layout_mode = 2 + +[node name="SettingLabel" type="Label" parent="."] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Label" + +[node name="PanelContainer" type="PanelContainer" parent="."] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="MarginContainer" type="MarginContainer" parent="PanelContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 4 +theme_override_constants/margin_top = 2 +theme_override_constants/margin_right = 4 +theme_override_constants/margin_bottom = 2 diff --git a/src/ui/menus/settings_menu/settings_menu.gd b/src/ui/menus/settings_menu/settings_menu.gd new file mode 100644 index 0000000..60c8004 --- /dev/null +++ b/src/ui/menus/settings_menu/settings_menu.gd @@ -0,0 +1,77 @@ +extends MarginContainer +## Menu allowing the user to adjust game configuration. + +const SETTINGS_GROUP := "Settings" +const VOLUME_GROUP := "VolumeSliders" +const BINDINGS_GROUP := "ControlBindings" + +@export var volume_slider_scene: PackedScene +@export var control_binding_scene: PackedScene + +@onready var bus_mixer_list: HBoxContainer = %BusMixerList +@onready var control_binding_list: VBoxContainer = %ControlBindingList + + +func _ready() -> void: + populate_volume_mixers() + populate_control_bindings() + + +func _get_settings_elements() -> Array[Setting]: + var elements: Array[Setting] = [] + elements.assign(get_tree().get_nodes_in_group(SETTINGS_GROUP)) + return elements + + +func _get_volume_sliders() -> Array[VolumeSlider]: + var elements: Array[VolumeSlider] = [] + elements.assign(get_tree().get_nodes_in_group(VOLUME_GROUP)) + return elements + + +func _get_control_bindings() -> Array[ControlBinding]: + var elements: Array[ControlBinding] = [] + elements.assign(get_tree().get_nodes_in_group(BINDINGS_GROUP)) + return elements + + +func populate_control_bindings() -> void: + for action: StringName in InputMap.get_actions(): + if not action.begins_with("ui_"): + var binding: ControlBinding = control_binding_scene.instantiate() + binding.key = action + control_binding_list.add_child(binding) + + +func populate_volume_mixers() -> void: + for idx: int in range(AudioServer.bus_count): + var slider: VolumeSlider = volume_slider_scene.instantiate() + slider.bus_idx = idx + bus_mixer_list.add_child(slider) + + +## Close menu without applying settings. +func cancel() -> void: + queue_free() + + +## Apply all settings. +func apply() -> void: + for setting: Setting in _get_settings_elements(): + setting.apply() + for mixer: VolumeSlider in _get_volume_sliders(): + mixer.apply() + for binding: ControlBinding in _get_control_bindings(): + binding.apply() + + +## Write all applied settings to disk. +func save_settings() -> void: + Game.settings.write() + + +## Apply settings and close menu. +func accept() -> void: + apply() + save_settings() + queue_free() diff --git a/src/ui/menus/settings_menu/settings_menu.gd.uid b/src/ui/menus/settings_menu/settings_menu.gd.uid new file mode 100644 index 0000000..f481117 --- /dev/null +++ b/src/ui/menus/settings_menu/settings_menu.gd.uid @@ -0,0 +1 @@ +uid://dyb8cwai3cyw2 diff --git a/src/ui/menus/settings_menu/settings_menu.gd~ b/src/ui/menus/settings_menu/settings_menu.gd~ new file mode 100644 index 0000000..de245e2 --- /dev/null +++ b/src/ui/menus/settings_menu/settings_menu.gd~ @@ -0,0 +1,14 @@ +extends MarginContainer +## Menu allowing the user to adjust game configuration. + + +## Close menu without applying settings. +func cancel() -> void: + queue_free() + + +## Close menu and apply settings. +func accept() -> void: + # TODO apply settings + # TODO write to disk + queue_free() diff --git a/src/ui/menus/settings_menu/settings_menu.tscn b/src/ui/menus/settings_menu/settings_menu.tscn new file mode 100644 index 0000000..6efc560 --- /dev/null +++ b/src/ui/menus/settings_menu/settings_menu.tscn @@ -0,0 +1,336 @@ +[gd_scene load_steps=8 format=3 uid="uid://d3eaqw2rdurct"] + +[ext_resource type="Script" uid="uid://dyb8cwai3cyw2" path="res://src/ui/menus/settings_menu/settings_menu.gd" id="1_lbcn7"] +[ext_resource type="Theme" uid="uid://doq7ay6f7dgfo" path="res://src/ui/menus/menu.theme" id="1_vuur6"] +[ext_resource type="PackedScene" uid="uid://bpmpj4n6xp17l" path="res://src/ui/menus/settings_menu/settings/checkbox_setting/checkbox_setting.tscn" id="2_f274v"] +[ext_resource type="PackedScene" uid="uid://dut1lj8ju37sq" path="res://src/ui/menus/settings_menu/settings/numeric_setting/numeric_setting.tscn" id="3_jox8e"] +[ext_resource type="PackedScene" uid="uid://d2ya6ly01724l" path="res://src/ui/menus/settings_menu/volume_slider/volume_slider.tscn" id="3_vuur6"] +[ext_resource type="PackedScene" uid="uid://dwvpddd7id1h" path="res://src/ui/menus/settings_menu/control_binding/control_binding.tscn" id="4_6bmd3"] +[ext_resource type="Script" uid="uid://wlo8hxahp1x7" path="res://src/ui/menus/settings_menu/settings/checkbox_setting/fullscreen_setting.gd" id="4_ug1id"] + +[node name="SettingsMenu" type="MarginContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme = ExtResource("1_vuur6") +script = ExtResource("1_lbcn7") +volume_slider_scene = ExtResource("3_vuur6") +control_binding_scene = ExtResource("4_6bmd3") + +[node name="TabContainer" type="TabContainer" parent="."] +layout_mode = 2 +current_tab = 0 + +[node name="SETTINGS_GAME" type="MarginContainer" parent="TabContainer"] +layout_mode = 2 +theme_type_variation = &"SettingsPageContainer" +metadata/_tab_index = 0 + +[node name="VBoxContainer" type="VBoxContainer" parent="TabContainer/SETTINGS_GAME"] +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="TabContainer/SETTINGS_GAME/VBoxContainer"] +layout_mode = 2 + +[node name="Icon" type="Label" parent="TabContainer/SETTINGS_GAME/VBoxContainer/HBoxContainer"] +layout_mode = 2 +theme_type_variation = &"InputPrompt" +theme_override_font_sizes/font_size = 48 +text = "⚙" + +[node name="Label" type="Label" parent="TabContainer/SETTINGS_GAME/VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_vertical = 8 +theme_type_variation = &"HeaderXLarge" +text = "SETTINGS_GAME_HEADING" + +[node name="ScrollContainer" type="ScrollContainer" parent="TabContainer/SETTINGS_GAME/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="MarginContainer" type="MarginContainer" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_type_variation = &"SettingsListMargin" + +[node name="SettingsList" type="VBoxContainer" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer"] +layout_mode = 2 + +[node name="AccessibilityHeading" type="HBoxContainer" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList"] +layout_mode = 2 + +[node name="Label" type="Label" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/AccessibilityHeading"] +layout_mode = 2 +theme_type_variation = &"HeaderMedium" +text = "SETTINGS_GAME_ACCESSIBILITY_HEADING" + +[node name="HSeparator" type="HSeparator" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/AccessibilityHeading"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="ScreenShake" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList" groups=["Settings"] instance=ExtResource("2_f274v")] +layout_mode = 2 +key = &"game/config/accessibility/enable_screen_shake" + +[node name="SettingLabel" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/ScreenShake" index="1"] +text = "SETTINGS_SCREEN_SHAKE" + +[node name="TextCheckbox" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/ScreenShake/PanelContainer/MarginContainer" index="0"] +physics_interpolation_mode = 0 + +[node name="HeadBob" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList" groups=["Settings"] instance=ExtResource("2_f274v")] +layout_mode = 2 +key = &"game/config/accessibility/enable_head_bob" + +[node name="SettingLabel" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/HeadBob" index="1"] +text = "SETTINGS_HEAD_BOB" + +[node name="TextCheckbox" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/HeadBob/PanelContainer/MarginContainer" index="0"] +physics_interpolation_mode = 0 + +[node name="CameraHeading" type="HBoxContainer" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList"] +layout_mode = 2 + +[node name="Label" type="Label" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/CameraHeading"] +layout_mode = 2 +theme_type_variation = &"HeaderMedium" +text = "SETTINGS_GAME_CAMERA_HEADING" + +[node name="HSeparator" type="HSeparator" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/CameraHeading"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="SensitivityX" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList" groups=["Settings"] instance=ExtResource("3_jox8e")] +layout_mode = 2 +key = &"game/config/input/mouse_sensitivity_x" + +[node name="SettingLabel" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/SensitivityX" index="1"] +text = "SETTINGS_SENSITIVITY_X" + +[node name="NumericSlider" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/SensitivityX/PanelContainer/MarginContainer" index="0"] +physics_interpolation_mode = 0 +max_value = 1.0 +step = 0.01 + +[node name="SensitivityY" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList" groups=["Settings"] instance=ExtResource("3_jox8e")] +layout_mode = 2 +key = &"game/config/input/mouse_sensitivity_y" + +[node name="SettingLabel" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/SensitivityY" index="1"] +text = "SETTINGS_SENSITIVITY_Y" + +[node name="NumericSlider" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/SensitivityY/PanelContainer/MarginContainer" index="0"] +physics_interpolation_mode = 0 +max_value = 1.0 +step = 0.01 + +[node name="MouseAcceleration" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList" groups=["Settings"] instance=ExtResource("3_jox8e")] +layout_mode = 2 +key = &"game/config/input/mouse_acceleration" + +[node name="SettingLabel" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/MouseAcceleration" index="1"] +text = "SETTINGS_MOUSE_ACCELERATION" + +[node name="NumericSlider" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/MouseAcceleration/PanelContainer/MarginContainer" index="0"] +physics_interpolation_mode = 0 + +[node name="InvertPitch" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList" groups=["Settings"] instance=ExtResource("2_f274v")] +layout_mode = 2 +key = &"game/config/input/invert_pitch" + +[node name="SettingLabel" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/InvertPitch" index="1"] +text = "SETTINGS_INVERT_PITCH" + +[node name="TextCheckbox" parent="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/InvertPitch/PanelContainer/MarginContainer" index="0"] +physics_interpolation_mode = 0 + +[node name="SETTINGS_GRAPHICS" type="MarginContainer" parent="TabContainer"] +visible = false +layout_mode = 2 +theme_type_variation = &"SettingsPageContainer" +metadata/_tab_index = 1 + +[node name="VBoxContainer" type="VBoxContainer" parent="TabContainer/SETTINGS_GRAPHICS"] +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer"] +layout_mode = 2 + +[node name="Icon" type="Label" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 4 +theme_type_variation = &"InputPrompt" +theme_override_font_sizes/font_size = 48 +text = "🖵" + +[node name="Label" type="Label" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 4 +size_flags_vertical = 0 +theme_type_variation = &"HeaderXLarge" +text = "SETTINGS_GRAPHICS_HEADING" + +[node name="ScrollContainer" type="ScrollContainer" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="MarginContainer" type="MarginContainer" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_type_variation = &"SettingsListMargin" + +[node name="SettingsList" type="VBoxContainer" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/ScrollContainer/MarginContainer"] +layout_mode = 2 + +[node name="DisplayHeading" type="HBoxContainer" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/ScrollContainer/MarginContainer/SettingsList"] +layout_mode = 2 + +[node name="Label" type="Label" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/DisplayHeading"] +layout_mode = 2 +theme_type_variation = &"HeaderMedium" +text = "SETTINGS_GRAPHICS_DISPLAY_HEADING" + +[node name="HSeparator" type="HSeparator" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/DisplayHeading"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Fullscreen" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/ScrollContainer/MarginContainer/SettingsList" groups=["Settings"] instance=ExtResource("2_f274v")] +layout_mode = 2 +script = ExtResource("4_ug1id") +key = &"display/window/size/mode" + +[node name="SettingLabel" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/Fullscreen" index="1"] +text = "SETTINGS_GRAPHICS_FULLSCREEN" + +[node name="TextCheckbox" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/Fullscreen/PanelContainer/MarginContainer" index="0"] +physics_interpolation_mode = 0 + +[node name="VSync" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/ScrollContainer/MarginContainer/SettingsList" groups=["Settings"] instance=ExtResource("2_f274v")] +layout_mode = 2 +key = &"display/window/vsync/vsync_mode" + +[node name="SettingLabel" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/VSync" index="1"] +text = "SETTINGS_GRAPHICS_VSYNC" + +[node name="TextCheckbox" parent="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/VSync/PanelContainer/MarginContainer" index="0"] +physics_interpolation_mode = 0 + +[node name="SETTINGS_AUDIO" type="MarginContainer" parent="TabContainer"] +visible = false +layout_mode = 2 +theme_type_variation = &"SettingsPageContainer" +metadata/_tab_index = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="TabContainer/SETTINGS_AUDIO"] +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="TabContainer/SETTINGS_AUDIO/VBoxContainer"] +layout_mode = 2 + +[node name="Icon" type="Label" parent="TabContainer/SETTINGS_AUDIO/VBoxContainer/HBoxContainer"] +layout_mode = 2 +theme_type_variation = &"InputPrompt" +theme_override_font_sizes/font_size = 48 +text = "🕬" + +[node name="Label" type="Label" parent="TabContainer/SETTINGS_AUDIO/VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_vertical = 8 +theme_type_variation = &"HeaderXLarge" +text = "SETTINGS_AUDIO_HEADING" + +[node name="ScrollContainer" type="ScrollContainer" parent="TabContainer/SETTINGS_AUDIO/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="MarginContainer" type="MarginContainer" parent="TabContainer/SETTINGS_AUDIO/VBoxContainer/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_type_variation = &"SettingsListMargin" + +[node name="BusMixerList" type="HBoxContainer" parent="TabContainer/SETTINGS_AUDIO/VBoxContainer/ScrollContainer/MarginContainer"] +unique_name_in_owner = true +layout_mode = 2 +theme_override_constants/separation = 16 + +[node name="SETTINGS_CONTROLS" type="MarginContainer" parent="TabContainer"] +visible = false +layout_mode = 2 +theme_type_variation = &"SettingsPageContainer" +metadata/_tab_index = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="TabContainer/SETTINGS_CONTROLS"] +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="TabContainer/SETTINGS_CONTROLS/VBoxContainer"] +layout_mode = 2 + +[node name="Icon" type="Label" parent="TabContainer/SETTINGS_CONTROLS/VBoxContainer/HBoxContainer"] +layout_mode = 2 +theme_type_variation = &"InputPrompt" +theme_override_font_sizes/font_size = 48 +text = "␼" + +[node name="Label" type="Label" parent="TabContainer/SETTINGS_CONTROLS/VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_vertical = 8 +theme_type_variation = &"HeaderXLarge" +text = "SETTINGS_CONTROLS_HEADING" + +[node name="ScrollContainer" type="ScrollContainer" parent="TabContainer/SETTINGS_CONTROLS/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="MarginContainer" type="MarginContainer" parent="TabContainer/SETTINGS_CONTROLS/VBoxContainer/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_type_variation = &"SettingsListMargin" + +[node name="ControlBindingList" type="VBoxContainer" parent="TabContainer/SETTINGS_CONTROLS/VBoxContainer/ScrollContainer/MarginContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="SouthEast" type="MarginContainer" parent="."] +layout_mode = 2 +size_flags_horizontal = 8 +size_flags_vertical = 8 +theme_override_constants/margin_left = 16 +theme_override_constants/margin_top = 16 +theme_override_constants/margin_right = 16 +theme_override_constants/margin_bottom = 16 + +[node name="HBoxContainer" type="HBoxContainer" parent="SouthEast"] +layout_mode = 2 +theme_override_constants/separation = 16 + +[node name="CancelButton" type="Button" parent="SouthEast/HBoxContainer"] +layout_mode = 2 +theme_type_variation = &"CancelButton" +text = "UI_CANCEL" + +[node name="AcceptButton" type="Button" parent="SouthEast/HBoxContainer"] +layout_mode = 2 +theme_type_variation = &"AlertButton" +text = "UI_ACCEPT" + +[connection signal="pressed" from="SouthEast/HBoxContainer/CancelButton" to="." method="cancel"] +[connection signal="pressed" from="SouthEast/HBoxContainer/AcceptButton" to="." method="accept"] + +[editable path="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/ScreenShake"] +[editable path="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/HeadBob"] +[editable path="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/SensitivityX"] +[editable path="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/SensitivityY"] +[editable path="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/MouseAcceleration"] +[editable path="TabContainer/SETTINGS_GAME/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/InvertPitch"] +[editable path="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/Fullscreen"] +[editable path="TabContainer/SETTINGS_GRAPHICS/VBoxContainer/ScrollContainer/MarginContainer/SettingsList/VSync"] diff --git a/src/ui/menus/settings_menu/volume_slider/volume_slider.gd b/src/ui/menus/settings_menu/volume_slider/volume_slider.gd new file mode 100644 index 0000000..e5e24f3 --- /dev/null +++ b/src/ui/menus/settings_menu/volume_slider/volume_slider.gd @@ -0,0 +1,63 @@ +class_name VolumeSlider extends VBoxContainer +## Input element for adjusting the volume of a single bus. + +const VOLUME_FACTOR := 50.0 + +@export var bus_idx: int + +var value: float: + get: + return slider.value + set(v): + slider.set_value_no_signal(v) + spin_box.set_value_no_signal(v) + _set_bus_volume(v) + +var _applied_value: float + +@onready var bus_name: Label = %BusName +@onready var slider: VSlider = %Slider +@onready var spin_box: SpinBox = %SpinBox +@onready var audio_demo: AudioStreamPlayer = %AudioDemo + + +func _ready() -> void: + bus_name.text = AudioServer.get_bus_name(bus_idx) + audio_demo.bus = bus_name.text + _applied_value = _db_to_volume(AudioServer.get_bus_volume_db(bus_idx)) + value = _applied_value + + +func _exit_tree() -> void: + # Revert volume to the value from the last time `apply` was called + _set_bus_volume(_applied_value) + + +func _db_to_volume(db: float) -> float: + return db_to_linear(db) * VOLUME_FACTOR + + +func _volume_to_db(volume: float) -> float: + return linear_to_db(volume / VOLUME_FACTOR) + + +func _on_component_value_changed(new_value: float) -> void: + value = new_value + + +func _set_bus_volume(new_value: float) -> void: + AudioServer.set_bus_volume_db(bus_idx, _volume_to_db(new_value)) + + +func apply() -> void: + # Prevent value from being reverted during destructor. + _applied_value = value + + +func _on_slider_drag_ended(value_changed: bool) -> void: + if value_changed: + audio_demo.play() + + +func _on_spin_box_value_changed(_new_value: float) -> void: + audio_demo.play() diff --git a/src/ui/menus/settings_menu/volume_slider/volume_slider.gd.uid b/src/ui/menus/settings_menu/volume_slider/volume_slider.gd.uid new file mode 100644 index 0000000..9d00896 --- /dev/null +++ b/src/ui/menus/settings_menu/volume_slider/volume_slider.gd.uid @@ -0,0 +1 @@ +uid://bt3oajqvevj4k diff --git a/src/ui/menus/settings_menu/volume_slider/volume_slider.tscn b/src/ui/menus/settings_menu/volume_slider/volume_slider.tscn new file mode 100644 index 0000000..9ed19e8 --- /dev/null +++ b/src/ui/menus/settings_menu/volume_slider/volume_slider.tscn @@ -0,0 +1,53 @@ +[gd_scene load_steps=3 format=3 uid="uid://d2ya6ly01724l"] + +[ext_resource type="Theme" uid="uid://doq7ay6f7dgfo" path="res://src/ui/menus/menu.theme" id="1_k7hk0"] +[ext_resource type="Script" uid="uid://bt3oajqvevj4k" path="res://src/ui/menus/settings_menu/volume_slider/volume_slider.gd" id="1_rfe0k"] + +[node name="VolumeSlider" type="VBoxContainer" groups=["VolumeSliders"]] +custom_minimum_size = Vector2(300, 0) +theme = ExtResource("1_k7hk0") +script = ExtResource("1_rfe0k") + +[node name="NameContainer" type="HBoxContainer" parent="."] +layout_mode = 2 +alignment = 1 + +[node name="Icon" type="Label" parent="NameContainer"] +layout_mode = 2 +theme_type_variation = &"InputPrompt" +theme_override_constants/outline_size = 5 +theme_override_font_sizes/font_size = 24 +text = "🎧" + +[node name="BusName" type="Label" parent="NameContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 8 +theme_type_variation = &"HeaderMedium" +text = "Channel" + +[node name="Slider" type="VSlider" parent="."] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 1 +size_flags_vertical = 3 +step = 0.1 + +[node name="DigitalDisplay" type="HBoxContainer" parent="."] +layout_mode = 2 +size_flags_horizontal = 4 + +[node name="SpinBox" type="SpinBox" parent="DigitalDisplay"] +unique_name_in_owner = true +layout_mode = 2 +step = 0.1 +alignment = 2 +suffix = "%" + +[node name="AudioDemo" type="AudioStreamPlayer" parent="."] +unique_name_in_owner = true + +[connection signal="drag_ended" from="Slider" to="." method="_on_slider_drag_ended"] +[connection signal="value_changed" from="Slider" to="." method="_on_component_value_changed"] +[connection signal="value_changed" from="DigitalDisplay/SpinBox" to="." method="_on_component_value_changed"] +[connection signal="value_changed" from="DigitalDisplay/SpinBox" to="." method="_on_spin_box_value_changed"] diff --git a/src/util/prompt_map.gd.uid b/src/util/prompt_map.gd.uid new file mode 100644 index 0000000..70e33da --- /dev/null +++ b/src/util/prompt_map.gd.uid @@ -0,0 +1 @@ +uid://bjcah3n6wug68 diff --git a/src/world/world.gd b/src/world/world.gd index 3c88e03..fe6ac7b 100644 --- a/src/world/world.gd +++ b/src/world/world.gd @@ -25,7 +25,7 @@ func pause() -> void: get_tree().paused = true var pause_menu: Control = pause_scene.instantiate() ui_root.add_child(pause_menu) - pause_menu.tree_exited.connect(unpause) + pause_menu.tree_exiting.connect(unpause) func unpause() -> void: