Merge branch '3.8.0' of https://dev.sp-tarkov.com/SPT-AKI/Server into testing-redux

This commit is contained in:
Dev 2023-11-06 14:54:34 +00:00
commit 214e81ba83
14 changed files with 96 additions and 87 deletions

View File

@ -81,7 +81,6 @@
"modloader-outdated_dependency": "Mod {{mod}} benötigt {{modDependency}} {{requiredVersion}}. Vorhandene Version ist {{currentVersion}}",
"modloader-user_mod_folder_missing": "ModLoader: User/Mod Ordner fehlt, wird erzeugt...",
"modloader-visited": "kontrolliert",
"modloader-x_duplicates_found": "ModLoader: Ein oder mehrere Mods sind mehrfach vorhanden: %s, jeweils nur eine Version sollte geladen werden",
"payment-not_enough_money_to_complete_transation": "Nicht genug Geld vorhanden um Transaktion abzuschließen, benötigt: {{amountToPay}}, vorhanden: {{amountAvailable}}",
"payment-not_enough_money_to_complete_transation_short": "Nicht genug Geld vorhanden um Transaktion abzuschließen",
"payment-zero_price_no_payment": "Preis ist 0, keine Bezahlung nötig",

View File

@ -136,7 +136,7 @@
"modloader-is_client_mod": "Mod (%s) is a client mod and should be placed in the following folder: /spt/bepinex/plugins",
"modloader-installing_external_dependencies": "Installing dependencies for Mod: {{name}} by: {{author}}",
"modloader-installing_external_dependencies_disabled": "Mod: {{name}} by: {{author}} requires external dependencies but the feature is currently disabled, go to \"{{configPath}}\", set \"{{configOption}}\" to true and restart the server.\nBy enabling this you accept all responsibility for what {{name}} downloads to your machine.",
"modloader-skipped_mod": "Skipping loading of Mod: {{name}} by: {{author}}",
"modloader-skipped_mod": "Skipping loading of Mod: {{mod}}",
"modloader-loaded_mod": "Mod: {{name}} version: {{version}} by: {{author}} loaded",
"modloader-loading_mods": "ModLoader: loading %s server mods...",
"modloader-main_property_not_js": "Mod %s package.json main property must be a .js file",
@ -156,7 +156,7 @@
"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, adding",
"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": "You are trying to load more than one version of %s mod. Skipping all of them.",
"openzone-unable_to_find_map": "Unable to add zones to location: %s as it doesn't exist",
"payment-not_enough_money_to_complete_transation": "Profile did not have enough money to complete transaction: needed {{amountToPay}}, has {{amountAvailable}}",
"payment-not_enough_money_to_complete_transation_short": "Not enough money to complete transaction",

View File

@ -123,7 +123,6 @@
"modloader-mod_order_error": "ModLoader: Errores encontrados en order.json, volviendo a orden de carga por defecto.",
"modloader-mod_order_missing_from_json": "ModLoader: Mod %s no está en order.json",
"modloader-visited": "visitado",
"modloader-x_duplicates_found": "Una o más modificaciones duplicadas fueron encontradas: %s, Solo una versión de una modificación debería estar instalada",
"openzone-unable_to_find_map": "No se pueden añadir zonas a la localización: %s ya que no existe.",
"payment-not_enough_money_to_complete_transation": "El perfil no tenía suficiente dinero para completar la transacción: necesitado {{amountToPay}}, tiene {{amountAvailable}}",
"payment-not_enough_money_to_complete_transation_short": "Insuficiente dinero para completar la transacción",

View File

@ -141,7 +141,6 @@
"modloader-mod_order_error": "ModLoader: des erreurs ont été trouvées dans order.json, REMISE A DEFAUT DE L'ORDRE DE CHARGEMENT",
"modloader-mod_order_missing_from_json": "ModLoader: Mod %s is missing from order.json",
"modloader-visited": "visité",
"modloader-x_duplicates_found": "un ou plusiers mod dupliqués ont été trouvés: %s, seulement une seule version de ce mod doit être installée",
"openzone-unable_to_find_map": "Impossible d'ajouter des zones à la localisation: %s car elle n'existe pas",
"payment-not_enough_money_to_complete_transation": "le profil n'a pas assez d'argent pour completer la transaction : somme requise {{amountToPay}}, possède uniquement {{amountAvailable}}",
"payment-not_enough_money_to_complete_transation_short": "pas assez d'argent pour compléter la transaction",

View File

@ -114,7 +114,6 @@
"modloader-outdated_dependency": "Mod {{mod}} richiede {{modDependency}} versione {{requiredVersion}}. Attualmente è installata la versione {{currentVersion}}",
"modloader-user_mod_folder_missing": "ModLoader: manca la cartella 'user/mod', creazione in corso...",
"modloader-visited": "visitato",
"modloader-x_duplicates_found": "Trovate una o piu mod duplicate: %s, Solo una sola versione di una mod puo essere caricata",
"openzone-unable_to_find_map": "Impossibile aggiungere zone al luogo: %s non esiste",
"payment-not_enough_money_to_complete_transation": "Il profilo non ha abbastanza soldi per completare la transizione: servono {{amountToPay}}, ha {{amountAvailable}}",
"payment-not_enough_money_to_complete_transation_short": "Soldi insufficienti per completare la transizione",

View File

@ -81,7 +81,6 @@
"modloader-outdated_dependency": "モッド {{mod}} の前提条件である {{modDependency}} はバージョン {{requiredVersion}} を要求されています。現在インストールされているバージョンは {{currentVersion}}",
"modloader-user_mod_folder_missing": "モッド読み込むツール: user/mod フォルダーが見つかりません、フォルダー作成中...",
"modloader-visited": "ビジットをカウントした。",
"modloader-x_duplicates_found": "一点以上の重複モッドを発見しました: %s、 読み込まれるモッドは一バージョンのみです。",
"payment-not_enough_money_to_complete_transation": "プロファイルに通貨が足りません: {{amountToPay}} が必要で、 {{amountAvailable}} が所持しています。",
"payment-not_enough_money_to_complete_transation_short": "プロファイルに通貨が足りません。",
"payment-zero_price_no_payment": "価額が0なので、支払いは要りません。",

View File

@ -124,7 +124,6 @@
"modloader-mod_order_error": "ModLoader: order.json에서 오류가 발견되어 기본 로드 순서를 사용하려고 합니다.",
"modloader-mod_order_missing_from_json": "ModLoader: Mod %s is missing from order.json",
"modloader-visited": "방문 된 모드 (visited)",
"modloader-x_duplicates_found": "중복된 모드 발견: %s, 한 버전의 모드만 사용해야 합니다",
"openzone-unable_to_find_map": "맵 개방 구역(openZone)에 지역(zone)을 추가 할 수 없음: %s 은(는) 존재하지 않는 지역입니다.",
"payment-not_enough_money_to_complete_transation": "프로필에 충분한 소지금이 없음: 필요한 금액 {{amountToPay}}, 소지금 {{amountAvailable}}",
"payment-not_enough_money_to_complete_transation_short": "거래하기 위한 충분한 소지금이 없습니다",

View File

@ -141,7 +141,6 @@
"modloader-mod_order_error": "ModLoader: Errors zijn gevonden in order.json, STANDAARD LAAD VOLGORDE WORDT GEBRUIKT",
"modloader-mod_order_missing_from_json": "ModLoader: Mod %s mist in order.json",
"modloader-visited": "bezocht",
"modloader-x_duplicates_found": "Een of meer identieke mods gevonden: %s, Er moet maar een versie van een mod geladen worden",
"openzone-unable_to_find_map": "Kon geen zones toevoegen aan locatie: %s omdat deze niet bestaat",
"payment-not_enough_money_to_complete_transation": "Profiel heeft niet genoeg geld om de transactie te voltooien: heeft {{amountToPay}} nodig, heeft {{amountAvailable}}",
"payment-not_enough_money_to_complete_transation_short": "Niet genoeg geld om de transactie te voltooien",

View File

@ -141,7 +141,6 @@
"modloader-mod_order_error": "ModLoader: Wykryto błędy w order.json, UŻYCIE DOMYŚLNEJ KOLEJNOŚCI ŁADOWANIA",
"modloader-mod_order_missing_from_json": "ModLoader: Mod %s brakuje w pliku order.json",
"modloader-visited": "odwiedzony",
"modloader-x_duplicates_found": "Znaleziono jeden lub wiele zduplikowanych modów: %s, Została załadowana tylko jedna wersja modu",
"openzone-unable_to_find_map": "Nie można dodać stref do lokalizacji: %s, ponieważ nie istnieje",
"payment-not_enough_money_to_complete_transation": "Profil nie miał wystarczająco pieniędzy, aby sfinalizować transakcję: potrzebne {{amountToPay}}, posiada {{amountAvailable}}",
"payment-not_enough_money_to_complete_transation_short": "Brak wystarczającej ilości pieniędzy, aby sfinalizować transakcję",

View File

@ -141,7 +141,6 @@
"modloader-mod_order_error": "Загрузчик модов: В файле order.json найдены ошибки, БУДЕТ ИСПОЛЬЗОВАТЬСЯ СТАНДАРТНЫЙ ПОРЯДОК ЗАГРУЗКИ",
"modloader-mod_order_missing_from_json": "Загрузчик модов: Мод %s отсутствует в файле order.json",
"modloader-visited": "посетил",
"modloader-x_duplicates_found": "Один или более дубликат(ов) мода обнаружено: %s, только одна версия мода должна быть загружена",
"openzone-unable_to_find_map": "Невозможно добавить зоны в локацию: %s, так как она не существует",
"payment-not_enough_money_to_complete_transation": "В профиле не хватает денег для завершения сделки: необходимо {{amountToPay}}, имеется {{amountAvailable}}",
"payment-not_enough_money_to_complete_transation_short": "Не хватает денег для завершения сделки",

View File

@ -115,7 +115,6 @@
"modloader-outdated_dependency": "Mod {{mod}} kräver {{modDependency}} version {{requiredVersion}}. Aktuell installerad version är {{currentVersion}}",
"modloader-user_mod_folder_missing": "ModLoader: användare/mod-mapp saknas, skapar...",
"modloader-visited": "besökta",
"modloader-x_duplicates_found": "En eller flera dubbletter av mods hittades: %s, Endast en version av en mod bör laddas",
"openzone-unable_to_find_map": "Det går inte att lägga till zoner på plats: %s eftersom den inte finns",
"payment-not_enough_money_to_complete_transation": "Profilen hade inte tillräckligt med pengar för att slutföra transaktionen: behövde {{amountToPay}}, har {{amountAvailable}}",
"payment-not_enough_money_to_complete_transation_short": "Inte tillräckligt med pengar för att slutföra transaktionen",

View File

@ -84,7 +84,6 @@
"modloader-mod_order_error": "ModLoader: order.json dosyasında hata bulundu, VARSAYILAN SIRALAMA ILE DEVAM EDILECEK",
"modloader-mod_order_missing_from_json": "ModLoader: %s modu order.json dosyasında eksik",
"modloader-visited": "ziyaret",
"modloader-x_duplicates_found": "Bir veya daha fazla yinelenen mod bulundu: %s, Bir modun yalnızca bir sürümü yüklenmelidir",
"payment-not_enough_money_to_complete_transation": "Profilde işlemi tamamlamak için yeterli para yoktu: gerekli {{amountToPay}}, sahip olmak {{amountAvailable}}",
"payment-not_enough_money_to_complete_transation_short": "İşlemi tamamlamak için yeterli para yok",
"payment-zero_price_no_payment": "Fiyatı 0, ödemeye gerek yok",

View File

@ -141,7 +141,6 @@
"modloader-mod_order_error": "模组加载器order.json中发现错误即 将 使 用 默 认 加 载 顺 序",
"modloader-mod_order_missing_from_json": "模组加载器order.json中缺少模组%s",
"modloader-visited": "已访问",
"modloader-x_duplicates_found": "找到一个或者更多的重复模组:%s一个模组只该加载一个版本",
"openzone-unable_to_find_map": "无法为地点:%s添加空间因为它不存在",
"payment-not_enough_money_to_complete_transation": "存档没有足够金钱完成交易,需要{{amountToPay}},拥有{{amountAvailable}}",
"payment-not_enough_money_to_complete_transation_short": "没有足够的钱来完成交易",

View File

@ -32,7 +32,7 @@ export class PreAkiModLoader implements IModLoader
protected imported: Record<string, IPackageJsonData> = {};
protected akiConfig: ICoreConfig;
protected serverDependencies: Record<string, string>;
protected skippedMods: string[] = [];
protected skippedMods: Set<string>;
constructor(
@inject("WinstonLogger") protected logger: ILogger,
@ -50,6 +50,7 @@ export class PreAkiModLoader implements IModLoader
const packageJsonPath: string = path.join(__dirname, "../../package.json");
this.serverDependencies = JSON.parse(this.vfs.readFile(packageJsonPath)).dependencies;
this.skippedMods = new Set();
}
public async load(container: DependencyContainer): Promise<void>
@ -125,7 +126,10 @@ export class PreAkiModLoader implements IModLoader
return;
}
let mods: string[] = this.vfs.getDirs(this.basepath);
/**
* array of mod folder names
*/
const mods: string[] = this.vfs.getDirs(this.basepath);
this.logger.info(this.localisationService.getText("modloader-loading_mods", mods.length));
@ -153,18 +157,22 @@ export class PreAkiModLoader implements IModLoader
}
}
// Validate and remove broken mods from mod list
const validMods = this.getValidMods(mods);
const modPackageData = this.getModsPackageData(validMods);
this.checkForDuplicateMods(modPackageData);
// Used to check all errors before stopping the load execution
let errorsFound = false;
// Validate and remove broken mods from mod list
const brokenMods: string[] = this.getBrokenMods(mods);
mods = mods.filter( ( mod ) => !brokenMods.includes( mod ) );
const modPackageData = this.getModsPackageData(mods);
this.checkForDuplicateMods(modPackageData);
for (const modFolderName in modPackageData)
for (const [modFolderName, modToValidate] of modPackageData)
{
const modToValidate = modPackageData[modFolderName];
if (this.shouldSkipMod(modToValidate))
{
// skip error checking and dependency install for mods already marked as skipped.
continue;
}
// if the mod has library dependencies check if these dependencies are bundled in the server, if not install them
if (modToValidate.dependencies && Object.keys(modToValidate.dependencies).length > 0 && !this.vfs.exists(`${this.basepath}${modFolderName}/node_modules`))
@ -199,15 +207,23 @@ export class PreAkiModLoader implements IModLoader
// sort mod order
const missingFromOrderJSON = {};
const sortedMods = mods.sort((prev, next) => this.sortMods(prev, next, missingFromOrderJSON));
validMods.sort((prev, next) => this.sortMods(prev, next, missingFromOrderJSON));
// log the missing mods from order.json
Object.keys(missingFromOrderJSON).forEach((missingMod) => (this.logger.debug(this.localisationService.getText("modloader-mod_order_missing_from_json", missingMod))));
// add mods
for (const mod of sortedMods)
for (const mod of validMods)
{
await this.addModAsync(mod);
const pkg = modPackageData.get(mod);
if (this.shouldSkipMod(pkg))
{
this.logger.warning(this.localisationService.getText("modloader-skipped_mod", { mod: mod }));
continue;
}
await this.addModAsync(mod, pkg);
}
this.modLoadOrder.setModList(this.imported);
@ -237,63 +253,64 @@ export class PreAkiModLoader implements IModLoader
/**
* Check for duplicate mods loaded, show error if any
* @param modPackageData Dictionary of mod package.json data
* @param modPackageData map of mod package.json data
*/
protected checkForDuplicateMods(modPackageData: Record<string, IPackageJsonData>): void
protected checkForDuplicateMods(modPackageData: Map<string, IPackageJsonData>): void
{
const modNames = [];
for (const modKey in modPackageData)
{
const mod = modPackageData[modKey];
modNames.push(`${mod.author}-${mod.name}`);
}
const dupes = this.getDuplicates(modNames);
if (dupes?.length > 0)
{
this.logger.error(this.localisationService.getText("modloader-x_duplicates_found", dupes.join(",")));
}
}
const grouppedMods: Map<string, IPackageJsonData[]> = new Map();
/**
* Check for and return duplicate strings inside an array
* @param stringArray Array to check for duplicates
* @returns string array of duplicates, empty if none found
*/
protected getDuplicates(stringArray: string[]): string[]
{
return stringArray.filter((s => v => s.has(v) || !s.add(v))(new Set));
}
/**
* Get an array of mods with errors that prevent them from working with SPT
* @param mods mods to validate
* @returns Mod names as array
*/
protected getBrokenMods(mods: string[]): string[]
{
const brokenMods: string[] = [];
for (const mod of mods)
for (const mod of modPackageData.values())
{
if (!this.validMod(mod))
const name = `${mod.author}-${mod.name}`;
grouppedMods.set(name, [...(grouppedMods.get(name) ?? []), mod]);
// if there's more than one entry for a given mod it means there's at least 2 mods with the same author and name trying to load.
if (grouppedMods.get(name).length > 1 && !this.skippedMods.has(name))
{
brokenMods.push(mod);
this.skippedMods.add(name);
}
}
return brokenMods;
// at this point this.skippedMods only contains mods that are duplicated, so we can just go through every single entry and log it
for (const modName of this.skippedMods)
{
this.logger.error(this.localisationService.getText("modloader-x_duplicates_found", modName));
}
}
/**
* Returns an array of valid mods.
*
* @param mods mods to validate
* @returns array of mod folder names
*/
protected getValidMods(mods: string[]): string[]
{
const validMods: string[] = [];
for (const mod of mods)
{
if (this.validMod(mod))
{
validMods.push(mod);
}
}
return validMods;
}
/**
* Get packageJson data for mods
* @param mods mods to get packageJson for
* @returns dictionary <modName - package.json>
* @returns map <modFolderName - package.json>
*/
protected getModsPackageData(mods: string[]): Record<string, IPackageJsonData>
protected getModsPackageData(mods: string[]): Map<string, IPackageJsonData>
{
const loadedMods: Record<string, IPackageJsonData> = {};
const loadedMods = new Map<string, IPackageJsonData>();
for (const mod of mods)
{
loadedMods[mod] = this.jsonUtil.deserialize(this.vfs.readFile(`${this.getModPath(mod)}/package.json`));
loadedMods.set(mod, this.jsonUtil.deserialize(this.vfs.readFile(`${this.getModPath(mod)}/package.json`)));
}
return loadedMods;
@ -414,18 +431,11 @@ export class PreAkiModLoader implements IModLoader
* Compile mod and add into class property "imported"
* @param mod Name of mod to compile/add
*/
protected async addModAsync(mod: string): Promise<void>
protected async addModAsync(mod: string, pkg: IPackageJsonData): Promise<void>
{
const modPath = this.getModPath(mod);
const packageData = this.jsonUtil.deserialize<IPackageJsonData>(this.vfs.readFile(`${modPath}/package.json`));
if (this.skippedMods.includes(packageData.name))
{
this.logger.warning(this.localisationService.getText("modloader-skipped_mod", {name: packageData.name, author: packageData.author}));
return;
}
const isBundleMod = packageData.isBundleMod ?? false;
const isBundleMod = pkg.isBundleMod ?? false;
if (isBundleMod)
{
@ -444,16 +454,27 @@ export class PreAkiModLoader implements IModLoader
else
{
// rename the mod entry point to .ts if it's set to .js because G_MODS_TRANSPILE_TS is set to false
packageData.main = (packageData.main).replace(".js", ".ts");
pkg.main = (pkg.main).replace(".js", ".ts");
}
}
// Purge scripts data from package object
packageData.scripts = {};
pkg.scripts = {};
// Add mod to imported list
this.imported[mod] = {...packageData, dependencies: packageData.modDependencies};
this.logger.info(this.localisationService.getText("modloader-loaded_mod", {name: packageData.name, version: packageData.version, author: packageData.author}));
this.imported[mod] = {...pkg, dependencies: pkg.modDependencies};
this.logger.info(this.localisationService.getText("modloader-loaded_mod", {name: pkg.name, version: pkg.version, author: pkg.author}));
}
/**
* Checks if a given mod should be loaded or skipped.
*
* @param pkg mod package.json data
* @returns
*/
protected shouldSkipMod(pkg: IPackageJsonData): boolean
{
return this.skippedMods.has(`${pkg.author}-${pkg.name}`);
}
protected autoInstallDependencies(modPath: string, pkg: IPackageJsonData): void
@ -492,7 +513,7 @@ export class PreAkiModLoader implements IModLoader
configOption: "autoInstallModDependencies"
}));
this.skippedMods.push(pkg.name);
this.skippedMods.add(`${pkg.author}-${pkg.name}`);
return;
}
@ -512,7 +533,7 @@ export class PreAkiModLoader implements IModLoader
this.vfs.rename(`${modPath}/package.json.bak`, `${modPath}/package.json`);
}
protected areModDependenciesFulfilled(pkg: IPackageJsonData, loadedMods: Record<string, IPackageJsonData>): boolean
protected areModDependenciesFulfilled(pkg: IPackageJsonData, loadedMods: Map<string, IPackageJsonData>): boolean
{
if (!pkg.modDependencies)
{
@ -524,15 +545,15 @@ export class PreAkiModLoader implements IModLoader
for (const [modDependency, requiredVersion ] of Object.entries(pkg.modDependencies))
{
// Raise dependency version incompatible if the dependency is not found in the mod list
if (!(modDependency in loadedMods))
if (!loadedMods.has(modDependency))
{
this.logger.error(this.localisationService.getText("modloader-missing_dependency", {mod: modName, modDependency: modDependency}));
return false;
}
if (!semver.satisfies(loadedMods[modDependency].version, requiredVersion))
if (!semver.satisfies(loadedMods.get(modDependency).version, requiredVersion))
{
this.logger.error(this.localisationService.getText("modloader-outdated_dependency", {mod: modName, modDependency: modDependency, currentVersion: loadedMods[modDependency].version, requiredVersion: requiredVersion}));
this.logger.error(this.localisationService.getText("modloader-outdated_dependency", {mod: modName, modDependency: modDependency, currentVersion: loadedMods.get(modDependency).version, requiredVersion: requiredVersion}));
return false;
}
}
@ -540,7 +561,7 @@ export class PreAkiModLoader implements IModLoader
return true;
}
protected isModCompatible(mod: IPackageJsonData, loadedMods: Record<string, IPackageJsonData>): boolean
protected isModCompatible(mod: IPackageJsonData, loadedMods: Map<string, IPackageJsonData>): boolean
{
const incompatbileModsList = mod.incompatibilities;
if (!incompatbileModsList)
@ -551,7 +572,7 @@ export class PreAkiModLoader implements IModLoader
for (const incompatibleModName of incompatbileModsList)
{
// Raise dependency version incompatible if any incompatible mod is found
if (incompatibleModName in loadedMods)
if (loadedMods.has(incompatibleModName))
{
this.logger.error(this.localisationService.getText("modloader-incompatible_mod_found", {author: mod.author, modName: mod.name, incompatibleModName: incompatibleModName}));
return false;