Changeset View
Changeset View
Standalone View
Standalone View
binaries/data/mods/public/simulation/ai/petra/defenseManager.js
var PETRA = function(m) | var PETRA = function(m) | ||||
{ | { | ||||
m.DefenseManager = function(Config) | m.DefenseManager = function(Config) | ||||
{ | { | ||||
this.armies = []; // array of "army" Objects | this.armies = []; // Array of "army" Objects | ||||
this.Config = Config; | this.Config = Config; | ||||
this.targetList = []; | this.targetList = []; | ||||
this.armyMergeSize = this.Config.Defense.armyMergeSize; | this.armyMergeSize = this.Config.Defense.armyMergeSize; | ||||
// stats on how many enemies are currently attacking our allies | // Stats on how many enemies are currently attacking our allies | ||||
// this.attackingArmies[enemy][ally] = number of enemy armies inside allied territory | // this.attackingArmies[enemy][ally] = number of enemy armies inside allied territory | ||||
// this.attackingUnits[enemy][ally] = number of enemy units not in armies inside allied territory | // this.attackingUnits[enemy][ally] = number of enemy units not in armies inside allied territory | ||||
// this.attackedAllies[ally] = number of enemies attacking the ally | // this.attackedAllies[ally] = number of enemies attacking the ally | ||||
this.attackingArmies = {}; | this.attackingArmies = {}; | ||||
this.attackingUnits = {}; | this.attackingUnits = {}; | ||||
this.attackedAllies = {}; | this.attackedAllies = {}; | ||||
}; | }; | ||||
m.DefenseManager.prototype.update = function(gameState, events) | m.DefenseManager.prototype.update = function(gameState, events) | ||||
{ | { | ||||
Engine.ProfileStart("Defense Manager"); | Engine.ProfileStart("Defense Manager"); | ||||
this.territoryMap = gameState.ai.HQ.territoryMap; | this.territoryMap = gameState.ai.HQ.territoryMap; | ||||
this.checkEvents(gameState, events); | this.checkEvents(gameState, events); | ||||
// Check if our potential targets are still valid | // Check if our potential targets are still valid. | ||||
for (let i = 0; i < this.targetList.length; ++i) | for (let i = 0; i < this.targetList.length; ++i) | ||||
{ | { | ||||
let target = gameState.getEntityById(this.targetList[i]); | let target = gameState.getEntityById(this.targetList[i]); | ||||
if (!target || !target.position() || !gameState.isPlayerEnemy(target.owner())) | if (!target || !target.position() || !gameState.isPlayerEnemy(target.owner())) | ||||
this.targetList.splice(i--, 1); | this.targetList.splice(i--, 1); | ||||
} | } | ||||
// Count the number of enemies attacking our allies in the previous turn | // Count the number of enemies attacking our allies in the previous turn. | ||||
// We'll be more cooperative if several enemies are attacking him simultaneously | // We'll be more cooperative if several enemies are attacking him simultaneously. | ||||
this.attackedAllies = {}; | this.attackedAllies = {}; | ||||
let attackingArmies = clone(this.attackingArmies); | let attackingArmies = clone(this.attackingArmies); | ||||
for (let enemy in this.attackingUnits) | for (let enemy in this.attackingUnits) | ||||
{ | { | ||||
if (!this.attackingUnits[enemy]) | if (!this.attackingUnits[enemy]) | ||||
continue; | continue; | ||||
for (let ally in this.attackingUnits[enemy]) | for (let ally in this.attackingUnits[enemy]) | ||||
{ | { | ||||
Show All 35 Lines | m.DefenseManager.prototype.makeIntoArmy = function(gameState, entityID, type = "default") | ||||
// Create a new army for it. | // Create a new army for it. | ||||
let army = new m.DefenseArmy(gameState, [entityID], type); | let army = new m.DefenseArmy(gameState, [entityID], type); | ||||
this.armies.push(army); | this.armies.push(army); | ||||
}; | }; | ||||
m.DefenseManager.prototype.getArmy = function(partOfArmy) | m.DefenseManager.prototype.getArmy = function(partOfArmy) | ||||
{ | { | ||||
// Find the army corresponding to this ID partOfArmy | // Find the army corresponding to this ID partOfArmy. | ||||
for (let army of this.armies) | for (let army of this.armies) | ||||
if (army.ID == partOfArmy) | if (army.ID == partOfArmy) | ||||
return army; | return army; | ||||
return undefined; | return undefined; | ||||
}; | }; | ||||
m.DefenseManager.prototype.isDangerous = function(gameState, entity) | m.DefenseManager.prototype.isDangerous = function(gameState, entity) | ||||
{ | { | ||||
if (!entity.position()) | if (!entity.position()) | ||||
return false; | return false; | ||||
let territoryOwner = this.territoryMap.getOwner(entity.position()); | let territoryOwner = this.territoryMap.getOwner(entity.position()); | ||||
if (territoryOwner != 0 && !gameState.isPlayerAlly(territoryOwner)) | if (territoryOwner != 0 && !gameState.isPlayerAlly(territoryOwner)) | ||||
return false; | return false; | ||||
// check if the entity is trying to build a new base near our buildings, | // Check if the entity is trying to build a new base near our buildings, | ||||
// and if yes, add this base in our target list | // and if yes, add this base in our target list. | ||||
if (entity.unitAIState() && entity.unitAIState() == "INDIVIDUAL.REPAIR.REPAIRING") | if (entity.unitAIState() && entity.unitAIState() == "INDIVIDUAL.REPAIR.REPAIRING") | ||||
{ | { | ||||
let targetId = entity.unitAIOrderData()[0].target; | let targetId = entity.unitAIOrderData()[0].target; | ||||
if (this.targetList.indexOf(targetId) != -1) | if (this.targetList.indexOf(targetId) != -1) | ||||
return true; | return true; | ||||
let target = gameState.getEntityById(targetId); | let target = gameState.getEntityById(targetId); | ||||
if (target) | if (target) | ||||
{ | { | ||||
Show All 18 Lines | if (target) | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
if (entity.attackTypes() === undefined || entity.hasClass("Support")) | if (entity.attackTypes() === undefined || entity.hasClass("Support")) | ||||
return false; | return false; | ||||
let dist2Min = 6000; | let dist2Min = 6000; | ||||
// TODO the 30 is to take roughly into account the structure size in following checks. Can be improved | // TODO the 30 is to take roughly into account the structure size in following checks. Can be improved. | ||||
if (entity.attackTypes().indexOf("Ranged") != -1) | if (entity.attackTypes().indexOf("Ranged") != -1) | ||||
dist2Min = (entity.attackRange("Ranged").max + 30) * (entity.attackRange("Ranged").max + 30); | dist2Min = (entity.attackRange("Ranged").max + 30) * (entity.attackRange("Ranged").max + 30); | ||||
for (let targetId of this.targetList) | for (let targetId of this.targetList) | ||||
{ | { | ||||
let target = gameState.getEntityById(targetId); | let target = gameState.getEntityById(targetId); | ||||
if (!target || !target.position()) // the enemy base is either destroyed or built | // The enemy base is either destroyed or built. | ||||
if (!target || !target.position()) | |||||
continue; | continue; | ||||
if (API3.SquareVectorDistance(target.position(), entity.position()) < dist2Min) | if (API3.SquareVectorDistance(target.position(), entity.position()) < dist2Min) | ||||
return true; | return true; | ||||
} | } | ||||
let ccEnts = gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre")); | let ccEnts = gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre")); | ||||
for (let cc of ccEnts.values()) | for (let cc of ccEnts.values()) | ||||
{ | { | ||||
if (!gameState.isEntityExclusiveAlly(cc) || cc.foundationProgress() == 0) | if (!gameState.isEntityExclusiveAlly(cc) || cc.foundationProgress() == 0) | ||||
continue; | continue; | ||||
let cooperation = this.GetCooperationLevel(cc.owner()); | let cooperation = this.GetCooperationLevel(cc.owner()); | ||||
if (cooperation < 0.6 && cc.foundationProgress() !== undefined) | if (cooperation < 0.3 || (cooperation < 0.6 && !!cc.foundationProgress())) | ||||
continue; | |||||
if (cooperation < 0.3) | |||||
continue; | continue; | ||||
if (API3.SquareVectorDistance(cc.position(), entity.position()) < dist2Min) | if (API3.SquareVectorDistance(cc.position(), entity.position()) < dist2Min) | ||||
Stan: !!cc.foundationProgress() ?
Maybe merge the two with a || operator ? | |||||
return true; | return true; | ||||
} | } | ||||
for (let building of gameState.getOwnStructures().values()) | for (let building of gameState.getOwnStructures().values()) | ||||
{ | { | ||||
if (building.foundationProgress() == 0 || | if (building.foundationProgress() == 0 || | ||||
API3.SquareVectorDistance(building.position(), entity.position()) > dist2Min) | API3.SquareVectorDistance(building.position(), entity.position()) > dist2Min) | ||||
continue; | continue; | ||||
if (!this.territoryMap.isBlinking(building.position()) || gameState.ai.HQ.isDefendable(building)) | if (!this.territoryMap.isBlinking(building.position()) || gameState.ai.HQ.isDefendable(building)) | ||||
return true; | return true; | ||||
} | } | ||||
if (gameState.isPlayerMutualAlly(territoryOwner)) | if (gameState.isPlayerMutualAlly(territoryOwner)) | ||||
{ | { | ||||
// If ally attacked by more than 2 enemies, help him not only for cc but also for structures | // If ally attacked by more than 2 enemies, help him not only for cc but also for structures. | ||||
if (territoryOwner != PlayerID && this.attackedAllies[territoryOwner] && | if (territoryOwner != PlayerID && this.attackedAllies[territoryOwner] && | ||||
this.attackedAllies[territoryOwner] > 1 && | this.attackedAllies[territoryOwner] > 1 && | ||||
this.GetCooperationLevel(territoryOwner) > 0.7) | this.GetCooperationLevel(territoryOwner) > 0.7) | ||||
{ | { | ||||
for (let building of gameState.getAllyStructures(territoryOwner).values()) | for (let building of gameState.getAllyStructures(territoryOwner).values()) | ||||
{ | { | ||||
if (building.foundationProgress() == 0 || | if (building.foundationProgress() == 0 || | ||||
API3.SquareVectorDistance(building.position(), entity.position()) > dist2Min) | API3.SquareVectorDistance(building.position(), entity.position()) > dist2Min) | ||||
continue; | continue; | ||||
if (!this.territoryMap.isBlinking(building.position())) | if (!this.territoryMap.isBlinking(building.position())) | ||||
return true; | return true; | ||||
} | } | ||||
} | } | ||||
// Update the number of enemies attacking this ally | // Update the number of enemies attacking this ally. | ||||
let enemy = entity.owner(); | let enemy = entity.owner(); | ||||
if (this.attackingUnits[enemy] === undefined) | if (this.attackingUnits[enemy] === undefined) | ||||
this.attackingUnits[enemy] = {}; | this.attackingUnits[enemy] = {}; | ||||
if (this.attackingUnits[enemy][territoryOwner] === undefined) | if (this.attackingUnits[enemy][territoryOwner] === undefined) | ||||
this.attackingUnits[enemy][territoryOwner] = 0; | this.attackingUnits[enemy][territoryOwner] = 0; | ||||
this.attackingUnits[enemy][territoryOwner] += 1; | this.attackingUnits[enemy][territoryOwner] += 1; | ||||
} | } | ||||
return false; | return false; | ||||
}; | }; | ||||
m.DefenseManager.prototype.checkEnemyUnits = function(gameState) | m.DefenseManager.prototype.checkEnemyUnits = function(gameState) | ||||
{ | { | ||||
const nbPlayers = gameState.sharedScript.playersData.length; | const nbPlayers = gameState.sharedScript.playersData.length; | ||||
let i = gameState.ai.playedTurn % nbPlayers; | let i = gameState.ai.playedTurn % nbPlayers; | ||||
this.attackingUnits[i] = undefined; | this.attackingUnits[i] = undefined; | ||||
if (i == PlayerID) | if (i == PlayerID) | ||||
{ | { | ||||
if (!this.armies.length) | if (!this.armies.length) | ||||
{ | { | ||||
// check if we can recover capture points from any of our notdecaying structures | // Check if we can recover capture points from any of our notdecaying structures. | ||||
for (let ent of gameState.getOwnStructures().values()) | for (let ent of gameState.getOwnStructures().values()) | ||||
{ | { | ||||
if (ent.decaying()) | if (ent.decaying()) | ||||
continue; | continue; | ||||
let capture = ent.capturePoints(); | let capture = ent.capturePoints(); | ||||
if (capture === undefined) | if (capture === undefined) | ||||
continue; | continue; | ||||
let lost = 0; | let lost = 0; | ||||
for (let j = 0; j < capture.length; ++j) | for (let j = 0; j < capture.length; ++j) | ||||
if (gameState.isPlayerEnemy(j)) | if (gameState.isPlayerEnemy(j)) | ||||
lost += capture[j]; | lost += capture[j]; | ||||
if (lost < Math.ceil(0.25 * capture[i])) | if (lost < Math.ceil(0.25 * capture[i])) | ||||
continue; | continue; | ||||
this.makeIntoArmy(gameState, ent.id(), "capturing"); | this.makeIntoArmy(gameState, ent.id(), "capturing"); | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
return; | return; | ||||
} | } | ||||
else if (!gameState.isPlayerEnemy(i)) | else if (!gameState.isPlayerEnemy(i)) | ||||
return; | return; | ||||
// loop through enemy units | |||||
for (let ent of gameState.getEnemyUnits(i).values()) | for (let ent of gameState.getEnemyUnits(i).values()) | ||||
{ | { | ||||
if (ent.getMetadata(PlayerID, "PartOfArmy") !== undefined) | if (ent.getMetadata(PlayerID, "PartOfArmy") !== undefined) | ||||
continue; | continue; | ||||
// keep animals attacking us or our allies | // Keep animals attacking us or our allies. | ||||
if (ent.hasClass("Animal")) | if (ent.hasClass("Animal")) | ||||
{ | { | ||||
if (!ent.unitAIState() || ent.unitAIState().split(".")[1] != "COMBAT") | if (!ent.unitAIState() || ent.unitAIState().split(".")[1] != "COMBAT") | ||||
continue; | continue; | ||||
let orders = ent.unitAIOrderData(); | let orders = ent.unitAIOrderData(); | ||||
if (!orders || !orders.length || !orders[0].target) | if (!orders || !orders.length || !orders[0].target) | ||||
continue; | continue; | ||||
let target = gameState.getEntityById(orders[0].target); | let target = gameState.getEntityById(orders[0].target); | ||||
if (!target || !gameState.isPlayerAlly(target.owner())) | if (!target || !gameState.isPlayerAlly(target.owner())) | ||||
continue; | continue; | ||||
} | } | ||||
// TODO what to do for ships ? | // TODO what to do for ships ? | ||||
if (ent.hasClass("Ship") || ent.hasClass("Trader")) | if (ent.hasClass("Ship") || ent.hasClass("Trader")) | ||||
continue; | continue; | ||||
// check if unit is dangerous "a priori" | // Check if unit is dangerous "a priori". | ||||
if (this.isDangerous(gameState, ent)) | if (this.isDangerous(gameState, ent)) | ||||
this.makeIntoArmy(gameState, ent.id()); | this.makeIntoArmy(gameState, ent.id()); | ||||
} | } | ||||
if (i != 0 || this.armies.length > 1 || gameState.ai.HQ.numActiveBases() == 0) | if (i != 0 || this.armies.length > 1 || gameState.ai.HQ.numActiveBases() == 0) | ||||
return; | return; | ||||
// look for possible gaia buildings inside our territory (may happen when enemy resign or after structure decay) | // Look for possible gaia buildings inside our territory (may happen when enemy resign or after structure decay) | ||||
// and attack it only if useful (and capturable) or dangereous | // and attack it only if useful (and capturable) or dangereous. | ||||
for (let ent of gameState.getEnemyStructures(i).values()) | for (let ent of gameState.getEnemyStructures(i).values()) | ||||
{ | { | ||||
if (!ent.position() || ent.getMetadata(PlayerID, "PartOfArmy") !== undefined) | if (!ent.position() || ent.getMetadata(PlayerID, "PartOfArmy") !== undefined) | ||||
continue; | continue; | ||||
if (!ent.capturePoints() && !ent.hasDefensiveFire()) | if (!ent.capturePoints() && !ent.hasDefensiveFire()) | ||||
continue; | continue; | ||||
let owner = this.territoryMap.getOwner(ent.position()); | let owner = this.territoryMap.getOwner(ent.position()); | ||||
if (owner == PlayerID) | if (owner == PlayerID) | ||||
this.makeIntoArmy(gameState, ent.id(), "capturing"); | this.makeIntoArmy(gameState, ent.id(), "capturing"); | ||||
} | } | ||||
}; | }; | ||||
m.DefenseManager.prototype.checkEnemyArmies = function(gameState) | m.DefenseManager.prototype.checkEnemyArmies = function(gameState) | ||||
{ | { | ||||
for (let i = 0; i < this.armies.length; ++i) | for (let i = 0; i < this.armies.length; ++i) | ||||
{ | { | ||||
let army = this.armies[i]; | let army = this.armies[i]; | ||||
// this returns a list of IDs: the units that broke away from the army for being too far. | // This returns a list of IDs: the units that broke away from the army for being too far. | ||||
let breakaways = army.update(gameState); | let breakaways = army.update(gameState); | ||||
for (let breaker of breakaways) | for (let breaker of breakaways) | ||||
this.makeIntoArmy(gameState, breaker); // assume dangerosity | this.makeIntoArmy(gameState, breaker); // Assume dangerosity. | ||||
Not Done Inline ActionsCaps for comments. Stan: Caps for comments. | |||||
Not Done Inline Actions+'.' wraitii: +'.' | |||||
if (army.getState() == 0) | if (army.getState() == 0) | ||||
{ | { | ||||
if (army.getType() == "default") | if (army.getType() == "default") | ||||
this.switchToAttack(gameState, army); | this.switchToAttack(gameState, army); | ||||
army.clear(gameState); | army.clear(gameState); | ||||
this.armies.splice(i--, 1); | this.armies.splice(i--, 1); | ||||
continue; | |||||
} | } | ||||
} | } | ||||
// Check if we can't merge it with another | // Check if we can't merge it with another. | ||||
for (let i = 0; i < this.armies.length - 1; ++i) | for (let i = 0; i < this.armies.length - 1; ++i) | ||||
{ | { | ||||
let army = this.armies[i]; | let army = this.armies[i]; | ||||
if (army.getType() != "default") | if (army.getType() != "default") | ||||
continue; | continue; | ||||
for (let j = i+1; j < this.armies.length; ++j) | for (let j = i+1; j < this.armies.length; ++j) | ||||
{ | { | ||||
let otherArmy = this.armies[j]; | let otherArmy = this.armies[j]; | ||||
if (otherArmy.getType() != "default" || | if (otherArmy.getType() != "default" || | ||||
API3.SquareVectorDistance(army.foePosition, otherArmy.foePosition) > this.armyMergeSize) | API3.SquareVectorDistance(army.foePosition, otherArmy.foePosition) > this.armyMergeSize) | ||||
continue; | continue; | ||||
// no need to clear here. | // No need to clear here. | ||||
army.merge(gameState, otherArmy); | army.merge(gameState, otherArmy); | ||||
this.armies.splice(j--, 1); | this.armies.splice(j--, 1); | ||||
} | } | ||||
} | } | ||||
if (gameState.ai.playedTurn % 5 != 0) | if (gameState.ai.playedTurn % 5 != 0) | ||||
return; | return; | ||||
// Check if any army is no more dangerous (possibly because it has defeated us and destroyed our base) | // Check if any army is no more dangerous (possibly because it has defeated us and destroyed our base). | ||||
this.attackingArmies = {}; | this.attackingArmies = {}; | ||||
for (let i = 0; i < this.armies.length; ++i) | for (let i = 0; i < this.armies.length; ++i) | ||||
{ | { | ||||
let army = this.armies[i]; | let army = this.armies[i]; | ||||
army.recalculatePosition(gameState); | army.recalculatePosition(gameState); | ||||
let owner = this.territoryMap.getOwner(army.foePosition); | let owner = this.territoryMap.getOwner(army.foePosition); | ||||
if (!gameState.isPlayerEnemy(owner)) | if (!gameState.isPlayerEnemy(owner)) | ||||
{ | { | ||||
if (gameState.isPlayerMutualAlly(owner)) | if (gameState.isPlayerMutualAlly(owner)) | ||||
{ | { | ||||
// update the number of enemies attacking this ally | // Update the number of enemies attacking this ally. | ||||
for (let id of army.foeEntities) | for (let id of army.foeEntities) | ||||
{ | { | ||||
let ent = gameState.getEntityById(id); | let ent = gameState.getEntityById(id); | ||||
if (!ent) | if (!ent) | ||||
continue; | continue; | ||||
let enemy = ent.owner(); | let enemy = ent.owner(); | ||||
if (this.attackingArmies[enemy] === undefined) | if (this.attackingArmies[enemy] === undefined) | ||||
this.attackingArmies[enemy] = {}; | this.attackingArmies[enemy] = {}; | ||||
if (this.attackingArmies[enemy][owner] === undefined) | if (this.attackingArmies[enemy][owner] === undefined) | ||||
this.attackingArmies[enemy][owner] = 0; | this.attackingArmies[enemy][owner] = 0; | ||||
this.attackingArmies[enemy][owner] += 1; | this.attackingArmies[enemy][owner] += 1; | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
continue; | continue; | ||||
} | } | ||||
else if (owner != 0) // enemy army back in its territory | else if (owner != 0) // Enemy army back in its territory. | ||||
{ | { | ||||
army.clear(gameState); | army.clear(gameState); | ||||
this.armies.splice(i--, 1); | this.armies.splice(i--, 1); | ||||
continue; | continue; | ||||
} | } | ||||
// army in neutral territory | // Army in neutral territory. | ||||
// TODO check smaller distance with all our buildings instead of only ccs with big distance | // TODO check smaller distance with all our buildings instead of only ccs with big distance. | ||||
let stillDangerous = false; | let stillDangerous = false; | ||||
let bases = gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre")); | let bases = gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre")); | ||||
for (let base of bases.values()) | for (let base of bases.values()) | ||||
{ | { | ||||
if (!gameState.isEntityAlly(base)) | if (!gameState.isEntityAlly(base)) | ||||
continue; | continue; | ||||
let cooperation = this.GetCooperationLevel(base.owner()); | let cooperation = this.GetCooperationLevel(base.owner()); | ||||
if (cooperation < 0.3 && !gameState.isEntityOwn(base)) | if (cooperation < 0.3 && !gameState.isEntityOwn(base)) | ||||
continue; | continue; | ||||
if (API3.SquareVectorDistance(base.position(), army.foePosition) > 40000) | if (API3.SquareVectorDistance(base.position(), army.foePosition) > 40000) | ||||
continue; | continue; | ||||
if(this.Config.debug > 1) | if(this.Config.debug > 1) | ||||
API3.warn("army in neutral territory, but still near one of our CC"); | API3.warn("army in neutral territory, but still near one of our CC"); | ||||
stillDangerous = true; | stillDangerous = true; | ||||
break; | break; | ||||
} | } | ||||
if (stillDangerous) | if (stillDangerous) | ||||
continue; | continue; | ||||
// Need to also check docks because of oversea bases | // Need to also check docks because of oversea bases. | ||||
for (let dock of gameState.getOwnStructures().filter(API3.Filters.byClass("Dock")).values()) | for (let dock of gameState.getOwnStructures().filter(API3.Filters.byClass("Dock")).values()) | ||||
{ | { | ||||
if (API3.SquareVectorDistance(dock.position(), army.foePosition) > 10000) | if (API3.SquareVectorDistance(dock.position(), army.foePosition) > 10000) | ||||
continue; | continue; | ||||
stillDangerous = true; | stillDangerous = true; | ||||
break; | break; | ||||
} | } | ||||
if (stillDangerous) | if (stillDangerous) | ||||
continue; | continue; | ||||
if (army.getType() == "default") | if (army.getType() == "default") | ||||
this.switchToAttack(gameState, army); | this.switchToAttack(gameState, army); | ||||
army.clear(gameState); | army.clear(gameState); | ||||
this.armies.splice(i--, 1); | this.armies.splice(i--, 1); | ||||
} | } | ||||
}; | }; | ||||
m.DefenseManager.prototype.assignDefenders = function(gameState) | m.DefenseManager.prototype.assignDefenders = function(gameState) | ||||
{ | { | ||||
if (!this.armies.length) | if (!this.armies.length) | ||||
return; | return; | ||||
let armiesNeeding = []; | let armiesNeeding = []; | ||||
// let's add defenders | // Let's add defenders. | ||||
for (let army of this.armies) | for (let army of this.armies) | ||||
{ | { | ||||
let needsDef = army.needsDefenders(gameState); | let needsDef = army.needsDefenders(gameState); | ||||
if (needsDef === false) | if (needsDef === false) | ||||
continue; | continue; | ||||
let armyAccess; | let armyAccess; | ||||
for (let entId of army.foeEntities) | for (let entId of army.foeEntities) | ||||
{ | { | ||||
let ent = gameState.getEntityById(entId); | let ent = gameState.getEntityById(entId); | ||||
if (!ent || !ent.position()) | if (!ent || !ent.position()) | ||||
continue; | continue; | ||||
armyAccess = m.getLandAccess(gameState, ent); | armyAccess = m.getLandAccess(gameState, ent); | ||||
break; | break; | ||||
} | } | ||||
if (!armyAccess) | if (!armyAccess) | ||||
API3.warn(" Petra error: attacking army " + army.ID + " without access"); | API3.warn(" Petra error: attacking army " + army.ID + " without access"); | ||||
army.recalculatePosition(gameState); | army.recalculatePosition(gameState); | ||||
armiesNeeding.push({ "army": army, "access": armyAccess, "need": needsDef }); | armiesNeeding.push({ "army": army, "access": armyAccess, "need": needsDef }); | ||||
} | } | ||||
if (!armiesNeeding.length) | if (!armiesNeeding.length) | ||||
return; | return; | ||||
// let's get our potential units | // Let's get our potential units. | ||||
let potentialDefenders = []; | let potentialDefenders = []; | ||||
gameState.getOwnUnits().forEach(function(ent) { | gameState.getOwnUnits().forEach(function(ent) { | ||||
if (!ent.position()) | if (!ent.position()) | ||||
return; | return; | ||||
if (ent.getMetadata(PlayerID, "plan") == -2 || ent.getMetadata(PlayerID, "plan") == -3) | if (ent.getMetadata(PlayerID, "plan") == -2 || ent.getMetadata(PlayerID, "plan") == -3) | ||||
return; | return; | ||||
if (ent.hasClass("Support") || ent.attackTypes() === undefined) | if (ent.hasClass("Support") || ent.attackTypes() === undefined) | ||||
return; | return; | ||||
Show All 12 Lines | if (ent.getMetadata(PlayerID, "plan") !== undefined && ent.getMetadata(PlayerID, "plan") != -1) | ||||
if (subrole && (subrole == "completing" || subrole == "walking" || subrole == "attacking")) | if (subrole && (subrole == "completing" || subrole == "walking" || subrole == "attacking")) | ||||
return; | return; | ||||
} | } | ||||
potentialDefenders.push(ent.id()); | potentialDefenders.push(ent.id()); | ||||
}); | }); | ||||
for (let ipass = 0; ipass < 2; ++ipass) | for (let ipass = 0; ipass < 2; ++ipass) | ||||
{ | { | ||||
// First pass only assign defenders with the right access | // First pass only assign defenders with the right access. | ||||
// Second pass assign all defenders | // Second pass assign all defenders. | ||||
// TODO could sort them by distance | // TODO could sort them by distance. | ||||
let backup = 0; | let backup = 0; | ||||
for (let i = 0; i < potentialDefenders.length; ++i) | for (let i = 0; i < potentialDefenders.length; ++i) | ||||
{ | { | ||||
let ent = gameState.getEntityById(potentialDefenders[i]); | let ent = gameState.getEntityById(potentialDefenders[i]); | ||||
if (!ent || !ent.position()) | if (!ent || !ent.position()) | ||||
continue; | continue; | ||||
let aMin; | let aMin; | ||||
let distMin; | let distMin; | ||||
let access = ipass == 0 ? m.getLandAccess(gameState, ent) : undefined; | let access = ipass == 0 ? m.getLandAccess(gameState, ent) : undefined; | ||||
for (let a = 0; a < armiesNeeding.length; ++a) | for (let a = 0; a < armiesNeeding.length; ++a) | ||||
{ | { | ||||
if (access && armiesNeeding[a].access != access) | if (access && armiesNeeding[a].access != access) | ||||
continue; | continue; | ||||
let dist = API3.SquareVectorDistance(ent.position(), armiesNeeding[a].army.foePosition); | let dist = API3.SquareVectorDistance(ent.position(), armiesNeeding[a].army.foePosition); | ||||
if (aMin !== undefined && dist > distMin) | if (aMin !== undefined && dist > distMin) | ||||
continue; | continue; | ||||
aMin = a; | aMin = a; | ||||
distMin = dist; | distMin = dist; | ||||
} | } | ||||
// If outside our territory (helping an ally or attacking a cc foundation) | // If outside our territory (helping an ally or attacking a cc foundation) | ||||
// or if in another access, keep some troops in backup | // or if in another access, keep some troops in backup. | ||||
if (backup < 12 && (aMin == undefined || distMin > 40000 && | if (backup < 12 && (aMin == undefined || distMin > 40000 && | ||||
this.territoryMap.getOwner(armiesNeeding[aMin].army.foePosition) != PlayerID)) | this.territoryMap.getOwner(armiesNeeding[aMin].army.foePosition) != PlayerID)) | ||||
{ | { | ||||
++backup; | ++backup; | ||||
potentialDefenders[i] = undefined; | potentialDefenders[i] = undefined; | ||||
continue; | continue; | ||||
} | } | ||||
else if (aMin === undefined) | else if (aMin === undefined) | ||||
continue; | continue; | ||||
armiesNeeding[aMin].need -= m.getMaxStrength(ent); | armiesNeeding[aMin].need -= m.getMaxStrength(ent); | ||||
armiesNeeding[aMin].army.addOwn(gameState, potentialDefenders[i]); | armiesNeeding[aMin].army.addOwn(gameState, potentialDefenders[i]); | ||||
armiesNeeding[aMin].army.assignUnit(gameState, potentialDefenders[i]); | armiesNeeding[aMin].army.assignUnit(gameState, potentialDefenders[i]); | ||||
potentialDefenders[i] = undefined; | potentialDefenders[i] = undefined; | ||||
if (armiesNeeding[aMin].need <= 0) | if (armiesNeeding[aMin].need <= 0) | ||||
armiesNeeding.splice(aMin, 1); | armiesNeeding.splice(aMin, 1); | ||||
if (!armiesNeeding.length) | if (!armiesNeeding.length) | ||||
return; | return; | ||||
} | } | ||||
} | } | ||||
// If shortage of defenders, produce infantry garrisoned in nearest civil center | // If shortage of defenders, produce infantry garrisoned in nearest civil center. | ||||
let armiesPos = []; | let armiesPos = []; | ||||
for (let a = 0; a < armiesNeeding.length; ++a) | for (let a = 0; a < armiesNeeding.length; ++a) | ||||
armiesPos.push(armiesNeeding[a].army.foePosition); | armiesPos.push(armiesNeeding[a].army.foePosition); | ||||
gameState.ai.HQ.trainEmergencyUnits(gameState, armiesPos); | gameState.ai.HQ.trainEmergencyUnits(gameState, armiesPos); | ||||
}; | }; | ||||
m.DefenseManager.prototype.abortArmy = function(gameState, army) | m.DefenseManager.prototype.abortArmy = function(gameState, army) | ||||
{ | { | ||||
army.clear(gameState); | army.clear(gameState); | ||||
for (let i = 0; i < this.armies.length; ++i) | for (let i = 0; i < this.armies.length; ++i) | ||||
{ | { | ||||
if (this.armies[i].ID != army.ID) | if (this.armies[i].ID != army.ID) | ||||
continue; | continue; | ||||
this.armies.splice(i, 1); | this.armies.splice(i, 1); | ||||
break; | break; | ||||
} | } | ||||
}; | }; | ||||
/** | /** | ||||
* If our defense structures are attacked, garrison soldiers inside when possible | * If our defense structures are attacked, garrison soldiers inside when possible | ||||
* and if a support unit is attacked and has less than 55% health, garrison it inside the nearest healing structure | * and if a support unit is attacked and has less than 55% health, garrison it inside the nearest healing structure | ||||
* and if a ranged siege unit (not used for defense) is attacked, garrison it in the nearest fortress | * and if a ranged siege unit (not used for defense) is attacked, garrison it in the nearest fortress. | ||||
* If our hero is attacked with regicide victory condition, the victoryManager will handle it | * If our hero is attacked with regicide victory condition, the victoryManager will handle it. | ||||
*/ | */ | ||||
m.DefenseManager.prototype.checkEvents = function(gameState, events) | m.DefenseManager.prototype.checkEvents = function(gameState, events) | ||||
{ | { | ||||
// must be called every turn for all armies | // Must be called every turn for all armies | ||||
for (let army of this.armies) | for (let army of this.armies) | ||||
army.checkEvents(gameState, events); | army.checkEvents(gameState, events); | ||||
for (let evt of events.OwnershipChanged) // capture events | // Capture events | ||||
for (let evt of events.OwnershipChanged) | |||||
{ | { | ||||
if (gameState.isPlayerMutualAlly(evt.from) && evt.to > 0) | if (gameState.isPlayerMutualAlly(evt.from) && evt.to > 0) | ||||
{ | { | ||||
let ent = gameState.getEntityById(evt.entity); | let ent = gameState.getEntityById(evt.entity); | ||||
if (ent && ent.hasClass("CivCentre")) // one of our cc has been captured | // One of our cc has been captured | ||||
if (ent && ent.hasClass("CivCentre")) | |||||
gameState.ai.HQ.attackManager.switchDefenseToAttack(gameState, ent, { "range": 150 }); | gameState.ai.HQ.attackManager.switchDefenseToAttack(gameState, ent, { "range": 150 }); | ||||
} | } | ||||
} | } | ||||
let allAttacked = {}; | let allAttacked = {}; | ||||
for (let evt of events.Attacked) | for (let evt of events.Attacked) | ||||
allAttacked[evt.target] = evt.attacker; | allAttacked[evt.target] = evt.attacker; | ||||
for (let evt of events.Attacked) | for (let evt of events.Attacked) | ||||
{ | { | ||||
let target = gameState.getEntityById(evt.target); | let target = gameState.getEntityById(evt.target); | ||||
if (!target || !target.position()) | if (!target || !target.position()) | ||||
continue; | continue; | ||||
let attacker = gameState.getEntityById(evt.attacker); | let attacker = gameState.getEntityById(evt.attacker); | ||||
if (attacker && gameState.isEntityOwn(attacker) && gameState.isEntityEnemy(target) && !attacker.hasClass("Ship") && | if (attacker && gameState.isEntityOwn(attacker) && gameState.isEntityEnemy(target) && !attacker.hasClass("Ship") && | ||||
(!target.hasClass("Structure") || target.attackRange("Ranged"))) | (!target.hasClass("Structure") || target.attackRange("Ranged"))) | ||||
{ | { | ||||
// If enemies are in range of one of our defensive structures, garrison it for arrow multiplier | // If enemies are in range of one of our defensive structures, garrison it for arrow multiplier. | ||||
// (enemy non-defensive structure are not considered to stay in sync with garrisonManager) | // (enemy non-defensive structure are not considered to stay in sync with garrisonManager) | ||||
if (attacker.position() && attacker.isGarrisonHolder() && attacker.getArrowMultiplier() && | if (attacker.position() && attacker.isGarrisonHolder() && attacker.getArrowMultiplier() && | ||||
(target.owner() != 0 || !target.hasClass("Unit") || | (target.owner() != 0 || !target.hasClass("Unit") || | ||||
target.unitAIState() && target.unitAIState().split(".")[1] == "COMBAT")) | target.unitAIState() && target.unitAIState().split(".")[1] == "COMBAT")) | ||||
this.garrisonUnitsInside(gameState, attacker, { "attacker": target }); | this.garrisonUnitsInside(gameState, attacker, { "attacker": target }); | ||||
} | } | ||||
if (!gameState.isEntityOwn(target)) | if (!gameState.isEntityOwn(target)) | ||||
continue; | continue; | ||||
// If attacked by one of our allies (he must trying to recover capture points), do not react | // If attacked by one of our allies (he must trying to recover capture points), do not react. | ||||
if (attacker && gameState.isEntityAlly(attacker)) | if (attacker && gameState.isEntityAlly(attacker)) | ||||
continue; | continue; | ||||
if (attacker && attacker.position() && target.hasClass("FishingBoat")) | if (attacker && attacker.position() && target.hasClass("FishingBoat")) | ||||
{ | { | ||||
let unitAIState = target.unitAIState(); | let unitAIState = target.unitAIState(); | ||||
let unitAIStateOrder = unitAIState ? unitAIState.split(".")[1] : ""; | let unitAIStateOrder = unitAIState ? unitAIState.split(".")[1] : ""; | ||||
if (target.isIdle() || unitAIStateOrder == "GATHER") | if (target.isIdle() || unitAIStateOrder == "GATHER") | ||||
{ | { | ||||
let pos = attacker.position(); | let pos = attacker.position(); | ||||
let range = attacker.attackRange("Ranged") ? attacker.attackRange("Ranged").max + 15 : 25; | let range = attacker.attackRange("Ranged") ? attacker.attackRange("Ranged").max + 15 : 25; | ||||
if (range * range > API3.SquareVectorDistance(pos, target.position())) | if (range * range > API3.SquareVectorDistance(pos, target.position())) | ||||
target.moveToRange(pos[0], pos[1], range, range); | target.moveToRange(pos[0], pos[1], range, range); | ||||
} | } | ||||
continue; | continue; | ||||
} | } | ||||
if (target.hasClass("Ship")) // TODO integrate other ships later, need to be sure it is accessible | // TODO integrate other ships later, need to be sure it is accessible. | ||||
if (target.hasClass("Ship")) | |||||
continue; | continue; | ||||
// If a building on a blinking tile is attacked, check if it can be defended. | // If a building on a blinking tile is attacked, check if it can be defended. | ||||
// Same thing for a building in an isolated base (not connected to a base with anchor). | // Same thing for a building in an isolated base (not connected to a base with anchor). | ||||
if (target.hasClass("Structure")) | if (target.hasClass("Structure")) | ||||
{ | { | ||||
let base = gameState.ai.HQ.getBaseByID(target.getMetadata(PlayerID, "base")); | let base = gameState.ai.HQ.getBaseByID(target.getMetadata(PlayerID, "base")); | ||||
if (this.territoryMap.isBlinking(target.position()) && !gameState.ai.HQ.isDefendable(target) || | if (this.territoryMap.isBlinking(target.position()) && !gameState.ai.HQ.isDefendable(target) || | ||||
!base || gameState.ai.HQ.baseManagers.every(b => !b.anchor || b.accessIndex != base.accessIndex)) | !base || gameState.ai.HQ.baseManagers.every(b => !b.anchor || b.accessIndex != base.accessIndex)) | ||||
{ | { | ||||
let capture = target.capturePoints(); | let capture = target.capturePoints(); | ||||
if (!capture) | if (!capture) | ||||
continue; | continue; | ||||
let captureRatio = capture[PlayerID] / capture.reduce((a, b) => a + b); | let captureRatio = capture[PlayerID] / capture.reduce((a, b) => a + b); | ||||
if (captureRatio > 0.50 && captureRatio < 0.70) | if (captureRatio > 0.50 && captureRatio < 0.70) | ||||
target.destroy(); | target.destroy(); | ||||
continue; | continue; | ||||
} | } | ||||
} | } | ||||
// If inside a started attack plan, let the plan deal with this unit | // If inside a started attack plan, let the plan deal with this unit. | ||||
let plan = target.getMetadata(PlayerID, "plan"); | let plan = target.getMetadata(PlayerID, "plan"); | ||||
if (plan !== undefined && plan >= 0) | if (plan !== undefined && plan >= 0) | ||||
{ | { | ||||
let attack = gameState.ai.HQ.attackManager.getPlan(plan); | let attack = gameState.ai.HQ.attackManager.getPlan(plan); | ||||
if (attack && attack.state != "unexecuted") | if (attack && attack.state != "unexecuted") | ||||
continue; | continue; | ||||
} | } | ||||
// Signal this attacker to our defense manager, except if we are in enemy territory | // Signal this attacker to our defense manager, except if we are in enemy territory. | ||||
// TODO treat ship attack | // TODO treat ship attack. | ||||
if (attacker && attacker.position() && attacker.getMetadata(PlayerID, "PartOfArmy") === undefined && | if (attacker && attacker.position() && attacker.getMetadata(PlayerID, "PartOfArmy") === undefined && | ||||
!attacker.hasClass("Structure") && !attacker.hasClass("Ship")) | !attacker.hasClass("Structure") && !attacker.hasClass("Ship")) | ||||
{ | { | ||||
let territoryOwner = this.territoryMap.getOwner(attacker.position()); | let territoryOwner = this.territoryMap.getOwner(attacker.position()); | ||||
if (territoryOwner == 0 || gameState.isPlayerAlly(territoryOwner)) | if (territoryOwner == 0 || gameState.isPlayerAlly(territoryOwner)) | ||||
this.makeIntoArmy(gameState, attacker.id()); | this.makeIntoArmy(gameState, attacker.id()); | ||||
} | } | ||||
if (target.getMetadata(PlayerID, "PartOfArmy") !== undefined) | if (target.getMetadata(PlayerID, "PartOfArmy") !== undefined) | ||||
{ | { | ||||
let army = this.getArmy(target.getMetadata(PlayerID, "PartOfArmy")); | let army = this.getArmy(target.getMetadata(PlayerID, "PartOfArmy")); | ||||
if (army.getType() == "capturing") | if (army.getType() == "capturing") | ||||
{ | { | ||||
let abort = false; | let abort = false; | ||||
// if one of the units trying to capture a structure is attacked, | // If one of the units trying to capture a structure is attacked, | ||||
// abort the army so that the unit can defend itself | // abort the army so that the unit can defend itself. | ||||
if (army.ownEntities.indexOf(target.id()) != -1) | if (army.ownEntities.indexOf(target.id()) != -1) | ||||
abort = true; | abort = true; | ||||
else if (army.foeEntities[0] == target.id() && target.owner() == PlayerID) | else if (army.foeEntities[0] == target.id() && target.owner() == PlayerID) | ||||
{ | { | ||||
// else we may be trying to regain some capture point from one of our structure | // Else we may be trying to regain some capture point from one of our structure. | ||||
abort = true; | abort = true; | ||||
let capture = target.capturePoints(); | let capture = target.capturePoints(); | ||||
for (let j = 0; j < capture.length; ++j) | for (let j = 0; j < capture.length; ++j) | ||||
{ | { | ||||
if (!gameState.isPlayerEnemy(j) || capture[j] == 0) | if (!gameState.isPlayerEnemy(j) || capture[j] == 0) | ||||
continue; | continue; | ||||
abort = false; | abort = false; | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
if (abort) | if (abort) | ||||
this.abortArmy(gameState, army); | this.abortArmy(gameState, army); | ||||
} | } | ||||
continue; | continue; | ||||
} | } | ||||
// try to garrison any attacked support unit if low healthlevel | // Try to garrison any attacked support unit if low healthlevel. | ||||
if (target.hasClass("Support") && target.healthLevel() < this.Config.garrisonHealthLevel.medium && | if (target.hasClass("Support") && target.healthLevel() < this.Config.garrisonHealthLevel.medium && | ||||
!target.getMetadata(PlayerID, "transport") && plan != -2 && plan != -3) | !target.getMetadata(PlayerID, "transport") && plan != -2 && plan != -3) | ||||
{ | { | ||||
this.garrisonAttackedUnit(gameState, target); | this.garrisonAttackedUnit(gameState, target); | ||||
continue; | continue; | ||||
} | } | ||||
// try to garrison any attacked catapult | // Try to garrison any attacked catapult. | ||||
if (target.hasClass("Catapult") && | if (target.hasClass("Catapult") && | ||||
!target.getMetadata(PlayerID, "transport") && plan != -2 && plan != -3) | !target.getMetadata(PlayerID, "transport") && plan != -2 && plan != -3) | ||||
{ | { | ||||
this.garrisonSiegeUnit(gameState, target); | this.garrisonSiegeUnit(gameState, target); | ||||
continue; | continue; | ||||
} | } | ||||
if (!attacker || !attacker.position()) | if (!attacker || !attacker.position()) | ||||
continue; | continue; | ||||
if (target.isGarrisonHolder() && target.getArrowMultiplier()) | if (target.isGarrisonHolder() && target.getArrowMultiplier()) | ||||
this.garrisonUnitsInside(gameState, target, { "attacker": attacker }); | this.garrisonUnitsInside(gameState, target, { "attacker": attacker }); | ||||
if (target.hasClass("Unit") && attacker.hasClass("Unit")) | if (target.hasClass("Unit") && attacker.hasClass("Unit")) | ||||
{ | { | ||||
// Consider if we should retaliate or continue our task | // Consider if we should retaliate or continue our task. | ||||
if (target.hasClass("Support") || target.attackTypes() === undefined) | if (target.hasClass("Support") || target.attackTypes() === undefined) | ||||
continue; | continue; | ||||
let orderData = target.unitAIOrderData(); | let orderData = target.unitAIOrderData(); | ||||
let currentTarget = orderData && orderData.length && orderData[0].target ? | let currentTarget = orderData && orderData.length && orderData[0].target ? | ||||
gameState.getEntityById(orderData[0].target) : undefined; | gameState.getEntityById(orderData[0].target) : undefined; | ||||
if (currentTarget) | if (currentTarget) | ||||
{ | { | ||||
let unitAIState = target.unitAIState(); | let unitAIState = target.unitAIState(); | ||||
let unitAIStateOrder = unitAIState ? unitAIState.split(".")[1] : ""; | let unitAIStateOrder = unitAIState ? unitAIState.split(".")[1] : ""; | ||||
if (unitAIStateOrder == "COMBAT" && (currentTarget == attacker.id() || | if (unitAIStateOrder == "COMBAT" && (currentTarget == attacker.id() || | ||||
!currentTarget.hasClass("Structure") && !currentTarget.hasClass("Support"))) | !currentTarget.hasClass("Structure") && !currentTarget.hasClass("Support"))) | ||||
continue; | continue; | ||||
if (unitAIStateOrder == "REPAIR" && currentTarget.hasDefensiveFire()) | if (unitAIStateOrder == "REPAIR" && currentTarget.hasDefensiveFire()) | ||||
continue; | continue; | ||||
if (unitAIStateOrder == "COMBAT" && !m.isSiegeUnit(currentTarget) && | if (unitAIStateOrder == "COMBAT" && !m.isSiegeUnit(currentTarget) && | ||||
gameState.ai.HQ.capturableTargets.has(orderData[0].target)) | gameState.ai.HQ.capturableTargets.has(orderData[0].target)) | ||||
{ | { | ||||
// take the nearest unit also attacking this structure to help us | // Take the nearest unit also attacking this structure to help us. | ||||
let capturableTarget = gameState.ai.HQ.capturableTargets.get(orderData[0].target); | let capturableTarget = gameState.ai.HQ.capturableTargets.get(orderData[0].target); | ||||
let minDist; | let minDist; | ||||
let minEnt; | let minEnt; | ||||
let pos = attacker.position(); | let pos = attacker.position(); | ||||
capturableTarget.ents.delete(target.id()); | capturableTarget.ents.delete(target.id()); | ||||
for (let entId of capturableTarget.ents) | for (let entId of capturableTarget.ents) | ||||
{ | { | ||||
if (allAttacked[entId]) | if (allAttacked[entId]) | ||||
continue; | continue; | ||||
let ent = gameState.getEntityById(entId); | let ent = gameState.getEntityById(entId); | ||||
if (!ent || !ent.position()) | if (!ent || !ent.position()) | ||||
continue; | continue; | ||||
// Check that the unit is still attacking the structure (since the last played turn) | // Check that the unit is still attacking the structure (since the last played turn). | ||||
let state = ent.unitAIState(); | let state = ent.unitAIState(); | ||||
if (!state || !state.split(".")[1] || state.split(".")[1] != "COMBAT") | if (!state || !state.split(".")[1] || state.split(".")[1] != "COMBAT") | ||||
continue; | continue; | ||||
let entOrderData = ent.unitAIOrderData(); | let entOrderData = ent.unitAIOrderData(); | ||||
if (!entOrderData || !entOrderData.length || !entOrderData[0].target || | if (!entOrderData || !entOrderData.length || !entOrderData[0].target || | ||||
entOrderData[0].target != orderData[0].target) | entOrderData[0].target != orderData[0].target) | ||||
continue; | continue; | ||||
let dist = API3.SquareVectorDistance(pos, ent.position()); | let dist = API3.SquareVectorDistance(pos, ent.position()); | ||||
Show All 33 Lines | m.DefenseManager.prototype.garrisonUnitsInside = function(gameState, target, data) | ||||
} | } | ||||
let access = m.getLandAccess(gameState, target); | let access = m.getLandAccess(gameState, target); | ||||
let garrisonManager = gameState.ai.HQ.garrisonManager; | let garrisonManager = gameState.ai.HQ.garrisonManager; | ||||
let garrisonArrowClasses = target.getGarrisonArrowClasses(); | let garrisonArrowClasses = target.getGarrisonArrowClasses(); | ||||
let typeGarrison = data.type || "protection"; | let typeGarrison = data.type || "protection"; | ||||
let allowMelee = gameState.ai.HQ.garrisonManager.allowMelee(target); | let allowMelee = gameState.ai.HQ.garrisonManager.allowMelee(target); | ||||
if (allowMelee === undefined) | if (allowMelee === undefined) | ||||
{ | { | ||||
// Should be kept in sync with garrisonManager to avoid garrisoning-ungarrisoning some units | // Should be kept in sync with garrisonManager to avoid garrisoning-ungarrisoning some units. | ||||
if (data.attacker) | if (data.attacker) | ||||
allowMelee = data.attacker.hasClass("Structure") ? data.attacker.attackRange("Ranged") : !m.isSiegeUnit(data.attacker); | allowMelee = data.attacker.hasClass("Structure") ? data.attacker.attackRange("Ranged") : !m.isSiegeUnit(data.attacker); | ||||
else | else | ||||
allowMelee = true; | allowMelee = true; | ||||
} | } | ||||
let units = gameState.getOwnUnits().filter(ent => { | let units = gameState.getOwnUnits().filter(ent => { | ||||
if (!ent.position()) | if (!ent.position()) | ||||
return false; | return false; | ||||
if (!MatchesClassList(ent.classes(), garrisonArrowClasses)) | if (!MatchesClassList(ent.classes(), garrisonArrowClasses)) | ||||
return false; | return false; | ||||
if (typeGarrison != "decay" && !allowMelee && ent.attackTypes().indexOf("Melee") != -1) | if (typeGarrison != "decay" && !allowMelee && ent.attackTypes().indexOf("Melee") != -1) | ||||
return false; | return false; | ||||
if (ent.getMetadata(PlayerID, "transport") !== undefined) | if (ent.getMetadata(PlayerID, "transport") !== undefined) | ||||
return false; | return false; | ||||
let army = ent.getMetadata(PlayerID, "PartOfArmy") ? this.getArmy(ent.getMetadata(PlayerID, "PartOfArmy")) : undefined; | let army = ent.getMetadata(PlayerID, "PartOfArmy") ? this.getArmy(ent.getMetadata(PlayerID, "PartOfArmy")) : undefined; | ||||
if (!army && (ent.getMetadata(PlayerID, "plan") == -2 || ent.getMetadata(PlayerID, "plan") == -3)) | if (!army && (ent.getMetadata(PlayerID, "plan") == -2 || ent.getMetadata(PlayerID, "plan") == -3)) | ||||
return false; | return false; | ||||
if (ent.getMetadata(PlayerID, "plan") !== undefined && ent.getMetadata(PlayerID, "plan") >= 0) | if (ent.getMetadata(PlayerID, "plan") !== undefined && ent.getMetadata(PlayerID, "plan") >= 0) | ||||
{ | { | ||||
let subrole = ent.getMetadata(PlayerID, "subrole"); | let subrole = ent.getMetadata(PlayerID, "subrole"); | ||||
// when structure decaying (usually because we've just captured it in enemy territory), also allow units from an attack plan | // When structure decaying (usually because we've just captured it in enemy territory), also allow units from an attack plan. | ||||
if (typeGarrison != "decay" && subrole && (subrole == "completing" || subrole == "walking" || subrole == "attacking")) | if (typeGarrison != "decay" && subrole && (subrole == "completing" || subrole == "walking" || subrole == "attacking")) | ||||
return false; | return false; | ||||
} | } | ||||
if (m.getLandAccess(gameState, ent) != access) | if (m.getLandAccess(gameState, ent) != access) | ||||
return false; | return false; | ||||
return true; | return true; | ||||
}).filterNearest(target.position()); | }).filterNearest(target.position()); | ||||
Show All 12 Lines | for (let ent of units.values()) | ||||
if (army) | if (army) | ||||
army.removeOwn(gameState, ent.id()); | army.removeOwn(gameState, ent.id()); | ||||
garrisonManager.garrison(gameState, ent, target, typeGarrison); | garrisonManager.garrison(gameState, ent, target, typeGarrison); | ||||
ret = true; | ret = true; | ||||
} | } | ||||
return ret; | return ret; | ||||
}; | }; | ||||
/** garrison a attacked siege ranged unit inside the nearest fortress */ | /** Garrison a attacked siege ranged unit inside the nearest fortress */ | ||||
m.DefenseManager.prototype.garrisonSiegeUnit = function(gameState, unit) | m.DefenseManager.prototype.garrisonSiegeUnit = function(gameState, unit) | ||||
{ | { | ||||
let distmin = Math.min(); | let distmin = Math.min(); | ||||
let nearest; | let nearest; | ||||
let unitAccess = m.getLandAccess(gameState, unit); | let unitAccess = m.getLandAccess(gameState, unit); | ||||
let garrisonManager = gameState.ai.HQ.garrisonManager; | let garrisonManager = gameState.ai.HQ.garrisonManager; | ||||
for (let ent of gameState.getAllyStructures().values()) | for (let ent of gameState.getAllyStructures().values()) | ||||
{ | { | ||||
Show All 14 Lines | for (let ent of gameState.getAllyStructures().values()) | ||||
nearest = ent; | nearest = ent; | ||||
} | } | ||||
if (nearest) | if (nearest) | ||||
garrisonManager.garrison(gameState, unit, nearest, "protection"); | garrisonManager.garrison(gameState, unit, nearest, "protection"); | ||||
return nearest !== undefined; | return nearest !== undefined; | ||||
}; | }; | ||||
/** | /** | ||||
* Garrison a hurt unit inside a player-owned or allied structure | * Garrison a hurt unit inside a player-owned or allied structure. | ||||
* If emergency is true, the unit will be garrisoned in the closest possible structure | * If emergency is true, the unit will be garrisoned in the closest possible structure. | ||||
* Otherwise, it will garrison in the closest healing structure | * Otherwise, it will garrison in the closest healing structure. | ||||
*/ | */ | ||||
m.DefenseManager.prototype.garrisonAttackedUnit = function(gameState, unit, emergency = false) | m.DefenseManager.prototype.garrisonAttackedUnit = function(gameState, unit, emergency = false) | ||||
{ | { | ||||
let distmin = Math.min(); | let distmin = Math.min(); | ||||
let nearest; | let nearest; | ||||
let unitAccess = m.getLandAccess(gameState, unit); | let unitAccess = m.getLandAccess(gameState, unit); | ||||
let garrisonManager = gameState.ai.HQ.garrisonManager; | let garrisonManager = gameState.ai.HQ.garrisonManager; | ||||
for (let ent of gameState.getAllyStructures().values()) | for (let ent of gameState.getAllyStructures().values()) | ||||
Show All 20 Lines | m.DefenseManager.prototype.garrisonAttackedUnit = function(gameState, unit, emergency = false) | ||||
if (!nearest) | if (!nearest) | ||||
return false; | return false; | ||||
if (!emergency) | if (!emergency) | ||||
{ | { | ||||
garrisonManager.garrison(gameState, unit, nearest, "protection"); | garrisonManager.garrison(gameState, unit, nearest, "protection"); | ||||
return true; | return true; | ||||
} | } | ||||
if (garrisonManager.numberOfGarrisonedUnits(nearest) >= nearest.garrisonMax()) // make room for this ent | // Make room for this ent. | ||||
if (garrisonManager.numberOfGarrisonedUnits(nearest) >= nearest.garrisonMax()) | |||||
nearest.unload(nearest.garrisoned()[0]); | nearest.unload(nearest.garrisoned()[0]); | ||||
garrisonManager.garrison(gameState, unit, nearest, nearest.buffHeal() ? "protection" : "emergency"); | garrisonManager.garrison(gameState, unit, nearest, nearest.buffHeal() ? "protection" : "emergency"); | ||||
return true; | return true; | ||||
}; | }; | ||||
/** | /** | ||||
* Be more inclined to help an ally attacked by several enemies | * Be more inclined to help an ally attacked by several enemies. | ||||
*/ | */ | ||||
m.DefenseManager.prototype.GetCooperationLevel = function(ally) | m.DefenseManager.prototype.GetCooperationLevel = function(ally) | ||||
{ | { | ||||
let cooperation = this.Config.personality.cooperative; | let cooperation = this.Config.personality.cooperative; | ||||
if (this.attackedAllies[ally] && this.attackedAllies[ally] > 1) | if (this.attackedAllies[ally] && this.attackedAllies[ally] > 1) | ||||
cooperation += 0.2 * (this.attackedAllies[ally] - 1); | cooperation += 0.2 * (this.attackedAllies[ally] - 1); | ||||
return cooperation; | return cooperation; | ||||
}; | }; | ||||
/** | /** | ||||
* Switch a defense army into an attack if needed | * Switch a defense army into an attack if needed. | ||||
*/ | */ | ||||
m.DefenseManager.prototype.switchToAttack = function(gameState, army) | m.DefenseManager.prototype.switchToAttack = function(gameState, army) | ||||
{ | { | ||||
if (!army) | if (!army) | ||||
return; | return; | ||||
for (let targetId of this.targetList) | for (let targetId of this.targetList) | ||||
{ | { | ||||
let target = gameState.getEntityById(targetId); | let target = gameState.getEntityById(targetId); | ||||
▲ Show 20 Lines • Show All 50 Lines • Show Last 20 Lines |
Wildfire Games · Phabricator
!!cc.foundationProgress() ?
Maybe merge the two with a || operator ?