Index: binaries/data/mods/public/globalscripts/Templates.js
===================================================================
--- binaries/data/mods/public/globalscripts/Templates.js
+++ binaries/data/mods/public/globalscripts/Templates.js
@@ -437,7 +437,7 @@
};
ret.icon = template.Identity.Icon;
ret.tooltip = template.Identity.Tooltip;
- ret.requiredTechnology = template.Identity.RequiredTechnology;
+ ret.requirements = template.Identity.Requirements;
ret.visibleIdentityClasses = GetVisibleIdentityClasses(template.Identity);
ret.nativeCiv = template.Identity.Civ;
}
@@ -472,8 +472,8 @@
"entity": upgrade.Entity,
"tooltip": upgrade.Tooltip,
"cost": cost,
- "icon": upgrade.Icon || undefined,
- "requiredTechnology": upgrade.RequiredTechnology || undefined
+ "icon": upgrade.Icon,
+ "requirements": upgrade.Requirements
});
}
}
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
@@ -956,14 +956,17 @@
return "";
}
-function getRequiredTechnologyTooltip(technologyEnabled, requiredTechnology, civ)
+function getRequirementsTooltip(enabled, requirements, civ)
{
- if (technologyEnabled)
+ if (enabled)
return "";
- return sprintf(translate("Requires %(technology)s"), {
- "technology": getEntityNames(GetTechnologyData(requiredTechnology, civ))
- });
+ // Simple requirements (one tech) can be translated on the fly.
+ if ("Techs" in requirements && !requirements.Techs.includes(" "))
+ return sprintf(translate("Requires %(technology)s"), {
+ "technology": getEntityNames(GetTechnologyData(requirements.Techs, civ))
+ });
+ return 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
@@ -293,8 +293,8 @@
if (parentTemplate.Upgrade[upgrade].Entity)
return [inheritedVariance[0], TemplateVariant.upgrade, upgrade.toLowerCase()];
- if (template.Identity.RequiredTechnology)
- return [inheritedVariance[0], TemplateVariant.unlockedByTechnology, template.Identity.RequiredTechnology];
+ if (template.Identity.Requirements?.Techs)
+ return [inheritedVariance[0], TemplateVariant.unlockedByTechnology, template.Identity.Requirements?.Techs];
if (parentTemplate.Cost)
for (let res in parentTemplate.Cost.Resources)
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
@@ -71,12 +71,12 @@
// Set the minimum phase that this entity is available.
// For gaia objects, this is meaningless.
- if (!parsed.requiredTechnology)
+ if (!parsed.requirements)
parsed.phase = this.phaseList[0];
- else if (this.TemplateLoader.isPhaseTech(parsed.requiredTechnology))
- parsed.phase = this.getActualPhase(parsed.requiredTechnology);
+ else if (this.TemplateLoader.isPhaseTech(parsed.requirements.Techs))
+ parsed.phase = this.getActualPhase(parsed.requirements.Techs);
else
- parsed.phase = this.getPhaseOfTechnology(parsed.requiredTechnology, civCode);
+ parsed.phase = this.getPhaseOfTechnology(parsed.requirements.Techs, civCode);
if (template.Identity.Rank)
parsed.promotion = {
@@ -256,14 +256,14 @@
data.cost = upgrade.cost;
data.icon = upgrade.icon || data.icon;
data.tooltip = upgrade.tooltip || data.tooltip;
- data.requiredTechnology = upgrade.requiredTechnology || data.requiredTechnology;
+ data.requirements = upgrade.requirements || data.requirements;
- if (!data.requiredTechnology)
+ if (!data.requirements)
data.phase = this.phaseList[0];
- else if (this.TemplateLoader.isPhaseTech(data.requiredTechnology))
- data.phase = this.getActualPhase(data.requiredTechnology);
+ else if (this.TemplateLoader.isPhaseTech(data.requirements.Techs))
+ data.phase = this.getActualPhase(data.requirements.Techs);
else
- data.phase = this.getPhaseOfTechnology(data.requiredTechnology, civCode);
+ data.phase = this.getPhaseOfTechnology(data.requirements.Techs, civCode);
newUpgrades.push(data);
}
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
@@ -65,15 +65,15 @@
let tooltip = translate(this.Tooltip);
- if (template.requiredTechnology &&
- !Engine.GuiInterfaceCall("IsTechnologyResearched", {
- "tech": template.requiredTechnology,
+ if (template.requirements &&
+ !Engine.GuiInterfaceCall("AreRequirementsMet", {
+ "requirements": template.requirements,
"player": g_ViewedPlayer
}))
{
- tooltip += "\n" + getRequiredTechnologyTooltip(
+ tooltip += "\n" + getRequirementsTooltip(
false,
- template.requiredTechnology,
+ template.requirements,
GetSimState().players[g_ViewedPlayer].civ);
this.diplomacySpyRequest.enabled = false;
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
@@ -166,8 +166,8 @@
if (!template)
return false;
- let technologyEnabled = Engine.GuiInterfaceCall("IsTechnologyResearched", {
- "tech": template.requiredTechnology,
+ const requirementsMet = Engine.GuiInterfaceCall("AreRequirementsMet", {
+ "requirements": template.requirements,
"player": data.player
});
@@ -203,13 +203,13 @@
tooltips.push(
formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers),
formatMatchLimitString(limits.matchLimit, limits.matchCount, limits.type),
- getRequiredTechnologyTooltip(technologyEnabled, template.requiredTechnology, GetSimState().players[data.player].civ),
+ getRequirementsTooltip(requirementsMet, template.requirements, GetSimState().players[data.player].civ),
getNeededResourcesTooltip(neededResources));
data.button.tooltip = tooltips.filter(tip => tip).join("\n");
let modifier = "";
- if (!technologyEnabled || limits.canBeAddedCount == 0)
+ if (!requirementsMet || limits.canBeAddedCount == 0)
{
data.button.enabled = false;
modifier += "color:0 0 0 127:grayscale:";
@@ -985,8 +985,8 @@
if (!template)
return false;
- let technologyEnabled = Engine.GuiInterfaceCall("IsTechnologyResearched", {
- "tech": template.requiredTechnology,
+ const requirementsMet = Engine.GuiInterfaceCall("AreRequirementsMet", {
+ "requirements": template.requirements,
"player": data.player
});
@@ -1043,13 +1043,13 @@
tooltips.push(showTemplateViewerOnRightClickTooltip());
tooltips.push(
formatBatchTrainingString(buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch),
- getRequiredTechnologyTooltip(technologyEnabled, template.requiredTechnology, GetSimState().players[data.player].civ),
+ getRequirementsTooltip(requirementsMet, template.requirements, GetSimState().players[data.player].civ),
getNeededResourcesTooltip(neededResources));
data.button.tooltip = tooltips.filter(tip => tip).join("\n");
let modifier = "";
- if (!technologyEnabled || limits.canBeAddedCount == 0)
+ if (!requirementsMet || limits.canBeAddedCount == 0)
{
data.button.enabled = false;
modifier = "color:0 0 0 127:grayscale:";
@@ -1101,11 +1101,9 @@
let progressOverlay = Engine.GetGUIObjectByName("unitUpgradeProgressSlider[" + data.i + "]");
progressOverlay.hidden = true;
- let technologyEnabled = true;
-
- if (data.item.requiredTechnology)
- technologyEnabled = Engine.GuiInterfaceCall("IsTechnologyResearched", {
- "tech": data.item.requiredTechnology,
+ const requirementsMet = !data.item.requirements ||
+ Engine.GuiInterfaceCall("AreRequirementsMet", {
+ "requirements": data.item.requirements,
"player": data.player
});
@@ -1162,7 +1160,7 @@
getEntityCostTooltip(data.item, undefined, undefined, data.unitEntStates.length),
formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers),
formatMatchLimitString(limits.matchLimit, limits.matchCount, limits.type),
- getRequiredTechnologyTooltip(technologyEnabled, data.item.requiredTechnology, GetSimState().players[data.player].civ),
+ getRequirementsTooltip(requirementsMet, data.item.requirements, GetSimState().players[data.player].civ),
getNeededResourcesTooltip(neededResources),
showTemplateViewerOnRightClickTooltip());
@@ -1174,7 +1172,7 @@
upgradableEntStates.map(state => state.id));
};
- if (!technologyEnabled || limits.canBeAddedCount == 0 &&
+ if (!requirementsMet || limits.canBeAddedCount == 0 &&
!upgradableEntStates.some(state => hasSameRestrictionCategory(data.item.entity, state.template)))
{
data.button.enabled = false;
Index: binaries/data/mods/public/simulation/ai/common-api/entity.js
===================================================================
--- binaries/data/mods/public/simulation/ai/common-api/entity.js
+++ binaries/data/mods/public/simulation/ai/common-api/entity.js
@@ -73,10 +73,19 @@
return this._classes && MatchesClassList(this._classes, array);
},
- "requiredTech": function() { return this.get("Identity/RequiredTechnology"); },
+ "requiredTech": function() {
+ const requirements = this.get("Identity/Requirements");
+ if (!requirements)
+ return "";
+
+ // For now, we only support simple requirements.
+ if ("Techs" in requirements && !requirements.Techs.includes(" "))
+ return requirements.Techs;
+ return true;
+ },
"available": function(gameState) {
- let techRequired = this.requiredTech();
+ const techRequired = this.requiredTech();
if (!techRequired)
return true;
return gameState.isResearched(techRequired);
Index: binaries/data/mods/public/simulation/ai/petra/queueplanBuilding.js
===================================================================
--- binaries/data/mods/public/simulation/ai/petra/queueplanBuilding.js
+++ binaries/data/mods/public/simulation/ai/petra/queueplanBuilding.js
@@ -25,7 +25,7 @@
if (!this.isGo(gameState))
return false;
- if (this.template.requiredTech() && !gameState.isResearched(this.template.requiredTech()))
+ if (!this.template.available(gameState))
return false;
return gameState.ai.HQ.buildManager.hasBuilder(this.type);
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
@@ -659,16 +659,10 @@
return GetTemplateDataHelper(template, owner, aurasTemplate);
};
-GuiInterface.prototype.IsTechnologyResearched = function(player, data)
+GuiInterface.prototype.AreRequirementsMet = function(player, data)
{
- if (!data.tech)
- return true;
-
- let cmpTechnologyManager = QueryPlayerIDInterface(data.player !== undefined ? data.player : player, IID_TechnologyManager);
- if (!cmpTechnologyManager)
- return false;
-
- return cmpTechnologyManager.IsTechnologyResearched(data.tech);
+ return !data.requirements || RequirementsHelper.AreRequirementsMet(data.requirements,
+ data.player !== undefined ? data.player : player);
};
/**
@@ -2074,7 +2068,7 @@
"GetMultipleEntityStates": 1,
"GetAverageRangeForBuildings": 1,
"GetTemplateData": 1,
- "IsTechnologyResearched": 1,
+ "AreRequirementsMet": 1,
"CheckTechnologyRequirements": 1,
"GetStartedResearch": 1,
"GetBattleState": 1,
Index: binaries/data/mods/public/simulation/components/Identity.js
===================================================================
--- binaries/data/mods/public/simulation/components/Identity.js
+++ binaries/data/mods/public/simulation/components/Identity.js
@@ -76,9 +76,7 @@
"" +
"" +
"" +
- "" +
- "" +
- "" +
+ RequirementsHelper.BuildSchema() +
"" +
"" +
"" +
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
@@ -276,8 +276,8 @@
var cmpTempManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var template = cmpTempManager.GetTemplate(templateName);
- if (template.Identity && template.Identity.RequiredTechnology)
- return this.IsTechnologyResearched(template.Identity.RequiredTechnology);
+ 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
return true;
};
Index: binaries/data/mods/public/simulation/components/Upgrade.js
===================================================================
--- binaries/data/mods/public/simulation/components/Upgrade.js
+++ binaries/data/mods/public/simulation/components/Upgrade.js
@@ -40,12 +40,7 @@
"" +
"" +
"" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
+ RequirementsHelper.BuildSchema() +
"" +
"" +
"" +
@@ -147,7 +142,7 @@
"icon": choice.Icon || undefined,
"cost": hasCost ? cost : undefined,
"tooltip": choice.Tooltip || undefined,
- "requiredTechnology": this.GetRequiredTechnology(option),
+ "requirements": this.GetRequirements(option),
});
}
@@ -183,14 +178,14 @@
return "CheckPlacementRestrictions" in this.template[this.upgradeTemplates[template]];
};
-Upgrade.prototype.GetRequiredTechnology = function(templateArg)
+Upgrade.prototype.GetRequirements = function(templateArg)
{
let choice = this.upgradeTemplates[templateArg] || templateArg;
- if (this.template[choice].RequiredTechnology)
- return this.template[choice].RequiredTechnology;
+ if (this.template[choice].Requirements)
+ return this.template[choice].Requirements;
- if (!("RequiredTechnology" in this.template[choice]))
+ if (!("Requirements" in this.template[choice]))
return undefined;
let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
@@ -201,7 +196,7 @@
entType = entType.replace(/\{civ\}/g, cmpIdentity.GetCiv());
let template = cmpTemplateManager.GetTemplate(entType);
- return template.Identity.RequiredTechnology || undefined;
+ return template.Identity.Requirements || undefined;
};
Upgrade.prototype.GetResourceCosts = function(template)
Index: binaries/data/mods/public/simulation/components/tests/test_Identity.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_Identity.js
+++ binaries/data/mods/public/simulation/components/tests/test_Identity.js
@@ -1,3 +1,4 @@
+Engine.LoadHelperScript("Requirements.js");
Engine.LoadComponentScript("Identity.js");
let cmpIdentity = ConstructComponent(5, "Identity", {
@@ -32,7 +33,9 @@
"Classes": { "_string": "CitizenSoldier Human Organic" },
"VisibleClasses": { "_string": "Javelineer" },
"Icon": "units/iber_infantry_javelineer.png",
- "RequiredTechnology": "phase_town"
+ "Requirements": {
+ "Techs": "phase_town"
+ }
});
TS_ASSERT_EQUALS(cmpIdentity.GetCiv(), "iber");
Index: binaries/data/mods/public/simulation/components/tests/test_UpgradeModification.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_UpgradeModification.js
+++ binaries/data/mods/public/simulation/components/tests/test_UpgradeModification.js
@@ -1,4 +1,5 @@
Engine.LoadHelperScript("Player.js");
+Engine.LoadHelperScript("Requirements.js");
Engine.LoadHelperScript("ValueModification.js");
Resources = {
"BuildSchema": type => {
Index: binaries/data/mods/public/simulation/helpers/Commands.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/Commands.js
+++ binaries/data/mods/public/simulation/helpers/Commands.js
@@ -783,10 +783,7 @@
continue;
}
- let cmpTechnologyManager = QueryOwnerInterface(ent, IID_TechnologyManager);
- let requiredTechnology = cmpUpgrade.GetRequiredTechnology(cmd.template);
-
- if (requiredTechnology && (!cmpTechnologyManager || !cmpTechnologyManager.IsTechnologyResearched(requiredTechnology)))
+ if (!RequirementsHelper.AreRequirementsMet(cmpUpgrade.GetRequirements(cmd.template), player))
{
if (g_DebugCommands)
warn("Invalid command: upgrading is not possible for this player or requires unresearched technology: " + uneval(cmd));
Index: binaries/data/mods/public/simulation/helpers/Requirements.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/helpers/Requirements.js
@@ -0,0 +1,164 @@
+function RequirementsHelper() {}
+
+RequirementsHelper.prototype.DEFAULT_RECURSION_DEPTH = 1;
+
+RequirementsHelper.prototype.EntityRequirementsSchema =
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "";
+
+RequirementsHelper.prototype.TechnologyRequirementsSchema =
+ "" +
+ "" +
+ "";
+
+/**
+ * @param {number} recursionDepth - How deep we recurse.
+ * @return {string} - A RelaxRNG schema for requirements.
+ */
+RequirementsHelper.prototype.RequirementsSchema = function(recursionDepth)
+{
+ return "" +
+ "" +
+ this.ChoicesSchema(--recursionDepth) +
+ "";
+};
+
+/**
+ * @param {number} recursionDepth - How deep we recurse.
+ * @return {string} - A RelaxRNG schema for chosing requirements.
+ */
+RequirementsHelper.prototype.ChoicesSchema = function(recursionDepth)
+{
+ const allAnySchema = recursionDepth > 0 ? "" +
+ "" +
+ this.RequirementsSchema(recursionDepth) +
+ "" +
+ "" +
+ this.RequirementsSchema(recursionDepth) +
+ "" : "";
+
+ return "" +
+ "" +
+ allAnySchema +
+ this.EntityRequirementsSchema +
+ this.TechnologyRequirementsSchema +
+ "";
+};
+
+/**
+ * @param {number} recursionDepth - How deeply recursive we build the schema.
+ * @return {string} - A RelaxRNG schema for requirements.
+ */
+RequirementsHelper.prototype.BuildSchema = function(recursionDepth = this.DEFAULT_RECURSION_DEPTH)
+{
+ return "" +
+ "" +
+ "" +
+ this.ChoicesSchema(recursionDepth) +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "";
+};
+
+/**
+ * @param {Object} template - The requirements template as defined above.
+ * @param {number} playerID -
+ * @return {boolean} -
+ */
+RequirementsHelper.prototype.AreRequirementsMet = function(template, playerID)
+{
+ if (!template || !Object.keys(template).length)
+ return true;
+
+ const cmpTechManager = QueryPlayerIDInterface(playerID, IID_TechnologyManager);
+ return this.AllRequirementsMet(template, cmpTechManager);
+};
+
+/**
+ * @param {Object} template - The requirements template for "all".
+ * @param {component} cmpTechManager -
+ * @return {boolean} -
+ */
+RequirementsHelper.prototype.AllRequirementsMet = function(template, cmpTechManager)
+{
+ for (const requirementType in template)
+ {
+ const requirement = template[requirementType];
+ if (requirementType === "All" && !this.AllRequirementsMet(requirement, cmpTechManager))
+ return false;
+ if (requirementType === "Any" && !this.AnyRequirementsMet(requirement, cmpTechManager))
+ return false;
+ if (requirementType === "Entities")
+ {
+ for (const className in requirement)
+ {
+ const entReq = requirement[className];
+ if ("Count" in entReq && (!(className in cmpTechManager.classCounts) || cmpTechManager.classCounts[className] < entReq.Count))
+ return false;
+ if ("Variants" in entReq && (!(className in cmpTechManager.typeCountsByClass) || Object.keys(cmpTechManager.typeCountsByClass[className]).length < entReq.Variants))
+ return false;
+ }
+ }
+ if (requirementType === "Techs")
+ for (const tech of requirement.split(" "))
+ if (tech[0] === "!" ? cmpTechManager.IsTechnologyResearched(tech.substring(1)) :
+ !cmpTechManager.IsTechnologyResearched(tech))
+ return false;
+ }
+ return true;
+};
+
+/**
+ * @param {Object} template - The requirements template for "any".
+ * @param {component} cmpTechManager -
+ * @return {boolean} -
+ */
+RequirementsHelper.prototype.AnyRequirementsMet = function(template, cmpTechManager)
+{
+ for (const requirementType in template)
+ {
+ const requirement = template[requirementType];
+ if (requirementType === "All" && this.AllRequirementsMet(requirement, cmpTechManager))
+ return true;
+ if (requirementType === "Any" && this.AnyRequirementsMet(requirement, cmpTechManager))
+ return true;
+ if (requirementType === "Entities")
+ {
+ for (const className in requirement)
+ {
+ const entReq = requirement[className];
+ if ("Count" in entReq && className in cmpTechManager.classCounts && cmpTechManager.classCounts[className] >= entReq.Count)
+ return true;
+ if ("Variants" in entReq && className in cmpTechManager.typeCountsByClass && Object.keys(cmpTechManager.typeCountsByClass[className]).length >= entReq.Variants)
+ return true;
+ }
+ }
+ if (requirementType === "Techs")
+ for (const tech of requirement.split(" "))
+ if (tech[0] === "!" ? !cmpTechManager.IsTechnologyResearched(tech.substring(1)) :
+ cmpTechManager.IsTechnologyResearched(tech))
+ return true;
+ }
+ return false;
+};
+
+Engine.RegisterGlobal("RequirementsHelper", new RequirementsHelper());
Index: binaries/data/mods/public/simulation/helpers/tests/test_Requirements.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/helpers/tests/test_Requirements.js
@@ -0,0 +1,718 @@
+Engine.LoadComponentScript("interfaces/PlayerManager.js");
+Engine.LoadComponentScript("interfaces/TechnologyManager.js");
+Engine.LoadHelperScript("Player.js");
+Engine.LoadHelperScript("Requirements.js");
+
+const playerID = 1;
+const playerEnt = 11;
+
+AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
+ "GetPlayerByID": () => playerEnt
+});
+
+// First test no requirements.
+let template = {
+};
+
+const met = () => TS_ASSERT(RequirementsHelper.AreRequirementsMet(template, playerID));
+const notMet = () => TS_ASSERT(!RequirementsHelper.AreRequirementsMet(template, playerID));
+
+met();
+
+// Simple requirements are assumed to be additive.
+template = {
+ "Techs": "phase_city"
+};
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "IsTechnologyResearched": (tech) => false
+});
+notMet();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "IsTechnologyResearched": (tech) => tech === "phase_town"
+});
+notMet();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "IsTechnologyResearched": (tech) => tech === "phase_town" || tech === "phase_city"
+});
+met();
+
+template = {
+ "Techs": "cartography phase_city"
+};
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "IsTechnologyResearched": (tech) => false
+});
+notMet();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "IsTechnologyResearched": (tech) => tech === "phase_town"
+});
+notMet();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "IsTechnologyResearched": (tech) => tech === "phase_town" || tech === "phase_city"
+});
+notMet();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "IsTechnologyResearched": (tech) => tech === "cartography" || tech === "phase_town"
+});
+notMet();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "IsTechnologyResearched": (tech) => tech === "cartography" || tech === "phase_city"
+});
+met();
+
+
+// Additive requirements (all should to be met).
+// Entity requirements.
+template = {
+ "All": {
+ "Entities": {
+ "class_1": {
+ "Count": 1
+ }
+ }
+ }
+};
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {},
+ "typeCountsByClass": {}
+});
+notMet();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 0
+ },
+ "typeCountsByClass": {}
+});
+notMet();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_2": 1
+ },
+ "typeCountsByClass": {}
+});
+notMet();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 1
+ },
+ "typeCountsByClass": {}
+});
+met();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 1
+ },
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 1
+ }
+ }
+});
+met();
+
+
+template = {
+ "All": {
+ "Entities": {
+ "class_1": {
+ "Variants": 2
+ }
+ }
+ }
+};
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 2
+ },
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 2
+ }
+ }
+});
+notMet();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 2
+ },
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 1,
+ "template_2": 1
+ }
+ }
+});
+met();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 1
+ },
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 1
+ }
+ }
+});
+notMet();
+
+template = {
+ "All": {
+ "Entities": {
+ "class_1": {
+ "Count": 1,
+ "Variants": 2
+ }
+ }
+ }
+};
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 1
+ },
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 1
+ }
+ }
+});
+notMet();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 2
+ },
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 1,
+ "template_2": 1
+ }
+ }
+});
+met();
+
+template = {
+ "All": {
+ "Entities": {
+ "class_1": {
+ "Count": 3,
+ "Variants": 2
+ }
+ }
+ }
+};
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 2
+ },
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 1,
+ "template_2": 1
+ }
+ }
+});
+notMet();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 3
+ },
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 2,
+ "template_2": 1
+ }
+ }
+});
+met();
+
+
+// Technology requirements.
+template = {
+ "All": {
+ "Techs": "phase_town"
+ }
+};
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "IsTechnologyResearched": (tech) => false
+});
+notMet();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "IsTechnologyResearched": (tech) => tech === "phase_town"
+});
+met();
+
+template = {
+ "All": {
+ "Techs": "phase_city"
+ }
+};
+notMet();
+
+template = {
+ "All": {
+ "Techs": "phase_town phase_city"
+ }
+};
+notMet();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "IsTechnologyResearched": (tech) => tech === "phase_town" || tech === "phase_city"
+});
+met();
+
+template = {
+ "All": {
+ "Techs": "!phase_city"
+ }
+};
+notMet();
+
+template = {
+ "All": {
+ "Techs": "!phase_town phase_city"
+ }
+};
+notMet();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "IsTechnologyResearched": (tech) => tech === "phase_city"
+});
+met();
+
+
+// Combination of Entity and Technology requirements.
+template = {
+ "All": {
+ "Entities": {
+ "class_1": {
+ "Count": 3,
+ "Variants": 2
+ }
+ },
+ "Techs": "phase_town"
+ }
+};
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 3
+ },
+ "IsTechnologyResearched": (tech) => false,
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 2,
+ "template_2": 1
+ }
+ }
+});
+notMet();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 3
+ },
+ "IsTechnologyResearched": (tech) => tech === "phase_town",
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 2,
+ "template_2": 1
+ }
+ }
+});
+met();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 3
+ },
+ "IsTechnologyResearched": (tech) => tech === "phase_city",
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 2,
+ "template_2": 1
+ }
+ }
+});
+notMet();
+
+
+// Choice requirements (at least one needs to be met).
+// Entity requirements.
+template = {
+ "Any": {
+ "Entities": {
+ "class_1": {
+ "Count": 1,
+ }
+ },
+ }
+};
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 0
+ }
+});
+notMet();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 1
+ }
+});
+met();
+
+template = {
+ "Any": {
+ "Entities": {
+ "class_1": {
+ "Count": 5,
+ "Variants": 2
+ }
+ },
+ }
+};
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 3
+ },
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 3,
+ }
+ }
+});
+notMet();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 3
+ },
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 2,
+ "template_2": 1
+ }
+ }
+});
+met();
+
+
+// Technology requirements.
+template = {
+ "Any": {
+ "Techs": "phase_town"
+ }
+};
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "IsTechnologyResearched": (tech) => tech === "phase_city"
+});
+notMet();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "IsTechnologyResearched": (tech) => tech === "phase_town"
+});
+met();
+
+template = {
+ "Any": {
+ "Techs": "phase_town phase_city"
+ }
+};
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "IsTechnologyResearched": (tech) => tech === "phase_city"
+});
+met();
+
+template = {
+ "Any": {
+ "Techs": "!phase_town"
+ }
+};
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "IsTechnologyResearched": (tech) => tech === "phase_town"
+});
+notMet();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "IsTechnologyResearched": (tech) => tech === "phase_city"
+});
+met();
+
+template = {
+ "Any": {
+ "Techs": "!phase_town phase_city"
+ }
+};
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "IsTechnologyResearched": (tech) => tech === "phase_town" || tech === "phase_city"
+});
+met();
+
+
+// Combinational requirements of entities and technologies.
+template = {
+ "Any": {
+ "Entities": {
+ "class_1": {
+ "Count": 3,
+ "Variants": 2
+ }
+ },
+ "Techs": "!phase_town"
+ }
+};
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 3
+ },
+ "IsTechnologyResearched": (tech) => tech === "phase_town",
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 3
+ }
+ }
+});
+met();
+
+
+// Nested requirements.
+template = {
+ "All": {
+ "All": {
+ "Techs": "!phase_town"
+ },
+ "Any": {
+ "Entities": {
+ "class_1": {
+ "Count": 3,
+ "Variants": 2
+ }
+ },
+ "Techs": "phase_city"
+ }
+ }
+};
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 3
+ },
+ "IsTechnologyResearched": (tech) => tech === "phase_town",
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 3
+ }
+ }
+});
+notMet();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 3
+ },
+ "IsTechnologyResearched": (tech) => tech === "phase_town" || tech === "phase_city",
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 3
+ }
+ }
+});
+notMet();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 2
+ },
+ "IsTechnologyResearched": (tech) => tech === "phase_city",
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 2
+ }
+ }
+});
+met();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 2
+ },
+ "IsTechnologyResearched": (tech) => false,
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 1,
+ "template_2": 1
+ }
+ }
+});
+met();
+
+
+template = {
+ "Any": {
+ "All": {
+ "Techs": "!phase_town"
+ },
+ "Any": {
+ "Entities": {
+ "class_1": {
+ "Count": 3,
+ "Variants": 2
+ }
+ },
+ "Techs": "phase_city"
+ }
+ }
+};
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 2
+ },
+ "IsTechnologyResearched": (tech) => tech === "phase_town",
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 2
+ }
+ }
+});
+notMet();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 3
+ },
+ "IsTechnologyResearched": (tech) => tech === "phase_town" || tech === "phase_city",
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 3
+ }
+ }
+});
+met();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 2
+ },
+ "IsTechnologyResearched": (tech) => tech === "phase_city",
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 2
+ }
+ }
+});
+met();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 2
+ },
+ "IsTechnologyResearched": (tech) => false,
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 1,
+ "template_2": 1
+ }
+ }
+});
+met();
+
+
+// Two levels deep nested.
+template = {
+ "All": {
+ "Any": {
+ "All": {
+ "Techs": "cartography phase_imperial",
+ },
+ "Entities": {
+ "class_1": {
+ "Count": 3,
+ "Variants": 2
+ }
+ },
+ "Techs": "phase_city"
+ },
+ "Techs": "!phase_town"
+ }
+};
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 2
+ },
+ "IsTechnologyResearched": (tech) => tech === "phase_town",
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 2
+ }
+ }
+});
+notMet();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 2
+ },
+ "IsTechnologyResearched": (tech) => tech === "phase_city",
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 2
+ }
+ }
+});
+met();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 2
+ },
+ "IsTechnologyResearched": (tech) => tech === "phase_imperial",
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 2
+ }
+ }
+});
+notMet();
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 2
+ },
+ "IsTechnologyResearched": (tech) => tech === "cartography" || tech === "phase_imperial",
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 2
+ }
+ }
+});
+met();