Index: ps/trunk/binaries/data/mods/public/simulation/ai/common-api/entity.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/ai/common-api/entity.js +++ ps/trunk/binaries/data/mods/public/simulation/ai/common-api/entity.js @@ -70,13 +70,7 @@ "hasClasses": function(array) { if (!this._classes) this._classes = this.classes(); - if (!this._classes) - return false; - - for (let cls of array) - if (this._classes.indexOf(cls) == -1) - return false; - return true; + return this._classes && MatchesClassList(this._classes, array); }, "requiredTech": function() { return this.get("Identity/RequiredTechnology"); }, @@ -295,9 +289,9 @@ return Classes; }, - // returns true if the entity counters those classes. + // returns true if the entity counters the target entity. // TODO: refine using the multiplier - "countersClasses": function(classes) { + "counters": function(target) { let attack = this.get("Attack"); if (!attack) return false; @@ -314,10 +308,7 @@ mcounter.concat(bonusClasses.split(" ")); } } - for (let i in classes) - if (mcounter.indexOf(classes[i]) != -1) - return true; - return false; + return target.hasClasses(mcounter); }, // returns, if it exists, the multiplier from each attack against a given class @@ -563,7 +554,7 @@ if (!target.get("Capturable")) return false; let restrictedClasses = this.get("Attack/Capture/RestrictedClasses/_string"); - return !restrictedClasses || !MatchesClassList(target.classes(), restrictedClasses); + return !restrictedClasses || !target.hasClasses(restrictedClasses); }, "isCapturable": function() { return this.get("Capturable") !== undefined; }, @@ -800,7 +791,7 @@ continue; let restrictedClasses = this.get("Attack/" + type + "/RestrictedClasses/_string"); - if (!restrictedClasses || !MatchesClassList(target.classes(), restrictedClasses)) + if (!restrictedClasses || !target.hasClasses(restrictedClasses)) return true; } Index: ps/trunk/binaries/data/mods/public/simulation/ai/common-api/filters.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/ai/common-api/filters.js +++ ps/trunk/binaries/data/mods/public/simulation/ai/common-api/filters.js @@ -12,13 +12,8 @@ "dynamicProperties": [] }), - "byClassesAnd": clsList => ({ - "func": ent => clsList.every(cls => ent.hasClass(cls)), - "dynamicProperties": [] - }), - - "byClassesOr": clsList => ({ - "func": ent => clsList.some(cls => ent.hasClass(cls)), + "byClasses": clsList => ({ + "func": ent => ent.hasClasses(clsList), "dynamicProperties": [] }), Index: ps/trunk/binaries/data/mods/public/simulation/ai/common-api/gamestate.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/ai/common-api/gamestate.js +++ ps/trunk/binaries/data/mods/public/simulation/ai/common-api/gamestate.js @@ -726,9 +726,7 @@ let limit = template.matchLimit(); if (matchCounts && limit && matchCounts[trainable] >= limit) continue; - if (classes.some(c => !template.hasClass(c))) - continue; - if (anticlasses.some(c => template.hasClass(c))) + if (!template.hasClasses(classes) || template.hasClasses(anticlasses)) continue; let category = template.trainingCategory(); if (category && limits[category] && current[category] >= limits[category]) Index: ps/trunk/binaries/data/mods/public/simulation/ai/common-api/technology.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/ai/common-api/technology.js +++ ps/trunk/binaries/data/mods/public/simulation/ai/common-api/technology.js @@ -130,24 +130,7 @@ m.Technology.prototype.isAffected = function(classes) { - if (!this._template.affects) - return false; - - for (let affect of this._template.affects) - { - let reqClasses = affect.split(" "); - let fitting = true; - for (let reqClass of reqClasses) - { - if (classes.indexOf(reqClass) !== -1) - continue; - fitting = false; - break; - } - if (fitting === true) - return true; - } - return false; + return this._template.affects && this._template.affects.some(affect => MatchesClassList(classes, affect)); }; return m; Index: ps/trunk/binaries/data/mods/public/simulation/ai/petra/attackManager.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/ai/petra/attackManager.js +++ ps/trunk/binaries/data/mods/public/simulation/ai/petra/attackManager.js @@ -151,7 +151,7 @@ } } - let bombers = gameState.updatingCollection("bombers", API3.Filters.byClassesOr(["BoltShooter", "StoneThrower"]), gameState.getOwnUnits()); + const bombers = gameState.updatingCollection("bombers", API3.Filters.byClasses(["BoltShooter", "StoneThrower"]), gameState.getOwnUnits()); for (let ent of bombers.values()) { if (!ent.position() || !ent.isIdle() || !ent.attackRange("Ranged")) @@ -475,7 +475,7 @@ continue; let enemyDefense = 0; for (let ent of gameState.getEnemyStructures(i).values()) - if (ent.hasClass("Tower") || ent.hasClass("WallTower") || ent.hasClass("Fortress")) + if (ent.hasClasses(["Tower", "WallTower", "Fortress"])) enemyDefense++; if (enemyDefense > 6) veto[i] = true; Index: ps/trunk/binaries/data/mods/public/simulation/ai/petra/attackPlan.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/ai/petra/attackPlan.js +++ ps/trunk/binaries/data/mods/public/simulation/ai/petra/attackPlan.js @@ -119,7 +119,7 @@ priority = 250; this.unitStat.Infantry = { "priority": 1, "minSize": 10, "targetSize": 20, "batchSize": 2, "classes": ["Infantry"], "interests": [["strength", 1], ["costsResource", 0.5, "stone"], ["costsResource", 0.6, "metal"]] }; - this.unitStat.FastMoving = { "priority": 1, "minSize": 2, "targetSize": 4, "batchSize": 2, "classes": ["FastMoving", "CitizenSoldier"], + this.unitStat.FastMoving = { "priority": 1, "minSize": 2, "targetSize": 4, "batchSize": 2, "classes": ["FastMoving+CitizenSoldier"], "interests": [["strength", 1]] }; if (data && data.targetSize) this.unitStat.Infantry.targetSize = data.targetSize; @@ -128,7 +128,7 @@ else if (type == "Raid") { priority = 150; - this.unitStat.FastMoving = { "priority": 1, "minSize": 3, "targetSize": 4, "batchSize": 2, "classes": ["FastMoving", "CitizenSoldier"], + this.unitStat.FastMoving = { "priority": 1, "minSize": 3, "targetSize": 4, "batchSize": 2, "classes": ["FastMoving+CitizenSoldier"], "interests": [ ["strength", 1] ] }; this.neededShips = 1; } @@ -136,21 +136,21 @@ { priority = 90; // basically we want a mix of citizen soldiers so our barracks have a purpose, and champion units. - this.unitStat.RangedInfantry = { "priority": 0.7, "minSize": 5, "targetSize": 20, "batchSize": 5, "classes": ["Infantry", "Ranged", "CitizenSoldier"], + this.unitStat.RangedInfantry = { "priority": 0.7, "minSize": 5, "targetSize": 20, "batchSize": 5, "classes": ["Infantry+Ranged+CitizenSoldier"], "interests": [["strength", 3]] }; - this.unitStat.MeleeInfantry = { "priority": 0.7, "minSize": 5, "targetSize": 20, "batchSize": 5, "classes": ["Infantry", "Melee", "CitizenSoldier"], + this.unitStat.MeleeInfantry = { "priority": 0.7, "minSize": 5, "targetSize": 20, "batchSize": 5, "classes": ["Infantry+Melee+CitizenSoldier"], "interests": [["strength", 3]] }; - this.unitStat.ChampRangedInfantry = { "priority": 1, "minSize": 3, "targetSize": 18, "batchSize": 3, "classes": ["Infantry", "Ranged", "Champion"], + this.unitStat.ChampRangedInfantry = { "priority": 1, "minSize": 3, "targetSize": 18, "batchSize": 3, "classes": ["Infantry+Ranged+Champion"], "interests": [["strength", 3]] }; - this.unitStat.ChampMeleeInfantry = { "priority": 1, "minSize": 3, "targetSize": 18, "batchSize": 3, "classes": ["Infantry", "Melee", "Champion"], + this.unitStat.ChampMeleeInfantry = { "priority": 1, "minSize": 3, "targetSize": 18, "batchSize": 3, "classes": ["Infantry+Melee+Champion"], "interests": [["strength", 3]] }; - this.unitStat.RangedFastMoving = { "priority": 0.7, "minSize": 4, "targetSize": 20, "batchSize": 4, "classes": ["FastMoving", "Ranged", "CitizenSoldier"], + this.unitStat.RangedFastMoving = { "priority": 0.7, "minSize": 4, "targetSize": 20, "batchSize": 4, "classes": ["FastMoving+Ranged+CitizenSoldier"], "interests": [["strength", 2]] }; - this.unitStat.MeleeFastMoving = { "priority": 0.7, "minSize": 4, "targetSize": 20, "batchSize": 4, "classes": ["FastMoving", "Melee", "CitizenSoldier"], + this.unitStat.MeleeFastMoving = { "priority": 0.7, "minSize": 4, "targetSize": 20, "batchSize": 4, "classes": ["FastMoving+Melee+CitizenSoldier"], "interests": [["strength", 2]] }; - this.unitStat.ChampRangedFastMoving = { "priority": 1, "minSize": 3, "targetSize": 15, "batchSize": 3, "classes": ["FastMoving", "Ranged", "Champion"], + this.unitStat.ChampRangedFastMoving = { "priority": 1, "minSize": 3, "targetSize": 15, "batchSize": 3, "classes": ["FastMoving+Ranged+Champion"], "interests": [["strength", 3]] }; - this.unitStat.ChampMeleeFastMoving = { "priority": 1, "minSize": 3, "targetSize": 15, "batchSize": 3, "classes": ["FastMoving", "Melee", "Champion"], + this.unitStat.ChampMeleeFastMoving = { "priority": 1, "minSize": 3, "targetSize": 15, "batchSize": 3, "classes": ["FastMoving+Melee+Champion"], "interests": [["strength", 2]] }; this.unitStat.Hero = { "priority": 1, "minSize": 0, "targetSize": 1, "batchSize": 1, "classes": ["Hero"], "interests": [["strength", 2]] }; @@ -159,11 +159,11 @@ else { priority = 70; - this.unitStat.RangedInfantry = { "priority": 1, "minSize": 6, "targetSize": 16, "batchSize": 3, "classes": ["Infantry", "Ranged"], + this.unitStat.RangedInfantry = { "priority": 1, "minSize": 6, "targetSize": 16, "batchSize": 3, "classes": ["Infantry+Ranged"], "interests": [["canGather", 1], ["strength", 1.6], ["costsResource", 0.3, "stone"], ["costsResource", 0.3, "metal"]] }; - this.unitStat.MeleeInfantry = { "priority": 1, "minSize": 6, "targetSize": 16, "batchSize": 3, "classes": ["Infantry", "Melee"], + this.unitStat.MeleeInfantry = { "priority": 1, "minSize": 6, "targetSize": 16, "batchSize": 3, "classes": ["Infantry+Melee"], "interests": [["canGather", 1], ["strength", 1.6], ["costsResource", 0.3, "stone"], ["costsResource", 0.3, "metal"]] }; - this.unitStat.FastMoving = { "priority": 1, "minSize": 2, "targetSize": 6, "batchSize": 2, "classes": ["FastMoving", "CitizenSoldier"], + this.unitStat.FastMoving = { "priority": 1, "minSize": 2, "targetSize": 6, "batchSize": 2, "classes": ["FastMoving+CitizenSoldier"], "interests": [["strength", 1]] }; this.neededShips = 3; } @@ -230,7 +230,7 @@ for (let cat in this.unitStat) { let Unit = this.unitStat[cat]; - this.unit[cat] = this.unitCollection.filter(API3.Filters.byClassesAnd(Unit.classes)); + this.unit[cat] = this.unitCollection.filter(API3.Filters.byClasses(Unit.classes)); this.unit[cat].registerUpdates(); if (this.canBuildUnits) this.buildOrders.push([0, Unit.classes, this.unit[cat], Unit, cat]); @@ -341,7 +341,7 @@ // no minsize as we don't want the plan to fail at the last minute though. this.unitStat[name] = unitStats; let Unit = this.unitStat[name]; - this.unit[name] = this.unitCollection.filter(API3.Filters.byClassesAnd(Unit.classes)); + this.unit[name] = this.unitCollection.filter(API3.Filters.byClasses(Unit.classes)); this.unit[name].registerUpdates(); this.buildOrders.push([0, Unit.classes, this.unit[name], Unit, name]); if (resetQueue) @@ -370,7 +370,7 @@ if (!template || !template.available(gameState)) continue; for (let i = 0; i < classes.length; ++i) - if (classes[i].every(c => template.hasClass(c))) + if (template.hasClasses(classes[i])) hasTrainer[i] = true; } } @@ -612,8 +612,7 @@ { // find the actual queue we want let queue = this.queue; - if (firstOrder[3].classes.indexOf("Siege") != -1 || firstOrder[3].classes.indexOf("Elephant") != -1 && - firstOrder[3].classes.indexOf("Melee") != -1 && firstOrder[3].classes.indexOf("Champion") != -1) + if (MatchesClassList(firstOrder[3].classes, ["Siege", "Elephant+Melee+Champion"])) queue = this.queueSiege; else if (firstOrder[3].classes.indexOf("Hero") != -1) queue = this.queueSiege; @@ -689,9 +688,9 @@ // Assign all units without specific role. for (let ent of gameState.getOwnEntitiesByRole(undefined, true).values()) { - if (!ent.hasClass("Unit") || !this.isAvailableUnit(gameState, ent)) - continue; - if (ent.hasClass("Ship") || ent.hasClass("Support") || ent.attackTypes() === undefined) + if (ent.hasClasses(["!Unit", "Ship", "Support"]) || + !this.isAvailableUnit(gameState, ent) || + ent.attackTypes() === undefined) continue; ent.setMetadata(PlayerID, "plan", plan); this.unitCollection.updateEnt(ent); @@ -758,7 +757,7 @@ { if (!ent.position() || ent.getMetadata(PlayerID, "transport") !== undefined) continue; - if (!ent.hasClass("FastMoving") || !ent.hasClass("CitizenSoldier")) + if (!ent.hasClasses(["FastMoving", "CitizenSoldier"])) continue; let raid = gameState.ai.HQ.attackManager.getAttackInPreparation("Raid"); ent.setMetadata(PlayerID, "plan", raid.name); @@ -1371,7 +1370,7 @@ if (orderData[0].target === attacker.id()) continue; let target = gameState.getEntityById(orderData[0].target); - if (target && !target.hasClass("Structure") && !target.hasClass("Support")) + if (target && !target.hasClasses(["Structure", "Support"])) continue; } ent.attack(attacker.id(), allowCapture); @@ -1385,7 +1384,7 @@ if (orderData[0].target === attacker.id()) continue; let target = gameState.getEntityById(orderData[0].target); - if (target && !target.hasClass("Structure") && !target.hasClass("Support")) + if (target && !target.hasClasses(["Structure", "Support"])) { if (!target.hasClass("Ranged") || !attacker.hasClass("Melee")) continue; @@ -1421,7 +1420,7 @@ { if (PETRA.isSiegeUnit(target) || target.hasClass("Hero")) unitTargets[targetId] = -8; - else if (target.hasClass("Champion") || target.hasClass("Ship")) + else if (target.hasClasses(["Champion", "Ship"])) unitTargets[targetId] = -5; else unitTargets[targetId] = -3; @@ -1441,7 +1440,7 @@ { if (this.target.hasClass("Fortress")) targetClassesUnit = { "attack": ["Unit", "Structure"], "avoid": ["Palisade", "Wall"], "vetoEntities": veto }; - else if (this.target.hasClass("Palisade") || this.target.hasClass("Wall")) + else if (this.target.hasClasses(["Palisade", "Wall"])) targetClassesUnit = { "attack": ["Unit", "Structure"], "avoid": ["Fortress"], "vetoEntities": veto }; else targetClassesUnit = { "attack": ["Unit", "Structure"], "avoid": ["Palisade", "Wall", "Fortress"], "vetoEntities": veto }; @@ -1597,7 +1596,7 @@ } else { - let nearby = !ent.hasClass("FastMoving") && !ent.hasClass("Ranged"); + const nearby = !ent.hasClasses(["FastMoving", "Ranged"]); let mUnit = enemyUnits.filter(enemy => { if (!enemy.position() || !ent.canAttackTarget(enemy, PETRA.allowCapture(gameState, ent, enemy))) return false; @@ -1620,10 +1619,10 @@ { mUnit.sort((unitA, unitB) => { let vala = unitA.hasClass("Support") ? 50 : 0; - if (ent.countersClasses(unitA.classes())) + if (ent.counters(unitA)) vala += 100; let valb = unitB.hasClass("Support") ? 50 : 0; - if (ent.countersClasses(unitB.classes())) + if (ent.counters(unitB)) valb += 100; let distA = unitA.getMetadata(PlayerID, "distance"); let distB = unitB.getMetadata(PlayerID, "distance"); @@ -1650,12 +1649,12 @@ let targetClasses = targetClassesUnit; if (maybeUpdate && ent.unitAIState() === "INDIVIDUAL.COMBAT.APPROACHING") // we may be blocked by walls, attack everything { - if (!ent.hasClass("Ranged") && !ent.hasClass("Ship")) + if (!ent.hasClasses(["Ranged", "Ship"])) targetClasses = { "attack": ["Unit", "Structure"], "avoid": ["Ship"], "vetoEntities": veto }; else targetClasses = { "attack": ["Unit", "Structure"], "vetoEntities": veto }; } - else if (!ent.hasClass("Ranged") && !ent.hasClass("Ship")) + else if (!ent.hasClasses(["Ranged", "Ship"])) targetClasses = { "attack": targetClassesUnit.attack, "avoid": targetClassesUnit.avoid.concat("Ship"), "vetoEntities": veto }; ent.attackMove(this.targetPos[0], this.targetPos[1], targetClasses); } Index: ps/trunk/binaries/data/mods/public/simulation/ai/petra/baseManager.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/ai/petra/baseManager.js +++ ps/trunk/binaries/data/mods/public/simulation/ai/petra/baseManager.js @@ -166,9 +166,8 @@ { if (!supply.position()) return; - if (supply.hasClass("Animal")) // moving resources are treated differently - return; - if (supply.hasClass("Field")) // fields are treated separately + // Moving resources and fields are treated differently. + if (supply.hasClasses(["Animal", "Field"])) return; // quick accessibility check if (PETRA.getLandAccess(gameState, supply) != accessIndex) @@ -273,7 +272,7 @@ const template = gameState.getTemplate(gameState.applyCiv(templateName)); // CCs and Docks are handled elsewhere. - if (template.hasClass("CivCentre") || template.hasClass("Dock")) + if (template.hasClasses(["CivCentre", "Dock"])) return { "quality": 0, "pos": [0, 0] }; let halfSize = 0; @@ -523,7 +522,7 @@ for (let ent of roleless) { - if (ent.hasClass("Worker") || ent.hasClass("CitizenSoldier") || ent.hasClass("FishingBoat")) + if (ent.hasClasses(["Worker", "CitizenSoldier", "FishingBoat"])) ent.setMetadata(PlayerID, "role", "worker"); } }; @@ -786,7 +785,7 @@ continue; // we do not build fields if (gameState.ai.HQ.isNearInvadingArmy(target.position())) - if (!target.hasClass("CivCentre") && !target.hasClass("Wall") && + if (!target.hasClasses(["CivCentre", "Wall"]) && (!target.hasClass("Wonder") || !gameState.getVictoryConditions().has("wonder"))) continue; @@ -802,13 +801,12 @@ gameState.getPopulationLimit() < gameState.getPopulationMax()) maxTotalBuilders += 2; let targetNB = 2; - if (target.hasClass("Fortress") || target.hasClass("Wonder") || + if (target.hasClasses(["Fortress", "Wonder"]) || target.getMetadata(PlayerID, "phaseUp") == true) targetNB = 7; - else if (target.hasClass("Barracks") || target.hasClass("Range") || target.hasClass("Stable") || - target.hasClass("Tower") || target.hasClass("Market")) + else if (target.hasClasses(["Barracks", "Range", "Stable", "Tower", "Market"])) targetNB = 4; - else if (target.hasClass("House") || target.hasClass("DropsiteWood")) + else if (target.hasClasses(["House", "DropsiteWood"])) targetNB = 3; if (target.getMetadata(PlayerID, "baseAnchor") == true || @@ -880,7 +878,7 @@ if (gameState.ai.HQ.isNearInvadingArmy(target.position())) { if (target.healthLevel() > 0.5 || - !target.hasClass("CivCentre") && !target.hasClass("Wall") && + !target.hasClasses(["CivCentre", "Wall"]) && (!target.hasClass("Wonder") || !gameState.getVictoryConditions().has("wonder"))) continue; } @@ -893,7 +891,7 @@ let assigned = gameState.getOwnEntitiesByMetadata("target-foundation", target.id()).length; let maxTotalBuilders = Math.ceil(workers.length * builderRatio); let targetNB = 1; - if (target.hasClass("Fortress") || target.hasClass("Wonder")) + if (target.hasClasses(["Fortress", "Wonder"])) targetNB = 3; if (target.getMetadata(PlayerID, "baseAnchor") == true || target.hasClass("Wonder") && gameState.getVictoryConditions().has("wonder")) Index: ps/trunk/binaries/data/mods/public/simulation/ai/petra/buildManager.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/ai/petra/buildManager.js +++ ps/trunk/binaries/data/mods/public/simulation/ai/petra/buildManager.js @@ -122,7 +122,7 @@ */ PETRA.BuildManager.prototype.findStructureWithClass = function(gameState, classes) { - return this.findStructuresByFilter(gameState, API3.Filters.byClassesOr(classes))[0]; + return this.findStructuresByFilter(gameState, API3.Filters.byClasses(classes))[0]; }; PETRA.BuildManager.prototype.hasBuilder = function(template) Index: ps/trunk/binaries/data/mods/public/simulation/ai/petra/defenseArmy.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/ai/petra/defenseArmy.js +++ ps/trunk/binaries/data/mods/public/simulation/ai/petra/defenseArmy.js @@ -493,7 +493,7 @@ // TODO adapt the getMaxStrength function for animals. // For the time being, just increase it for elephants as the returned value is too small. - if (ent.hasClass("Animal") && ent.hasClass("Elephant")) + if (ent.hasClasses(["Animal+Elephant"])) entStrength *= 3; if (remove) Index: ps/trunk/binaries/data/mods/public/simulation/ai/petra/defenseManager.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/ai/petra/defenseManager.js +++ ps/trunk/binaries/data/mods/public/simulation/ai/petra/defenseManager.js @@ -251,7 +251,7 @@ } // TODO what to do for ships ? - if (ent.hasClass("Ship") || ent.hasClass("Trader")) + if (ent.hasClasses(["Ship", "Trader"])) continue; // Check if unit is dangerous "a priori". @@ -428,9 +428,7 @@ return; if (ent.hasClass("Support") || ent.attackTypes() === undefined) return; - if (ent.hasClass("StoneThrower")) - return; - if (ent.hasClass("FishingBoat") || ent.hasClass("Trader")) + if (ent.hasClasses(["StoneThrower", "Support", "FishingBoat"])) return; if (ent.getMetadata(PlayerID, "transport") !== undefined || ent.getMetadata(PlayerID, "transporter") !== undefined) @@ -624,7 +622,7 @@ // Signal this attacker to our defense manager, except if we are in enemy territory. // TODO treat ship attack. if (attacker && attacker.position() && attacker.getMetadata(PlayerID, "PartOfArmy") === undefined && - !attacker.hasClass("Structure") && !attacker.hasClass("Ship")) + !attacker.hasClasses(["Structure", "Ship"])) { let territoryOwner = this.territoryMap.getOwner(attacker.position()); if (territoryOwner == 0 || gameState.isPlayerAlly(territoryOwner)) @@ -695,7 +693,7 @@ let unitAIState = target.unitAIState(); let unitAIStateOrder = unitAIState ? unitAIState.split(".")[1] : ""; if (unitAIStateOrder == "COMBAT" && (currentTarget == attacker.id() || - !currentTarget.hasClass("Structure") && !currentTarget.hasClass("Support"))) + !currentTarget.hasClasses(["Structure", "Support"]))) continue; if (unitAIStateOrder == "REPAIR" && currentTarget.hasDefensiveFire()) continue; @@ -776,7 +774,7 @@ let units = gameState.getOwnUnits().filter(ent => { if (!ent.position()) return false; - if (!MatchesClassList(ent.classes(), garrisonArrowClasses)) + if (!ent.hasClasses(garrisonArrowClasses)) return false; if (typeGarrison != "decay" && !allowMelee && ent.attackTypes().indexOf("Melee") != -1) return false; @@ -828,7 +826,7 @@ { if (!ent.isGarrisonHolder()) continue; - if (!MatchesClassList(unit.classes(), ent.garrisonableClasses())) + if (!unit.hasClasses(ent.garrisonableClasses())) continue; if (garrisonManager.numberOfGarrisonedSlots(ent) >= ent.garrisonMax()) continue; @@ -864,7 +862,7 @@ continue; if (!emergency && !ent.buffHeal()) continue; - if (!MatchesClassList(unit.classes(), ent.garrisonableClasses())) + if (!unit.hasClasses(ent.garrisonableClasses())) continue; if (garrisonManager.numberOfGarrisonedSlots(ent) >= ent.garrisonMax() && (!emergency || !ent.garrisoned().length)) Index: ps/trunk/binaries/data/mods/public/simulation/ai/petra/entityExtend.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/ai/petra/entityExtend.js +++ ps/trunk/binaries/data/mods/public/simulation/ai/petra/entityExtend.js @@ -1,7 +1,7 @@ /** returns true if this unit should be considered as a siege unit */ PETRA.isSiegeUnit = function(ent) { - return ent.hasClass("Siege") || ent.hasClass("Elephant") && ent.hasClass("Melee"); + return ent.hasClasses(["Siege", "Elephant+Melee"]); }; /** returns true if this unit should be considered as "fast". */ @@ -208,9 +208,8 @@ let bonus = bonuses[key]; if (bonus.Civ && bonus.Civ !== target.civ()) continue; - if (bonus.Classes && bonus.Classes.split(/\s+/).some(cls => !target.hasClass(cls))) - continue; - attackBonus *= bonus.Multiplier; + if (!bonus.Classes || target.hasClasses(bonus.Classes)) + attackBonus *= bonus.Multiplier; } return attackBonus; }; Index: ps/trunk/binaries/data/mods/public/simulation/ai/petra/garrisonManager.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/ai/petra/garrisonManager.js +++ ps/trunk/binaries/data/mods/public/simulation/ai/petra/garrisonManager.js @@ -294,7 +294,7 @@ let capture = ent.capturePoints(); if (capture && capture[PlayerID] / capture.reduce((a, b) => a + b) < 0.8) return true; - if (MatchesClassList(ent.classes(), holder.getGarrisonArrowClasses())) + if (ent.hasClasses(holder.getGarrisonArrowClasses())) { if (around.unit || around.defenseStructure) return true; Index: ps/trunk/binaries/data/mods/public/simulation/ai/petra/headquarters.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/ai/petra/headquarters.js +++ ps/trunk/binaries/data/mods/public/simulation/ai/petra/headquarters.js @@ -334,7 +334,7 @@ } if (ent.hasClass("Ship")) PETRA.setSeaAccess(gameState, ent); - if (!ent.hasClass("Support") && !ent.hasClass("Ship") && ent.attackTypes() !== undefined) + if (!ent.hasClasses(["Support", "Ship"]) && ent.attackTypes() !== undefined) ent.setMetadata(PlayerID, "plan", -1); continue; } @@ -939,7 +939,7 @@ halfSize = +template.get("Footprint/Circle/@radius"); let ccEnts = gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre")); - let dpEnts = gameState.getOwnDropsites().filter(API3.Filters.not(API3.Filters.byClassesOr(["CivCentre", "Unit"]))); + const dpEnts = gameState.getOwnDropsites().filter(API3.Filters.not(API3.Filters.byClasses(["CivCentre", "Unit"]))); let ccList = []; for (let cc of ccEnts.values()) ccList.push({ "ent": cc, "pos": cc.position(), "ally": gameState.isPlayerAlly(cc.owner()) }); @@ -1295,7 +1295,7 @@ let bestDistSq; let bestGainMult; let radius = Math.ceil(template.obstructionRadius().max / obstructions.cellSize); - let isNavalMarket = template.hasClass("Naval") && template.hasClass("Trade"); + const isNavalMarket = template.hasClasses(["Naval+Trade"]); let width = this.territoryMap.width; let cellSize = this.territoryMap.cellSize; @@ -1324,7 +1324,7 @@ let gainMultiplier; for (let market of markets) { - if (isNavalMarket && template.hasClass("Naval") && template.hasClass("Trade")) + if (isNavalMarket && template.hasClasses(["Naval+Trade"])) { if (PETRA.getSeaAccess(gameState, market) != gameState.ai.accessibility.getAccessValue(pos, true)) continue; @@ -1394,16 +1394,16 @@ // but requiring a minimal distance with our other defensive structures // and not in range of any enemy defensive structure to avoid building under fire. - let ownStructures = gameState.getOwnStructures().filter(API3.Filters.byClassesOr(["Fortress", "Tower"])).toEntityArray(); + const ownStructures = gameState.getOwnStructures().filter(API3.Filters.byClasses(["Fortress", "Tower"])).toEntityArray(); let enemyStructures = gameState.getEnemyStructures().filter(API3.Filters.not(API3.Filters.byOwner(0))). - filter(API3.Filters.byClassesOr(["CivCentre", "Fortress", "Tower"])); + filter(API3.Filters.byClasses(["CivCentre", "Fortress", "Tower"])); if (!enemyStructures.hasEntities()) // we may be in cease fire mode, build defense against neutrals { enemyStructures = gameState.getNeutralStructures().filter(API3.Filters.not(API3.Filters.byOwner(0))). - filter(API3.Filters.byClassesOr(["CivCentre", "Fortress", "Tower"])); + filter(API3.Filters.byClasses(["CivCentre", "Fortress", "Tower"])); if (!enemyStructures.hasEntities() && !gameState.getAlliedVictory()) enemyStructures = gameState.getAllyStructures().filter(API3.Filters.not(API3.Filters.byOwner(PlayerID))). - filter(API3.Filters.byClassesOr(["CivCentre", "Fortress", "Tower"])); + filter(API3.Filters.byClasses(["CivCentre", "Fortress", "Tower"])); if (!enemyStructures.hasEntities()) return undefined; } @@ -2128,9 +2128,9 @@ if (gameState.isTemplateDisabled(trainable)) continue; let template = gameState.getTemplate(trainable); - if (!template || !template.hasClass("Infantry") || !template.hasClass("CitizenSoldier")) + if (!template || !template.hasClasses(["Infantry+CitizenSoldier"])) continue; - if (autogarrison && !MatchesClassList(template.classes(), garrisonArrowClasses)) + if (autogarrison && !template.hasClasses(garrisonArrowClasses)) continue; if (!total.canAfford(new API3.Resources(template.cost()))) continue; Index: ps/trunk/binaries/data/mods/public/simulation/ai/petra/navalManager.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/ai/petra/navalManager.js +++ ps/trunk/binaries/data/mods/public/simulation/ai/petra/navalManager.js @@ -35,7 +35,7 @@ PETRA.NavalManager.prototype.init = function(gameState, deserializing) { // docks - this.docks = gameState.getOwnStructures().filter(API3.Filters.byClassesOr(["Dock", "Shipyard"])); + this.docks = gameState.getOwnStructures().filter(API3.Filters.byClasses(["Dock", "Shipyard"])); this.docks.registerUpdates(); this.ships = gameState.getOwnUnits().filter(API3.Filters.and(API3.Filters.byClass("Ship"), API3.Filters.not(API3.Filters.byMetadata(PlayerID, "role", "trader")))); @@ -255,7 +255,7 @@ if (!evt.entity) continue; let ent = gameState.getEntityById(evt.entity); - if (ent && ent.isOwn(PlayerID) && ent.foundationProgress() !== undefined && (ent.hasClass("Dock") || ent.hasClass("Shipyard"))) + if (ent && ent.isOwn(PlayerID) && ent.foundationProgress() !== undefined && ent.hasClasses(["Dock", "Shipyard"])) PETRA.setSeaAccess(gameState, ent); } @@ -322,7 +322,7 @@ if (evt.to !== PlayerID) continue; let ent = gameState.getEntityById(evt.entity); - if (ent && (ent.hasClass("Dock") || ent.hasClass("Shipyard"))) + if (ent && ent.hasClasses(["Dock", "Shipyard"])) PETRA.setSeaAccess(gameState, ent); } }; Index: ps/trunk/binaries/data/mods/public/simulation/ai/petra/queueplanBuilding.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/ai/petra/queueplanBuilding.js +++ ps/trunk/binaries/data/mods/public/simulation/ai/petra/queueplanBuilding.js @@ -152,7 +152,7 @@ } return false; } - else if (template.hasClass("Tower") || template.hasClass("Fortress") || template.hasClass("ArmyCamp")) + else if (template.hasClasses(["Tower", "Fortress", "ArmyCamp"])) { let pos = HQ.findDefensiveLocation(gameState, template); if (pos) @@ -217,7 +217,7 @@ let struct = PETRA.getBuiltEntity(gameState, ent); if (struct.resourceDropsiteTypes() && struct.resourceDropsiteTypes().indexOf("food") != -1) { - if (template.hasClass("Field") || template.hasClass("Corral")) + if (template.hasClasses(["Field", "Corral"])) placement.addInfluence(x, z, 80 / cellSize, 50); else // If this is not a field add a negative influence because we want to leave this area for fields placement.addInfluence(x, z, 80 / cellSize, -20); @@ -229,11 +229,11 @@ placement.addInfluence(x, z, 60 / cellSize, 40); // houses are close to other houses alreadyHasHouses = true; } - else if (!ent.hasClass("Wall") || ent.hasClass("Gate")) + else if (ent.hasClasses(["Gate", "!Wall"])) placement.addInfluence(x, z, 60 / cellSize, -40); // and further away from other stuffs } - else if (template.hasClass("Farmstead") && (!ent.hasClass("Field") && !ent.hasClass("Corral") && - (!ent.hasClass("Wall") || ent.hasClass("Gate")))) + else if (template.hasClass("Farmstead") && !ent.hasClasses(["Field", "Corral"]) && + ent.hasClasses(["Gate", "!Wall"])) placement.addInfluence(x, z, 100 / cellSize, -25); // move farmsteads away to make room (Wall test needed for iber) else if (template.hasClass("GarrisonFortress") && ent.hasClass("House")) placement.addInfluence(x, z, 120 / cellSize, -50); @@ -315,11 +315,10 @@ // obstructions.dumpIm(template.buildPlacementType() + "_obstructions.png"); let radius = 0; - if (template.hasClass("Fortress") || template.hasClass("Arsenal") || + if (template.hasClasses(["Fortress", "Arsenal"]) || this.type == gameState.applyCiv("structures/{civ}/elephant_stable")) radius = Math.floor((template.obstructionRadius().max + 8) / obstructions.cellSize); - else if (template.resourceDropsiteTypes() === undefined && !template.hasClass("House") && - !template.hasClass("Field") && !template.hasClass("Market")) + else if (template.resourceDropsiteTypes() === undefined && !template.hasClasses(["House", "Field", "Market"])) radius = Math.ceil((template.obstructionRadius().max + 4) / obstructions.cellSize); else radius = Math.ceil(template.obstructionRadius().max / obstructions.cellSize); Index: ps/trunk/binaries/data/mods/public/simulation/ai/petra/startingStrategy.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/ai/petra/startingStrategy.js +++ ps/trunk/binaries/data/mods/public/simulation/ai/petra/startingStrategy.js @@ -66,7 +66,7 @@ for (let ent of gameState.getOwnEntities().values()) { // do not affect merchant ship immediately to trade as they may-be useful for transport - if (ent.hasClass("Trader") && !ent.hasClass("Ship")) + if (ent.hasClasses(["Trader+!Ship"])) this.tradeManager.assignTrader(ent); let pos = ent.position(); @@ -368,7 +368,7 @@ let num1 = Math.floor(num / 2); let num2 = num1; // first pass to affect ranged infantry - units.filter(API3.Filters.byClassesAnd(["Infantry", "Ranged"])).forEach(ent => { + units.filter(API3.Filters.byClasses(["Infantry+Ranged"])).forEach(ent => { if (!num || !num1) return; if (ent.getMetadata(PlayerID, "allied")) @@ -387,7 +387,7 @@ } }); // second pass to affect melee infantry - units.filter(API3.Filters.byClassesAnd(["Infantry", "Melee"])).forEach(ent => { + units.filter(API3.Filters.byClasses(["Infantry+Melee"])).forEach(ent => { if (!num || !num2) return; if (ent.getMetadata(PlayerID, "allied")) Index: ps/trunk/binaries/data/mods/public/simulation/ai/petra/worker.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/ai/petra/worker.js +++ ps/trunk/binaries/data/mods/public/simulation/ai/petra/worker.js @@ -219,7 +219,7 @@ { let supplyId = ent.unitAIOrderData()[0].target; let supply = gameState.getEntityById(supplyId); - if (supply && !supply.hasClass("Field") && !supply.hasClass("Animal") && + if (supply && !supply.hasClasses(["Field", "Animal"]) && supplyId != ent.getMetadata(PlayerID, "supply")) { let nbGatherers = supply.resourceSupplyNumGatherers() + gameState.ai.HQ.GetTCGatherer(supplyId);