# Hurry Curry! - a game about cooking # Copyright (C) 2025 Hurry Curry! contributors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, version 3 of the License only. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # extends Node class_name Settings static func get_root(): return SettingsRoot.new([ SettingsCategory.new("gameplay", [ ToggleSetting.new("usernames", true), ToggleSetting.new("latch_boost", true), ToggleSetting.new("vibration", true), ToggleSetting.new("invert_camera", false), ToggleSetting.new("interpolate_camera_rotation", false), ButtonSetting.new("setup_completed", false, launch_setup), ToggleSetting.new("tutorial_disabled", false), ToggleSetting.new("hints_started", false), ToggleSetting.new("accessible_movement", false), ToggleSetting.new("first_person", false), DropdownSetting.new("interact_target", "dirsnap", ["dir", "dirsnap"]), PathSetting.new("screenshot_path", "", FileDialog.FileMode.FILE_MODE_OPEN_DIR), ]), SettingsCategory.new("graphics", [ PresetRow.new("preset", { "low": {"ui_blur": true, "aa": "disabled", "ssao": false, "taa": false, "shadows": false, "glow": false, "grass_amount": 0, "lq_trees": true}, "medium": {"ui_blur": true, "aa": "fx", "ssao": false, "taa": false, "shadows": true, "glow": false, "grass_amount": 16, "lq_trees": false}, "high": {"ui_blur": true, "aa": "ms2x", "ssao": true, "taa": false, "shadows": true, "glow": true, "grass_amount": 24, "lq_trees": false} }), DropdownSetting.new("fullscreen", "keep", ["keep", "always", "never"]), DropdownSetting.new("aa", "ms2x" if Global.on_high_end() else "disabled", ["disabled", "fx", "ms2x", "ms4x"]), ToggleSetting.new("ssao", true if Global.on_high_end() else false), ToggleSetting.new("taa", false), DropdownSetting.new("gi", "disabled", ["disabled", "sdfgi", "voxelgi"]), ToggleSetting.new("shadows", true if Global.on_high_end() else false), ToggleSetting.new("glow", true if Global.on_high_end() else false), RangeSetting.new("grass_amount", 24 if Global.on_high_end() else 0, 0, 32, false), ToggleSetting.new("lq_trees", false if Global.on_high_end() else true), ToggleSetting.new("debug_info", false), ToggleSetting.new("ui_blur", true) ]), SettingsCategory.new("audio", [ RangeSetting.new("master_volume", 0, -30, 0), RangeSetting.new("music_volume", 0, -30, 0), RangeSetting.new("sfx_volume", 0, -30, 0), ]), SettingsCategory.new("ui", [ DropdownSetting.new("touch_controls", "automatic", ["automatic", "enabled", "disabled"]), DropdownSetting.new("language", "system", Global.language_list()), ToggleSetting.new("hide_overlays", false), DropdownSetting.new("scale_mode", "resize", ["resize", "disabled"]), RangeSetting.new("scale_factor", 1. if not Global.on_mobile() else 1.5, 0.5, 1.5, 3), ]), SettingsCategory.new("input", InputManager.settings([ RangeSetting.new("fps_mouse_sensitivity", 0.001, 0.0001, 0.003) ])), SettingsCategory.new("server", [ PathSetting.new("binary_path", "", FileDialog.FileMode.FILE_MODE_OPEN_FILE), PathSetting.new("editor_binary_path", "", FileDialog.FileMode.FILE_MODE_OPEN_DIR), PathSetting.new("data_path", "", FileDialog.FileMode.FILE_MODE_OPEN_DIR), TextSetting.new("name", "A Hurry Curry! Server"), ToggleSetting.new("allow_external_connections", true), ToggleSetting.new("enable_ipv6", true), NumberSetting.new("bind_port", 27032, 1, 65535), ToggleSetting.new("upnp", false), ToggleSetting.new("mdns", true), ToggleSetting.new("register", false), ]), SettingsCategory.new("online", [ ToggleSetting.new("use_registry", false), TextSetting.new("registry_url", "https://hurrycurry-registry.metamuffin.org/"), ToggleSetting.new("use_discover", true), PathSetting.new("discover_binary", "", FileDialog.FileMode.FILE_MODE_OPEN_FILE), ]) ]) static var tree: GameSetting static var values: Dictionary = {} static var loaded_path: String static func read(key: String): if !values.has(key): push_error("Tried to access setting \"%s\", which does not exist (missing key)" % key) return null return values[key] static func write_unchecked(key: String, value): value = value.duplicate(true) if value is Array else value if key in values and typeof(values[key]) == typeof(value) and not value is Array and values[key] == value: return values[key] = value trigger_hook(key, value) static func write(key: String, value): if !values.has(key): push_error("Tried to set setting \"%s\", which does not yet exist (missing key)" % key) return else: write_unchecked(key, value) static func load(path: String): print("Loading settings from %s" % path) tree = get_root() loaded_path = path var changed = {} if FileAccess.file_exists(path): var f = FileAccess.open(path, FileAccess.READ) changed = JSON.parse_string(f.get_as_text()) tree.load(changed) static func save(): var changed = {} tree.save(changed) DirAccess.make_dir_recursive_absolute(loaded_path.rsplit("/", true, 1)[0]) var f = FileAccess.open(loaded_path, FileAccess.WRITE) f.store_string(JSON.stringify(changed)) static func trigger_hook(key: String, value): for slot in change_hooks.get(key, {}): change_hooks[key][slot].callv([value] if value != null else []) if key.find(".") != -1: trigger_hook(key.rsplit(".", false, 1)[0], key.rsplit(".", false, 1)[1]) static func hook_changed(key: String, slot: String, callable: Callable): if not change_hooks.has(key): change_hooks[key] = {} change_hooks[key][slot] = callable static func hook_changed_init(key: String, slot: String, callable: Callable): hook_changed(key, slot, callable) callable.call(read(key)) static func get_category_dict(prefix: String): var map = {} for k in values.keys(): var kn = k.trim_prefix(prefix + ".") if kn == k: continue map[kn] = read(k) return map static func launch_setup(): Global.focused_menu.submenu("res://gui/menus/setup/setup.tscn") static var change_hooks = { "input": { "static": h_input }, "gameplay.hints_started": { "static": h_hints_started }, "graphics.aa": { "static": h_aa }, "graphics.taa": { "static": h_taa }, "graphics.fullscreen": { "static": h_fullscreen }, "ui.scale_mode": { "static": h_scale_mode }, "ui.scale_factor": { "static": h_scale_factor }, "ui.language": { "static": h_language }, "audio.master_volume": { "static": h_volume_master }, "audio.music_volume": { "static": h_volume_music }, "audio.sfx_volume": { "static": h_volume_sfx }, } static func h_aa(_mode): Global.configure_viewport_aa(Global.get_viewport()) static func h_taa(enabled): Global.get_viewport().use_taa = enabled static func h_scale_mode(mode: String): var root = Global.get_tree().root if OS.has_feature("movie"): root.content_scale_mode = Window.CONTENT_SCALE_MODE_VIEWPORT root.content_scale_aspect = Window.CONTENT_SCALE_ASPECT_KEEP else: match mode: "resize": root.content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS "disabled": root.content_scale_mode = Window.CONTENT_SCALE_MODE_DISABLED static func h_scale_factor(value: float): Global.get_tree().root.content_scale_factor = value static func h_volume_master(value: float): Sound.set_volume(0, value) static func h_volume_music(value: float): Sound.set_volume(1, value) static func h_volume_sfx(value: float): Sound.set_volume(2, value) static func h_language(language: String): if language == "system": language = OS.get_locale_language() TranslationServer.set_locale(language) static func h_input(key: String): InputManager.update_input_map(key) static func h_fullscreen(mode: String): match mode: "keep": pass "always": DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN) "never": if DisplayServer.window_get_mode() == DisplayServer.WINDOW_MODE_FULLSCREEN: DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) static func h_hints_started(started: bool): if not started: for k in Profile.values["hints"].keys(): Profile.set_hint(k, false)