d13e86ba46
Rebranded src code and scripts to SPT Co-authored-by: clodan <clodan@clodan.com> Reviewed-on: https://dev.sp-tarkov.com/SPT-AKI/Server/pulls/345 Co-authored-by: Alex <clodan@noreply.dev.sp-tarkov.com> Co-committed-by: Alex <clodan@noreply.dev.sp-tarkov.com>
168 lines
4.8 KiB
TypeScript
168 lines
4.8 KiB
TypeScript
import { inject, injectable } from "tsyringe";
|
|
import { IPackageJsonData } from "@spt/models/spt/mod/IPackageJsonData";
|
|
import { ILogger } from "@spt/models/spt/utils/ILogger";
|
|
import { LocalisationService } from "@spt/services/LocalisationService";
|
|
|
|
@injectable()
|
|
export class ModLoadOrder
|
|
{
|
|
protected mods = new Map<string, IPackageJsonData>();
|
|
protected modsAvailable = new Map<string, IPackageJsonData>();
|
|
protected loadOrder = new Set<string>();
|
|
|
|
constructor(
|
|
@inject("WinstonLogger") protected logger: ILogger,
|
|
@inject("LocalisationService") protected localisationService: LocalisationService,
|
|
)
|
|
{}
|
|
|
|
public setModList(mods: Record<string, IPackageJsonData>): void
|
|
{
|
|
this.mods = new Map<string, IPackageJsonData>(Object.entries(mods));
|
|
this.modsAvailable = structuredClone(this.mods);
|
|
this.loadOrder = new Set<string>();
|
|
|
|
const visited = new Set<string>();
|
|
|
|
// invert loadBefore into loadAfter on specified mods
|
|
for (const [modName, modConfig] of this.modsAvailable)
|
|
{
|
|
if ((modConfig.loadBefore ?? []).length > 0)
|
|
{
|
|
this.invertLoadBefore(modName);
|
|
}
|
|
}
|
|
|
|
for (const modName of this.modsAvailable.keys())
|
|
{
|
|
this.getLoadOrderRecursive(modName, visited);
|
|
}
|
|
}
|
|
|
|
public getLoadOrder(): string[]
|
|
{
|
|
return Array.from(this.loadOrder);
|
|
}
|
|
|
|
public getModsOnLoadBefore(mod: string): Set<string>
|
|
{
|
|
if (!this.mods.has(mod))
|
|
{
|
|
throw new Error(`Mod: ${mod} isn't present.`);
|
|
}
|
|
|
|
const config = this.mods.get(mod);
|
|
|
|
const loadBefore = new Set<string>(config.loadBefore);
|
|
|
|
for (const loadBeforeMod of loadBefore)
|
|
{
|
|
if (!this.mods.has(loadBeforeMod))
|
|
{
|
|
loadBefore.delete(loadBeforeMod);
|
|
}
|
|
}
|
|
|
|
return loadBefore;
|
|
}
|
|
|
|
public getModsOnLoadAfter(mod: string): Set<string>
|
|
{
|
|
if (!this.mods.has(mod))
|
|
{
|
|
throw new Error(`Mod: ${mod} isn't present.`);
|
|
}
|
|
|
|
const config = this.mods.get(mod);
|
|
|
|
const loadAfter = new Set<string>(config.loadAfter);
|
|
|
|
for (const loadAfterMod of loadAfter)
|
|
{
|
|
if (!this.mods.has(loadAfterMod))
|
|
{
|
|
loadAfter.delete(loadAfterMod);
|
|
}
|
|
}
|
|
|
|
return loadAfter;
|
|
}
|
|
|
|
protected invertLoadBefore(mod: string): void
|
|
{
|
|
const loadBefore = this.getModsOnLoadBefore(mod);
|
|
|
|
for (const loadBeforeMod of loadBefore)
|
|
{
|
|
const loadBeforeModConfig = this.modsAvailable.get(loadBeforeMod);
|
|
|
|
loadBeforeModConfig.loadAfter ??= [];
|
|
loadBeforeModConfig.loadAfter.push(mod);
|
|
|
|
this.modsAvailable.set(loadBeforeMod, loadBeforeModConfig);
|
|
}
|
|
}
|
|
|
|
protected getLoadOrderRecursive(mod: string, visited: Set<string>): void
|
|
{
|
|
// Validate package
|
|
if (this.loadOrder.has(mod))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (visited.has(mod))
|
|
{
|
|
// Additional info to help debug
|
|
this.logger.debug(this.localisationService.getText("modloader-checking_mod", mod));
|
|
this.logger.debug(`${this.localisationService.getText("modloader-checked")}:`);
|
|
this.logger.debug(JSON.stringify(this.loadOrder, null, "\t"));
|
|
this.logger.debug(`${this.localisationService.getText("modloader-visited")}:`);
|
|
this.logger.debug(JSON.stringify(visited, null, "\t"));
|
|
|
|
throw new Error(this.localisationService.getText("modloader-cyclic_dependency"));
|
|
}
|
|
|
|
// Check dependencies
|
|
if (!this.modsAvailable.has(mod))
|
|
{
|
|
throw new Error(this.localisationService.getText("modloader-error_parsing_mod_load_order"));
|
|
}
|
|
|
|
const config = this.modsAvailable.get(mod);
|
|
|
|
config.loadAfter ??= [];
|
|
config.modDependencies ??= {};
|
|
|
|
const dependencies = new Set<string>(Object.keys(config.modDependencies));
|
|
|
|
for (const modAfter of config.loadAfter)
|
|
{
|
|
if (this.modsAvailable.has(modAfter))
|
|
{
|
|
if (this.modsAvailable.get(modAfter)?.loadAfter?.includes(mod))
|
|
{
|
|
throw new Error(
|
|
this.localisationService.getText("modloader-load_order_conflict", {
|
|
modOneName: mod,
|
|
modTwoName: modAfter,
|
|
}),
|
|
);
|
|
}
|
|
|
|
dependencies.add(modAfter);
|
|
}
|
|
}
|
|
|
|
visited.add(mod);
|
|
|
|
for (const mod of dependencies)
|
|
{
|
|
this.getLoadOrderRecursive(mod, visited);
|
|
}
|
|
|
|
visited.delete(mod);
|
|
this.loadOrder.add(mod);
|
|
}
|
|
}
|