From 93c6b207c11fdaba484410eb53fc11b35fbbb3bd Mon Sep 17 00:00:00 2001 From: Hop311 Date: Sun, 26 Feb 2023 16:12:07 +0000 Subject: Add Setting Sanatization (#21) --- game/src/Autoload/Events/Options.gd | 7 +- game/src/Autoload/Resolution.gd | 101 +++++++++++++-------- game/src/LocaleButton.gd | 44 ++++++--- game/src/OptionMenu/MonitorDisplaySelector.gd | 18 ++-- game/src/OptionMenu/OptionsMenu.gd | 16 ++-- game/src/OptionMenu/ResolutionSelector.gd | 69 +++++++------- game/src/OptionMenu/ScreenModeSelector.gd | 12 ++- game/src/OptionMenu/SettingNodes/SettingHSlider.gd | 13 ++- .../OptionMenu/SettingNodes/SettingOptionButton.gd | 40 +++++--- game/src/OptionMenu/VideoTab.tscn | 1 - game/src/OptionMenu/VolumeGrid.gd | 20 ++-- 11 files changed, 215 insertions(+), 126 deletions(-) diff --git a/game/src/Autoload/Events/Options.gd b/game/src/Autoload/Events/Options.gd index 0acaa63..8af620d 100644 --- a/game/src/Autoload/Events/Options.gd +++ b/game/src/Autoload/Events/Options.gd @@ -7,14 +7,17 @@ signal reset_settings() func load_settings_from_file() -> void: load_settings.emit(_settings_file) -func save_settings_from_file() -> void: +func save_settings_to_file() -> void: save_settings.emit(_settings_file) _settings_file.save(_settings_file_path) func try_reset_settings() -> void: reset_settings.emit() -var _settings_file_path := ProjectSettings.get_setting("openvic2/settings/settings_file_path", "user://settings.cfg") as String +const settings_file_path_setting : String = "openvic2/settings/settings_file_path" +const settings_file_path_default : String = "user://settings.cfg" + +var _settings_file_path : String = ProjectSettings.get_setting(settings_file_path_setting, settings_file_path_default) var _settings_file := ConfigFile.new() func _init(): diff --git a/game/src/Autoload/Resolution.gd b/game/src/Autoload/Resolution.gd index ac54c0d..e1e788b 100644 --- a/game/src/Autoload/Resolution.gd +++ b/game/src/Autoload/Resolution.gd @@ -1,36 +1,72 @@ extends Node -var _resolutions : Array[Dictionary]= [ - { "name": &"", "value": Vector2i(3840,2160) }, - { "name": &"", "value": Vector2i(2560,1080) }, - { "name": &"", "value": Vector2i(1920,1080) }, - { "name": &"", "value": Vector2i(1366,768) }, - { "name": &"", "value": Vector2i(1536,864) }, - { "name": &"", "value": Vector2i(1280,720) }, - { "name": &"", "value": Vector2i(1440,900) }, - { "name": &"", "value": Vector2i(1600,900) }, - { "name": &"", "value": Vector2i(1024,600) }, - { "name": &"", "value": Vector2i(800,600) } -] +const error_resolution : Vector2i = Vector2i(-1,-1) + +@export +var minimum_resolution : Vector2i = Vector2i(1,1) + +const _starting_resolutions : Dictionary = { + Vector2i(3840,2160): &"4K", + Vector2i(2560,1080): &"UW1080p", + Vector2i(1920,1080): &"1080p", + Vector2i(1366,768): &"", + Vector2i(1536,864): &"", + Vector2i(1280,720): &"720p", + Vector2i(1440,900): &"", + Vector2i(1600,900): &"", + Vector2i(1024,600): &"", + Vector2i(800,600): &"" +} + +var _resolutions : Dictionary + +const _regex_pattern : String = "(\\d+)\\s*[xX,]\\s*(\\d+)" +var _regex : RegEx func _ready(): - for resolution in _resolutions: - resolution["tag"] = _get_name_of_resolution(resolution["name"], resolution["value"]) + assert(minimum_resolution.x > 0 and minimum_resolution.y > 0, "Minimum resolution must be positive!") + for resolution_value in _starting_resolutions: + add_resolution(resolution_value, _starting_resolutions[resolution_value]) + assert(not _resolutions.is_empty(), "No valid starting resolutions!") + + _regex = RegEx.new() + var err := _regex.compile(_regex_pattern) + assert(err == OK, "Resolution RegEx failed to compile!") -func has_resolution(resolution_name : StringName) -> bool: - return resolution_name in _resolutions -func get_resolution(resolution_name : StringName, default : Vector2i = Vector2i(1920, 1080)) -> Vector2i: - var resolution := _get_resolution_by_name(resolution_name) - if resolution.x < 0 and resolution.y < 0: - return default - return resolution +func has_resolution(resolution_value : Vector2i) -> bool: + return resolution_value in _resolutions -func get_resolution_name_list() -> Array[StringName]: - var result : Array[StringName] = [] - for resolution in _resolutions: - result.append(resolution["tag"]) - return result +func add_resolution(resolution_value : Vector2i, resolution_name : StringName = &"") -> bool: + if has_resolution(resolution_value): return true + var res_dict := { value = resolution_value } + var display_name := "%sx%s" % [resolution_value.x, resolution_value.y] + if not resolution_name.is_empty(): + res_dict.name = resolution_name + display_name = "%s (%s)" % [display_name, resolution_name] + res_dict.display_name = StringName(display_name) + if resolution_value.x < minimum_resolution.x or resolution_value.y < minimum_resolution.y: + push_error("Resolution %s is smaller than minimum (%sx%s)" % [res_dict.display_name, minimum_resolution.x, minimum_resolution.y]) + return false + _resolutions[resolution_value] = res_dict + return true + +func get_resolution_value_list() -> Array: + var list := _resolutions.keys() + list.sort_custom(func(a, b): return a > b) + return list + +func get_resolution_display_name(resolution_value : Vector2i) -> StringName: + return _resolutions.get(resolution_value, { display_name = &"unknown resolution" }).display_name + +func get_resolution_value_from_string(resolution_string : String) -> Vector2i: + if not resolution_string.is_empty(): + for resolution in _resolutions.values(): + if resolution_string == resolution.get(name) or resolution_string == resolution.display_name: + return resolution.value + var result := _regex.search(resolution_string) + if result: return Vector2i(result.get_string(1).to_int(), result.get_string(2).to_int()) + return error_resolution func get_current_resolution() -> Vector2i: var window := get_viewport().get_window() @@ -41,6 +77,8 @@ func get_current_resolution() -> Vector2i: return window.size func set_resolution(resolution : Vector2i) -> void: + if not has_resolution(resolution): + push_warning("Setting resolution to non-standard value %sx%s" % [resolution.x, resolution.y]) var window := get_viewport().get_window() match window.mode: Window.MODE_EXCLUSIVE_FULLSCREEN, Window.MODE_FULLSCREEN: @@ -51,14 +89,3 @@ func set_resolution(resolution : Vector2i) -> void: func reset_resolution() -> void: set_resolution(get_current_resolution()) - -func _get_name_of_resolution(resolution_name : StringName, resolution_value : Vector2i) -> StringName: - if resolution_name != null and not resolution_name.is_empty(): - return "%s (%sx%s)" % [resolution_name, resolution_value.x, resolution_value.y] - return "%sx%s" % [resolution_value.x, resolution_value.y] - -func _get_resolution_by_name(resolution_name : StringName) -> Vector2i: - for resolution in _resolutions: - if resolution["name"] == resolution_name or resolution["tag"] == resolution_name: - return resolution["value"] - return Vector2i(-1, -1) diff --git a/game/src/LocaleButton.gd b/game/src/LocaleButton.gd index 32807d0..9f499b8 100644 --- a/game/src/LocaleButton.gd +++ b/game/src/LocaleButton.gd @@ -1,5 +1,8 @@ extends OptionButton +const section_name : String = "Localization" +const setting_name : String = "Locale" + var _locales_country_rename : Dictionary var _locales_list : Array[String] @@ -21,15 +24,32 @@ func _ready(): Events.Options.load_settings.connect(load_setting) Events.Options.save_settings.connect(save_setting) - -func load_setting(file : ConfigFile): - var locale_index := _locales_list.find(file.get_value("Localization", "Locale", "") as String) - if locale_index != -1: - selected = locale_index - -func save_setting(file : ConfigFile): - file.set_value("Localization", "Locale", _locales_list[selected]) - -func _on_item_selected(index): - TranslationServer.set_locale(_locales_list[index]) - Events.Options.save_settings_from_file.call_deferred() +func _valid_index(index : int) -> bool: + return 0 <= index and index < _locales_list.size() + +func load_setting(file : ConfigFile) -> void: + if file == null: return + var load_value = file.get_value(section_name, setting_name, TranslationServer.get_locale()) + match typeof(load_value): + TYPE_STRING, TYPE_STRING_NAME: + var locale_index := _locales_list.find(load_value as String) + if locale_index != -1: + selected = locale_index + return + push_error("Setting value '%s' invalid for setting [%s] %s" % [load_value, section_name, setting_name]) + reset_setting() + +func save_setting(file : ConfigFile) -> void: + if file == null: return + file.set_value(section_name, setting_name, _locales_list[selected]) + +func reset_setting() -> void: + selected = _locales_list.find(TranslationServer.get_locale()) + +func _on_item_selected(index : int) -> void: + if _valid_index(index): + TranslationServer.set_locale(_locales_list[index]) + Events.Options.save_settings_to_file.call_deferred() + else: + push_error("Invalid LocaleButton index: %d" % index) + reset_setting() diff --git a/game/src/OptionMenu/MonitorDisplaySelector.gd b/game/src/OptionMenu/MonitorDisplaySelector.gd index f2f0dc8..b665658 100644 --- a/game/src/OptionMenu/MonitorDisplaySelector.gd +++ b/game/src/OptionMenu/MonitorDisplaySelector.gd @@ -2,13 +2,17 @@ extends SettingOptionButton func _setup_button(): clear() - for screen_index in range(DisplayServer.get_screen_count()): + for screen_index in DisplayServer.get_screen_count(): add_item("Monitor %d" % (screen_index + 1)) default_selected = get_viewport().get_window().current_screen -func _on_item_selected(index): - var window := get_viewport().get_window() - var mode := window.mode - window.mode = Window.MODE_WINDOWED - get_viewport().get_window().set_current_screen(index) - window.mode = mode +func _on_item_selected(index : int): + if _valid_index(index): + var window := get_viewport().get_window() + var mode := window.mode + window.mode = Window.MODE_WINDOWED + get_viewport().get_window().set_current_screen(index) + window.mode = mode + else: + push_error("Invalid MonitorDisplaySelector index: %d" % index) + reset_setting() diff --git a/game/src/OptionMenu/OptionsMenu.gd b/game/src/OptionMenu/OptionsMenu.gd index 5aba7f2..c25c3b8 100644 --- a/game/src/OptionMenu/OptionsMenu.gd +++ b/game/src/OptionMenu/OptionsMenu.gd @@ -38,21 +38,23 @@ func toggle_locale_button_visibility(locale_visible : bool): $LocaleVBox/LocaleHBox/LocaleButton.visible = locale_visible func _on_back_button_pressed(): - Events.Options.save_settings_from_file() + Events.Options.save_settings_to_file() back_button_pressed.emit() func _on_window_close_requested() -> void: if visible: - Events.Options.save_settings_from_file() + Events.Options.save_settings_to_file() func _save_overrides() -> void: - var override_path := ProjectSettings.get_setting("application/config/project_settings_override") as String - if override_path == null or override_path.is_empty(): - override_path = ProjectSettings.get_setting("openvic2/settings/settings_file_path") as String + var override_path : String = ProjectSettings.get_setting("application/config/project_settings_override", "") + if override_path.is_empty(): + override_path = ProjectSettings.get_setting(Events.Options.settings_file_path_setting, Events.Options.settings_file_path_default) var file := ConfigFile.new() - file.load(override_path) + var err_ret := file.load(override_path) + if err_ret != OK: push_error("Failed to load overrides from %s" % override_path) file.set_value("display", "window/size/mode", get_viewport().get_window().mode) var resolution : Vector2i = Resolution.get_current_resolution() file.set_value("display", "window/size/viewport_width", resolution.x) file.set_value("display", "window/size/viewport_height", resolution.y) - file.save(override_path) + err_ret = file.save(override_path) + if err_ret != OK: push_error("Failed to save overrides to %s" % override_path) diff --git a/game/src/OptionMenu/ResolutionSelector.gd b/game/src/OptionMenu/ResolutionSelector.gd index e602bab..6cdaca8 100644 --- a/game/src/OptionMenu/ResolutionSelector.gd +++ b/game/src/OptionMenu/ResolutionSelector.gd @@ -1,58 +1,65 @@ extends SettingOptionButton @export -var default_value : Vector2i = Vector2i(-1, -1) +var default_value : Vector2i = Resolution.error_resolution -func add_resolution(value : Vector2i, selection_name : String = "") -> void: - if selection_name.is_empty(): - selection_name = "%sx%s" % [value.x, value.y] - add_item(selection_name) - set_item_metadata(item_count - 1, value) - -func find_resolution_value(value : Vector2i) -> int: - for item_index in range(item_count): +func _find_resolution_index_by_value(value : Vector2i) -> int: + for item_index in item_count: if get_item_metadata(item_index) == value: return item_index return -1 -func _setup_button(): - if default_value.x < 0: - default_value.x = ProjectSettings.get_setting("display/window/size/viewport_width") - - if default_value.y < 0: - default_value.y = ProjectSettings.get_setting("display/window/size/viewport_height") - +func _sync_resolutions(to_select : Vector2i = Resolution.get_current_resolution()) -> void: clear() default_selected = -1 selected = -1 - for resolution in Resolution.get_resolution_name_list(): - var resolution_value := Resolution.get_resolution(resolution) - add_resolution(resolution_value, resolution) + for resolution_value in Resolution.get_resolution_value_list(): + add_item(Resolution.get_resolution_display_name(resolution_value)) + set_item_metadata(item_count - 1, resolution_value) if resolution_value == default_value: default_selected = item_count - 1 - if resolution_value == Resolution.get_current_resolution(): + if resolution_value == to_select: selected = item_count - 1 if default_selected == -1: - add_resolution(default_value) default_selected = item_count - 1 if selected == -1: selected = default_selected +func _setup_button(): + if default_value.x <= 0: + default_value.x = ProjectSettings.get_setting("display/window/size/viewport_width") + if default_value.y <= 0: + default_value.y = ProjectSettings.get_setting("display/window/size/viewport_height") + Resolution.add_resolution(default_value, &"default") + _sync_resolutions() + func _get_value_for_file(select_value : int): - return get_item_metadata(select_value) + if _valid_index(select_value): + return get_item_metadata(select_value) + else: + return null func _set_value_from_file(load_value): - var resolution_value := load_value as Vector2i - selected = find_resolution_value(resolution_value) - if selected == -1: - if add_nonstandard_value: - add_resolution(resolution_value) - selected = item_count - 1 - else: push_error("Setting value '%s' invalid for setting [%s] %s" % [load_value, section_name, setting_name]) + var target_resolution := Resolution.error_resolution + match typeof(load_value): + TYPE_VECTOR2I: target_resolution = load_value + TYPE_STRING, TYPE_STRING_NAME: target_resolution = Resolution.get_resolution_value_from_string(load_value) + if target_resolution != Resolution.error_resolution: + selected = _find_resolution_index_by_value(target_resolution) + if selected != -1: return + if Resolution.add_resolution(target_resolution): + _sync_resolutions(target_resolution) + return + push_error("Setting value '%s' invalid for setting [%s] %s" % [load_value, section_name, setting_name]) + selected = default_selected -func _on_item_selected(index): - Resolution.set_resolution(get_item_metadata(index)) +func _on_item_selected(index : int): + if _valid_index(index): + Resolution.set_resolution(get_item_metadata(index)) + else: + push_error("Invalid ResolutionSelector index: %d" % index) + reset_setting() diff --git a/game/src/OptionMenu/ScreenModeSelector.gd b/game/src/OptionMenu/ScreenModeSelector.gd index 92c5d60..b4fc5ab 100644 --- a/game/src/OptionMenu/ScreenModeSelector.gd +++ b/game/src/OptionMenu/ScreenModeSelector.gd @@ -29,7 +29,11 @@ func _setup_button(): selected = default_selected func _on_item_selected(index : int): - var window := get_viewport().get_window() - var current_resolution := Resolution.get_current_resolution() - window.mode = get_window_mode_from_screen_mode(index) - Resolution.set_resolution(current_resolution) + if _valid_index(index): + var window := get_viewport().get_window() + var current_resolution := Resolution.get_current_resolution() + window.mode = get_window_mode_from_screen_mode(index) + Resolution.set_resolution(current_resolution) + else: + push_error("Invalid ScreenModeSelector index: %d" % index) + reset_setting() diff --git a/game/src/OptionMenu/SettingNodes/SettingHSlider.gd b/game/src/OptionMenu/SettingNodes/SettingHSlider.gd index cf2adf4..6a0e5ed 100644 --- a/game/src/OptionMenu/SettingNodes/SettingHSlider.gd +++ b/game/src/OptionMenu/SettingNodes/SettingHSlider.gd @@ -17,7 +17,18 @@ func _ready(): func load_setting(file : ConfigFile): if file == null: return - value = file.get_value(section_name, setting_name, default_value) + var load_value = file.get_value(section_name, setting_name, default_value) + match typeof(load_value): + TYPE_FLOAT, TYPE_INT: + value = load_value + return + TYPE_STRING, TYPE_STRING_NAME: + var load_string := load_value as String + if load_string.is_valid_float(): + value = load_string.to_float() + return + push_error("Setting value '%s' invalid for setting [%s] \"%s\"" % [load_value, section_name, setting_name]) + value = default_value func save_setting(file : ConfigFile): if file == null: return diff --git a/game/src/OptionMenu/SettingNodes/SettingOptionButton.gd b/game/src/OptionMenu/SettingNodes/SettingOptionButton.gd index 3a5c979..c7b760d 100644 --- a/game/src/OptionMenu/SettingNodes/SettingOptionButton.gd +++ b/game/src/OptionMenu/SettingNodes/SettingOptionButton.gd @@ -11,30 +11,39 @@ var setting_name : String = "SettingOptionMenu" var default_selected : int = -1: get: return default_selected set(v): - if v == -1: + if v < 0 or item_count == 0: default_selected = -1 return default_selected = v % item_count -@export -var add_nonstandard_value := false +func _valid_index(index : int) -> bool: + return 0 <= index and index < item_count func _get_value_for_file(select_value : int): - if select_value > -1: - return get_item_text(select_value) + if _valid_index(select_value): + return select_value else: return null func _set_value_from_file(load_value) -> void: - selected = -1 - for item_index in range(item_count): - if load_value == get_item_text(item_index): - selected = item_index - if selected == -1: - if add_nonstandard_value: - add_item(load_value) - selected = item_count - 1 - else: push_error("Setting value '%s' invalid for setting [%s] \"%s\"" % [load_value, section_name, setting_name]) + match typeof(load_value): + TYPE_INT: + if _valid_index(load_value): + selected = load_value + return + TYPE_STRING, TYPE_STRING_NAME: + var load_string := load_value as String + if load_string.is_valid_int(): + var load_int := load_string.to_int() + if _valid_index(load_int): + selected = load_int + return + for item_index in item_count: + if load_string == get_item_text(item_index): + selected = item_index + return + push_error("Setting value '%s' invalid for setting [%s] \"%s\"" % [load_value, section_name, setting_name]) + selected = default_selected func _setup_button() -> void: pass @@ -44,6 +53,9 @@ func _ready(): Events.Options.save_settings.connect(save_setting) Events.Options.reset_settings.connect(reset_setting) _setup_button() + if not _valid_index(default_selected) or selected == -1: + OS.alert("Failed to generate %s %s options." % [setting_name, section_name], "%s Options Error" % section_name) + get_tree().quit() func load_setting(file : ConfigFile) -> void: if file == null: return diff --git a/game/src/OptionMenu/VideoTab.tscn b/game/src/OptionMenu/VideoTab.tscn index d46f056..f14f757 100644 --- a/game/src/OptionMenu/VideoTab.tscn +++ b/game/src/OptionMenu/VideoTab.tscn @@ -34,7 +34,6 @@ popup/item_0/id = 0 script = ExtResource("1_i8nro") section_name = "Video" setting_name = "Resolution" -add_nonstandard_value = true [node name="ScreenModeLabel" type="Label" parent="VBoxContainer/GridContainer"] layout_mode = 2 diff --git a/game/src/OptionMenu/VolumeGrid.gd b/game/src/OptionMenu/VolumeGrid.gd index fae5ff6..8d5a016 100644 --- a/game/src/OptionMenu/VolumeGrid.gd +++ b/game/src/OptionMenu/VolumeGrid.gd @@ -1,8 +1,8 @@ extends GridContainer -const RATIO_FOR_LINEAR = 100 +const RATIO_FOR_LINEAR : float = 100 -var _slider_dictionary := {} +var _slider_dictionary : Dictionary func get_db_as_volume_value(db : float) -> float: # db_to_linear produces a float between 0 and 1 from a db value @@ -12,7 +12,7 @@ func get_volume_value_as_db(value : float) -> float: # linear_to_db consumes a float between 0 and 1 to produce the db value return linear_to_db(value / RATIO_FOR_LINEAR) -func add_volume_column(bus_name : StringName, bus_index : int) -> HSlider: +func add_volume_row(bus_name : StringName, bus_index : int) -> HSlider: var volume_label := Label.new() volume_label.text = bus_name + " Volume" add_child(volume_label) @@ -33,22 +33,22 @@ func add_volume_column(bus_name : StringName, bus_index : int) -> HSlider: func _ready(): for bus_index in AudioServer.bus_count: - add_volume_column(AudioServer.get_bus_name(bus_index), bus_index) + add_volume_row(AudioServer.get_bus_name(bus_index), bus_index) func _on_slider_value_changed(value : float, bus_index : int) -> void: AudioServer.set_bus_volume_db(bus_index, get_volume_value_as_db(value)) func _on_options_menu_load_settings(load_file : ConfigFile): - for volume_label_text in _slider_dictionary: - _slider_dictionary[volume_label_text].load_setting(load_file) + for volume_slider in _slider_dictionary.values(): + volume_slider.load_setting(load_file) func _on_options_menu_save_settings(save_file : ConfigFile): - for volume_label_text in _slider_dictionary: - _slider_dictionary[volume_label_text].save_setting(save_file) + for volume_slider in _slider_dictionary.values(): + volume_slider.save_setting(save_file) func _on_options_menu_reset_settings(): - for volume_label_text in _slider_dictionary: - _slider_dictionary[volume_label_text].reset_setting() + for volume_slider in _slider_dictionary.values(): + volume_slider.reset_setting() -- cgit v1.2.3-56-ga3b1