# Hurry Curry! - a game about cooking # Copyright 2024 tpart # Copyright 2024 metamuffin # Copyright 2024 nokoe # Copyright 2024 BigBrotherNii # # 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 using_joypad_change(using: bool) signal using_touch_change(using: bool) const VERSION := "2.2.0" var default_profile := { "username": "Giovanni", "character": 0, "last_server_url": "", "tutorial_ingredients_played": [], "registry_asked": false, "hints": { "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 using_joypad := false var using_touch := false # profile and settings are stored in a Dictionary[String, Any] var profile: Dictionary var settings: Dictionary var settings_tree: GameSetting var server_url = "" var error_message = "" var focused_node: Control func _ready(): profile = load_dict("user://profile", default_profile) load_settings("user://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("graphics.fullscreen"): "keep": 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) "always": set_setting("graphics.fullscreen", "never") "never": set_setting("graphics.fullscreen", "always") # 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("ui.touch_controls") == "automatic": # 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 save_profile(): save_dict("user://profile", profile) func save_settings(): var saved = {} for key in settings_tree.changed_keys(): saved[key] = Global.get_setting(key) save_dict("user://settings", saved) func save_dict(path: String, dict: Dictionary): var f = FileAccess.open(path, FileAccess.WRITE) var to_save = dict.duplicate(true) f.store_var(to_save, 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(true) if saved_dict != null and saved_dict is Dictionary: add_missing_keys(saved_dict, default) return saved_dict func load_settings(path: String): if FileAccess.file_exists(path): var f = FileAccess.open(path, FileAccess.READ) settings = f.get_var(true) else: print("No settings file found.") settings = {} settings_tree = Settings.get_root() settings_tree.check() Settings.apply_initial() 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 get_setting(key: String): if !settings.has(key): push_error("Tried to access setting \"%s\", which does not exist (missing key)" % key) return null return settings[key] func set_setting_unchecked(key: String, value): value = value.duplicate(true) if value is Array else value if key in settings and typeof(settings[key]) == typeof(value) and not value is Array and settings[key] == value: return settings[key] = value Settings.trigger_hook(key, value) 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 else: set_setting_unchecked(key, value) 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 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("gameplay.hints_started", true) save_settings() profile["hints"][key] = value save_profile() # TODO avoid this call when bulk-unsetting hints 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 target - current > PI: target -= PI * 2 elif current - target > PI: current -= 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()) func get_message_str(m: Dictionary) -> String: if "text" in m: return m.text if "translation" in m: return tr(m.translation.id).format(m.translation.params.map(get_message_str)) return "[unsupported message type]" func language_list(): var a = TranslationServer.get_loaded_locales() a.sort() a.insert(0, "system") return a func array_eq(a, b): return a.all(func(e): return a.count(e) == b.count(e)) func add_missing_keys(dict: Dictionary, reference: Dictionary): for k in reference.keys(): if !dict.has(k) or typeof(dict[k]) != typeof(reference[k]): dict[k] = reference[k] else: if dict[k] is Dictionary: add_missing_keys(dict[k], reference[k]) func array_has_all(parent: Array, children: Array) -> bool: for i in children: if not i in parent: return false return true class ParsedItem: var name: String var contents: Array func _init(full_name: String): var c = Array(full_name.split(":")) name = c[0] contents = c[1].split(",") if c.size() > 1 else [] func configure_viewport_aa(vp: Viewport, aa: String) -> void: match aa: "disabled": vp.msaa_3d = Viewport.MSAA_DISABLED vp.screen_space_aa = Viewport.SCREEN_SPACE_AA_DISABLED "fx": vp.msaa_3d = Viewport.MSAA_DISABLED vp.screen_space_aa = Viewport.SCREEN_SPACE_AA_FXAA "ms2x": vp.msaa_3d = Viewport.MSAA_2X vp.screen_space_aa = Viewport.SCREEN_SPACE_AA_DISABLED "ms4x": vp.msaa_3d = Viewport.MSAA_4X vp.screen_space_aa = Viewport.SCREEN_SPACE_AA_DISABLED