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 @@ -744,6 +744,34 @@ return false; }, + /** + * Returns true when an entity can attack a target. + * Derived from Attack.js' similary named function. + */ + "canAttackTarget": function(target) + { + let attackTypes = this.get("Attack"); + if (!attackTypes) + return false; + + let canCapture = this.canCapture(target); + let armourStrengths = target.get("Armour"); + if (!armourStrengths) + return canCapture; + + for (let type in attackTypes) + { + if (type == "Capture" ? !canCapture : target.isInvulnerable()) + continue; + + let restrictedClasses = this.get("Attack/" + type + "/RestrictedClasses/_string"); + if (!restrictedClasses || !MatchesClassList(target.classes(), restrictedClasses)) + return true; + }; + + return false; + }, + "move": function(x, z, queued = false) { Engine.PostCommand(PlayerID, { "type": "walk", "entities": [this.id()], "x": x, "z": z, "queued": queued }); return this; Index: binaries/data/mods/public/simulation/ai/common-api/filters.js =================================================================== --- binaries/data/mods/public/simulation/ai/common-api/filters.js +++ binaries/data/mods/public/simulation/ai/common-api/filters.js @@ -124,6 +124,14 @@ "dynamicProperties": [] }; }, + "byCanAttackTarget": function(target) + { + return { "func": function(ent){ + return ent.canAttackTarget(target); + }, + "dynamicProperties": [] }; + }, + "isGarrisoned": function(){ return { "func": function(ent){ return ent.position() === undefined; Index: binaries/data/mods/public/simulation/ai/petra/attackManager.js =================================================================== --- binaries/data/mods/public/simulation/ai/petra/attackManager.js +++ binaries/data/mods/public/simulation/ai/petra/attackManager.js @@ -180,6 +180,9 @@ let access = PETRA.getLandAccess(gameState, ent); for (let struct of gameState.getEnemyStructures().values()) { + if (!ent.canAttackTarget(struct.id())) + continue; + let structPos = struct.position(); let x; let z; Index: binaries/data/mods/public/simulation/ai/petra/attackPlan.js =================================================================== --- binaries/data/mods/public/simulation/ai/petra/attackPlan.js +++ binaries/data/mods/public/simulation/ai/petra/attackPlan.js @@ -1327,11 +1327,13 @@ { if (PETRA.isSiegeUnit(ent)) // needed as mauryan elephants are not filtered out continue; + if (!ent.canAttackTarget(attacker)) + continue; ent.attack(attacker.id(), PETRA.allowCapture(gameState, ent, attacker)); ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time); } // And if this attacker is a non-ranged siege unit and our unit also, attack it - if (PETRA.isSiegeUnit(attacker) && attacker.hasClass("Melee") && ourUnit.hasClass("Melee")) + if (PETRA.isSiegeUnit(attacker) && attacker.hasClass("Melee") && ourUnit.hasClass("Melee") && ourUnit.canAttackTarget(attacker)) { ourUnit.attack(attacker.id(), PETRA.allowCapture(gameState, ourUnit, attacker)); ourUnit.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time); @@ -1350,6 +1352,8 @@ let collec = this.unitCollection.filter(API3.Filters.byClass("Melee")).filterNearest(ourUnit.position(), 5); for (let ent of collec.values()) { + if (!ent.canAttackTarget(attacker)) + continue; ent.attack(attacker.id(), PETRA.allowCapture(gameState, ent, attacker)); ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time); } @@ -1360,7 +1364,7 @@ let collec = this.unitCollection.filterNearest(ourUnit.position(), 2); for (let ent of collec.values()) { - if (PETRA.isSiegeUnit(ent)) + if (PETRA.isSiegeUnit(ent) || !ent.canAttackTarget(attacker)) continue; let orderData = ent.unitAIOrderData(); if (orderData && orderData.length && orderData[0].target) @@ -1388,8 +1392,11 @@ continue; } } - ourUnit.attack(attacker.id(), PETRA.allowCapture(gameState, ourUnit, attacker)); - ourUnit.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time); + if (ourUnit.canAttackTarget(attacker)) + { + ourUnit.attack(attacker.id(), PETRA.allowCapture(gameState, ourUnit, attacker)); + ourUnit.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time); + } } } } @@ -1540,7 +1547,7 @@ if (siegeUnit) { let mStruct = enemyStructures.filter(enemy => { - if (!enemy.position() || enemy.hasClass("StoneWall") && !ent.canAttackClass("StoneWall")) + if (!enemy.position() || !ent.canAttackTarget(enemy)) return false; if (API3.SquareVectorDistance(enemy.position(), ent.position()) > range) return false; @@ -1592,7 +1599,7 @@ { let nearby = !ent.hasClass("Cavalry") && !ent.hasClass("Ranged"); let mUnit = enemyUnits.filter(enemy => { - if (!enemy.position()) + if (!enemy.position() || !ent.canAttackTarget(enemy)) return false; if (enemy.hasClass("Animal")) return false; @@ -1634,7 +1641,7 @@ let rand = randIntExclusive(0, mUnit.length * 0.1); ent.attack(mUnit[rand].id(), PETRA.allowCapture(gameState, ent, mUnit[rand])); } - else if (this.isBlocked) + else if (this.isBlocked && ent.canAttackTarget(this.target)) ent.attack(this.target.id(), false); else if (API3.SquareVectorDistance(this.targetPos, ent.position()) > 2500) { @@ -1655,7 +1662,7 @@ let mStruct = enemyStructures.filter(enemy => { if (this.isBlocked && enemy.id() != this.target.id()) return false; - if (!enemy.position() || enemy.hasClass("StoneWall") && !ent.canAttackClass("StoneWall")) + if (!enemy.position() || !ent.canAttackTarget(enemy)) return false; if (API3.SquareVectorDistance(enemy.position(), ent.position()) > range) return false; @@ -1696,13 +1703,15 @@ if (unit.unitAIState().split(".")[1] != "COMBAT" || !unit.unitAIOrderData().length || !unit.unitAIOrderData()[0].target) return; - if (!gameState.getEntityById(unit.unitAIOrderData()[0].target)) + attacker = gameState.getEntityById(unit.unitAIOrderData()[0].target); + if (!attacker) return; let dist = API3.SquareVectorDistance(unit.position(), ent.position()); if (dist > distmin) return; distmin = dist; - attacker = gameState.getEntityById(unit.unitAIOrderData()[0].target); + if (!ent.canAttackTarget(attacker)) + return; }); if (attacker) ent.attack(attacker.id(), PETRA.allowCapture(gameState, ent, attacker)); @@ -1756,7 +1765,7 @@ { if (ent.getMetadata(PlayerID, "transport") !== undefined) continue; - if (!ent.isIdle()) + if (!ent.isIdle() || !ent.canAttackTarget(attacker)) continue; ent.attack(attacker.id(), PETRA.allowCapture(gameState, ent, attacker)); } Index: binaries/data/mods/public/simulation/ai/petra/defenseArmy.js =================================================================== --- binaries/data/mods/public/simulation/ai/petra/defenseArmy.js +++ binaries/data/mods/public/simulation/ai/petra/defenseArmy.js @@ -312,6 +312,9 @@ if (!eEnt || !eEnt.position()) // probably can't happen. continue; + if (!ent.canAttackTarget(eEnt)) + continue; + if (eEnt.hasClass("Unit") && eEnt.unitAIOrderData() && eEnt.unitAIOrderData().length && eEnt.unitAIOrderData()[0].target && eEnt.unitAIOrderData()[0].target == entID) { // being attacked >>> target the unit Index: binaries/data/mods/public/simulation/ai/petra/defenseManager.js =================================================================== --- binaries/data/mods/public/simulation/ai/petra/defenseManager.js +++ binaries/data/mods/public/simulation/ai/petra/defenseManager.js @@ -464,6 +464,12 @@ { if (access && armiesNeeding[a].access != access) continue; + + // Do not assign defender if it cannot attack at least part of the attacking army. + if (!armiesNeeding[a].army.foeEntities.some(eEnt => + ent.canAttackTarget(gameState.getEntityById(eEnt)))) + continue; + let dist = API3.SquareVectorDistance(ent.position(), armiesNeeding[a].army.foePosition); if (aMin !== undefined && dist > distMin) continue; @@ -702,7 +708,7 @@ if (allAttacked[entId]) continue; let ent = gameState.getEntityById(entId); - if (!ent || !ent.position()) + if (!ent || !ent.position() || !ent.canAttackTarget(attacker)) continue; // Check that the unit is still attacking the structure (since the last played turn) let state = ent.unitAIState(); @@ -725,7 +731,8 @@ } } } - target.attack(attacker.id(), PETRA.allowCapture(gameState, target, attacker)); + if (target.canAttackTarget(attacker)) + target.attack(attacker.id(), PETRA.allowCapture(gameState, target, attacker)); } } };