diff --git a/extension/deps/openvic-simulation b/extension/deps/openvic-simulation index 053ba2de..3768b5e6 160000 --- a/extension/deps/openvic-simulation +++ b/extension/deps/openvic-simulation @@ -1 +1 @@ -Subproject commit 053ba2de4ff39ca3fc90a809d80aad180cf87b5a +Subproject commit 3768b5e62ea92e71d0df7b68e304aff5fd282a3e diff --git a/extension/doc_classes/GameSingleton.xml b/extension/doc_classes/GameSingleton.xml index ab927635..d1eb0bc4 100644 --- a/extension/doc_classes/GameSingleton.xml +++ b/extension/doc_classes/GameSingleton.xml @@ -84,6 +84,12 @@ Returns the localization key [String] of the mapmode with the specified [param index]. + + + + Returns an array of [Dictionary] containing information about available and loaded mods. + + @@ -133,6 +139,8 @@ + + Load compatibility mode text defines, localization string and map and flag images. Returns [code]FAILED[/code] if there are any problems when loading all this data, otherwise returns [code]OK[/code]. @@ -151,12 +159,11 @@ Searches for the base game's install path, checking the [param hint_path] if it's provided as well as the Steam install folder as identified by the [code]"libraryfolders.vdf"[/code] file. This function will return an empty [String] should it fail to find the base game's install path. - + - - + - Set the dataloading roots to those provided in [param file_paths], ignoring the filepaths in [param replace_paths] in favor of mods, which should contain full filepaths to the base game's installation and to any mods that are to be loaded on top of it. Returns [code]FAILED[/code] if there are any problems when setting the dataloading roots, otherwise returns [code]OK[/code]. + Sets the root dataloader path to [param base_path]. Returns [code]FAILED[/code] if the path is invalid, otherwise returns [code]OK[/code]. diff --git a/extension/src/openvic-extension/singletons/GameSingleton.cpp b/extension/src/openvic-extension/singletons/GameSingleton.cpp index 18abb2f0..b7554550 100644 --- a/extension/src/openvic-extension/singletons/GameSingleton.cpp +++ b/extension/src/openvic-extension/singletons/GameSingleton.cpp @@ -1,10 +1,17 @@ #include "GameSingleton.hpp" +#include #include +#include #include +#include +#include +#include +#include #include +#include #include #include "openvic-extension/singletons/AssetManager.hpp" @@ -34,13 +41,14 @@ StringName const& GameSingleton::_signal_mapmode_changed() { void GameSingleton::_bind_methods() { OV_BIND_SMETHOD(setup_logger); - OV_BIND_METHOD(GameSingleton::load_defines_compatibility_mode); - OV_BIND_METHOD(GameSingleton::set_compatibility_mode_roots, { "file_paths", "replace_paths" }, DEFVAL(PackedStringArray{})); + OV_BIND_METHOD(GameSingleton::set_compatibility_mode_base_path, { "base_path" }); + OV_BIND_METHOD(GameSingleton::load_defines_compatibility_mode, { "base_path", "mods " }); OV_BIND_SMETHOD(search_for_game_path, { "hint_path" }, DEFVAL(String {})); OV_BIND_METHOD(GameSingleton::lookup_file_path, { "path" }); OV_BIND_METHOD(GameSingleton::get_bookmark_info); + OV_BIND_METHOD(GameSingleton::get_mod_info); OV_BIND_METHOD(GameSingleton::setup_game, { "bookmark_index" }); OV_BIND_METHOD(GameSingleton::start_game_session); @@ -108,6 +116,38 @@ void GameSingleton::setup_logger() { }); } +TypedArray GameSingleton::get_mod_info() const { + static const StringName mod_info_identifier_key = "mod_identifier"; + static const StringName mod_info_dependencies_key = "mod_dependencies"; + static const StringName mod_info_loaded_key = "mod_loaded"; + + TypedArray results; + + for (Mod const& mod : game_manager.get_mod_manager().get_mods()) { + Dictionary mod_info_dictionary; + + mod_info_dictionary[mod_info_identifier_key] = Utilities::std_to_godot_string(mod.get_identifier()); + + PackedStringArray dependencies; + for (std::string_view dep_id : mod.get_dependencies()) { + dependencies.push_back(Utilities::std_to_godot_string(dep_id)); + } + mod_info_dictionary[mod_info_dependencies_key] = std::move(dependencies); + + #define loaded_mods game_manager.get_mod_manager().get_loaded_mods() + if (std::find(loaded_mods.begin(), loaded_mods.end(), &mod) != loaded_mods.end()) { + mod_info_dictionary[mod_info_loaded_key] = true; + } else { + mod_info_dictionary[mod_info_loaded_key] = false; + } + #undef loaded_mods + + results.push_back(std::move(mod_info_dictionary)); + } + + return results; +} + TypedArray GameSingleton::get_bookmark_info() const { static const StringName bookmark_info_name_key = "bookmark_name"; static const StringName bookmark_info_date_key = "bookmark_date"; @@ -610,29 +650,36 @@ Error GameSingleton::_load_flag_sheet() { return ret; } -Error GameSingleton::set_compatibility_mode_roots( - PackedStringArray const& file_paths, godot::PackedStringArray const& replace_paths -) { - Dataloader::path_vector_t roots; - roots.reserve(file_paths.size()); - for (String const& path : file_paths) { - roots.emplace_back(Utilities::godot_to_std_string(path)); - } - - Dataloader::path_vector_t replace; - replace.reserve(replace_paths.size()); - for (String const& path : replace_paths) { - replace.emplace_back(Utilities::godot_to_std_string(path)); +Error GameSingleton::set_compatibility_mode_base_path(String const& base_path) { + Dataloader::path_vector_t roots { Utilities::godot_to_std_string(base_path) }, replace_paths; + if (!game_manager.set_base_path(roots)) { + UtilityFunctions::push_error("Failed to set base path!"); + return FAILED; } - - ERR_FAIL_COND_V_MSG(!game_manager.set_roots(roots, replace), FAILED, "Failed to set dataloader roots!"); return OK; } -Error GameSingleton::load_defines_compatibility_mode() { +Error GameSingleton::load_defines_compatibility_mode(String const& base_path, PackedStringArray const& mods) { Error err = OK; - auto add_message = std::bind_front(&LoadLocalisation::add_message, LoadLocalisation::get_singleton()); + if (!game_manager.load_mod_descriptors()) { + UtilityFunctions::push_error("Failed to load mod descriptors!"); + err = FAILED; + } + + Dataloader::path_vector_t roots { Utilities::godot_to_std_string(base_path) }, replace_paths; + std::vector converted_mods; + converted_mods.reserve(mods.size()); + for (String const& mod : mods) { + converted_mods.push_back(Utilities::godot_to_std_string(mod)); + } + + if (!game_manager.load_mods(roots, replace_paths, converted_mods)) { + UtilityFunctions::push_error("Failed to load mods!"); + err = FAILED; + } + + auto add_message = std::bind_front(&LoadLocalisation::add_message, LoadLocalisation::get_singleton()); if (!game_manager.load_definitions(add_message)) { UtilityFunctions::push_error("Failed to load defines!"); err = FAILED; diff --git a/extension/src/openvic-extension/singletons/GameSingleton.hpp b/extension/src/openvic-extension/singletons/GameSingleton.hpp index 613458cc..1fddeb4c 100644 --- a/extension/src/openvic-extension/singletons/GameSingleton.hpp +++ b/extension/src/openvic-extension/singletons/GameSingleton.hpp @@ -2,6 +2,9 @@ #include #include +#include +#include +#include #include #include @@ -70,15 +73,14 @@ namespace OpenVic { /* Load the game's defines in compatibility mode from the filepath * pointing to the defines folder. */ - godot::Error set_compatibility_mode_roots( - godot::PackedStringArray const& file_paths, godot::PackedStringArray const& replace_paths = {} - ); - godot::Error load_defines_compatibility_mode(); + godot::Error set_compatibility_mode_base_path(godot::String const& base_path); + godot::Error load_defines_compatibility_mode(godot::String const& base_path, godot::PackedStringArray const& mods); static godot::String search_for_game_path(godot::String const& hint_path = {}); godot::String lookup_file_path(godot::String const& path) const; godot::TypedArray get_bookmark_info() const; + godot::TypedArray get_mod_info() const; /* Post-load/restart game setup - reset the game to post-load state and load the specified bookmark. */ godot::Error setup_game(int32_t bookmark_index); diff --git a/game/src/Systems/Startup/GameStart.gd b/game/src/Systems/Startup/GameStart.gd index 8be951cd..e8272756 100644 --- a/game/src/Systems/Startup/GameStart.gd +++ b/game/src/Systems/Startup/GameStart.gd @@ -13,7 +13,8 @@ const GameMenuScene := preload("res://src/UI/GameMenu/GameMenu/GameMenu.tscn") @export var setting_name : String = "base_defines_path" var _settings_base_path : String = "" -var _compatibility_path_list : PackedStringArray = [] +var actual_base_path : String = "" +var mod_names : PackedStringArray = [] func _enter_tree() -> void: Keychain.keep_binding_check = func(action_name : StringName) -> bool: @@ -77,13 +78,9 @@ func _save_setting(file : ConfigFile) -> void: file.set_value(section_name, setting_name, _settings_base_path) func _setup_compatibility_mode_paths() -> void: - # To test mods, set your base path to Victoria II and then pass mods in reverse order with --mod="mod" for each mod. - var arg_base_path : String = ArgumentParser.get_argument(&"base-path", "") var arg_search_path : String = ArgumentParser.get_argument(&"search-path", "") - var actual_base_path : String = "" - if arg_base_path: if arg_search_path: push_warning("Exact base path and search base path arguments both used:\nBase: ", arg_base_path, "\nSearch: ", arg_search_path) @@ -127,21 +124,22 @@ func _setup_compatibility_mode_paths() -> void: # Save the path found in the search Events.Options.save_settings_to_file() - _compatibility_path_list = [actual_base_path] - # Add mod paths - var settings_mod_names : PackedStringArray = ArgumentParser.get_argument(&"mod", "") - for mod_name : String in settings_mod_names: - _compatibility_path_list.push_back(actual_base_path + "/mod/" + mod_name) + var mod_status_file := ConfigFile.new() + mod_status_file.load("user://mods.cfg") + mod_names = mod_status_file.get_value("mods", "load_list", []) + for mod in ArgumentParser.get_argument(&"mod", ""): + if mod not in mod_names and mod != "": + mod_names.push_back(mod) func _load_compatibility_mode() -> void: - if GameSingleton.set_compatibility_mode_roots(_compatibility_path_list) != OK: + if GameSingleton.set_compatibility_mode_base_path(actual_base_path) != OK: push_error("Errors setting game roots!") CursorManager.initial_cursor_setup() setup_title_theme() - if GameSingleton.load_defines_compatibility_mode() != OK: + if GameSingleton.load_defines_compatibility_mode(actual_base_path, mod_names) != OK: push_error("Errors loading game defines!") SoundSingleton.load_sounds() diff --git a/game/src/UI/GameMenu/GameMenu/GameMenu.gd b/game/src/UI/GameMenu/GameMenu/GameMenu.gd index cbb60e53..4bebb6ff 100644 --- a/game/src/UI/GameMenu/GameMenu/GameMenu.gd +++ b/game/src/UI/GameMenu/GameMenu/GameMenu.gd @@ -5,6 +5,7 @@ extends Control @export var _multiplayer_menu : Control @export var _lobby_menu : Control @export var _credits_menu : Control +@export var _mod_menu : Control # REQUIREMENTS # * SS-10 @@ -51,3 +52,13 @@ func _on_multiplayer_menu_back_button_pressed() -> void: func _on_main_menu_multiplayer_button_pressed() -> void: _multiplayer_menu.show() _main_menu.hide() + + +func _on_main_menu_mod_button_pressed() -> void: + _mod_menu.show() + _main_menu.hide() + + +func _on_mod_menu_back_button_pressed() -> void: + _mod_menu.hide() + _main_menu.show() diff --git a/game/src/UI/GameMenu/GameMenu/GameMenu.tscn b/game/src/UI/GameMenu/GameMenu/GameMenu.tscn index c9403720..726315f7 100644 --- a/game/src/UI/GameMenu/GameMenu/GameMenu.tscn +++ b/game/src/UI/GameMenu/GameMenu/GameMenu.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=8 format=3 uid="uid://o4u142w4qkln"] +[gd_scene load_steps=9 format=3 uid="uid://o4u142w4qkln"] [ext_resource type="Script" uid="uid://bf36b41ip0jyu" path="res://src/UI/GameMenu/GameMenu/GameMenu.gd" id="1_cafwe"] [ext_resource type="PackedScene" uid="uid://bp5n3mlu45ygw" path="res://src/UI/GameMenu/MainMenu/MainMenu.tscn" id="2_2jbkh"] @@ -7,8 +7,9 @@ [ext_resource type="PackedScene" uid="uid://do60kx0d3nrh4" path="res://src/UI/GameMenu/LobbyMenu/LobbyMenu.tscn" id="4_nofk1"] [ext_resource type="PackedScene" uid="uid://btri1i0hkhdsh" path="res://src/UI/GameMenu/MultiplayerMenu/MultiplayerMenu.tscn" id="4_s7nkl"] [ext_resource type="PackedScene" uid="uid://cvl76duuym1wq" path="res://src/UI/Shared/MusicMenu/MusicMenu.tscn" id="6_lts1m"] +[ext_resource type="PackedScene" uid="uid://bh7otkxuf17sg" path="res://src/UI/GameMenu/ModMenu/ModMenu.tscn" id="7_mu3ov"] -[node name="GameMenu" type="Control" node_paths=PackedStringArray("_main_menu", "_options_menu", "_multiplayer_menu", "_lobby_menu", "_credits_menu")] +[node name="GameMenu" type="Control" node_paths=PackedStringArray("_main_menu", "_options_menu", "_multiplayer_menu", "_lobby_menu", "_credits_menu", "_mod_menu")] layout_mode = 3 anchors_preset = 15 anchor_right = 1.0 @@ -22,6 +23,7 @@ _options_menu = NodePath("OptionsMenu") _multiplayer_menu = NodePath("MultiplayerMenu") _lobby_menu = NodePath("LobbyMenu") _credits_menu = NodePath("CreditsMenu") +_mod_menu = NodePath("ModMenu") [node name="MainMenu" parent="." instance=ExtResource("2_2jbkh")] layout_mode = 1 @@ -43,6 +45,10 @@ layout_mode = 1 visible = false layout_mode = 1 +[node name="ModMenu" parent="." instance=ExtResource("7_mu3ov")] +visible = false +layout_mode = 1 + [node name="MusicPlayer" parent="." instance=ExtResource("6_lts1m")] layout_mode = 1 anchors_preset = 1 @@ -53,6 +59,7 @@ offset_right = -34.0 grow_horizontal = 0 [connection signal="credits_button_pressed" from="MainMenu" to="." method="_on_main_menu_credits_button_pressed"] +[connection signal="mod_button_pressed" from="MainMenu" to="." method="_on_main_menu_mod_button_pressed"] [connection signal="multiplayer_button_pressed" from="MainMenu" to="." method="_on_main_menu_multiplayer_button_pressed"] [connection signal="new_game_button_pressed" from="MainMenu" to="." method="_on_main_menu_new_game_button_pressed"] [connection signal="options_button_pressed" from="MainMenu" to="." method="_on_main_menu_options_button_pressed"] @@ -60,3 +67,4 @@ grow_horizontal = 0 [connection signal="back_button_pressed" from="MultiplayerMenu" to="." method="_on_multiplayer_menu_back_button_pressed"] [connection signal="back_button_pressed" from="LobbyMenu" to="." method="_on_lobby_menu_back_button_pressed"] [connection signal="back_button_pressed" from="CreditsMenu" to="." method="_on_credits_back_button_pressed"] +[connection signal="back_button_pressed" from="ModMenu" to="." method="_on_mod_menu_back_button_pressed"] diff --git a/game/src/UI/GameMenu/MainMenu/MainMenu.gd b/game/src/UI/GameMenu/MainMenu/MainMenu.gd index b5983bc9..b8047086 100644 --- a/game/src/UI/GameMenu/MainMenu/MainMenu.gd +++ b/game/src/UI/GameMenu/MainMenu/MainMenu.gd @@ -1,10 +1,11 @@ extends Control -signal options_button_pressed signal new_game_button_pressed -signal credits_button_pressed -signal multiplayer_button_pressed signal continue_button_pressed +signal multiplayer_button_pressed +signal mod_button_pressed +signal options_button_pressed +signal credits_button_pressed @export var _new_game_button : BaseButton @@ -18,19 +19,18 @@ func _ready() -> void: # * SS-14 # * UIFUN-32 func _on_new_game_button_pressed() -> void: - print("Start a new game!") new_game_button_pressed.emit() func _on_continue_button_pressed() -> void: - print("Continue last game!") continue_button_pressed.emit() func _on_multi_player_button_pressed() -> void: - print("Have fun with friends!") multiplayer_button_pressed.emit() +func _on_mod_button_pressed(): + mod_button_pressed.emit() + func _on_options_button_pressed() -> void: - print("Check out some options!") options_button_pressed.emit() # REQUIREMENTS diff --git a/game/src/UI/GameMenu/MainMenu/MainMenu.tscn b/game/src/UI/GameMenu/MainMenu/MainMenu.tscn index 2168e273..84798e9d 100644 --- a/game/src/UI/GameMenu/MainMenu/MainMenu.tscn +++ b/game/src/UI/GameMenu/MainMenu/MainMenu.tscn @@ -54,11 +54,10 @@ alignment = 1 editor_description = "UI-26" layout_mode = 2 size_flags_horizontal = 3 -focus_neighbor_left = NodePath("../ExitButton") -focus_neighbor_top = NodePath("../ExitButton") +focus_neighbor_left = NodePath("../../../BottomMargin/HBoxContainer/LocaleButton") focus_neighbor_right = NodePath("../ContinueButton") focus_next = NodePath("../ContinueButton") -focus_previous = NodePath("../ExitButton") +focus_previous = NodePath("../../../BottomMargin/HBoxContainer/LocaleButton") theme_type_variation = &"TitleButton" text = "MAINMENU_NEW_GAME" clip_text = true @@ -80,35 +79,34 @@ editor_description = "UI-27" layout_mode = 2 size_flags_horizontal = 3 focus_neighbor_left = NodePath("../ContinueButton") -focus_neighbor_right = NodePath("../OptionsButton") -focus_next = NodePath("../OptionsButton") +focus_neighbor_right = NodePath("../ModButton") +focus_next = NodePath("../ModButton") focus_previous = NodePath("../ContinueButton") theme_type_variation = &"TitleButton" text = "MAINMENU_MULTIPLAYER" clip_text = true -[node name="OptionsButton" type="Button" parent="MenuPanel/MenuList/ButtonListMargin/ButtonList"] -editor_description = "UI-5" +[node name="ModButton" type="Button" parent="MenuPanel/MenuList/ButtonListMargin/ButtonList"] layout_mode = 2 size_flags_horizontal = 3 focus_neighbor_left = NodePath("../MultiplayerButton") -focus_neighbor_right = NodePath("../CreditsButton") -focus_next = NodePath("../CreditsButton") +focus_neighbor_right = NodePath("../OptionsButton") +focus_next = NodePath("../OptionsButton") focus_previous = NodePath("../MultiplayerButton") theme_type_variation = &"TitleButton" -text = "MAINMENU_OPTIONS" +text = "MAINMENU_MODS" clip_text = true -[node name="CreditsButton" type="Button" parent="MenuPanel/MenuList/ButtonListMargin/ButtonList"] -editor_description = "UI-32" +[node name="OptionsButton" type="Button" parent="MenuPanel/MenuList/ButtonListMargin/ButtonList"] +editor_description = "UI-5" layout_mode = 2 size_flags_horizontal = 3 -focus_neighbor_left = NodePath("../OptionsButton") +focus_neighbor_left = NodePath("../ModButton") focus_neighbor_right = NodePath("../ExitButton") focus_next = NodePath("../ExitButton") -focus_previous = NodePath("../OptionsButton") +focus_previous = NodePath("../ModButton") theme_type_variation = &"TitleButton" -text = "MAINMENU_CREDITS" +text = "MAINMENU_OPTIONS" clip_text = true [node name="ExitButton" type="Button" parent="MenuPanel/MenuList/ButtonListMargin/ButtonList"] @@ -116,8 +114,8 @@ editor_description = "UI-3" layout_mode = 2 size_flags_horizontal = 3 focus_neighbor_left = NodePath("../OptionsButton") -focus_neighbor_right = NodePath("../NewGameButton") -focus_next = NodePath("../NewGameButton") +focus_neighbor_right = NodePath("../../../BottomMargin/HBoxContainer/CreditsButton") +focus_next = NodePath("../../../BottomMargin/HBoxContainer/CreditsButton") focus_previous = NodePath("../OptionsButton") theme_type_variation = &"TitleButton" text = "MAINMENU_EXIT" @@ -132,13 +130,37 @@ size_flags_stretch_ratio = 0.35 layout_mode = 2 theme_type_variation = &"BottomMargin" -[node name="ReleaseInfoBox" parent="MenuPanel/MenuList/BottomMargin" instance=ExtResource("3_km0er")] +[node name="HBoxContainer" type="HBoxContainer" parent="MenuPanel/MenuList/BottomMargin"] +layout_mode = 2 +theme_override_constants/separation = 8 + +[node name="ReleaseInfoBox" parent="MenuPanel/MenuList/BottomMargin/HBoxContainer" instance=ExtResource("3_km0er")] +layout_mode = 2 + +[node name="Spacer" type="Control" parent="MenuPanel/MenuList/BottomMargin/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="CreditsButton" type="Button" parent="MenuPanel/MenuList/BottomMargin/HBoxContainer"] +editor_description = "UI-32" +custom_minimum_size = Vector2(160, 0) layout_mode = 2 +focus_neighbor_left = NodePath("../../../ButtonListMargin/ButtonList/ExitButton") +focus_neighbor_right = NodePath("../LocaleButton") +focus_next = NodePath("../LocaleButton") +focus_previous = NodePath("../../../ButtonListMargin/ButtonList/ExitButton") +theme_type_variation = &"TitleButton" +text = "MAINMENU_CREDITS" +clip_text = true -[node name="LocaleButton" parent="MenuPanel/MenuList/BottomMargin" instance=ExtResource("3_amonp")] +[node name="LocaleButton" parent="MenuPanel/MenuList/BottomMargin/HBoxContainer" instance=ExtResource("3_amonp")] editor_description = "SS-87" layout_mode = 2 size_flags_horizontal = 8 +focus_neighbor_left = NodePath("../CreditsButton") +focus_neighbor_right = NodePath("../../../ButtonListMargin/ButtonList/NewGameButton") +focus_next = NodePath("../../../ButtonListMargin/ButtonList/NewGameButton") +focus_previous = NodePath("../CreditsButton") alignment = 0 text_overrun_behavior = 4 @@ -146,6 +168,7 @@ text_overrun_behavior = 4 [connection signal="visibility_changed" from="MenuPanel/MenuList/ButtonListMargin/ButtonList/NewGameButton" to="." method="_on_new_game_button_visibility_changed"] [connection signal="pressed" from="MenuPanel/MenuList/ButtonListMargin/ButtonList/ContinueButton" to="." method="_on_continue_button_pressed"] [connection signal="pressed" from="MenuPanel/MenuList/ButtonListMargin/ButtonList/MultiplayerButton" to="." method="_on_multi_player_button_pressed"] +[connection signal="pressed" from="MenuPanel/MenuList/ButtonListMargin/ButtonList/ModButton" to="." method="_on_mod_button_pressed"] [connection signal="pressed" from="MenuPanel/MenuList/ButtonListMargin/ButtonList/OptionsButton" to="." method="_on_options_button_pressed"] -[connection signal="pressed" from="MenuPanel/MenuList/ButtonListMargin/ButtonList/CreditsButton" to="." method="_on_credits_button_pressed"] [connection signal="pressed" from="MenuPanel/MenuList/ButtonListMargin/ButtonList/ExitButton" to="." method="_on_exit_button_pressed"] +[connection signal="pressed" from="MenuPanel/MenuList/BottomMargin/HBoxContainer/CreditsButton" to="." method="_on_credits_button_pressed"] diff --git a/game/src/UI/GameMenu/ModMenu/ModMenu.gd b/game/src/UI/GameMenu/ModMenu/ModMenu.gd new file mode 100644 index 00000000..74f86dc5 --- /dev/null +++ b/game/src/UI/GameMenu/ModMenu/ModMenu.gd @@ -0,0 +1,96 @@ +extends Control + +signal back_button_pressed + +@export var mod_list_box : VBoxContainer + +var mod_info: Array[Dictionary] +var checkboxes = {} +var selected_mods : PackedStringArray = [] +var selected_and_required_mods : PackedStringArray = [] + +func _ready(): + mod_info = GameSingleton.get_mod_info() + var mod_status_file := ConfigFile.new() + mod_status_file.load("user://mods.cfg") + selected_mods = mod_status_file.get_value("mods", "load_list", []) + for mod in selected_mods: + _select_mod_dependencies(mod) + + for mod in mod_info: + var mod_name : String = mod["mod_identifier"] + var mod_loaded : bool = mod["mod_loaded"] + + var hbox : HBoxContainer = HBoxContainer.new() + hbox.name = mod_name + + var checkbox : CheckBox = CheckBox.new() + checkbox.text = mod_name + checkbox.button_pressed = mod_loaded + checkbox.disabled = mod_loaded and mod_name not in selected_mods + checkbox.toggled.connect(_on_mod_toggled.bind(mod_name)) + checkboxes[mod_name] = checkbox + hbox.add_child(checkbox) + + var status : Label = Label.new() + status.text = "Loaded" if mod_loaded else "" + hbox.add_child(status) + + mod_list_box.add_child(hbox) + +func _select_mod_dependencies(mod_name: String): + if mod_name not in selected_and_required_mods: + selected_and_required_mods.push_back(mod_name) + for dep in _get_mod_from_identifier(mod_name)["mod_dependencies"]: + if checkboxes.has(dep): + var dep_checkbox: CheckBox = checkboxes[dep] + dep_checkbox.set_pressed_no_signal(true) + dep_checkbox.disabled = true + dep_checkbox.tooltip_text = "This mod is a dependency of another mod, and cannot be disabled." + _select_mod_dependencies(dep) + +func _deselect_mod_dependencies(mod_name: String): + if not _is_dependency_required(mod_name): + if mod_name in selected_and_required_mods: + selected_and_required_mods.remove_at(selected_and_required_mods.find(mod_name)) + for dep in _get_mod_from_identifier(mod_name)["mod_dependencies"]: + if checkboxes.has(dep): + var dep_checkbox: CheckBox = checkboxes[dep] + dep_checkbox.set_pressed_no_signal(false) + dep_checkbox.disabled = false + dep_checkbox.tooltip_text = "" + _deselect_mod_dependencies(dep) + +func _get_mod_from_identifier(mod_name: String) -> Dictionary: + for mod in mod_info: + if mod["mod_identifier"] == mod_name: + return mod + return {"mod_dependencies":[]} + +func _is_dependency_required(dep_name: String) -> bool: + for mod_name in selected_mods: + var deps: PackedStringArray = _get_mod_from_identifier(mod_name)["mod_dependencies"] + if dep_name in deps: + return true + return false + +func _on_mod_toggled(checked: bool, mod_name: String) -> void: + if checked: + print("Selected Mod: " + mod_name) + selected_mods.push_back(mod_name) + _select_mod_dependencies(mod_name) + else: + print("Unselected Mod: " + mod_name) + selected_mods.remove_at(selected_mods.find(mod_name)) + _deselect_mod_dependencies(mod_name) + +func _on_save_button_pressed(): + var mod_status_file := ConfigFile.new() + mod_status_file.set_value("mods", "load_list", selected_mods) + mod_status_file.save("user://mods.cfg") + # reload game to apply changes + OS.set_restart_on_exit(true) + get_tree().quit() + +func _on_back_button_pressed(): + back_button_pressed.emit() diff --git a/game/src/UI/GameMenu/ModMenu/ModMenu.gd.uid b/game/src/UI/GameMenu/ModMenu/ModMenu.gd.uid new file mode 100644 index 00000000..aa880dd6 --- /dev/null +++ b/game/src/UI/GameMenu/ModMenu/ModMenu.gd.uid @@ -0,0 +1 @@ +uid://daf0wi7bbaf3n diff --git a/game/src/UI/GameMenu/ModMenu/ModMenu.tscn b/game/src/UI/GameMenu/ModMenu/ModMenu.tscn new file mode 100644 index 00000000..66edf2aa --- /dev/null +++ b/game/src/UI/GameMenu/ModMenu/ModMenu.tscn @@ -0,0 +1,69 @@ +[gd_scene load_steps=3 format=3 uid="uid://bh7otkxuf17sg"] + +[ext_resource type="Theme" uid="uid://fbxssqcg1s0m" path="res://assets/graphics/theme/options_menu.tres" id="1_8in6e"] +[ext_resource type="Script" uid="uid://daf0wi7bbaf3n" path="res://src/UI/GameMenu/ModMenu/ModMenu.gd" id="1_ahk3j"] + +[node name="ModMenu" type="PanelContainer" node_paths=PackedStringArray("mod_list_box")] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme = ExtResource("1_8in6e") +theme_type_variation = &"BackgroundPanel" +script = ExtResource("1_ahk3j") +mod_list_box = NodePath("Margin/PanelContainer/Margin/VBox/ModListBox") + +[node name="Margin" type="MarginContainer" parent="."] +layout_mode = 2 +theme_override_constants/margin_left = 256 +theme_override_constants/margin_top = 180 +theme_override_constants/margin_right = 256 +theme_override_constants/margin_bottom = 180 + +[node name="PanelContainer" type="PanelContainer" parent="Margin"] +layout_mode = 2 + +[node name="Margin" type="MarginContainer" parent="Margin/PanelContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 12 +theme_override_constants/margin_top = 8 +theme_override_constants/margin_right = 12 +theme_override_constants/margin_bottom = 10 + +[node name="VBox" type="VBoxContainer" parent="Margin/PanelContainer/Margin"] +layout_mode = 2 + +[node name="Label" type="Label" parent="Margin/PanelContainer/Margin/VBox"] +layout_mode = 2 +text = "Mod Menu" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="ModListBox" type="VBoxContainer" parent="Margin/PanelContainer/Margin/VBox"] +layout_mode = 2 + +[node name="Spacer" type="Control" parent="Margin/PanelContainer/Margin/VBox"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="HBox" type="HBoxContainer" parent="Margin/PanelContainer/Margin/VBox"] +layout_mode = 2 +alignment = 1 + +[node name="SaveButton" type="Button" parent="Margin/PanelContainer/Margin/VBox/HBox"] +layout_mode = 2 +size_flags_horizontal = 4 +text = " Save (Reloads Game)" + +[node name="Spacer" type="Control" parent="Margin/PanelContainer/Margin/VBox/HBox"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="BackButton" type="Button" parent="Margin/PanelContainer/Margin/VBox/HBox"] +layout_mode = 2 +size_flags_horizontal = 4 +text = " Back " + +[connection signal="pressed" from="Margin/PanelContainer/Margin/VBox/HBox/SaveButton" to="." method="_on_save_button_pressed"] +[connection signal="pressed" from="Margin/PanelContainer/Margin/VBox/HBox/BackButton" to="." method="_on_back_button_pressed"]