1
0
forked from 0ad/0ad

Improve support for classes in PetraAI.

Let PetraAI use MatchesClassList more.

Differential revision: https://code.wildfiregames.com/D2150
Reviewed by: Angen
Comments by: Stan
This was SVN commit r25584.
This commit is contained in:
Freagarach 2021-05-28 06:54:48 +00:00
parent f7ad2daf62
commit 3d7af82328
17 changed files with 89 additions and 129 deletions

View File

@ -70,13 +70,7 @@ m.Template = m.Class({
"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 @@ m.Template = m.Class({
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 @@ m.Template = m.Class({
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 @@ m.Template = m.Class({
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 @@ m.Entity = m.Class({
continue;
let restrictedClasses = this.get("Attack/" + type + "/RestrictedClasses/_string");
if (!restrictedClasses || !MatchesClassList(target.classes(), restrictedClasses))
if (!restrictedClasses || !target.hasClasses(restrictedClasses))
return true;
}

View File

@ -12,13 +12,8 @@ m.Filters = {
"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": []
}),

View File

@ -726,9 +726,7 @@ m.GameState.prototype.findTrainableUnits = function(classes, anticlasses)
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])

View File

@ -130,24 +130,7 @@ m.Technology.prototype.affects = function()
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;

View File

@ -151,7 +151,7 @@ PETRA.AttackManager.prototype.assignBombers = function(gameState)
}
}
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 @@ PETRA.AttackManager.prototype.getEnemyPlayer = function(gameState, attack)
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;

View File

@ -119,7 +119,7 @@ PETRA.AttackPlan = function(gameState, Config, uniqueID, type, data)
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 @@ PETRA.AttackPlan = function(gameState, Config, uniqueID, type, data)
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 @@ PETRA.AttackPlan = function(gameState, Config, uniqueID, type, data)
{
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 @@ PETRA.AttackPlan = function(gameState, Config, uniqueID, type, data)
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 @@ PETRA.AttackPlan.prototype.init = function(gameState)
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 @@ PETRA.AttackPlan.prototype.addBuildOrder = function(gameState, name, unitStats,
// 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 @@ PETRA.AttackPlan.prototype.addSiegeUnits = function(gameState)
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 @@ PETRA.AttackPlan.prototype.trainMoreUnits = function(gameState)
{
// 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 @@ PETRA.AttackPlan.prototype.assignUnits = function(gameState)
// 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 @@ PETRA.AttackPlan.prototype.reassignFastUnit = function(gameState)
{
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 @@ PETRA.AttackPlan.prototype.update = function(gameState, events)
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 @@ PETRA.AttackPlan.prototype.update = function(gameState, events)
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 @@ PETRA.AttackPlan.prototype.update = function(gameState, events)
{
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 @@ PETRA.AttackPlan.prototype.update = function(gameState, events)
{
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 @@ PETRA.AttackPlan.prototype.update = function(gameState, events)
}
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 @@ PETRA.AttackPlan.prototype.update = function(gameState, events)
{
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 @@ PETRA.AttackPlan.prototype.update = function(gameState, events)
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);
}

View File

@ -166,9 +166,8 @@ PETRA.BaseManager.prototype.assignResourceToDropsite = function(gameState, drops
{
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 @@ PETRA.BaseManager.prototype.findBestDropsiteLocation = function(gameState, resou
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 @@ PETRA.BaseManager.prototype.assignRolelessUnits = function(gameState, roleless)
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 @@ PETRA.BaseManager.prototype.assignToFoundations = function(gameState, noRepair)
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 @@ PETRA.BaseManager.prototype.assignToFoundations = function(gameState, noRepair)
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 @@ PETRA.BaseManager.prototype.assignToFoundations = function(gameState, noRepair)
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 @@ PETRA.BaseManager.prototype.assignToFoundations = function(gameState, noRepair)
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"))

View File

@ -122,7 +122,7 @@ PETRA.BuildManager.prototype.findStructuresByFilter = function(gameState, filter
*/
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)

View File

@ -493,7 +493,7 @@ PETRA.DefenseArmy.prototype.evaluateStrength = function(ent, isOwn, remove)
// 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)

View File

@ -251,7 +251,7 @@ PETRA.DefenseManager.prototype.checkEnemyUnits = function(gameState)
}
// 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 @@ PETRA.DefenseManager.prototype.assignDefenders = function(gameState)
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 @@ PETRA.DefenseManager.prototype.checkEvents = function(gameState, events)
// 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 @@ PETRA.DefenseManager.prototype.checkEvents = function(gameState, events)
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 @@ PETRA.DefenseManager.prototype.garrisonUnitsInside = function(gameState, target,
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 @@ PETRA.DefenseManager.prototype.garrisonSiegeUnit = function(gameState, unit)
{
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 @@ PETRA.DefenseManager.prototype.garrisonAttackedUnit = function(gameState, unit,
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))

View File

@ -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 @@ PETRA.getAttackBonus = function(ent, target, type)
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;
};

View File

@ -294,7 +294,7 @@ PETRA.GarrisonManager.prototype.keepGarrisoned = function(ent, holder, around)
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;

View File

@ -334,7 +334,7 @@ PETRA.HQ.prototype.checkEvents = function(gameState, events)
}
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 @@ PETRA.HQ.prototype.findEconomicCCLocation = function(gameState, template, resour
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 @@ PETRA.HQ.prototype.findMarketLocation = function(gameState, template)
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 @@ PETRA.HQ.prototype.findMarketLocation = function(gameState, template)
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 @@ PETRA.HQ.prototype.findDefensiveLocation = function(gameState, template)
// 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 @@ PETRA.HQ.prototype.trainEmergencyUnits = function(gameState, positions)
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;

View File

@ -35,7 +35,7 @@ PETRA.NavalManager = function(Config)
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 @@ PETRA.NavalManager.prototype.checkEvents = function(gameState, queues, events)
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 @@ PETRA.NavalManager.prototype.checkEvents = function(gameState, queues, events)
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);
}
};

View File

@ -152,7 +152,7 @@ PETRA.ConstructionPlan.prototype.findGoodPosition = function(gameState)
}
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 @@ PETRA.ConstructionPlan.prototype.findGoodPosition = function(gameState)
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 @@ PETRA.ConstructionPlan.prototype.findGoodPosition = function(gameState)
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 @@ PETRA.ConstructionPlan.prototype.findGoodPosition = function(gameState)
// 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);

View File

@ -66,7 +66,7 @@ PETRA.HQ.prototype.assignStartingEntities = function(gameState)
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 @@ PETRA.HQ.prototype.dispatchUnits = function(gameState)
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 @@ PETRA.HQ.prototype.dispatchUnits = function(gameState)
}
});
// 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"))

View File

@ -219,7 +219,7 @@ PETRA.Worker.prototype.update = function(gameState, ent)
{
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);