123 lines
3.4 KiB
GDScript
123 lines
3.4 KiB
GDScript
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
|