diff --git a/addons/mod_loader/api/mod.gd b/addons/mod_loader/api/mod.gd new file mode 100644 index 00000000..cd810f8e --- /dev/null +++ b/addons/mod_loader/api/mod.gd @@ -0,0 +1,96 @@ +class_name ModLoaderMod +extends Object + +# Helper functions to build mods + +const LOG_NAME := "ModLoader:Mod" + + +# Add a script that extends a vanilla script. `child_script_path` should point +# to your mod's extender script, eg "MOD/extensions/singletons/utils.gd". +# Inside that extender script, it should include "extends {target}", where +# {target} is the vanilla path, eg: `extends "res://singletons/utils.gd"`. +# Note that your extender script doesn't have to follow the same directory path +# as the vanilla file, but it's good practice to do so. +static func install_script_extension(child_script_path:String) -> void: + + # If this is called during initialization, add it with the other + # extensions to be installed taking inheritance chain into account + if ModLoaderStore.is_initializing: + ModLoaderStore.script_extensions.push_back(child_script_path) + + # If not, apply the extension directly + else: + ModLoader._apply_extension(child_script_path) + + +static func uninstall_script_extension(extension_script_path: String) -> void: + + # Currently this is the only thing we do, but it is better to expose + # this function like this for further changes + ModLoader._remove_extension(extension_script_path) + + +# This function should be called only when actually necessary +# as it can break the game and require a restart for mods +# that do not fully use the systems put in place by the mod loader, +# so anything that just uses add_node, move_node ecc... +# To not have your mod break on reload please use provided functions +# like ModLoader::save_scene, ModLoader::append_node_in_scene and +# all the functions that will be added in the next versions +# Used to reload already present mods and load new ones +func reload_mods() -> void: + + # Currently this is the only thing we do, but it is better to expose + # this function like this for further changes + ModLoader._reload_mods() + + +# Register an array of classes to the global scope, since Godot only does that in the editor. +# Format: { "base": "ParentClass", "class": "ClassName", "language": "GDScript", "path": "res://path/class_name.gd" } +# You can find these easily in the project.godot file under "_global_script_classes" +# (but you should only include classes belonging to your mod) +static func register_global_classes_from_array(new_global_classes: Array) -> void: + ModLoaderUtils.register_global_classes_from_array(new_global_classes) + var _savecustom_error: int = ProjectSettings.save_custom(ModLoaderUtils.get_override_path()) + + +# Add a translation file, eg "mytranslation.en.translation". The translation +# file should have been created in Godot already: When you import a CSV, such +# a file will be created for you. +static func add_translation_from_resource(resource_path: String) -> void: + if not File.new().file_exists(resource_path): + ModLoaderUtils.log_fatal("Tried to load a translation resource from a file that doesn't exist. The invalid path was: %s" % [resource_path], LOG_NAME) + return + + var translation_object: Translation = load(resource_path) + TranslationServer.add_translation(translation_object) + ModLoaderUtils.log_info("Added Translation from Resource -> %s" % resource_path, LOG_NAME) + + +static func append_node_in_scene(modified_scene: Node, node_name: String = "", node_parent = null, instance_path: String = "", is_visible: bool = true) -> void: + var new_node: Node + if not instance_path == "": + new_node = load(instance_path).instance() + else: + new_node = Node.instance() + if not node_name == "": + new_node.name = node_name + if is_visible == false: + new_node.visible = false + if not node_parent == null: + var tmp_node: Node = modified_scene.get_node(node_parent) + tmp_node.add_child(new_node) + new_node.set_owner(modified_scene) + else: + modified_scene.add_child(new_node) + new_node.set_owner(modified_scene) + + +static func save_scene(modified_scene: Node, scene_path: String) -> void: + var packed_scene := PackedScene.new() + var _pack_error := packed_scene.pack(modified_scene) + ModLoaderUtils.log_debug("packing scene -> %s" % packed_scene, LOG_NAME) + packed_scene.take_over_path(scene_path) + ModLoaderUtils.log_debug("save_scene - taking over path - new path -> %s" % packed_scene.resource_path, LOG_NAME) + ModLoader._saved_objects.append(packed_scene) diff --git a/addons/mod_loader/mod_loader.gd b/addons/mod_loader/mod_loader.gd index 66effb59..354c84a4 100644 --- a/addons/mod_loader/mod_loader.gd +++ b/addons/mod_loader/mod_loader.gd @@ -64,19 +64,13 @@ var mod_missing_dependencies := {} # Things to keep to ensure they are not garbage collected (used by `save_scene`) var _saved_objects := [] -# Store all extenders paths -var script_extensions := [] - # Store vanilla classes for script extension sorting var loaded_vanilla_parents_cache := {} -# Set to false after _init() -# Helps to decide whether a script extension should go through the _handle_script_extensions process -var is_initializing := true - # Stores all the taken over scripts for restoration var _saved_scripts := {} + # Main # ============================================================================= @@ -103,7 +97,7 @@ func _init() -> void: _load_mods() - is_initializing = false + ModLoaderStore.is_initializing = false func _load_mods() -> void: @@ -190,6 +184,8 @@ func _load_mods() -> void: ModLoaderUtils.log_success("DONE: Installed all script extensions", LOG_NAME) + ModLoaderStore.is_initializing = false + # Internal call to reload mods func _reload_mods() -> void: @@ -202,7 +198,7 @@ func _reset_mods() -> void: mod_data.clear() mod_load_order.clear() mod_missing_dependencies.clear() - script_extensions.clear() + ModLoaderStore.script_extensions.clear() _remove_all_extensions_from_all_scripts() @@ -611,7 +607,7 @@ func _init_mod(mod: ModData) -> void: # in a ScriptExtensionData resource func _handle_script_extensions()->void: var script_extension_data_array := [] - for extension_path in script_extensions: + for extension_path in ModLoaderStore.script_extensions: if not File.new().file_exists(extension_path): ModLoaderUtils.log_error("The child script path '%s' does not exist" % [extension_path], LOG_NAME) @@ -821,107 +817,33 @@ func _remove_all_extensions_from_all_scripts() -> void: _remove_all_extensions_from_script(script) -# Helpers +# Deprecated # ============================================================================= -# Helper functions to build mods - -# Add a script that extends a vanilla script. `child_script_path` should point -# to your mod's extender script, eg "MOD/extensions/singletons/utils.gd". -# Inside that extender script, it should include "extends {target}", where -# {target} is the vanilla path, eg: `extends "res://singletons/utils.gd"`. -# Note that your extender script doesn't have to follow the same directory path -# as the vanilla file, but it's good practice to do so. -func install_script_extension(child_script_path:String): - - # If this is called during initialization, add it with the other - # extensions to be installed taking inheritance chain into account - if is_initializing: - script_extensions.push_back(child_script_path) - - # If not, apply the extension directly - else: - _apply_extension(child_script_path) - - -# Remove a specific script from the vanilla extension chain -# This will remove only the specifically provided extension -# and keep all other extensions of that vanilla script running -func uninstall_script_extension(extension_script_path: String) -> void: - - # Currently this is the only thing we do, but it is better to expose - # this function like this for further changes - _remove_specific_extension_from_script(extension_script_path) +func install_script_extension(child_script_path:String) -> void: + ModLoaderDeprecated.deprecated_changed("ModLoader.install_script_extension", "ModLoaderMod.install_script_extension", "6.0.0") + ModLoaderMod.install_script_extension(child_script_path) -# This function should be called only when actually necessary -# as it can break the game and require a restart for mods -# that do not fully use the systems put in place by the mod loader, -# so anything that just uses add_node, move_node ecc... -# To not have your mod break on reload please use provided functions -# like ModLoader::save_scene, ModLoader::append_node_in_scene and -# all the functions that will be added in the next versions -# Used to reload already present mods and load new ones -func reload_mods() -> void: - - # Currently this is the only thing we do, but it is better to expose - # this function like this for further changes - _reload_mods() - - -# Register an array of classes to the global scope, since Godot only does that in the editor. -# Format: { "base": "ParentClass", "class": "ClassName", "language": "GDScript", "path": "res://path/class_name.gd" } -# You can find these easily in the project.godot file under "_global_script_classes" -# (but you should only include classes belonging to your mod) func register_global_classes_from_array(new_global_classes: Array) -> void: - ModLoaderUtils.register_global_classes_from_array(new_global_classes) - var _savecustom_error: int = ProjectSettings.save_custom(ModLoaderUtils.get_override_path()) + ModLoaderDeprecated.deprecated_changed("ModLoader.register_global_classes_from_array", "ModLoaderMod.register_global_classes_from_array", "6.0.0") + ModLoaderMod.register_global_classes_from_array(new_global_classes) -# Add a translation file, eg "mytranslation.en.translation". The translation -# file should have been created in Godot already: When you import a CSV, such -# a file will be created for you. func add_translation_from_resource(resource_path: String) -> void: - if not File.new().file_exists(resource_path): - ModLoaderUtils.log_fatal("Tried to load a translation resource from a file that doesn't exist. The invalid path was: %s" % [resource_path], LOG_NAME) - return - - var translation_object: Translation = load(resource_path) - TranslationServer.add_translation(translation_object) - ModLoaderUtils.log_info("Added Translation from Resource -> %s" % resource_path, LOG_NAME) + ModLoaderDeprecated.deprecated_changed("ModLoader.add_translation_from_resource", "ModLoaderMod.add_translation_from_resource", "6.0.0") + ModLoaderMod.add_translation_from_resource(resource_path) func append_node_in_scene(modified_scene: Node, node_name: String = "", node_parent = null, instance_path: String = "", is_visible: bool = true) -> void: - var new_node: Node - if not instance_path == "": - new_node = load(instance_path).instance() - else: - new_node = Node.instance() - if not node_name == "": - new_node.name = node_name - if is_visible == false: - new_node.visible = false - if not node_parent == null: - var tmp_node: Node = modified_scene.get_node(node_parent) - tmp_node.add_child(new_node) - new_node.set_owner(modified_scene) - else: - modified_scene.add_child(new_node) - new_node.set_owner(modified_scene) + ModLoaderDeprecated.deprecated_changed("ModLoader.append_node_in_scene", "ModLoaderMod.append_node_in_scene", "6.0.0") + ModLoaderMod.append_node_in_scene(modified_scene, node_name, node_parent, instance_path, is_visible) func save_scene(modified_scene: Node, scene_path: String) -> void: - var packed_scene := PackedScene.new() - var _pack_error := packed_scene.pack(modified_scene) - ModLoaderUtils.log_debug("packing scene -> %s" % packed_scene, LOG_NAME) - packed_scene.take_over_path(scene_path) - ModLoaderUtils.log_debug("save_scene - taking over path - new path -> %s" % packed_scene.resource_path, LOG_NAME) - _saved_objects.append(packed_scene) - - + ModLoaderDeprecated.deprecated_changed("ModLoader.save_scene", "ModLoaderMod.save_scene", "6.0.0") + ModLoaderMod.save_scene(modified_scene, scene_path) -# Deprecated -# ============================================================================= func get_mod_config(mod_dir_name: String = "", key: String = "") -> Dictionary: ModLoaderDeprecated.deprecated_changed("ModLoader.get_mod_config", "ModLoaderConfig.get_mod_config", "6.0.0") diff --git a/addons/mod_loader/mod_loader_setup.gd b/addons/mod_loader/mod_loader_setup.gd index ecfbf49b..f52948c4 100644 --- a/addons/mod_loader/mod_loader_setup.gd +++ b/addons/mod_loader/mod_loader_setup.gd @@ -50,6 +50,11 @@ const new_global_classes := [ "class": "ModLoaderDeprecated", "language": "GDScript", "path": "res://addons/mod_loader/api/deprecated.gd" + }, { + "base": "Object", + "class": "ModLoaderMod", + "language": "GDScript", + "path": "res://addons/mod_loader/api/mod.gd" }, { "base": "Object", "class": "ModLoaderGodot", diff --git a/addons/mod_loader/mod_loader_store.gd b/addons/mod_loader/mod_loader_store.gd index 75943bd2..5b9f03fb 100644 --- a/addons/mod_loader/mod_loader_store.gd +++ b/addons/mod_loader/mod_loader_store.gd @@ -14,6 +14,13 @@ const LOG_NAME = "ModLoader:Store" # Vars # ============================================================================= +# Set to false after ModLoader._init() +# Helps to decide whether a script extension should go through the _handle_script_extensions process +var is_initializing := true + +# Store all extenders paths +var script_extensions := [] + # True if ModLoader has displayed the warning about using zipped mods var has_shown_editor_zips_warning := false