diff --git a/assetloader/assetloader.gd b/assetloader/AssetLoader.gd similarity index 67% rename from assetloader/assetloader.gd rename to assetloader/AssetLoader.gd index ac1c73a..a48f03c 100644 --- a/assetloader/assetloader.gd +++ b/assetloader/AssetLoader.gd @@ -1,7 +1,8 @@ +@tool extends Object class_name AssetLoader -#region declaration +#region Declaration --- # === CONST === const DEFAULT_ASSETS_PATH = "res://mods" @@ -20,12 +21,12 @@ var mod_folders:PackedStringArray var critical_error := false var mod_paths : PackedStringArray -var mod_manifests : Dictionary[String,Dictionary] +var mod_manifests : Dictionary[String,ModManifest] # === SIGNALS === signal loading_finished -#endregion +#endregion --- func _init() -> void: @@ -45,6 +46,8 @@ func _init() -> void: critical_error = true return +# === PRIVATE === + ## This will show a native MessageBox on Windows, ## a native dialog on macOS, and GTK/QT dialog on Linux. @@ -52,11 +55,13 @@ func _show_error_popup(message: String) -> void: OS.alert("AssetLoader:"+message, "AssetLoader:Error") +# === PUBLIC === func load_all(): load_mods() - #load_mods_content() + load_mods_content() +## Load and unpack all .pck before serialization func load_mods(): for mod in mod_folders: var mod_name = mod @@ -64,14 +69,28 @@ func load_mods(): var manifest_path = mod_path + "/" + mod_name + "." + MOD_INFOS_EXTENSION if FileAccess.file_exists(manifest_path): var manifest_file := FileAccess.open(manifest_path, FileAccess.READ) - var manifest: Dictionary = JSON.parse_string(manifest_file.get_as_text()) - if typeof(manifest) == TYPE_DICTIONARY: # always true ? - mod_paths.append(mod_path) - if manifest["id"]: - mod_manifests[manifest["id"]] = manifest - print_verbose("Mod loaded: %s" % manifest["name"]) - else: - mod_manifests[mod_name] = manifest - print_verbose("Mod loaded: %s" % manifest["name"]) + var manifest: ModManifest = ModManifest.new_from_file(manifest_file) + if !manifest:continue + if !mod_manifests.has(manifest.id): + mod_manifests[manifest.id] = manifest + print_verbose("Mod manifest is loaded: %s" % manifest.name) + var modpacks:Array = manifest.get_mod_packs() + if manifest.packs.size() > 0: + for pack in manifest.packs: + if dir.file_exists(ASSETS_PATH + "/" + mod_name + pack + ".pck"): # variable re-usage here + pass + modpacks.append(pack) + else: + push_warning("Another mod as the same id:\n %s will not be loaded" % manifest_path) + + else: + push_warning("No manifest found in %s" % manifest_path) for mod in mod_manifests: print("Mod loaded: %s" % mod) + + + + + +func load_mods_content(): + pass diff --git a/assetloader/assetloader.gd.uid b/assetloader/AssetLoader.gd.uid similarity index 100% rename from assetloader/assetloader.gd.uid rename to assetloader/AssetLoader.gd.uid diff --git a/assetloader/ModManifest.gd b/assetloader/ModManifest.gd new file mode 100644 index 0000000..9a72b38 --- /dev/null +++ b/assetloader/ModManifest.gd @@ -0,0 +1,69 @@ +extends RefCounted +class_name ModManifest +## Object for easier json manifest operations. + +#region declaration +var id: StringName ## Mod unique id. Internal usage only. +var name: String ## Displayed mod name +var version: String ## Displayed mod version +var desc: String ## Displayed mod description +var author:String ## Mod Author +var dependencies := [] ## Mod dependecies, optional. +var tags:= [] ## ["units", "vehicles", "buildings", "textures", "maps", "quests"] +var packs := [] ## Optional. [br] By default [param mod_id.pck] is loaded. + +var _validated = false ## true if the manifest as a correct syntax +#endregion + +# === Init === # Initialization logic, if needed + + +func _init(_id:String,_name:=_id,_desc:="",_dependencies:=[],_packs:=[],_author := "",_version:="1.0",_tags:=[]) -> void: + if id == "": + push_warning("Mod id cannot be null, fallback to "+_name) + self.id = _name.strip_edges() + else: + self.id = _id + self.name= _name + self.version = _version + self.desc=_desc + self.author = _author + self.dependencies = _dependencies + self.tags= _tags + self.packs =_packs + + +# === PUBLIC FUNCTIONS === +func is_valid() -> bool: + return _validated + + +## Return [param null] if the json is not parsed correctly +static func new_from_file(_manifest_file:FileAccess) -> ModManifest: + var man_dic:Dictionary = JSON.parse_string(_manifest_file.get_as_text()) + if !man_dic: + push_warning("Invalid manifest format: %s" % _manifest_file.get_path()) + return null + var _id: String + if man_dic["id"]: + _id = man_dic["id"] + else: + _id = str(_manifest_file).strip_edges() # ugly. But should fallback on a generated one. + return ModManifest.new(_id, man_dic["name"],man_dic["desc"],man_dic["dependencies"],man_dic["packs"],man_dic["author"],man_dic["version"],man_dic["tags"]) + + +## Check if the array is empty, if not, return the packs and the main one if it's not on the list. +func get_mod_packs() -> Array[String]: + if packs.size() > 0: + # check if the main pack is in the list + if packs.has(self.id): + return self.packs + else: + var pck = self.packs + pck.append(id) + return pck + return [id] + +# === PRIVATE FUNCTIONS === # use _underscore() to make the difference between private and public functions + +# ==================== diff --git a/assetloader/ModManifest.gd.uid b/assetloader/ModManifest.gd.uid new file mode 100644 index 0000000..3986a55 --- /dev/null +++ b/assetloader/ModManifest.gd.uid @@ -0,0 +1 @@ +uid://5ii0g6tl4dpj diff --git a/autoloads/bootstrap.gd b/autoloads/bootstrap.gd index bcfc9d2..81e2ad5 100644 --- a/autoloads/bootstrap.gd +++ b/autoloads/bootstrap.gd @@ -10,6 +10,7 @@ func _ready() -> void: return loader.load_all() # TODO: Making it async _start_game() + loader = null ## This will show a native MessageBox on Windows, diff --git a/mods/README.md b/mods/README.md new file mode 100644 index 0000000..3c95344 --- /dev/null +++ b/mods/README.md @@ -0,0 +1,25 @@ +# Modding + +By default a modding system is made. +Each mods need to be in the mod folder, each folder is a mod containing at least a `mod_id.modinfo` and a `mod_id.pck` and optionnaly a `mod_id..loc` + +## modinfo example + +```json +{ + "id": "mod_id", + "name": "Mod Displayed Name", + "version": "1.0", + "desc": "Mod displayed description", + "author": "Mod Author", + "dependencies": [], + "tags": ["units", "vehicles", "buildings", "textures", "maps", "quests"], + "packs": ["mod_id", "addon_pack"] +} +``` + +*id* (string) : should be the unique name of the mod. Only used internally. should be the same as the modinfo and the folder name +*name* (string) : the displayed name of the mode +*version* (string) : to keep track of the version of you mod. displayed in the mod manager +*author* (string) : you, the awesome modder +*dependecies* (array of strings) : list of ids of requiered mod. be sure to load them before this one diff --git a/mods/base_content/units/soldier.json b/mods/base_content/units/soldier.json new file mode 100644 index 0000000..e69de29 diff --git a/mods/modinfo_example.json b/mods/modinfo_example.json new file mode 100644 index 0000000..d2e46b0 --- /dev/null +++ b/mods/modinfo_example.json @@ -0,0 +1,10 @@ +{ + "id": "mod_id", + "name": "Mod Displayed Name", + "version": "1.0", + "desc": "Mod displayed description", + "author": "Mod Author", + "dependencies": [], + "tags": ["units", "vehicles", "buildings", "textures", "maps", "quests"], + "packs": ["mod_id", "addon_pack"] +}