Skip to content

Commit 0484f2f

Browse files
committed
Add mod loading support
Supports command line Supports loading from `user://mods.cfg`
1 parent a5ddb8a commit 0484f2f

4 files changed

Lines changed: 83 additions & 35 deletions

File tree

extension/doc_classes/GameSingleton.xml

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,15 @@
8989
Returns the localization key [String] of the mapmode with the specified [param index].
9090
</description>
9191
</method>
92+
<method name="get_mod_info" qualifiers="const">
93+
<return type="Dictionary[]" />
94+
<description>
95+
Returns an [Array] of [Dictionary] containing information about available mods, with each [Dictionary] containing values for the following [StringName] keys:
96+
- [code]"identifier"[/code] - The mod's [String] name
97+
- [code]"dependencies"[/code] - A [PackedStringArray] of dependency mod names
98+
- [code]"is_loaded"[/code] - Is [code]true[/code] if the mod is a currently loaded mod, else [code]false[/code]
99+
</description>
100+
</method>
92101
<method name="get_province_colour_texture" qualifiers="const">
93102
<return type="ImageTexture" />
94103
<description>
@@ -153,8 +162,10 @@
153162
</method>
154163
<method name="load_defines_compatibility_mode">
155164
<return type="int" enum="Error" />
165+
<param index="0" name="mods" type="PackedStringArray" default="PackedStringArray()" />
156166
<description>
157-
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].
167+
Load compatibility mode text defines, localization string and map and flag images, loading data from [param mods] if not empty. Returns [code]FAILED[/code] if there are any problems when loading all this data, otherwise returns [code]OK[/code].
168+
See [method get_mod_info] for a valid list of values.
158169
</description>
159170
</method>
160171
<method name="lookup_file_path" qualifiers="const">
@@ -173,10 +184,9 @@
173184
</method>
174185
<method name="set_compatibility_mode_roots">
175186
<return type="int" enum="Error" />
176-
<param index="0" name="file_paths" type="PackedStringArray" />
177-
<param index="1" name="replace_paths" type="PackedStringArray" default="PackedStringArray()" />
187+
<param index="0" name="path" type="String" />
178188
<description>
179-
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].
189+
Set the dataloading roots to those provided in [param path] which should contain full filepaths to the base game's installation and where the search for mods will begin from. Returns [code]FAILED[/code] if there are any problems when setting the dataloading roots, otherwise returns [code]OK[/code].
180190
</description>
181191
</method>
182192
<method name="set_mapmode">

extension/src/openvic-extension/singletons/GameSingleton.cpp

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
#include <godot_cpp/classes/time.hpp>
66
#include <godot_cpp/core/error_macros.hpp>
7+
#include <godot_cpp/variant/packed_string_array.hpp>
78
#include <godot_cpp/variant/utility_functions.hpp>
89

10+
#include <openvic-simulation/dataloader/ModManager.hpp>
911
#include <openvic-simulation/utility/Containers.hpp>
1012
#include <openvic-simulation/utility/Logger.hpp>
1113

@@ -16,6 +18,8 @@
1618
#include "openvic-extension/utility/ClassBindings.hpp"
1719
#include "openvic-extension/utility/Utilities.hpp"
1820

21+
#include <range/v3/algorithm/contains.hpp>
22+
1923
#include <spdlog/spdlog.h>
2024
#include <spdlog/sinks/callback_sink.h>
2125

@@ -39,12 +43,14 @@ StringName const& GameSingleton::_signal_mapmode_changed() {
3943
void GameSingleton::_bind_methods() {
4044
OV_BIND_SMETHOD(setup_logger);
4145

42-
OV_BIND_METHOD(GameSingleton::load_defines_compatibility_mode);
43-
OV_BIND_METHOD(GameSingleton::set_compatibility_mode_roots, { "file_paths", "replace_paths" }, DEFVAL(PackedStringArray{}));
46+
OV_BIND_METHOD(GameSingleton::load_defines_compatibility_mode, { "mods" }, DEFVAL(PackedStringArray()));
47+
OV_BIND_METHOD(GameSingleton::set_compatibility_mode_roots, { "path" });
4448

4549
OV_BIND_SMETHOD(search_for_game_path, { "hint_path" }, DEFVAL(String {}));
4650
OV_BIND_METHOD(GameSingleton::lookup_file_path, { "path" });
4751

52+
OV_BIND_METHOD(GameSingleton::get_mod_info);
53+
4854
OV_BIND_METHOD(GameSingleton::get_bookmark_info);
4955
OV_BIND_METHOD(GameSingleton::setup_game, { "bookmark_index" });
5056
OV_BIND_METHOD(GameSingleton::is_game_instance_setup);
@@ -143,6 +149,37 @@ void GameSingleton::setup_logger() {
143149
spdlog::default_logger_raw()->sinks().push_back(std::move(godot_sink));
144150
}
145151

152+
TypedArray<Dictionary> GameSingleton::get_mod_info() const {
153+
static const StringName identifier_key = "identifier";
154+
static const StringName dependencies_key = "dependencies";
155+
static const StringName is_loaded_key = "is_loaded";
156+
157+
TypedArray<Dictionary> results;
158+
159+
ModManager const& mod_manager = game_manager.get_mod_manager();
160+
memory::vector<Mod const*> const& loaded_mods = mod_manager.get_loaded_mods();
161+
162+
for (Mod const& mod : mod_manager.get_mods()) {
163+
Dictionary mod_info_dictionary;
164+
165+
mod_info_dictionary[identifier_key] = Utilities::std_to_godot_string(mod.get_identifier());
166+
167+
mod_info_dictionary[dependencies_key] = [&]() -> PackedStringArray {
168+
PackedStringArray result;
169+
for (std::string_view dep_id : mod.get_dependencies()) {
170+
result.push_back(Utilities::std_to_godot_string(dep_id));
171+
}
172+
return result;
173+
}();
174+
175+
mod_info_dictionary[is_loaded_key] = ranges::contains(loaded_mods, &mod);
176+
177+
results.push_back(std::move(mod_info_dictionary));
178+
}
179+
180+
return results;
181+
}
182+
146183
TypedArray<Dictionary> GameSingleton::get_bookmark_info() const {
147184
static const StringName bookmark_info_name_key = "bookmark_name";
148185
static const StringName bookmark_info_date_key = "bookmark_date";
@@ -662,30 +699,29 @@ Error GameSingleton::_load_flag_sheet() {
662699
return ret;
663700
}
664701

665-
Error GameSingleton::set_compatibility_mode_roots(
666-
PackedStringArray const& file_paths, godot::PackedStringArray const& replace_paths
667-
) {
668-
Dataloader::path_vector_t roots;
669-
roots.reserve(file_paths.size());
670-
for (String const& path : file_paths) {
671-
roots.emplace_back(Utilities::godot_to_std_string(path));
672-
}
673-
674-
// TODO @BrickPI https://github.com/OpenVicProject/OpenVic/pull/512
675-
// Dataloader::path_vector_t replace;
676-
// replace.reserve(replace_paths.size());
677-
// for (String const& path : replace_paths) {
678-
// replace.emplace_back(Utilities::godot_to_std_string(path));
679-
// }
680-
702+
Error GameSingleton::set_compatibility_mode_roots(String const& path) {
703+
Dataloader::path_vector_t roots { Utilities::godot_to_std_string(path) };
681704
ERR_FAIL_COND_V_MSG(!game_manager.set_base_path(roots), FAILED, "Failed to set dataloader roots!");
682705
return OK;
683706
}
684707

685-
Error GameSingleton::load_defines_compatibility_mode() {
708+
Error GameSingleton::load_defines_compatibility_mode(PackedStringArray const& mods) {
686709
Error err = OK;
687710
auto add_message = std::bind_front(&LoadLocalisation::add_message, LoadLocalisation::get_singleton());
688711

712+
ERR_FAIL_COND_V_MSG(!game_manager.load_mod_descriptors(), FAILED, "Failed to load mod descriptors!");
713+
714+
memory::vector<memory::string> std_mods;
715+
std_mods.reserve(mods.size());
716+
for (String const& mod : mods) {
717+
std_mods.emplace_back(Utilities::godot_to_std_string(mod));
718+
}
719+
720+
memory::vector<fs::path> roots = game_manager.get_dataloader().get_roots();
721+
memory::vector<fs::path> replaces = game_manager.get_dataloader().get_replace_paths();
722+
723+
ERR_FAIL_COND_V_MSG(!game_manager.load_mods(roots, replaces, std_mods), FAILED, "Loading mods failed.");
724+
689725
if (!game_manager.load_definitions(add_message)) {
690726
UtilityFunctions::push_error("Failed to load defines!");
691727
err = FAILED;

extension/src/openvic-extension/singletons/GameSingleton.hpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <godot_cpp/classes/image_texture.hpp>
44
#include <godot_cpp/classes/texture2d_array.hpp>
5+
#include <godot_cpp/variant/packed_string_array.hpp>
56

67
#include <openvic-simulation/GameManager.hpp>
78
#include <openvic-simulation/dataloader/Dataloader.hpp>
@@ -66,14 +67,14 @@ namespace OpenVic {
6667

6768
/* Load the game's defines in compatibility mode from the filepath
6869
* pointing to the defines folder. */
69-
godot::Error set_compatibility_mode_roots(
70-
godot::PackedStringArray const& file_paths, godot::PackedStringArray const& replace_paths = {}
71-
);
72-
godot::Error load_defines_compatibility_mode();
70+
godot::Error set_compatibility_mode_roots(godot::String const& path);
71+
godot::Error load_defines_compatibility_mode(godot::PackedStringArray const& mods = {});
7372

7473
static godot::String search_for_game_path(godot::String const& hint_path = {});
7574
godot::String lookup_file_path(godot::String const& path) const;
7675

76+
godot::TypedArray<godot::Dictionary> get_mod_info() const;
77+
7778
godot::TypedArray<godot::Dictionary> get_bookmark_info() const;
7879

7980
/* After initial load or resigning a previous session game setup, all mutable components of the simulation

game/src/Systems/Startup/GameStart.gd

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const GameMenuScene := preload("res://src/UI/GameMenu/GameMenu/GameMenu.tscn")
1313
@export var setting_name : String = "base_defines_path"
1414

1515
var _settings_base_path : String = ""
16-
var _compatibility_path_list : PackedStringArray = []
16+
var _mod_list : PackedStringArray = []
1717

1818
func _enter_tree() -> void:
1919
Keychain.keep_binding_check = func(action_name : StringName) -> bool:
@@ -127,21 +127,22 @@ func _setup_compatibility_mode_paths() -> void:
127127
# Save the path found in the search
128128
Events.Options.save_settings_to_file()
129129

130-
_compatibility_path_list = [actual_base_path]
131-
132130
# Add mod paths
133-
var settings_mod_names : PackedStringArray = ArgumentParser.get_option_value(&"mod")
134-
for mod_name : String in settings_mod_names:
135-
_compatibility_path_list.push_back(actual_base_path + "/mod/" + mod_name)
131+
var mod_status_file := ConfigFile.new()
132+
mod_status_file.load("user://mods.cfg")
133+
_mod_list = mod_status_file.get_value("mods", "load_list", [])
134+
for mod in ArgumentParser.get_option_value(&"mod"):
135+
if mod not in _mod_list and mod != "":
136+
_mod_list.push_back(mod)
136137

137138
func _load_compatibility_mode() -> void:
138-
if GameSingleton.set_compatibility_mode_roots(_compatibility_path_list) != OK:
139+
if GameSingleton.set_compatibility_mode_roots(_settings_base_path) != OK:
139140
push_error("Errors setting game roots!")
140141

141142
CursorManager.initial_cursor_setup()
142143
setup_title_theme()
143144

144-
if GameSingleton.load_defines_compatibility_mode() != OK:
145+
if GameSingleton.load_defines_compatibility_mode(_mod_list) != OK:
145146
push_error("Errors loading game defines!")
146147

147148
SoundSingleton.load_sounds()

0 commit comments

Comments
 (0)