Skip to content

Commit e4b6a42

Browse files
Merge branch 'main' into addon
2 parents 56258c4 + c4458b7 commit e4b6a42

File tree

2 files changed

+162
-18
lines changed

2 files changed

+162
-18
lines changed

README.md

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,23 +40,26 @@ Mods you create must have the following 2 files:
4040

4141
```json
4242
{
43-
"name": "ModName",
44-
"version": "1.0.0",
45-
"description": "Mod description goes here",
46-
"website_url": "https://github.com/example/repo",
47-
"dependencies": [
43+
"name": "ModName",
44+
"namespace": "AuthorName",
45+
"version_number": "1.0.0",
46+
"description": "Mod description goes here",
47+
"website_url": "https://github.com/example/repo",
48+
"dependencies": [
4849
"Add IDs of other mods here, if your mod needs them to work"
49-
],
50-
"extra": {
51-
"godot": {
52-
"id": "AuthorName-ModName",
53-
"incompatibilities": [
50+
],
51+
"extra": {
52+
"godot": {
53+
"id": "AuthorName-ModName",
54+
"incompatibilities": [
5455
"Add IDs of other mods here, if your mod conflicts with them"
5556
],
56-
"authors": ["AuthorName"],
57-
"compatible_game_version": ["0.6.1.6"],
58-
}
59-
}
57+
"authors": ["AuthorName"],
58+
"compatible_mod_loader_version": "3.0.0",
59+
"compatible_game_version": ["0.6.1.6"],
60+
"config_defaults": {}
61+
}
62+
}
6063
}
6164
```
6265

addons/mod_loader_tools/mod_loader.gd

Lines changed: 145 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,11 @@ const REQUIRED_MANIFEST_KEYS_ROOT = [
6161

6262
# Required keys in manifest's `json.extra.godot`
6363
const REQUIRED_MANIFEST_KEYS_EXTRA = [
64-
"id",
6564
"incompatibilities",
6665
"authors",
6766
"compatible_mod_loader_version",
6867
"compatible_game_version",
68+
"config_defaults",
6969
]
7070

7171
# Set to true to require using "--enable-mods" to enable them
@@ -91,6 +91,12 @@ var mod_load_order = []
9191
# Example: --mods-path="C://path/mods"
9292
var os_mods_path_override = ""
9393

94+
# Override for the path config JSONs are loaded from
95+
# Default: "res://configs"
96+
# Set via: --configs-path
97+
# Example: --configs-path="C://path/configs"
98+
var os_configs_path_override = ""
99+
94100
# Any mods that are missing their dependancies are added to this
95101
# Example property: "mod_id": ["dep_mod_id_0", "dep_mod_id_2"]
96102
var mod_missing_dependencies = {}
@@ -116,6 +122,12 @@ func _init():
116122
os_mods_path_override = cmd_line_mod_path
117123
mod_log("The path mods are loaded from has been changed via the CLI arg `--mods-path`, to: " + cmd_line_mod_path, LOG_NAME)
118124

125+
# Check for the CLI arg that overrides the configs path
126+
var cmd_line_configs_path = _get_cmd_line_arg("--configs-path")
127+
if cmd_line_configs_path != "":
128+
os_configs_path_override = cmd_line_configs_path
129+
mod_log("The path configs are loaded from has been changed via the CLI arg `--configs-path`, to: " + cmd_line_configs_path, LOG_NAME)
130+
119131
# Loop over "res://mods" and add any mod zips to the unpacked virtual
120132
# directory (UNPACKED_DIR)
121133
_load_mod_zips()
@@ -125,6 +137,10 @@ func _init():
125137
# directory, which adds their data to mod_data.
126138
_setup_mods()
127139

140+
# Set up mod configs. If a mod's JSON file is found, its data gets added
141+
# to mod_data.{mod_id}.config
142+
_load_mod_configs()
143+
128144
# Loop over all loaded mods via their entry in mod_data. Verify that they
129145
# have all the required files (REQUIRED_MOD_FILES), load their meta data
130146
# (from their manifest.json file), and verify that the meta JSON has all
@@ -162,7 +178,8 @@ func _init():
162178

163179
# Instance every mod and add it as a node to the Mod Loader
164180
for mod in mod_load_order:
165-
mod_log(str("Initializing -> ", mod.meta_data.extra.godot.id), LOG_NAME)
181+
# mod_log(str("Initializing -> ", mod.meta_data.extra.godot.id), LOG_NAME)
182+
mod_log(str("Initializing -> ", _get_mod_full_id(mod)), LOG_NAME)
166183
_init_mod(mod)
167184

168185
dev_log(str("mod_data: ", JSON.print(mod_data, ' ')), LOG_NAME)
@@ -317,6 +334,53 @@ func _setup_mods():
317334
dir.list_dir_end()
318335

319336

337+
# Load mod config JSONs from res://configs
338+
func _load_mod_configs():
339+
var found_configs_count = 0
340+
var configs_path = _get_local_folder_dir("configs")
341+
342+
# CLI override, set with `--configs-path="C://path/configs"`
343+
# (similar to os_mods_path_override)
344+
if (os_configs_path_override != ""):
345+
configs_path = os_configs_path_override
346+
347+
for mod_id in mod_data:
348+
var json_path = configs_path.plus_file(mod_id + ".json")
349+
var mod_config = _get_json_as_dict(json_path)
350+
351+
dev_log(str("Config JSON: Looking for config at path: ", json_path), LOG_NAME)
352+
353+
if mod_config.size() > 0:
354+
found_configs_count += 1
355+
356+
mod_log(str("Config JSON: Found a config file: '", json_path, "'"), LOG_NAME)
357+
dev_log(str("Config JSON: File data: ", JSON.print(mod_config)), LOG_NAME)
358+
359+
# Check `load_from` option. This lets you specify the name of a
360+
# different JSON file to load your config from. Must be in the same
361+
# dir. Means you can have multiple config files for a single mod
362+
# and switch between them quickly. Should include ".json" extension.
363+
# Ignored if the filename matches the mod ID, or is empty
364+
if mod_config.has("load_from"):
365+
var new_path = mod_config.load_from
366+
if new_path != "" && new_path != str(mod_id, ".json"):
367+
mod_log(str("Config JSON: Following load_from path: ", new_path), LOG_NAME)
368+
var new_config = _get_json_as_dict(configs_path + new_path)
369+
if new_config.size() > 0 != null:
370+
mod_config = new_config
371+
mod_log(str("Config JSON: Loaded from custom json: ", new_path), LOG_NAME)
372+
dev_log(str("Config JSON: File data: ", JSON.print(mod_config)), LOG_NAME)
373+
else:
374+
mod_log(str("Config JSON: ERROR - Could not load data via `load_from` for ", mod_id, ", at path: ", new_path), LOG_NAME)
375+
376+
mod_data[mod_id].config = mod_config
377+
378+
if found_configs_count > 0:
379+
mod_log(str("Config JSON: Loaded ", str(found_configs_count), " config(s)"), LOG_NAME)
380+
else:
381+
mod_log(str("Config JSON: No mod configs were found"), LOG_NAME)
382+
383+
320384
# Add a mod's data to mod_data.
321385
# The mod_folder_path is just the folder name that was added to UNPACKED_DIR,
322386
# which depends on the name used in a given mod ZIP (eg "mods-unpacked/Folder-Name")
@@ -333,6 +397,7 @@ func _init_mod_data(mod_folder_path):
333397
mod_data[mod_id].is_loadable = true
334398
mod_data[mod_id].importance = 0
335399
mod_data[mod_id].dir = local_mod_path
400+
mod_data[mod_id].config = {} # updated in _load_mod_configs
336401

337402
if DEBUG_ENABLE_STORING_FILEPATHS:
338403
# Get the mod file paths
@@ -390,6 +455,13 @@ func _load_meta_data(mod_id):
390455
# Add the meta data to the mod
391456
mod.meta_data = meta_data
392457

458+
# Check that the mod ID is correct. This will fail if the mod's folder in
459+
# "res://mods-unpacked" does not match its full ID, which is `namespace.name`
460+
var mod_check_id = _get_mod_full_id(mod)
461+
if mod_id != mod_check_id:
462+
mod_log(str("ERROR - ", mod_id, " - Mod ID does not match the data in manifest.json. Expected '", mod_id ,"', but '{namespace}-{name}' was '", mod_check_id ,"'"), LOG_NAME)
463+
mod.is_loadable = false
464+
393465

394466
# Ensure manifest.json has all required keys
395467
func _check_meta_file(meta_data):
@@ -489,7 +561,8 @@ func _init_mod(mod):
489561
dev_log(str("Loaded script -> ", mod_main_script), LOG_NAME)
490562

491563
var mod_main_instance = mod_main_script.new(self)
492-
mod_main_instance.name = mod.meta_data.extra.godot.id
564+
# mod_main_instance.name = mod.meta_data.extra.godot.id
565+
mod_main_instance.name = _get_mod_full_id(mod)
493566

494567
dev_log(str("Adding child -> ", mod_main_instance), LOG_NAME)
495568
add_child(mod_main_instance, true)
@@ -500,6 +573,12 @@ func _init_mod(mod):
500573

501574
# Util functions used in the mod loading process
502575

576+
func _get_mod_full_id(mod:Dictionary)->String:
577+
var name = mod.meta_data.name
578+
var namespace = mod.meta_data.namespace
579+
return str(namespace, "-", name)
580+
581+
503582
# Check if the provided command line argument was present when launching the game
504583
func _check_cmd_line_arg(argument) -> bool:
505584
for arg in OS.get_cmdline_args():
@@ -622,7 +701,6 @@ func _get_flat_view_dict(p_dir = "res://", p_match = "", p_match_is_regex = fals
622701
return data
623702

624703

625-
626704
# Helpers
627705
# =============================================================================
628706

@@ -692,3 +770,66 @@ func save_scene(modified_scene, scene_path:String):
692770
packed_scene.take_over_path(scene_path)
693771
dev_log(str("save_scene - taking over path - new path -> ", packed_scene.resource_path), LOG_NAME)
694772
_saved_objects.append(packed_scene)
773+
774+
775+
# Get the config data for a specific mod. Always returns a dictionary with two
776+
# keys: `error` and `data`.
777+
# Data (`data`) is either the full config, or data from a specific key if one was specified.
778+
# Error (`error`) is 0 if there were no errors, or > 0 if the setting could not be retrieved:
779+
# 0 = No errors
780+
# 1 = Invalid mod ID
781+
# 2 = No custom JSON. File probably does not exist. Defaults will be used if available
782+
# 3 = No custom JSON, and key was invalid when trying to get the default from your manifest defaults (`extra.godot.config_defaults`)
783+
# 4 = Invalid key, although config data does exists
784+
func get_mod_config(mod_id:String = "", key:String = "")->Dictionary:
785+
var error_num = 0
786+
var error_msg = ""
787+
var data = {}
788+
var defaults = null
789+
790+
# Invalid mod ID
791+
if !mod_data.has(mod_id):
792+
error_num = 1
793+
error_msg = str("ERROR - Mod ID was invalid: ", mod_id)
794+
795+
# Mod ID is valid
796+
if error_num == 0:
797+
var config_data = mod_data[mod_id].config
798+
defaults = mod_data[mod_id].meta_data.extra.godot.config_defaults
799+
800+
# No custom JSON file
801+
if config_data.size() == 0:
802+
error_num = 2
803+
error_msg = str("WARNING - No config file for ", mod_id, ".json. ")
804+
if key == "":
805+
data = defaults
806+
error_msg += "Using defaults (extra.godot.config_defaults)"
807+
else:
808+
if defaults.has(key):
809+
data = defaults[key]
810+
error_msg += str("Using defaults for key '", key, "' (extra.godot.config_defaults.", key, ")")
811+
else:
812+
error_num = 3
813+
# error_msg = str("WARNING - No config file for Invalid key '", key, "' for mod ID: ", mod_id)
814+
error_msg += str("Requested key '", key, "' is not present in the defaults (extra.godot.config_defaults.", key, ")")
815+
816+
# JSON file exists
817+
if error_num == 0:
818+
if key == "":
819+
data = config_data
820+
else:
821+
if config_data.has(key):
822+
data = config_data[key]
823+
else:
824+
error_num = 4
825+
error_msg = str("WARNING - Invalid key '", key, "' for mod ID: ", mod_id)
826+
827+
# Log if any errors occured
828+
if error_num != 0:
829+
dev_log(str("Config: ", error_msg), mod_id)
830+
831+
return {
832+
"error": error_num,
833+
"error_msg": error_msg,
834+
"data": data,
835+
}

0 commit comments

Comments
 (0)