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.requiredTechnology = template.Identity.Requirements?.Techs;
ret.visibleIdentityClasses = GetVisibleIdentityClasses(template.Identity);
ret.nativeCiv = template.Identity.Civ;
}
@@ -473,7 +473,7 @@
"tooltip": upgrade.Tooltip,
"cost": cost,
"icon": upgrade.Icon || undefined,
- "requiredTechnology": upgrade.RequiredTechnology || undefined
+ "requiredTechnology": upgrade.Requirements?.Techs || undefined
});
}
}
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/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/Requirements.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/helpers/Requirements.js
@@ -0,0 +1,179 @@
+function RequirementsHelper() {}
+
+RequirementsHelper.prototype.DEFAULT_RECURSION_DEPTH = 1;
+
+RequirementsHelper.prototype.EntityRequirementsSchema =
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "";
+
+RequirementsHelper.prototype.TechnologyRequirementsSchema =
+ "" +
+ "" +
+ "tokens" +
+ "" +
+ "" +
+ "";
+
+/**
+ * @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 - The player ID to check the requirements for.
+ * @return {boolean} - Whether the requirements are met.
+ */
+RequirementsHelper.prototype.AreRequirementsMet = function(template, playerID)
+{
+ if (!template || !Object.keys(template).length)
+ return true;
+
+ const cmpTechManager = QueryPlayerIDInterface(playerID, IID_TechnologyManager);
+
+ if (template.All)
+ return this.AllRequirementsMet(template.All, cmpTechManager);
+ if (template.Any)
+ return this.AnyRequirementsMet(template.Any, cmpTechManager);
+ return true;
+};
+
+/**
+ * @param {Object} template - The requirements template for "all".
+ * @param {component} cmpTechManager - The technologyManager component to use when checking.
+ * @return {boolean} - Whether all given requirements are met.
+ */
+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(" "))
+ {
+ const negate = tech[0] === "!";
+ if (negate && cmpTechManager.IsTechnologyResearched(tech.substring(1)))
+ return false;
+ if (!negate && !cmpTechManager.IsTechnologyResearched(tech))
+ return false;
+ }
+ }
+ }
+ return true;
+};
+
+/**
+ * @param {Object} template - The requirements template for "any".
+ * @param {component} cmpTechManager - The technologyManager component to use when checking.
+ * @return {boolean} - Whether any of the given requirements is met.
+ */
+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(" "))
+ {
+ const negate = tech[0] === "!";
+ if (negate && !cmpTechManager.IsTechnologyResearched(tech.substring(1)))
+ return true;
+ if (!negate && 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,673 @@
+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 = (bool) => {
+ if (bool)
+ TS_ASSERT(RequirementsHelper.AreRequirementsMet(template, playerID));
+ else
+ TS_ASSERT(!RequirementsHelper.AreRequirementsMet(template, playerID));
+};
+
+met(true);
+
+
+// Additive requirements (all should to be met).
+// Entity requirements.
+template = {
+ "All": {
+ "Entities": {
+ "class_1": {
+ "Count": 1
+ }
+ }
+ }
+};
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {},
+ "typeCountsByClass": {}
+});
+met(false);
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 0
+ },
+ "typeCountsByClass": {}
+});
+met(false);
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_2": 1
+ },
+ "typeCountsByClass": {}
+});
+met(false);
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 1
+ },
+ "typeCountsByClass": {}
+});
+met(true);
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 1
+ },
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 1
+ }
+ }
+});
+met(true);
+
+
+template = {
+ "All": {
+ "Entities": {
+ "class_1": {
+ "Variants": 2
+ }
+ }
+ }
+};
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 2
+ },
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 2
+ }
+ }
+});
+met(false);
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 2
+ },
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 1,
+ "template_2": 1
+ }
+ }
+});
+met(true);
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 1
+ },
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 1
+ }
+ }
+});
+met(false);
+
+template = {
+ "All": {
+ "Entities": {
+ "class_1": {
+ "Count": 1,
+ "Variants": 2
+ }
+ }
+ }
+};
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 1
+ },
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 1
+ }
+ }
+});
+met(false);
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 2
+ },
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 1,
+ "template_2": 1
+ }
+ }
+});
+met(true);
+
+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
+ }
+ }
+});
+met(false);
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 3
+ },
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 2,
+ "template_2": 1
+ }
+ }
+});
+met(true);
+
+
+// Technology requirements.
+template = {
+ "All": {
+ "Techs": "phase_town"
+ }
+};
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "IsTechnologyResearched": (tech) => false
+});
+met(false);
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "IsTechnologyResearched": (tech) => tech === "phase_town"
+});
+met(true);
+
+template = {
+ "All": {
+ "Techs": "phase_city"
+ }
+};
+met(false);
+
+template = {
+ "All": {
+ "Techs": "phase_town phase_city"
+ }
+};
+met(false);
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "IsTechnologyResearched": (tech) => tech === "phase_town" || tech === "phase_city"
+});
+met(true);
+
+template = {
+ "All": {
+ "Techs": "!phase_city"
+ }
+};
+met(false);
+
+template = {
+ "All": {
+ "Techs": "!phase_town phase_city"
+ }
+};
+met(false);
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "IsTechnologyResearched": (tech) => tech === "phase_city"
+});
+met(true);
+
+
+// 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
+ }
+ }
+});
+met(false);
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 3
+ },
+ "IsTechnologyResearched": (tech) => tech === "phase_town",
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 2,
+ "template_2": 1
+ }
+ }
+});
+met(true);
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 3
+ },
+ "IsTechnologyResearched": (tech) => tech === "phase_city",
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 2,
+ "template_2": 1
+ }
+ }
+});
+met(false);
+
+
+// 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
+ }
+});
+met(false);
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 1
+ }
+});
+met(true);
+
+template = {
+ "Any": {
+ "Entities": {
+ "class_1": {
+ "Count": 5,
+ "Variants": 2
+ }
+ },
+ }
+};
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 3
+ },
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 3,
+ }
+ }
+});
+met(false);
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 3
+ },
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 2,
+ "template_2": 1
+ }
+ }
+});
+met(true);
+
+
+// Technology requirements.
+template = {
+ "Any": {
+ "Techs": "phase_town"
+ }
+};
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "IsTechnologyResearched": (tech) => tech === "phase_city"
+});
+met(false);
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "IsTechnologyResearched": (tech) => tech === "phase_town"
+});
+met(true);
+
+template = {
+ "Any": {
+ "Techs": "phase_town phase_city"
+ }
+};
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "IsTechnologyResearched": (tech) => tech === "phase_city"
+});
+met(true);
+
+template = {
+ "Any": {
+ "Techs": "!phase_town"
+ }
+};
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "IsTechnologyResearched": (tech) => tech === "phase_town"
+});
+met(false);
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "IsTechnologyResearched": (tech) => tech === "phase_city"
+});
+met(true);
+
+template = {
+ "Any": {
+ "Techs": "!phase_town phase_city"
+ }
+};
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "IsTechnologyResearched": (tech) => tech === "phase_town" || tech === "phase_city"
+});
+met(true);
+
+
+// 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(true);
+
+
+// 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
+ }
+ }
+});
+met(false);
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 3
+ },
+ "IsTechnologyResearched": (tech) => tech === "phase_town" || tech === "phase_city",
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 3
+ }
+ }
+});
+met(false);
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 2
+ },
+ "IsTechnologyResearched": (tech) => tech === "phase_city",
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 2
+ }
+ }
+});
+met(true);
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 2
+ },
+ "IsTechnologyResearched": (tech) => false,
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 1,
+ "template_2": 1
+ }
+ }
+});
+met(true);
+
+
+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
+ }
+ }
+});
+met(false);
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 3
+ },
+ "IsTechnologyResearched": (tech) => tech === "phase_town" || tech === "phase_city",
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 3
+ }
+ }
+});
+met(true);
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 2
+ },
+ "IsTechnologyResearched": (tech) => tech === "phase_city",
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 2
+ }
+ }
+});
+met(true);
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 2
+ },
+ "IsTechnologyResearched": (tech) => false,
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 1,
+ "template_2": 1
+ }
+ }
+});
+met(true);
+
+
+// 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
+ }
+ }
+});
+met(false);
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 2
+ },
+ "IsTechnologyResearched": (tech) => tech === "phase_city",
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 2
+ }
+ }
+});
+met(true);
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 2
+ },
+ "IsTechnologyResearched": (tech) => tech === "phase_imperial",
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 2
+ }
+ }
+});
+met(false);
+
+AddMock(playerEnt, IID_TechnologyManager, {
+ "classCounts": {
+ "class_1": 2
+ },
+ "IsTechnologyResearched": (tech) => tech === "cartography" || tech === "phase_imperial",
+ "typeCountsByClass": {
+ "class_1": {
+ "template_1": 2
+ }
+ }
+});
+met(true);