diff --git a/scenes/Catapult.tscn b/scenes/Catapult.tscn index a9185aae..c6034f34 100644 --- a/scenes/Catapult.tscn +++ b/scenes/Catapult.tscn @@ -1,11 +1,10 @@ -[gd_scene load_steps=42 format=2] +[gd_scene load_steps=41 format=2] [ext_resource path="res://icons/info.svg" type="Texture" id=1] [ext_resource path="res://icons/buttons/tex_resume.tex" type="Texture" id=2] [ext_resource path="res://icons/buttons/tex_download.tex" type="Texture" id=3] [ext_resource path="res://scripts/Catapult.gd" type="Script" id=4] [ext_resource path="res://scripts/ReleaseManager.gd" type="Script" id=5] -[ext_resource path="res://scripts/InstallProbe.gd" type="Script" id=6] [ext_resource path="res://icons/lang/tex_lang_cs.res" type="Texture" id=7] [ext_resource path="res://scripts/SettingsUI.gd" type="Script" id=8] [ext_resource path="res://scripts/ReleaseInstaller.gd" type="Script" id=9] @@ -121,7 +120,7 @@ margin_bottom = 100.0 [node name="Tabs" type="TabContainer" parent="Main" groups=["disable_during_backup_operations", "disable_during_mod_operations", "disable_during_soundpack_operations", "disable_while_fetching_releases", "disable_while_installing_game"]] margin_top = 104.0 margin_right = 592.0 -margin_bottom = 373.0 +margin_bottom = 513.0 tab_align = 0 script = ExtResource( 16 ) @@ -219,18 +218,18 @@ margin_top = 124.0 margin_right = 577.0 margin_bottom = 132.0 -[node name="CurrentInstall" type="VBoxContainer" parent="Main/Tabs/Game"] +[node name="ActiveInstall" type="VBoxContainer" parent="Main/Tabs/Game"] margin_top = 136.0 margin_right = 577.0 margin_bottom = 224.0 -[node name="Label" type="Label" parent="Main/Tabs/Game/CurrentInstall"] +[node name="Label" type="Label" parent="Main/Tabs/Game/ActiveInstall"] margin_right = 577.0 margin_bottom = 24.0 -text = "lbl_currently_installed" +text = "Active install:" align = 1 -[node name="Build" type="HBoxContainer" parent="Main/Tabs/Game/CurrentInstall"] +[node name="Build" type="HBoxContainer" parent="Main/Tabs/Game/ActiveInstall"] margin_left = 217.0 margin_top = 28.0 margin_right = 360.0 @@ -238,13 +237,13 @@ margin_bottom = 52.0 size_flags_horizontal = 4 custom_constants/separation = 4 -[node name="Name" type="Label" parent="Main/Tabs/Game/CurrentInstall/Build"] +[node name="Name" type="Label" parent="Main/Tabs/Game/ActiveInstall/Build"] margin_right = 99.0 margin_bottom = 24.0 text = "lbl_build_none" align = 1 -[node name="GameDir" parent="Main/Tabs/Game/CurrentInstall/Build" instance=ExtResource( 26 )] +[node name="GameDir" parent="Main/Tabs/Game/ActiveInstall/Build" instance=ExtResource( 26 )] margin_left = 103.0 margin_top = 3.0 margin_right = 121.0 @@ -252,7 +251,7 @@ margin_bottom = 21.0 hint_tooltip = "tooltip_game_dir" texture_normal = ExtResource( 24 ) -[node name="UserDir" parent="Main/Tabs/Game/CurrentInstall/Build" instance=ExtResource( 26 )] +[node name="UserDir" parent="Main/Tabs/Game/ActiveInstall/Build" instance=ExtResource( 26 )] margin_left = 125.0 margin_top = 3.0 margin_right = 143.0 @@ -260,14 +259,14 @@ margin_bottom = 21.0 hint_tooltip = "tooltip_user_dir" texture_normal = ExtResource( 23 ) -[node name="Launch" type="HBoxContainer" parent="Main/Tabs/Game/CurrentInstall"] +[node name="Launch" type="HBoxContainer" parent="Main/Tabs/Game/ActiveInstall"] margin_left = 187.0 margin_top = 56.0 margin_right = 389.0 margin_bottom = 88.0 size_flags_horizontal = 4 -[node name="BtnPlay" type="Button" parent="Main/Tabs/Game/CurrentInstall/Launch" groups=["disable_while_installing_game"]] +[node name="BtnPlay" type="Button" parent="Main/Tabs/Game/ActiveInstall/Launch" groups=["disable_while_installing_game"]] margin_right = 88.0 margin_bottom = 32.0 hint_tooltip = "tooltip_play" @@ -275,7 +274,7 @@ size_flags_horizontal = 4 text = "btn_play" icon = ExtResource( 25 ) -[node name="BtnResume" type="Button" parent="Main/Tabs/Game/CurrentInstall/Launch" groups=["disable_while_installing_game"]] +[node name="BtnResume" type="Button" parent="Main/Tabs/Game/ActiveInstall/Launch" groups=["disable_while_installing_game"]] margin_left = 94.0 margin_right = 202.0 margin_bottom = 32.0 @@ -284,10 +283,49 @@ size_flags_horizontal = 4 text = "btn_resume" icon = ExtResource( 2 ) -[node name="Spacer" type="Control" parent="Main/Tabs/Game"] +[node name="HSeparator2" type="HSeparator" parent="Main/Tabs/Game"] margin_top = 228.0 margin_right = 577.0 -margin_bottom = 228.0 +margin_bottom = 236.0 + +[node name="GameInstalls" type="VBoxContainer" parent="Main/Tabs/Game"] +margin_top = 240.0 +margin_right = 577.0 +margin_bottom = 368.0 + +[node name="Label2" type="Label" parent="Main/Tabs/Game/GameInstalls"] +margin_right = 577.0 +margin_bottom = 24.0 +text = "Installed versions:" +align = 1 + +[node name="HBox" type="HBoxContainer" parent="Main/Tabs/Game/GameInstalls"] +margin_top = 28.0 +margin_right = 577.0 +margin_bottom = 128.0 + +[node name="InstallsList" type="ItemList" parent="Main/Tabs/Game/GameInstalls/HBox"] +margin_right = 488.0 +margin_bottom = 100.0 +rect_min_size = Vector2( 0, 100 ) +size_flags_horizontal = 3 +max_columns = 3 + +[node name="VBox" type="VBoxContainer" parent="Main/Tabs/Game/GameInstalls/HBox"] +margin_left = 494.0 +margin_right = 577.0 +margin_bottom = 100.0 + +[node name="Button" type="Button" parent="Main/Tabs/Game/GameInstalls/HBox/VBox"] +margin_right = 83.0 +margin_bottom = 24.0 +text = "Make active" + +[node name="Button2" type="Button" parent="Main/Tabs/Game/GameInstalls/HBox/VBox"] +margin_top = 28.0 +margin_right = 83.0 +margin_bottom = 52.0 +text = "Delete" [node name="ChangelogDialog" parent="Main/Tabs/Game" instance=ExtResource( 32 )] @@ -1219,7 +1257,7 @@ size_flags_horizontal = 4 text = "Print screen info" [node name="Log" type="RichTextLabel" parent="Main"] -margin_top = 377.0 +margin_top = 517.0 margin_right = 592.0 margin_bottom = 692.0 focus_mode = 2 @@ -1244,12 +1282,6 @@ __meta__ = { "_editor_description_": "Installs game releases from scratch into specified location. Makes use of the Downloader." } -[node name="InstallProbe" type="Node" parent="."] -script = ExtResource( 6 ) -__meta__ = { -"_editor_description_": "For now only provides information about installed game builds." -} - [node name="Mods" type="Node" parent="."] script = ExtResource( 12 ) @@ -1279,10 +1311,10 @@ __meta__ = { [connection signal="item_selected" from="Main/Tabs/Game/Builds/BuildsList" to="." method="_on_BuildsList_item_selected"] [connection signal="pressed" from="Main/Tabs/Game/Builds/BtnRefresh" to="." method="_on_BtnRefresh_pressed"] [connection signal="pressed" from="Main/Tabs/Game/BtnInstall" to="." method="_on_BtnInstall_pressed"] -[connection signal="pressed" from="Main/Tabs/Game/CurrentInstall/Build/GameDir" to="." method="_on_GameDir_pressed"] -[connection signal="pressed" from="Main/Tabs/Game/CurrentInstall/Build/UserDir" to="." method="_on_UserDir_pressed"] -[connection signal="pressed" from="Main/Tabs/Game/CurrentInstall/Launch/BtnPlay" to="." method="_on_BtnPlay_pressed"] -[connection signal="pressed" from="Main/Tabs/Game/CurrentInstall/Launch/BtnResume" to="." method="_on_BtnResume_pressed"] +[connection signal="pressed" from="Main/Tabs/Game/ActiveInstall/Build/GameDir" to="." method="_on_GameDir_pressed"] +[connection signal="pressed" from="Main/Tabs/Game/ActiveInstall/Build/UserDir" to="." method="_on_UserDir_pressed"] +[connection signal="pressed" from="Main/Tabs/Game/ActiveInstall/Launch/BtnPlay" to="." method="_on_BtnPlay_pressed"] +[connection signal="pressed" from="Main/Tabs/Game/ActiveInstall/Launch/BtnResume" to="." method="_on_BtnResume_pressed"] [connection signal="item_selected" from="Main/Tabs/Mods/HBox/Installed/InstalledList" to="Main/Tabs/Mods" method="_on_InstalledList_item_selected"] [connection signal="multi_selected" from="Main/Tabs/Mods/HBox/Installed/InstalledList" to="Main/Tabs/Mods" method="_on_InstalledList_multi_selected"] [connection signal="toggled" from="Main/Tabs/Mods/HBox/Installed/ShowStock" to="Main/Tabs/Mods" method="_on_ShowStock_toggled"] diff --git a/scripts/Catapult.gd b/scripts/Catapult.gd index 287db340..f4e7182c 100644 --- a/scripts/Catapult.gd +++ b/scripts/Catapult.gd @@ -6,7 +6,6 @@ onready var _log = $Main/Log onready var _game_info = $Main/GameInfo onready var _game_desc = $Main/GameInfo/Description onready var _mod_info = $Main/Tabs/Mods/ModInfo -onready var _inst_probe = $InstallProbe onready var _tabs = $Main/Tabs onready var _mods = $Mods onready var _releases = $Releases @@ -15,15 +14,16 @@ onready var _btn_install = $Main/Tabs/Game/BtnInstall onready var _btn_refresh = $Main/Tabs/Game/Builds/BtnRefresh onready var _changelog = $Main/Tabs/Game/ChangelogDialog onready var _lbl_changelog = $Main/Tabs/Game/Channel/HBox/ChangelogLink -onready var _btn_game_dir = $Main/Tabs/Game/CurrentInstall/Build/GameDir -onready var _btn_user_dir = $Main/Tabs/Game/CurrentInstall/Build/UserDir -onready var _btn_play = $Main/Tabs/Game/CurrentInstall/Launch/BtnPlay -onready var _btn_resume = $Main/Tabs/Game/CurrentInstall/Launch/BtnResume +onready var _btn_game_dir = $Main/Tabs/Game/ActiveInstall/Build/GameDir +onready var _btn_user_dir = $Main/Tabs/Game/ActiveInstall/Build/UserDir +onready var _btn_play = $Main/Tabs/Game/ActiveInstall/Launch/BtnPlay +onready var _btn_resume = $Main/Tabs/Game/ActiveInstall/Launch/BtnResume onready var _lst_builds = $Main/Tabs/Game/Builds/BuildsList onready var _lst_games = $Main/GameChoice/GamesList onready var _rbtn_stable = $Main/Tabs/Game/Channel/Group/RBtnStable onready var _rbtn_exper = $Main/Tabs/Game/Channel/Group/RBtnExperimental -onready var _lbl_build = $Main/Tabs/Game/CurrentInstall/Build/Name +onready var _lbl_build = $Main/Tabs/Game/ActiveInstall/Build/Name +onready var _lst_installs = $Main/Tabs/Game/GameInstalls/HBox/InstallsList var _disable_savestate := {} @@ -148,13 +148,6 @@ func _smart_reenable_controls(group_name: String) -> void: _disable_savestate.erase(group_name) -func _is_selected_game_installed() -> bool: - - var info = _inst_probe.probe_installed_games() - var game = Settings.read("game") - return (game in info) - - func _on_ui_scale_changed(new_scale: float) -> void: _scale_control_min_sizes(new_scale) @@ -271,12 +264,12 @@ func _on_BtnRefresh_pressed() -> void: func _on_BuildsList_item_selected(index: int) -> void: - var info = _inst_probe.probe_installed_games() + var info = Paths.installs_summary var game = Settings.read("game") if (not Settings.read("update_to_same_build_allowed")) \ and (game in info) \ - and (info[game]["name"] == _releases.releases[_get_release_key()][index]["name"]): + and (_releases.releases[_get_release_key()][index]["name"] in info[game]): _btn_install.disabled = true else: _btn_install.disabled = false @@ -286,8 +279,8 @@ func _on_BtnInstall_pressed() -> void: var index = _lst_builds.selected var release = _releases.releases[_get_release_key()][index] - var update = Settings.read("game") in _inst_probe.probe_installed_games() - _installer.install_release(release, Settings.read("game"), update) +# var update = Settings.read("game") in _inst_probe.probe_installed_games() + _installer.install_release(release, Settings.read("game"), "") func _get_release_key() -> String: @@ -412,34 +405,45 @@ func _start_game(world := "") -> void: func _refresh_currently_installed() -> void: - var info = _inst_probe.probe_installed_games() - var game = Settings.read("game") +# var info = Paths.installs_summary +# var game = Settings.read("game") var releases = _releases.releases[_get_release_key()] - - if _is_selected_game_installed(): - _lbl_build.text = info[game]["name"] - _btn_install.text = tr("btn_update") + + _lst_installs.clear() + var game = Settings.read("game") + var installs = Paths.installs_summary + var active_name = Settings.read("active_install_" + game) + if game in installs: + for name in installs[game]: + _lst_installs.add_item(name) + var curr_idx = _lst_installs.get_item_count() - 1 + _lst_installs.set_item_tooltip(curr_idx, "Location: " + installs[game][name]) +# if name == active_name: +# _lst_installs.set_item_custom_fg_color(curr_idx, Color(0, 0.8, 0)) + + if game in installs: + _lbl_build.text = active_name _btn_play.disabled = false _btn_resume.disabled = not (Directory.new().file_exists(Paths.config.plus_file("lastworld.json"))) _btn_game_dir.visible = true _btn_user_dir.visible = true if (_lst_builds.selected != -1) and (_lst_builds.selected < len(releases)): if not Settings.read("update_to_same_build_allowed"): - _btn_install.disabled = (releases[_lst_builds.selected]["name"] == info[game]["name"]) + _btn_install.disabled = (releases[_lst_builds.selected]["name"] == active_name) else: _btn_install.disabled = true - + else: _lbl_build.text = tr("lbl_none") - _btn_install.text = tr("btn_install") _btn_install.disabled = false _btn_play.disabled = true _btn_resume.disabled = true _btn_game_dir.visible = false _btn_user_dir.visible = false - + for i in [1, 2, 3, 4]: - _tabs.set_tab_disabled(i, not _is_selected_game_installed()) + _tabs.set_tab_disabled(i, not game in installs) + func _on_InfoIcon_gui_input(event: InputEvent) -> void: diff --git a/scripts/InstallProbe.gd b/scripts/InstallProbe.gd deleted file mode 100644 index 9b8d6e58..00000000 --- a/scripts/InstallProbe.gd +++ /dev/null @@ -1,49 +0,0 @@ -extends Node - - -const _INFO_FILENAME = "catapult_install_info.json" - - -func create_info_file(location: String, name: String) -> void: - - var info = {"name": name} - var path = location + "/" + _INFO_FILENAME - var f = File.new() - if (f.open(path, File.WRITE) == 0): - f.store_string(JSON.print(info, " ")) - f.close() - else: - Status.post(tr("msg_cannot_create_install_info") % path, Enums.MSG_ERROR) - - -func _load_json(path: String) -> Dictionary: - - var f = File.new() - var result: JSONParseResult - - f.open(path, File.READ) - result = JSON.parse(f.get_as_text()) - f.close() - - if result.error: - Status.post(tr("msg_cannot_parse_install_info") % path, Enums.MSG_ERROR) - return {} - - return result.result - - - -func probe_installed_games() -> Dictionary: - - var result = {} - var d = Directory.new() - - var path_dda = Paths.own_dir + "/dda/current/" + _INFO_FILENAME - if d.file_exists(path_dda): - result["dda"] = _load_json(path_dda) - - var path_bn = Paths.own_dir + "/bn/current/" + _INFO_FILENAME - if d.file_exists(path_bn): - result["bn"] = _load_json(path_bn) - - return result diff --git a/scripts/ReleaseInstaller.gd b/scripts/ReleaseInstaller.gd index de377763..58f3d946 100644 --- a/scripts/ReleaseInstaller.gd +++ b/scripts/ReleaseInstaller.gd @@ -4,14 +4,12 @@ extends Node signal installation_started signal installation_finished -onready var _probe := $"../InstallProbe" - -func install_release(release_info: Dictionary, game: String, update: bool = false) -> void: +func install_release(release_info: Dictionary, game: String, update_in: String = "") -> void: emit_signal("installation_started") - if update: + if update_in: Status.post(tr("msg_updating_game") % release_info["name"]) else: Status.post(tr("msg_installing_game") % release_info["name"]) @@ -35,16 +33,20 @@ func install_release(release_info: Dictionary, game: String, update: bool = fals "Windows": extracted_root = Paths.tmp_dir - _probe.create_info_file(extracted_root, release_info["name"]) + Helpers.create_info_file(extracted_root, release_info["name"]) - if update: - FS.rm_dir(Paths.game_dir) + var target_dir: String + if update_in: + target_dir = update_in + FS.rm_dir(target_dir) yield(FS, "rm_dir_done") + else: + target_dir = Paths.next_install_dir - FS.move_dir(extracted_root, Paths.game_dir) + FS.move_dir(extracted_root, target_dir) yield(FS, "move_dir_done") - if update: + if update_in: Status.post(tr("msg_game_updated")) else: Status.post(tr("msg_game_installed")) diff --git a/scripts/helpers.gd b/scripts/helpers.gd index f6d7ab08..5588d433 100644 --- a/scripts/helpers.gd +++ b/scripts/helpers.gd @@ -1,6 +1,21 @@ extends Node +const INFO_FILENAME := "catapult_install_info.json" + + +func create_info_file(location: String, name: String) -> void: + + var info = {"name": name} + var path = location + "/" + INFO_FILENAME + var f = File.new() + if (f.open(path, File.WRITE) == 0): + f.store_string(JSON.print(info, " ")) + f.close() + else: + Status.post(tr("msg_cannot_create_install_info") % path, Enums.MSG_ERROR) + + func get_all_nodes_within(n: Node) -> Array: var result = [] diff --git a/scripts/path_helper.gd b/scripts/path_helper.gd index 5bd8f5ee..7ccd4f6c 100644 --- a/scripts/path_helper.gd +++ b/scripts/path_helper.gd @@ -5,7 +5,9 @@ extends Node signal status_message var own_dir: String setget , _get_own_dir +var installs_summary: Dictionary setget , _get_installs_summary var game_dir: String setget , _get_game_dir +var next_install_dir: String setget , _get_next_install_dir var userdata: String setget , _get_userdata_dir var config: String setget , _get_config_dir var savegames: String setget , _get_savegame_dir @@ -24,15 +26,79 @@ var tmp_dir: String setget , _get_tmp_dir var utils_dir: String setget , _get_utils_dir var save_backups: String setget , _get_save_backups_dir +var _last_active_install_name := "" +var _last_active_install_dir := "" + func _get_own_dir() -> String: return OS.get_executable_path().get_base_dir() +func _get_installs_summary() -> Dictionary: + + var result = {} + var d = Directory.new() + + for game in ["dda", "bn"]: + var installs = {} + var base_dir = Paths.own_dir.plus_file(game) + for subdir in FS.list_dir(base_dir): + var info_file = base_dir.plus_file(subdir).plus_file(Helpers.INFO_FILENAME) + if d.file_exists(info_file): + var info = Helpers.load_json_file(info_file) + installs[info["name"]] = base_dir.plus_file(subdir) + if not installs.empty(): + result[game] = installs + + # Ensure that some installation of the game is set as active + var game = Settings.read("game") + var active_name = Settings.read("active_install_" + game) + if game in result: + if (active_name == "") or (not active_name in result[game]): + Settings.store("active_install_" + game, result[game].keys()[0]) + + return result + + func _get_game_dir() -> String: + + var active_name = Settings.read("active_install_" + Settings.read("game")) + + if active_name == "": + return _get_next_install_dir() + elif active_name == _last_active_install_name: + return _last_active_install_dir + else: + return _find_active_game_dir() + + +func _find_active_game_dir() -> String: + + var d = Directory.new() + var base_dir = _get_own_dir().plus_file(Settings.read("game")) + for subdir in FS.list_dir(base_dir): + var curr_dir = base_dir.plus_file(subdir) + var info_file = curr_dir.plus_file("catapult_install_info.json") + if d.file_exists(info_file): + var info = Helpers.load_json_file(info_file) + if ("name" in info) and (info["name"] == Settings.read("active_install_" + Settings.read("game"))): + _last_active_install_dir = curr_dir + return curr_dir + + return "" + + +func _get_next_install_dir() -> String: + # Finds a suitable directory name for a new game installation in the + # multi-install system. The names follow the pattern "game0, game1, ..." - return _get_own_dir().plus_file(Settings.read("game")).plus_file("current") + var base_dir := _get_own_dir().plus_file(Settings.read("game")) + var dir_number := 0 + var d := Directory.new() + while d.dir_exists(base_dir.plus_file("game" + str(dir_number))): + dir_number += 1 + return base_dir.plus_file("game" + str(dir_number)) func _get_userdata_dir() -> String: diff --git a/scripts/settings_manager.gd b/scripts/settings_manager.gd index ad63adfe..d04762ba 100644 --- a/scripts/settings_manager.gd +++ b/scripts/settings_manager.gd @@ -6,8 +6,10 @@ const _SETTINGS_FILENAME = "catapult_settings.json" const _HARDCODED_DEFAULTS = { "game": "dda", "channel": "stable", # Currently used only for DDA. + "active_install_dda": "Cataclysm-DDA experimental build 2022-07-26-0606", + "active_install_bn": "", "launcher_locale": "", - "launcher_theme": "Godot_3.theme", + "launcher_theme": "Godot_3.res", "window_state": {}, "print_tips_of_the_day": true, "update_to_same_build_allowed": false,