Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
692652c
feat: :sparkles: option to disable game version validation
KANAjetzt Jan 30, 2025
4a743cb
feat: :sparkles: added custom validation option
KANAjetzt Jan 31, 2025
8a19d72
docs: :memo: added missing doc comments
KANAjetzt Jan 31, 2025
2dc7e5a
refactor: :recycle: use ENUM
KANAjetzt Feb 2, 2025
d342385
refactor: :recycle: only pass `ml_options`
KANAjetzt Feb 2, 2025
24f723c
refactor: :recycle: move `customize_script_path` out of export group
KANAjetzt Feb 2, 2025
172e0ff
docs: :memo: added example customize script
KANAjetzt Feb 2, 2025
581684f
style: :pencil2: improved spelling
KANAjetzt Feb 2, 2025
67127e3
docs: :memo: reworked comments
KANAjetzt Feb 2, 2025
666f4c6
refactor: :recycle: `ml_options_path` as param
KANAjetzt Feb 5, 2025
00b9292
refactor: :fire: remove example script
KANAjetzt Feb 5, 2025
013b790
refactor: :recycle: removed example added `@tutorial`
KANAjetzt Feb 5, 2025
b97fab7
test: :test_tube: added custom validation test
KANAjetzt Feb 5, 2025
c354ae0
fix: :test_tube: fixed test setup
KANAjetzt Feb 5, 2025
0743144
fix: :test_tube: removed editor override
KANAjetzt Feb 5, 2025
9ef663b
fix: :bug: set `customize_script_path` outside of for loop
KANAjetzt Feb 5, 2025
df193ca
refactor: :truck: added sub dir
KANAjetzt Feb 5, 2025
8b122c3
test: :test_tube: added test for game version validation disabled
KANAjetzt Feb 5, 2025
52c582a
fix: :test_tube: updated custom script path
KANAjetzt Feb 5, 2025
8d12b7a
test: :test_tube: added `test_game_verion_validation_default`
KANAjetzt Feb 5, 2025
b8ef08a
fix: :test_tube: replace white space chars with `""`
KANAjetzt Feb 5, 2025
c26a4bf
refactor: :recycle: clean up a bit
KANAjetzt Feb 5, 2025
d136db4
test: :test_tube: added no callable set test
KANAjetzt Feb 5, 2025
b34413e
Update addons/mod_loader/resources/options_profile.gd
KANAjetzt Feb 5, 2025
7eea1d7
Update addons/mod_loader/resources/options_profile.gd
KANAjetzt Feb 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions addons/mod_loader/mod_loader_store.gd
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,10 @@ func _init():


# Update ModLoader's options, via the custom options resource
func _update_ml_options_from_options_resource() -> void:
# Path to the options resource
# See: res://addons/mod_loader/resources/options_current.gd
var ml_options_path := "res://addons/mod_loader/options/options.tres"

#
# Parameters:
# - ml_options_path: Path to the options resource. See: res://addons/mod_loader/resources/options_current.gd
func _update_ml_options_from_options_resource(ml_options_path := "res://addons/mod_loader/options/options.tres") -> void:
# Get user options for ModLoader
if not _ModLoaderFile.file_exists(ml_options_path) and not ResourceLoader.exists(ml_options_path):
ModLoaderLog.fatal(str("A critical file is missing: ", ml_options_path), LOG_NAME)
Expand Down Expand Up @@ -177,8 +176,8 @@ func _update_ml_options_from_options_resource() -> void:
# Update from the options in the resource
ml_options = override_options

if not ml_options.customize_script_path.is_empty():
ml_options.customize_script_instance = load(ml_options.customize_script_path).new(ml_options)
if not ml_options.customize_script_path.is_empty():
ml_options.customize_script_instance = load(ml_options.customize_script_path).new(ml_options)


func _exit_tree() -> void:
Expand Down
36 changes: 7 additions & 29 deletions addons/mod_loader/resources/options_profile.gd
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ class_name ModLoaderOptionsProfile
extends Resource
##
## Class to define and store Mod Loader Options.
##
## @tutorial(Example Customization Script): https://example.com


## Settings for game version validation.
Expand All @@ -17,36 +19,12 @@ enum VERSION_VALIDATION {
## Use [member customize_script_path] to specify a script that customizes the Mod Loader options.
## In this script, you must set [member custom_game_version_validation_callable]
## to a custom validation [Callable].
##
## Example:
## [codeblock]
## extends RefCounted
##
## func _init(ml_options: ModLoaderOptionsProfile) -> void:
## # Assign a custom validation function.
## # Use `OS.has_feature(feature_tag)` to apply different validations for different platforms.
## ml_options.custom_game_version_validation_callable = custom_is_game_version_compatible
##
## func custom_is_game_version_compatible(manifest: ModManifest) -> bool:
## print("! ☞゚ヮ゚)☞ CUSTOM VALIDATION HERE ☜゚ヮ゚☜) !")
##
## var mod_id := manifest.get_mod_id()
##
## for version in manifest.compatible_game_version:
## if not version == "pizza":
## manifest.validation_messages_warning.push_back(
## "The mod \"%s\" may not be compatible with the current game version.
## Enable at your own risk. (current game version: %s, mod compatible with game versions: %s)" %
## [mod_id, MyGlobalVars.MyGameVersion, manifest.compatible_game_version]
## )
## return false
##
## return true
## [/codeblock]
##
## [br]
## ===[br]
## [b]Note:[color=note "Easier Mod Loader Updates"][/color][/b][br]
## Using a customization script allows you to keep your custom code outside the addon directory,
## making it easier to update the mod loader without affecting your modifications.
##
## making it easier to update the mod loader without affecting your modifications. [br]
## ===[br]
CUSTOM,
}

Expand Down
47 changes: 47 additions & 0 deletions test/Unit/test_options.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
extends GutTest


func load_manifest_test_mod_1() -> ModManifest:
var mod_path := "res://mods-unpacked/test-mod1/"
var manifest_data: Dictionary = _ModLoaderFile.load_manifest_file(mod_path)

return ModManifest.new(manifest_data, mod_path)


func test_customize_script() -> void:
ModLoaderStore._update_ml_options_from_options_resource("res://test_options/customize_script/options_custom_validation.tres")
var manifest := load_manifest_test_mod_1()

assert_eq(
"".join(manifest.validation_messages_warning),
"! ☞゚ヮ゚)☞ CUSTOM VALIDATION HERE ☜゚ヮ゚☜) !"
)


func test_customize_script_no_callable() -> void:
# Clear saved error logs before testing to prevent false positives.
ModLoaderLog.logged_messages.by_type.error.clear()

ModLoaderStore._update_ml_options_from_options_resource("res://test_options/customize_script_no_callable_set/options_custom_validation_no_callable_set.tres")
var manifest := load_manifest_test_mod_1()

var logs := ModLoaderLog.get_by_type_as_string("error")

assert_string_contains("".join(logs), "No custom game version validation callable detected. Please provide a valid validation callable.")


func test_game_verion_validation_disabled() -> void:
ModLoaderStore._update_ml_options_from_options_resource("res://test_options/game_version_validation_disabled/options_game_version_validation_disabled.tres")
var manifest := load_manifest_test_mod_1()

assert_true(manifest.validation_messages_error.size() == 0)


func test_game_verion_validation_default() -> void:
ModLoaderStore._update_ml_options_from_options_resource("res://test_options/game_version_validation_default/options_game_version_validation_default.tres")
var manifest := load_manifest_test_mod_1()

assert_eq(
"".join(manifest.validation_messages_error).replace("\r", "").replace("\n", "").replace("\t", ""),
"The mod \"test-mod1\" is incompatible with the current game version.(current game version: 1000.0.0, mod compatible with game versions: [\"0.0.1\"])"
)
26 changes: 26 additions & 0 deletions test/test_options/customize_script/custom_validation.tres
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[gd_resource type="Resource" script_class="ModLoaderOptionsProfile" load_steps=2 format=3 uid="uid://dky5648t3gmp2"]

[ext_resource type="Script" path="res://addons/mod_loader/resources/options_profile.gd" id="1_3rpjy"]

[resource]
script = ExtResource("1_3rpjy")
enable_mods = true
locked_mods = Array[String]([])
disabled_mods = Array[String]([])
allow_modloader_autoloads_anywhere = false
customize_script_path = "res://test_options/customize_script/customize_script.gd"
log_level = 3
ignore_deprecated_errors = false
ignored_mod_names_in_log = Array[String]([])
steam_id = 0
semantic_version = "0.0.0"
load_from_steam_workshop = false
load_from_local = true
override_path_to_mods = ""
override_path_to_configs = ""
override_path_to_workshop = ""
override_path_to_hook_pack = ""
override_hook_pack_name = ""
restart_notification_scene_path = "res://addons/mod_loader/restart_notification.tscn"
disable_restart = false
game_version_validation = 2
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ extends RefCounted
# This is an example script for the ModLoaderOptionsProfile `customize_script_path`.
# Ideally, place this script outside the `mod_loader` directory to simplify the update process.


# This script is loaded after `mod_loader_store.ml_options` has been initialized.
# It receives `ml_options` as an argument, allowing you to apply settings
# that cannot be configured through the editor UI.
Expand All @@ -17,30 +18,9 @@ func _init(ml_options: ModLoaderOptionsProfile) -> void:
# Set `custom_game_version_validation_callable` to use a custom validation function.
ml_options.custom_game_version_validation_callable = custom_is_game_version_compatible


# Custom validation function
# See `ModManifest._is_game_version_compatible()` for the default validation logic.
func custom_is_game_version_compatible(manifest: ModManifest) -> bool:
print("! ☞゚ヮ゚)☞ CUSTOM VALIDATION HERE ☜゚ヮ゚☜) !")

var mod_id := manifest.get_mod_id()

for version in manifest.compatible_game_version:
if not version == "pizza":
# Push a warning message displayed after manifest validation is complete.
manifest.validation_messages_warning.push_back(
"The mod \"%s\" may not be compatible with the current game version.
Enable at your own risk. (Current game version: %s, mod compatible with game versions: %s)" %
[mod_id, "MyGlobalVars.MyGameVersion", manifest.compatible_game_version]
)
return true

if not version == "pineapple":
# Push an error message displayed after manifest validation is complete.
manifest.validation_messages_error.push_back(
"The mod \"%s\" is incompatible with the current game version.
(Current game version: %s, mod compatible with game versions: %s)" %
[mod_id, "MyGlobalVars.MyGameVersion", manifest.compatible_game_version]
)
return false

manifest.validation_messages_warning.push_back("! ☞゚ヮ゚)☞ CUSTOM VALIDATION HERE ☜゚ヮ゚☜) !")
return true
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[gd_resource type="Resource" script_class="ModLoaderCurrentOptions" load_steps=3 format=3 uid="uid://d08kklljebrnh"]

[ext_resource type="Resource" uid="uid://dky5648t3gmp2" path="res://test_options/customize_script/custom_validation.tres" id="1_s4sec"]
[ext_resource type="Script" path="res://addons/mod_loader/resources/options_current.gd" id="2_1rct1"]

[resource]
script = ExtResource("2_1rct1")
current_options = ExtResource("1_s4sec")
feature_override_options = {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[gd_resource type="Resource" script_class="ModLoaderOptionsProfile" load_steps=2 format=3 uid="uid://1gab2n8lgi60"]

[ext_resource type="Script" path="res://addons/mod_loader/resources/options_profile.gd" id="1_d2tfu"]

[resource]
script = ExtResource("1_d2tfu")
enable_mods = true
locked_mods = Array[String]([])
disabled_mods = Array[String]([])
allow_modloader_autoloads_anywhere = false
customize_script_path = "res://test_options/customize_script_no_callable_set/customize_script_no_callable_set.gd"
log_level = 3
ignore_deprecated_errors = false
ignored_mod_names_in_log = Array[String]([])
steam_id = 0
semantic_version = "0.0.0"
load_from_steam_workshop = false
load_from_local = true
override_path_to_mods = ""
override_path_to_configs = ""
override_path_to_workshop = ""
override_path_to_hook_pack = ""
override_hook_pack_name = ""
restart_notification_scene_path = "res://addons/mod_loader/restart_notification.tscn"
disable_restart = false
game_version_validation = 2
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
extends RefCounted

# This is an example script for the ModLoaderOptionsProfile `customize_script_path`.
# Ideally, place this script outside the `mod_loader` directory to simplify the update process.


# This script is loaded after `mod_loader_store.ml_options` has been initialized.
# It receives `ml_options` as an argument, allowing you to apply settings
# that cannot be configured through the editor UI.
func _init(ml_options: ModLoaderOptionsProfile) -> void:
# Use OS.has_feature() to apply changes only for specific platforms,
# or create multiple customization scripts and set their paths accordingly in the option profiles.
if OS.has_feature("Steam"):
pass
elif OS.has_feature("Epic"):
pass
else:
pass
# Set `custom_game_version_validation_callable` to use a custom validation function.
#ml_options.custom_game_version_validation_callable = custom_is_game_version_compatible


# Custom validation function
# See `ModManifest._is_game_version_compatible()` for the default validation logic.
func custom_is_game_version_compatible(manifest: ModManifest) -> bool:
manifest.validation_messages_warning.push_back("! ☞゚ヮ゚)☞ CUSTOM VALIDATION HERE ☜゚ヮ゚☜) !")
return true
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[gd_resource type="Resource" script_class="ModLoaderCurrentOptions" load_steps=3 format=3 uid="uid://c25j7kt7y8ora"]

[ext_resource type="Resource" uid="uid://1gab2n8lgi60" path="res://test_options/customize_script_no_callable_set/custom_validation_no_callable_set.tres" id="1_xrqi6"]
[ext_resource type="Script" path="res://addons/mod_loader/resources/options_current.gd" id="2_4o6bw"]

[resource]
script = ExtResource("2_4o6bw")
current_options = ExtResource("1_xrqi6")
feature_override_options = {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[gd_resource type="Resource" script_class="ModLoaderOptionsProfile" load_steps=2 format=3 uid="uid://bnc6gslxpnx3y"]

[ext_resource type="Script" path="res://addons/mod_loader/resources/options_profile.gd" id="1_kdajl"]

[resource]
script = ExtResource("1_kdajl")
enable_mods = true
locked_mods = Array[String]([])
disabled_mods = Array[String]([])
allow_modloader_autoloads_anywhere = false
customize_script_path = ""
log_level = 3
ignore_deprecated_errors = false
ignored_mod_names_in_log = Array[String]([])
steam_id = 0
semantic_version = "1000.0.0"
load_from_steam_workshop = false
load_from_local = true
override_path_to_mods = ""
override_path_to_configs = ""
override_path_to_workshop = ""
override_path_to_hook_pack = ""
override_hook_pack_name = ""
restart_notification_scene_path = "res://addons/mod_loader/restart_notification.tscn"
disable_restart = false
game_version_validation = 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[gd_resource type="Resource" script_class="ModLoaderCurrentOptions" load_steps=3 format=3 uid="uid://emmn66l0e1n0"]

[ext_resource type="Resource" uid="uid://bnc6gslxpnx3y" path="res://test_options/game_version_validation_default/game_version_validation_default.tres" id="1_ey6sk"]
[ext_resource type="Script" path="res://addons/mod_loader/resources/options_current.gd" id="2_0ultl"]

[resource]
script = ExtResource("2_0ultl")
current_options = ExtResource("1_ey6sk")
feature_override_options = {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[gd_resource type="Resource" script_class="ModLoaderOptionsProfile" load_steps=2 format=3 uid="uid://d2ktmje1gd5vb"]

[ext_resource type="Script" path="res://addons/mod_loader/resources/options_profile.gd" id="1_vd02r"]

[resource]
script = ExtResource("1_vd02r")
enable_mods = true
locked_mods = Array[String]([])
disabled_mods = Array[String]([])
allow_modloader_autoloads_anywhere = false
customize_script_path = ""
log_level = 3
ignore_deprecated_errors = false
ignored_mod_names_in_log = Array[String]([])
steam_id = 0
semantic_version = "1000.0.0"
load_from_steam_workshop = false
load_from_local = true
override_path_to_mods = ""
override_path_to_configs = ""
override_path_to_workshop = ""
override_path_to_hook_pack = ""
override_hook_pack_name = ""
restart_notification_scene_path = "res://addons/mod_loader/restart_notification.tscn"
disable_restart = false
game_version_validation = 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[gd_resource type="Resource" script_class="ModLoaderCurrentOptions" load_steps=3 format=3 uid="uid://dsegljus5l2qm"]

[ext_resource type="Resource" uid="uid://d2ktmje1gd5vb" path="res://test_options/game_version_validation_disabled/game_version_validation_disabled.tres" id="1_18vx8"]
[ext_resource type="Script" path="res://addons/mod_loader/resources/options_current.gd" id="2_5hgrx"]

[resource]
script = ExtResource("2_5hgrx")
current_options = ExtResource("1_18vx8")
feature_override_options = {}