mirror of
https://github.com/JHDev2006/Super-Mario-Bros.-Remastered-Public.git
synced 2025-10-22 07:28:14 +00:00
157 lines
5.7 KiB
GDScript
157 lines
5.7 KiB
GDScript
class_name _ModLoaderScriptExtension
|
|
extends RefCounted
|
|
|
|
# This Class provides methods for working with script extensions.
|
|
# Currently all of the included methods are internal and should only be used by the mod loader itself.
|
|
|
|
const LOG_NAME := "ModLoader:ScriptExtension"
|
|
|
|
|
|
# Sort script extensions by inheritance and apply them in order
|
|
static func handle_script_extensions() -> void:
|
|
var extension_paths := []
|
|
for extension_path in ModLoaderStore.script_extensions:
|
|
if FileAccess.file_exists(extension_path):
|
|
extension_paths.push_back(extension_path)
|
|
else:
|
|
ModLoaderLog.error(
|
|
"The child script path '%s' does not exist" % [extension_path], LOG_NAME
|
|
)
|
|
|
|
# Sort by inheritance
|
|
InheritanceSorting.new(extension_paths)
|
|
|
|
# Load and install all extensions
|
|
for extension in extension_paths:
|
|
var script: Script = apply_extension(extension)
|
|
_reload_vanilla_child_classes_for(script)
|
|
|
|
|
|
# Sorts script paths by their ancestors. Scripts are organized by their common
|
|
# ancestors then sorted such that scripts extending script A will be before
|
|
# a script extending script B if A is an ancestor of B.
|
|
class InheritanceSorting:
|
|
var stack_cache := {}
|
|
# This dictionary's keys are mod_ids and it stores the corresponding position in the load_order
|
|
var load_order := {}
|
|
|
|
func _init(inheritance_array_to_sort: Array) -> void:
|
|
_populate_load_order_table()
|
|
inheritance_array_to_sort.sort_custom(check_inheritances)
|
|
|
|
# Comparator function. return true if a should go before b. This may
|
|
# enforce conditions beyond the stated inheritance relationship.
|
|
func check_inheritances(extension_a: String, extension_b: String) -> bool:
|
|
var a_stack := cached_inheritances_stack(extension_a)
|
|
var b_stack := cached_inheritances_stack(extension_b)
|
|
|
|
var last_index: int
|
|
for index in a_stack.size():
|
|
if index >= b_stack.size():
|
|
return false
|
|
if a_stack[index] != b_stack[index]:
|
|
return a_stack[index] < b_stack[index]
|
|
last_index = index
|
|
|
|
if last_index < b_stack.size() - 1:
|
|
return true
|
|
|
|
return compare_mods_order(extension_a, extension_b)
|
|
|
|
# Returns a list of scripts representing all the ancestors of the extension
|
|
# script with the most recent ancestor last.
|
|
#
|
|
# Results are stored in a cache keyed by extension path
|
|
func cached_inheritances_stack(extension_path: String) -> Array:
|
|
if stack_cache.has(extension_path):
|
|
return stack_cache[extension_path]
|
|
|
|
var stack := []
|
|
|
|
var parent_script: Script = load(extension_path)
|
|
while parent_script:
|
|
stack.push_front(parent_script.resource_path)
|
|
parent_script = parent_script.get_base_script()
|
|
stack.pop_back()
|
|
|
|
stack_cache[extension_path] = stack
|
|
return stack
|
|
|
|
# Secondary comparator function for resolving scripts extending the same vanilla script
|
|
# Will return whether a comes before b in the load order
|
|
func compare_mods_order(extension_a: String, extension_b: String) -> bool:
|
|
var mod_a_id: String = _ModLoaderPath.get_mod_dir(extension_a)
|
|
var mod_b_id: String = _ModLoaderPath.get_mod_dir(extension_b)
|
|
|
|
return load_order[mod_a_id] < load_order[mod_b_id]
|
|
|
|
# Populate a load order dictionary for faster access and comparison between mod ids
|
|
func _populate_load_order_table() -> void:
|
|
var mod_index := 0
|
|
for mod in ModLoaderStore.mod_load_order:
|
|
load_order[mod.dir_name] = mod_index
|
|
mod_index += 1
|
|
|
|
|
|
static func apply_extension(extension_path: String) -> Script:
|
|
# Check path to file exists
|
|
if not FileAccess.file_exists(extension_path):
|
|
ModLoaderLog.error("The child script path '%s' does not exist" % [extension_path], LOG_NAME)
|
|
return null
|
|
|
|
var child_script: Script = load(extension_path)
|
|
# Adding metadata that contains the extension script path
|
|
# We cannot get that path in any other way
|
|
# Passing the child_script as is would return the base script path
|
|
# Passing the .duplicate() would return a '' path
|
|
child_script.set_meta("extension_script_path", extension_path)
|
|
|
|
# Force Godot to compile the script now.
|
|
# We need to do this here to ensure that the inheritance chain is
|
|
# properly set up, and multiple mods can chain-extend the same
|
|
# class multiple times.
|
|
# This is also needed to make Godot instantiate the extended class
|
|
# when creating singletons.
|
|
child_script.reload()
|
|
|
|
var parent_script: Script = child_script.get_base_script()
|
|
var parent_script_path: String = parent_script.resource_path
|
|
|
|
# We want to save scripts for resetting later
|
|
# All the scripts are saved in order already
|
|
if not ModLoaderStore.saved_scripts.has(parent_script_path):
|
|
ModLoaderStore.saved_scripts[parent_script_path] = []
|
|
# The first entry in the saved script array that has the path
|
|
# used as a key will be the duplicate of the not modified script
|
|
ModLoaderStore.saved_scripts[parent_script_path].append(parent_script.duplicate())
|
|
|
|
ModLoaderStore.saved_scripts[parent_script_path].append(child_script)
|
|
|
|
ModLoaderLog.info(
|
|
"Installing script extension: %s <- %s" % [parent_script_path, extension_path], LOG_NAME
|
|
)
|
|
child_script.take_over_path(parent_script_path)
|
|
|
|
return child_script
|
|
|
|
|
|
# Reload all children classes of the vanilla class we just extended
|
|
# Calling reload() the children of an extended class seems to allow them to be extended
|
|
# e.g if B is a child class of A, reloading B after apply an extender of A allows extenders of B to properly extend B, taking A's extender(s) into account
|
|
static func _reload_vanilla_child_classes_for(script: Script) -> void:
|
|
if script == null:
|
|
return
|
|
var current_child_classes := []
|
|
var actual_path: String = script.get_base_script().resource_path
|
|
var classes: Array = ProjectSettings.get_global_class_list()
|
|
|
|
for _class in classes:
|
|
if _class.path == actual_path:
|
|
current_child_classes.push_back(_class)
|
|
break
|
|
|
|
for _class in current_child_classes:
|
|
for child_class in classes:
|
|
if child_class.base == _class.get_class():
|
|
load(child_class.path).reload()
|