diff options
| -rw-r--r-- | client/game.gd | 10 | ||||
| -rw-r--r-- | client/global.gd | 88 | ||||
| -rw-r--r-- | client/gui/components/message/item/item_message.gd | 2 | ||||
| -rw-r--r-- | client/gui/menus/character.gd | 14 | ||||
| -rw-r--r-- | client/gui/menus/main/play.gd | 12 | ||||
| -rw-r--r-- | client/gui/menus/setup/setup.gd | 8 | ||||
| -rw-r--r-- | client/gui/overlays/popup_message/popup_message.gd | 34 | ||||
| -rw-r--r-- | client/profile.gd | 91 | ||||
| -rw-r--r-- | client/profile.gd.uid | 1 | ||||
| -rw-r--r-- | client/settings.gd | 9 | 
10 files changed, 140 insertions, 129 deletions
diff --git a/client/game.gd b/client/game.gd index 76308349..a8f905fb 100644 --- a/client/game.gd +++ b/client/game.gd @@ -234,7 +234,7 @@ func handle_packet(p):  					if (player.is_customer  						and not Settings.read("gameplay.tutorial_disabled")  						and join_state == JoinState.JOINED): -						var completed_ingredients: Array = Global.get_profile("tutorial_ingredients_played") +						var completed_ingredients: Array = Profile.read("tutorial_ingredients_played")  						var completed := Global.array_has_all(completed_ingredients, ingredients)  						if not completed: @@ -301,11 +301,11 @@ func handle_packet(p):  			if p.success:  				var completed_item := ItemFactory.ParsedItem.new(item_names[p.item]) -				var played: Array = Global.get_profile("tutorial_ingredients_played") +				var played: Array = Profile.read("tutorial_ingredients_played")  				played.append(completed_item.name)  				played.append_array(completed_item.contents) -				Global.set_profile("tutorial_ingredients_played", played) -				Global.save_profile() +				Profile.write("tutorial_ingredients_played", played) +				Profile.save()  				while item_names[p.item] in tutorial_queue:  					tutorial_queue.erase(item_names[p.item]) @@ -372,7 +372,7 @@ func toggle_join():  	match join_state:  		JoinState.SPECTATING:  			set_join_state(JoinState.WAITING) -			mp.send_join(Global.get_profile("username"), Global.get_profile("character_style")) +			mp.send_join(Profile.read("username"), Profile.read("character_style"))  		JoinState.WAITING:  			push_error("Join/Leave action already toggled.")  		JoinState.JOINED: diff --git a/client/global.gd b/client/global.gd index c7ee555e..d9d714aa 100644 --- a/client/global.gd +++ b/client/global.gd @@ -24,37 +24,9 @@ signal using_touch_change(using: bool)  @warning_ignore("UNUSED_SIGNAL")  signal hand_count_change(count: bool) -var default_profile := { -	"username": "", -	"character_style": { -		"color": 0, -		"headwear": 0, -		"hairstyle": 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 game_paused := false  var hand_count := 0 @@ -68,7 +40,7 @@ var focused_node: Control  var focused_menu: Menu # only use this as a last resort, currently exists to open setup menu from settings  func _ready(): -	profile = load_dict("user://profile", default_profile) +	Profile.load("user://profile")  	Settings.load("user://settings.json")  	get_viewport().gui_focus_changed.connect(Sound.play_hover_maybe)  	get_viewport().gui_focus_changed.connect(func(node): focused_node = node) @@ -105,27 +77,6 @@ func _input(event):  				using_touch = false  				using_touch_change.emit(using_touch) -func save_profile(): -	save_dict("user://profile", profile) - -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 on_mobile() -> bool:  	var os_name := OS.get_name()  	return os_name == "Android" or os_name == "iOS" @@ -138,37 +89,6 @@ func on_high_end() -> bool:  func on_vulkan() -> bool:  	return ProjectSettings.get_setting("rendering/rendering_device/driver") == "vulkan" -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: -			Settings.write("gameplay.hints_started", true) -			Settings.save() -		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) @@ -206,7 +126,7 @@ func language_list():  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): +static 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] @@ -220,8 +140,8 @@ func array_has_all(parent: Array, children: Array) -> bool:  			return false  	return true -func configure_viewport_aa(vp: Viewport, aa: String) -> void: -	match aa: +func configure_viewport_aa(vp: Viewport) -> void: +	match Settings.read("graphics.aa"):  		"disabled":  			vp.msaa_3d = Viewport.MSAA_DISABLED  			vp.screen_space_aa = Viewport.SCREEN_SPACE_AA_DISABLED diff --git a/client/gui/components/message/item/item_message.gd b/client/gui/components/message/item/item_message.gd index 4079e363..f5a97723 100644 --- a/client/gui/components/message/item/item_message.gd +++ b/client/gui/components/message/item/item_message.gd @@ -31,7 +31,7 @@ var timeout_initial := 0.  @onready var v_box_container: VBoxContainer = $VBoxContainer  func _ready() -> void: -	Global.configure_viewport_aa(sub_viewport, Settings.read("graphics.aa")) +	Global.configure_viewport_aa(sub_viewport)  	if enable_grayscale:  		sub_viewport_container.material = PRINTED_MAT diff --git a/client/gui/menus/character.gd b/client/gui/menus/character.gd index 3c1230ae..715a7b1f 100644 --- a/client/gui/menus/character.gd +++ b/client/gui/menus/character.gd @@ -22,8 +22,8 @@ extends Menu  func _ready():  	super() -	$VBoxContainer/top_panel/a/username.text = Global.get_profile("username") -	character.set_style(Global.get_profile("character_style"), "chef") +	$VBoxContainer/top_panel/a/username.text = Profile.read("username") +	character.set_style(Profile.read("character_style"), "chef")  	init_map()  func init_map(): @@ -61,8 +61,8 @@ func exit():  		popup_data.buttons = [accept_button]  		await submenu("res://gui/menus/popup.tscn", popup_data)  		return -	Global.set_profile("username", username_edit.text) -	Global.save_profile() +	Profile.write("username", username_edit.text) +	Profile.save()  	super()  func _on_character_back_pressed(): @@ -90,7 +90,7 @@ func _on_hairstyle_forward_pressed() -> void:  		current_style.hairstyle = G.rem_euclid(current_style.hairstyle + 1, character.hairstyles.size()))  func modify_style(modifier: Callable): -	var current_style: Dictionary = Global.get_profile("character_style") +	var current_style: Dictionary = Profile.read("character_style")  	modifier.call(current_style) -	Global.set_profile("character_style", current_style) -	character.set_style(Global.get_profile("character_style"), "chef") +	Profile.write("character_style", current_style) +	character.set_style(Profile.read("character_style"), "chef") diff --git a/client/gui/menus/main/play.gd b/client/gui/menus/main/play.gd index 87b0b5bf..1e64a02f 100644 --- a/client/gui/menus/main/play.gd +++ b/client/gui/menus/main/play.gd @@ -34,7 +34,7 @@ func _ready():  	url_regex.compile("^(?:(ws|wss)://)?([^:]+)(?::([0-9]+))?$")  	if OS.has_feature("web"):  		server.hide() -	connect_uri.text = Global.get_profile("last_server_url") +	connect_uri.text = Profile.read("last_server_url")  	Sound.play_music("MainMenu")  	ServerList.update_server_list.connect(update_server_list) @@ -43,7 +43,7 @@ func _ready():  	update_server_list_loading(ServerList.loading)  	super() -	if not Global.get_profile("registry_asked"): +	if not Profile.read("registry_asked"):  		var popup_data := MenuPopup.Data.new()  		popup_data.text = tr("c.menu.play.allow_query_registry").format([Settings.read("online.registry_url")])  		var allow_button := Button.new() @@ -54,8 +54,8 @@ func _ready():  		deny_button.pressed.connect(func(): Settings.write("online.use_registry", false))  		popup_data.buttons = [allow_button, deny_button]  		await submenu("res://gui/menus/popup.tscn", popup_data) -		Global.set_profile("registry_asked", true) -		Global.save_profile() +		Profile.write("registry_asked", true) +		Profile.save()  		Settings.save()  	ServerList.start() @@ -110,8 +110,8 @@ func _on_connect_pressed():  		if result.get_string(3) == "" and result.get_string(1) != "wss":  			url = url + ":27032"  		connect_uri.text = url -	Global.set_profile("last_server_url", url) -	Global.save_profile() +	Profile.write("last_server_url", url) +	Profile.save()  	connect_to(url)  func _on_quick_connect_pressed(): diff --git a/client/gui/menus/setup/setup.gd b/client/gui/menus/setup/setup.gd index e4103603..878331ff 100644 --- a/client/gui/menus/setup/setup.gd +++ b/client/gui/menus/setup/setup.gd @@ -92,12 +92,12 @@ func _on_sign_pressed():  	anim.play_backwards("paper_slide")  	await anim.animation_finished -	Global.set_profile("username", username.text) -	Global.set_profile("character_style", character_style) +	Profile.write("username", username.text) +	Profile.write("character_style", character_style)  	if skip_tutorial.button_pressed:  		for k in Global.profile["hints"].keys(): -			Global.set_hint(k, true) -	Global.save_profile() +			Profile.set_hint(k, true) +	Profile.save()  	Settings.write("gameplay.hints_started", skip_tutorial.button_pressed)  	Settings.write("gameplay.tutorial_disabled", skip_tutorial.button_pressed) diff --git a/client/gui/overlays/popup_message/popup_message.gd b/client/gui/overlays/popup_message/popup_message.gd index d577465b..6a3d8273 100644 --- a/client/gui/overlays/popup_message/popup_message.gd +++ b/client/gui/overlays/popup_message/popup_message.gd @@ -131,26 +131,26 @@ func stop_game_hints():  func _input(_event):  	if Input.is_action_just_pressed("boost"): -		Global.set_hint("has_boosted", true) +		Profile.set_hint("has_boosted", true)  	if any_action_just_pressed(["forwards", "backwards", "left", "right"]): -		Global.set_hint("has_moved", true) +		Profile.set_hint("has_moved", true)  	if any_action_just_pressed(["rotate_left", "rotate_right", "rotate_up", "rotate_down"]): -		if not Global.get_hint("has_reset"): +		if not Profile.get_hint("has_reset"):  			reset_timer.start() -		Global.set_hint("has_rotated", true) +		Profile.set_hint("has_rotated", true)  	if any_action_just_pressed(["zoom_in", "zoom_out"]): -		Global.set_hint("has_zoomed", true) +		Profile.set_hint("has_zoomed", true)  	if Input.is_action_just_pressed("interact_left") or Input.is_action_just_pressed("interact_right"): -		Global.set_hint("has_interacted", true) +		Profile.set_hint("has_interacted", true)  	if Input.is_action_just_pressed("reset"): -		Global.set_hint("has_reset", true) +		Profile.set_hint("has_reset", true)  func _on_boost_timeout(): -	if not Global.get_hint("has_boosted") and not Global.using_touch: +	if not Profile.get_hint("has_boosted") and not Global.using_touch:  		display_hint_msg(tr("c.hint.boost").format([display_keybind("boost")]))  func _on_move_timeout(): -	if not Global.get_hint("has_moved") and not Global.using_touch: +	if not Profile.get_hint("has_moved") and not Global.using_touch:  		display_hint_msg(tr("c.hint.movement").format([", ".join(  			[  				display_keybind("forwards"), @@ -161,15 +161,15 @@ func _on_move_timeout():  		)]))  func _on_interact_timeout(): -	if not Global.get_hint("has_interacted") and not Global.using_touch: +	if not Profile.get_hint("has_interacted") and not Global.using_touch:  		display_hint_msg(tr("c.hint.interact").format([display_keybind("interact")]))  func _on_reset_timeout(): -	if not Global.get_hint("has_reset") and not Global.using_touch: +	if not Profile.get_hint("has_reset") and not Global.using_touch:  		display_hint_msg(tr("c.hint.reset_camera").format([display_keybind("reset")]))  func _on_zoom_timeout(): -	if not Global.get_hint("has_zoomed") and not Global.using_touch: +	if not Profile.get_hint("has_zoomed") and not Global.using_touch:  		display_hint_msg(tr("c.hint.zoom_camera").format([", ".join(  			[  				display_keybind("zoom_in"), @@ -203,7 +203,7 @@ func any_action_just_pressed(actions: Array) -> bool:  	return false  func _on_rotate_camera_timeout(): -	if not Global.get_hint("has_rotated") and not Global.using_touch: +	if not Profile.get_hint("has_rotated") and not Global.using_touch:  		display_hint_msg(tr("c.hint.rotate").format([", ".join(  			[  				display_keybind("rotate_up"), @@ -214,13 +214,13 @@ func _on_rotate_camera_timeout():  		)]))  func _on_join_while_running_timeout(): -	if not game.join_state == Game.JoinState.JOINED and not Global.get_hint("has_seen_join_while_running"): -		Global.set_hint("has_seen_join_while_running", true) +	if not game.join_state == Game.JoinState.JOINED and not Profile.get_hint("has_seen_join_while_running"): +		Profile.set_hint("has_seen_join_while_running", true)  		display_hint_msg(tr("c.hint.join_while_running").format([display_keybind("menu")]))  func _on_performance_timeout() -> void: -	if not Global.get_hint("has_seen_performance") and Engine.get_frames_per_second() < DisplayServer.screen_get_refresh_rate() * 0.75: -		Global.set_hint("has_seen_performance", true) +	if not Profile.get_hint("has_seen_performance") and Engine.get_frames_per_second() < DisplayServer.screen_get_refresh_rate() * 0.75: +		Profile.set_hint("has_seen_performance", true)  		display_hint_msg(tr("c.hint.framerate_low"))  class PositionalMessage: diff --git a/client/profile.gd b/client/profile.gd new file mode 100644 index 00000000..438e8e66 --- /dev/null +++ b/client/profile.gd @@ -0,0 +1,91 @@ +# 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 <https://www.gnu.org/licenses/>. +# +extends Node +class_name Profile + +static var default_profile := { +	"username": "", +	"character_style": { +		"color": 0, +		"headwear": 0, +		"hairstyle": 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 +	} +} + +# profile is stored in a Dictionary[String, Any] +static var values: Dictionary +static var loaded_path: String + +static func load(path: String): +	# TOCTOU here. Godot docs says its fine. +	if not FileAccess.file_exists(path): +		print("Skip profile load") +		return default_profile +	var f = FileAccess.open(path, FileAccess.READ) + +	values = f.get_var(true) +	if values != null and values is Dictionary: +		G.add_missing_keys(values, default_profile) +	loaded_path = path + +static func save(): +	var f = FileAccess.open(loaded_path, FileAccess.WRITE) +	var to_save = values.duplicate(true) +	f.store_var(to_save, true) + +static func read(key: String): +	if values.has(key): +		return values[key] +	else: +		push_error("Tried to access profile setting \"%s\", which does not exist (missing key)" % key) +		return null + +static func write(key: String, value): +	if !values.has(key): +		push_error("Tried to set profile setting \"%s\", which does not yet exist (missing key)" % key) +		return +	if values[key] != value: +		values[key] = value + +static func set_hint(key: String, value: bool): +	if !values["hints"].has(key): +		push_error("Tried to set hint \"%s\", which does not yet exist (missing key)" % key) +	if values["hints"][key] != value: +		if value: +			Settings.write("gameplay.hints_started", true) +			Settings.save() +		values["hints"][key] = value +		save() # TODO avoid this call when bulk-unsetting hints + +static func get_hint(key: String): +	if values["hints"].has(key): +		return values["hints"][key] +	else: +		push_error("Tried to access hint \"%s\", which does not exist (missing key)" % key) +		return null diff --git a/client/profile.gd.uid b/client/profile.gd.uid new file mode 100644 index 00000000..5cd63b28 --- /dev/null +++ b/client/profile.gd.uid @@ -0,0 +1 @@ +uid://1hlvx8wuxl2d diff --git a/client/settings.gd b/client/settings.gd index 8381ddc2..55a2b90e 100644 --- a/client/settings.gd +++ b/client/settings.gd @@ -134,7 +134,7 @@ static func hook_changed_init(key: String, display: bool, callable: Callable):  static func get_category_dict(prefix: String):  	var map = {} -	for k in Global.settings.keys(): +	for k in values.keys():  		var kn = k.trim_prefix(prefix + ".")  		if kn == k: continue  		map[kn] = read(k) @@ -158,9 +158,8 @@ static var change_hooks_apply = {  	"audio.sfx_volume": h_volume_sfx,  } -static func h_aa(mode): -	var vp = Global.get_viewport() -	Global.configure_viewport_aa(vp, mode) +static func h_aa(_mode): +	Global.configure_viewport_aa(Global.get_viewport())  static func h_taa(enabled):  	Global.get_viewport().use_taa = enabled @@ -198,4 +197,4 @@ static func h_input():  static func h_hints_started(started: bool):  	if not started:  		for k in Global.profile["hints"].keys(): -			Global.set_hint(k, false) +			Profile.set_hint(k, false)  |