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
57 changes: 23 additions & 34 deletions addons/mod_loader/api/mod.gd
Original file line number Diff line number Diff line change
Expand Up @@ -74,50 +74,39 @@ static func add_translation(resource_path: String) -> void:
ModLoaderLog.info("Added Translation from Resource -> %s" % resource_path, LOG_NAME)


# Appends a new node to a modified scene.
# Refreshes a specific scene by marking it for refresh.
#
# This function is useful if a script extension is not automatically applied.
# This situation can occur when a script is attached to a preloaded scene.
# If you encounter issues where your script extension is not working as expected,
# try to identify the scene to which it is attached and use this method to refresh it.
# This will reload already loaded scenes and apply the script extension.
#
# Parameters:
# - modified_scene (Node): The modified scene where the node will be appended.
# - node_name (String): (Optional) The name of the new node. Default is an empty string.
# - node_parent (Node): (Optional) The parent node where the new node will be added. Default is null (direct child of modified_scene).
# - instance_path (String): (Optional) The path to a scene resource that will be instantiated as the new node.
# Default is an empty string resulting in a `Node` instance.
# - is_visible (bool): (Optional) If true, the new node will be visible. Default is true.
# - scene_path (String): The path to the scene file to be refreshed.
#
# Returns: void
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).instantiate()
else:
new_node = Node.new()
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 refresh_scene(scene_path: String) -> void:
if scene_path in ModLoaderStore.scenes_to_refresh:
return

ModLoaderStore.scenes_to_refresh.push_back(scene_path)
ModLoaderLog.debug("Added \"%s\" to be refreshed." % scene_path, LOG_NAME)


# Saves a modified scene to a file.
# Extends a specific scene by providing a callable function to modify it.
# The callable receives an instance of the vanilla_scene as the first parameter.
#
# Parameters:
# - modified_scene (Node): The modified scene instance to be saved.
# - scene_path (String): The path to the scene file that will be replaced.
# - scene_vanilla_path (String): The path to the vanilla scene file.
# - edit_callable (Callable): The callable function to modify the scene.
#
# Returns: void
static func save_scene(modified_scene: Node, scene_path: String) -> void:
var packed_scene := PackedScene.new()
var _pack_error := packed_scene.pack(modified_scene)
ModLoaderLog.debug("packing scene -> %s" % packed_scene, LOG_NAME)
packed_scene.take_over_path(scene_path)
ModLoaderLog.debug("save_scene - taking over path - new path -> %s" % packed_scene.resource_path, LOG_NAME)
ModLoaderStore.saved_objects.append(packed_scene)
static func extend_scene(scene_vanilla_path: String, edit_callable: Callable) -> void:
if not ModLoaderStore.scenes_to_modify.has(scene_vanilla_path):
ModLoaderStore.scenes_to_modify[scene_vanilla_path] = []

ModLoaderStore.scenes_to_modify[scene_vanilla_path].push_back(edit_callable)


# Gets the ModData from the provided namespace
Expand Down
43 changes: 43 additions & 0 deletions addons/mod_loader/internal/scene_extension.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
class_name _ModLoaderSceneExtension
extends RefCounted

# This Class provides methods for working with scene extensions.
# Currently all of the included methods are internal and should only be used by the mod loader itself.

const LOG_NAME := "ModLoader:SceneExtension"


# Iterates over the list of scenes to refresh them from storage.
# Used to apply script extensions to preloaded scenes.
static func refresh_scenes() -> void:
for scene_path in ModLoaderStore.scenes_to_refresh:
# Refresh cached scenes from storage
var _scene_from_file: PackedScene = ResourceLoader.load(scene_path, "", ResourceLoader.CACHE_MODE_REPLACE)
ModLoaderLog.debug("Refreshed scene at path: %s" % scene_path, LOG_NAME)


# Iterates over the list of scenes to modify and applies the specified edits to each scene.
static func handle_scene_extensions() -> void:
for scene_path in ModLoaderStore.scenes_to_modify.keys():
for edit_callable in ModLoaderStore.scenes_to_modify[scene_path]:
var cached_scene: PackedScene = load(scene_path)
var cached_scene_instance: Node = cached_scene.instantiate()
var edited_scene: Node = edit_callable.call(cached_scene_instance)
_save_scene(edited_scene, scene_path)


# Saves a modified scene to resource cache.
# Further attempts to load this scene by path will instead return this resource.
#
# Parameters:
# - modified_scene (Node): The modified scene instance to be saved.
# - scene_path (String): The path to the scene file that will be replaced.
#
# Returns: void
static func _save_scene(modified_scene: Node, scene_path: String) -> void:
var packed_scene := PackedScene.new()
var _pack_error := packed_scene.pack(modified_scene)
ModLoaderLog.debug("packing scene -> %s" % packed_scene, LOG_NAME)
packed_scene.take_over_path(scene_path)
ModLoaderLog.debug("save_scene - taking over path - new path -> %s" % packed_scene.resource_path, LOG_NAME)
ModLoaderStore.saved_objects.append(packed_scene)
18 changes: 7 additions & 11 deletions addons/mod_loader/mod_loader.gd
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,12 @@ func _load_mods() -> void:

ModLoaderLog.success("DONE: Installed all script extensions", LOG_NAME)

_ModLoaderSceneExtension.refresh_scenes()

_ModLoaderSceneExtension.handle_scene_extensions()

ModLoaderLog.success("DONE: Applied all scene extensions", LOG_NAME)

ModLoaderStore.is_initializing = false


Expand Down Expand Up @@ -235,7 +241,7 @@ func _load_mod_zips() -> Dictionary:
# Loop over the mod zips in the "mods" directory
var loaded_zip_data := _ModLoaderFile.load_zips_in_folder(mods_folder_path)
zip_data.merge(loaded_zip_data)

if ModLoaderStore.ml_options.load_from_steam_workshop or ModLoaderStore.ml_options.steam_workshop_enabled:
# If we're using Steam workshop, loop over the workshop item directories
var loaded_workshop_zip_data := _ModLoaderSteam.load_steam_workshop_zips()
Expand Down Expand Up @@ -398,16 +404,6 @@ func add_translation_from_resource(resource_path: String) -> void:
ModLoaderMod.add_translation(resource_path)


func append_node_in_scene(modified_scene: Node, node_name: String = "", node_parent = null, instance_path: String = "", is_visible: bool = true) -> void:
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:
ModLoaderDeprecated.deprecated_changed("ModLoader.save_scene", "ModLoaderMod.save_scene", "6.0.0")
ModLoaderMod.save_scene(modified_scene, scene_path)


func get_mod_config(mod_dir_name: String = "", key: String = "") -> ModConfig:
ModLoaderDeprecated.deprecated_changed("ModLoader.get_mod_config", "ModLoaderConfig.get_config", "6.0.0")
return ModLoaderConfig.get_config(mod_dir_name, ModLoaderConfig.DEFAULT_CONFIG_NAME)
Expand Down
8 changes: 8 additions & 0 deletions addons/mod_loader/mod_loader_store.gd
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ var previous_mod_dirs := []
# Store all extenders paths
var script_extensions := []

# Stores scene paths that need to be reloaded from file.
# Used to apply extension to scripts that are attached to preloaded scenes.
var scenes_to_refresh := []

# Dictionary of callables to modify a specific scene.
# Example property: "scene_path": [Callable, Callable]
var scenes_to_modify := {}

# True if ModLoader has displayed the warning about using zipped mods
var has_shown_editor_zips_warning := false

Expand Down