1
0
forked from 0ad/0ad

[PetraAI] Do not try to attack entities which can't be attacked.

Avoid trying to attack unattackable entities based on restricted classes
from Attack component.

Differential Revision: https://code.wildfiregames.com/D2111
Patch by: Freagarach
Reviewed by: Angen
This was SVN commit r23759.
This commit is contained in:
Angen 2020-06-10 18:23:41 +00:00
parent 3336d6d543
commit 267ccd3127
6 changed files with 85 additions and 17 deletions

View File

@ -744,6 +744,34 @@ m.Entity = m.Class({
return false;
},
/**
* Derived from Attack.js' similary named function.
* @return {boolean} - Whether an entity can attack a given target.
*/
"canAttackTarget": function(target, allowCapture)
{
let attackTypes = this.get("Attack");
if (!attackTypes)
return false;
let canCapture = allowCapture && 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;

View File

@ -82,6 +82,11 @@ m.Filters = {
"dynamicProperties": []
}),
"byCanAttackTarget": target => ({
"func": ent => ent.canAttackTarget(target),
"dynamicProperties": []
}),
"isGarrisoned": () => ({
"func": ent => ent.position() === undefined,
"dynamicProperties": []

View File

@ -180,6 +180,9 @@ PETRA.AttackManager.prototype.assignBombers = function(gameState)
let access = PETRA.getLandAccess(gameState, ent);
for (let struct of gameState.getEnemyStructures().values())
{
if (!ent.canAttackTarget(struct, PETRA.allowCapture(gameState, ent, struct)))
continue;
let structPos = struct.position();
let x;
let z;

View File

@ -1327,11 +1327,15 @@ PETRA.AttackPlan.prototype.update = function(gameState, events)
{
if (PETRA.isSiegeUnit(ent)) // needed as mauryan elephants are not filtered out
continue;
ent.attack(attacker.id(), PETRA.allowCapture(gameState, ent, attacker));
let allowCapture = PETRA.allowCapture(gameState, ent, attacker);
if (!ent.canAttackTarget(attacker, allowCapture))
continue;
ent.attack(attacker.id(), allowCapture);
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, PETRA.allowCapture(gameState, ourUnit, attacker)))
{
ourUnit.attack(attacker.id(), PETRA.allowCapture(gameState, ourUnit, attacker));
ourUnit.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time);
@ -1350,7 +1354,10 @@ PETRA.AttackPlan.prototype.update = function(gameState, events)
let collec = this.unitCollection.filter(API3.Filters.byClass("Melee")).filterNearest(ourUnit.position(), 5);
for (let ent of collec.values())
{
ent.attack(attacker.id(), PETRA.allowCapture(gameState, ent, attacker));
let allowCapture = PETRA.allowCapture(gameState, ent, attacker);
if (!ent.canAttackTarget(attacker, allowCapture))
continue;
ent.attack(attacker.id(), allowCapture);
ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time);
}
}
@ -1360,7 +1367,8 @@ PETRA.AttackPlan.prototype.update = function(gameState, events)
let collec = this.unitCollection.filterNearest(ourUnit.position(), 2);
for (let ent of collec.values())
{
if (PETRA.isSiegeUnit(ent))
let allowCapture = PETRA.allowCapture(gameState, ent, attacker);
if (PETRA.isSiegeUnit(ent) || !ent.canAttackTarget(attacker, allowCapture))
continue;
let orderData = ent.unitAIOrderData();
if (orderData && orderData.length && orderData[0].target)
@ -1371,7 +1379,7 @@ PETRA.AttackPlan.prototype.update = function(gameState, events)
if (target && !target.hasClass("Structure") && !target.hasClass("Support"))
continue;
}
ent.attack(attacker.id(), PETRA.allowCapture(gameState, ent, attacker));
ent.attack(attacker.id(), allowCapture);
ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time);
}
// Then the unit under attack: abandon its target (if it was a structure or a support) and retaliate
@ -1388,8 +1396,12 @@ PETRA.AttackPlan.prototype.update = function(gameState, events)
continue;
}
}
ourUnit.attack(attacker.id(), PETRA.allowCapture(gameState, ourUnit, attacker));
ourUnit.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time);
let allowCapture = PETRA.allowCapture(gameState, ourUnit, attacker);
if (ourUnit.canAttackTarget(attacker, allowCapture))
{
ourUnit.attack(attacker.id(), allowCapture);
ourUnit.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time);
}
}
}
}
@ -1540,7 +1552,7 @@ PETRA.AttackPlan.prototype.update = function(gameState, events)
if (siegeUnit)
{
let mStruct = enemyStructures.filter(enemy => {
if (!enemy.position() || enemy.hasClass("StoneWall") && !ent.canAttackClass("StoneWall"))
if (!enemy.position() || !ent.canAttackTarget(enemy, PETRA.allowCapture(gameState, ent, enemy)))
return false;
if (API3.SquareVectorDistance(enemy.position(), ent.position()) > range)
return false;
@ -1592,7 +1604,7 @@ PETRA.AttackPlan.prototype.update = function(gameState, events)
{
let nearby = !ent.hasClass("Cavalry") && !ent.hasClass("Ranged");
let mUnit = enemyUnits.filter(enemy => {
if (!enemy.position())
if (!enemy.position() || !ent.canAttackTarget(enemy, PETRA.allowCapture(gameState, ent, enemy)))
return false;
if (enemy.hasClass("Animal"))
return false;
@ -1634,7 +1646,9 @@ PETRA.AttackPlan.prototype.update = function(gameState, events)
let rand = randIntExclusive(0, mUnit.length * 0.1);
ent.attack(mUnit[rand].id(), PETRA.allowCapture(gameState, ent, mUnit[rand]));
}
else if (this.isBlocked)
// This may prove dangerous as we may be blocked by something we
// cannot attack. See similar behaviour at #5741.
else if (this.isBlocked && ent.canAttackTarget(this.target, false))
ent.attack(this.target.id(), false);
else if (API3.SquareVectorDistance(this.targetPos, ent.position()) > 2500)
{
@ -1655,7 +1669,7 @@ PETRA.AttackPlan.prototype.update = function(gameState, events)
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, PETRA.allowCapture(gameState, ent, enemy)))
return false;
if (API3.SquareVectorDistance(enemy.position(), ent.position()) > range)
return false;
@ -1696,13 +1710,16 @@ PETRA.AttackPlan.prototype.update = function(gameState, events)
if (unit.unitAIState().split(".")[1] != "COMBAT" || !unit.unitAIOrderData().length ||
!unit.unitAIOrderData()[0].target)
return;
if (!gameState.getEntityById(unit.unitAIOrderData()[0].target))
let target = gameState.getEntityById(unit.unitAIOrderData()[0].target);
if (!target)
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(target, PETRA.allowCapture(gameState, ent, target)))
return;
attacker = target;
});
if (attacker)
ent.attack(attacker.id(), PETRA.allowCapture(gameState, ent, attacker));
@ -1756,9 +1773,11 @@ PETRA.AttackPlan.prototype.UpdateTransporting = function(gameState, events)
{
if (ent.getMetadata(PlayerID, "transport") !== undefined)
continue;
if (!ent.isIdle())
let allowCapture = PETRA.allowCapture(gameState, ent, attacker);
if (!ent.isIdle() || !ent.canAttackTarget(attacker, allowCapture))
continue;
ent.attack(attacker.id(), PETRA.allowCapture(gameState, ent, attacker));
ent.attack(attacker.id(), allowCapture);
}
break;
}

View File

@ -312,6 +312,9 @@ PETRA.DefenseArmy.prototype.assignUnit = function(gameState, entID)
if (!eEnt || !eEnt.position()) // probably can't happen.
continue;
if (!ent.canAttackTarget(eEnt, PETRA.allowCapture(gameState, ent, 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

View File

@ -464,6 +464,14 @@ PETRA.DefenseManager.prototype.assignDefenders = function(gameState)
{
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 => {
let eEntID = gameState.getEntityById(eEnt);
return ent.canAttackTarget(eEntID, PETRA.allowCapture(gameState, ent, eEntID));
}))
continue;
let dist = API3.SquareVectorDistance(ent.position(), armiesNeeding[a].army.foePosition);
if (aMin !== undefined && dist > distMin)
continue;
@ -705,7 +713,7 @@ PETRA.DefenseManager.prototype.checkEvents = function(gameState, events)
if (allAttacked[entId])
continue;
let ent = gameState.getEntityById(entId);
if (!ent || !ent.position())
if (!ent || !ent.position() || !ent.canAttackTarget(attacker, PETRA.allowCapture(gameState, ent, attacker)))
continue;
// Check that the unit is still attacking the structure (since the last played turn).
let state = ent.unitAIState();
@ -728,7 +736,9 @@ PETRA.DefenseManager.prototype.checkEvents = function(gameState, events)
}
}
}
target.attack(attacker.id(), PETRA.allowCapture(gameState, target, attacker));
let allowCapture = PETRA.allowCapture(gameState, target, attacker);
if (target.canAttackTarget(attacker, allowCapture))
target.attack(attacker.id(), allowCapture);
}
}
};