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,20 @@ 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)) - }); + if (Object.keys(requirements).length == 1) + { + const techs = requirements.Techs.split(" "); + if (techs.length == 1) + return sprintf(translate("Requires %(technology)s"), { + "technology": getEntityNames(GetTechnologyData(techs[0], 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,7 +73,7 @@ return this._classes && MatchesClassList(this._classes, array); }, - "requiredTech": function() { return this.get("Identity/RequiredTechnology"); }, + "requiredTech": function() { return this.get("Identity/Requirements/Techs"); }, "available": function(gameState) { let techRequired = this.requiredTech(); 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,176 @@ +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 - 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); + return this.AllRequirementsMet(template, cmpTechManager); +}; + +/** + * @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,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();