From 8ab166ae0bd784145c3706aba0c3f3326cbbaac1 Mon Sep 17 00:00:00 2001 From: Spartan322 Date: Sun, 23 Apr 2023 16:01:49 -0400 Subject: Add SaveManager autoload singleton Globally handles save finding, creation, removal, changes, naming, and flushing Globally handles the session tags for the active save Add SaveLoadMenu to GameSession Add Start Date Panel and Save Panel loading to LobbyMenu Includes session tag filter Add LobbyPanelButton and SavePanelButton scene and scripts Add SaveResource to handle saving and loading individual save files Add localization for SaveLoadMenu Expand localization for additions to LobbyMenu Add ButtonContainer variation type to default theme Fulfill: UI-82, UI-83, UI-84, UI-85, UI-87, UI-89, UI-90, UI-91, UI-92, UI-94 UIFUN-82, UIFUN-83, UIFUN-84, UIFUN-86, UIFUN-87, UIFUN-89 FS-28 --- game/src/Autoload/SaveManager.gd | 55 +++++++++++ game/src/GameMenu.tscn | 2 +- game/src/GameSession/GameSession.tscn | 14 ++- game/src/GameSession/GameSessionMenu.gd | 10 ++ game/src/GameSession/GameSessionMenu.tscn | 4 +- game/src/LobbyMenu/LobbyMenu.gd | 145 ++++++++++++++++++++++++++--- game/src/LobbyMenu/LobbyMenu.tscn | 65 +++++++++++-- game/src/LobbyMenu/LobbyPanelButton.gd | 102 ++++++++++++++++++++ game/src/LobbyMenu/LobbyPanelButton.tscn | 30 ++++++ game/src/SaveLoadMenu/SaveLoadMenu.gd | 119 +++++++++++++++++++++++ game/src/SaveLoadMenu/SaveLoadMenu.tscn | 109 ++++++++++++++++++++++ game/src/SaveLoadMenu/SavePanelButton.gd | 41 ++++++++ game/src/SaveLoadMenu/SavePanelButton.tscn | 58 ++++++++++++ game/src/SaveLoadMenu/SaveResource.gd | 59 ++++++++++++ 14 files changed, 792 insertions(+), 21 deletions(-) create mode 100644 game/src/Autoload/SaveManager.gd create mode 100644 game/src/LobbyMenu/LobbyPanelButton.gd create mode 100644 game/src/LobbyMenu/LobbyPanelButton.tscn create mode 100644 game/src/SaveLoadMenu/SaveLoadMenu.gd create mode 100644 game/src/SaveLoadMenu/SaveLoadMenu.tscn create mode 100644 game/src/SaveLoadMenu/SavePanelButton.gd create mode 100644 game/src/SaveLoadMenu/SavePanelButton.tscn create mode 100644 game/src/SaveLoadMenu/SaveResource.gd (limited to 'game/src') diff --git a/game/src/Autoload/SaveManager.gd b/game/src/Autoload/SaveManager.gd new file mode 100644 index 0000000..c653b2c --- /dev/null +++ b/game/src/Autoload/SaveManager.gd @@ -0,0 +1,55 @@ +extends Node + +# Requirements +# * FS-28 +const save_directory_setting := &"openvic2/data/saves_directory" + +var current_save : SaveResource +var current_session_tag : StringName + +var _save_dictionary : Dictionary = {} +var _dirty_save : SaveResource + +func _ready(): + var saves_dir_path : String = ProjectSettings.get_setting_with_override(save_directory_setting) + assert(saves_dir_path != null, "'%s' setting could not be found." % save_directory_setting) + + DirAccess.make_dir_recursive_absolute(saves_dir_path) + var saves_dir := DirAccess.open(saves_dir_path) + for file in saves_dir.get_files(): + var save := SaveResource.new() + save.load_save(saves_dir_path.path_join(file)) + add_or_replace_save(save, true) + +func get_save_file_name(save_name : StringName, session_tag : StringName = current_session_tag) -> StringName: + return ("%s - %s" % [save_name, session_tag]).validate_filename() + +func make_new_save(save_name : String, session_tag : StringName = current_session_tag) -> SaveResource: + var file_name := get_save_file_name(save_name, session_tag) + ".tres" + var new_save := SaveResource.new() + new_save.set_file_path(save_name, ProjectSettings.get_setting_with_override(save_directory_setting).path_join(file_name)) + print(new_save.file_path) + new_save.session_tag = session_tag + return new_save + +func has_save(save_name : StringName, session_tag : StringName = current_session_tag) -> bool: + return _save_dictionary.has(get_save_file_name(save_name, session_tag)) + +func add_or_replace_save(save : SaveResource, ignore_dirty : bool = false) -> void: + var binded_func := _on_save_deleted_or_moved.bind(save) + save.deleted.connect(binded_func) + save.trash_moved.connect(binded_func) + _save_dictionary[get_save_file_name(save.save_name, save.session_tag)] = save + if not ignore_dirty: + _dirty_save = save + +func delete_save(save : SaveResource) -> void: + save.delete() + +func flush_save() -> void: + if _dirty_save == null: return + _dirty_save.flush_save() + _dirty_save = null + +func _on_save_deleted_or_moved(save : SaveResource) -> void: + _save_dictionary.erase(get_save_file_name(save.save_name, save.session_tag)) diff --git a/game/src/GameMenu.tscn b/game/src/GameMenu.tscn index c642351..224ae2e 100644 --- a/game/src/GameMenu.tscn +++ b/game/src/GameMenu.tscn @@ -4,7 +4,7 @@ [ext_resource type="PackedScene" uid="uid://bp5n3mlu45ygw" path="res://src/MainMenu/MainMenu.tscn" id="2_2jbkh"] [ext_resource type="PackedScene" uid="uid://cnbfxjy1m6wja" path="res://src/OptionMenu/OptionsMenu.tscn" id="3_111lv"] [ext_resource type="PackedScene" uid="uid://c8knthxkwj1uj" path="res://src/CreditsMenu/CreditsMenu.tscn" id="4_n0hoo"] -[ext_resource type="PackedScene" uid="uid://crhkgngfnxf4y" path="res://src/LobbyMenu/LobbyMenu.tscn" id="4_nofk1"] +[ext_resource type="PackedScene" uid="uid://do60kx0d3nrh4" path="res://src/LobbyMenu/LobbyMenu.tscn" id="4_nofk1"] [ext_resource type="PackedScene" uid="uid://cvl76duuym1wq" path="res://src/MusicConductor/MusicPlayer.tscn" id="6_lts1m"] [node name="GameMenu" type="Control" node_paths=PackedStringArray("_main_menu", "_options_menu", "_lobby_menu", "_credits_menu")] diff --git a/game/src/GameSession/GameSession.tscn b/game/src/GameSession/GameSession.tscn index ce3471f..996891a 100644 --- a/game/src/GameSession/GameSession.tscn +++ b/game/src/GameSession/GameSession.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=9 format=3 uid="uid://bgnupcshe1m7r"] +[gd_scene load_steps=10 format=3 uid="uid://bgnupcshe1m7r"] [ext_resource type="Script" path="res://src/GameSession/GameSession.gd" id="1_eklvp"] [ext_resource type="PackedScene" uid="uid://cvl76duuym1wq" path="res://src/MusicConductor/MusicPlayer.tscn" id="2_kt6aa"] @@ -8,6 +8,7 @@ [ext_resource type="PackedScene" uid="uid://byq323jbel48u" path="res://src/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.tscn" id="5_osjnn"] [ext_resource type="PackedScene" uid="uid://cnbfxjy1m6wja" path="res://src/OptionMenu/OptionsMenu.tscn" id="6_p5mnx"] [ext_resource type="PackedScene" uid="uid://dd8k3p7r3huwc" path="res://src/GameSession/GameSpeedPanel.tscn" id="7_myy4q"] +[ext_resource type="PackedScene" uid="uid://dayy28gn8kq34" path="res://src/SaveLoadMenu/SaveLoadMenu.tscn" id="8_4g7ko"] [node name="GameSession" type="Control" node_paths=PackedStringArray("_game_session_menu")] editor_description = "SS-102, UI-546" @@ -56,6 +57,15 @@ offset_bottom = 31.0 visible = false layout_mode = 1 +[node name="SaveLoadMenu" parent="." instance=ExtResource("8_4g7ko")] +visible = false +layout_mode = 1 +anchors_preset = -1 +anchor_left = 0.5 +anchor_right = 0.5 +offset_left = -640.0 +offset_right = 640.0 + [node name="MusicPlayer" parent="." instance=ExtResource("2_kt6aa")] layout_mode = 1 anchors_preset = 1 @@ -66,7 +76,9 @@ offset_right = 0.0 grow_horizontal = 0 [connection signal="map_view_camera_changed" from="MapView" to="MapControlPanel" method="_on_map_view_camera_changed"] +[connection signal="load_button_pressed" from="GameSessionMenu" to="SaveLoadMenu" method="show_for_load"] [connection signal="options_button_pressed" from="GameSessionMenu" to="OptionsMenu" method="show"] +[connection signal="save_button_pressed" from="GameSessionMenu" to="SaveLoadMenu" method="show_for_save"] [connection signal="game_session_menu_button_pressed" from="MapControlPanel" to="." method="_on_game_session_menu_button_pressed"] [connection signal="minimap_clicked" from="MapControlPanel" to="MapView" method="_on_minimap_clicked"] [connection signal="mouse_entered" from="MapControlPanel" to="MapView" method="_on_mouse_exited_viewport"] diff --git a/game/src/GameSession/GameSessionMenu.gd b/game/src/GameSession/GameSessionMenu.gd index 6f373d7..23ef2ef 100644 --- a/game/src/GameSession/GameSessionMenu.gd +++ b/game/src/GameSession/GameSessionMenu.gd @@ -10,6 +10,8 @@ var _main_menu_save_separator : Control var _quit_save_button : Button var _quit_save_separator : Control +signal save_button_pressed +signal load_button_pressed signal options_button_pressed func _ready() -> void: @@ -45,6 +47,8 @@ func show_save_dialog_button() -> void: # * SS-47 # * UIFUN-69 func _on_main_menu_confirmed() -> void: + SaveManager.current_session_tag = "" + SaveManager.current_save = null get_tree().change_scene_to_packed(_main_menu_scene) # REQUIREMENTS: @@ -68,3 +72,9 @@ func _on_quit_dialog_custom_action(action : StringName) -> void: match action: &"save_and_quit": _on_quit_confirmed() + +func _on_save_button_pressed(): + save_button_pressed.emit() + +func _on_load_button_pressed(): + load_button_pressed.emit() diff --git a/game/src/GameSession/GameSessionMenu.tscn b/game/src/GameSession/GameSessionMenu.tscn index 99f38df..025ef3b 100644 --- a/game/src/GameSession/GameSessionMenu.tscn +++ b/game/src/GameSession/GameSessionMenu.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=4 format=3 uid="uid://dvdynl6eir40o"] -[ext_resource type="Theme" uid="uid://dndova5cw036e" path="res://theme/game_session_menu.tres" id="1_2onog"] +[ext_resource type="Theme" uid="uid://cqrfmjt5yeti7" path="res://theme/game_session_menu.tres" id="1_2onog"] [ext_resource type="Script" path="res://src/GameSession/GameSessionMenu.gd" id="1_usq6o"] [ext_resource type="PackedScene" uid="uid://o4u142w4qkln" path="res://src/GameMenu.tscn" id="2_xi6a4"] @@ -79,6 +79,8 @@ ok_button_text = "DIALOG_OK" dialog_text = "GAMESESSIONMENU_QUIT_DIALOG_TEXT" cancel_button_text = "DIALOG_CANCEL" +[connection signal="pressed" from="ButtonListMargin/ButtonList/SaveButton" to="." method="_on_save_button_pressed"] +[connection signal="pressed" from="ButtonListMargin/ButtonList/LoadButton" to="." method="_on_load_button_pressed"] [connection signal="pressed" from="ButtonListMargin/ButtonList/OptionsButton" to="." method="_on_options_button_pressed"] [connection signal="pressed" from="ButtonListMargin/ButtonList/MainMenuButton" to="MainMenuDialog" method="popup_centered"] [connection signal="pressed" from="ButtonListMargin/ButtonList/QuitButton" to="QuitDialog" method="popup_centered"] diff --git a/game/src/LobbyMenu/LobbyMenu.gd b/game/src/LobbyMenu/LobbyMenu.gd index 4fc06c9..3cd3b40 100644 --- a/game/src/LobbyMenu/LobbyMenu.gd +++ b/game/src/LobbyMenu/LobbyMenu.gd @@ -5,25 +5,97 @@ extends HBoxContainer # * SS-12 signal back_button_pressed -signal save_game_selected +signal save_game_selected(save : SaveResource) +signal start_date_selected(index : int) -@export -var start_button : BaseButton +@export var lobby_panel_button : PackedScene +@export var save_scene : PackedScene + +@export_group("Nodes") +@export var game_select_start_date : BoxContainer +@export var game_select_save_tab : TabBar +@export var game_select_save_list : BoxContainer +@export var start_button : BaseButton +@export var session_tag_line_edit : LineEdit +@export var session_tag_dialog : ConfirmationDialog +@export var delete_dialog : ConfirmationDialog + +func filter_for_tag(tag : StringName) -> void: + for child in game_select_save_list.get_children(): + if tag == &"": + child.show() + else: + if tag == child.resource.session_tag: + child.show() + else: + child.hide() + +func _build_date_list() -> void: + var start_date := lobby_panel_button.instantiate() + start_date.set_text(&"1836") + start_date.pressed.connect(_on_start_date_panel_button_pressed.bind(start_date)) + game_select_start_date.add_child(start_date) + start_date = lobby_panel_button.instantiate() + start_date.set_text(&"1863") + start_date.pressed.connect(_on_start_date_panel_button_pressed.bind(start_date)) + game_select_start_date.add_child(start_date) + +var _id_to_tag : Array[StringName] = [] +func _build_save_list() -> void: + game_select_save_tab.add_tab("GAMELOBBY_SELECT_ALL") + for save_name in SaveManager._save_dictionary: + var save : SaveResource = SaveManager._save_dictionary[save_name] + var save_node := _create_save_node(save) + game_select_save_list.add_child(save_node) + if not _id_to_tag.has(save.session_tag): + _id_to_tag.append(save.session_tag) + game_select_save_tab.add_tab(save.session_tag) + +func _create_save_node(resource : SaveResource) -> Control: + var save_node = save_scene.instantiate() + save_node.resource = resource + save_node.pressed.connect(_on_save_node_pressed.bind(save_node)) + save_node.request_to_delete.connect(_on_save_node_delete_requested.bind(save_node)) + return save_node + +func _queue_clear_lists() -> void: + var full_list = game_select_start_date.get_children() + full_list.append_array(game_select_save_list.get_children()) + for child in full_list: + child.queue_free() + game_select_save_tab.clear_tabs() + _id_to_tag.clear() # REQUIREMENTS: # * SS-16 # * UIFUN-40 func _on_back_button_button_down(): print("Returning to Main Menu.") + SaveManager.current_session_tag = "" + SaveManager.current_save = null back_button_pressed.emit() - # REQUIREMENTS: # * SS-21 -func _on_start_button_button_down(): +func _on_start_button_pressed(): print("Starting new game.") - get_tree().change_scene_to_file("res://src/GameSession/GameSession.tscn") - + if SaveManager.current_session_tag == "": + # TODO: Get country tag as well + var datetime := Time.get_datetime_dict_from_system() + SaveManager.current_session_tag = "%s/%s/%s-%s:%s:%s" % [ + datetime["day"], + datetime["month"], + datetime["year"], + datetime["hour"], + datetime["minute"], + datetime["second"] + ] + if SaveManager.current_save == null and SaveManager.current_session_tag in _id_to_tag: + session_tag_dialog.dialog_text = tr("GAMELOBBY_SESSIONTAG_DIALOG_TEXT").format({ "session_tag": SaveManager.current_session_tag }) + session_tag_dialog.title = tr("GAMELOBBY_SESSIONTAG_DIALOG_TITLE").format({ "session_tag": SaveManager.current_session_tag }) + session_tag_dialog.popup_centered() + else: + _on_session_tag_dialog_confirmed() # REQUIREMENTS: # * SS-19 @@ -31,11 +103,60 @@ func _on_game_select_list_item_selected(index): print("Selected save game: ", index) save_game_selected.emit(index) - -func _on_save_game_selected(_index): - start_button.disabled = false - # If the date is double-clicked, start the game! func _on_game_select_list_item_activated(index): _on_game_select_list_item_selected(index) - _on_start_button_button_down() + _on_start_button_pressed() + +func _on_session_tag_edit_text_submitted(new_text): + SaveManager.current_session_tag = new_text + _on_start_button_pressed() + +func _on_session_tag_dialog_confirmed(): + get_tree().change_scene_to_file("res://src/GameSession/GameSession.tscn") + +var _requested_node_to_delete : Control +func _on_save_node_delete_requested(node : Control) -> void: + _requested_node_to_delete = node + delete_dialog.dialog_text = tr("GAMELOBBY_DELETE_DIALOG_TEXT").format({ "file_name": _requested_node_to_delete.resource.save_name }) + delete_dialog.title = tr("GAMELOBBY_DELETE_DIALOG_TITLE").format({ "file_name": _requested_node_to_delete.resource.save_name }) + delete_dialog.popup_centered() + +var _start_date_index := -1 +func _on_start_date_panel_button_pressed(node : Control) -> void: + if node is LobbyPanelButton and node.get_index(true) == _start_date_index: + _on_start_button_pressed() + return + _start_date_index = node.get_index(true) + start_button.disabled = false + start_date_selected.emit(_start_date_index) + +func _on_save_node_pressed(node : Control) -> void: + if SaveManager.current_save != null and SaveManager.current_save == node.resource: + SaveManager.current_session_tag = SaveManager.current_save.session_tag + _on_start_button_pressed() + return + SaveManager.current_save = node.resource + if SaveManager.current_save != null: + session_tag_line_edit.text = SaveManager.current_save.session_tag + else: + session_tag_line_edit.text = "" + start_button.disabled = false + save_game_selected.emit(SaveManager.current_save) + +func _on_game_select_save_tab_tab_changed(tab) -> void: + if tab == 0: + filter_for_tag(&"") + else: + filter_for_tag(_id_to_tag[tab - 1]) + +func _on_delete_dialog_confirmed(): + _requested_node_to_delete.resource.delete() + _requested_node_to_delete.queue_free() + +func _on_visibility_changed(): + if visible: + _build_date_list() + _build_save_list() + else: + _queue_clear_lists() diff --git a/game/src/LobbyMenu/LobbyMenu.tscn b/game/src/LobbyMenu/LobbyMenu.tscn index 174fb72..63a66d8 100644 --- a/game/src/LobbyMenu/LobbyMenu.tscn +++ b/game/src/LobbyMenu/LobbyMenu.tscn @@ -1,8 +1,10 @@ -[gd_scene load_steps=2 format=3 uid="uid://crhkgngfnxf4y"] +[gd_scene load_steps=4 format=3 uid="uid://do60kx0d3nrh4"] [ext_resource type="Script" path="res://src/LobbyMenu/LobbyMenu.gd" id="1_cvwum"] +[ext_resource type="PackedScene" uid="uid://k71f5gibwmtc" path="res://src/LobbyMenu/LobbyPanelButton.tscn" id="2_exh17"] +[ext_resource type="PackedScene" uid="uid://d2s7roinx2or7" path="res://src/SaveLoadMenu/SavePanelButton.tscn" id="3_4otj7"] -[node name="LobbyMenu" type="HBoxContainer" node_paths=PackedStringArray("start_button")] +[node name="LobbyMenu" type="HBoxContainer" node_paths=PackedStringArray("game_select_start_date", "game_select_save_tab", "game_select_save_list", "start_button", "session_tag_line_edit", "session_tag_dialog", "delete_dialog")] editor_description = "UI-36" anchors_preset = 15 anchor_right = 1.0 @@ -10,7 +12,15 @@ anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 script = ExtResource("1_cvwum") +lobby_panel_button = ExtResource("2_exh17") +save_scene = ExtResource("3_4otj7") +game_select_start_date = NodePath("GameSelectPanel/VBoxContainer/GameSelectScroll/GameSelectList/GameSelectStartDate") +game_select_save_tab = NodePath("GameSelectPanel/VBoxContainer/GameSelectScroll/GameSelectList/GameSelectSaveTab") +game_select_save_list = NodePath("GameSelectPanel/VBoxContainer/GameSelectScroll/GameSelectList/GameSelectSaveList") start_button = NodePath("GameStartPanel/VBoxContainer/StartButton") +session_tag_line_edit = NodePath("GameStartPanel/VBoxContainer/SessionTagEdit") +session_tag_dialog = NodePath("SessionTagDialog") +delete_dialog = NodePath("DeleteDialog") [node name="GameSelectPanel" type="PanelContainer" parent="."] layout_mode = 2 @@ -19,7 +29,30 @@ size_flags_horizontal = 3 [node name="VBoxContainer" type="VBoxContainer" parent="GameSelectPanel"] layout_mode = 2 +[node name="GameSelectScroll" type="ScrollContainer" parent="GameSelectPanel/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +horizontal_scroll_mode = 0 + +[node name="GameSelectList" type="VBoxContainer" parent="GameSelectPanel/VBoxContainer/GameSelectScroll"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="GameSelectStartDate" type="VBoxContainer" parent="GameSelectPanel/VBoxContainer/GameSelectScroll/GameSelectList"] +layout_mode = 2 + +[node name="GameSelectSaveTab" type="TabBar" parent="GameSelectPanel/VBoxContainer/GameSelectScroll/GameSelectList"] +layout_mode = 2 +tab_count = 1 +tab_0/title = "GAMELOBBY_SELECT_ALL" + +[node name="GameSelectSaveList" type="VBoxContainer" parent="GameSelectPanel/VBoxContainer/GameSelectScroll/GameSelectList"] +layout_mode = 2 +size_flags_vertical = 3 + [node name="GameSelectList" type="ItemList" parent="GameSelectPanel/VBoxContainer"] +visible = false layout_mode = 2 size_flags_vertical = 3 item_count = 2 @@ -67,6 +100,10 @@ custom_minimum_size = Vector2(0, 150) layout_mode = 2 size_flags_vertical = 3 +[node name="SessionTagEdit" type="LineEdit" parent="GameStartPanel/VBoxContainer"] +layout_mode = 2 +placeholder_text = "GAMELOBBY_SESSION_TAG" + [node name="StartButton" type="Button" parent="GameStartPanel/VBoxContainer"] editor_description = "UI-43" layout_mode = 2 @@ -77,8 +114,24 @@ text = "GAMELOBBY_START" custom_minimum_size = Vector2(0, 33) layout_mode = 2 -[connection signal="save_game_selected" from="." to="." method="_on_save_game_selected"] -[connection signal="item_activated" from="GameSelectPanel/VBoxContainer/GameSelectList" to="." method="_on_game_select_list_item_activated"] -[connection signal="item_selected" from="GameSelectPanel/VBoxContainer/GameSelectList" to="." method="_on_game_select_list_item_selected"] +[node name="SessionTagDialog" type="ConfirmationDialog" parent="."] +disable_3d = true +title = "GAMELOBBY_SESSIONTAG_DIALOG_TITLE" +ok_button_text = "DIALOG_OK" +dialog_text = "GAMELOBBY_SESSIONTAG_DIALOG_TEXT" +cancel_button_text = "DIALOG_CANCEL" + +[node name="DeleteDialog" type="ConfirmationDialog" parent="."] +disable_3d = true +title = "GAMELOBBY_DELETE_DIALOG_TITLE" +ok_button_text = "DIALOG_OK" +dialog_text = "GAMELOBBY_DELETE_DIALOG_TEXT" +cancel_button_text = "DIALOG_CANCEL" + +[connection signal="visibility_changed" from="." to="." method="_on_visibility_changed"] +[connection signal="tab_changed" from="GameSelectPanel/VBoxContainer/GameSelectScroll/GameSelectList/GameSelectSaveTab" to="." method="_on_game_select_save_tab_tab_changed"] [connection signal="button_down" from="GameSelectPanel/VBoxContainer/BackButton" to="." method="_on_back_button_button_down"] -[connection signal="button_down" from="GameStartPanel/VBoxContainer/StartButton" to="." method="_on_start_button_button_down"] +[connection signal="text_submitted" from="GameStartPanel/VBoxContainer/SessionTagEdit" to="." method="_on_session_tag_edit_text_submitted"] +[connection signal="pressed" from="GameStartPanel/VBoxContainer/StartButton" to="." method="_on_start_button_pressed"] +[connection signal="confirmed" from="SessionTagDialog" to="." method="_on_session_tag_dialog_confirmed"] +[connection signal="confirmed" from="DeleteDialog" to="." method="_on_delete_dialog_confirmed"] diff --git a/game/src/LobbyMenu/LobbyPanelButton.gd b/game/src/LobbyMenu/LobbyPanelButton.gd new file mode 100644 index 0000000..5f3ea46 --- /dev/null +++ b/game/src/LobbyMenu/LobbyPanelButton.gd @@ -0,0 +1,102 @@ +@tool +class_name LobbyPanelButton +extends Container + +signal button_down +signal button_up +signal pressed +signal toggled(button_pressed : bool) + +var is_start_date : bool: + get = _is_start_date + +func _is_start_date() -> bool: + return true + +@export_group("Nodes") +@export var background_button : BaseButton +@export var name_label : Label + +var text : StringName: + get = get_text, + set = set_text + +func get_text() -> StringName: + return name_label.text + +func set_text(value : StringName) -> void: + name_label.text = value + +func _get_minimum_size() -> Vector2: + var result := Vector2() + for child in get_children(): + child = child as Control + if child == null or not child.visible: + continue + if child.top_level: + continue + + var minsize : Vector2 = child.get_combined_minimum_size() + result.x = max(result.x, minsize.x) + result.y = max(result.y, minsize.y) + + var draw_style := _get_draw_mode_style() + if draw_style != null: + result += draw_style.get_minimum_size() + + return result + +func _get_draw_mode_name(support_rtl : bool = true) -> StringName: + var rtl := support_rtl and background_button != null and background_button.is_layout_rtl() + match background_button.get_draw_mode() if background_button != null else BaseButton.DrawMode.DRAW_NORMAL: + BaseButton.DrawMode.DRAW_NORMAL: + if rtl: return &"normal_mirrored" + return &"normal" + BaseButton.DrawMode.DRAW_PRESSED: + if rtl: return &"pressed_mirrored" + return &"pressed" + BaseButton.DrawMode.DRAW_HOVER: + if rtl: return &"hover_mirrored" + return &"hover" + BaseButton.DrawMode.DRAW_DISABLED: + if rtl: return &"disabled_mirrored" + return &"disabled" + BaseButton.DrawMode.DRAW_HOVER_PRESSED: + if rtl: return &"hover_pressed_mirrored" + return &"hover_pressed" + return &"" + +func _get_draw_mode_style() -> StyleBox: + if background_button == null: return null + var result := background_button.get_theme_stylebox(_get_draw_mode_name()) + if result == null: + return background_button.get_theme_stylebox(_get_draw_mode_name(false)) + return result + +func _notification(what) -> void: + if what == NOTIFICATION_SORT_CHILDREN: + var _size := size + var offset := Vector2() + var style := _get_draw_mode_style() + if style != null: + _size -= style.get_minimum_size() + offset += style.get_offset() + + for child in get_children(): + child = child as Control + if child == null or not child.is_visible_in_tree() or child.top_level: + continue + + fit_child_in_rect(child, Rect2(offset, _size)) + +func _on_background_button_button_down(): + button_down.emit() + +func _on_background_button_button_up(): + button_up.emit() + +func _on_background_button_pressed(): + pressed.emit() + +func _on_background_button_toggled(button_pressed : bool): + toggled.emit(button_pressed) diff --git a/game/src/LobbyMenu/LobbyPanelButton.tscn b/game/src/LobbyMenu/LobbyPanelButton.tscn new file mode 100644 index 0000000..2ba6c99 --- /dev/null +++ b/game/src/LobbyMenu/LobbyPanelButton.tscn @@ -0,0 +1,30 @@ +[gd_scene load_steps=2 format=3 uid="uid://k71f5gibwmtc"] + +[ext_resource type="Script" path="res://src/LobbyMenu/LobbyPanelButton.gd" id="1_327u2"] + +[node name="LobbyPanelButton" type="Container" node_paths=PackedStringArray("background_button", "name_label")] +offset_right = 113.0 +offset_bottom = 48.0 +script = ExtResource("1_327u2") +background_button = NodePath("BackgroundButton") +name_label = NodePath("SaveList/NameLabel") + +[node name="BackgroundButton" type="Button" parent="."] +layout_mode = 2 +theme_type_variation = &"ButtonContainer" + +[node name="SaveList" type="HBoxContainer" parent="."] +layout_mode = 2 +size_flags_horizontal = 0 +mouse_filter = 2 + +[node name="NameLabel" type="Label" parent="SaveList"] +layout_mode = 2 +size_flags_vertical = 1 +text = "PLACEHOLDER" +vertical_alignment = 1 + +[connection signal="button_down" from="BackgroundButton" to="." method="_on_background_button_button_down"] +[connection signal="button_up" from="BackgroundButton" to="." method="_on_background_button_button_up"] +[connection signal="pressed" from="BackgroundButton" to="." method="_on_background_button_pressed"] +[connection signal="toggled" from="BackgroundButton" to="." method="_on_background_button_toggled"] diff --git a/game/src/SaveLoadMenu/SaveLoadMenu.gd b/game/src/SaveLoadMenu/SaveLoadMenu.gd new file mode 100644 index 0000000..abf1f8c --- /dev/null +++ b/game/src/SaveLoadMenu/SaveLoadMenu.gd @@ -0,0 +1,119 @@ +extends Control + +@export var _save_scene : PackedScene + +@export_group("Nodes") +@export var _label : Label +@export var _scroll_list : BoxContainer +@export var _save_line_edit : LineEdit +@export var _save_load_button : Button +@export var _tag_selection_tab : TabBar +@export var _delete_dialog : ConfirmationDialog +@export var _overwrite_dialog : ConfirmationDialog + +var is_save_menu : bool = true +var _id_to_tag : Array[StringName] = [] + +func filter_for_tag(tag : StringName) -> void: + for child in _scroll_list.get_children(): + if tag == &"": + child.show() + else: + if tag == child.resource.session_tag: + child.show() + else: + child.hide() + +func show_for_load() -> void: + _label.text = "SAVELOADMENU_LOAD_TITLE" + _save_load_button.text = "SAVELOADMENU_LOAD_BUTTON" + _save_line_edit.editable = false + is_save_menu = false + show() + +func show_for_save() -> void: + _label.text = "SAVELOADMENU_SAVE_TITLE" + _save_load_button.text = "SAVELOADMENU_SAVE_BUTTON" + _save_line_edit.editable = true + is_save_menu = true + show() + +func _build_save_list() -> void: + _tag_selection_tab.add_tab("SAVELOADMENU_TABSELECTIONTABBAR_ALL") + for save_name in SaveManager._save_dictionary: + var save : SaveResource = SaveManager._save_dictionary[save_name] + var save_node := _create_save_node(save) + _scroll_list.add_child(save_node) + if not _id_to_tag.has(save.session_tag): + _id_to_tag.append(save.session_tag) + _tag_selection_tab.add_tab(save.session_tag) + +func _create_save_node(resource : SaveResource) -> Control: + var save_node = _save_scene.instantiate() + save_node.resource = resource + save_node.pressed.connect(_on_save_node_pressed.bind(save_node)) + save_node.request_to_delete.connect(_on_save_node_delete_requested.bind(save_node)) + return save_node + +func _queue_clear_scroll_list() -> void: + for child in _scroll_list.get_children(): + child.queue_free() + _tag_selection_tab.clear_tabs() + _id_to_tag.clear() + +# REQUIREMENTS: +# * UIFUN-84 +# * UIFUN-89 +func _on_close_button_pressed() -> void: + hide() + +func _on_delete_dialog_confirmed() -> void: + _requested_node_to_delete.resource.delete() + _requested_node_to_delete.queue_free() + +# REQUIREMENTS: +# * UIFUNC-83 +func _on_overwrite_dialog_confirmed() -> void: + SaveManager.add_or_replace_save(SaveManager.make_new_save(_submitted_text)) + _on_close_button_pressed() + +var _submitted_text : String = "" +func _on_save_line_edit_text_submitted(new_text) -> void: + _submitted_text = new_text + if SaveManager.has_save(new_text): + _overwrite_dialog.dialog_text = tr("SAVELOADMENU_OVERWRITE_DIALOG_TEXT").format({ "file_name": _submitted_text }) + _overwrite_dialog.title = tr("SAVELOADMENU_OVERWRITE_DIALOG_TITLE").format({ "file_name": _submitted_text }) + _overwrite_dialog.popup_centered() + return + _on_overwrite_dialog_confirmed() + +func _on_save_load_button_pressed() -> void: + if is_save_menu: + _save_line_edit.text_submitted.emit(_save_line_edit.text) + +var _requested_node_to_delete : Control +func _on_save_node_delete_requested(node : Control) -> void: + _requested_node_to_delete = node + _delete_dialog.dialog_text = tr("SAVELOADMENU_DELETE_DIALOG_TEXT").format({ "file_name": _requested_node_to_delete.resource.save_name }) + _delete_dialog.title = tr("SAVELOADMENU_DELETE_DIALOG_TITLE").format({ "file_name": _requested_node_to_delete.resource.save_name }) + _delete_dialog.popup_centered() + +# REQUIREMENTS: +# * UIFUN-81 +# * UIFUN-86 +func _on_save_node_pressed(node : Control) -> void: + if is_save_menu: + _save_line_edit.text = node.resource.save_name + +func _on_tag_selection_tab_bar_tab_changed(tab) -> void: + if tab == 0: + filter_for_tag(&"") + else: + filter_for_tag(_id_to_tag[tab - 1]) + +func _on_visibility_changed() -> void: + if visible: + _build_save_list() + else: + _queue_clear_scroll_list() + SaveManager.flush_save() diff --git a/game/src/SaveLoadMenu/SaveLoadMenu.tscn b/game/src/SaveLoadMenu/SaveLoadMenu.tscn new file mode 100644 index 0000000..e9f068e --- /dev/null +++ b/game/src/SaveLoadMenu/SaveLoadMenu.tscn @@ -0,0 +1,109 @@ +[gd_scene load_steps=5 format=3 uid="uid://d3g6wbvwflmyk"] + +[ext_resource type="Script" path="res://src/SaveLoadMenu/SaveLoadMenu.gd" id="1_3jkds"] +[ext_resource type="PackedScene" uid="uid://d2s7roinx2or7" path="res://src/SaveLoadMenu/SavePanelButton.tscn" id="2_fc6r3"] + +[sub_resource type="InputEventAction" id="InputEventAction_8vo2t"] +action = &"ui_accept" +pressed = true + +[sub_resource type="Shortcut" id="Shortcut_o1f2l"] +events = [SubResource("InputEventAction_8vo2t")] + +[node name="SaveLoadMenu" type="MarginContainer" node_paths=PackedStringArray("_label", "_scroll_list", "_save_line_edit", "_save_load_button", "_tag_selection_tab", "_delete_dialog", "_overwrite_dialog")] +editor_description = "UI-82, UI-89" +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 250 +theme_override_constants/margin_top = 100 +theme_override_constants/margin_right = 250 +theme_override_constants/margin_bottom = 100 +script = ExtResource("1_3jkds") +_save_scene = ExtResource("2_fc6r3") +_label = NodePath("SaveLoadPanel/SaveLoadList/TitleBarList/SaveLoadLabel") +_scroll_list = NodePath("SaveLoadPanel/SaveLoadList/SaveLoadScroll/SaveLoadScrollList") +_save_line_edit = NodePath("SaveLoadPanel/SaveLoadList/SaveLineEdit") +_save_load_button = NodePath("SaveLoadPanel/SaveLoadList/SaveLoadButton") +_tag_selection_tab = NodePath("SaveLoadPanel/SaveLoadList/TagSelectionList/TagSelectionTabBar") +_delete_dialog = NodePath("DeleteDialog") +_overwrite_dialog = NodePath("OverwriteDialog") + +[node name="SaveLoadPanel" type="PanelContainer" parent="."] +layout_mode = 2 + +[node name="SaveLoadList" type="VBoxContainer" parent="SaveLoadPanel"] +layout_mode = 2 + +[node name="TitleBarList" type="HBoxContainer" parent="SaveLoadPanel/SaveLoadList"] +layout_mode = 2 +alignment = 2 + +[node name="SaveLoadLabel" type="Label" parent="SaveLoadPanel/SaveLoadList/TitleBarList"] +layout_mode = 2 +size_flags_horizontal = 6 +text = "SAVELOADMENU_SAVE_TITLE" + +[node name="CloseButton" type="Button" parent="SaveLoadPanel/SaveLoadList/TitleBarList"] +editor_description = "UI-87, UI-94" +layout_mode = 2 +text = "X" + +[node name="TagSelectionList" type="HBoxContainer" parent="SaveLoadPanel/SaveLoadList"] +layout_mode = 2 + +[node name="TagSelectionLabel" type="Label" parent="SaveLoadPanel/SaveLoadList/TagSelectionList"] +layout_mode = 2 +text = "SAVELOADMENU_SESSION" + +[node name="TagSelectionTabBar" type="TabBar" parent="SaveLoadPanel/SaveLoadList/TagSelectionList"] +layout_mode = 2 +size_flags_horizontal = 3 +tab_count = 1 +tab_0/title = "SAVELOADMENU_TABSELECTIONTABBAR_ALL" + +[node name="SaveLoadScroll" type="ScrollContainer" parent="SaveLoadPanel/SaveLoadList"] +editor_description = "UI-83, UI-90" +layout_mode = 2 +size_flags_vertical = 3 + +[node name="SaveLoadScrollList" type="VBoxContainer" parent="SaveLoadPanel/SaveLoadList/SaveLoadScroll"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="SaveLineEdit" type="LineEdit" parent="SaveLoadPanel/SaveLoadList"] +editor_description = "UI-85, UI-92" +layout_mode = 2 + +[node name="SaveLoadButton" type="Button" parent="SaveLoadPanel/SaveLoadList"] +editor_description = "UIFUN-82, UIFUN-87" +layout_mode = 2 +size_flags_horizontal = 4 +shortcut = SubResource("Shortcut_o1f2l") +shortcut_feedback = false +text = "SAVELOADMENU_SAVE_BUTTON" + +[node name="DeleteDialog" type="ConfirmationDialog" parent="."] +disable_3d = true +title = "SAVELOADMENU_DELETE_DIALOG_TITLE" +ok_button_text = "DIALOG_OK" +dialog_text = "SAVELOADMENU_DELETE_DIALOG_TEXT" +cancel_button_text = "DIALOG_CANCEL" + +[node name="OverwriteDialog" type="ConfirmationDialog" parent="."] +disable_3d = true +title = "SAVELOADMENU_OVERWRITE_DIALOG_TITLE" +ok_button_text = "DIALOG_OK" +dialog_text = "SAVELOADMENU_OVERWRITE_DIALOG_TEXT" +cancel_button_text = "DIALOG_CANCEL" + +[connection signal="visibility_changed" from="." to="." method="_on_visibility_changed"] +[connection signal="pressed" from="SaveLoadPanel/SaveLoadList/TitleBarList/CloseButton" to="." method="_on_close_button_pressed"] +[connection signal="tab_changed" from="SaveLoadPanel/SaveLoadList/TagSelectionList/TagSelectionTabBar" to="." method="_on_tag_selection_tab_bar_tab_changed"] +[connection signal="text_submitted" from="SaveLoadPanel/SaveLoadList/SaveLineEdit" to="." method="_on_save_line_edit_text_submitted"] +[connection signal="pressed" from="SaveLoadPanel/SaveLoadList/SaveLoadButton" to="." method="_on_save_load_button_pressed"] +[connection signal="confirmed" from="DeleteDialog" to="." method="_on_delete_dialog_confirmed"] +[connection signal="confirmed" from="OverwriteDialog" to="." method="_on_overwrite_dialog_confirmed"] diff --git a/game/src/SaveLoadMenu/SavePanelButton.gd b/game/src/SaveLoadMenu/SavePanelButton.gd new file mode 100644 index 0000000..5fe4917 --- /dev/null +++ b/game/src/SaveLoadMenu/SavePanelButton.gd @@ -0,0 +1,41 @@ +@tool +extends LobbyPanelButton + +signal request_to_delete + +@export_group("Nodes") +@export var country_flag : TextureRect +@export var date_label : Label +@export var delete_button : BaseButton + +var resource : SaveResource: + get: + return resource + set(value): + if resource != null: + resource.changed.disconnect(_resource_changed) + resource = value + if resource != null: + resource.changed.connect(_resource_changed) + _resource_changed() + +func get_text() -> StringName: + return resource.save_name + +func set_text(value : StringName) -> void: + if resource != null: + resource.save_name = value + +func _ready(): + _resource_changed() + +func _is_start_date() -> bool: + return false + +func _resource_changed() -> void: + if resource == null: return + name_label.text = resource.save_name + date_label.text = Time.get_datetime_string_from_unix_time(resource.get_save_file_time(), true) + +func _on_delete_button_pressed() -> void: + request_to_delete.emit() diff --git a/game/src/SaveLoadMenu/SavePanelButton.tscn b/game/src/SaveLoadMenu/SavePanelButton.tscn new file mode 100644 index 0000000..3a71a57 --- /dev/null +++ b/game/src/SaveLoadMenu/SavePanelButton.tscn @@ -0,0 +1,58 @@ +[gd_scene load_steps=2 format=3 uid="uid://d2s7roinx2or7"] + +[ext_resource type="Script" path="res://src/SaveLoadMenu/SavePanelButton.gd" id="1_rtuo6"] + +[node name="SavePanelButton" type="Container" node_paths=PackedStringArray("country_flag", "date_label", "delete_button", "background_button", "name_label")] +editor_description = "UI-84, UI-91" +offset_right = 276.0 +offset_bottom = 48.0 +script = ExtResource("1_rtuo6") +country_flag = NodePath("SaveList/CountryFlag") +date_label = NodePath("SaveList/DateLabel") +delete_button = NodePath("SaveList/DeleteButton") +background_button = NodePath("BackgroundButton") +name_label = NodePath("SaveList/NameLabel") + +[node name="BackgroundButton" type="Button" parent="."] +layout_mode = 2 +theme_type_variation = &"ButtonContainer" + +[node name="SaveList" type="HBoxContainer" parent="."] +layout_mode = 2 +mouse_filter = 2 + +[node name="CountryFlag" type="TextureRect" parent="SaveList"] +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 4 +mouse_filter = 2 + +[node name="NameLabel" type="Label" parent="SaveList"] +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 1 +text = "PLACEHOLDER" +vertical_alignment = 1 + +[node name="DateLabel" type="Label" parent="SaveList"] +layout_mode = 2 +size_flags_horizontal = 0 +size_flags_vertical = 1 +text = "00.00.0000" +vertical_alignment = 1 + +[node name="Separator" type="Control" parent="SaveList"] +layout_mode = 2 +size_flags_horizontal = 3 +mouse_filter = 2 + +[node name="DeleteButton" type="Button" parent="SaveList"] +layout_mode = 2 +size_flags_horizontal = 8 +text = "x" + +[connection signal="button_down" from="BackgroundButton" to="." method="_on_background_button_button_down"] +[connection signal="button_up" from="BackgroundButton" to="." method="_on_background_button_button_up"] +[connection signal="pressed" from="BackgroundButton" to="." method="_on_background_button_pressed"] +[connection signal="toggled" from="BackgroundButton" to="." method="_on_background_button_toggled"] +[connection signal="pressed" from="SaveList/DeleteButton" to="." method="_on_delete_button_pressed"] diff --git a/game/src/SaveLoadMenu/SaveResource.gd b/game/src/SaveLoadMenu/SaveResource.gd new file mode 100644 index 0000000..5e7faa6 --- /dev/null +++ b/game/src/SaveLoadMenu/SaveResource.gd @@ -0,0 +1,59 @@ +extends Resource +class_name SaveResource + +signal file_flushed(path : String) +signal file_loaded +signal file_moved_to_trash +signal file_deleted +signal trash_moved +signal deleted + +var save_name : StringName: + get: return save_name + set(v): + save_name = v + file.set_value("Save", "name", save_name) + emit_changed() +var session_tag : StringName: + get: return session_tag + set(v): + session_tag = v + file.set_value("Save", "session_tag", v) + emit_changed() +var file_path : String: + get: return file_path + set(v): + file_path = v + emit_changed() +var file : ConfigFile = ConfigFile.new() + +func set_file_path(name : StringName, path : String): + file_path = path + save_name = name + +func flush_save() -> Error: + file_flushed.emit(file_path) + var result := file.save(file_path) + file.clear() + return result + +func load_save(path : String = file_path) -> Error: + file_loaded.emit() + var result := file.load(path) + session_tag = file.get_value("Save", "session_tag", session_tag) + if path != file_path: + set_file_path(file.get_value("Save", "name", save_name), path) + return result + +func get_save_file_time() -> int: + return FileAccess.get_modified_time(file_path) + +func move_to_trash() -> Error: + trash_moved.emit() + file_moved_to_trash.emit() + return OS.move_to_trash(file_path) + +func delete() -> Error: + deleted.emit() + file_deleted.emit() + return DirAccess.remove_absolute(file_path) -- cgit v1.2.3-56-ga3b1