Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
96 changes: 96 additions & 0 deletions addons/mod_loader/api/mod.gd
Original file line number Diff line number Diff line change
@@ -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)
114 changes: 18 additions & 96 deletions addons/mod_loader/mod_loader.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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
# =============================================================================

Expand All @@ -103,7 +97,7 @@ func _init() -> void:

_load_mods()

is_initializing = false
ModLoaderStore.is_initializing = false


func _load_mods() -> void:
Expand Down Expand Up @@ -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:
Expand All @@ -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()


Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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")
Expand Down
5 changes: 5 additions & 0 deletions addons/mod_loader/mod_loader_setup.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
7 changes: 7 additions & 0 deletions addons/mod_loader/mod_loader_store.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down