extends Node var ConsoleService var data_path = "." var mod_dict = {} var mod_arr = [] var mod_instances = {} func _ready(): ConsoleService = $"/root/ConsoleService" # Locate all mods that can be loaded for the given game. func find_local_mods(): var dir = Directory.new() var mod_dir_exists = dir.open(data_path + "/mods") if (mod_dir_exists != 0): dir.change_dir(data_path) dir.make_dir("mods") dir.change_dir(data_path + "/mods") dir.list_dir_begin() while true: var file = dir.get_next() var basename = file.get_basename() if file == "": break elif file.ends_with(".zip"): var mod_desc = { "name": basename, "path": data_path + "/mods/" + file, "md5": File.new().get_md5(data_path + "/mods/" + file), "type": "zip" } mod_arr.append(mod_desc) mod_dict[mod_desc.name] = mod_desc elif dir.file_exists(basename+"/main.gd"): var mod_desc = { "name": basename, "path": data_path + "/mods/" + file, "md5": null, "type": "folder" } mod_arr.append(mod_desc) mod_dict[mod_desc.name] = mod_desc dir.list_dir_end() # Prepare initial instances of mods for preprocessing. Used to determine mod dependencies and configuration # Prior to final loading and initialization. func preload_instances(): if mod_arr.size() == 0: ConsoleService.error("[ModService] No mods found (Run find_local_mods and download_server_mods first)") return for mod_desc in mod_arr: mod_instances[mod_desc.name] = _get_mod_instance(mod_desc) if !mod_instances[mod_desc.name]: ConsoleService.error("[ModService] Failed to load mod '"+mod_desc.name+"'") func compute_dependency_graph(): if mod_instances.size() == 0: ConsoleService.error("[ModService] Mods have not been preloaded (Run preload_instances first)") return var resolved = []; # Properly ordered mod list. for mod_desc in mod_arr: _resolve_dependency(mod_desc, resolved, []) return resolved func _resolve_dependency(mod_desc: Dictionary, resolved: Array, unresolved: Array): if mod_desc in resolved: return # Seen is used to prevent unresolved.append(mod_desc) if not "dependencies" in mod_instances[mod_desc.name]: resolved.append(mod_desc) unresolved.erase(mod_desc) return var deps = mod_instances[mod_desc.name].dependencies for dep in deps: var dep_desc = mod_dict[dep] if not dep_desc in resolved: if dep_desc in unresolved: ConsoleService.error("[ModService] Circular dependency between '"+mod_desc.name+"' <-> '"+dep+"'") assert(false) if not dep in mod_instances: ConsoleService.error("[ModService] Mod '"+mod_desc.name+"' requires mod '"+dep+"' to be installed, but it is not") assert(false) _resolve_dependency(dep_desc, resolved, unresolved) resolved.append(mod_desc) unresolved.erase(mod_desc) func load_mods(): if mod_arr.size() == 0: print("[ModService] No mods found (Run find_mods first)") return for mod_desc in mod_arr: var mod_instance = _get_mod_instance(mod_desc) mod_instances[mod_desc.name] = mod_instance if !mod_instance: ConsoleService.log("[ModService] Failed to load mod '"+mod_desc.name+"'") continue $"/root/Main/Mods".add_child(mod_instance) if mod_instance.has_method('init'): mod_instance.init() func _get_mod_instance(mod_desc: Dictionary): if mod_desc.type == 'zip': ProjectSettings.load_resource_pack(mod_desc.path, true) var mod_script = load("res://mods/"+mod_desc.name+"/main.gd"); if !mod_script: return var mod_instance = mod_script.new() return mod_instance