mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
Up to now `eslint-plugin-brace-rules` was used to enforce a common brace style for JavaScript code. This plugin was however updated the last time over 9 years ago and will be incompatible with ESLint v10, as that [removes `context.getSourceCode()`][1], the plugin relies on. To keep the eslint config working with ESLint v10, this replaces `eslint-plugin-brace-rules` with the [`@stylistic/brace-style`][2] rule from `@stylistic/eslint-plugin`, a package we already use. While `@stylistic/brace-style` doesn't offer an option to format braces in exactly the same way as before, the "allman" style seems to be the one closest to the existing code. [1]: https://eslint.org/blog/2025/11/eslint-v10.0.0-alpha.0-released/#removed-deprecated-rule-context-members [2]: https://eslint.style/rules/brace-style
337 lines
9.8 KiB
JavaScript
337 lines
9.8 KiB
JavaScript
/**
|
|
* This class handles the loading of files.
|
|
*/
|
|
class TemplateLoader
|
|
{
|
|
constructor()
|
|
{
|
|
/**
|
|
* Raw Data Caches.
|
|
*/
|
|
this.auraData = {};
|
|
this.playerData = {};
|
|
this.technologyData = {};
|
|
this.templateData = {};
|
|
|
|
/**
|
|
* Partly-composed data.
|
|
*/
|
|
this.autoResearchTechList = this.findAllAutoResearchedTechs();
|
|
}
|
|
|
|
/**
|
|
* Loads raw aura template.
|
|
*
|
|
* Loads from local cache if available, else from file system.
|
|
*
|
|
* @param {string} templateName
|
|
* @return {Object} Object containing raw template data.
|
|
*/
|
|
loadAuraTemplate(templateName)
|
|
{
|
|
if (!(templateName in this.auraData))
|
|
{
|
|
const data = Engine.ReadJSONFile(this.AuraPath + templateName + ".json");
|
|
translateObjectKeys(data, this.AuraTranslateKeys);
|
|
|
|
this.auraData[templateName] = data;
|
|
}
|
|
|
|
return this.auraData[templateName];
|
|
}
|
|
|
|
/**
|
|
* Loads raw entity template.
|
|
*
|
|
* Loads from local cache if data present, else from file system.
|
|
*
|
|
* @param {string} templateName
|
|
* @param {string} civCode
|
|
* @return {Object} Object containing raw template data.
|
|
*/
|
|
loadEntityTemplate(templateName, civCode)
|
|
{
|
|
if (!(templateName in this.templateData))
|
|
{
|
|
// We need to clone the template because we want to perform some translations.
|
|
const data = clone(Engine.GetTemplate(templateName));
|
|
translateObjectKeys(data, this.EntityTranslateKeys);
|
|
|
|
if (data.Auras)
|
|
for (const auraID of data.Auras._string.split(/\s+/))
|
|
this.loadAuraTemplate(auraID);
|
|
|
|
if (data.Identity.Civ != this.DefaultCiv && civCode != this.DefaultCiv && data.Identity.Civ != civCode)
|
|
warn("The \"" + templateName + "\" template has a defined civ of \"" + data.Identity.Civ + "\". " +
|
|
"This does not match the currently selected civ \"" + civCode + "\".");
|
|
|
|
this.templateData[templateName] = data;
|
|
}
|
|
|
|
return this.templateData[templateName];
|
|
}
|
|
|
|
/**
|
|
* Loads raw player template.
|
|
*
|
|
* Loads from local cache if data present, else from file system.
|
|
*
|
|
* If a civ doesn't have their own civ-specific template,
|
|
* then we return the generic template.
|
|
*
|
|
* @param {string} civCode
|
|
* @return {Object} Object containing raw template data.
|
|
*/
|
|
loadPlayerTemplate(civCode)
|
|
{
|
|
if (!(civCode in this.playerData))
|
|
{
|
|
const templateName = this.buildPlayerTemplateName(civCode);
|
|
this.playerData[civCode] = Engine.GetTemplate(templateName);
|
|
|
|
// No object keys need to be translated
|
|
}
|
|
|
|
return this.playerData[civCode];
|
|
}
|
|
|
|
/**
|
|
* Loads raw technology template.
|
|
*
|
|
* Loads from local cache if available, else from file system.
|
|
*
|
|
* @param {string} templateName
|
|
* @return {Object} Object containing raw template data.
|
|
*/
|
|
loadTechnologyTemplate(templateName)
|
|
{
|
|
if (!(templateName in this.technologyData))
|
|
{
|
|
const data = Engine.ReadJSONFile(this.TechnologyPath + templateName + ".json");
|
|
translateObjectKeys(data, this.TechnologyTranslateKeys);
|
|
|
|
// Translate specificName as in GetTechnologyData() from gui/session/session.js
|
|
if (typeof (data.specificName) === 'object')
|
|
for (const civ in data.specificName)
|
|
data.specificName[civ] = translate(data.specificName[civ]);
|
|
else if (data.specificName)
|
|
warn("specificName should be an object of civ->name mappings in " + templateName + ".json");
|
|
|
|
this.technologyData[templateName] = data;
|
|
}
|
|
|
|
return this.technologyData[templateName];
|
|
}
|
|
|
|
/**
|
|
* @param {string} templateName
|
|
* @param {string} civCode
|
|
* @return {Object} Contains a list and the requirements of the techs in the pair
|
|
*/
|
|
loadTechnologyPairTemplate(templateName, civCode)
|
|
{
|
|
const template = this.loadTechnologyTemplate(templateName);
|
|
return {
|
|
"techs": [template.top, template.bottom],
|
|
"reqs": DeriveTechnologyRequirements(template, civCode)
|
|
};
|
|
}
|
|
|
|
deriveProduction(template, civCode)
|
|
{
|
|
const production = {
|
|
"techs": [],
|
|
"units": []
|
|
};
|
|
|
|
if (!template.Researcher && !template.Trainer)
|
|
return production;
|
|
|
|
if (template.Trainer?.Entities?._string)
|
|
for (let templateName of template.Trainer.Entities._string.split(" "))
|
|
{
|
|
templateName = templateName.replace(/\{(civ|native)\}/g, civCode);
|
|
if (Engine.TemplateExists(templateName))
|
|
production.units.push(templateName);
|
|
}
|
|
|
|
const appendTechnology = (technologyName) =>
|
|
{
|
|
const technology = this.loadTechnologyTemplate(technologyName, civCode);
|
|
if (DeriveTechnologyRequirements(technology, civCode))
|
|
production.techs.push(technologyName);
|
|
};
|
|
|
|
if (template.Researcher?.Technologies?._string)
|
|
for (let technologyName of template.Researcher.Technologies._string.split(" "))
|
|
{
|
|
if (technologyName.indexOf("{civ}") != -1)
|
|
{
|
|
const civTechName = technologyName.replace("{civ}", civCode);
|
|
technologyName = TechnologyTemplateExists(civTechName) ? civTechName : technologyName.replace("{civ}", "generic");
|
|
}
|
|
|
|
if (this.isPairTech(technologyName))
|
|
{
|
|
const technologyPair = this.loadTechnologyPairTemplate(technologyName, civCode);
|
|
if (technologyPair.reqs)
|
|
for (technologyName of technologyPair.techs)
|
|
appendTechnology(technologyName);
|
|
}
|
|
else
|
|
appendTechnology(technologyName);
|
|
}
|
|
|
|
return production;
|
|
}
|
|
|
|
deriveBuildQueue(template, civCode)
|
|
{
|
|
const buildQueue = [];
|
|
|
|
if (!template.Builder || !template.Builder.Entities._string)
|
|
return buildQueue;
|
|
|
|
for (let build of template.Builder.Entities._string.split(" "))
|
|
{
|
|
build = build.replace(/\{(civ|native)\}/g, civCode);
|
|
if (Engine.TemplateExists(build))
|
|
buildQueue.push(build);
|
|
}
|
|
|
|
return buildQueue;
|
|
}
|
|
|
|
deriveModifications(civCode, auraList)
|
|
{
|
|
const modificationData = [];
|
|
for (const techName of this.autoResearchTechList)
|
|
modificationData.push(GetTechnologyBasicDataHelper(this.loadTechnologyTemplate(techName), civCode));
|
|
|
|
for (const auraName of auraList)
|
|
modificationData.push(this.loadAuraTemplate(auraName));
|
|
|
|
return DeriveModificationsFromTechnologies(modificationData);
|
|
}
|
|
|
|
/**
|
|
* If a civ doesn't have its own civ-specific player template,
|
|
* this returns the name of the generic player template.
|
|
*
|
|
* @see simulation/helpers/Player.js GetPlayerTemplateName()
|
|
* (Which can't be combined with this due to different Engine contexts)
|
|
*/
|
|
buildPlayerTemplateName(civCode)
|
|
{
|
|
const templateName = this.PlayerPath + civCode;
|
|
if (Engine.TemplateExists(templateName))
|
|
return templateName;
|
|
|
|
warn("No template found for civ " + civCode + ".");
|
|
|
|
return this.PlayerPath + this.DefaultCiv;
|
|
}
|
|
|
|
/**
|
|
* Crudely iterates through every tech JSON file and identifies those
|
|
* that are auto-researched.
|
|
*
|
|
* @return {array} List of techs that are researched automatically
|
|
*/
|
|
findAllAutoResearchedTechs()
|
|
{
|
|
const techList = [];
|
|
for (const templateName of listFiles(this.TechnologyPath, ".json", true))
|
|
{
|
|
const data = this.loadTechnologyTemplate(templateName);
|
|
if (data && data.autoResearch)
|
|
techList.push(templateName);
|
|
}
|
|
return techList;
|
|
}
|
|
|
|
/**
|
|
* A template may be a variant of another template,
|
|
* eg. `*_house`, `*_trireme`, or a promotion.
|
|
*
|
|
* This method returns an array containing:
|
|
* [0] - The template's basename
|
|
* [1] - The variant type
|
|
* [2] - Further information (if available)
|
|
*
|
|
* e.g.:
|
|
* units/athen/infantry_swordsman_e
|
|
* -> ["units/athen/infantry_swordsman_b", TemplateVariant.promotion, "elite"]
|
|
*
|
|
* units/brit/support_female_citizen_house
|
|
* -> ["units/brit/support_female_citizen", TemplateVariant.unlockedByTechnology, "unlock_female_house"]
|
|
*/
|
|
getVariantBaseAndType(templateName, civCode)
|
|
{
|
|
if (!templateName || !Engine.TemplateExists(templateName))
|
|
return undefined;
|
|
|
|
templateName = removeFiltersFromTemplateName(templateName);
|
|
const template = this.loadEntityTemplate(templateName, civCode);
|
|
|
|
if (!dirname(templateName) || dirname(template["@parent"]) != dirname(templateName))
|
|
return [templateName, TemplateVariant.base];
|
|
|
|
const parentTemplate = this.loadEntityTemplate(template["@parent"], civCode);
|
|
const inheritedVariance = this.getVariantBaseAndType(template["@parent"], civCode);
|
|
|
|
if (parentTemplate.Identity)
|
|
{
|
|
if (parentTemplate.Identity.Civ && parentTemplate.Identity.Civ != template.Identity.Civ)
|
|
return [templateName, TemplateVariant.base];
|
|
|
|
if (parentTemplate.Identity.Rank && parentTemplate.Identity.Rank != template.Identity.Rank)
|
|
return [inheritedVariance[0], TemplateVariant.promotion, template.Identity.Rank.toLowerCase()];
|
|
}
|
|
|
|
if (parentTemplate.Upgrade)
|
|
for (const upgrade in parentTemplate.Upgrade)
|
|
if (parentTemplate.Upgrade[upgrade].Entity)
|
|
return [inheritedVariance[0], TemplateVariant.upgrade, upgrade.toLowerCase()];
|
|
|
|
if (template.Identity.Requirements?.Techs)
|
|
return [inheritedVariance[0], TemplateVariant.unlockedByTechnology, template.Identity.Requirements?.Techs];
|
|
|
|
if (parentTemplate.Cost)
|
|
for (const res in parentTemplate.Cost.Resources)
|
|
if (+parentTemplate.Cost.Resources[res])
|
|
return [inheritedVariance[0], TemplateVariant.trainable];
|
|
|
|
warn("Template variance unknown: " + templateName);
|
|
return [templateName, TemplateVariant.unknown];
|
|
}
|
|
|
|
isPairTech(technologyCode)
|
|
{
|
|
return !!this.loadTechnologyTemplate(technologyCode).top;
|
|
}
|
|
|
|
isPhaseTech(technologyCode)
|
|
{
|
|
return basename(technologyCode).startsWith("phase");
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Paths to certain files.
|
|
*
|
|
* It might be nice if we could get these from somewhere, instead of having them hardcoded here.
|
|
*/
|
|
TemplateLoader.prototype.AuraPath = "simulation/data/auras/";
|
|
TemplateLoader.prototype.PlayerPath = "special/players/";
|
|
TemplateLoader.prototype.TechnologyPath = "simulation/data/technologies/";
|
|
|
|
TemplateLoader.prototype.DefaultCiv = "gaia";
|
|
|
|
/**
|
|
* Keys of template values that are to be translated on load.
|
|
*/
|
|
TemplateLoader.prototype.AuraTranslateKeys = ["auraName", "auraDescription"];
|
|
TemplateLoader.prototype.EntityTranslateKeys = ["GenericName", "SpecificName", "Tooltip", "History"];
|
|
TemplateLoader.prototype.TechnologyTranslateKeys = ["genericName", "tooltip", "description"];
|