Cleanup of async method names

Add method comments
Reduction of code complexity inside `executeModsAsync()`
Add error message when loaded mod has no `main` property
This commit is contained in:
Dev 2023-10-20 12:23:19 +01:00
parent 33d3e6ce05
commit 3f07fc1bfc
2 changed files with 66 additions and 44 deletions

View File

@ -138,6 +138,7 @@
"modloader-missing_package_json": "Mod (%s) is missing package.json", "modloader-missing_package_json": "Mod (%s) is missing package.json",
"modloader-missing_package_json_property": "Mod {{modName}} package.json requires {{prop}} property", "modloader-missing_package_json_property": "Mod {{modName}} package.json requires {{prop}} property",
"modloader-mod_incompatible": "ModLoader: Mod (%s) is incompatible. It must implement at least one of IPostAkiLoadMod, IPostDBLoadMod, IPreAkiLoadMod", "modloader-mod_incompatible": "ModLoader: Mod (%s) is incompatible. It must implement at least one of IPostAkiLoadMod, IPostDBLoadMod, IPreAkiLoadMod",
"modloader-mod_has_no_main_property": "ModLoader: Mod (%s) is incompatible. It lacks a 'main' property",
"modloader-async_mod_error": "ModLoader: Error when loading async mod: %s", "modloader-async_mod_error": "ModLoader: Error when loading async mod: %s",
"modloader-no_mods_loaded": "Errors were found with mods, NO MODS WILL BE LOADED", "modloader-no_mods_loaded": "Errors were found with mods, NO MODS WILL BE LOADED",
"modloader-outdated_akiversion_field": "Mod %s is not compatible with the current version of AKI. You may encounter issues - no support will be provided!", "modloader-outdated_akiversion_field": "Mod %s is not compatible with the current version of AKI. You may encounter issues - no support will be provided!",
@ -145,7 +146,7 @@
"modloader-user_mod_folder_missing": "ModLoader: user/mod folder missing, creating...", "modloader-user_mod_folder_missing": "ModLoader: user/mod folder missing, creating...",
"modloader-mod_order_missing": "ModLoader: order.json is missing, creating...", "modloader-mod_order_missing": "ModLoader: order.json is missing, creating...",
"modloader-mod_order_error": "ModLoader: Errors were found in order.json, GOING TO USE DEFAULT LOAD ORDER", "modloader-mod_order_error": "ModLoader: Errors were found in order.json, GOING TO USE DEFAULT LOAD ORDER",
"modloader-mod_order_missing_from_json": "ModLoader: Mod %s is missing from order.json", "modloader-mod_order_missing_from_json": "ModLoader: Mod %s is missing from order.json, adding",
"modloader-visited": "visited", "modloader-visited": "visited",
"modloader-x_duplicates_found": "One or more duplicate mods found: %s, Only one version of a mod should be loaded", "modloader-x_duplicates_found": "One or more duplicate mods found: %s, Only one version of a mod should be loaded",
"openzone-unable_to_find_map": "Unable to add zones to location: %s as it doesn't exist", "openzone-unable_to_find_map": "Unable to add zones to location: %s as it doesn't exist",

View File

@ -57,8 +57,8 @@ export class PreAkiModLoader implements IModLoader
if (globalThis.G_MODS_ENABLED) if (globalThis.G_MODS_ENABLED)
{ {
PreAkiModLoader.container = container; PreAkiModLoader.container = container;
await this.importMods(); await this.importModsAsync();
await this.executeMods(container); await this.executeModsAsync(container);
} }
} }
@ -115,7 +115,7 @@ export class PreAkiModLoader implements IModLoader
return `${this.basepath}${mod}/`; return `${this.basepath}${mod}/`;
} }
protected async importMods(): Promise<void> protected async importModsAsync(): Promise<void>
{ {
if (!this.vfs.exists(this.basepath)) if (!this.vfs.exists(this.basepath))
{ {
@ -207,7 +207,7 @@ export class PreAkiModLoader implements IModLoader
// add mods // add mods
for (const mod of sortedMods) for (const mod of sortedMods)
{ {
await this.addMod(mod); await this.addModAsync(mod);
} }
this.modLoadOrder.setModList(this.imported); this.modLoadOrder.setModList(this.imported);
@ -299,7 +299,11 @@ export class PreAkiModLoader implements IModLoader
return loadedMods; return loadedMods;
} }
/**
* Is the passed in mod compatible with the running server version
* @param mod Mod to check compatibiltiy with AKI
* @returns True if compatible
*/
protected isModCombatibleWithAki(mod: IPackageJsonData): boolean protected isModCombatibleWithAki(mod: IPackageJsonData): boolean
{ {
const akiVersion = this.akiConfig.akiVersion; const akiVersion = this.akiConfig.akiVersion;
@ -329,53 +333,70 @@ export class PreAkiModLoader implements IModLoader
return true; return true;
} }
protected async executeMods(container: DependencyContainer): Promise<void> /**
* Execute each mod found in this.imported
* @param container Dependence container to give to mod when it runs
* @returns void promise
*/
protected async executeModsAsync(container: DependencyContainer): Promise<void>
{ {
// sort mods load order // Sort mods load order
const source = this.sortModsLoadOrder(); const source = this.sortModsLoadOrder();
// import mod classes // Import mod classes
for (const mod of source) for (const mod of source)
{ {
if (!this.imported[mod].main)
if ("main" in this.imported[mod])
{ {
const filepath = `${this.getModPath(mod)}${this.imported[mod].main}`; this.logger.error(this.localisationService.getText("modloader-mod_has_no_main_property", mod));
// import class
const modFilePath = `${process.cwd()}/${filepath}`;
// eslint-disable-next-line @typescript-eslint/no-var-requires continue;
const requiredMod = require(modFilePath); }
if (!this.modTypeCheck.isPostV3Compatible(requiredMod.mod)) const filepath = `${this.getModPath(mod)}${this.imported[mod].main}`;
// Import class
const modFilePath = `${process.cwd()}/${filepath}`;
// eslint-disable-next-line @typescript-eslint/no-var-requires
const requiredMod = require(modFilePath);
if (!this.modTypeCheck.isPostV3Compatible(requiredMod.mod))
{
this.logger.error(this.localisationService.getText("modloader-mod_incompatible", mod));
delete this.imported[mod];
return;
}
// Perform async load of mod
if (this.modTypeCheck.isPreAkiLoadAsync(requiredMod.mod))
{
try
{ {
this.logger.error(this.localisationService.getText("modloader-mod_incompatible", mod)); await (requiredMod.mod as IPreAkiLoadModAsync).preAkiLoadAsync(container);
delete this.imported[mod];
return;
}
if (this.modTypeCheck.isPreAkiLoadAsync(requiredMod.mod))
{
try
{
await (requiredMod.mod as IPreAkiLoadModAsync).preAkiLoadAsync(container);
globalThis[mod] = requiredMod;
}
catch (err)
{
this.logger.error(this.localisationService.getText("modloader-async_mod_error", `${err?.message ?? ""}\n${err.stack ?? ""}`));
}
}
if (this.modTypeCheck.isPreAkiLoad(requiredMod.mod))
{
(requiredMod.mod as IPreAkiLoadMod).preAkiLoad(container);
globalThis[mod] = requiredMod; globalThis[mod] = requiredMod;
} }
catch (err)
{
this.logger.error(this.localisationService.getText("modloader-async_mod_error", `${err?.message ?? ""}\n${err.stack ?? ""}`));
}
continue;
}
// Perform sync load of mod
if (this.modTypeCheck.isPreAkiLoad(requiredMod.mod))
{
(requiredMod.mod as IPreAkiLoadMod).preAkiLoad(container);
globalThis[mod] = requiredMod;
} }
} }
} }
/**
* Read loadorder.json (create if doesnt exist) and return sorted list of mods
* @returns string array of sorted mod names
*/
public sortModsLoadOrder(): string[] public sortModsLoadOrder(): string[]
{ {
// if loadorder.json exists: load it, otherwise generate load order // if loadorder.json exists: load it, otherwise generate load order
@ -393,7 +414,7 @@ export class PreAkiModLoader implements IModLoader
* Compile mod and add into class property "imported" * Compile mod and add into class property "imported"
* @param mod Name of mod to compile/add * @param mod Name of mod to compile/add
*/ */
protected async addMod(mod: string): Promise<void> protected async addModAsync(mod: string): Promise<void>
{ {
const modPath = this.getModPath(mod); const modPath = this.getModPath(mod);
const packageData = this.jsonUtil.deserialize<IPackageJsonData>(this.vfs.readFile(`${modPath}/package.json`)); const packageData = this.jsonUtil.deserialize<IPackageJsonData>(this.vfs.readFile(`${modPath}/package.json`));
@ -455,13 +476,13 @@ export class PreAkiModLoader implements IModLoader
depIdx++; depIdx++;
} }
//if the mod has no extra dependencies return as there's nothing that needs to be done. // If the mod has no extra dependencies return as there's nothing that needs to be done.
if (dependenciesToInstall.length === 0) if (dependenciesToInstall.length === 0)
{ {
return; return;
} }
//if this feature flag is set to false, we warn the user he has a mod that requires extra dependencies and might not work, point them in the right direction on how to enable this feature. // If this feature flag is set to false, we warn the user he has a mod that requires extra dependencies and might not work, point them in the right direction on how to enable this feature.
if (!this.akiConfig.features.autoInstallModDependencies) if (!this.akiConfig.features.autoInstallModDependencies)
{ {
this.logger.warning(this.localisationService.getText("modloader-installing_external_dependencies_disabled", { this.logger.warning(this.localisationService.getText("modloader-installing_external_dependencies_disabled", {
@ -475,18 +496,18 @@ export class PreAkiModLoader implements IModLoader
return; return;
} }
//temporarily rename package.json because otherwise npm, pnpm and any other package manager will forcefully download all packages in dependencies without any way of disabling this behavior // Temporarily rename package.json because otherwise npm, pnpm and any other package manager will forcefully download all packages in dependencies without any way of disabling this behavior
this.vfs.rename(`${modPath}/package.json`, `${modPath}/package.json.bak`); this.vfs.rename(`${modPath}/package.json`, `${modPath}/package.json.bak`);
this.vfs.writeFile(`${modPath}/package.json`, "{}"); this.vfs.writeFile(`${modPath}/package.json`, "{}");
this.logger.info(this.localisationService.getText("modloader-installing_external_dependencies", {name: pkg.name, author: pkg.author})); this.logger.info(this.localisationService.getText("modloader-installing_external_dependencies", {name: pkg.name, author: pkg.author}));
const pnpmPath = path.join(process.cwd(), (globalThis.G_RELEASE_CONFIGURATION ? "Aki_Data/Server/@pnpm/exe" : "node_modules/@pnpm/exe"), (os.platform() === "win32" ? "pnpm.exe" : "pnpm")); const pnpmPath = path.join(process.cwd(), (globalThis.G_RELEASE_CONFIGURATION ? "Aki_Data/Server/@pnpm/exe" : "node_modules/@pnpm/exe"), (os.platform() === "win32" ? "pnpm.exe" : "pnpm"));
let command: string = `${pnpmPath} install `; let command = `${pnpmPath} install `;
command += dependenciesToInstall.map(([depName, depVersion]) => `${depName}@${depVersion}`).join(" "); command += dependenciesToInstall.map(([depName, depVersion]) => `${depName}@${depVersion}`).join(" ");
execSync(command, { cwd: modPath }); execSync(command, { cwd: modPath });
// delete the new blank package.json then rename the backup back to the original name // Delete the new blank package.json then rename the backup back to the original name
this.vfs.removeFile(`${modPath}/package.json`); this.vfs.removeFile(`${modPath}/package.json`);
this.vfs.rename(`${modPath}/package.json.bak`, `${modPath}/package.json`); this.vfs.rename(`${modPath}/package.json.bak`, `${modPath}/package.json`);
} }