Changeset View
Changeset View
Standalone View
Standalone View
binaries/data/mods/public/simulation/ai/petra/attackPlan.js
Show First 20 Lines • Show All 1,317 Lines • ▼ Show 20 Lines | for (let evt of events.Attacked) | ||||
if (PETRA.isSiegeUnit(ourUnit)) | if (PETRA.isSiegeUnit(ourUnit)) | ||||
{ // if our siege units are attacked, we'll send some units to deal with enemies. | { // if our siege units are attacked, we'll send some units to deal with enemies. | ||||
let collec = this.unitCollection.filter(API3.Filters.not(API3.Filters.byClass("Siege"))).filterNearest(ourUnit.position(), 5); | let collec = this.unitCollection.filter(API3.Filters.not(API3.Filters.byClass("Siege"))).filterNearest(ourUnit.position(), 5); | ||||
for (let ent of collec.values()) | for (let ent of collec.values()) | ||||
{ | { | ||||
if (PETRA.isSiegeUnit(ent)) // needed as mauryan elephants are not filtered out | if (PETRA.isSiegeUnit(ent)) // needed as mauryan elephants are not filtered out | ||||
continue; | continue; | ||||
let allowCapture = PETRA.allowCapture(gameState, ent, attacker); | let ignoreAttackEffects = PETRA.ignoreAttackEffects(gameState, ent, attacker); | ||||
if (!ent.canAttackTarget(attacker, allowCapture)) | if (!ent.canAttackTarget(attacker, false, ignoreAttackEffects)) | ||||
continue; | continue; | ||||
ent.attack(attacker.id(), allowCapture); | ent.attack(attacker.id(), ignoreAttackEffects); | ||||
ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time); | ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time); | ||||
} | } | ||||
// And if this attacker is a non-ranged siege unit and our unit also, attack it | // 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") && ourUnit.canAttackTarget(attacker, PETRA.allowCapture(gameState, ourUnit, attacker))) | let ignoreAttackEffects = PETRA.ignoreAttackEffects(gameState, ourUnit, attacker); | ||||
if (PETRA.isSiegeUnit(attacker) && attacker.hasClass("Melee") && ourUnit.hasClass("Melee") && ourUnit.canAttackTarget(attacker, false, ignoreAttackEffects)) | |||||
{ | { | ||||
ourUnit.attack(attacker.id(), PETRA.allowCapture(gameState, ourUnit, attacker)); | ourUnit.attack(attacker.id(), ignoreAttackEffects); | ||||
ourUnit.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time); | ourUnit.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time); | ||||
} | } | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
if (this.isBlocked && !ourUnit.hasClass("Ranged") && attacker.hasClass("Ranged")) | if (this.isBlocked && !ourUnit.hasClass("Ranged") && attacker.hasClass("Ranged")) | ||||
{ | { | ||||
// do not react if our melee units are attacked by ranged one and we are blocked by walls | // do not react if our melee units are attacked by ranged one and we are blocked by walls | ||||
// TODO check that the attacker is from behind the wall | // TODO check that the attacker is from behind the wall | ||||
continue; | continue; | ||||
} | } | ||||
else if (PETRA.isSiegeUnit(attacker)) | else if (PETRA.isSiegeUnit(attacker)) | ||||
{ // if our unit is attacked by a siege unit, we'll send some melee units to help it. | { // if our unit is attacked by a siege unit, we'll send some melee units to help it. | ||||
let collec = this.unitCollection.filter(API3.Filters.byClass("Melee")).filterNearest(ourUnit.position(), 5); | let collec = this.unitCollection.filter(API3.Filters.byClass("Melee")).filterNearest(ourUnit.position(), 5); | ||||
for (let ent of collec.values()) | for (let ent of collec.values()) | ||||
{ | { | ||||
let allowCapture = PETRA.allowCapture(gameState, ent, attacker); | let ignoreAttackEffects= PETRA.ignoreAttackEffects(gameState, ent, attacker); | ||||
if (!ent.canAttackTarget(attacker, allowCapture)) | if (!ent.canAttackTarget(attacker, false, ignoreAttackEffects)) | ||||
continue; | continue; | ||||
ent.attack(attacker.id(), allowCapture); | ent.attack(attacker.id(), ignoreAttackEffects); | ||||
ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time); | ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time); | ||||
} | } | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
// Look first for nearby units to help us if possible | // Look first for nearby units to help us if possible | ||||
let collec = this.unitCollection.filterNearest(ourUnit.position(), 2); | let collec = this.unitCollection.filterNearest(ourUnit.position(), 2); | ||||
for (let ent of collec.values()) | for (let ent of collec.values()) | ||||
{ | { | ||||
let allowCapture = PETRA.allowCapture(gameState, ent, attacker); | let ignoreAttackEffects = PETRA.ignoreAttackEffects(gameState, ent, attacker); | ||||
if (PETRA.isSiegeUnit(ent) || !ent.canAttackTarget(attacker, allowCapture)) | if (PETRA.isSiegeUnit(ent) || !ent.canAttackTarget(attacker, false, ignoreAttackEffects)) | ||||
continue; | continue; | ||||
let orderData = ent.unitAIOrderData(); | let orderData = ent.unitAIOrderData(); | ||||
if (orderData && orderData.length && orderData[0].target) | if (orderData && orderData.length && orderData[0].target) | ||||
{ | { | ||||
if (orderData[0].target === attacker.id()) | if (orderData[0].target === attacker.id()) | ||||
continue; | continue; | ||||
let target = gameState.getEntityById(orderData[0].target); | let target = gameState.getEntityById(orderData[0].target); | ||||
if (target && !target.hasClass("Structure") && !target.hasClass("Support")) | if (target && !target.hasClass("Structure") && !target.hasClass("Support")) | ||||
continue; | continue; | ||||
} | } | ||||
ent.attack(attacker.id(), allowCapture); | ent.attack(attacker.id(), ignoreAttackEffects); | ||||
ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time); | ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time); | ||||
} | } | ||||
// Then the unit under attack: abandon its target (if it was a structure or a support) and retaliate | // Then the unit under attack: abandon its target (if it was a structure or a support) and retaliate | ||||
// also if our unit is attacking a range unit and the attacker is a melee unit, retaliate | // also if our unit is attacking a range unit and the attacker is a melee unit, retaliate | ||||
let orderData = ourUnit.unitAIOrderData(); | let orderData = ourUnit.unitAIOrderData(); | ||||
if (orderData && orderData.length && orderData[0].target) | if (orderData && orderData.length && orderData[0].target) | ||||
{ | { | ||||
if (orderData[0].target === attacker.id()) | if (orderData[0].target === attacker.id()) | ||||
continue; | continue; | ||||
let target = gameState.getEntityById(orderData[0].target); | let target = gameState.getEntityById(orderData[0].target); | ||||
if (target && !target.hasClass("Structure") && !target.hasClass("Support")) | if (target && !target.hasClass("Structure") && !target.hasClass("Support")) | ||||
{ | { | ||||
if (!target.hasClass("Ranged") || !attacker.hasClass("Melee")) | if (!target.hasClass("Ranged") || !attacker.hasClass("Melee")) | ||||
continue; | continue; | ||||
} | } | ||||
} | } | ||||
let allowCapture = PETRA.allowCapture(gameState, ourUnit, attacker); | let ignoreAttackEffects = PETRA.ignoreAttackEffects(gameState, ourUnit, attacker); | ||||
if (ourUnit.canAttackTarget(attacker, allowCapture)) | if (ourUnit.canAttackTarget(attacker, false, ignoreAttackEffects)) | ||||
{ | { | ||||
ourUnit.attack(attacker.id(), allowCapture); | ourUnit.attack(attacker.id(), ignoreAttackEffects); | ||||
ourUnit.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time); | ourUnit.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
let enemyUnits = gameState.getEnemyUnits(this.targetPlayer); | let enemyUnits = gameState.getEnemyUnits(this.targetPlayer); | ||||
let enemyStructures = gameState.getEnemyStructures(this.targetPlayer); | let enemyStructures = gameState.getEnemyStructures(this.targetPlayer); | ||||
▲ Show 20 Lines • Show All 136 Lines • ▼ Show 20 Lines | for (let check = 0; check < lgth; check++) | ||||
else if (ent.hasClass("FastMoving")) | else if (ent.hasClass("FastMoving")) | ||||
range += 30; | range += 30; | ||||
range *= range; | range *= range; | ||||
let entAccess = PETRA.getLandAccess(gameState, ent); | let entAccess = PETRA.getLandAccess(gameState, ent); | ||||
// Checking for gates if we're a siege unit. | // Checking for gates if we're a siege unit. | ||||
if (siegeUnit) | if (siegeUnit) | ||||
{ | { | ||||
let mStruct = enemyStructures.filter(enemy => { | let mStruct = enemyStructures.filter(enemy => { | ||||
if (!enemy.position() || !ent.canAttackTarget(enemy, PETRA.allowCapture(gameState, ent, enemy))) | if (!enemy.position() || !ent.canAttackTarget(enemy, false, PETRA.ignoreAttackEffects(gameState, ent, enemy))) | ||||
return false; | return false; | ||||
if (API3.SquareVectorDistance(enemy.position(), ent.position()) > range) | if (API3.SquareVectorDistance(enemy.position(), ent.position()) > range) | ||||
return false; | return false; | ||||
if (enemy.foundationProgress() == 0) | if (enemy.foundationProgress() == 0) | ||||
return false; | return false; | ||||
if (PETRA.getLandAccess(gameState, enemy) != entAccess) | if (PETRA.getLandAccess(gameState, enemy) != entAccess) | ||||
return false; | return false; | ||||
return true; | return true; | ||||
Show All 13 Lines | for (let check = 0; check < lgth; check++) | ||||
valb += 10000; | valb += 10000; | ||||
else if (structb.hasDefensiveFire()) | else if (structb.hasDefensiveFire()) | ||||
valb += 1000; | valb += 1000; | ||||
else if (structb.hasClass("ConquestCritical")) | else if (structb.hasClass("ConquestCritical")) | ||||
valb += 200; | valb += 200; | ||||
return valb - vala; | return valb - vala; | ||||
}); | }); | ||||
if (mStruct[0].hasClass("Gate")) | if (mStruct[0].hasClass("Gate")) | ||||
ent.attack(mStruct[0].id(), PETRA.allowCapture(gameState, ent, mStruct[0])); | ent.attack(mStruct[0].id(), PETRA.ignoreAttackEffects(gameState, ent, mStruct[0])); | ||||
else | else | ||||
{ | { | ||||
let rand = randIntExclusive(0, mStruct.length * 0.2); | let rand = randIntExclusive(0, mStruct.length * 0.2); | ||||
ent.attack(mStruct[rand].id(), PETRA.allowCapture(gameState, ent, mStruct[rand])); | ent.attack(mStruct[rand].id(), PETRA.ignoreAttackEffects(gameState, ent, mStruct[rand])); | ||||
} | } | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
if (!ent.hasClass("Ranged")) | if (!ent.hasClass("Ranged")) | ||||
{ | { | ||||
let targetClasses = { "attack": targetClassesSiege.attack, "avoid": targetClassesSiege.avoid.concat("Ship"), "vetoEntities": veto }; | let targetClasses = { "attack": targetClassesSiege.attack, "avoid": targetClassesSiege.avoid.concat("Ship"), "vetoEntities": veto }; | ||||
ent.attackMove(this.targetPos[0], this.targetPos[1], targetClasses); | ent.attackMove(this.targetPos[0], this.targetPos[1], targetClasses); | ||||
} | } | ||||
else | else | ||||
ent.attackMove(this.targetPos[0], this.targetPos[1], targetClassesSiege); | ent.attackMove(this.targetPos[0], this.targetPos[1], targetClassesSiege); | ||||
} | } | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
let nearby = !ent.hasClass("FastMoving") && !ent.hasClass("Ranged"); | let nearby = !ent.hasClass("FastMoving") && !ent.hasClass("Ranged"); | ||||
let mUnit = enemyUnits.filter(enemy => { | let mUnit = enemyUnits.filter(enemy => { | ||||
if (!enemy.position() || !ent.canAttackTarget(enemy, PETRA.allowCapture(gameState, ent, enemy))) | if (!enemy.position() || !ent.canAttackTarget(enemy, false, PETRA.ignoreAttackEffects(gameState, ent, enemy))) | ||||
return false; | return false; | ||||
if (enemy.hasClass("Animal")) | if (enemy.hasClass("Animal")) | ||||
return false; | return false; | ||||
if (nearby && enemy.hasClass("FemaleCitizen") && enemy.unitAIState().split(".")[1] == "FLEEING") | if (nearby && enemy.hasClass("FemaleCitizen") && enemy.unitAIState().split(".")[1] == "FLEEING") | ||||
return false; | return false; | ||||
let dist = API3.SquareVectorDistance(enemy.position(), ent.position()); | let dist = API3.SquareVectorDistance(enemy.position(), ent.position()); | ||||
if (dist > range) | if (dist > range) | ||||
return false; | return false; | ||||
Show All 23 Lines | for (let check = 0; check < lgth; check++) | ||||
} | } | ||||
if (veto[unitA.id()]) | if (veto[unitA.id()]) | ||||
vala -= 20000; | vala -= 20000; | ||||
if (veto[unitB.id()]) | if (veto[unitB.id()]) | ||||
valb -= 20000; | valb -= 20000; | ||||
return valb - vala; | return valb - vala; | ||||
}); | }); | ||||
let rand = randIntExclusive(0, mUnit.length * 0.1); | let rand = randIntExclusive(0, mUnit.length * 0.1); | ||||
ent.attack(mUnit[rand].id(), PETRA.allowCapture(gameState, ent, mUnit[rand])); | ent.attack(mUnit[rand].id(), PETRA.ignoreAttackEffects(gameState, ent, mUnit[rand])); | ||||
} | } | ||||
// This may prove dangerous as we may be blocked by something we | // This may prove dangerous as we may be blocked by something we | ||||
// cannot attack. See similar behaviour at #5741. | // cannot attack. See similar behaviour at #5741. | ||||
else if (this.isBlocked && ent.canAttackTarget(this.target, false)) | else if (this.isBlocked && ent.canAttackTarget(this.target, false, { "Capture": true })) | ||||
ent.attack(this.target.id(), false); | ent.attack(this.target.id(), { "Capture": true }); | ||||
else if (API3.SquareVectorDistance(this.targetPos, ent.position()) > 2500) | else if (API3.SquareVectorDistance(this.targetPos, ent.position()) > 2500) | ||||
{ | { | ||||
let targetClasses = targetClassesUnit; | let targetClasses = targetClassesUnit; | ||||
if (maybeUpdate && ent.unitAIState() === "INDIVIDUAL.COMBAT.APPROACHING") // we may be blocked by walls, attack everything | if (maybeUpdate && ent.unitAIState() === "INDIVIDUAL.COMBAT.APPROACHING") // we may be blocked by walls, attack everything | ||||
{ | { | ||||
if (!ent.hasClass("Ranged") && !ent.hasClass("Ship")) | if (!ent.hasClass("Ranged") && !ent.hasClass("Ship")) | ||||
targetClasses = { "attack": ["Unit", "Structure"], "avoid": ["Ship"], "vetoEntities": veto }; | targetClasses = { "attack": ["Unit", "Structure"], "avoid": ["Ship"], "vetoEntities": veto }; | ||||
else | else | ||||
targetClasses = { "attack": ["Unit", "Structure"], "vetoEntities": veto }; | targetClasses = { "attack": ["Unit", "Structure"], "vetoEntities": veto }; | ||||
} | } | ||||
else if (!ent.hasClass("Ranged") && !ent.hasClass("Ship")) | else if (!ent.hasClass("Ranged") && !ent.hasClass("Ship")) | ||||
targetClasses = { "attack": targetClassesUnit.attack, "avoid": targetClassesUnit.avoid.concat("Ship"), "vetoEntities": veto }; | targetClasses = { "attack": targetClassesUnit.attack, "avoid": targetClassesUnit.avoid.concat("Ship"), "vetoEntities": veto }; | ||||
ent.attackMove(this.targetPos[0], this.targetPos[1], targetClasses); | ent.attackMove(this.targetPos[0], this.targetPos[1], targetClasses); | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
let mStruct = enemyStructures.filter(enemy => { | let mStruct = enemyStructures.filter(enemy => { | ||||
if (this.isBlocked && enemy.id() != this.target.id()) | if (this.isBlocked && enemy.id() != this.target.id()) | ||||
return false; | return false; | ||||
if (!enemy.position() || !ent.canAttackTarget(enemy, PETRA.allowCapture(gameState, ent, enemy))) | if (!enemy.position() || !ent.canAttackTarget(enemy, false, PETRA.ignoreAttackEffects(gameState, ent, enemy))) | ||||
return false; | return false; | ||||
if (API3.SquareVectorDistance(enemy.position(), ent.position()) > range) | if (API3.SquareVectorDistance(enemy.position(), ent.position()) > range) | ||||
return false; | return false; | ||||
if (PETRA.getLandAccess(gameState, enemy) != entAccess) | if (PETRA.getLandAccess(gameState, enemy) != entAccess) | ||||
return false; | return false; | ||||
return true; | return true; | ||||
}, this).toEntityArray(); | }, this).toEntityArray(); | ||||
if (mStruct.length) | if (mStruct.length) | ||||
{ | { | ||||
mStruct.sort((structa, structb) => { | mStruct.sort((structa, structb) => { | ||||
let vala = structa.costSum(); | let vala = structa.costSum(); | ||||
if (structa.hasClass("Gate") && ent.canAttackClass("Wall")) | if (structa.hasClass("Gate") && ent.canAttackClass("Wall")) | ||||
vala += 10000; | vala += 10000; | ||||
else if (structa.hasClass("ConquestCritical")) | else if (structa.hasClass("ConquestCritical")) | ||||
vala += 100; | vala += 100; | ||||
let valb = structb.costSum(); | let valb = structb.costSum(); | ||||
if (structb.hasClass("Gate") && ent.canAttackClass("Wall")) | if (structb.hasClass("Gate") && ent.canAttackClass("Wall")) | ||||
valb += 10000; | valb += 10000; | ||||
else if (structb.hasClass("ConquestCritical")) | else if (structb.hasClass("ConquestCritical")) | ||||
valb += 100; | valb += 100; | ||||
return valb - vala; | return valb - vala; | ||||
}); | }); | ||||
if (mStruct[0].hasClass("Gate")) | if (mStruct[0].hasClass("Gate")) | ||||
ent.attack(mStruct[0].id(), false); | ent.attack(mStruct[0].id(), { "Capture": true }); | ||||
else | else | ||||
{ | { | ||||
let rand = randIntExclusive(0, mStruct.length * 0.2); | let rand = randIntExclusive(0, mStruct.length * 0.2); | ||||
ent.attack(mStruct[rand].id(), PETRA.allowCapture(gameState, ent, mStruct[rand])); | ent.attack(mStruct[rand].id(), PETRA.ignoreAttackEffects(gameState, ent, mStruct[rand])); | ||||
} | } | ||||
} | } | ||||
else if (needsUpdate) // really nothing let's try to help our nearest unit | else if (needsUpdate) // really nothing let's try to help our nearest unit | ||||
{ | { | ||||
let distmin = Math.min(); | let distmin = Math.min(); | ||||
let attacker; | let attacker; | ||||
this.unitCollection.forEach(unit => { | this.unitCollection.forEach(unit => { | ||||
if (!unit.position()) | if (!unit.position()) | ||||
return; | return; | ||||
if (unit.unitAIState().split(".")[1] != "COMBAT" || !unit.unitAIOrderData().length || | if (unit.unitAIState().split(".")[1] != "COMBAT" || !unit.unitAIOrderData().length || | ||||
!unit.unitAIOrderData()[0].target) | !unit.unitAIOrderData()[0].target) | ||||
return; | return; | ||||
let target = gameState.getEntityById(unit.unitAIOrderData()[0].target); | let target = gameState.getEntityById(unit.unitAIOrderData()[0].target); | ||||
if (!target) | if (!target) | ||||
return; | return; | ||||
let dist = API3.SquareVectorDistance(unit.position(), ent.position()); | let dist = API3.SquareVectorDistance(unit.position(), ent.position()); | ||||
if (dist > distmin) | if (dist > distmin) | ||||
return; | return; | ||||
distmin = dist; | distmin = dist; | ||||
if (!ent.canAttackTarget(target, PETRA.allowCapture(gameState, ent, target))) | if (!ent.canAttackTarget(target, false, PETRA.ignoreAttackEffects(gameState, ent, target))) | ||||
return; | return; | ||||
attacker = target; | attacker = target; | ||||
}); | }); | ||||
if (attacker) | if (attacker) | ||||
ent.attack(attacker.id(), PETRA.allowCapture(gameState, ent, attacker)); | ent.attack(attacker.id(), PETRA.ignoreAttackEffects(gameState, ent, attacker)); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
this.unitCollUpdateArray.splice(0, lgth); | this.unitCollUpdateArray.splice(0, lgth); | ||||
this.startingAttack = false; | this.startingAttack = false; | ||||
// check if this enemy has resigned | // check if this enemy has resigned | ||||
Show All 35 Lines | for (let evt of events.Attacked) | ||||
let attacker = gameState.getEntityById(evt.attacker); | let attacker = gameState.getEntityById(evt.attacker); | ||||
if (!attacker || !gameState.getEntityById(evt.target)) | if (!attacker || !gameState.getEntityById(evt.target)) | ||||
continue; | continue; | ||||
for (let ent of this.unitCollection.values()) | for (let ent of this.unitCollection.values()) | ||||
{ | { | ||||
if (ent.getMetadata(PlayerID, "transport") !== undefined) | if (ent.getMetadata(PlayerID, "transport") !== undefined) | ||||
continue; | continue; | ||||
let allowCapture = PETRA.allowCapture(gameState, ent, attacker); | let ignoreAttackEffects = PETRA.ignoreAttackEffects(gameState, ent, attacker); | ||||
if (!ent.isIdle() || !ent.canAttackTarget(attacker, allowCapture)) | if (!ent.isIdle() || !ent.canAttackTarget(attacker, false, ignoreAttackEffects)) | ||||
continue; | continue; | ||||
ent.attack(attacker.id(), allowCapture); | ent.attack(attacker.id(), ignoreAttackEffects); | ||||
} | } | ||||
break; | break; | ||||
} | } | ||||
}; | }; | ||||
PETRA.AttackPlan.prototype.UpdateWalking = function(gameState, events) | PETRA.AttackPlan.prototype.UpdateWalking = function(gameState, events) | ||||
{ | { | ||||
// we're marching towards the target | // we're marching towards the target | ||||
▲ Show 20 Lines • Show All 390 Lines • Show Last 20 Lines |
Wildfire Games · Phabricator