diff --git a/binaries/data/mods/public/globalscripts/Technologies.js b/binaries/data/mods/public/globalscripts/Technologies.js index 1af45a4dc5..8361d06300 100644 --- a/binaries/data/mods/public/globalscripts/Technologies.js +++ b/binaries/data/mods/public/globalscripts/Technologies.js @@ -50,3 +50,238 @@ function DoesModificationApply(modification, classes) { return MatchesClassList(classes, modification.affects); } + +/** + * Derives the technology requirements from a given technology template. + * Takes into account the `supersedes` attribute. + * + * @param {object} template - The template object. Loading of the template must have already occured. + * + * @return Derived technology requirements. See `InterpretTechRequirements` for object's syntax. + */ +function DeriveTechnologyRequirements(template, civ) +{ + let requirements = []; + + if (template.requirements) + { + let op = Object.keys(template.requirements)[0]; + let val = template.requirements[op]; + requirements = InterpretTechRequirements(civ, op, val); + } + + if (template.supersedes && requirements) + { + if (!requirements.length) + requirements.push({}); + + for (let req of requirements) + { + if (!req.techs) + req.techs = []; + req.techs.push(template.supersedes); + } + } + + return requirements; +} + +/** + * Interprets the prerequisite requirements of a technology. + * + * Takes the initial { key: value } from the short-form requirements object in entity templates, + * and parses it into an object that can be more easily checked by simulation and gui. + * + * Works recursively if needed. + * + * The returned object is in the form: + * ``` + * { "techs": ["tech1", "tech2"] }, + * { "techs": ["tech3"] } + * ``` + * or + * ``` + * { "entities": [[{ + * "class": "human", + * "number": 2, + * "check": "count" + * } + * or + * ``` + * false; + * ``` + * (Or, to translate: + * 1. need either both `tech1` and `tech2`, or `tech3` + * 2. need 2 entities with the `human` class + * 3. cannot research this tech at all) + * + * @param {string} civ - The civ code + * @param {string} operator - The base operation. Can be "civ", "notciv", "tech", "entity", "all" or "any". + * @param {mixed} value - The value associated with the above operation. + * + * @return Object containing the requirements for the given civ, or false if the civ cannot research the tech. + */ +function InterpretTechRequirements(civ, operator, value) +{ + let requirements = []; + + switch (operator) + { + case "civ": + return !civ || civ == value ? [] : false; + + case "notciv": + return civ == value ? false : []; + + case "entity": + { + let number = value.number || value.numberOfTypes || 0; + if (number > 0) + requirements.push({ + "entities": [{ + "class": value.class, + "number": number, + "check": value.number ? "count" : "variants" + }] + }); + break; + } + + case "tech": + requirements.push({ + "techs": [value] + }); + break; + + case "all": + { + let civPermitted = undefined; // tri-state (undefined, false, or true) + for (let subvalue of value) + { + let newOper = Object.keys(subvalue)[0]; + let newValue = subvalue[newOper]; + let result = InterpretTechRequirements(civ, newOper, newValue); + + switch (newOper) + { + case "civ": + if (result) + civPermitted = true; + else if (civPermitted !== true) + civPermitted = false; + break; + + case "notciv": + if (!result) + return false; + break; + + case "any": + if (!result) + return false; + // else, fall through + + case "all": + if (!result) + { + let nullcivreqs = InterpretTechRequirements(null, newOper, newValue); + if (!nullcivreqs || !nullcivreqs.length) + civPermitted = false; + continue; + } + // else, fall through + + case "tech": + case "entity": + { + if (result.length) + { + if (!requirements.length) + requirements.push({}); + + let newRequirements = []; + for (let currReq of requirements) + for (let res of result) + { + let newReq = {} + for (let subtype in currReq) + newReq[subtype] = currReq[subtype]; + + for (let subtype in res) + { + if (!newReq[subtype]) + newReq[subtype] = []; + newReq[subtype] = newReq[subtype].concat(res[subtype]); + } + newRequirements.push(newReq); + } + requirements = newRequirements; + } + break; + } + + } + } + if (civPermitted === false) // if and only if false + return false; + break; + } + + case "any": + { + let civPermitted = false; + for (let subvalue of value) + { + let newOper = Object.keys(subvalue)[0]; + let newValue = subvalue[newOper]; + let result = InterpretTechRequirements(civ, newOper, newValue); + + switch (newOper) + { + + case "civ": + if (result) + return []; + break; + + case "notciv": + if (!result) + return false; + civPermitted = true; + break; + + case "any": + if (!result) + { + let nullcivreqs = InterpretTechRequirements(null, newOper, newValue); + if (!nullcivreqs || !nullcivreqs.length) + continue; + return false; + } + // else, fall through + + case "all": + if (!result) + continue; + civPermitted = true; + // else, fall through + + case "tech": + case "entity": + for (let res of result) + requirements.push(res); + break; + + } + } + if (!civPermitted && !requirements.length) + return false; + break; + } + + default: + warn("Unknown requirement operator: "+operator); + } + + return requirements; +} diff --git a/binaries/data/mods/public/globalscripts/Templates.js b/binaries/data/mods/public/globalscripts/Templates.js index 66d445c91d..db54ca4690 100644 --- a/binaries/data/mods/public/globalscripts/Templates.js +++ b/binaries/data/mods/public/globalscripts/Templates.js @@ -351,17 +351,12 @@ function GetTemplateDataHelper(template, player, auraTemplates, resources) */ function GetTechnologyDataHelper(template, civ, resources) { - var ret = {}; + let ret = {}; // Get specific name for this civ or else the generic specific name - var specific; + let specific; if (template.specificName) - { - if (template.specificName[civ]) - specific = template.specificName[civ]; - else - specific = template.specificName['generic']; - } + specific = template.specificName[civ] || template.specificName.generic; ret.name = { "specific": specific, @@ -377,19 +372,7 @@ function GetTechnologyDataHelper(template, civ, resources) ret.tooltip = template.tooltip; ret.requirementsTooltip = template.requirementsTooltip || ""; - // TODO: This doesn't handle all types of requirements - if (template.requirements && template.requirements.class) - ret.classRequirements = { - "class": template.requirements.class, - "number": template.requirements.number - }; - else if (template.requirements && template.requirements.all) - for (let req of template.requirements.all) - if (req.class) - ret.classRequirements = { - "class": req.class, - "number": req.number - }; + ret.reqs = DeriveTechnologyRequirements(template, civ); ret.description = template.description; diff --git a/binaries/data/mods/public/globalscripts/utility.js b/binaries/data/mods/public/globalscripts/utility.js index 6146b46342..cb854d1761 100644 --- a/binaries/data/mods/public/globalscripts/utility.js +++ b/binaries/data/mods/public/globalscripts/utility.js @@ -34,3 +34,13 @@ function shuffleArray(source) } return result; } + +/** + * Removes prefixing path from a path or filename, leaving just the file's name (with extension) + * + * ie. a/b/c/file.ext -> file.ext + */ +function basename(path) +{ + return path.slice(path.lastIndexOf("/") + 1); +} diff --git a/binaries/data/mods/public/gui/common/tooltips.js b/binaries/data/mods/public/gui/common/tooltips.js index adae9432b7..460c520618 100644 --- a/binaries/data/mods/public/gui/common/tooltips.js +++ b/binaries/data/mods/public/gui/common/tooltips.js @@ -459,13 +459,13 @@ function getEntityCostTooltip(template, trainNum, entity) return ""; } -function getRequiredTechnologyTooltip(technologyEnabled, requiredTechnology) +function getRequiredTechnologyTooltip(technologyEnabled, requiredTechnology, civ) { if (technologyEnabled) return ""; return sprintf(translate("Requires %(technology)s"), { - "technology": getEntityNames(GetTechnologyData(requiredTechnology)) + "technology": getEntityNames(GetTechnologyData(requiredTechnology, civ)) }); } diff --git a/binaries/data/mods/public/gui/session/selection_panels.js b/binaries/data/mods/public/gui/session/selection_panels.js index 6bc6e12be0..74789e0cdb 100644 --- a/binaries/data/mods/public/gui/session/selection_panels.js +++ b/binaries/data/mods/public/gui/session/selection_panels.js @@ -350,7 +350,7 @@ g_SelectionPanels.Construction = { let limits = getEntityLimitAndCount(data.playerState, data.item); tooltips.push( formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers), - getRequiredTechnologyTooltip(technologyEnabled, template.requiredTechnology), + getRequiredTechnologyTooltip(technologyEnabled, template.requiredTechnology, GetSimState().players[data.player].civ), getNeededResourcesTooltip(neededResources)); data.button.tooltip = tooltips.filter(tip => tip).join("\n"); @@ -794,12 +794,14 @@ g_SelectionPanels.Research = { setPanelObjectPosition(pair, data.i, data.rowLength); // Handle one or two techs (tech pair) + let player = data.player; for (let i in techs) { let tech = techs[i]; + let playerState = GetSimState().players[player]; // Don't change the object returned by GetTechnologyData - let template = clone(GetTechnologyData(tech)); + let template = clone(GetTechnologyData(tech, playerState.civ)); if (!template) return false; @@ -808,12 +810,12 @@ g_SelectionPanels.Research = { let neededResources = Engine.GuiInterfaceCall("GetNeededResources", { "cost": template.cost, - "player": data.player + "player": player }); let requirementsPassed = Engine.GuiInterfaceCall("CheckTechnologyRequirements", { "tech": tech, - "player": data.player + "player": player }); let button = Engine.GetGUIObjectByName("unitResearchButton[" + position + "]"); @@ -828,13 +830,40 @@ g_SelectionPanels.Research = { if (!requirementsPassed) { let tip = template.requirementsTooltip; - if (template.classRequirements) + let reqs = template.reqs; + for (let req of reqs) { - let player = data.player; - let current = GetSimState().players[player].classCounts[template.classRequirements.class] || 0; - let remaining = template.classRequirements.number - current; - tip += " " + sprintf(translatePlural("Remaining: %(number)s to build.", "Remaining: %(number)s to build.", remaining), { - "number": remaining + 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": entity.class + })); + } + + tip += " " + sprintf(translate("Remaining: %(entityCounts)s"), { + "entityCounts": entityCounts.join(translate(", ")) }); } tooltips.push(tip); @@ -1064,7 +1093,7 @@ g_SelectionPanels.Training = { "[color=\"" + g_HotkeyColor + "\"]" + formatBatchTrainingString(buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch) + "[/color]", - getRequiredTechnologyTooltip(technologyEnabled, template.requiredTechnology), + getRequiredTechnologyTooltip(technologyEnabled, template.requiredTechnology, GetSimState().players[data.player].civ), getNeededResourcesTooltip(neededResources)); data.button.tooltip = tooltips.filter(tip => tip).join("\n"); @@ -1154,7 +1183,7 @@ g_SelectionPanels.Upgrade = { tooltips.push( getEntityCostTooltip(data.item), formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers), - getRequiredTechnologyTooltip(technologyEnabled, data.item.requiredTechnology), + getRequiredTechnologyTooltip(technologyEnabled, data.item.requiredTechnology, GetSimState().players[data.player].civ), getNeededResourcesTooltip(neededResources)); tooltip = tooltips.filter(tip => tip).join("\n"); diff --git a/binaries/data/mods/public/gui/session/session.js b/binaries/data/mods/public/gui/session/session.js index e2b43aae5d..d0b479581a 100644 --- a/binaries/data/mods/public/gui/session/session.js +++ b/binaries/data/mods/public/gui/session/session.js @@ -222,16 +222,19 @@ function GetTemplateDataWithoutLocalization(templateName) return g_TemplateDataWithoutLocalization[templateName]; } -function GetTechnologyData(technologyName) +function GetTechnologyData(technologyName, civ) { - if (!(technologyName in g_TechnologyData)) + if (!g_TechnologyData[civ]) + g_TechnologyData[civ] = {}; + + if (!(technologyName in g_TechnologyData[civ])) { - let template = Engine.GuiInterfaceCall("GetTechnologyData", technologyName); + let template = Engine.GuiInterfaceCall("GetTechnologyData", { "name": technologyName, "civ": civ }); translateObjectKeys(template, ["specific", "generic", "description", "tooltip", "requirementsTooltip"]); - g_TechnologyData[technologyName] = template; + g_TechnologyData[civ][technologyName] = template; } - return g_TechnologyData[technologyName]; + return g_TechnologyData[civ][technologyName]; } function init(initData, hotloadData) diff --git a/binaries/data/mods/public/gui/structree/draw.js b/binaries/data/mods/public/gui/structree/draw.js index bb437cc8b7..44be507838 100644 --- a/binaries/data/mods/public/gui/structree/draw.js +++ b/binaries/data/mods/public/gui/structree/draw.js @@ -97,9 +97,9 @@ function draw() if (stru.production.technology[prod_pha]) for (let prod of stru.production.technology[prod_pha]) { - prod = clone(depath(prod).slice(0,5) == "phase" ? + prod = clone(basename(prod).slice(0,5) == "phase" ? g_ParsedData.phases[prod] : - g_ParsedData.techs[prod]); + g_ParsedData.techs[g_SelectedCiv][prod]); for (let res in stru.techCostMultiplier) if (prod.cost[res]) @@ -183,7 +183,7 @@ function draw() prod = g_ParsedData.units[prod]; break; case "techs": - prod = clone(g_ParsedData.techs[prod]); + prod = clone(g_ParsedData.techs[civCode][prod]); for (let res in trainer.techCostMultiplier) if (prod.cost[res]) prod.cost[res] *= trainer.techCostMultiplier[res]; diff --git a/binaries/data/mods/public/gui/structree/helper.js b/binaries/data/mods/public/gui/structree/helper.js index b0c8577ac8..da01b79e17 100644 --- a/binaries/data/mods/public/gui/structree/helper.js +++ b/binaries/data/mods/public/gui/structree/helper.js @@ -48,11 +48,6 @@ function loadAuraData(templateName) return g_AuraData[templateName]; } -function depath(path) -{ - return path.slice(path.lastIndexOf("/") + 1); -} - /** * This is needed because getEntityCostTooltip in tooltip.js needs to get * the template data of the different wallSet pieces. In the session this @@ -63,3 +58,60 @@ function GetTemplateData(templateName) var template = loadTemplate(templateName); return GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData); } + +/** + * Determines and returns the phase in which a given technology can be + * first researched. Works recursively through the given tech's + * pre-requisite and superseded techs if necessary. + * + * @param {string} techName - The Technology's name + * @return The name of the phase the technology belongs to, or false if + * the current civ can't research this tech + */ +function GetPhaseOfTechnology(techName) +{ + let phaseIdx = -1; + + if (basename(techName).slice(0, 5) === "phase") + { + phaseIdx = g_ParsedData.phaseList.indexOf(GetActualPhase(techName)); + if (phaseIdx > 0) + return g_ParsedData.phaseList[phaseIdx - 1]; + } + + if (!g_ParsedData.techs[g_SelectedCiv][techName]) + warn(g_SelectedCiv + " : " + techName); + let techReqs = g_ParsedData.techs[g_SelectedCiv][techName].reqs; + if (!techReqs) + return false; + + for (let option of techReqs) + if (option.techs) + for (let tech of option.techs) + { + if (basename(tech).slice(0, 5) === "phase") + return tech; + phaseIdx = Math.max(phaseIdx, g_ParsedData.phaseList.indexOf(GetPhaseOfTechnology(tech))); + } + return g_ParsedData.phaseList[phaseIdx] || false; +} + +function GetActualPhase(phaseName) +{ + if (g_ParsedData.phases[phaseName]) + return g_ParsedData.phases[phaseName].actualPhase; + + warn("Unrecognised phase (" + techName + ")"); + return g_ParsedData.phaseList[0]; +} + +function GetPhaseOfTemplate(template) +{ + if (!template.requiredTechnology) + return g_ParsedData.phaseList[0]; + + if (basename(template.requiredTechnology).slice(0, 5) == "phase") + return GetActualPhase(template.requiredTechnology); + + return GetPhaseOfTechnology(template.requiredTechnology); +} diff --git a/binaries/data/mods/public/gui/structree/load.js b/binaries/data/mods/public/gui/structree/load.js index 07a55cb1c6..2d6b58b77c 100644 --- a/binaries/data/mods/public/gui/structree/load.js +++ b/binaries/data/mods/public/gui/structree/load.js @@ -2,18 +2,9 @@ function loadUnit(templateName) { if (!Engine.TemplateExists(templateName)) return null; - var template = loadTemplate(templateName); - var unit = GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData); - unit.phase = false; - - if (unit.requiredTechnology) - { - if (depath(unit.requiredTechnology).slice(0, 5) == "phase") - unit.phase = unit.requiredTechnology; - else if (unit.requiredTechnology.length) - unit.required = unit.requiredTechnology; - } + let template = loadTemplate(templateName); + let unit = GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData); if (template.ProductionQueue) { @@ -54,17 +45,8 @@ function loadUnit(templateName) function loadStructure(templateName) { - var template = loadTemplate(templateName); - var structure = GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData); - structure.phase = false; - - if (structure.requiredTechnology) - { - if (depath(structure.requiredTechnology).slice(0, 5) == "phase") - structure.phase = structure.requiredTechnology; - else if (structure.requiredTechnology.length) - structure.required = structure.requiredTechnology; - } + let template = loadTemplate(templateName); + let structure = GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData); structure.production = { "technology": [], @@ -140,64 +122,12 @@ function loadStructure(templateName) function loadTechnology(techName) { - var template = loadTechData(techName); - var tech = GetTechnologyDataHelper(template, g_SelectedCiv, g_ResourceData); - tech.reqs = {}; + let template = loadTechData(techName); + let tech = GetTechnologyDataHelper(template, g_SelectedCiv, g_ResourceData); if (template.pair !== undefined) tech.pair = template.pair; - if (template.requirements !== undefined) - { - for (let op in template.requirements) - { - let val = template.requirements[op]; - let req = calcReqs(op, val); - - switch (op) - { - case "tech": - tech.reqs.generic = req; - break; - - case "civ": - tech.reqs[req] = []; - break; - - case "any": - if (req[0].length > 0) - for (let r of req[0]) - { - let v = req[0][r]; - if (typeof r == "number") - tech.reqs[v] = []; - else - tech.reqs[r] = v; - } - if (req[1].length > 0) - tech.reqs.generic = req[1]; - break; - - case "all": - if (!req[0].length) - tech.reqs.generic = req[1]; - else - for (let r of req[0]) - tech.reqs[r] = req[1]; - break; - } - } - } - - if (template.supersedes !== undefined) - { - if (tech.reqs.generic !== undefined) - tech.reqs.generic.push(template.supersedes); - else - for (let ck of Object.keys(tech.reqs)) - tech.reqs[ck].push(template.supersedes); - } - return tech; } @@ -219,76 +149,10 @@ function loadTechnologyPair(pairCode) return { "techs": [ pairInfo.top, pairInfo.bottom ], - "req": pairInfo.supersedes || "" + "reqs": DeriveTechnologyRequirements(pairInfo, g_SelectedCiv) }; } -/** - * Calculate the prerequisite requirements of a technology. - * Works recursively if needed. - * - * @param op The base operation. Can be "civ", "tech", "all" or "any". - * @param val The value associated with the above operation. - * - * @return Sorted requirments. - */ -function calcReqs(op, val) -{ - switch (op) - { - case "civ": - case "class": - case "notciv": - case "number": - // nothing needs doing - break; - - case "tech": - if (depath(val).slice(0,4) === "pair") - return loadTechnologyPair(val).techs; - return [ val ]; - - case "all": - case "any": - let t = []; - let c = []; - for (let nv of val) - { - for (let o in nv) - { - let v = nv[o]; - let r = calcReqs(o, v); - switch (o) - { - case "civ": - case "notciv": - c.push(r); - break; - - case "tech": - t = t.concat(r); - break; - - case "any": - c = c.concat(r[0]); - t = t.concat(r[1]); - break; - - case "all": - for (let ci in r[0]) - c[ci] = r[1]; - t = t; - } - } - } - return [ c, t ]; - - default: - warn("Unknown reqs operator: "+op); - } - return val; -} - /** * Unravel phases * @@ -304,28 +168,24 @@ function unravelPhases(techs) { let techdata = techs[techcode]; - if (!("generic" in techdata.reqs) || techdata.reqs.generic.length < 2) + if (!techdata.reqs || !techdata.reqs.length || !techdata.reqs[0].techs || techdata.reqs[0].techs.length < 2) continue; - let reqTech = techs[techcode].reqs.generic[1]; + let reqTech = techs[techcode].reqs[0].techs[1]; - // Tech that can't be researched anywhere - if (!(reqTech in techs)) + if (!techs[reqTech] || !techs[reqTech].reqs.length) continue; - if (!("generic" in techs[reqTech].reqs)) - continue; + let reqPhase = techs[reqTech].reqs[0].techs[0]; + let myPhase = techs[techcode].reqs[0].techs[0]; - let reqPhase = techs[reqTech].reqs.generic[0]; - let myPhase = techs[techcode].reqs.generic[0]; - - if (reqPhase == myPhase || depath(reqPhase).slice(0,5) !== "phase" || depath(myPhase).slice(0,5) !== "phase") + if (reqPhase == myPhase || basename(reqPhase).slice(0,5) !== "phase" || basename(myPhase).slice(0,5) !== "phase") continue; let reqPhasePos = phaseList.indexOf(reqPhase); let myPhasePos = phaseList.indexOf(myPhase); - if (phaseList.length === 0) + if (!phaseList.length) phaseList = [reqPhase, myPhase]; else if (reqPhasePos < 0 && myPhasePos > -1) phaseList.splice(myPhasePos, 0, reqPhase); diff --git a/binaries/data/mods/public/gui/structree/structree.js b/binaries/data/mods/public/gui/structree/structree.js index fcb6a9c257..20f20fdcb6 100644 --- a/binaries/data/mods/public/gui/structree/structree.js +++ b/binaries/data/mods/public/gui/structree/structree.js @@ -61,18 +61,15 @@ function selectCiv(civCode) "structures": [], "techs": [] }; + g_ParsedData.techs[civCode] = {}; // get initial units - var startStructs = []; for (let entity of g_CivData[civCode].StartEntities) { if (entity.Template.slice(0, 5) == "units") g_Lists.units.push(entity.Template); else if (entity.Template.slice(0, 6) == "struct") - { g_Lists.structures.push(entity.Template); - startStructs.push(entity.Template); - } } // Load units and structures @@ -95,44 +92,52 @@ function selectCiv(civCode) var techPairs = {}; for (let techcode of g_Lists.techs) { - let realcode = depath(techcode); + let realcode = basename(techcode); if (realcode.slice(0,4) == "pair" || realcode.indexOf("_pair") > -1) techPairs[techcode] = loadTechnologyPair(techcode); else if (realcode.slice(0,5) == "phase") g_ParsedData.phases[techcode] = loadPhase(techcode); else - g_ParsedData.techs[techcode] = loadTechnology(techcode); + g_ParsedData.techs[civCode][techcode] = loadTechnology(techcode); } // Expand tech pairs for (let paircode in techPairs) { let pair = techPairs[paircode]; + if (pair.reqs === false) + continue; + for (let techcode of pair.techs) { - if (depath(techcode).slice(0, 5) === "phase") + if (basename(techcode).slice(0, 5) === "phase") g_ParsedData.phases[techcode] = loadPhase(techcode); else { let newTech = loadTechnology(techcode); - if (pair.req !== "") - { - if ("generic" in newTech.reqs) - newTech.reqs.generic.concat(techPairs[pair.req].techs); - else - { - for (let civkey of Object.keys(newTech.reqs)) - newTech.reqs[civkey].concat(techPairs[pair.req].techs); - } - } - g_ParsedData.techs[techcode] = newTech; + + if (!newTech.reqs) + newTech.reqs = {}; + else if (newTech.reqs === false) + continue; + + for (let option of pair.reqs) + for (let type in option) + for (let opt in newTech.reqs) + { + if (!newTech.reqs[opt][type]) + newTech.reqs[opt][type] = []; + newTech.reqs[opt][type] = newTech.reqs[opt][type].concat(option[type]); + } + + g_ParsedData.techs[civCode][techcode] = newTech; } } } // Establish phase order - g_ParsedData.phaseList = unravelPhases(g_ParsedData.techs); + g_ParsedData.phaseList = unravelPhases(g_ParsedData.techs[civCode]); for (let phasecode of g_ParsedData.phaseList) { let phaseInfo = loadTechData(phasecode); @@ -170,6 +175,7 @@ function selectCiv(civCode) for (let structCode of g_Lists.structures) { let structInfo = g_ParsedData.structures[structCode]; + structInfo.phase = GetPhaseOfTemplate(structInfo); let structPhaseIdx = g_ParsedData.phaseList.indexOf(structInfo.phase); // If this building is shared with another civ, @@ -181,43 +187,20 @@ function selectCiv(civCode) for (let prod of structInfo.production.technology) if (prod in techPairs) structInfo.production.technology.splice( - structInfo.production.technology.indexOf(prod), 1, - techPairs[prod].techs[0], techPairs[prod].techs[1] + structInfo.production.technology.indexOf(prod), + 1, ...techPairs[prod].techs ); // Sort techs by phase let newProdTech = {}; for (let prod of structInfo.production.technology) { - let phase = ""; + let phase = GetPhaseOfTechnology(prod); + if (phase === false) + continue; - if (depath(prod).slice(0,5) === "phase") - { - phase = g_ParsedData.phaseList.indexOf(g_ParsedData.phases[prod].actualPhase); - if (phase > 0) - phase = g_ParsedData.phaseList[phase - 1]; - } - else if (g_SelectedCiv in g_ParsedData.techs[prod].reqs) - { - for (let req of g_ParsedData.techs[prod].reqs[g_SelectedCiv]) - if (depath(req).slice(0,5) === "phase") - phase = req; - } - else if ("generic" in g_ParsedData.techs[prod].reqs) - { - for (let req of g_ParsedData.techs[prod].reqs.generic) - if (depath(req).slice(0,5) === "phase") - phase = req; - } - - if (depath(phase).slice(0,5) !== "phase" || - g_ParsedData.phaseList.indexOf(phase) < structPhaseIdx) - { - if (structInfo.phase !== false) - phase = structInfo.phase; - else - phase = g_ParsedData.phaseList[0]; - } + if (g_ParsedData.phaseList.indexOf(phase) < structPhaseIdx) + phase = structInfo.phase; if (!(phase in newProdTech)) newProdTech[phase] = []; @@ -225,46 +208,19 @@ function selectCiv(civCode) newProdTech[phase].push(prod); } - // Determine phase for units + // Sort units by phase let newProdUnits = {}; for (let prod of structInfo.production.units) { if (!g_ParsedData.units[prod]) continue; - let unit = g_ParsedData.units[prod]; - let phase = ""; + let phase = GetPhaseOfTemplate(g_ParsedData.units[prod]); + if (phase === false) + continue; - if (unit.phase !== false) - { - if (g_ParsedData.phaseList.indexOf(unit.phase) < 0) - phase = g_ParsedData.phases[unit.phase].actualPhase; - else - phase = unit.phase; - } - else if (unit.required !== undefined) - { - if (g_ParsedData.phases[unit.required]) - phase = g_ParsedData.phases[unit.required].actualPhase; - else if (g_ParsedData.techs[unit.required]) - { - let reqs = g_ParsedData.techs[unit.required].reqs; - if (reqs[g_SelectedCiv]) - phase = reqs[g_SelectedCiv][0]; - else if (reqs.generic) - phase = reqs.generic[0]; - else - warn("Empty requirements found on technology " + unit.required); - } - else - warn("Technology " + unit.required + " for " + prod + " not found."); - } - - if (depath(phase).slice(0,5) !== "phase" || g_ParsedData.phaseList.indexOf(phase) < structPhaseIdx) - if (structInfo.phase !== false) - phase = structInfo.phase; - else - phase = g_ParsedData.phaseList[0]; + if (g_ParsedData.phaseList.indexOf(phase) < structPhaseIdx) + phase = structInfo.phase; if (!(phase in newProdUnits)) newProdUnits[phase] = []; @@ -279,20 +235,14 @@ function selectCiv(civCode) } // Determine the buildList for the civ (grouped by phase) - var buildList = {}; - var trainerList = []; + let buildList = {}; + let trainerList = []; for (let pha of g_ParsedData.phaseList) buildList[pha] = []; for (let structCode of g_Lists.structures) { - if (!g_ParsedData.structures[structCode].phase || startStructs.indexOf(structCode) > -1) - g_ParsedData.structures[structCode].phase = g_ParsedData.phaseList[0]; - - let myPhase = g_ParsedData.structures[structCode].phase; - if (g_ParsedData.phaseList.indexOf(myPhase) === -1) - myPhase = g_ParsedData.phases[myPhase].actualPhase; - - buildList[myPhase].push(structCode); + let phase = g_ParsedData.structures[structCode].phase; + buildList[phase].push(structCode); } for (let unitCode of g_Lists.units) if (g_ParsedData.units[unitCode] && g_ParsedData.units[unitCode].production) diff --git a/binaries/data/mods/public/simulation/ai/common-api/gamestate.js b/binaries/data/mods/public/simulation/ai/common-api/gamestate.js index d0663bcf98..293deca9f6 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/gamestate.js +++ b/binaries/data/mods/public/simulation/ai/common-api/gamestate.js @@ -36,7 +36,7 @@ m.GameState.prototype.init = function(SharedScript, state, player) { let k = techs.indexOf(this.phases[i].name); if (k !== -1) { - this.phases[i].requirements = (this.getTemplate(techs[k]))._template.requirements; + this.phases[i].requirements = DeriveTechnologyRequirements(this.getTemplate(techs[k])._template, this.getPlayerCiv()); continue; } for (let tech of techs) @@ -45,7 +45,7 @@ m.GameState.prototype.init = function(SharedScript, state, player) { if (template.replaces && template.replaces.indexOf(this.phases[i].name) != -1) { this.phases[i].name = tech; - this.phases[i].requirements = template.requirements; + this.phases[i].requirements = DeriveTechnologyRequirements(template, this.getPlayerCiv()); break; } } @@ -169,21 +169,24 @@ m.GameState.prototype.cityPhase = function() return this.phases[2].name; }; -m.GameState.prototype.getPhaseRequirements = function(i) +m.GameState.prototype.getPhaseEntityRequirements = function(i) { - if (!this.phases[i-1].requirements) - return undefined; - let requirements = this.phases[i-1].requirements; - if (requirements.number) - return requirements; - else if (requirements.all) + let entityReqs = []; + + for (let requirement of this.phases[i-1].requirements) { - for (let req of requirements.all) - if (req.number) - return req; + if (!requirement.entities) + continue; + for (let entity of requirement.entities) + if (entity.check == "count") + entityReqs.push({ + "class": entity.class, + "count": entity.number + }); } - return undefined; -}; + + return entityReqs; +} m.GameState.prototype.isResearched = function(template) { @@ -213,14 +216,6 @@ m.GameState.prototype.canResearch = function(techTemplateName, noRequirementChec if (noRequirementCheck === true) return true; - // not already researched, check if we can. - // basically a copy of the function in technologyManager since we can't use it. - // Checks the requirements for a technology to see if it can be researched at the current time - - // The technology which this technology supersedes is required - if (template.supersedes() && !this.playerData.researchedTechs[template.supersedes()]) - return false; - // if this is a pair, we must check that the pair tech is not being researched if (template.pair()) { @@ -231,39 +226,52 @@ m.GameState.prototype.canResearch = function(techTemplateName, noRequirementChec return false; } - return this.checkTechRequirements(template.requirements()); + return this.checkTechRequirements(template.requirements(this.playerData.civ)); }; /** - * Private function for checking a set of requirements is met - * basically copies TechnologyManager + * Private function for checking a set of requirements is met. + * Basically copies TechnologyManager, but compares against + * variables only available within the AI */ m.GameState.prototype.checkTechRequirements = function(reqs) { - // If there are no requirements then all requirements are met if (!reqs) + return false; + + if (!reqs.length) return true; - if (reqs.all) - return reqs.all.every(r => this.checkTechRequirements(r)); - if (reqs.any) - return reqs.any.some(r => this.checkTechRequirements(r)); - if (reqs.civ) - return this.playerData.civ == reqs.civ; - if (reqs.notciv) - return this.playerData.civ != reqs.notciv; - if (reqs.tech) - return this.playerData.researchedTechs[reqs.tech] !== undefined && this.playerData.researchedTechs[reqs.tech]; - if (reqs.class && reqs.numberOfTypes) - return this.playerData.typeCountsByClass[reqs.class] && - Object.keys(this.playerData.typeCountsByClass[reqs.class]).length >= reqs.numberOfTypes; - if (reqs.class && reqs.number) - return this.playerData.classCounts[reqs.class] && - this.playerData.classCounts[reqs.class] >= reqs.number; + function doesEntitySpecPass(entity) + { + switch (entity.check) + { + case "count": + if (!this.playerData.classCounts[entity.class] || this.playerData.classCounts[entity.class] < entity.number) + return false; + break; - // The technologies requirements are not a recognised format - error("Bad requirements " + uneval(reqs)); - return false; + case "variants": + if (!this.playerData.typeCountsByClass[entity.class] || Object.keys(this.playerData.typeCountsByClass[entity.class]).length < entity.number) + return false; + break; + } + return true; + }; + + return reqs.some(req => { + return Object.keys(req).every(type => { + switch (type) + { + case "techs": + return req[type].every(tech => !!this.playerData.researchedTechs[tech]); + + case "entities": + return req[type].every(doesEntitySpecPass, this); + } + return false; + }); + }); }; m.GameState.prototype.getMap = function() diff --git a/binaries/data/mods/public/simulation/ai/common-api/technology.js b/binaries/data/mods/public/simulation/ai/common-api/technology.js index 14788a768a..3f9a9189b9 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/technology.js +++ b/binaries/data/mods/public/simulation/ai/common-api/technology.js @@ -97,11 +97,9 @@ m.Technology.prototype.researchTime = function() return this._template.researchTime; }; -m.Technology.prototype.requirements = function() +m.Technology.prototype.requirements = function(civ) { - if (!this._template.requirements) - return undefined; - return this._template.requirements; + return DeriveTechnologyRequirements(this._template, civ); }; m.Technology.prototype.autoResearch = function() diff --git a/binaries/data/mods/public/simulation/ai/petra/headquarters.js b/binaries/data/mods/public/simulation/ai/petra/headquarters.js index 828872231a..add83336ef 100644 --- a/binaries/data/mods/public/simulation/ai/petra/headquarters.js +++ b/binaries/data/mods/public/simulation/ai/petra/headquarters.js @@ -1274,11 +1274,12 @@ m.HQ.prototype.buildTemple = function(gameState, queues) { if (gameState.currentPhase() < 2 || this.econState !== "cityPhasing") return; - let requirements = gameState.getPhaseRequirements(3); - if (!requirements || !requirements.number) - return; - if (gameState.getOwnStructures().filter(API3.Filters.byClass(requirements["class"])).length >= requirements.number) + let requirements = gameState.getPhaseEntityRequirements(3); + if (!requirements.length) return; + for (let entityReq of requirements) + if (gameState.getOwnStructures().filter(API3.Filters.byClass(entityReq.class)).length >= entityReq.count) + return; } if (!this.canBuild(gameState, "structures/{civ}_temple")) return; @@ -1388,7 +1389,7 @@ m.HQ.prototype.manageCorral = function(gameState, queues) * build more houses if needed. * kinda ugly, lots of special cases to both build enough houses but not tooo many… */ -m.HQ.prototype.buildMoreHouses = function(gameState,queues) +m.HQ.prototype.buildMoreHouses = function(gameState, queues) { if (gameState.getPopulationMax() <= gameState.getPopulationLimit()) return; @@ -1418,35 +1419,45 @@ m.HQ.prototype.buildMoreHouses = function(gameState,queues) queues.house.addPlan(plan); } - if (numPlanned > 0 && this.econState == "townPhasing" && gameState.getPhaseRequirements(2)) + if (numPlanned > 0 && this.econState == "townPhasing" && gameState.getPhaseEntityRequirements(2).length) { - let requirements = gameState.getPhaseRequirements(2); - let count = gameState.getOwnStructures().filter(API3.Filters.byClass(requirements["class"])).length; - if (requirements && count < requirements.number && this.stopBuilding.has(gameState.applyCiv("structures/{civ}_house"))) + let houseTemplateName = gameState.applyCiv("structures/{civ}_house"); + let houseTemplate = gameState.getTemplate(houseTemplateName); + + let needed = 0; + for (let entityReq of gameState.getPhaseEntityRequirements(2)) { - if (this.Config.debug > 1) - API3.warn("no room to place a house ... try to be less restrictive"); - this.stopBuilding.delete(gameState.applyCiv("structures/{civ}_house")); - this.requireHouses = true; + if (!houseTemplate.hasClass(entityReq.class)) + continue; + + let count = gameState.getOwnStructures().filter(API3.Filters.byClass(entityReq.class)).length; + if (count < entityReq.count && this.stopBuilding.has(houseTemplateName)) + { + if (this.Config.debug > 1) + API3.warn("no room to place a house ... try to be less restrictive"); + this.stopBuilding.delete(houseTemplateName); + this.requireHouses = true; + } + needed = Math.max(needed, entityReq.count - count); } + let houseQueue = queues.house.plans; for (let i = 0; i < numPlanned; ++i) - { if (houseQueue[i].isGo(gameState)) - ++count; - else if (count < requirements.number) + --needed; + else if (needed > 0) { houseQueue[i].isGo = function () { return true; }; - ++count; + --needed; } - } } if (this.requireHouses) { - let requirements = gameState.getPhaseRequirements(2); - if (gameState.getOwnStructures().filter(API3.Filters.byClass(requirements["class"])).length >= requirements.number) - this.requireHouses = undefined; + let houseTemplate = gameState.getTemplate(gameState.applyCiv("structures/{civ}_house")); + if (gameState.getPhaseEntityRequirements(2).every(req => + !houseTemplate.hasClass(req.class) || gameState.getOwnStructures().filter(API3.Filters.byClass(req.class)).length >= req.count)) + this.requireHouses = undefined; } // When population limit too tight diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js index 932c8cd3de..04ced0a3e3 100644 --- a/binaries/data/mods/public/simulation/components/GuiInterface.js +++ b/binaries/data/mods/public/simulation/components/GuiInterface.js @@ -658,19 +658,19 @@ GuiInterface.prototype.GetTemplateData = function(player, extendedName) return GetTemplateDataHelper(template, player, aurasTemplate, Resources); }; -GuiInterface.prototype.GetTechnologyData = function(player, name) +GuiInterface.prototype.GetTechnologyData = function(player, data) { let cmpDataTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_DataTemplateManager); - let template = cmpDataTemplateManager.GetTechnologyTemplate(name); + let template = cmpDataTemplateManager.GetTechnologyTemplate(data.name); if (!template) { - warn("Tried to get data for invalid technology: " + name); + warn("Tried to get data for invalid technology: " + data.name); return null; } let cmpPlayer = QueryPlayerIDInterface(player, IID_Player); - return GetTechnologyDataHelper(template, cmpPlayer.GetCiv(), Resources); + return GetTechnologyDataHelper(template, data.civ || cmpPlayer.GetCiv(), Resources); }; GuiInterface.prototype.IsTechnologyResearched = function(player, data) diff --git a/binaries/data/mods/public/simulation/components/ProductionQueue.js b/binaries/data/mods/public/simulation/components/ProductionQueue.js index c9c90a51eb..720cafdfea 100644 --- a/binaries/data/mods/public/simulation/components/ProductionQueue.js +++ b/binaries/data/mods/public/simulation/components/ProductionQueue.js @@ -162,7 +162,7 @@ ProductionQueue.prototype.GetTechnologiesList = function() // Remove any technologies that can't be researched by this civ techs = techs.filter(tech => { - let reqs = cmpTechnologyManager.GetTechnologyTemplate(tech).requirements || null; + let reqs = DeriveTechnologyRequirements(cmpTechnologyManager.GetTechnologyTemplate(tech), cmpPlayer.GetCiv()); return cmpTechnologyManager.CheckTechnologyRequirements(reqs, true); }); diff --git a/binaries/data/mods/public/simulation/components/TechnologyManager.js b/binaries/data/mods/public/simulation/components/TechnologyManager.js index f5a17a78de..33a372df69 100644 --- a/binaries/data/mods/public/simulation/components/TechnologyManager.js +++ b/binaries/data/mods/public/simulation/components/TechnologyManager.js @@ -97,17 +97,14 @@ TechnologyManager.prototype.IsTechnologyResearched = function(tech) // Checks the requirements for a technology to see if it can be researched at the current time TechnologyManager.prototype.CanResearch = function(tech) { - var template = this.GetTechnologyTemplate(tech); + let template = this.GetTechnologyTemplate(tech); + if (!template) { warn("Technology \"" + tech + "\" does not exist"); return false; } - // The technology which this technology supersedes is required - if (template.supersedes && !this.IsTechnologyResearched(template.supersedes)) - return false; - if (template.top && this.IsInProgress(template.top) || template.bottom && this.IsInProgress(template.bottom)) return false; @@ -121,50 +118,56 @@ TechnologyManager.prototype.CanResearch = function(tech) if (this.IsTechnologyResearched(tech)) return false; - return this.CheckTechnologyRequirements(template.requirements || null); + return this.CheckTechnologyRequirements(DeriveTechnologyRequirements(template, Engine.QueryInterface(this.entity, IID_Player).GetCiv())); }; /** * Private function for checking a set of requirements is met - * @param reqs Object of technology requirements as given by the technology template - * @param civonly A boolean set to true if only the civ requirement is checked + * @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 are checked - * false otherwise + * @return true if the requirements pass, false otherwise */ -TechnologyManager.prototype.CheckTechnologyRequirements = function(reqs, civonly) +TechnologyManager.prototype.CheckTechnologyRequirements = function(reqs, civonly = false) { - // If there are no requirements then all requirements are met + let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player); + if (!reqs) + return false; + + if (civonly || !reqs.length) return true; - if (reqs.all) - return reqs.all.every(r => this.CheckTechnologyRequirements(r, civonly)); - if (reqs.any) - return reqs.any.some(r => this.CheckTechnologyRequirements(r, civonly)); - if (reqs.civ) + 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; + }); + }); +} + +TechnologyManager.prototype.DoesEntitySpecPass = function(entity) +{ + switch (entity.check) { - let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player); - return cmpPlayer && cmpPlayer.GetCiv() == reqs.civ; + 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 (reqs.notciv) - { - let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player); - return cmpPlayer && cmpPlayer.GetCiv() != reqs.notciv; - } - if (civonly) - return true; - if (reqs.tech) - return this.IsTechnologyResearched(reqs.tech); - if (reqs.class && reqs.numberOfTypes) - return this.typeCountsByClass[reqs.class] && - Object.keys(this.typeCountsByClass[reqs.class]).length >= reqs.numberOfTypes; - if (reqs.class && reqs.number) - return this.classCounts[reqs.class] && - this.classCounts[reqs.class] >= reqs.number; - // The technologies requirements are not a recognised format - error("Bad requirements " + uneval(reqs)); - return false; + return true; }; TechnologyManager.prototype.OnGlobalOwnershipChanged = function(msg) diff --git a/binaries/data/mods/public/simulation/components/tests/test_Technologies.js b/binaries/data/mods/public/simulation/components/tests/test_Technologies.js new file mode 100644 index 0000000000..70081ed076 --- /dev/null +++ b/binaries/data/mods/public/simulation/components/tests/test_Technologies.js @@ -0,0 +1,510 @@ +// TODO: Move this to a folder of tests for GlobalScripts (once one is created) + +// No requirements set in template +let template = {}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []); + +/** + * First, the basics: + */ + +// Technology Requirement +template.requirements = { "tech": "expected_tech" }; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["expected_tech"] }]); + +// Entity Requirement: Count of entities matching given class +template.requirements = { "entity": { "class": "Village", "number": 5 } }; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "entities": [{ "class": "Village", "number": 5, "check": "count" }] }]); + +// Entity Requirement: Count of entities matching given class +template.requirements = { "entity": { "class": "Village", "numberOfTypes": 5 } }; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "entities": [{ "class": "Village", "number": 5, "check": "variants" }] }]); + +// Single `civ` +template.requirements = { "civ": "athen" }; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), false); + +// Single `notciv` +template.requirements = { "notciv": "athen" }; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), []); + + +/** + * Basic `all`s: + */ + +// Multiple techs +template.requirements = { "all": [{ "tech": "tech_A" }, { "tech": "tech_B" }, { "tech": "tech_C" }] }; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A", "tech_B", "tech_C"] }]); + +// Multiple entity definitions +template.requirements = { + "all": [ + { "entity": { "class": "class_A", "number": 5 } }, + { "entity": { "class": "class_B", "number": 5 } }, + { "entity": { "class": "class_C", "number": 5 } } + ] +}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), + [{ "entities": [{ "class": "class_A", "number": 5, "check": "count" }, { "class": "class_B", "number": 5, "check": "count" }, { "class": "class_C", "number": 5, "check": "count" }] }]); + +// A `tech` and an `entity` +template.requirements = { "all": [{ "tech": "tech_A" }, { "entity": { "class": "class_B", "number": 5, "check": "count" } }] }; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A"], "entities": [{ "class": "class_B", "number": 5, "check": "count" }] }]); + +// Multiple `civ`s +template.requirements = { "all": [{ "civ": "civ_A"}, { "civ": "civ_B"}, { "civ": "civ_C"}] }; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_A"), []); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_B"), []); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_C"), []); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_D"), false); + +// Multiple `notciv`s +template.requirements = { "all": [{ "notciv": "civ_A"}, { "notciv": "civ_B"}, { "notciv": "civ_C"}] }; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_A"), false); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_B"), false); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_C"), false); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_D"), []); + +// A `civ` with a tech/entity +template.requirements = { "all": [{ "civ": "athen" }, { "tech": "expected_tech" }] }; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["expected_tech"] }]); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), false); + +template.requirements = { "all": [{ "civ": "athen" }, { "entity": { "class": "Village", "number": 5 } }] }; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "entities": [{ "class": "Village", "number": 5, "check": "count" }] }]); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), false); + +template.requirements = { "all": [{ "civ": "athen" }, { "entity": { "class": "Village", "numberOfTypes": 5 } }] }; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "entities": [{ "class": "Village", "number": 5, "check": "variants" }] }]); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), false); + +// A `notciv` with a tech/entity +template.requirements = { "all": [{ "notciv": "athen" }, { "tech": "expected_tech" }] }; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "techs": ["expected_tech"] }]); + +template.requirements = { "all": [{ "notciv": "athen" }, { "entity": { "class": "Village", "number": 5 } }] }; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "count" }] }]); + +template.requirements = { "all": [{ "notciv": "athen" }, { "entity": { "class": "Village", "numberOfTypes": 5 } }] }; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "variants" }] }]); + + +/** + * Basic `any`s: + */ + +// Multiple techs +template.requirements = { "any": [{ "tech": "tech_A" }, { "tech": "tech_B" }, { "tech": "tech_C" }] }; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A"] }, { "techs": ["tech_B"] }, { "techs": ["tech_C"] }]); + +// Multiple entity definitions +template.requirements = { + "any": [ + { "entity": { "class": "class_A", "number": 5 } }, + { "entity": { "class": "class_B", "number": 5 } }, + { "entity": { "class": "class_C", "number": 5 } } + ] +}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [ + { "entities": [{ "class": "class_A", "number": 5, "check": "count" }] }, + { "entities": [{ "class": "class_B", "number": 5, "check": "count" }] }, + { "entities": [{ "class": "class_C", "number": 5, "check": "count" }] } +]); + +// A tech or an entity +template.requirements = { "any": [{ "tech": "tech_A" }, { "entity": { "class": "class_B", "number": 5, "check": "count" } }] }; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A"] }, { "entities": [{ "class": "class_B", "number": 5, "check": "count" }] }]); + +// Multiple `civ`s +template.requirements = { "any": [{ "civ": "civ_A" }, { "civ": "civ_B" }, { "civ": "civ_C" }] }; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_A"), []); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_B"), []); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_C"), []); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_D"), false); + +// Multiple `notciv`s +template.requirements = { "any": [{ "notciv": "civ_A" }, { "notciv": "civ_B" }, { "notciv": "civ_C" }] }; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_A"), false); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_B"), false); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_C"), false); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civ_D"), []); + +// A `civ` or a tech/entity +template.requirements = { "any": [{ "civ": "athen" }, { "tech": "expected_tech" }] }; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "techs": ["expected_tech"] }]); + +template.requirements = { "any": [{ "civ": "athen" }, { "entity": { "class": "Village", "number": 5 } }] }; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "count" }] }]); + +template.requirements = { "any": [{ "civ": "athen" }, { "entity": { "class": "Village", "numberOfTypes": 5 } }] }; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "variants" }] }]); + +// A `notciv` or a tech +template.requirements = { "any": [{ "notciv": "athen" }, { "tech": "expected_tech" }] }; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "techs": ["expected_tech"] }]); + +template.requirements = { "any": [{ "notciv": "athen" }, { "entity": { "class": "Village", "number": 5 } }] }; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "count" }] }]); + +template.requirements = { "any": [{ "notciv": "athen" }, { "entity": { "class": "Village", "numberOfTypes": 5 } }] }; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "spart"), [{ "entities": [{ "class": "Village", "number": 5, "check": "variants" }] }]); + + +/** + * Complicated `all`s, part 1 - an `all` inside an `all`: + */ + +// Techs +template.requirements = { + "all": [ + { "all": [{ "tech": "tech_A" }, { "tech": "tech_B" }] }, + { "all": [{ "tech": "tech_C" }, { "tech": "tech_D" }] } + ] +}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A", "tech_B", "tech_C", "tech_D"] }]); + +// Techs and entities +template.requirements = { + "all": [ + { "all": [{ "tech": "tech_A" }, { "entity": { "class": "class_A", "number": 5 } }] }, + { "all": [{ "entity": { "class": "class_B", "numberOfTypes": 5 } }, { "tech": "tech_B" }] } + ] +}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ + "techs": ["tech_A", "tech_B"], + "entities": [{ "class": "class_A", "number": 5, "check": "count" }, { "class": "class_B", "number": 5, "check": "variants" }] +}]); + +// Two `civ`s, without and with a tech +template.requirements = { + "all": [ + { "all": [{ "civ": "athen" }, { "civ": "spart" }] } + ] +}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false); + +template.requirements = { + "all": [ + { "tech": "required_tech" }, + { "all": [{ "civ": "athen" }, { "civ": "spart" }] } + ] +}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false); + +// Two `notciv`s, without and with a tech +template.requirements = { + "all": [ + { "all": [{ "notciv": "athen" }, { "notciv": "spart" }] } + ] +}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), []); + +template.requirements = { + "all": [ + { "tech": "required_tech" }, + { "all": [{ "notciv": "athen" }, { "notciv": "spart" }] } + ] +}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]); + +// Inner `all` has a tech and a `civ`/`notciv` +template.requirements = { + "all": [ + { "all": [{ "tech": "tech_A" }, { "civ": "maur" }] }, + { "tech": "tech_B" } + ] +}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_B"] }]); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["tech_A", "tech_B"] }]); + +template.requirements = { + "all": [ + { "all": [{ "tech": "tech_A" }, { "notciv": "maur" }] }, + { "tech": "tech_B" } + ] +} +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["tech_A", "tech_B"] }]); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["tech_B"] }]); + + +/** + * Complicated `all`s, part 2 - an `any` inside an `all`: + */ + +// Techs +template.requirements = { + "all": [ + { "any": [{ "tech": "tech_A" }, { "tech": "tech_B" }] }, + { "any": [{ "tech": "tech_C" }, { "tech": "tech_D" }] } + ] +}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [ + { "techs": ["tech_A", "tech_C"] }, + { "techs": ["tech_A", "tech_D"] }, + { "techs": ["tech_B", "tech_C"] }, + { "techs": ["tech_B", "tech_D"] } +]); + +// Techs and entities +template.requirements = { + "all": [ + { "any": [{ "tech": "tech_A" }, { "entity": { "class": "class_A", "number": 5 } }] }, + { "any": [{ "entity": { "class": "class_B", "numberOfTypes": 5 } }, { "tech": "tech_B" }] } + ] +}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [ + { "techs": ["tech_A"], "entities": [{ "class": "class_B", "number": 5, "check": "variants" }] }, + { "techs": ["tech_A", "tech_B"] }, + { "entities": [{ "class": "class_A", "number": 5, "check": "count" }, { "class": "class_B", "number": 5, "check": "variants" }] }, + { "entities": [{ "class": "class_A", "number": 5, "check": "count" }], "techs": ["tech_B"] } +]); + +// Two `civ`s, without and with a tech +template.requirements = { + "all": [ + { "any": [{ "civ": "athen" }, { "civ": "spart" }] } + ] +}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false); + +template.requirements = { + "all": [ + { "tech": "required_tech" }, + { "any": [{ "civ": "athen" }, { "civ": "spart" }] } + ] +}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false); + +// Two `notciv`s, without and with a tech +template.requirements = { + "all": [ + { "any": [{ "notciv": "athen" }, { "notciv": "spart" }] } + ] +}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), []); + +template.requirements = { + "all": [ + { "tech": "required_tech" }, + { "any": [{ "notciv": "athen" }, { "notciv": "spart" }] } + ] +}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]); + + +/** + * Complicated `any`s, part 1 - an `all` inside an `any`: + */ + +// Techs +template.requirements = { + "any": [ + { "all": [{ "tech": "tech_A" }, { "tech": "tech_B" }] }, + { "all": [{ "tech": "tech_C" }, { "tech": "tech_D" }] } + ] +}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [ + { "techs": ["tech_A", "tech_B"] }, + { "techs": ["tech_C", "tech_D"] } +]); + +// Techs and entities +template.requirements = { + "any": [ + { "all": [{ "tech": "tech_A" }, { "entity": { "class": "class_A", "number": 5 } }] }, + { "all": [{ "entity": { "class": "class_B", "numberOfTypes": 5 } }, { "tech": "tech_B" }] } + ] +}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [ + { "techs": ["tech_A"], "entities": [{ "class": "class_A", "number": 5, "check": "count" }] }, + { "entities": [{ "class": "class_B", "number": 5, "check": "variants" }], "techs": ["tech_B"] } +]); + +// Two `civ`s, without and with a tech +template.requirements = { + "any": [ + { "all": [{ "civ": "athen" }, { "civ": "spart" }] } + ] +}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false); + +template.requirements = { + "any": [ + { "tech": "required_tech" }, + { "all": [{ "civ": "athen" }, { "civ": "spart" }] } + ] +}; +// Note: these requirements don't really make sense, as the `any` makes the `civ`s in the the inner `all` irrelevant. +// We test it anyway as a precursor to later tests. +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]); + +// Two `notciv`s, without and with a tech +template.requirements = { + "any": [ + { "all": [{ "notciv": "athen" }, { "notciv": "spart" }] } + ] +}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), []); + +template.requirements = { + "any": [ + { "tech": "required_tech" }, + { "all": [{ "notciv": "athen" }, { "notciv": "spart" }] } + ] +}; +// Note: these requirements have a result that might seen unexpected at first glance. +// This is because the `notciv`s are rendered irrelevant by the `any`, and they have nothing else to operate on. +// We test it anyway as a precursor for later tests. +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]); + +// Inner `all` has a tech and a `civ`/`notciv` +template.requirements = { + "any": [ + { "all": [{ "civ": "civA" }, { "tech": "tech1" }] }, + { "tech": "tech2" } + ] +}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civA"), [{ "techs": ["tech1"] }, { "techs": ["tech2"] }]); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civB"), [{ "techs": ["tech2"] }]); + +template.requirements = { + "any": [ + { "all": [{ "notciv": "civA" }, { "tech": "tech1" }] }, + { "tech": "tech2" } + ] +}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civA"), [{ "techs": ["tech2"] }]); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civB"), [{ "techs": ["tech1"] }, { "techs": ["tech2"] }]); + + +/** + * Complicated `any`s, part 2 - an `any` inside an `any`: + */ + +// Techs +template.requirements = { + "any": [ + { "any": [{ "tech": "tech_A" }, { "tech": "tech_B" }] }, + { "any": [{ "tech": "tech_C" }, { "tech": "tech_D" }] } + ] +}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [ + { "techs": ["tech_A"] }, + { "techs": ["tech_B"] }, + { "techs": ["tech_C"] }, + { "techs": ["tech_D"] } +]); + +// Techs and entities +template.requirements = { + "any": [ + { "any": [{ "tech": "tech_A" }, { "entity": { "class": "class_A", "number": 5 } }] }, + { "any": [{ "entity": { "class": "class_B", "numberOfTypes": 5 } }, { "tech": "tech_B" }] } + ] +}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [ + { "techs": ["tech_A"] }, + { "entities": [{ "class": "class_A", "number": 5, "check": "count" }] }, + { "entities": [{ "class": "class_B", "number": 5, "check": "variants" }] }, + { "techs": ["tech_B"] } +]); + +// Two `civ`s, without and with a tech +template.requirements = { + "any": [ + { "any": [{ "civ": "athen" }, { "civ": "spart" }] } + ] +}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), []); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), false); + +template.requirements = { + "any": [ + { "tech": "required_tech" }, + { "any": [{ "civ": "athen" }, { "civ": "spart" }] } + ] +}; +// These requirements may not make sense, as the `civ`s are unable to restrict the requirements due to the outer `any` +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]); + +// Two `notciv`s, without and with a tech +template.requirements = { + "any": [ + { "any": [{ "notciv": "athen" }, { "notciv": "spart" }] } + ] +}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), false); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), []); + +template.requirements = { + "any": [ + { "tech": "required_tech" }, + { "any": [{ "notciv": "athen" }, { "notciv": "spart" }] } + ] +}; +// These requirements may not make sense, as the `notciv`s are made irrelevant by the outer `any` +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "athen"), [{ "techs": ["required_tech"] }]); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "maur"), [{ "techs": ["required_tech"] }]); + + +/** + * Further tests + */ + +template.requirements = { + "all": [ + { "tech": "tech1" }, + { "any": [{ "civ": "civA" }, { "civ": "civB" }] }, + { "notciv": "civC" } + ] +}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civA"), [{ "techs": ["tech1"] }]); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civC"), false); + +template.requirements = { + "any": [ + { "all": [{ "civ": "civA" }, { "tech": "tech1" }] }, + { "all": [{ "civ": "civB" }, { "tech": "tech2" }] } + ] +}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civA"), [{ "techs": ["tech1"] }]); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civB"), [{ "techs": ["tech2"] }]); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civC"), false); + +template.requirements = { + "any": [ + { "all": [{ "civ": "civA" }, { "tech": "tech1" }] }, + { "all": [ + { "any": [{ "civ": "civB" }, { "civ": "civC" }] }, + { "tech": "tech2" } + ] } + ] +}; +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civA"), [{ "techs": ["tech1"] }]); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civC"), [{ "techs": ["tech2"] }]); +TS_ASSERT_UNEVAL_EQUALS(DeriveTechnologyRequirements(template, "civD"), false); diff --git a/binaries/data/mods/public/simulation/data/technologies/phase_city.json b/binaries/data/mods/public/simulation/data/technologies/phase_city.json index 809a206965..c14fed5dd3 100644 --- a/binaries/data/mods/public/simulation/data/technologies/phase_city.json +++ b/binaries/data/mods/public/simulation/data/technologies/phase_city.json @@ -6,7 +6,7 @@ }, "description": "Advances from a bustling town to a veritable metropolis, full of the wonders of modern technology.", "cost": { "food": 0, "wood": 0, "stone": 750, "metal": 750 }, - "requirements": { "all": [{ "class": "Town", "number": 4 }, { "notciv": "athen" }] }, + "requirements": { "all": [{ "entity": { "class": "Town", "number": 4 } }, { "notciv": "athen" }] }, "requirementsTooltip": "Requires 4 new Town Phase structures (except Walls and Civic Centers).", "supersedes": "phase_town", "icon": "city_phase.png", diff --git a/binaries/data/mods/public/simulation/data/technologies/phase_city_athen.json b/binaries/data/mods/public/simulation/data/technologies/phase_city_athen.json index a2c2b1085e..2aaf38dd98 100644 --- a/binaries/data/mods/public/simulation/data/technologies/phase_city_athen.json +++ b/binaries/data/mods/public/simulation/data/technologies/phase_city_athen.json @@ -5,7 +5,7 @@ }, "description": "Advances from a bustling town to a veritable metropolis, full of the wonders of modern technology. This is the Athenian city phase, where metal gathering rates are boosted because of the 'Silver Owls' bonus.", "cost": { "food": 0, "wood": 0, "stone": 750, "metal": 750 }, - "requirements": { "all": [{ "class": "Town", "number": 4 }, { "civ": "athen" }] }, + "requirements": { "all": [{ "entity": { "class": "Town", "number": 4 } }, { "civ": "athen" }] }, "requirementsTooltip": "Requires 4 new Town Phase structures (except Walls and Civic Centers).", "supersedes": "phase_town_athen", "replaces": ["phase_city"], diff --git a/binaries/data/mods/public/simulation/data/technologies/phase_town.json b/binaries/data/mods/public/simulation/data/technologies/phase_town.json index 087435dbbb..2bb12d10f2 100644 --- a/binaries/data/mods/public/simulation/data/technologies/phase_town.json +++ b/binaries/data/mods/public/simulation/data/technologies/phase_town.json @@ -6,7 +6,7 @@ }, "description": "Advances from a small village to a bustling town, ready to expand rapidly.", "cost": { "food": 500, "wood": 500, "stone": 0, "metal": 0 }, - "requirements": { "all": [{ "class": "Village", "number": 5 }, { "notciv": "athen" }] }, + "requirements": { "all": [{ "entity": { "class": "Village", "number": 5 } }, { "notciv": "athen" }] }, "requirementsTooltip": "Requires 5 Village Phase structures (except Palisades and Farm Fields).", "supersedes": "phase_village", "icon": "town_phase.png", diff --git a/binaries/data/mods/public/simulation/data/technologies/phase_town_athen.json b/binaries/data/mods/public/simulation/data/technologies/phase_town_athen.json index e5ae4bf0f8..9cf810c496 100644 --- a/binaries/data/mods/public/simulation/data/technologies/phase_town_athen.json +++ b/binaries/data/mods/public/simulation/data/technologies/phase_town_athen.json @@ -5,7 +5,7 @@ }, "description": "Advances from a small village to a bustling town, ready to expand rapidly. This is the Athenian town phase, where metal gathering rates are boosted because of the 'Silver Owls' bonus.", "cost": { "food": 500, "wood": 500, "stone": 0, "metal": 0 }, - "requirements": { "all": [{ "class": "Village", "number": 5 }, { "civ": "athen" }] }, + "requirements": { "all": [{ "entity": { "class": "Village", "number": 5 } }, { "civ": "athen" }] }, "requirementsTooltip": "Requires 5 Village Phase structures (except Palisades and Farm Fields).", "supersedes": "phase_village", "replaces": ["phase_town"], diff --git a/binaries/data/mods/public/simulation/data/technologies/siege_bolt_accuracy.json b/binaries/data/mods/public/simulation/data/technologies/siege_bolt_accuracy.json index 8a969440a3..134223281b 100644 --- a/binaries/data/mods/public/simulation/data/technologies/siege_bolt_accuracy.json +++ b/binaries/data/mods/public/simulation/data/technologies/siege_bolt_accuracy.json @@ -5,16 +5,12 @@ "requirements": { "all": [ { "tech": "phase_city" }, - { - "all": [ - { "notciv": "brit"}, - { "notciv": "gaul" }, - { "notciv": "iber" }, - { "notciv": "maur" }, - { "notciv": "pers" }, - { "notciv": "sele" } - ] - } + { "notciv": "brit"}, + { "notciv": "gaul" }, + { "notciv": "iber" }, + { "notciv": "maur" }, + { "notciv": "pers" }, + { "notciv": "sele" } ] }, "requirementsTooltip": "Unlocked in City Phase.", diff --git a/binaries/data/mods/public/simulation/data/technologies/unlock_shared_dropsites.json b/binaries/data/mods/public/simulation/data/technologies/unlock_shared_dropsites.json index 57f8ad689d..d6007e5ff2 100644 --- a/binaries/data/mods/public/simulation/data/technologies/unlock_shared_dropsites.json +++ b/binaries/data/mods/public/simulation/data/technologies/unlock_shared_dropsites.json @@ -2,7 +2,7 @@ "genericName": "Diaspora", "description": "The extension of trade leads to the permanent establishment of storekeepers and their families in foreign countries, allowing them to exploit the wealth of these countries.", "cost": { "food": 200, "wood": 200, "stone": 100, "metal": 100 }, - "requirements": { "class": "Trader", "number": 3 }, + "requirements": { "entity": { "class": "Trader", "number": 3 } }, "requirementsTooltip": "Requires 3 Traders", "supersedes": "unlock_shared_los", "icon": "diaspora.png",