Index: binaries/data/mods/public/globalscripts/ModificationTemplates.js =================================================================== --- binaries/data/mods/public/globalscripts/ModificationTemplates.js +++ binaries/data/mods/public/globalscripts/ModificationTemplates.js @@ -17,11 +17,6 @@ deepfreeze(this.templates); } -ModificationTemplates.prototype.GetNames = function() -{ - return this.names; -}; - ModificationTemplates.prototype.Has = function(name) { return this.names.indexOf(name) != -1; @@ -37,11 +32,9 @@ return this.templates; }; - function LoadModificationTemplates() { global.AuraTemplates = new ModificationTemplates("simulation/data/auras/"); - global.TechnologyTemplates = new ModificationTemplates("simulation/data/technologies/"); } /** Index: binaries/data/mods/public/globalscripts/Templates.js =================================================================== --- binaries/data/mods/public/globalscripts/Templates.js +++ binaries/data/mods/public/globalscripts/Templates.js @@ -313,8 +313,12 @@ if (template.Cost) { ret.cost = {}; - for (let resCode in template.Cost.Resources) - ret.cost[resCode] = getEntityValue("Cost/Resources/" + resCode); + if (template.Cost.Resources) + { + ret.cost.resources = {} + for (let resCode in template.Cost.Resources) + ret.cost.resources[resCode] = getEntityValue("Cost/Resources/" + resCode); + } if (template.Cost.Population) ret.cost.population = getEntityValue("Cost/Population"); @@ -494,6 +498,15 @@ "GainMultiplier": getEntityValue("Trader/GainMultiplier") }; + if (template.Technology) + ret.technology = { + "choice": template.Technology.Choice, + "choiceRoot": template.Technology.ChoiceRoot, + "ID": template.Technology.ID, + "modifications": template.Technology.Modifiers, + "supersedes": template.Technology.Supersedes + }; + if (template.Treasure) { ret.treasure = { @@ -551,49 +564,6 @@ } /** - * Get basic information about a technology template. - * @param {Object} template - A valid template as obtained by loading the tech JSON file. - * @param {string} civ - Civilization for which the tech requirements should be calculated. - */ -function GetTechnologyBasicDataHelper(template, civ) -{ - return { - "name": { - "generic": template.genericName - }, - "icon": template.icon ? "technologies/" + template.icon : undefined, - "description": template.description, - "reqs": DeriveTechnologyRequirements(template, civ), - "modifications": template.modifications, - "affects": template.affects, - "replaces": template.replaces - }; -} - -/** - * Get information about a technology template. - * @param {Object} template - A valid template as obtained by loading the tech JSON file. - * @param {string} civ - Civilization for which the specific name and tech requirements should be returned. - * @param {Object} resources - An instance of the Resources class. - */ -function GetTechnologyDataHelper(template, civ, resources) -{ - let ret = GetTechnologyBasicDataHelper(template, civ); - - if (template.specificName) - ret.name.specific = template.specificName[civ] || template.specificName.generic; - - ret.cost = { "time": template.researchTime ? +template.researchTime : 0 }; - for (let type of resources.GetCodes()) - ret.cost[type] = +(template.cost && template.cost[type] || 0); - - ret.tooltip = template.tooltip; - ret.requirementsTooltip = template.requirementsTooltip || ""; - - return ret; -} - -/** * Get information about an aura template. * @param {object} template - A valid template as obtained by loading the aura JSON file. */ Index: binaries/data/mods/public/gui/common/tooltips.js =================================================================== --- binaries/data/mods/public/gui/common/tooltips.js +++ binaries/data/mods/public/gui/common/tooltips.js @@ -971,7 +971,7 @@ if ("Techs" in requirements && !requirements.Techs._string.includes(" ") && requirements.Techs._string[0] != "!") return objectionFont(sprintf(translate("Requires %(technology)s"), { - "technology": getEntityNames(GetTechnologyData(requirements.Techs._string, civ)) + "technology": getEntityNames(GetTemplateData(Engine.GetTemplate(requirements.Techs._string), civ)) })); // More complex ones need a tooltip. Index: binaries/data/mods/public/gui/reference/common/TemplateLoader.js =================================================================== --- binaries/data/mods/public/gui/reference/common/TemplateLoader.js +++ binaries/data/mods/public/gui/reference/common/TemplateLoader.js @@ -100,23 +100,17 @@ * * Loads from local cache if available, else from file system. * + * @param {string} civCode * @param {string} templateName * @return {Object} Object containing raw template data. */ - loadTechnologyTemplate(templateName) + loadTechnologyTemplate(templateName, civCode) { if (!(templateName in this.technologyData)) { - let data = Engine.ReadJSONFile(this.TechnologyPath + templateName + ".json"); + const data = clone(Engine.GetTemplate(templateName)); translateObjectKeys(data, this.TechnologyTranslateKeys); - // Translate specificName as in GetTechnologyData() from gui/session/session.js - if (typeof (data.specificName) === 'object') - for (let 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; } @@ -232,21 +226,15 @@ } /** - * Crudely iterates through every tech JSON file and identifies those + * Crudely iterates through every tech file and identifies those * that are auto-researched. * * @return {array} List of techs that are researched automatically */ findAllAutoResearchedTechs() { - let techList = []; - for (let templateName of listFiles(this.TechnologyPath, ".json", true)) - { - let data = this.loadTechnologyTemplate(templateName); - if (data && data.autoResearch) - techList.push(templateName); - } - return techList; + return Engine.FindAllTemplates().filter(templateName => + this.loadTechnologyTemplate(templateName)?.Technology?.AutoResearched); } /** @@ -333,4 +321,4 @@ */ TemplateLoader.prototype.AuraTranslateKeys = ["auraName", "auraDescription"]; TemplateLoader.prototype.EntityTranslateKeys = ["GenericName", "SpecificName", "Tooltip", "History"]; -TemplateLoader.prototype.TechnologyTranslateKeys = ["genericName", "tooltip", "description"]; +TemplateLoader.prototype.TechnologyTranslateKeys = ["GenericName", "SpecificName", "Tooltip", "History"]; Index: binaries/data/mods/public/gui/reference/common/TemplateParser.js =================================================================== --- binaries/data/mods/public/gui/reference/common/TemplateParser.js +++ binaries/data/mods/public/gui/reference/common/TemplateParser.js @@ -192,8 +192,8 @@ else if (technologyName in this.techs[civCode]) return this.techs[civCode][technologyName]; - let template = this.TemplateLoader.loadTechnologyTemplate(technologyName); - const tech = GetTechnologyDataHelper(template, civCode, g_ResourceData, this.modifiers[civCode] || {}); + let template = this.TemplateLoader.loadTechnologyTemplate(technologyName, civCode); + const tech = GetTemplateDataHelper(template, civCode, g_ResourceData, this.modifiers[civCode] || {}); tech.name.internal = technologyName; if (template.pair !== undefined) Index: binaries/data/mods/public/gui/session/ResearchProgress.js =================================================================== --- binaries/data/mods/public/gui/session/ResearchProgress.js +++ binaries/data/mods/public/gui/session/ResearchProgress.js @@ -68,7 +68,7 @@ { this.researcher = researchStatus.researcher; - let template = GetTechnologyData(techName, g_Players[g_ViewedPlayer].civ); + const template = GetTemplateData(techName, g_ViewedPlayer); let modifier = "stretched:"; if (researchStatus.paused) modifier += "color:0 0 0 127:grayscale:"; Index: binaries/data/mods/public/gui/session/chat/ChatMessageFormatSimulation.js =================================================================== --- binaries/data/mods/public/gui/session/chat/ChatMessageFormatSimulation.js +++ binaries/data/mods/public/gui/session/chat/ChatMessageFormatSimulation.js @@ -116,7 +116,7 @@ return { "text": sprintf(message, { "player": colorizePlayernameByID(msg.player), - "phaseName": getEntityNames(GetTechnologyData(msg.phaseName, g_Players[msg.player].civ)) + "phaseName": getEntityNames(GetTemplateData(msg.phaseName, msg.player)) }) }; } Index: binaries/data/mods/public/gui/session/selection_details.js =================================================================== --- binaries/data/mods/public/gui/session/selection_details.js +++ binaries/data/mods/public/gui/session/selection_details.js @@ -80,7 +80,10 @@ // Rank if (entState.identity && entState.identity.rank && entState.identity.classes) { - const rankObj = GetTechnologyData(entState.identity.rankTechName, playerState.civ); + const rankObj = Engine.GUIInterfaceCall("GetTemplateData", { + "templateName": entState.identity.rankTechName, + "player": entState.player + }); Engine.GetGUIObjectByName("rankIcon").tooltip = sprintf(translate("%(rank)s Rank"), { "rank": translateWithContext("Rank", entState.identity.rank) }) + (rankObj ? "\n" + rankObj.tooltip : ""); Index: binaries/data/mods/public/gui/session/selection_panels.js =================================================================== --- binaries/data/mods/public/gui/session/selection_panels.js +++ binaries/data/mods/public/gui/session/selection_panels.js @@ -545,7 +545,7 @@ if (queuedItem.unitTemplate) template = GetTemplateData(queuedItem.unitTemplate); else if (queuedItem.technologyTemplate) - template = GetTechnologyData(queuedItem.technologyTemplate, GetSimState().players[data.player].civ); + template = GetTemplateData(queuedItem.technologyTemplate); else { warning("Unknown production queue template " + uneval(queuedItem)); @@ -712,10 +712,10 @@ // Handle one or two techs (tech pair) let player = data.player; let playerState = GetSimState().players[player]; - for (let tech of data.item.tech.pair ? [data.item.tech.bottom, data.item.tech.top] : [data.item.tech]) + for (let tech of data.item.tech.Choice ? data.item.tech.Choice.split(" ") : [data.item.tech]) { - // Don't change the object returned by GetTechnologyData - let template = clone(GetTechnologyData(tech, playerState.civ)); + // Don't change the object returned by GetTemplateData + let template = clone(GetTemplateData(tech)); if (!template) return false; @@ -737,8 +737,8 @@ "player": player }); - let requirementsPassed = Engine.GuiInterfaceCall("CheckTechnologyRequirements", { - "tech": tech, + const requirementsPassed = Engine.GuiInterfaceCall("AreRequirementsMet", { + "requirements": template.requirements, "player": player }); Index: binaries/data/mods/public/gui/session/session.js =================================================================== --- binaries/data/mods/public/gui/session/session.js +++ binaries/data/mods/public/gui/session/session.js @@ -221,30 +221,12 @@ if (!(templateName in g_TemplateData)) { let template = Engine.GuiInterfaceCall("GetTemplateData", { "templateName": templateName, "player": player }); - translateObjectKeys(template, ["specific", "generic", "tooltip"]); + translateObjectKeys(template, ["specific", "generic", "tooltip", "history"]); g_TemplateData[templateName] = deepfreeze(template); } return g_TemplateData[templateName]; } -function GetTechnologyData(technologyName, civ) -{ - if (!g_TechnologyData[civ]) - g_TechnologyData[civ] = {}; - - if (!(technologyName in g_TechnologyData[civ])) - { - const tech = TechnologyTemplates.Get(technologyName); - if (!tech) - return; - let template = GetTechnologyDataHelper(tech, civ, g_ResourceData); - translateObjectKeys(template, ["specific", "generic", "description", "tooltip", "requirementsTooltip"]); - g_TechnologyData[civ][technologyName] = deepfreeze(template); - } - - return g_TechnologyData[civ][technologyName]; -} - function init(initData, hotloadData) { if (!g_Settings) Index: binaries/data/mods/public/simulation/components/GuiInterface.js =================================================================== --- binaries/data/mods/public/simulation/components/GuiInterface.js +++ binaries/data/mods/public/simulation/components/GuiInterface.js @@ -669,18 +669,6 @@ }; /** - * Checks whether the requirements for this technology have been met. - */ -GuiInterface.prototype.CheckTechnologyRequirements = function(player, data) -{ - let cmpTechnologyManager = QueryPlayerIDInterface(data.player !== undefined ? data.player : player, IID_TechnologyManager); - if (!cmpTechnologyManager) - return false; - - return cmpTechnologyManager.CanResearch(data.tech); -}; - -/** * Returns technologies that are being actively researched, along with * which entity is researching them and how far along the research is. */ Index: binaries/data/mods/public/simulation/components/Researcher.js =================================================================== --- binaries/data/mods/public/simulation/components/Researcher.js +++ binaries/data/mods/public/simulation/components/Researcher.js @@ -199,36 +199,26 @@ let techs = string.split(/\s+/); // Replace the civ specific technologies. - const civ = Engine.QueryInterface(playerEnt, IID_Identity).GetCiv(); + const playerCiv = Engine.QueryInterface(playerEnt, IID_Identity).GetCiv(); + const nativeCiv = Engine.QueryInterface(this.entity, IID_Identity).GetCiv(); for (let i = 0; i < techs.length; ++i) - { - const tech = techs[i]; - if (tech.indexOf("{civ}") == -1) - continue; - const civTech = tech.replace("{civ}", civ); - techs[i] = TechnologyTemplates.Has(civTech) ? civTech : tech.replace("{civ}", "generic"); - } - - // Remove any technologies that can't be researched by this civ. - techs = techs.filter(tech => - cmpTechnologyManager.CheckTechnologyRequirements( - DeriveTechnologyRequirements(TechnologyTemplates.Get(tech), civ), - true)); + techs[i] = tech.replace("{native}", nativeCiv).replace("{civ}", playerCiv); const techList = []; const superseded = {}; const disabledTechnologies = cmpPlayer.GetDisabledTechnologies(); + const cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); // Add any top level technologies to an array which corresponds to the displayed icons. // Also store what technology is superseded in the superseded object { "tech1":"techWhichSupercedesTech1", ... }. for (const tech of techs) { - if (disabledTechnologies && disabledTechnologies[tech]) + if (!cmpTemplateManager.TemplateExists(tech) || disabledTechnologies && disabledTechnologies[tech]) continue; - const template = TechnologyTemplates.Get(tech); - if (!template.supersedes || techs.indexOf(template.supersedes) === -1) + const template = GetTemplateDataHelper(cmpTemplateManager.GetTemplate(tech)).technology; + if (!template.supersedes || !techs.includes(template.supersedes)) techList.push(tech); else superseded[template.supersedes] = tech; @@ -256,7 +246,7 @@ continue; } - const template = TechnologyTemplates.Get(tech); + const template = cmpTemplateManager.GetTemplate(tech); if (template.top) ret[i] = { "pair": true, "top": template.top, "bottom": template.bottom }; else @@ -293,7 +283,7 @@ if (!cmpTechnologyManager) return false; - const template = TechnologyTemplates.Get(tech); + const template = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager).GetTemplate(tech); if (template.top) return cmpTechnologyManager.IsTechnologyResearched(template.top) || cmpTechnologyManager.IsInProgress(template.top) || Index: binaries/data/mods/public/simulation/components/Technology.js =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/components/Technology.js @@ -0,0 +1,28 @@ +function Technology() {} + +Technology.prototype.Schema = + "Specifies the effects of a technology." + + "" + + "phase_village" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ModificationsSchema + + "" + + "" + + "" + + "" + + "" + + ""; Index: binaries/data/mods/public/simulation/components/TechnologyManager.js =================================================================== --- binaries/data/mods/public/simulation/components/TechnologyManager.js +++ binaries/data/mods/public/simulation/components/TechnologyManager.js @@ -23,14 +23,14 @@ */ TechnologyManager.prototype.Technology.prototype.Queue = function(techCostMultiplier) { - const template = TechnologyTemplates.Get(this.templateName); + const template = GetTemplateDataHelper(Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager).GetTemplate(this.templateName)); if (!template) return false; this.resources = {}; - if (template.cost) - for (const res in template.cost) - this.resources[res] = Math.floor(techCostMultiplier[res] * template.cost[res]); + if (template.cost?.resources) + for (const res in template.cost.resources) + this.resources[res] = Math.floor(techCostMultiplier[res] * template.cost.resources[res]); // ToDo: Subtract resources here or in cmpResearcher? const cmpPlayer = Engine.QueryInterface(this.player, IID_Player); @@ -38,7 +38,7 @@ if (!cmpPlayer?.TrySubtractResources(this.resources)) return false; - const time = techCostMultiplier.time * (template.researchTime || 0) * 1000; + const time = techCostMultiplier.time * (template.cost.time || 0) * 1000; this.timeRemaining = time; this.timeTotal = time; @@ -89,9 +89,9 @@ { this.finished = true; - const template = TechnologyTemplates.Get(this.templateName); - if (template.soundComplete) - Engine.QueryInterface(SYSTEM_ENTITY, IID_SoundManager)?.PlaySoundGroup(template.soundComplete, this.researcher); + const template = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager).GetTemplate(this.templateName); + if (template.Sound?.completed) + Engine.QueryInterface(SYSTEM_ENTITY, IID_SoundManager)?.PlaySoundGroup(template.Sound.completed, this.researcher); if (template.modifications) { @@ -207,10 +207,15 @@ // Some technologies are automatically researched when their conditions are met. They have no cost and are // researched instantly. This allows civ bonuses and more complicated technologies. this.unresearchedAutoResearchTechs = new Set(); - let allTechs = TechnologyTemplates.GetAll(); - for (let key in allTechs) - if (allTechs[key].autoResearch || allTechs[key].top) + + const cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); + const allTemplates = cmpTemplateManager.FindAllTemplates(false); + for (const key in allTemplates) + { + const template = cmpTemplateManager.GetTemplate(key); + if (template.Technology && (template.Technology.AutoResearched || template.Technology.Choice)) this.unresearchedAutoResearchTechs.add(key); + } }; TechnologyManager.prototype.SerializableAttributes = [ @@ -259,9 +264,9 @@ { for (let key of this.unresearchedAutoResearchTechs) { - let tech = TechnologyTemplates.Get(key); - if ((tech.autoResearch && this.CanResearch(key)) || - (tech.top && (this.IsTechnologyResearched(tech.top) || this.IsTechnologyResearched(tech.bottom)))) + let tech = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager).GetTemplate(key).Technology; + if (tech.AutoResearched && this.CanResearch(key) || + tech.Choice?._string.split(" ").some(choiceTech => this.IsTechnologyResearched(choiceTech))) { this.unresearchedAutoResearchTechs.delete(key); this.ResearchTechnology(key); Index: binaries/data/mods/public/simulation/components/tests/test_Technology.js =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/components/tests/test_Technology.js @@ -0,0 +1 @@ +Engine.LoadComponentScript("Technology.js"); Index: binaries/data/mods/public/simulation/helpers/Cheat.js =================================================================== --- binaries/data/mods/public/simulation/helpers/Cheat.js +++ binaries/data/mods/public/simulation/helpers/Cheat.js @@ -109,22 +109,20 @@ if (!cmpTechnologyManager) return; - // store the phase we want in the next input parameter - let parameter; - if (!cmpTechnologyManager.IsTechnologyResearched("phase_town")) - parameter = "phase_town"; - else if (!cmpTechnologyManager.IsTechnologyResearched("phase_city")) - parameter = "phase_city"; - else - return; + const phases = [ "village", "town", "city" ]; - const civ = Engine.QueryInterface(playerEnt, IID_Identity).GetCiv(); - parameter += TechnologyTemplates.Has(parameter + "_" + civ) ? "_" + civ : "_generic"; + let technologyName = "technologies/{civ}/phase_"; + for (const phase of phases) + if (!cmpTechnologyManager.IsTechnologyResearched(technologyName + phase)) + { + technologyName += phase; + break; + } Cheat({ "player": input.player, "action": "researchTechnology", - "parameter": parameter, + "parameter": technologyName, "selected": input.selected }); return; @@ -173,7 +171,7 @@ } } - if (TechnologyTemplates.Has(techname)) + if (Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager).TemplateExists(techname)) cmpTechnologyManager.ResearchTechnology(techname); return; }