# 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) signal using_touch_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, "has_seen_performance": false, "has_seen_join_while_running": false } } var languages := language_array() var using_joypad := false var using_touch := false var default_settings := { "language": DropdownSetting.new(tr("Language"), 0, languages.map(func (e): return e[1])), "master_volume": RangeSetting.new(tr("Master Volume"), 0, -30, 0), "music_volume": RangeSetting.new(tr("Music Volume"), 0, -30, 0), "sfx_volume": RangeSetting.new(tr("SFX Volume"), 0, -30, 0), "fullscreen": DropdownSetting.new(tr("Fullscreen"), 0, [tr("Keep"), tr("Always"), tr("Never")]), "touch_controls": DropdownSetting.new(tr("Enable touch screen controls"), 0, [tr("Automatic"), tr("Enabled"), tr("Disabled")]), "interpolate_camera_rotation": ToggleSetting.new(tr("Smooth 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_mode": DropdownSetting.new(tr("UI scale mode"), 0, [tr("Resize"), tr("Disabled")]), "ui_scale_factor": RangeSetting.new(tr("UI scale factor"), 1. if not on_mobile() else 1.5, 0.5, 1.5, 3), "aa": DropdownSetting.new(tr("Anti-aliasing"), 2 if on_high_end() else 0, [tr("Disabled"), "FXAA", "MSAA 2x", "MSAA 4x"]), "ssao": ToggleSetting.new(tr("Ambient occlusion"), true if on_high_end() else false), "taa": ToggleSetting.new(tr("Temporal Anti-Aliasing"), false), "gi": DropdownSetting.new(tr("Global illumination"), 0, [tr("Disabled"), tr("SDFGI"), tr("Voxel GI")]), "shadows": ToggleSetting.new(tr("Enable shadows"), true if on_high_end() else false), "glow": ToggleSetting.new(tr("Enable glow"), true if on_high_end() else false), "debug_info": ToggleSetting.new(tr("Display debug info (Framerate, etc.)"), false), "grass_amount": RangeSetting.new(tr("3D grass amount per grass tile"), 16 if on_high_end() else 0, 0, 32, false), "lq_trees": ToggleSetting.new(tr("Low-poly trees"), false if on_high_end() else true), "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), "ui_blur": ToggleSetting.new(tr("Enable UI blur"), true) } var profile: Dictionary var settings: Dictionary var presets: Array[Preset] = [ Preset.new(tr("Graphics"), { tr("Low"): { "ui_blur": false, "aa": 0, "ssao": false, "taa": false, "shadows": false, "glow": false, "grass_amount": 0, "lq_trees": true }, tr("Medium"): { "ui_blur": true, "aa": 1, "ssao": false, "taa": false, "shadows": true, "glow": false, "grass_amount": 0, "lq_trees": false }, tr("High"): { "ui_blur": true, "aa": 2, "ssao": true, "taa": false, "shadows": true, "glow": true, "grass_amount": 16, "lq_trees": false } }) ] 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"): match Global.get_setting("fullscreen"): 0: # Keep setting if DisplayServer.window_get_mode() == DisplayServer.WINDOW_MODE_FULLSCREEN: DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) else: DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN) 1: # Always set_setting("fullscreen", 2) # Set to never update_fullscreen() 2: # Never set_setting("fullscreen", 1) # Set to always 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) # Update using_touch variable if get_setting("touch_controls") == 0: # Only if set to automatic if event is InputEventScreenTouch or event is InputEventScreenDrag: if not using_touch: using_touch = true using_touch_change.emit(using_touch) if event is InputEventKey or event is InputEventJoypadButton or event is InputEventJoypadMotion: if using_touch: using_touch = false using_touch_change.emit(using_touch) 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") # UI scale mode match get_setting("ui_scale_mode"): 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 # UI scale factor get_tree().root.content_scale_factor = get_setting("ui_scale_factor") print("SCALE FACTOR ", get_tree().root.content_scale_factor) # Hints if not get_setting("tutorial_started"): for k in profile["hints"].keys(): set_hint(k, false) # Sets all volumes Sound.set_volume(0, get_setting("master_volume")) Sound.set_volume(1, get_setting("music_volume")) Sound.set_volume(2, get_setting("sfx_volume")) # Touch controls match get_setting("touch_controls"): # 0: Automatically adjusted 1: # Enabled using_touch = true 2: # Disabled using_touch = false using_touch_change.emit() settings_changed.emit() func update_language(): var language = languages[get_setting("language")][0] if language == "system": language = OS.get_locale_language() TranslationServer.set_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) and typeof(setting.get_value()) == typeof(saved_dict[k]): setting.set_value(saved_dict[k]) settings[k] = setting save_settings() # Save updated keys func on_mobile() -> bool: var os_name := OS.get_name() return os_name == "Android" or os_name == "iOS" func on_high_end() -> bool: if on_mobile(): return false return on_vulkan() 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): current = fmod(current, PI * 2) target = fmod(target, PI * 2) if abs(target - current) > PI: if target < 0: target += PI * 2 else: target -= PI * 2 return target + (current - target) * exp(-dt) # TODO not working in all cases yet but there was an attempt static func interpolate_angle_closest_quarter(current, target, dt): current = fmod(current, PI * 2) target = fmod(target, PI * 2) while abs(target - current) > PI / 4.: if target - current < 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()) const NATIVE_LANGUAGE_NAMES = { "en": "English", "de": "Deutsch", "fr": "Français", "es": "Español", "ja": "日本語", "he": "עִברִית", "tr": "Türkçe", "fi": "suomen", "ar": "العربية", } func language_display(l: String): return "%s (%s)" % [NATIVE_LANGUAGE_NAMES[l], l] func language_array() -> Array: var lang: Array = [["system", tr("System default")], ["en", language_display("en")]] for l in TranslationServer.get_loaded_locales(): lang.append([l, language_display(l)]) return lang