/** * 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"];