Skip to content

Commit 4f2ea00

Browse files
feat: ✨ ModLoaderStore: New singleton to store data (#172)
* ModLoaderStore: Initial, including integration * ModLoaderStore: Cleanup * ModLoaderStore: Cleanup [2] * ModLoaderStore: Cleanup [3] * Store - Don't store main vars in a dictionary * Store - Revert to debug log level if ModLoaderStore isn't the first autoload * Store - Check autoload position sooner
1 parent 09cf37e commit 4f2ea00

File tree

13 files changed

+226
-169
lines changed

13 files changed

+226
-169
lines changed

addons/mod_loader/api/config.gd

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ static func get_mod_config(mod_dir_name: String = "", key: String = "") -> Dicti
7070
# No user config file exists. Low importance as very likely to trigger
7171
var full_msg = "Config JSON Notice: %s" % status_msg
7272
# Only log this once, to avoid flooding the log
73-
if not ModLoader.logged_messages.has(full_msg):
73+
if not ModLoaderStore.logged_messages.has(full_msg):
7474
ModLoaderUtils.log_debug(full_msg, mod_dir_name)
75-
ModLoader.logged_messages.push_back(full_msg)
75+
ModLoaderStore.logged_messages.push_back(full_msg)
7676
else:
7777
# Code error (eg. invalid mod ID)
7878
ModLoaderUtils.log_fatal("Config JSON Error (%s): %s" % [status_code, status_msg], mod_dir_name)
@@ -119,11 +119,7 @@ static func save_mod_config_dictionary(mod_id: String, data: Dictionary, update_
119119
data_new = data_original.duplicate(true)
120120
data_new.merge(data, true)
121121

122-
# Note: This bit of code is duped from `_load_mod_configs`
123-
var configs_path := ModLoaderUtils.get_local_folder_dir("configs")
124-
if not ModLoader.os_configs_path_override == "":
125-
configs_path = ModLoader.os_configs_path_override
126-
122+
var configs_path := ModLoaderUtils.get_path_to_configs()
127123
var json_path := configs_path.plus_file(mod_id + ".json")
128124

129125
return ModLoaderUtils.save_dictionary_to_json_file(data_new, json_path)

addons/mod_loader/api/godot.gd

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
class_name ModLoaderGodot
2+
extends Object
3+
4+
# API methods for interacting with Godot
5+
6+
const LOG_NAME := "ModLoader:Godot"
7+
8+
9+
# Check the index position of the provided autoload (0 = 1st, 1 = 2nd, etc).
10+
# Returns a bool if the position does not match.
11+
# Optionally triggers a fatal error
12+
static func check_autoload_position(autoload_name: String, position_index: int, trigger_error: bool = false) -> bool:
13+
var autoload_array := ModLoaderUtils.get_autoload_array()
14+
var autoload_index := autoload_array.find(autoload_name)
15+
var position_matches := autoload_index == position_index
16+
17+
if not position_matches and trigger_error:
18+
var error_msg := "Expected %s to be the autoload in position %s, but this is currently %s." % [autoload_name, str(position_index + 1), autoload_array[position_index]]
19+
var help_msg := ""
20+
21+
if OS.has_feature("editor"):
22+
help_msg = " To configure your autoloads, go to Project > Project Settings > Autoload."
23+
24+
ModLoaderUtils.log_fatal(error_msg + help_msg, LOG_NAME)
25+
26+
return position_matches

addons/mod_loader/api/third_party/steam.gd

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ const LOG_NAME := "ModLoader:ThirdParty:Steam"
1515
# Eg. Brotato:
1616
# GAME = Steam/steamapps/common/Brotato
1717
# WORKSHOP = Steam/steamapps/workshop/content/1942280
18-
static func get_steam_workshop_dir() -> String:
18+
static func get_path_to_workshop() -> String:
19+
if ModLoaderStore.ml_options.override_path_to_workshop:
20+
return ModLoaderStore.ml_options.override_path_to_workshop
21+
1922
var game_install_directory := ModLoaderUtils.get_local_folder_dir()
2023
var path := ""
2124

addons/mod_loader/classes/options_profile.gd

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ extends Resource
66
# export (Array, Resource) var elites: = []
77

88
export (bool) var enable_mods = true
9-
export (ModLoaderUtils.verbosity_level) var log_level: = ModLoaderUtils.verbosity_level.DEBUG
10-
export (String, DIR) var path_to_mods = "res://mods"
11-
export (String, DIR) var path_to_configs = "res://configs"
12-
export (bool) var steam_workshop_enabled = false
13-
export (String, DIR) var steam_workshop_path_override = ""
9+
export (ModLoaderUtils.VERBOSITY_LEVEL) var log_level: = ModLoaderUtils.VERBOSITY_LEVEL.DEBUG
1410
export (Array, String) var disabled_mods = []
11+
export (bool) var steam_workshop_enabled = false
12+
export (String, DIR) var override_path_to_mods = ""
13+
export (String, DIR) var override_path_to_configs = ""
14+
export (String, DIR) var override_path_to_workshop = ""

addons/mod_loader/mod_loader.gd

Lines changed: 22 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -57,19 +57,6 @@ var mod_data := {}
5757
# Order for mods to be loaded in, set by `_get_load_order`
5858
var mod_load_order := []
5959

60-
# Override for the path mods are loaded from. Only set if the CLI arg is present.
61-
# Can be tested in the editor via: Project Settings > Display> Editor > Main Run Args
62-
# Default: "res://mods"
63-
# Set via: --mods-path
64-
# Example: --mods-path="C://path/mods"
65-
var os_mods_path_override := ""
66-
67-
# Override for the path config JSONs are loaded from
68-
# Default: "res://configs"
69-
# Set via: --configs-path
70-
# Example: --configs-path="C://path/configs"
71-
var os_configs_path_override := ""
72-
7360
# Any mods that are missing their dependancies are added to this
7461
# Example property: "mod_id": ["dep_mod_id_0", "dep_mod_id_2"]
7562
var mod_missing_dependencies := {}
@@ -87,73 +74,30 @@ var loaded_vanilla_parents_cache := {}
8774
# Helps to decide whether a script extension should go through the _handle_script_extensions process
8875
var is_initializing := true
8976

90-
# True if ModLoader has displayed the warning about using zipped mods
91-
var has_shown_editor_warning := false
92-
93-
# Keeps track of logged messages, to avoid flooding the log with duplicate notices
94-
var logged_messages := []
95-
96-
# Path to the options resource
97-
# See: res://addons/mod_loader/options/options_current_data.gd
98-
var ml_options_path := "res://addons/mod_loader/options/options.tres"
99-
100-
# These variables handle various options, which can be changed via Godot's GUI
101-
# by adding a ModLoaderOptions resource to the resource file specified by
102-
# `ml_options_path`. See res://addons/mod_loader/options_examples for some
103-
# resource files you can add to the options_curent file.
104-
# See: res://addons/mod_loader/options/classes/options_profile.gd
105-
var ml_options := {
106-
enable_mods = true,
107-
log_level = ModLoaderUtils.verbosity_level.DEBUG,
108-
path_to_mods = "res://mods",
109-
path_to_configs = "res://configs",
110-
111-
# If true, ModLoader will load mod ZIPs from the Steam workshop directory,
112-
# instead of the default location (res://mods)
113-
steam_workshop_enabled = false,
114-
115-
# Can be used in the editor to load mods from your Steam workshop directory
116-
steam_workshop_path_override = "",
117-
118-
# Array of mod ID strings to skip in `_setup_mods`
119-
disabled_mods = []
120-
}
121-
12277

12378
# Main
12479
# =============================================================================
12580

12681
func _init() -> void:
127-
_update_ml_options()
128-
12982
# if mods are not enabled - don't load mods
13083
if REQUIRE_CMD_LINE and not ModLoaderUtils.is_running_with_command_line_arg("--enable-mods"):
13184
return
13285

133-
if not ml_options.enable_mods:
134-
ModLoaderUtils.log_info("Mods are currently disabled", LOG_NAME)
135-
return
136-
13786
# Rotate the log files once on startup. Can't be checked in utils, since it's static
13887
ModLoaderUtils.rotate_log_file()
13988

140-
# Ensure ModLoader is the first autoload
141-
_check_first_autoload()
89+
# Ensure ModLoaderStore and ModLoader are the 1st and 2nd autoloads
90+
_check_autoload_positions()
91+
92+
# Log the autoloads order. Helpful when providing support to players
93+
ModLoaderUtils.log_debug_json_print("Autoload order", ModLoaderUtils.get_autoload_array(), LOG_NAME)
14294

14395
# Log game install dir
14496
ModLoaderUtils.log_info("game_install_directory: %s" % ModLoaderUtils.get_local_folder_dir(), LOG_NAME)
14597

146-
# check if we want to use a different mods path that is provided as a command line argument
147-
var cmd_line_mod_path := ModLoaderUtils.get_cmd_line_arg_value("--mods-path")
148-
if not cmd_line_mod_path == "":
149-
os_mods_path_override = cmd_line_mod_path
150-
ModLoaderUtils.log_info("The path mods are loaded from has been changed via the CLI arg `--mods-path`, to: " + cmd_line_mod_path, LOG_NAME)
151-
152-
# Check for the CLI arg that overrides the configs path
153-
var cmd_line_configs_path := ModLoaderUtils.get_cmd_line_arg_value("--configs-path")
154-
if not cmd_line_configs_path == "":
155-
os_configs_path_override = cmd_line_configs_path
156-
ModLoaderUtils.log_info("The path configs are loaded from has been changed via the CLI arg `--configs-path`, to: " + cmd_line_configs_path, LOG_NAME)
98+
if not ModLoaderStore.ml_options.enable_mods:
99+
ModLoaderUtils.log_info("Mods are currently disabled", LOG_NAME)
100+
return
157101

158102
# Loop over "res://mods" and add any mod zips to the unpacked virtual
159103
# directory (UNPACKED_DIR)
@@ -220,58 +164,28 @@ func _init() -> void:
220164
is_initializing = false
221165

222166

223-
# Update ModLoader's options, via the custom options resource
224-
func _update_ml_options() -> void:
225-
# Get user options for ModLoader
226-
if File.new().file_exists(ml_options_path):
227-
var options_resource := load(ml_options_path)
228-
if not options_resource.current_options == null:
229-
var current_options: Resource = options_resource.current_options
230-
# Update from the options in the resource
231-
for key in ml_options:
232-
ml_options[key] = current_options[key]
233-
else:
234-
ModLoaderUtils.log_fatal(str("A critical file is missing: ", ml_options_path), LOG_NAME)
235-
236-
237-
# Ensure ModLoader is the first autoload
238-
func _check_first_autoload() -> void:
239-
var autoload_array = ModLoaderUtils.get_autoload_array()
240-
var mod_loader_index = autoload_array.find("ModLoader")
241-
var is_mod_loader_first = mod_loader_index == 0
242-
243-
var override_cfg_path = ModLoaderUtils.get_override_path()
244-
var is_override_cfg_setup = ModLoaderUtils.file_exists(override_cfg_path)
245-
246-
# Log the autoloads order. Might seem superflous but could help when providing support
247-
ModLoaderUtils.log_debug_json_print("Autoload order", autoload_array, LOG_NAME)
248-
167+
# Check autoload positions:
168+
# Ensure 1st autoload is `ModLoaderStore`, and 2nd is `ModLoader`.
169+
func _check_autoload_positions() -> void:
249170
# If the override file exists we assume the ModLoader was setup with the --setup-create-override-cfg cli arg
250171
# In that case the ModLoader will be the last entry in the autoload array
172+
var override_cfg_path := ModLoaderUtils.get_override_path()
173+
var is_override_cfg_setup := ModLoaderUtils.file_exists(override_cfg_path)
251174
if is_override_cfg_setup:
252175
ModLoaderUtils.log_info("override.cfg setup detected, ModLoader will be the last autoload loaded.", LOG_NAME)
253176
return
254177

255-
var base_msg = "ModLoader needs to be the first autoload to work correctly, "
256-
var help_msg = ""
257-
258-
if OS.has_feature("editor"):
259-
help_msg = "To configure your autoloads, go to Project > Project Settings > Autoload, and add ModLoader as the first item. For more info, see the 'Godot Project Setup' page on the ModLoader GitHub wiki."
260-
else:
261-
help_msg = "If you're seeing this error, something must have gone wrong in the setup process."
262-
263-
if not is_mod_loader_first:
264-
ModLoaderUtils.log_fatal(str(base_msg, 'but the first autoload is currently: "%s". ' % autoload_array[0], help_msg), LOG_NAME)
178+
var _pos_ml_store := ModLoaderGodot.check_autoload_position("ModLoaderStore", 0, true)
179+
var _pos_ml_core := ModLoaderGodot.check_autoload_position("ModLoader", 1, true)
265180

266181

267182
# Loop over "res://mods" and add any mod zips to the unpacked virtual directory
268183
# (UNPACKED_DIR)
269184
func _load_mod_zips() -> int:
270185
var zipped_mods_count := 0
271186

272-
if not ml_options.steam_workshop_enabled:
273-
# Path to the games mod folder
274-
var mods_folder_path := ModLoaderUtils.get_local_folder_dir("mods")
187+
if not ModLoaderStore.ml_options.steam_workshop_enabled:
188+
var mods_folder_path := ModLoaderUtils.get_path_to_mods()
275189

276190
# If we're not using Steam workshop, just loop over the mod ZIPs.
277191
zipped_mods_count += _load_zips_in_folder(mods_folder_path)
@@ -326,12 +240,12 @@ func _load_zips_in_folder(folder_path: String) -> int:
326240
# "don't use ZIPs with unpacked mods!"
327241
# https://github.com/godotengine/godot/issues/19815
328242
# https://github.com/godotengine/godot/issues/16798
329-
if OS.has_feature("editor") and not has_shown_editor_warning:
243+
if OS.has_feature("editor") and not ModLoaderStore.has_shown_editor_zips_warning:
330244
ModLoaderUtils.log_warning(str(
331245
"Loading any resource packs (.zip/.pck) with `load_resource_pack` will WIPE the entire virtual res:// directory. ",
332246
"If you have any unpacked mods in ", UNPACKED_DIR, ", they will not be loaded. ",
333247
"Please unpack your mod ZIPs instead, and add them to ", UNPACKED_DIR), LOG_NAME)
334-
has_shown_editor_warning = true
248+
ModLoaderStore.has_shown_editor_zips_warning = true
335249

336250
ModLoaderUtils.log_debug("Found mod ZIP: %s" % mod_folder_global_path, LOG_NAME)
337251

@@ -355,10 +269,7 @@ func _load_zips_in_folder(folder_path: String) -> int:
355269
# inside each workshop item's folder
356270
func _load_steam_workshop_zips() -> int:
357271
var temp_zipped_mods_count := 0
358-
var workshop_folder_path := ModLoaderSteam.get_steam_workshop_dir()
359-
360-
if not ml_options.steam_workshop_path_override == "":
361-
workshop_folder_path = ml_options.steam_workshop_path_override
272+
var workshop_folder_path := ModLoaderSteam.get_path_to_workshop()
362273

363274
ModLoaderUtils.log_info("Checking workshop items, with path: \"%s\"" % workshop_folder_path, LOG_NAME)
364275

@@ -428,7 +339,7 @@ func _setup_mods() -> int:
428339
if mod_dir_name == "." or mod_dir_name == "..":
429340
continue
430341

431-
if ml_options.disabled_mods.has(mod_dir_name):
342+
if ModLoaderStore.ml_options.disabled_mods.has(mod_dir_name):
432343
ModLoaderUtils.log_info("Skipped setting up mod: \"%s\"" % mod_dir_name, LOG_NAME)
433344
continue
434345

@@ -443,12 +354,7 @@ func _setup_mods() -> int:
443354
# Load mod config JSONs from res://configs
444355
func _load_mod_configs() -> void:
445356
var found_configs_count := 0
446-
var configs_path := ModLoaderUtils.get_local_folder_dir("configs")
447-
448-
# CLI override, set with `--configs-path="C://path/configs"`
449-
# (similar to os_mods_path_override)
450-
if not os_configs_path_override == "":
451-
configs_path = os_configs_path_override
357+
var configs_path := ModLoaderUtils.get_path_to_configs()
452358

453359
for dir_name in mod_data:
454360
var json_path := configs_path.plus_file(dir_name + ".json")

addons/mod_loader/mod_loader_setup.gd

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ const new_global_classes := [
5050
"class": "ModLoaderDeprecated",
5151
"language": "GDScript",
5252
"path": "res://addons/mod_loader/api/deprecated.gd"
53+
}, {
54+
"base": "Object",
55+
"class": "ModLoaderGodot",
56+
"language": "GDScript",
57+
"path": "res://addons/mod_loader/api/godot.gd"
5358
}, {
5459
"base": "Node",
5560
"class": "ModLoaderSteam",
@@ -147,7 +152,10 @@ func reorder_autoloads() -> void:
147152
for autoload in original_autoloads.keys():
148153
ProjectSettings.set_setting(autoload, null)
149154

150-
# add ModLoader autoload (the * marks the path as autoload)
155+
# Add ModLoaderStore autoload (the * marks the path as autoload)
156+
ProjectSettings.set_setting("autoload/ModLoaderStore", "*" + "res://addons/mod_loader/mod_loader_store.gd")
157+
158+
# Add ModLoader autoload (the * marks the path as autoload)
151159
ProjectSettings.set_setting("autoload/ModLoader", "*" + "res://addons/mod_loader/mod_loader.gd")
152160

153161
# add all previous autoloads back again

0 commit comments

Comments
 (0)