# Hurry Curry! - a game about cooking # Copyright 2024 tpart # Copyright 2024 metamuffin # Copyright 2024 nokoe # # 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 . # class_name G extends Node signal settings_changed() signal using_joypad_change(using: bool) var default_profile := { "username": "Giovanni", "character": 0, "last_server_url": "", "hints": { "has_seen_nametags": false, "has_moved": false, "has_boosted": false, "has_interacted": false, "has_rotated": false, "has_reset": false, "has_zoomed": false } } var languages := [tr("System default"), "en", "de"] var using_joypad := false var default_settings := { "language": DropdownSetting.new(tr("Language"), 0, languages), "fullscreen": DropdownSetting.new(tr("Fullscreen"), 0, [tr("Keep"), tr("Always"), tr("Never")]), "touch_controls": ToggleSetting.new(tr("Enable touch screen conrols"), DisplayServer.is_touchscreen_available()), "interpolate_camera_rotation": ToggleSetting.new(tr("Interpolate the camera rotation"), true), "invert_camera": ToggleSetting.new(tr("Invert camera movement"), false), "usernames": ToggleSetting.new(tr("Show username tags"), true), "server_binary": TextSetting.new(tr("Server binary (leave empty to search PATH)"), "", tr("Enter path")), "server_data": TextSetting.new(tr("Server data directory (leave empty to auto-detect)"), "", tr("Enter path")), "ui_scale": DropdownSetting.new(tr("UI scale"), 0, [tr("Resize"), tr("Disabled")]), "aa": DropdownSetting.new(tr("Anti-aliasing"), 2, [tr("Disabled"), "FXAA", "MSAA 2x", "MSAA 4x"]), "ssao": ToggleSetting.new(tr("Ambient occlusion"), true), "taa": ToggleSetting.new(tr("Temporal Anti-Aliasing"), false), "voxel_gi": ToggleSetting.new(tr("Use VoxelGI (Blocks the game on map update but is more accurate)"), false), "sdfgi": ToggleSetting.new(tr("Use SDFGI (Doesn't block the game but produces more artifacts)"), false), "shadows": ToggleSetting.new(tr("Enable shadows"), true), "debug_info": ToggleSetting.new(tr("Display debug info (Framerate, etc.)"), false), "grass_amount": RangeSetting.new(tr("3D grass amount per grass tile"), 16, 0, 32), "setup_complete": ToggleSetting.new(tr("Initial setup complete. (Uncheck and restart to reenter)"), false), "tutorial_started": ToggleSetting.new(tr("Tutorial started"), false), "latch_boost": ToggleSetting.new(tr("Always extend boost to maximum duration"), true) } var profile: Dictionary var settings: Dictionary var server_url = "" var error_message = "" var focused_node: Control func _ready(): profile = load_dict("user://profile", default_profile) load_settings("user://settings") apply_settings() get_viewport().gui_focus_changed.connect(Sound.play_hover_maybe) get_viewport().gui_focus_changed.connect(func (node): focused_node = node) func _input(event): if Input.is_action_just_pressed("fullscreen"): Global.set_setting("fullscreen", not Global.get_setting("fullscreen")) save_settings() update_fullscreen() # Update using_joypad variable if event is InputEventMouseButton or event is InputEventKey: if using_joypad: using_joypad = false using_joypad_change.emit(using_joypad) elif event is InputEventJoypadButton or event is InputEventJoypadMotion: if not using_joypad: using_joypad = true using_joypad_change.emit(using_joypad) func apply_settings(): update_fullscreen() update_language() # Anti-aliasing match get_setting("aa"): 0: get_viewport().msaa_2d = Viewport.MSAA_DISABLED get_viewport().msaa_3d = Viewport.MSAA_DISABLED get_viewport().screen_space_aa = Viewport.SCREEN_SPACE_AA_DISABLED 1: get_viewport().msaa_2d = Viewport.MSAA_DISABLED get_viewport().msaa_3d = Viewport.MSAA_DISABLED get_viewport().screen_space_aa = Viewport.SCREEN_SPACE_AA_FXAA 2: get_viewport().msaa_2d = Viewport.MSAA_2X get_viewport().msaa_3d = Viewport.MSAA_2X get_viewport().screen_space_aa = Viewport.SCREEN_SPACE_AA_DISABLED 3: get_viewport().msaa_2d = Viewport.MSAA_4X get_viewport().msaa_3d = Viewport.MSAA_4X get_viewport().screen_space_aa = Viewport.SCREEN_SPACE_AA_DISABLED # Temporal Anti-aliasing get_viewport().use_taa = get_setting("taa") emit_signal("settings_changed") # UI scale match get_setting("ui_scale"): 0: get_tree().root.content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS 1: get_tree().root.content_scale_mode = Window.CONTENT_SCALE_MODE_DISABLED # Hints if not get_setting("tutorial_started"): for k in profile["hints"].keys(): set_hint(k, false) func update_language(): var lang_idx: int = get_setting("language") var lang = languages[lang_idx] if lang_idx != 0: # 0 is system language TranslationServer.set_locale(lang) else: TranslationServer.set_locale(OS.get_locale_language()) func update_fullscreen(): match get_setting("fullscreen"): 0: pass 1: DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN) 2: if DisplayServer.window_get_mode() == DisplayServer.WINDOW_MODE_FULLSCREEN: DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) func save_profile(): save_dict("user://profile", profile) func save_settings(): for v in settings.values(): v.fetch_setting() var dict := {} for k in settings.keys(): var setting: GameSetting = settings[k] dict[k] = setting.get_value() save_dict("user://settings", dict) func save_dict(path: String, dict: Dictionary): var f = FileAccess.open(path, FileAccess.WRITE) f.store_var(dict.duplicate(true)) func load_dict(path: String, default: Dictionary) -> Dictionary: # TOCTOU here. Godot docs says its fine. if not FileAccess.file_exists(path): print("Skip profile load") return default var f = FileAccess.open(path, FileAccess.READ) var saved_dict = f.get_var() if saved_dict != null and saved_dict is Dictionary: add_missing_keys(saved_dict, default) print("Loaded dict: ", saved_dict) return saved_dict func load_settings(path: String): settings = default_settings if not FileAccess.file_exists(path): print("Skip settings load") return var f = FileAccess.open(path, FileAccess.READ) var saved_dict = f.get_var() if saved_dict != null and saved_dict is Dictionary: for k in default_settings.keys(): var setting: GameSetting = default_settings[k] if saved_dict.has(k): setting.set_value(saved_dict[k]) settings[k] = setting func on_vulkan() -> bool: return ProjectSettings.get_setting("rendering/rendering_device/driver") == "vulkan" func add_missing_keys(dict: Dictionary, reference: Dictionary): for k in reference.keys(): if !dict.has(k): dict[k] = reference[k] else: if dict[k] is Dictionary: add_missing_keys(dict[k], reference[k]) func get_setting(key: String): if settings.has(key): return settings[key].get_value() else: push_error("Tried to access setting \"%s\", which does not exist (missing key)" % key) return null func set_setting(key: String, value): if !settings.has(key): push_error("Tried to set setting \"%s\", which does not yet exist (missing key)" % key) return if get_setting(key) != value: settings[key].set_value(value) save_settings() func get_profile(key: String): if profile.has(key): return profile[key] else: push_error("Tried to access profile setting \"%s\", which does not exist (missing key)" % key) return null func set_profile(key: String, value): if !profile.has(key): push_error("Tried to set profile setting \"%s\", which does not yet exist (missing key)" % key) return if profile[key] != value: profile[key] = value save_profile() func set_hint(key: String, value: bool): if !profile["hints"].has(key): push_error("Tried to set hint \"%s\", which does not yet exist (missing key)" % key) if profile["hints"][key] != value: if value: set_setting("tutorial_started", true) profile["hints"][key] = value save_profile() func get_hint(key: String): if profile["hints"].has(key): return profile["hints"][key] else: push_error("Tried to access hint \"%s\", which does not exist (missing key)" % key) return null static func interpolate(current, target, dt): return target + (current - target) * exp(-dt) static func interpolate_angle(current, target, dt): if abs(target - current) > PI: if target < 0: target += PI * 2 else: target -= PI * 2 return target + (current - target) * exp(-dt) func find_menu(node: Node) -> Menu: if node is Menu: return node else: return find_menu(node.get_parent())