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/");
}
/**
@@ -125,12 +118,14 @@
"" +
"" +
"" +
- "" +
- "" +
- "tokens" +
- "" +
- "" +
- "" +
+ "" +
+ "" +
+ "" +
+ "tokens" +
+ "" +
+ "" +
+ "" +
+ "" +
"" +
"" +
"" +
@@ -168,7 +163,8 @@
if (template.Multiply)
effect.multiply = +template.Multiply;
if (template.Replace)
- effect.replace = template.Replace;
+ effect.replace = isNaN(template.Replace) ? template.Replace : +template.Replace;
+
effect.affects = template.Affects ? template.Affects._string.split(/\s/) : [];
let ret = {};
Index: binaries/data/mods/public/globalscripts/Technologies.js
===================================================================
--- binaries/data/mods/public/globalscripts/Technologies.js
+++ binaries/data/mods/public/globalscripts/Technologies.js
@@ -367,27 +367,21 @@
* @param {Object} phases - The current available store of phases.
* @return {array} List of phases
*/
-function UnravelPhases(phases)
+function UnravelPhases(phases, civCode)
{
- let phaseMap = {};
- for (let phaseName in phases)
+ const phaseMap = {};
+ for (const phaseName in phases)
{
- let phaseData = phases[phaseName];
- if (!phaseData.reqs.length || !phaseData.reqs[0].techs || !phaseData.replaces)
- continue;
+ const phaseData = phases[phaseName];
+
+ const prevPhase = phaseData.technology.supersedes.replace(/\{(civ|native)\}/g, civCode);
+ phaseMap[phaseName] = prevPhase;
- let myPhase = phaseData.replaces[0];
- let reqPhase = phaseData.reqs[0].techs[0];
- if (phases[reqPhase] && phases[reqPhase].replaces)
- reqPhase = phases[reqPhase].replaces[0];
-
- phaseMap[myPhase] = reqPhase;
- if (!phaseMap[reqPhase])
- phaseMap[reqPhase] = undefined;
+ if (!phaseMap[prevPhase])
+ phaseMap[prevPhase] = undefined;
}
let phaseList = Object.keys(phaseMap);
- phaseList.sort((a, b) => phaseList.indexOf(a) - phaseList.indexOf(phaseMap[b]));
-
+ phaseList.sort((a, b) => a == phaseMap[b] ? -1 : 1);
return phaseList;
}
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 (const 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,14 @@
"GainMultiplier": getEntityValue("Trader/GainMultiplier")
};
+ if (template.Technology)
+ ret.technology = {
+ "choice": template.Technology.Choice,
+ "choiceRoot": template.Technology.ChoiceRoot,
+ "modifications": template.Technology.Modifiers,
+ "supersedes": template.Technology.Supersedes
+ };
+
if (template.Treasure)
{
ret.treasure = {
@@ -551,49 +563,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
@@ -710,7 +710,9 @@
{
let totalCosts = {};
for (let r of getCostTypes())
- if (template.cost[r])
+ if (template.cost.resources && template.cost.resources[r])
+ totalCosts[r] = Math.floor(template.cost.resources[r] * trainNum);
+ else if (template.cost[r])
totalCosts[r] = Math.floor(template.cost[r] * trainNum);
return totalCosts;
@@ -962,19 +964,67 @@
return "";
}
-function getRequirementsTooltip(enabled, requirements, civ)
+function getRequirementsTooltip(enabled, requirements, civ, playerID)
{
if (enabled)
return "";
- // Simple requirements (one tech) can be translated on the fly.
+ // Simple requirements can be translated on the fly...
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(requirements.Techs._string.replace("{civ}", civ), playerID))
}));
- // More complex ones need a tooltip.
+ if ("Entities" in requirements)
+ {
+ const playerState = GetSimState().players[playerID];
+ const entityCounts = [];
+ for (const entity in requirements.Entities)
+ {
+ const req = requirements.Entities[entity];
+ let needed = 0;
+ let current = 0;
+ if ("Count" in req)
+ {
+ needed = +req.Count;
+ current = playerState.classCounts[entity] || 0;
+ }
+ else if ("Variants" in req)
+ {
+ needed = +req.Variants;
+ current = playerState.typeCountsByClass[entity] ?
+ Object.keys(playerState.typeCountsByClass[entity]).length : 0;
+ }
+ else
+ {
+ warn("Unknown entity requirement: " + uneval(requirements) + ".");
+ return "";
+ }
+
+ const remaining = needed - current;
+ if (remaining < 1)
+ continue;
+
+ if (current === 0)
+ entityCounts.push(sprintf(translatePlural("%(number)s entity of class %(class)s", "%(number)s entities of class %(class)s", remaining), {
+ "number": remaining,
+ "class": translate(entity)
+ }));
+ else
+ entityCounts.push(sprintf(translatePlural("%(number)s more entity of class %(class)s", "%(number)s more entities of class %(class)s", remaining), {
+ "number": remaining,
+ "class": translate(entity)
+ }));
+ }
+
+ const tip = sprintf(translate("Requires %(entityCounts)s"), {
+ "entityCounts": entityCounts.join(translateWithContext("Separator for a list of entity counts", ", "))
+ });
+ return objectionFont(tip);
+ }
+
+ // ...more complex ones need a tooltip.
if ("Tooltip" in requirements)
return objectionFont(translate(requirements.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;
}
@@ -162,13 +156,10 @@
};
if (template.Researcher?.Technologies?._string)
- for (let technologyName of template.Researcher.Technologies._string.split(" "))
+ for (let technologyName of template.Researcher.Technologies._string.replace(/\{(civ|native)\}/g, civCode).split(" "))
{
- if (technologyName.indexOf("{civ}") != -1)
- {
- const civTechName = technologyName.replace("{civ}", civCode);
- technologyName = TechnologyTemplateExists(civTechName) ? civTechName : technologyName.replace("{civ}", "generic");
- }
+ if (!Engine.TemplateExists(technologyName))
+ continue;
if (this.isPairTech(technologyName))
{
@@ -205,7 +196,7 @@
{
const modificationData = [];
for (const techName of this.autoResearchTechList)
- modificationData.push(GetTechnologyBasicDataHelper(this.loadTechnologyTemplate(techName), civCode));
+ modificationData.push(GetTemplateDataHelper(this.loadTechnologyTemplate(techName), civCode));
for (const auraName of auraList)
modificationData.push(this.loadAuraTemplate(auraName));
@@ -232,21 +223,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 +318,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
@@ -77,14 +77,13 @@
else
{
let highestPhaseIndex = 0;
- for (const tech of parsed.requirements.Techs._string.split(" "))
+ for (const tech of parsed.requirements.Techs._string.replace(/\{(civ|native)\}/g, civCode).split(" "))
{
if (tech[0] === "!")
continue;
const phaseIndex = this.phaseList.indexOf(
- this.TemplateLoader.isPhaseTech(tech) ? this.getActualPhase(tech) :
- this.getPhaseOfTechnology(tech, civCode));
+ this.getPhaseOfTechnology(tech, civCode));
if (phaseIndex > highestPhaseIndex)
highestPhaseIndex = phaseIndex;
}
@@ -173,7 +172,7 @@
}
/**
- * Load and parse technology from json template.
+ * Load and parse technology from template.
*
* @param {string} technologyName
* @param {string} civCode
@@ -181,10 +180,11 @@
*/
getTechnology(technologyName, civCode)
{
- if (!TechnologyTemplateExists(technologyName))
+ technologyName = technologyName.replace(/\{(civ|native)\}/g, civCode);
+ if (!Engine.TemplateExists(technologyName))
return null;
- if (this.TemplateLoader.isPhaseTech(technologyName) && technologyName in this.phases)
+ if (technologyName in this.phases)
return this.phases[technologyName];
if (!(civCode in this.techs))
@@ -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, null, g_ResourceData, this.modifiers[civCode] || {});
tech.name.internal = technologyName;
if (template.pair !== undefined)
@@ -203,12 +203,7 @@
}
if (this.TemplateLoader.isPhaseTech(technologyName))
- {
- tech.actualPhase = technologyName;
- if (tech.replaces !== undefined)
- tech.actualPhase = tech.replaces[0];
this.phases[technologyName] = tech;
- }
else
this.techs[civCode][technologyName] = tech;
return tech;
@@ -282,8 +277,7 @@
continue;
const phaseIndex = this.phaseList.indexOf(
- this.TemplateLoader.isPhaseTech(tech) ? this.getActualPhase(tech) :
- this.getPhaseOfTechnology(tech, civCode));
+ this.getPhaseOfTechnology(tech, civCode));
if (phaseIndex > highestPhaseIndex)
highestPhaseIndex = phaseIndex;
}
@@ -308,51 +302,29 @@
getPhaseOfTechnology(techName, civCode)
{
let phaseIdx = -1;
-
- if (basename(techName).startsWith("phase"))
+ if (basename(techName).includes("phase"))
{
- if (!this.phases[techName].reqs)
- return false;
-
- phaseIdx = this.phaseList.indexOf(this.getActualPhase(techName));
+ phaseIdx = this.phaseList.indexOf(techName);
if (phaseIdx > 0)
return this.phaseList[phaseIdx - 1];
}
- let techReqs = this.getTechnology(techName, civCode).reqs;
+ let techReqs = this.getTechnology(techName, civCode).requirements;
if (!techReqs)
return false;
- for (let option of techReqs)
- if (option.techs)
- for (let tech of option.techs)
- {
- if (basename(tech).startsWith("phase"))
- return tech;
- if (basename(tech).startsWith("pair"))
- continue;
- phaseIdx = Math.max(phaseIdx, this.phaseList.indexOf(this.getPhaseOfTechnology(tech, civCode)));
- }
+ if (techReqs.Techs?._string)
+ for (let tech of techReqs.Techs._string.replace(/\{(civ|native)\}/g, civCode).split(" "))
+ {
+ if (basename(tech).includes("phase"))
+ return tech;
+ if (basename(tech).startsWith("pair"))
+ continue;
+ phaseIdx = Math.max(phaseIdx, this.phaseList.indexOf(this.getPhaseOfTechnology(tech, civCode)));
+ }
return this.phaseList[phaseIdx] || false;
}
- /**
- * Returns the actual phase a certain phase tech represents or stands in for.
- *
- * For example, passing `phase_city_athen` would result in `phase_city`.
- *
- * @param {string} phaseName
- * @return {string}
- */
- getActualPhase(phaseName)
- {
- if (this.phases[phaseName])
- return this.phases[phaseName].actualPhase;
-
- warn("Unrecognized phase (" + phaseName + ")");
- return this.phaseList[0];
- }
-
getModifiers(civCode)
{
return this.modifiers[civCode];
@@ -376,7 +348,7 @@
if (this.TemplateLoader.isPhaseTech(techcode))
this.getTechnology(techcode, civCode);
- this.phaseList = UnravelPhases(this.phases);
+ this.phaseList = UnravelPhases(this.phases, civCode);
// Make sure all required generic phases are loaded and parsed
for (let phasecode of this.phaseList)
Index: binaries/data/mods/public/gui/reference/structree/Sections/Tree/PhaseIdent.js
===================================================================
--- binaries/data/mods/public/gui/reference/structree/Sections/Tree/PhaseIdent.js
+++ binaries/data/mods/public/gui/reference/structree/Sections/Tree/PhaseIdent.js
@@ -50,7 +50,7 @@
drawPhaseIcon(phaseIcon, phaseIndex, civCode)
{
let phaseName = this.page.TemplateParser.phaseList[phaseIndex];
- let prodPhaseTemplate = this.page.TemplateParser.getTechnology(phaseName + "_" + civCode, civCode) || this.page.TemplateParser.getTechnology(phaseName, civCode);
+ let prodPhaseTemplate = this.page.TemplateParser.getTechnology(phaseName, civCode);
phaseIcon.sprite = "stretched:" + this.page.IconPath + prodPhaseTemplate.icon;
phaseIcon.tooltip = getEntityNamesFormatted(prodPhaseTemplate);
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/diplomacy/playercontrols/SpyRequestButton.js
===================================================================
--- binaries/data/mods/public/gui/session/diplomacy/playercontrols/SpyRequestButton.js
+++ binaries/data/mods/public/gui/session/diplomacy/playercontrols/SpyRequestButton.js
@@ -74,7 +74,9 @@
tooltip += "\n" + getRequirementsTooltip(
false,
template.requirements,
- GetSimState().players[g_ViewedPlayer].civ);
+ GetSimState().players[g_ViewedPlayer].civ,
+ g_ViewedPlayer
+ );
this.diplomacySpyRequest.enabled = false;
this.diplomacySpyRequest.tooltip = tooltip;
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,7 @@
// Rank
if (entState.identity && entState.identity.rank && entState.identity.classes)
{
- const rankObj = GetTechnologyData(entState.identity.rankTechName, playerState.civ);
+ const rankObj = GetTemplateData(entState.identity.rankTechName, 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
@@ -203,7 +203,7 @@
tooltips.push(
formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers),
formatMatchLimitString(limits.matchLimit, limits.matchCount, limits.type),
- getRequirementsTooltip(requirementsMet, template.requirements, GetSimState().players[data.player].civ),
+ getRequirementsTooltip(requirementsMet, template.requirements, GetSimState().players[data.player].civ, data.player),
getNeededResourcesTooltip(neededResources));
data.button.tooltip = tooltips.filter(tip => tip).join("\n");
@@ -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));
@@ -663,16 +663,15 @@
tech => tech != null && !ret.some(
item =>
(item.tech == tech ||
- item.tech.pair &&
- tech.pair &&
- item.tech.bottom == tech.bottom &&
- item.tech.top == tech.top) &&
+ item.tech.ChoiceRoot &&
+ tech.ChoiceRoot &&
+ item.tech.Choices == tech.Choices) &&
Object.keys(item.techCostMultiplier).every(
k => item.techCostMultiplier[k] == state.researcher.techCostMultiplier[k])
));
if (filteredTechs.length + ret.length <= this.getMaxNumberOfItems() &&
- getNumberOfRightPanelButtons() <= this.getMaxNumberOfItems() * (filteredTechs.some(tech => !!tech.pair) ? 1 : 2))
+ getNumberOfRightPanelButtons() <= this.getMaxNumberOfItems() * (filteredTechs.some(tech => !!tech.ChoiceRoot) ? 1 : 2))
ret = ret.concat(filteredTechs.map(tech => ({
"tech": tech,
"techCostMultiplier": state.researcher.techCostMultiplier,
@@ -701,7 +700,7 @@
let position = data.i + data.rowLength;
// Only show the top button for pairs
- if (!data.item.tech.pair)
+ if (!data.item.tech.ChoiceRoot)
Engine.GetGUIObjectByName("unitResearchButton[" + data.i + "]").hidden = true;
// Set up the tech connector
@@ -710,35 +709,32 @@
setPanelObjectPosition(pair, data.i, data.rowLength);
// 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])
+ const player = data.player;
+ const playerState = GetSimState().players[player];
+ for (const tech of data.item.tech.Choices ? data.item.tech.Choices : [data.item.tech])
{
- // Don't change the object returned by GetTechnologyData
- let template = clone(GetTechnologyData(tech, playerState.civ));
- if (!template)
- return false;
-
+ const template = clone(GetTemplateData(tech));
// Not allowed by civ.
- if (!template.reqs)
+ if (!template)
{
- // One of the pair may still be researchable by the current civ,
+ // One of the choices may still be researchable by the current civ,
// hence don't hide everything.
Engine.GetGUIObjectByName("unitResearchButton[" + data.i + "]").hidden = true;
pair.hidden = true;
continue;
}
- for (let res in template.cost)
- template.cost[res] *= data.item.techCostMultiplier[res] !== undefined ? data.item.techCostMultiplier[res] : 1;
+ template.cost.time *= data.item.techCostMultiplier.time ?? 1;
+ for (const res in template.cost.resources)
+ template.cost.resources[res] *= data.item.techCostMultiplier[res] ?? 1;
- let neededResources = Engine.GuiInterfaceCall("GetNeededResources", {
- "cost": template.cost,
+ const neededResources = Engine.GuiInterfaceCall("GetNeededResources", {
+ "cost": template.cost.resources,
"player": player
});
- let requirementsPassed = Engine.GuiInterfaceCall("CheckTechnologyRequirements", {
- "tech": tech,
+ const requirementsPassed = Engine.GuiInterfaceCall("AreRequirementsMet", {
+ "requirements": template.requirements,
"player": player
});
@@ -752,47 +748,8 @@
showTemplateViewerOnRightClickTooltip
].map(func => func(template));
- if (!requirementsPassed)
- {
- let tip = template.requirementsTooltip;
- let reqs = template.reqs;
- for (let req of reqs)
- {
- if (!req.entities)
- continue;
-
- let entityCounts = [];
- for (let entity of req.entities)
- {
- let current = 0;
- switch (entity.check)
- {
- case "count":
- current = playerState.classCounts[entity.class] || 0;
- break;
-
- case "variants":
- current = playerState.typeCountsByClass[entity.class] ?
- Object.keys(playerState.typeCountsByClass[entity.class]).length : 0;
- break;
- }
-
- let remaining = entity.number - current;
- if (remaining < 1)
- continue;
-
- entityCounts.push(sprintf(translatePlural("%(number)s entity of class %(class)s", "%(number)s entities of class %(class)s", remaining), {
- "number": remaining,
- "class": translate(entity.class)
- }));
- }
-
- tip += " " + sprintf(translate("Remaining: %(entityCounts)s"), {
- "entityCounts": entityCounts.join(translateWithContext("Separator for a list of entity counts", ", "))
- });
- }
- tooltips.push(objectionFont(tip));
- }
+ tooltips.push(getRequirementsTooltip(requirementsPassed, template.requirements, playerState.civ, player));
+
tooltips.push(getNeededResourcesTooltip(neededResources));
button.tooltip = tooltips.filter(tip => tip).join("\n");
@@ -800,7 +757,7 @@
addResearchToQueue(data.item.researchFacilityId, t);
})(tech);
- let showTemplateFunc = (t => function() {
+ const showTemplateFunc = (t => function() {
showTemplateDetails(
t,
GetTemplateData(data.unitEntStates.find(state => state.id == data.item.researchFacilityId).template).nativeCiv);
@@ -809,10 +766,10 @@
button.onPressRight = showTemplateFunc(tech);
button.onPressRightDisabled = showTemplateFunc(tech);
- if (data.item.tech.pair)
+ if (data.item.tech.Choices)
{
// On mouse enter, show a cross over the other icon
- let unchosenIcon = Engine.GetGUIObjectByName("unitResearchUnchosenIcon[" + (position + data.rowLength) % (2 * data.rowLength) + "]");
+ const unchosenIcon = Engine.GetGUIObjectByName("unitResearchUnchosenIcon[" + (position + data.rowLength) % (2 * data.rowLength) + "]");
button.onMouseEnter = function() {
unchosenIcon.hidden = false;
};
@@ -1048,7 +1005,7 @@
tooltips.push(showTemplateViewerOnRightClickTooltip());
tooltips.push(
formatBatchTrainingString(buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch),
- getRequirementsTooltip(requirementsMet, template.requirements, GetSimState().players[data.player].civ),
+ getRequirementsTooltip(requirementsMet, template.requirements, GetSimState().players[data.player].civ, data.player),
getNeededResourcesTooltip(neededResources));
data.button.tooltip = tooltips.filter(tip => tip).join("\n");
@@ -1165,7 +1122,7 @@
getEntityCostTooltip(data.item, undefined, undefined, data.unitEntStates.length),
formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers),
formatMatchLimitString(limits.matchLimit, limits.matchCount, limits.type),
- getRequirementsTooltip(requirementsMet, data.item.requirements, GetSimState().players[data.player].civ),
+ getRequirementsTooltip(requirementsMet, data.item.requirements, GetSimState().players[data.player].civ, data.player),
getNeededResourcesTooltip(neededResources),
showTemplateViewerOnRightClickTooltip());
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/EntityLimits.js
===================================================================
--- binaries/data/mods/public/simulation/components/EntityLimits.js
+++ binaries/data/mods/public/simulation/components/EntityLimits.js
@@ -75,30 +75,6 @@
this.classCount = {};
this.removedLimit = {};
this.matchTemplateCount = {};
- for (var category in this.template.Limits)
- {
- this.limit[category] = +this.template.Limits[category];
- this.count[category] = 0;
- if (category in this.template.LimitChangers)
- {
- this.changers[category] = {};
- for (var c in this.template.LimitChangers[category])
- this.changers[category][c] = +this.template.LimitChangers[category][c];
- }
- if (category in this.template.LimitRemovers)
- {
- // Keep a copy of removable limits for possible restoration.
- this.removedLimit[category] = this.limit[category];
- this.removers[category] = {};
- for (var c in this.template.LimitRemovers[category])
- {
- this.removers[category][c] = this.template.LimitRemovers[category][c]._string.split(/\s+/);
- if (c === "RequiredClasses")
- for (var cls of this.removers[category][c])
- this.classCount[cls] = 0;
- }
- }
- }
};
EntityLimits.prototype.ChangeCount = function(category, value)
@@ -298,4 +274,33 @@
this.UpdateLimitRemoval();
};
+EntityLimits.prototype.OnCreate = function(msg)
+{
+ const civ = Engine.QueryInterface(this.entity, IID_Identity).GetCiv();
+ for (const category in this.template.Limits)
+ {
+ this.limit[category] = +this.template.Limits[category];
+ this.count[category] = 0;
+ if (category in this.template.LimitChangers)
+ {
+ this.changers[category] = {};
+ for (const c in this.template.LimitChangers[category])
+ this.changers[category][c] = +this.template.LimitChangers[category][c];
+ }
+ if (category in this.template.LimitRemovers)
+ {
+ // Keep a copy of removable limits for possible restoration.
+ this.removedLimit[category] = this.limit[category];
+ this.removers[category] = {};
+ for (const c in this.template.LimitRemovers[category])
+ {
+ this.removers[category][c] = this.template.LimitRemovers[category][c]._string.replace(/{civ}/gi, civ).split(/\s+/);
+ if (c === "RequiredClasses")
+ for (const cls of this.removers[category][c])
+ this.classCount[cls] = 0;
+ }
+ }
+ }
+};
+
Engine.RegisterComponentType(IID_EntityLimits, "EntityLimits", EntityLimits);
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.
*/
@@ -2072,7 +2060,6 @@
"GetAverageRangeForBuildings": 1,
"GetTemplateData": 1,
"AreRequirementsMet": 1,
- "CheckTechnologyRequirements": 1,
"GetStartedResearch": 1,
"GetBattleState": 1,
"GetIncomingAttacks": 1,
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
@@ -198,40 +198,29 @@
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] = techs[i].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 = cmpTemplateManager.GetTemplate(tech).Technology;
+ if (!template?.Supersedes || !techs.includes(template.Supersedes))
techList.push(tech);
else
- superseded[template.supersedes] = tech;
+ superseded[template.Supersedes] = tech;
}
// Now make researched/in progress techs invisible.
@@ -256,9 +245,9 @@
continue;
}
- const template = TechnologyTemplates.Get(tech);
- if (template.top)
- ret[i] = { "pair": true, "top": template.top, "bottom": template.bottom };
+ const template = cmpTemplateManager.GetTemplate(tech).Technology;
+ if (template?.Choices)
+ ret[i] = { "ChoiceRoot": true, "Choices": template.Choices._string.split(" ") };
else
ret[i] = tech;
}
@@ -293,7 +282,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,30 @@
+function Technology() {}
+
+Technology.prototype.Schema =
+ "Specifies the effects of a technology." +
+ "" +
+ "technologies/{civ}/phase_village" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ ModificationsSchema +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "";
+
+Engine.RegisterComponentType(IID_Technology, "Technology", Technology);
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
@@ -1,7 +1,15 @@
function TechnologyManager() {}
TechnologyManager.prototype.Schema =
- "";
+ "Handles technologies for a player." +
+ "" +
+ "" +
+ "" +
+ "" +
+ "tokens" +
+ "" +
+ "" +
+ "";
/**
* This object represents a technology under research.
@@ -23,14 +31,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 +46,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;
@@ -88,30 +96,20 @@
TechnologyManager.prototype.Technology.prototype.Finish = function()
{
this.finished = true;
+ Engine.QueryInterface(this.player, IID_TechnologyManager).MarkTechnologyAsResearched(this.templateName);
- 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)
+ if (template.Technology?.Modifiers)
{
const cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager);
- cmpModifiersManager.AddModifiers("tech/" + this.templateName, DeriveModificationsFromTech(template), this.player);
+ cmpModifiersManager.AddModifiers("tech/" + this.templateName, DeriveModificationsFromXMLTemplate(template.Technology.Modifiers), this.player);
}
- const cmpEntityLimits = Engine.QueryInterface(this.player, IID_EntityLimits);
- const cmpTechnologyManager = Engine.QueryInterface(this.player, IID_TechnologyManager);
- if (template.replaces && template.replaces.length > 0)
- for (const i of template.replaces)
- {
- cmpTechnologyManager.MarkTechnologyAsResearched(i);
- cmpEntityLimits?.UpdateLimitsFromTech(i);
- }
-
- cmpTechnologyManager.MarkTechnologyAsResearched(this.templateName);
-
// ToDo: Move to EntityLimits.js.
- cmpEntityLimits?.UpdateLimitsFromTech(this.templateName);
+ Engine.QueryInterface(this.player, IID_EntityLimits)?.UpdateLimitsFromTech(this.templateName);
const playerID = Engine.QueryInterface(this.player, IID_Player).GetPlayerID();
Engine.PostMessage(this.player, MT_ResearchFinished, { "player": playerID, "tech": this.templateName });
@@ -206,11 +204,23 @@
// 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)
- this.unresearchedAutoResearchTechs.add(key);
+ this.unresearchedAutoResearchTechs;
+};
+
+TechnologyManager.prototype.OnCreate = function()
+{
+ const civCode = Engine.QueryInterface(this.entity, IID_Identity).GetCiv();
+ const autoResearchedTemplate = this.template?.AutoResearched?._string?.replace(/\{(civ|native)\}/g, civCode)?.split(" ");
+ this.unresearchedAutoResearchTechs = new Set(autoResearchedTemplate || []);
+
+ const cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
+ const allTemplates = cmpTemplateManager.FindAllTemplates(false);
+ for (const templateName of allTemplates)
+ {
+ const template = cmpTemplateManager.GetTemplate(templateName);
+ if (template?.Technology?.Choices)
+ this.unresearchedAutoResearchTechs.add(templateName);
+ }
};
TechnologyManager.prototype.SerializableAttributes = [
@@ -257,11 +267,11 @@
// This function checks if the requirements of any autoresearch techs are met and if they are it researches them
TechnologyManager.prototype.UpdateAutoResearch = function()
{
- for (let key of this.unresearchedAutoResearchTechs)
+ for (const 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))))
+ const tech = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager).GetTemplate(key).Technology;
+ if (this.CanResearch(key) ||
+ tech.Choices?._string.split(" ").some(choiceTech => this.IsTechnologyResearched(choiceTech)))
{
this.unresearchedAutoResearchTechs.delete(key);
this.ResearchTechnology(key);
@@ -273,9 +283,7 @@
// Checks an entity template to see if its technology requirements have been met
TechnologyManager.prototype.CanProduce = function(templateName)
{
- var cmpTempManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
- var template = cmpTempManager.GetTemplate(templateName);
-
+ const template = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager).GetTemplate(templateName);
if (template.Identity?.Requirements)
return RequirementsHelper.AreRequirementsMet(template.Identity.Requirements, Engine.QueryInterface(this.entity, IID_Player).GetPlayerID());
// If there is no required technology then this entity can be produced
@@ -284,87 +292,37 @@
TechnologyManager.prototype.IsTechnologyQueued = function(tech)
{
- return this.researchQueued.has(tech);
+ return this.researchQueued.has(tech.replace("{civ}", Engine.QueryInterface(this.entity, IID_Identity).GetCiv()));
};
TechnologyManager.prototype.IsTechnologyResearched = function(tech)
{
- return this.researchedTechs.has(tech);
+ return this.researchedTechs.has(tech.replace("{civ}", Engine.QueryInterface(this.entity, IID_Identity).GetCiv()));
};
// Checks the requirements for a technology to see if it can be researched at the current time
TechnologyManager.prototype.CanResearch = function(tech)
{
- let template = TechnologyTemplates.Get(tech);
-
- if (!template)
+ const template = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager).GetTemplate(tech);
+ if (!("Technology" in template))
{
warn("Technology \"" + tech + "\" does not exist");
return false;
}
- if (template.top && this.IsInProgress(template.top) ||
- template.bottom && this.IsInProgress(template.bottom))
+ if (template.Technology?.Choices?.some(choice => this.IsInProgress(choice) || this.IsTechnologyResearched(choice)))
return false;
- if (template.pair && !this.CanResearch(template.pair))
+ if (template.Technology?.ChoiceRoot && !this.CanResearch(template.Technology.ChoiceRoot))
return false;
- if (this.IsInProgress(tech))
+ if (this.IsInProgress(tech) || this.IsTechnologyResearched(tech))
return false;
- if (this.IsTechnologyResearched(tech))
- return false;
-
- return this.CheckTechnologyRequirements(DeriveTechnologyRequirements(template, Engine.QueryInterface(this.entity, IID_Identity).GetCiv()));
-};
-
-/**
- * Private function for checking a set of requirements is met
- * @param {Object} reqs - Technology requirements as derived from the technology template by globalscripts
- * @param {boolean} civonly - True if only the civ requirement is to be checked
- *
- * @return true if the requirements pass, false otherwise
- */
-TechnologyManager.prototype.CheckTechnologyRequirements = function(reqs, civonly = false)
-{
- let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
-
- if (!reqs)
- return false;
-
- if (civonly || !reqs.length)
- return true;
-
- return reqs.some(req => {
- return Object.keys(req).every(type => {
- switch (type)
- {
- case "techs":
- return req[type].every(this.IsTechnologyResearched, this);
-
- case "entities":
- return req[type].every(this.DoesEntitySpecPass, this);
- }
- return false;
- });
- });
-};
+ if (template.Identity?.Requirements)
+ return RequirementsHelper.AreRequirementsMet(template.Identity.Requirements, Engine.QueryInterface(this.entity, IID_Player).GetPlayerID());
-TechnologyManager.prototype.DoesEntitySpecPass = function(entity)
-{
- switch (entity.check)
- {
- case "count":
- if (!this.classCounts[entity.class] || this.classCounts[entity.class] < entity.number)
- return false;
- break;
-
- case "variants":
- if (!this.typeCountsByClass[entity.class] || Object.keys(this.typeCountsByClass[entity.class]).length < entity.number)
- return false;
- break;
- }
+ // If there is no requirement then this technology can be researched.
return true;
};
Index: binaries/data/mods/public/simulation/components/interfaces/Technology.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/components/interfaces/Technology.js
@@ -0,0 +1 @@
+Engine.RegisterInterface("Technology");
Index: binaries/data/mods/public/simulation/components/tests/test_EntityLimits.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_EntityLimits.js
+++ binaries/data/mods/public/simulation/components/tests/test_EntityLimits.js
@@ -24,11 +24,15 @@
AddMock(10, IID_Player, {
"GetPlayerID": id => 1
});
+AddMock(10, IID_Identity, {
+ "GetCiv": id => "athen"
+});
AddMock(SYSTEM_ENTITY, IID_GuiInterface, {
"PushNotification": () => {}
});
let cmpEntityLimits = ConstructComponent(10, "EntityLimits", template);
+cmpEntityLimits.OnCreate(null);
// Test getters
TS_ASSERT_UNEVAL_EQUALS(cmpEntityLimits.GetCounts(), { "Tower": 0, "Wonder": 0, "Hero": 0, "Champion": 0 });
Index: binaries/data/mods/public/simulation/components/tests/test_Researcher.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_Researcher.js
+++ binaries/data/mods/public/simulation/components/tests/test_Researcher.js
@@ -13,9 +13,11 @@
const playerEntityID = 11;
const entityID = 21;
-Engine.RegisterGlobal("TechnologyTemplates", {
- "Has": name => name == "phase_town_athen" || name == "phase_city_athen",
- "Get": () => ({})
+let techsPresent = [];
+
+AddMock(SYSTEM_ENTITY, IID_TemplateManager, {
+ "TemplateExists": name => techsPresent.includes(name),
+ "GetTemplate": () => ({ "Technology": {} })
});
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
@@ -31,7 +33,6 @@
});
AddMock(playerEntityID, IID_TechnologyManager, {
- "CheckTechnologyRequirements": () => true,
"IsInProgress": () => false,
"IsTechnologyResearched": () => false
});
@@ -44,31 +45,39 @@
"GetCiv": () => "iber"
});
+techsPresent = ["technologies/iber/phase_town", "technologies/iber/phase_city"];
let cmpResearcher = ConstructComponent(entityID, "Researcher", {
- "Technologies": { "_string": "gather_fishing_net " +
- "phase_town_{civ} " +
- "phase_city_{civ}" }
+ "Technologies": { "_string": ["technologies/{native}/gather_fishing_net",
+ "technologies/{civ}/phase_town",
+ "technologies/{civ}/phase_city"].join(" ") }
});
TS_ASSERT_UNEVAL_EQUALS(
+ cmpResearcher.GetTechnologiesList(), techsPresent
+);
+
+techsPresent = ["technologies/iber/gather_fishing_net", "technologies/athen/phase_town", "technologies/athen/phase_city"];
+
+TS_ASSERT_UNEVAL_EQUALS(
cmpResearcher.GetTechnologiesList(),
- ["gather_fishing_net", "phase_town_generic", "phase_city_generic"]
+ ["technologies/iber/gather_fishing_net"]
);
AddMock(playerEntityID, IID_Player, {
- "GetDisabledTechnologies": () => ({ "gather_fishing_net": true })
+ "GetDisabledTechnologies": () => ({ "technologies/iber/gather_fishing_net": true })
});
AddMock(playerEntityID, IID_Identity, {
"GetCiv": () => "athen",
});
-TS_ASSERT_UNEVAL_EQUALS(cmpResearcher.GetTechnologiesList(), ["phase_town_athen", "phase_city_athen"]);
+TS_ASSERT_UNEVAL_EQUALS(cmpResearcher.GetTechnologiesList(), [
+ "technologies/athen/phase_town", "technologies/athen/phase_city"
+]);
AddMock(playerEntityID, IID_TechnologyManager, {
- "CheckTechnologyRequirements": () => true,
"IsInProgress": () => false,
- "IsTechnologyResearched": tech => tech == "phase_town_athen"
+ "IsTechnologyResearched": tech => tech == "technologies/athen/phase_town"
});
-TS_ASSERT_UNEVAL_EQUALS(cmpResearcher.GetTechnologiesList(), [undefined, "phase_city_athen"]);
+TS_ASSERT_UNEVAL_EQUALS(cmpResearcher.GetTechnologiesList(), [undefined, "technologies/athen/phase_city"]);
AddMock(playerEntityID, IID_Player, {
"GetDisabledTechnologies": () => ({})
@@ -76,29 +85,29 @@
AddMock(playerEntityID, IID_Identity, {
"GetCiv": () => "iber",
});
+techsPresent.push("technologies/iber/phase_town", "technologies/iber/phase_city");
TS_ASSERT_UNEVAL_EQUALS(
cmpResearcher.GetTechnologiesList(),
- ["gather_fishing_net", "phase_town_generic", "phase_city_generic"]
+ ["technologies/iber/gather_fishing_net", "technologies/iber/phase_town", "technologies/iber/phase_city"]
);
+techsPresent.push("some_test");
Engine.RegisterGlobal("ApplyValueModificationsToEntity", (_, value) => typeof value === "string" ? value + " some_test": value);
TS_ASSERT_UNEVAL_EQUALS(
cmpResearcher.GetTechnologiesList(),
- ["gather_fishing_net", "phase_town_generic", "phase_city_generic", "some_test"]
+ ["technologies/iber/gather_fishing_net", "technologies/iber/phase_town", "technologies/iber/phase_city", "some_test"]
);
// Test Queuing a tech.
-const queuedTech = "gather_fishing_net";
+const queuedTech = "technologies/iber/gather_fishing_net";
const cost = {
"food": 10
};
-Engine.RegisterGlobal("TechnologyTemplates", {
- "Has": () => true,
- "Get": () => ({
- "cost": cost,
- "researchTime": 1
- })
+
+AddMock(SYSTEM_ENTITY, IID_TemplateManager, {
+ "TemplateExists": name => true,
+ "GetTemplate": () => ({ "Technology": {} })
});
const cmpPlayer = AddMock(playerEntityID, IID_Player, {
@@ -110,7 +119,6 @@
"GetCiv": () => "iber",
});
const techManager = AddMock(playerEntityID, IID_TechnologyManager, {
- "CheckTechnologyRequirements": () => true,
"IsInProgress": () => false,
"IsTechnologyResearched": () => false,
"QueuedResearch": (templateName, researcher, techCostMultiplier) => {
Index: binaries/data/mods/public/simulation/components/tests/test_Technologies.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_Technologies.js
+++ binaries/data/mods/public/simulation/components/tests/test_Technologies.js
@@ -53,11 +53,13 @@
Engine.RegisterGlobal("ApplyValueModificationsToEntity", (_, value) => typeof value === "string" ? value + " some_test": value);
const template = {
+ "Technology": {},
"name": "templateName"
};
-Engine.RegisterGlobal("TechnologyTemplates", {
- "GetAll": () => [],
- "Get": (tech) => {
+AddMock(SYSTEM_ENTITY, IID_TemplateManager, {
+ "TemplateExists": () => true,
+ "FindAllTemplates": () => [],
+ "GetTemplate": (tech) => {
return template;
}
});
@@ -74,13 +76,18 @@
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
"GetPlayerByID": id => playerEntityID
});
+AddMock(researcherID, IID_Identity, {
+ "GetCiv": () => "gaia"
+});
AddMock(playerEntityID, IID_Identity, {
"GetCiv": () => "gaia"
});
-template.cost = {
- "food": 100
+template.Cost = {
+ "Resources": {
+ "food": 100
+ },
+ "BuildTime": 1.5
};
-template.researchTime = 1.5;
const cmpPlayer = ConstructComponent(playerEntityID, "Player", {
"SpyCostMultiplier": "1",
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,2 @@
+Engine.LoadComponentScript("interfaces/Technology.js");
+Engine.LoadComponentScript("Technology.js");
Index: binaries/data/mods/public/simulation/components/tests/test_TechnologyManager.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_TechnologyManager.js
+++ binaries/data/mods/public/simulation/components/tests/test_TechnologyManager.js
@@ -8,10 +8,12 @@
ConstructComponent(SYSTEM_ENTITY, "Trigger");
-const techTemplate = {};
-Engine.RegisterGlobal("TechnologyTemplates", {
- "GetAll": () => [],
- "Get": (tech) => {
+const techTemplate = {
+ "Technology": {},
+};
+AddMock(SYSTEM_ENTITY, IID_TemplateManager, {
+ "FindAllTemplates": () => [],
+ "GetTemplate": (tech) => {
return techTemplate;
}
});
@@ -21,33 +23,25 @@
let cmpTechnologyManager = ConstructComponent(playerEntityID, "TechnologyManager", null);
-// Test CheckTechnologyRequirements
-const template = { "requirements": { "all": [{ "entity": { "class": "Village", "number": 5 } }, { "civ": "athen" }] } };
-cmpTechnologyManager.classCounts.Village = 2;
-TS_ASSERT_EQUALS(cmpTechnologyManager.CheckTechnologyRequirements(DeriveTechnologyRequirements(template, "athen")), false);
-TS_ASSERT_EQUALS(cmpTechnologyManager.CheckTechnologyRequirements(DeriveTechnologyRequirements(template, "athen"), true), true);
-TS_ASSERT_EQUALS(cmpTechnologyManager.CheckTechnologyRequirements(DeriveTechnologyRequirements(template, "maur"), true), false);
-cmpTechnologyManager.classCounts.Village = 6;
-TS_ASSERT_EQUALS(cmpTechnologyManager.CheckTechnologyRequirements(DeriveTechnologyRequirements(template, "athen")), true);
-TS_ASSERT_EQUALS(cmpTechnologyManager.CheckTechnologyRequirements(DeriveTechnologyRequirements(template, "maur")), false);
-
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
"GetPlayerByID": id => playerEntityID
});
const templateName = "template";
-techTemplate.cost = {
- "food": 100
+techTemplate.Cost = {
+ "Resources": {
+ "food": 100
+ }
};
const cmpPlayer = AddMock(playerEntityID, IID_Player, {
"GetPlayerID": () => playerID,
"TrySubtractResources": (resources) => {
- TS_ASSERT_UNEVAL_EQUALS(resources, techTemplate.cost);
+ TS_ASSERT_UNEVAL_EQUALS(resources, techTemplate.Cost.Resources);
// Just have enough resources.
return true;
},
"RefundResources": (resources) => {
- TS_ASSERT_UNEVAL_EQUALS(resources, techTemplate.cost);
+ TS_ASSERT_UNEVAL_EQUALS(resources, techTemplate.Cost.Resources);
},
});
const spyCmpPlayerSubtract = new Spy(cmpPlayer, "TrySubtractResources");
@@ -62,7 +56,7 @@
TS_ASSERT(!cmpTechnologyManager.IsInProgress(templateName));
TS_ASSERT_EQUALS(spyCmpPlayerRefund._called, 1);
-techTemplate.researchTime = 2;
+techTemplate.Cost.BuildTime = 2;
TS_ASSERT(cmpTechnologyManager.QueuedResearch(templateName, INVALID_ENTITY, { "food": 1, "time": 1 }));
TS_ASSERT_EQUALS(cmpTechnologyManager.Progress(templateName, 500), 500);
@@ -79,6 +73,10 @@
},
});
+AddMock(playerEntityID, IID_Identity, {
+ "GetCiv": () => "iber",
+});
+
TS_ASSERT(!cmpTechnologyManager.IsTechnologyResearched(templateName));
TS_ASSERT_EQUALS(cmpTechnologyManager.Progress(templateName, 2000), 1500);
TS_ASSERT(cmpTechnologyManager.IsTechnologyResearched(templateName));
Index: binaries/data/mods/public/simulation/components/tests/test_Trainer.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_Trainer.js
+++ binaries/data/mods/public/simulation/components/tests/test_Trainer.js
@@ -181,7 +181,7 @@
},
"LimitChangers": {},
"LimitRemovers": {}
-});
+}).OnCreate(null);
// Test that we can't exceed the entity limit.
TS_ASSERT_EQUALS(cmpTrainer.QueueBatch(queuedUnit, 1), -1);
// And that in that case, the resources are not lost.
@@ -195,7 +195,7 @@
},
"LimitChangers": {},
"LimitRemovers": {}
-});
+}).OnCreate(null);
let id = cmpTrainer.QueueBatch(queuedUnit, 1);
TS_ASSERT_EQUALS(spyCmpPlayerSubtract._called, 2);
TS_ASSERT_EQUALS(cmpTrainer.queue.size, 1);
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;
@@ -172,8 +170,8 @@
}
}
}
-
- if (TechnologyTemplates.Has(techname))
+ techname = techname.replace("{civ}", Engine.QueryInterface(playerEnt, IID_Identity).GetCiv());
+ if (Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager).TemplateExists(techname))
cmpTechnologyManager.ResearchTechnology(techname);
return;
}
Index: source/gui/GUIManager.h
===================================================================
--- source/gui/GUIManager.h
+++ source/gui/GUIManager.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2022 Wildfire Games.
+/* Copyright (C) 2023 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -125,6 +125,11 @@
const CParamNode& GetTemplate(const std::string& templateName);
/**
+ * Retrieve a list of all available templates.
+ */
+ const std::vector FindAllTemplates(bool actors);
+
+ /**
* Display progress / description in loading screen.
*/
void DisplayLoadProgress(int percent, const wchar_t* pending_task);
Index: source/gui/GUIManager.cpp
===================================================================
--- source/gui/GUIManager.cpp
+++ source/gui/GUIManager.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2022 Wildfire Games.
+/* Copyright (C) 2023 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -24,6 +24,7 @@
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "ps/GameSetup/Config.h"
+#include "ps/TemplateLoader.h"
#include "ps/Profile.h"
#include "ps/VideoMode.h"
#include "ps/XML/Xeromyces.h"
@@ -422,6 +423,12 @@
return templateRoot;
}
+const std::vector CGUIManager::FindAllTemplates(bool includeActors)
+{
+ ETemplatesType templatesType = includeActors ? ALL_TEMPLATES : SIMULATION_TEMPLATES;
+ return m_TemplateLoader.FindTemplates("", true, templatesType);
+}
+
void CGUIManager::DisplayLoadProgress(int percent, const wchar_t* pending_task)
{
const ScriptInterface& scriptInterface = *(GetActiveGUI()->GetScriptInterface());
Index: source/gui/Scripting/JSInterface_GUIManager.cpp
===================================================================
--- source/gui/Scripting/JSInterface_GUIManager.cpp
+++ source/gui/Scripting/JSInterface_GUIManager.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2021 Wildfire Games.
+/* Copyright (C) 2023 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -73,6 +73,11 @@
return g_GUI->GetTemplate(templateName);
}
+std::vector FindAllTemplates(bool actors)
+{
+ return g_GUI->FindAllTemplates(actors);
+}
+
void RegisterScriptFunctions(const ScriptRequest& rq)
{
@@ -83,6 +88,7 @@
ScriptFunction::Register<&ResetCursor>(rq, "ResetCursor");
ScriptFunction::Register<&TemplateExists>(rq, "TemplateExists");
ScriptFunction::Register<&GetTemplate>(rq, "GetTemplate");
+ ScriptFunction::Register<&FindAllTemplates>(rq, "FindAllTemplates");
ScriptFunction::Register<&CGUI::FindObjectByName, &ScriptInterface::ObjectFromCBData>(rq, "GetGUIObjectByName");
ScriptFunction::Register<&CGUI::SetGlobalHotkey, &ScriptInterface::ObjectFromCBData>(rq, "SetGlobalHotkey");