Changeset View
Changeset View
Standalone View
Standalone View
ps/trunk/binaries/data/mods/public/simulation/ai/petra/attackPlan.js
var PETRA = function(m) | |||||
{ | |||||
/** | /** | ||||
* This is an attack plan: | * This is an attack plan: | ||||
* It deals with everything in an attack, from picking a target to picking a path to it | * It deals with everything in an attack, from picking a target to picking a path to it | ||||
* To making sure units are built, and pushing elements to the queue manager otherwise | * To making sure units are built, and pushing elements to the queue manager otherwise | ||||
* It also handles the actual attack, though much work is needed on that. | * It also handles the actual attack, though much work is needed on that. | ||||
*/ | */ | ||||
m.AttackPlan = function(gameState, Config, uniqueID, type, data) | PETRA.AttackPlan = function(gameState, Config, uniqueID, type, data) | ||||
{ | { | ||||
this.Config = Config; | this.Config = Config; | ||||
this.name = uniqueID; | this.name = uniqueID; | ||||
this.type = type || "Attack"; | this.type = type || "Attack"; | ||||
this.state = "unexecuted"; | this.state = "unexecuted"; | ||||
this.forced = false; // true when this attacked has been forced to help an ally | this.forced = false; // true when this attacked has been forced to help an ally | ||||
if (data && data.target) | if (data && data.target) | ||||
Show All 14 Lines | PETRA.AttackPlan = function(gameState, Config, uniqueID, type, data) | ||||
// get a starting rallyPoint ... will be improved later | // get a starting rallyPoint ... will be improved later | ||||
let rallyPoint; | let rallyPoint; | ||||
let rallyAccess; | let rallyAccess; | ||||
let allAccesses = {}; | let allAccesses = {}; | ||||
for (let base of gameState.ai.HQ.baseManagers) | for (let base of gameState.ai.HQ.baseManagers) | ||||
{ | { | ||||
if (!base.anchor || !base.anchor.position()) | if (!base.anchor || !base.anchor.position()) | ||||
continue; | continue; | ||||
let access = m.getLandAccess(gameState, base.anchor); | let access = PETRA.getLandAccess(gameState, base.anchor); | ||||
if (!rallyPoint) | if (!rallyPoint) | ||||
{ | { | ||||
rallyPoint = base.anchor.position(); | rallyPoint = base.anchor.position(); | ||||
rallyAccess = access; | rallyAccess = access; | ||||
} | } | ||||
if (!allAccesses[access]) | if (!allAccesses[access]) | ||||
allAccesses[access] = base.anchor.position(); | allAccesses[access] = base.anchor.position(); | ||||
} | } | ||||
if (!rallyPoint) // no base ? take the position of any of our entities | if (!rallyPoint) // no base ? take the position of any of our entities | ||||
{ | { | ||||
for (let ent of gameState.getOwnEntities().values()) | for (let ent of gameState.getOwnEntities().values()) | ||||
{ | { | ||||
if (!ent.position()) | if (!ent.position()) | ||||
continue; | continue; | ||||
let access = m.getLandAccess(gameState, ent); | let access = PETRA.getLandAccess(gameState, ent); | ||||
rallyPoint = ent.position(); | rallyPoint = ent.position(); | ||||
rallyAccess = access; | rallyAccess = access; | ||||
allAccesses[access] = rallyPoint; | allAccesses[access] = rallyPoint; | ||||
break; | break; | ||||
} | } | ||||
if (!rallyPoint) | if (!rallyPoint) | ||||
{ | { | ||||
this.failed = true; | this.failed = true; | ||||
return false; | return false; | ||||
} | } | ||||
} | } | ||||
this.rallyPoint = rallyPoint; | this.rallyPoint = rallyPoint; | ||||
this.overseas = 0; | this.overseas = 0; | ||||
if (gameState.ai.HQ.navalMap) | if (gameState.ai.HQ.navalMap) | ||||
{ | { | ||||
for (let structure of gameState.getEnemyStructures().values()) | for (let structure of gameState.getEnemyStructures().values()) | ||||
{ | { | ||||
if (this.target && structure.id() != this.target.id()) | if (this.target && structure.id() != this.target.id()) | ||||
continue; | continue; | ||||
if (!structure.position()) | if (!structure.position()) | ||||
continue; | continue; | ||||
let access = m.getLandAccess(gameState, structure); | let access = PETRA.getLandAccess(gameState, structure); | ||||
if (access in allAccesses) | if (access in allAccesses) | ||||
{ | { | ||||
this.overseas = 0; | this.overseas = 0; | ||||
this.rallyPoint = allAccesses[access]; | this.rallyPoint = allAccesses[access]; | ||||
break; | break; | ||||
} | } | ||||
else if (!this.overseas) | else if (!this.overseas) | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 124 Lines • ▼ Show 20 Lines | PETRA.AttackPlan = function(gameState, Config, uniqueID, type, data) | ||||
this.position5TurnsAgo = [0, 0]; | this.position5TurnsAgo = [0, 0]; | ||||
this.lastPosition = [0, 0]; | this.lastPosition = [0, 0]; | ||||
this.position = [0, 0]; | this.position = [0, 0]; | ||||
this.isBlocked = false; // true when this attack faces walls | this.isBlocked = false; // true when this attack faces walls | ||||
return true; | return true; | ||||
}; | }; | ||||
m.AttackPlan.prototype.init = function(gameState) | PETRA.AttackPlan.prototype.init = function(gameState) | ||||
{ | { | ||||
this.queue = gameState.ai.queues["plan_" + this.name]; | this.queue = gameState.ai.queues["plan_" + this.name]; | ||||
this.queueChamp = gameState.ai.queues["plan_" + this.name +"_champ"]; | this.queueChamp = gameState.ai.queues["plan_" + this.name +"_champ"]; | ||||
this.queueSiege = gameState.ai.queues["plan_" + this.name +"_siege"]; | this.queueSiege = gameState.ai.queues["plan_" + this.name +"_siege"]; | ||||
this.unitCollection = gameState.getOwnUnits().filter(API3.Filters.byMetadata(PlayerID, "plan", this.name)); | this.unitCollection = gameState.getOwnUnits().filter(API3.Filters.byMetadata(PlayerID, "plan", this.name)); | ||||
this.unitCollection.registerUpdates(); | this.unitCollection.registerUpdates(); | ||||
this.unit = {}; | this.unit = {}; | ||||
// defining the entity collections. Will look for units I own, that are part of this plan. | // defining the entity collections. Will look for units I own, that are part of this plan. | ||||
// Also defining the buildOrders. | // Also defining the buildOrders. | ||||
for (let cat in this.unitStat) | for (let cat in this.unitStat) | ||||
{ | { | ||||
let Unit = this.unitStat[cat]; | let Unit = this.unitStat[cat]; | ||||
this.unit[cat] = this.unitCollection.filter(API3.Filters.byClassesAnd(Unit.classes)); | this.unit[cat] = this.unitCollection.filter(API3.Filters.byClassesAnd(Unit.classes)); | ||||
this.unit[cat].registerUpdates(); | this.unit[cat].registerUpdates(); | ||||
if (this.canBuildUnits) | if (this.canBuildUnits) | ||||
this.buildOrders.push([0, Unit.classes, this.unit[cat], Unit, cat]); | this.buildOrders.push([0, Unit.classes, this.unit[cat], Unit, cat]); | ||||
} | } | ||||
}; | }; | ||||
m.AttackPlan.prototype.getName = function() | PETRA.AttackPlan.prototype.getName = function() | ||||
{ | { | ||||
return this.name; | return this.name; | ||||
}; | }; | ||||
m.AttackPlan.prototype.getType = function() | PETRA.AttackPlan.prototype.getType = function() | ||||
{ | { | ||||
return this.type; | return this.type; | ||||
}; | }; | ||||
m.AttackPlan.prototype.isStarted = function() | PETRA.AttackPlan.prototype.isStarted = function() | ||||
{ | { | ||||
return this.state !== "unexecuted" && this.state !== "completing"; | return this.state !== "unexecuted" && this.state !== "completing"; | ||||
}; | }; | ||||
m.AttackPlan.prototype.isPaused = function() | PETRA.AttackPlan.prototype.isPaused = function() | ||||
{ | { | ||||
return this.paused; | return this.paused; | ||||
}; | }; | ||||
m.AttackPlan.prototype.setPaused = function(boolValue) | PETRA.AttackPlan.prototype.setPaused = function(boolValue) | ||||
{ | { | ||||
this.paused = boolValue; | this.paused = boolValue; | ||||
}; | }; | ||||
/** | /** | ||||
* Returns true if the attack can be executed at the current time | * Returns true if the attack can be executed at the current time | ||||
* Basically it checks we have enough units. | * Basically it checks we have enough units. | ||||
*/ | */ | ||||
m.AttackPlan.prototype.canStart = function() | PETRA.AttackPlan.prototype.canStart = function() | ||||
{ | { | ||||
if (!this.canBuildUnits) | if (!this.canBuildUnits) | ||||
return true; | return true; | ||||
for (let unitCat in this.unitStat) | for (let unitCat in this.unitStat) | ||||
if (this.unit[unitCat].length < this.unitStat[unitCat].minSize) | if (this.unit[unitCat].length < this.unitStat[unitCat].minSize) | ||||
return false; | return false; | ||||
return true; | return true; | ||||
}; | }; | ||||
m.AttackPlan.prototype.mustStart = function() | PETRA.AttackPlan.prototype.mustStart = function() | ||||
{ | { | ||||
if (this.isPaused()) | if (this.isPaused()) | ||||
return false; | return false; | ||||
if (!this.canBuildUnits) | if (!this.canBuildUnits) | ||||
return this.unitCollection.hasEntities(); | return this.unitCollection.hasEntities(); | ||||
let MaxReachedEverywhere = true; | let MaxReachedEverywhere = true; | ||||
Show All 13 Lines | PETRA.AttackPlan.prototype.mustStart = function() | ||||
if (MaxReachedEverywhere) | if (MaxReachedEverywhere) | ||||
return true; | return true; | ||||
if (MinReachedEverywhere) | if (MinReachedEverywhere) | ||||
return this.type == "Raid" && this.target && this.target.foundationProgress() && | return this.type == "Raid" && this.target && this.target.foundationProgress() && | ||||
this.target.foundationProgress() > 50; | this.target.foundationProgress() > 50; | ||||
return false; | return false; | ||||
}; | }; | ||||
m.AttackPlan.prototype.forceStart = function() | PETRA.AttackPlan.prototype.forceStart = function() | ||||
{ | { | ||||
for (let unitCat in this.unitStat) | for (let unitCat in this.unitStat) | ||||
{ | { | ||||
let Unit = this.unitStat[unitCat]; | let Unit = this.unitStat[unitCat]; | ||||
Unit.targetSize = 0; | Unit.targetSize = 0; | ||||
Unit.minSize = 0; | Unit.minSize = 0; | ||||
} | } | ||||
this.forced = true; | this.forced = true; | ||||
}; | }; | ||||
/** Adds a build order. If resetQueue is true, this will reset the queue. */ | /** Adds a build order. If resetQueue is true, this will reset the queue. */ | ||||
m.AttackPlan.prototype.addBuildOrder = function(gameState, name, unitStats, resetQueue) | PETRA.AttackPlan.prototype.addBuildOrder = function(gameState, name, unitStats, resetQueue) | ||||
{ | { | ||||
if (!this.isStarted()) | if (!this.isStarted()) | ||||
{ | { | ||||
// no minsize as we don't want the plan to fail at the last minute though. | // no minsize as we don't want the plan to fail at the last minute though. | ||||
this.unitStat[name] = unitStats; | this.unitStat[name] = unitStats; | ||||
let Unit = this.unitStat[name]; | let Unit = this.unitStat[name]; | ||||
this.unit[name] = this.unitCollection.filter(API3.Filters.byClassesAnd(Unit.classes)); | this.unit[name] = this.unitCollection.filter(API3.Filters.byClassesAnd(Unit.classes)); | ||||
this.unit[name].registerUpdates(); | this.unit[name].registerUpdates(); | ||||
this.buildOrders.push([0, Unit.classes, this.unit[name], Unit, name]); | this.buildOrders.push([0, Unit.classes, this.unit[name], Unit, name]); | ||||
if (resetQueue) | if (resetQueue) | ||||
{ | { | ||||
this.queue.empty(); | this.queue.empty(); | ||||
this.queueChamp.empty(); | this.queueChamp.empty(); | ||||
this.queueSiege.empty(); | this.queueSiege.empty(); | ||||
} | } | ||||
} | } | ||||
}; | }; | ||||
m.AttackPlan.prototype.addSiegeUnits = function(gameState) | PETRA.AttackPlan.prototype.addSiegeUnits = function(gameState) | ||||
{ | { | ||||
if (this.siegeState == 2 || this.state !== "unexecuted") | if (this.siegeState == 2 || this.state !== "unexecuted") | ||||
return false; | return false; | ||||
let civ = gameState.getPlayerCiv(); | let civ = gameState.getPlayerCiv(); | ||||
let classes = [[ "Siege", "Melee"], ["Siege", "Ranged"], ["Elephant", "Melee", "Champion"]]; | let classes = [[ "Siege", "Melee"], ["Siege", "Ranged"], ["Elephant", "Melee", "Champion"]]; | ||||
let hasTrainer = [false, false, false]; | let hasTrainer = [false, false, false]; | ||||
for (let ent of gameState.getOwnTrainingFacilities().values()) | for (let ent of gameState.getOwnTrainingFacilities().values()) | ||||
Show All 35 Lines | PETRA.AttackPlan.prototype.addSiegeUnits = function(gameState) | ||||
// no minsize as we don't want the plan to fail at the last minute though. | // no minsize as we don't want the plan to fail at the last minute though. | ||||
let stat = { "priority": 1, "minSize": 0, "targetSize": targetSize, "batchSize": Math.min(targetSize, 2), | let stat = { "priority": 1, "minSize": 0, "targetSize": targetSize, "batchSize": Math.min(targetSize, 2), | ||||
"classes": classes[i], "interests": [ ["siegeStrength", 3] ] }; | "classes": classes[i], "interests": [ ["siegeStrength", 3] ] }; | ||||
this.addBuildOrder(gameState, "Siege", stat, true); | this.addBuildOrder(gameState, "Siege", stat, true); | ||||
return true; | return true; | ||||
}; | }; | ||||
/** Three returns possible: 1 is "keep going", 0 is "failed plan", 2 is "start". */ | /** Three returns possible: 1 is "keep going", 0 is "failed plan", 2 is "start". */ | ||||
m.AttackPlan.prototype.updatePreparation = function(gameState) | PETRA.AttackPlan.prototype.updatePreparation = function(gameState) | ||||
{ | { | ||||
// the completing step is used to return resources and regroup the units | // the completing step is used to return resources and regroup the units | ||||
// so we check that we have no more forced order before starting the attack | // so we check that we have no more forced order before starting the attack | ||||
if (this.state == "completing") | if (this.state == "completing") | ||||
{ | { | ||||
// if our target was destroyed, go back to "unexecuted" state | // if our target was destroyed, go back to "unexecuted" state | ||||
if (this.targetPlayer === undefined || !this.target || !gameState.getEntityById(this.target.id())) | if (this.targetPlayer === undefined || !this.target || !gameState.getEntityById(this.target.id())) | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 139 Lines • ▼ Show 20 Lines | for (let ent of this.unitCollection.values()) | ||||
{ | { | ||||
ent.setMetadata(PlayerID, "plan", -1); | ent.setMetadata(PlayerID, "plan", -1); | ||||
continue; | continue; | ||||
} | } | ||||
ent.setMetadata(PlayerID, "role", "attack"); | ent.setMetadata(PlayerID, "role", "attack"); | ||||
ent.setMetadata(PlayerID, "subrole", "completing"); | ent.setMetadata(PlayerID, "subrole", "completing"); | ||||
let queued = false; | let queued = false; | ||||
if (ent.resourceCarrying() && ent.resourceCarrying().length) | if (ent.resourceCarrying() && ent.resourceCarrying().length) | ||||
queued = m.returnResources(gameState, ent); | queued = PETRA.returnResources(gameState, ent); | ||||
let index = m.getLandAccess(gameState, ent); | let index = PETRA.getLandAccess(gameState, ent); | ||||
if (index == rallyIndex) | if (index == rallyIndex) | ||||
ent.moveToRange(rallyPoint[0], rallyPoint[1], 0, 15, queued); | ent.moveToRange(rallyPoint[0], rallyPoint[1], 0, 15, queued); | ||||
else | else | ||||
gameState.ai.HQ.navalManager.requireTransport(gameState, ent, index, rallyIndex, rallyPoint); | gameState.ai.HQ.navalManager.requireTransport(gameState, ent, index, rallyIndex, rallyPoint); | ||||
} | } | ||||
// reset all queued units | // reset all queued units | ||||
let plan = this.name; | let plan = this.name; | ||||
gameState.ai.queueManager.removeQueue("plan_" + plan); | gameState.ai.queueManager.removeQueue("plan_" + plan); | ||||
gameState.ai.queueManager.removeQueue("plan_" + plan + "_champ"); | gameState.ai.queueManager.removeQueue("plan_" + plan + "_champ"); | ||||
gameState.ai.queueManager.removeQueue("plan_" + plan + "_siege"); | gameState.ai.queueManager.removeQueue("plan_" + plan + "_siege"); | ||||
return 1; | return 1; | ||||
}; | }; | ||||
m.AttackPlan.prototype.trainMoreUnits = function(gameState) | PETRA.AttackPlan.prototype.trainMoreUnits = function(gameState) | ||||
{ | { | ||||
// let's sort by training advancement, ie 'current size / target size' | // let's sort by training advancement, ie 'current size / target size' | ||||
// count the number of queued units too. | // count the number of queued units too. | ||||
// substract priority. | // substract priority. | ||||
for (let order of this.buildOrders) | for (let order of this.buildOrders) | ||||
{ | { | ||||
let special = "Plan_" + this.name + "_" + order[4]; | let special = "Plan_" + this.name + "_" + order[4]; | ||||
let aQueued = gameState.countOwnQueuedEntitiesWithMetadata("special", special); | let aQueued = gameState.countOwnQueuedEntitiesWithMetadata("special", special); | ||||
▲ Show 20 Lines • Show All 57 Lines • ▼ Show 20 Lines | if (queue.length() <= 5) | ||||
else | else | ||||
{ | { | ||||
if (this.Config.debug > 2) | if (this.Config.debug > 2) | ||||
API3.warn("attack template " + template + " added for plan " + this.name); | API3.warn("attack template " + template + " added for plan " + this.name); | ||||
let max = firstOrder[3].batchSize; | let max = firstOrder[3].batchSize; | ||||
let specialData = "Plan_" + this.name + "_" + firstOrder[4]; | let specialData = "Plan_" + this.name + "_" + firstOrder[4]; | ||||
let data = { "plan": this.name, "special": specialData, "base": 0 }; | let data = { "plan": this.name, "special": specialData, "base": 0 }; | ||||
data.role = gameState.getTemplate(template).hasClass("CitizenSoldier") ? "worker" : "attack"; | data.role = gameState.getTemplate(template).hasClass("CitizenSoldier") ? "worker" : "attack"; | ||||
let trainingPlan = new m.TrainingPlan(gameState, template, data, max, max); | let trainingPlan = new PETRA.TrainingPlan(gameState, template, data, max, max); | ||||
if (trainingPlan.template) | if (trainingPlan.template) | ||||
queue.addPlan(trainingPlan); | queue.addPlan(trainingPlan); | ||||
else if (this.Config.debug > 1) | else if (this.Config.debug > 1) | ||||
API3.warn("training plan canceled because no template for " + template + " build1 " + uneval(firstOrder[1]) + | API3.warn("training plan canceled because no template for " + template + " build1 " + uneval(firstOrder[1]) + | ||||
" build3 " + uneval(firstOrder[3].interests)); | " build3 " + uneval(firstOrder[3].interests)); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
}; | }; | ||||
m.AttackPlan.prototype.assignUnits = function(gameState) | PETRA.AttackPlan.prototype.assignUnits = function(gameState) | ||||
{ | { | ||||
let plan = this.name; | let plan = this.name; | ||||
let added = false; | let added = false; | ||||
// If we can not build units, assign all available except those affected to allied defense to the current attack | // If we can not build units, assign all available except those affected to allied defense to the current attack | ||||
if (!this.canBuildUnits) | if (!this.canBuildUnits) | ||||
{ | { | ||||
for (let ent of gameState.getOwnUnits().values()) | for (let ent of gameState.getOwnUnits().values()) | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 57 Lines • ▼ Show 20 Lines | for (let ent of gameState.getOwnEntitiesByRole("worker", true).values()) | ||||
if (!ent.hasClass("CitizenSoldier") || !this.isAvailableUnit(gameState, ent)) | if (!ent.hasClass("CitizenSoldier") || !this.isAvailableUnit(gameState, ent)) | ||||
continue; | continue; | ||||
let baseID = ent.getMetadata(PlayerID, "base"); | let baseID = ent.getMetadata(PlayerID, "base"); | ||||
if (baseID) | if (baseID) | ||||
numbase[baseID] = numbase[baseID] ? ++numbase[baseID] : 1; | numbase[baseID] = numbase[baseID] ? ++numbase[baseID] : 1; | ||||
else | else | ||||
{ | { | ||||
API3.warn("Petra problem ent without base "); | API3.warn("Petra problem ent without base "); | ||||
m.dumpEntity(ent); | PETRA.dumpEntity(ent); | ||||
continue; | continue; | ||||
} | } | ||||
if (num++ < keep || numbase[baseID] < 5) | if (num++ < keep || numbase[baseID] < 5) | ||||
continue; | continue; | ||||
if (this.type != "Rush" && ent.getMetadata(PlayerID, "subrole") != "idle") | if (this.type != "Rush" && ent.getMetadata(PlayerID, "subrole") != "idle") | ||||
continue; | continue; | ||||
ent.setMetadata(PlayerID, "plan", plan); | ent.setMetadata(PlayerID, "plan", plan); | ||||
this.unitCollection.updateEnt(ent); | this.unitCollection.updateEnt(ent); | ||||
added = true; | added = true; | ||||
} | } | ||||
return added; | return added; | ||||
}; | }; | ||||
m.AttackPlan.prototype.isAvailableUnit = function(gameState, ent) | PETRA.AttackPlan.prototype.isAvailableUnit = function(gameState, ent) | ||||
{ | { | ||||
if (!ent.position()) | if (!ent.position()) | ||||
return false; | return false; | ||||
if (ent.getMetadata(PlayerID, "plan") !== undefined && ent.getMetadata(PlayerID, "plan") !== -1 || | if (ent.getMetadata(PlayerID, "plan") !== undefined && ent.getMetadata(PlayerID, "plan") !== -1 || | ||||
ent.getMetadata(PlayerID, "transport") !== undefined || ent.getMetadata(PlayerID, "transporter") !== undefined) | ent.getMetadata(PlayerID, "transport") !== undefined || ent.getMetadata(PlayerID, "transporter") !== undefined) | ||||
return false; | return false; | ||||
if (gameState.ai.HQ.victoryManager.criticalEnts.has(ent.id()) && (this.overseas || ent.healthLevel() < 0.8)) | if (gameState.ai.HQ.victoryManager.criticalEnts.has(ent.id()) && (this.overseas || ent.healthLevel() < 0.8)) | ||||
return false; | return false; | ||||
return true; | return true; | ||||
}; | }; | ||||
/** Reassign one (at each turn) Cav unit to fasten raid preparation. */ | /** Reassign one (at each turn) Cav unit to fasten raid preparation. */ | ||||
m.AttackPlan.prototype.reassignCavUnit = function(gameState) | PETRA.AttackPlan.prototype.reassignCavUnit = function(gameState) | ||||
{ | { | ||||
for (let ent of this.unitCollection.values()) | for (let ent of this.unitCollection.values()) | ||||
{ | { | ||||
if (!ent.position() || ent.getMetadata(PlayerID, "transport") !== undefined) | if (!ent.position() || ent.getMetadata(PlayerID, "transport") !== undefined) | ||||
continue; | continue; | ||||
if (!ent.hasClass("Cavalry") || !ent.hasClass("CitizenSoldier")) | if (!ent.hasClass("Cavalry") || !ent.hasClass("CitizenSoldier")) | ||||
continue; | continue; | ||||
let raid = gameState.ai.HQ.attackManager.getAttackInPreparation("Raid"); | let raid = gameState.ai.HQ.attackManager.getAttackInPreparation("Raid"); | ||||
ent.setMetadata(PlayerID, "plan", raid.name); | ent.setMetadata(PlayerID, "plan", raid.name); | ||||
this.unitCollection.updateEnt(ent); | this.unitCollection.updateEnt(ent); | ||||
raid.unitCollection.updateEnt(ent); | raid.unitCollection.updateEnt(ent); | ||||
return; | return; | ||||
} | } | ||||
}; | }; | ||||
m.AttackPlan.prototype.chooseTarget = function(gameState) | PETRA.AttackPlan.prototype.chooseTarget = function(gameState) | ||||
{ | { | ||||
if (this.targetPlayer === undefined) | if (this.targetPlayer === undefined) | ||||
{ | { | ||||
this.targetPlayer = gameState.ai.HQ.attackManager.getEnemyPlayer(gameState, this); | this.targetPlayer = gameState.ai.HQ.attackManager.getEnemyPlayer(gameState, this); | ||||
if (this.targetPlayer === undefined) | if (this.targetPlayer === undefined) | ||||
return false; | return false; | ||||
} | } | ||||
this.target = this.getNearestTarget(gameState, this.rallyPoint); | this.target = this.getNearestTarget(gameState, this.rallyPoint); | ||||
if (!this.target) | if (!this.target) | ||||
{ | { | ||||
if (this.uniqueTargetId) | if (this.uniqueTargetId) | ||||
return false; | return false; | ||||
// may-be all our previous enemey target (if not recomputed here) have been destroyed ? | // may-be all our previous enemey target (if not recomputed here) have been destroyed ? | ||||
this.targetPlayer = gameState.ai.HQ.attackManager.getEnemyPlayer(gameState, this); | this.targetPlayer = gameState.ai.HQ.attackManager.getEnemyPlayer(gameState, this); | ||||
if (this.targetPlayer !== undefined) | if (this.targetPlayer !== undefined) | ||||
this.target = this.getNearestTarget(gameState, this.rallyPoint); | this.target = this.getNearestTarget(gameState, this.rallyPoint); | ||||
if (!this.target) | if (!this.target) | ||||
return false; | return false; | ||||
} | } | ||||
this.targetPos = this.target.position(); | this.targetPos = this.target.position(); | ||||
// redefine a new rally point for this target if we have a base on the same land | // redefine a new rally point for this target if we have a base on the same land | ||||
// find a new one on the pseudo-nearest base (dist weighted by the size of the island) | // find a new one on the pseudo-nearest base (dist weighted by the size of the island) | ||||
let targetIndex = m.getLandAccess(gameState, this.target); | let targetIndex = PETRA.getLandAccess(gameState, this.target); | ||||
let rallyIndex = gameState.ai.accessibility.getAccessValue(this.rallyPoint); | let rallyIndex = gameState.ai.accessibility.getAccessValue(this.rallyPoint); | ||||
if (targetIndex != rallyIndex) | if (targetIndex != rallyIndex) | ||||
{ | { | ||||
let distminSame = Math.min(); | let distminSame = Math.min(); | ||||
let rallySame; | let rallySame; | ||||
let distminDiff = Math.min(); | let distminDiff = Math.min(); | ||||
let rallyDiff; | let rallyDiff; | ||||
for (let base of gameState.ai.HQ.baseManagers) | for (let base of gameState.ai.HQ.baseManagers) | ||||
▲ Show 20 Lines • Show All 45 Lines • ▼ Show 20 Lines | PETRA.AttackPlan.prototype.chooseTarget = function(gameState) | ||||
else if (this.overseas) | else if (this.overseas) | ||||
this.overseas = 0; | this.overseas = 0; | ||||
return true; | return true; | ||||
}; | }; | ||||
/** | /** | ||||
* sameLand true means that we look for a target for which we do not need to take a transport | * sameLand true means that we look for a target for which we do not need to take a transport | ||||
*/ | */ | ||||
m.AttackPlan.prototype.getNearestTarget = function(gameState, position, sameLand) | PETRA.AttackPlan.prototype.getNearestTarget = function(gameState, position, sameLand) | ||||
{ | { | ||||
this.isBlocked = false; | this.isBlocked = false; | ||||
// Temporary variables needed by isValidTarget | // Temporary variables needed by isValidTarget | ||||
this.gameState = gameState; | this.gameState = gameState; | ||||
this.sameLand = sameLand && sameLand > 1 ? sameLand : false; | this.sameLand = sameLand && sameLand > 1 ? sameLand : false; | ||||
let targets; | let targets; | ||||
if (this.uniqueTargetId) | if (this.uniqueTargetId) | ||||
▲ Show 20 Lines • Show All 55 Lines • ▼ Show 20 Lines | PETRA.AttackPlan.prototype.getNearestTarget = function(gameState, position, sameLand) | ||||
this.targetPlayer = target.owner(); | this.targetPlayer = target.owner(); | ||||
return target; | return target; | ||||
}; | }; | ||||
/** | /** | ||||
* Default target finder aims for conquest critical targets | * Default target finder aims for conquest critical targets | ||||
* We must apply the *same* selection (isValidTarget) as done in getNearestTarget | * We must apply the *same* selection (isValidTarget) as done in getNearestTarget | ||||
*/ | */ | ||||
m.AttackPlan.prototype.defaultTargetFinder = function(gameState, playerEnemy) | PETRA.AttackPlan.prototype.defaultTargetFinder = function(gameState, playerEnemy) | ||||
{ | { | ||||
let targets = new API3.EntityCollection(gameState.sharedScript); | let targets = new API3.EntityCollection(gameState.sharedScript); | ||||
if (gameState.getVictoryConditions().has("wonder")) | if (gameState.getVictoryConditions().has("wonder")) | ||||
for (let ent of gameState.getEnemyStructures(playerEnemy).filter(API3.Filters.byClass("Wonder")).values()) | for (let ent of gameState.getEnemyStructures(playerEnemy).filter(API3.Filters.byClass("Wonder")).values()) | ||||
targets.addEnt(ent); | targets.addEnt(ent); | ||||
if (gameState.getVictoryConditions().has("regicide")) | if (gameState.getVictoryConditions().has("regicide")) | ||||
for (let ent of gameState.getEnemyUnits(playerEnemy).filter(API3.Filters.byClass("Hero")).values()) | for (let ent of gameState.getEnemyUnits(playerEnemy).filter(API3.Filters.byClass("Hero")).values()) | ||||
targets.addEnt(ent); | targets.addEnt(ent); | ||||
Show All 16 Lines | PETRA.AttackPlan.prototype.defaultTargetFinder = function(gameState, playerEnemy) | ||||
// No buildings, attack anything conquest critical, units included. | // No buildings, attack anything conquest critical, units included. | ||||
// TODO Should add naval attacks against the last remaining ships. | // TODO Should add naval attacks against the last remaining ships. | ||||
if (!targets.hasEntities()) | if (!targets.hasEntities()) | ||||
targets = gameState.getEntities(playerEnemy).filter(API3.Filters.byClass("ConquestCritical")). | targets = gameState.getEntities(playerEnemy).filter(API3.Filters.byClass("ConquestCritical")). | ||||
filter(API3.Filters.not(API3.Filters.byClass("Ship"))); | filter(API3.Filters.not(API3.Filters.byClass("Ship"))); | ||||
return targets; | return targets; | ||||
}; | }; | ||||
m.AttackPlan.prototype.isValidTarget = function(ent) | PETRA.AttackPlan.prototype.isValidTarget = function(ent) | ||||
{ | { | ||||
if (!ent.position()) | if (!ent.position()) | ||||
return false; | return false; | ||||
if (this.sameLand && m.getLandAccess(this.gameState, ent) != this.sameLand) | if (this.sameLand && PETRA.getLandAccess(this.gameState, ent) != this.sameLand) | ||||
return false; | return false; | ||||
return !ent.decaying() || ent.getDefaultArrow() || ent.isGarrisonHolder() && ent.garrisoned().length; | return !ent.decaying() || ent.getDefaultArrow() || ent.isGarrisonHolder() && ent.garrisoned().length; | ||||
}; | }; | ||||
/** Rush target finder aims at isolated non-defended buildings */ | /** Rush target finder aims at isolated non-defended buildings */ | ||||
m.AttackPlan.prototype.rushTargetFinder = function(gameState, playerEnemy) | PETRA.AttackPlan.prototype.rushTargetFinder = function(gameState, playerEnemy) | ||||
{ | { | ||||
let targets = new API3.EntityCollection(gameState.sharedScript); | let targets = new API3.EntityCollection(gameState.sharedScript); | ||||
let buildings; | let buildings; | ||||
if (playerEnemy !== undefined) | if (playerEnemy !== undefined) | ||||
buildings = gameState.getEnemyStructures(playerEnemy).toEntityArray(); | buildings = gameState.getEnemyStructures(playerEnemy).toEntityArray(); | ||||
else | else | ||||
buildings = gameState.getEnemyStructures().toEntityArray(); | buildings = gameState.getEnemyStructures().toEntityArray(); | ||||
if (!buildings.length) | if (!buildings.length) | ||||
Show All 39 Lines | PETRA.AttackPlan.prototype.rushTargetFinder = function(gameState, playerEnemy) | ||||
if (!targets.hasEntities() && this.type == "Rush" && playerEnemy) | if (!targets.hasEntities() && this.type == "Rush" && playerEnemy) | ||||
targets = this.rushTargetFinder(gameState); | targets = this.rushTargetFinder(gameState); | ||||
return targets; | return targets; | ||||
}; | }; | ||||
/** Raid target finder aims at destructing foundations from which our defenseManager has attacked the builders */ | /** Raid target finder aims at destructing foundations from which our defenseManager has attacked the builders */ | ||||
m.AttackPlan.prototype.raidTargetFinder = function(gameState) | PETRA.AttackPlan.prototype.raidTargetFinder = function(gameState) | ||||
{ | { | ||||
let targets = new API3.EntityCollection(gameState.sharedScript); | let targets = new API3.EntityCollection(gameState.sharedScript); | ||||
for (let targetId of gameState.ai.HQ.defenseManager.targetList) | for (let targetId of gameState.ai.HQ.defenseManager.targetList) | ||||
{ | { | ||||
let target = gameState.getEntityById(targetId); | let target = gameState.getEntityById(targetId); | ||||
if (target && target.position()) | if (target && target.position()) | ||||
targets.addEnt(target); | targets.addEnt(target); | ||||
} | } | ||||
return targets; | return targets; | ||||
}; | }; | ||||
/** | /** | ||||
* Check that we can have a path to this target | * Check that we can have a path to this target | ||||
* otherwise we may be blocked by walls and try to react accordingly | * otherwise we may be blocked by walls and try to react accordingly | ||||
* This is done only when attacker and target are on the same land | * This is done only when attacker and target are on the same land | ||||
*/ | */ | ||||
m.AttackPlan.prototype.checkTargetObstruction = function(gameState, target, position) | PETRA.AttackPlan.prototype.checkTargetObstruction = function(gameState, target, position) | ||||
{ | { | ||||
if (m.getLandAccess(gameState, target) != gameState.ai.accessibility.getAccessValue(position)) | if (PETRA.getLandAccess(gameState, target) != gameState.ai.accessibility.getAccessValue(position)) | ||||
return target; | return target; | ||||
let targetPos = target.position(); | let targetPos = target.position(); | ||||
let startPos = { "x": position[0], "y": position[1] }; | let startPos = { "x": position[0], "y": position[1] }; | ||||
let endPos = { "x": targetPos[0], "y": targetPos[1] }; | let endPos = { "x": targetPos[0], "y": targetPos[1] }; | ||||
let blocker; | let blocker; | ||||
let path = Engine.ComputePath(startPos, endPos, gameState.getPassabilityClassMask("default")); | let path = Engine.ComputePath(startPos, endPos, gameState.getPassabilityClassMask("default")); | ||||
if (!path.length) | if (!path.length) | ||||
▲ Show 20 Lines • Show All 96 Lines • ▼ Show 20 Lines | /* } | ||||
{ | { | ||||
this.isBlocked = true; | this.isBlocked = true; | ||||
return blocker; | return blocker; | ||||
} | } | ||||
return target; | return target; | ||||
}; | }; | ||||
m.AttackPlan.prototype.getPathToTarget = function(gameState, fixedRallyPoint = false) | PETRA.AttackPlan.prototype.getPathToTarget = function(gameState, fixedRallyPoint = false) | ||||
{ | { | ||||
let startAccess = gameState.ai.accessibility.getAccessValue(this.rallyPoint); | let startAccess = gameState.ai.accessibility.getAccessValue(this.rallyPoint); | ||||
let endAccess = m.getLandAccess(gameState, this.target); | let endAccess = PETRA.getLandAccess(gameState, this.target); | ||||
if (startAccess != endAccess) | if (startAccess != endAccess) | ||||
return false; | return false; | ||||
Engine.ProfileStart("AI Compute path"); | Engine.ProfileStart("AI Compute path"); | ||||
let startPos = { "x": this.rallyPoint[0], "y": this.rallyPoint[1] }; | let startPos = { "x": this.rallyPoint[0], "y": this.rallyPoint[1] }; | ||||
let endPos = { "x": this.targetPos[0], "y": this.targetPos[1] }; | let endPos = { "x": this.targetPos[0], "y": this.targetPos[1] }; | ||||
let path = Engine.ComputePath(startPos, endPos, gameState.getPassabilityClassMask("large")); | let path = Engine.ComputePath(startPos, endPos, gameState.getPassabilityClassMask("large")); | ||||
this.path = []; | this.path = []; | ||||
this.path.push(this.targetPos); | this.path.push(this.targetPos); | ||||
for (let p in path) | for (let p in path) | ||||
this.path.push([path[p].x, path[p].y]); | this.path.push([path[p].x, path[p].y]); | ||||
this.path.push(this.rallyPoint); | this.path.push(this.rallyPoint); | ||||
this.path.reverse(); | this.path.reverse(); | ||||
// Change the rally point to something useful | // Change the rally point to something useful | ||||
if (!fixedRallyPoint) | if (!fixedRallyPoint) | ||||
this.setRallyPoint(gameState); | this.setRallyPoint(gameState); | ||||
Engine.ProfileStop(); | Engine.ProfileStop(); | ||||
return true; | return true; | ||||
}; | }; | ||||
/** Set rally point at the border of our territory */ | /** Set rally point at the border of our territory */ | ||||
m.AttackPlan.prototype.setRallyPoint = function(gameState) | PETRA.AttackPlan.prototype.setRallyPoint = function(gameState) | ||||
{ | { | ||||
for (let i = 0; i < this.path.length; ++i) | for (let i = 0; i < this.path.length; ++i) | ||||
{ | { | ||||
if (gameState.ai.HQ.territoryMap.getOwner(this.path[i]) === PlayerID) | if (gameState.ai.HQ.territoryMap.getOwner(this.path[i]) === PlayerID) | ||||
continue; | continue; | ||||
if (i === 0) | if (i === 0) | ||||
this.rallyPoint = this.path[0]; | this.rallyPoint = this.path[0]; | ||||
Show All 10 Lines | for (let i = 0; i < this.path.length; ++i) | ||||
break; | break; | ||||
} | } | ||||
}; | }; | ||||
/** | /** | ||||
* Executes the attack plan, after this is executed the update function will be run every turn | * Executes the attack plan, after this is executed the update function will be run every turn | ||||
* If we're here, it's because we have enough units. | * If we're here, it's because we have enough units. | ||||
*/ | */ | ||||
m.AttackPlan.prototype.StartAttack = function(gameState) | PETRA.AttackPlan.prototype.StartAttack = function(gameState) | ||||
{ | { | ||||
if (this.Config.debug > 1) | if (this.Config.debug > 1) | ||||
API3.warn("start attack " + this.name + " with type " + this.type); | API3.warn("start attack " + this.name + " with type " + this.type); | ||||
// if our target was destroyed during preparation, choose a new one | // if our target was destroyed during preparation, choose a new one | ||||
if ((this.targetPlayer === undefined || !this.target || !gameState.getEntityById(this.target.id())) && | if ((this.targetPlayer === undefined || !this.target || !gameState.getEntityById(this.target.id())) && | ||||
!this.chooseTarget(gameState)) | !this.chooseTarget(gameState)) | ||||
return false; | return false; | ||||
// erase our queue. This will stop any leftover unit from being trained. | // erase our queue. This will stop any leftover unit from being trained. | ||||
gameState.ai.queueManager.removeQueue("plan_" + this.name); | gameState.ai.queueManager.removeQueue("plan_" + this.name); | ||||
gameState.ai.queueManager.removeQueue("plan_" + this.name + "_champ"); | gameState.ai.queueManager.removeQueue("plan_" + this.name + "_champ"); | ||||
gameState.ai.queueManager.removeQueue("plan_" + this.name + "_siege"); | gameState.ai.queueManager.removeQueue("plan_" + this.name + "_siege"); | ||||
for (let ent of this.unitCollection.values()) | for (let ent of this.unitCollection.values()) | ||||
{ | { | ||||
ent.setMetadata(PlayerID, "subrole", "walking"); | ent.setMetadata(PlayerID, "subrole", "walking"); | ||||
let stance = ent.isPackable() ? "standground" : "aggressive"; | let stance = ent.isPackable() ? "standground" : "aggressive"; | ||||
if (ent.getStance() != stance) | if (ent.getStance() != stance) | ||||
ent.setStance(stance); | ent.setStance(stance); | ||||
} | } | ||||
let rallyAccess = gameState.ai.accessibility.getAccessValue(this.rallyPoint); | let rallyAccess = gameState.ai.accessibility.getAccessValue(this.rallyPoint); | ||||
let targetAccess = m.getLandAccess(gameState, this.target); | let targetAccess = PETRA.getLandAccess(gameState, this.target); | ||||
if (rallyAccess == targetAccess) | if (rallyAccess == targetAccess) | ||||
{ | { | ||||
if (!this.path) | if (!this.path) | ||||
this.getPathToTarget(gameState, true); | this.getPathToTarget(gameState, true); | ||||
if (!this.path || !this.path[0][0] || !this.path[0][1]) | if (!this.path || !this.path[0][0] || !this.path[0][1]) | ||||
return false; | return false; | ||||
this.overseas = 0; | this.overseas = 0; | ||||
this.state = "walking"; | this.state = "walking"; | ||||
Show All 9 Lines | else | ||||
// and put back its state to "walking" when the transport is finished | // and put back its state to "walking" when the transport is finished | ||||
for (let ent of this.unitCollection.values()) | for (let ent of this.unitCollection.values()) | ||||
gameState.ai.HQ.navalManager.requireTransport(gameState, ent, rallyAccess, targetAccess, this.targetPos); | gameState.ai.HQ.navalManager.requireTransport(gameState, ent, rallyAccess, targetAccess, this.targetPos); | ||||
} | } | ||||
return true; | return true; | ||||
}; | }; | ||||
/** Runs every turn after the attack is executed */ | /** Runs every turn after the attack is executed */ | ||||
m.AttackPlan.prototype.update = function(gameState, events) | PETRA.AttackPlan.prototype.update = function(gameState, events) | ||||
{ | { | ||||
if (!this.unitCollection.hasEntities()) | if (!this.unitCollection.hasEntities()) | ||||
return 0; | return 0; | ||||
Engine.ProfileStart("Update Attack"); | Engine.ProfileStart("Update Attack"); | ||||
this.position = this.unitCollection.getCentrePosition(); | this.position = this.unitCollection.getCentrePosition(); | ||||
▲ Show 20 Lines • Show All 48 Lines • ▼ Show 20 Lines | for (let evt of events.Attacked) | ||||
let ourUnit = gameState.getEntityById(evt.target); | let ourUnit = gameState.getEntityById(evt.target); | ||||
if (!ourUnit || !attacker || !attacker.position()) | if (!ourUnit || !attacker || !attacker.position()) | ||||
continue; | continue; | ||||
if (!attacker.hasClass("Unit")) | if (!attacker.hasClass("Unit")) | ||||
{ | { | ||||
attackedByStructure[evt.target] = true; | attackedByStructure[evt.target] = true; | ||||
continue; | continue; | ||||
} | } | ||||
if (m.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 (m.isSiegeUnit(ent)) // needed as mauryan elephants are not filtered out | if (PETRA.isSiegeUnit(ent)) // needed as mauryan elephants are not filtered out | ||||
continue; | continue; | ||||
ent.attack(attacker.id(), m.allowCapture(gameState, ent, attacker)); | ent.attack(attacker.id(), PETRA.allowCapture(gameState, ent, attacker)); | ||||
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 (m.isSiegeUnit(attacker) && attacker.hasClass("Melee") && ourUnit.hasClass("Melee")) | if (PETRA.isSiegeUnit(attacker) && attacker.hasClass("Melee") && ourUnit.hasClass("Melee")) | ||||
{ | { | ||||
ourUnit.attack(attacker.id(), m.allowCapture(gameState, ourUnit, attacker)); | ourUnit.attack(attacker.id(), PETRA.allowCapture(gameState, ourUnit, attacker)); | ||||
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 (m.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()) | ||||
{ | { | ||||
ent.attack(attacker.id(), m.allowCapture(gameState, ent, attacker)); | ent.attack(attacker.id(), PETRA.allowCapture(gameState, ent, attacker)); | ||||
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()) | ||||
{ | { | ||||
if (m.isSiegeUnit(ent)) | if (PETRA.isSiegeUnit(ent)) | ||||
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(), m.allowCapture(gameState, ent, attacker)); | ent.attack(attacker.id(), PETRA.allowCapture(gameState, ent, attacker)); | ||||
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; | ||||
} | } | ||||
} | } | ||||
ourUnit.attack(attacker.id(), m.allowCapture(gameState, ourUnit, attacker)); | ourUnit.attack(attacker.id(), PETRA.allowCapture(gameState, ourUnit, attacker)); | ||||
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); | ||||
// Count the number of times an enemy is targeted, to prevent all units to follow the same target | // Count the number of times an enemy is targeted, to prevent all units to follow the same target | ||||
let unitTargets = {}; | let unitTargets = {}; | ||||
for (let ent of this.unitCollection.values()) | for (let ent of this.unitCollection.values()) | ||||
{ | { | ||||
if (ent.hasClass("Ship")) // TODO What to do with ships | if (ent.hasClass("Ship")) // TODO What to do with ships | ||||
continue; | continue; | ||||
let orderData = ent.unitAIOrderData(); | let orderData = ent.unitAIOrderData(); | ||||
if (!orderData || !orderData.length || !orderData[0].target) | if (!orderData || !orderData.length || !orderData[0].target) | ||||
continue; | continue; | ||||
let targetId = orderData[0].target; | let targetId = orderData[0].target; | ||||
let target = gameState.getEntityById(targetId); | let target = gameState.getEntityById(targetId); | ||||
if (!target || target.hasClass("Structure")) | if (!target || target.hasClass("Structure")) | ||||
continue; | continue; | ||||
if (!(targetId in unitTargets)) | if (!(targetId in unitTargets)) | ||||
{ | { | ||||
if (m.isSiegeUnit(target) || target.hasClass("Hero")) | if (PETRA.isSiegeUnit(target) || target.hasClass("Hero")) | ||||
unitTargets[targetId] = -8; | unitTargets[targetId] = -8; | ||||
else if (target.hasClass("Champion") || target.hasClass("Ship")) | else if (target.hasClass("Champion") || target.hasClass("Ship")) | ||||
unitTargets[targetId] = -5; | unitTargets[targetId] = -5; | ||||
else | else | ||||
unitTargets[targetId] = -3; | unitTargets[targetId] = -3; | ||||
} | } | ||||
++unitTargets[targetId]; | ++unitTargets[targetId]; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 44 Lines • ▼ Show 20 Lines | for (let check = 0; check < lgth; check++) | ||||
let targetId; | let targetId; | ||||
let orderData = ent.unitAIOrderData(); | let orderData = ent.unitAIOrderData(); | ||||
if (orderData && orderData.length && orderData[0].target) | if (orderData && orderData.length && orderData[0].target) | ||||
targetId = orderData[0].target; | targetId = orderData[0].target; | ||||
// update the order if needed | // update the order if needed | ||||
let needsUpdate = false; | let needsUpdate = false; | ||||
let maybeUpdate = false; | let maybeUpdate = false; | ||||
let siegeUnit = m.isSiegeUnit(ent); | let siegeUnit = PETRA.isSiegeUnit(ent); | ||||
if (ent.isIdle()) | if (ent.isIdle()) | ||||
needsUpdate = true; | needsUpdate = true; | ||||
else if (siegeUnit && targetId) | else if (siegeUnit && targetId) | ||||
{ | { | ||||
let target = gameState.getEntityById(targetId); | let target = gameState.getEntityById(targetId); | ||||
if (!target || gameState.isPlayerAlly(target.owner())) | if (!target || gameState.isPlayerAlly(target.owner())) | ||||
needsUpdate = true; | needsUpdate = true; | ||||
else if (unitTargets[targetId] && unitTargets[targetId] > 0) | else if (unitTargets[targetId] && unitTargets[targetId] > 0) | ||||
▲ Show 20 Lines • Show All 45 Lines • ▼ Show 20 Lines | for (let check = 0; check < lgth; check++) | ||||
else | else | ||||
range = 10; | range = 10; | ||||
} | } | ||||
else if (attackTypes && attackTypes.indexOf("Ranged") !== -1) | else if (attackTypes && attackTypes.indexOf("Ranged") !== -1) | ||||
range = 30 + ent.attackRange("Ranged").max; | range = 30 + ent.attackRange("Ranged").max; | ||||
else if (ent.hasClass("Cavalry")) | else if (ent.hasClass("Cavalry")) | ||||
range += 30; | range += 30; | ||||
range = range * range; | range = range * range; | ||||
let entAccess = m.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() || enemy.hasClass("StoneWall") && !ent.canAttackClass("StoneWall")) | if (!enemy.position() || enemy.hasClass("StoneWall") && !ent.canAttackClass("StoneWall")) | ||||
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 (m.getLandAccess(gameState, enemy) != entAccess) | if (PETRA.getLandAccess(gameState, enemy) != entAccess) | ||||
return false; | return false; | ||||
return true; | return true; | ||||
}).toEntityArray(); | }).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("Gates") && ent.canAttackClass("StoneWall")) | if (structa.hasClass("Gates") && ent.canAttackClass("StoneWall")) | ||||
vala += 10000; | vala += 10000; | ||||
else if (structa.hasDefensiveFire()) | else if (structa.hasDefensiveFire()) | ||||
vala += 1000; | vala += 1000; | ||||
else if (structa.hasClass("ConquestCritical")) | else if (structa.hasClass("ConquestCritical")) | ||||
vala += 200; | vala += 200; | ||||
let valb = structb.costSum(); | let valb = structb.costSum(); | ||||
if (structb.hasClass("Gates") && ent.canAttackClass("StoneWall")) | if (structb.hasClass("Gates") && ent.canAttackClass("StoneWall")) | ||||
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("Gates")) | if (mStruct[0].hasClass("Gates")) | ||||
ent.attack(mStruct[0].id(), m.allowCapture(gameState, ent, mStruct[0])); | ent.attack(mStruct[0].id(), PETRA.allowCapture(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(), m.allowCapture(gameState, ent, mStruct[rand])); | ent.attack(mStruct[rand].id(), PETRA.allowCapture(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); | ||||
Show All 10 Lines | for (let check = 0; check < lgth; check++) | ||||
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; | ||||
if (m.getLandAccess(gameState, enemy) != entAccess) | if (PETRA.getLandAccess(gameState, enemy) != entAccess) | ||||
return false; | return false; | ||||
// if already too much units targeting this enemy, let's continue towards our main target | // if already too much units targeting this enemy, let's continue towards our main target | ||||
if (veto[enemy.id()] && API3.SquareVectorDistance(this.targetPos, ent.position()) > 2500) | if (veto[enemy.id()] && API3.SquareVectorDistance(this.targetPos, ent.position()) > 2500) | ||||
return false; | return false; | ||||
enemy.setMetadata(PlayerID, "distance", Math.sqrt(dist)); | enemy.setMetadata(PlayerID, "distance", Math.sqrt(dist)); | ||||
return true; | return true; | ||||
}, this).toEntityArray(); | }, this).toEntityArray(); | ||||
if (mUnit.length) | if (mUnit.length) | ||||
Show All 14 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(), m.allowCapture(gameState, ent, mUnit[rand])); | ent.attack(mUnit[rand].id(), PETRA.allowCapture(gameState, ent, mUnit[rand])); | ||||
} | } | ||||
else if (this.isBlocked) | else if (this.isBlocked) | ||||
ent.attack(this.target.id(), false); | ent.attack(this.target.id(), false); | ||||
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 | ||||
{ | { | ||||
Show All 10 Lines | for (let check = 0; check < lgth; check++) | ||||
{ | { | ||||
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() || enemy.hasClass("StoneWall") && !ent.canAttackClass("StoneWall")) | if (!enemy.position() || enemy.hasClass("StoneWall") && !ent.canAttackClass("StoneWall")) | ||||
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 (m.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("Gates") && ent.canAttackClass("StoneWall")) | if (structa.hasClass("Gates") && ent.canAttackClass("StoneWall")) | ||||
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("Gates") && ent.canAttackClass("StoneWall")) | if (structb.hasClass("Gates") && ent.canAttackClass("StoneWall")) | ||||
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("Gates")) | if (mStruct[0].hasClass("Gates")) | ||||
ent.attack(mStruct[0].id(), false); | ent.attack(mStruct[0].id(), false); | ||||
else | else | ||||
{ | { | ||||
let rand = randIntExclusive(0, mStruct.length * 0.2); | let rand = randIntExclusive(0, mStruct.length * 0.2); | ||||
ent.attack(mStruct[rand].id(), m.allowCapture(gameState, ent, mStruct[rand])); | ent.attack(mStruct[rand].id(), PETRA.allowCapture(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; | ||||
if (!gameState.getEntityById(unit.unitAIOrderData()[0].target)) | if (!gameState.getEntityById(unit.unitAIOrderData()[0].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; | ||||
attacker = gameState.getEntityById(unit.unitAIOrderData()[0].target); | attacker = gameState.getEntityById(unit.unitAIOrderData()[0].target); | ||||
}); | }); | ||||
if (attacker) | if (attacker) | ||||
ent.attack(attacker.id(), m.allowCapture(gameState, ent, attacker)); | ent.attack(attacker.id(), PETRA.allowCapture(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 | ||||
if (this.target && this.target.owner() === 0 && this.targetPlayer !== 0) | if (this.target && this.target.owner() === 0 && this.targetPlayer !== 0) | ||||
this.target = undefined; | this.target = undefined; | ||||
} | } | ||||
this.lastPosition = this.position; | this.lastPosition = this.position; | ||||
Engine.ProfileStop(); | Engine.ProfileStop(); | ||||
return this.unitCollection.length; | return this.unitCollection.length; | ||||
}; | }; | ||||
m.AttackPlan.prototype.UpdateTransporting = function(gameState, events) | PETRA.AttackPlan.prototype.UpdateTransporting = function(gameState, events) | ||||
{ | { | ||||
let done = true; | let done = true; | ||||
for (let ent of this.unitCollection.values()) | for (let ent of this.unitCollection.values()) | ||||
{ | { | ||||
if (this.Config.debug > 1 && ent.getMetadata(PlayerID, "transport") !== undefined) | if (this.Config.debug > 1 && ent.getMetadata(PlayerID, "transport") !== undefined) | ||||
Engine.PostCommand(PlayerID, { "type": "set-shading-color", "entities": [ent.id()], "rgb": [2, 2, 0] }); | Engine.PostCommand(PlayerID, { "type": "set-shading-color", "entities": [ent.id()], "rgb": [2, 2, 0] }); | ||||
else if (this.Config.debug > 1) | else if (this.Config.debug > 1) | ||||
Engine.PostCommand(PlayerID, { "type": "set-shading-color", "entities": [ent.id()], "rgb": [1, 1, 1] }); | Engine.PostCommand(PlayerID, { "type": "set-shading-color", "entities": [ent.id()], "rgb": [1, 1, 1] }); | ||||
Show All 18 Lines | for (let evt of events.Attacked) | ||||
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; | ||||
if (!ent.isIdle()) | if (!ent.isIdle()) | ||||
continue; | continue; | ||||
ent.attack(attacker.id(), m.allowCapture(gameState, ent, attacker)); | ent.attack(attacker.id(), PETRA.allowCapture(gameState, ent, attacker)); | ||||
} | } | ||||
break; | break; | ||||
} | } | ||||
}; | }; | ||||
m.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 | ||||
// Let's check if any of our unit has been attacked. | // Let's check if any of our unit has been attacked. | ||||
// In case yes, we'll determine if we're simply off against an enemy army, a lone unit/building | // In case yes, we'll determine if we're simply off against an enemy army, a lone unit/building | ||||
// or if we reached the enemy base. Different plans may react differently. | // or if we reached the enemy base. Different plans may react differently. | ||||
let attackedNB = 0; | let attackedNB = 0; | ||||
let attackedUnitNB = 0; | let attackedUnitNB = 0; | ||||
for (let evt of events.Attacked) | for (let evt of events.Attacked) | ||||
▲ Show 20 Lines • Show All 86 Lines • ▼ Show 20 Lines | else | ||||
this.state = "arrived"; | this.state = "arrived"; | ||||
return true; | return true; | ||||
} | } | ||||
} | } | ||||
return true; | return true; | ||||
}; | }; | ||||
m.AttackPlan.prototype.UpdateTarget = function(gameState) | PETRA.AttackPlan.prototype.UpdateTarget = function(gameState) | ||||
{ | { | ||||
// First update the target position in case it's a unit (and check if it has garrisoned) | // First update the target position in case it's a unit (and check if it has garrisoned) | ||||
if (this.target && this.target.hasClass("Unit")) | if (this.target && this.target.hasClass("Unit")) | ||||
{ | { | ||||
this.targetPos = this.target.position(); | this.targetPos = this.target.position(); | ||||
if (!this.targetPos) | if (!this.targetPos) | ||||
{ | { | ||||
let holder = m.getHolder(gameState, this.target); | let holder = PETRA.getHolder(gameState, this.target); | ||||
if (holder && gameState.isPlayerEnemy(holder.owner())) | if (holder && gameState.isPlayerEnemy(holder.owner())) | ||||
{ | { | ||||
this.target = holder; | this.target = holder; | ||||
this.targetPos = holder.position(); | this.targetPos = holder.position(); | ||||
} | } | ||||
else | else | ||||
this.target = undefined; | this.target = undefined; | ||||
} | } | ||||
Show All 28 Lines | if (!this.target) | ||||
{ | { | ||||
for (let attack of attackManager.startedAttacks[attackType]) | for (let attack of attackManager.startedAttacks[attackType]) | ||||
{ | { | ||||
if (attack.name == this.name) | if (attack.name == this.name) | ||||
continue; | continue; | ||||
if (!attack.target || !gameState.getEntityById(attack.target.id()) || | if (!attack.target || !gameState.getEntityById(attack.target.id()) || | ||||
!gameState.isPlayerEnemy(attack.target.owner())) | !gameState.isPlayerEnemy(attack.target.owner())) | ||||
continue; | continue; | ||||
if (accessIndex != m.getLandAccess(gameState, attack.target)) | if (accessIndex != PETRA.getLandAccess(gameState, attack.target)) | ||||
continue; | continue; | ||||
if (attack.target.owner() == 0 && attack.targetPlayer != 0) // looks like it has resigned | if (attack.target.owner() == 0 && attack.targetPlayer != 0) // looks like it has resigned | ||||
continue; | continue; | ||||
if (!gameState.isPlayerEnemy(attack.targetPlayer)) | if (!gameState.isPlayerEnemy(attack.targetPlayer)) | ||||
continue; | continue; | ||||
this.target = attack.target; | this.target = attack.target; | ||||
this.targetPlayer = attack.targetPlayer; | this.targetPlayer = attack.targetPlayer; | ||||
this.targetPos = this.target.position(); | this.targetPos = this.target.position(); | ||||
Show All 18 Lines | if (!this.target) | ||||
API3.warn("We will help one of our other attacks"); | API3.warn("We will help one of our other attacks"); | ||||
} | } | ||||
this.targetPos = this.target.position(); | this.targetPos = this.target.position(); | ||||
} | } | ||||
return true; | return true; | ||||
}; | }; | ||||
/** reset any units */ | /** reset any units */ | ||||
m.AttackPlan.prototype.Abort = function(gameState) | PETRA.AttackPlan.prototype.Abort = function(gameState) | ||||
{ | { | ||||
this.unitCollection.unregister(); | this.unitCollection.unregister(); | ||||
if (this.unitCollection.hasEntities()) | if (this.unitCollection.hasEntities()) | ||||
{ | { | ||||
// If the attack was started, look for a good rallyPoint to withdraw | // If the attack was started, look for a good rallyPoint to withdraw | ||||
let rallyPoint; | let rallyPoint; | ||||
if (this.isStarted()) | if (this.isStarted()) | ||||
{ | { | ||||
let access = this.getAttackAccess(gameState); | let access = this.getAttackAccess(gameState); | ||||
let dist = Math.min(); | let dist = Math.min(); | ||||
if (this.rallyPoint && gameState.ai.accessibility.getAccessValue(this.rallyPoint) == access) | if (this.rallyPoint && gameState.ai.accessibility.getAccessValue(this.rallyPoint) == access) | ||||
{ | { | ||||
rallyPoint = this.rallyPoint; | rallyPoint = this.rallyPoint; | ||||
dist = API3.SquareVectorDistance(this.position, rallyPoint); | dist = API3.SquareVectorDistance(this.position, rallyPoint); | ||||
} | } | ||||
// Then check if we have a nearer base (in case this attack has captured one) | // Then check if we have a nearer base (in case this attack has captured one) | ||||
for (let base of gameState.ai.HQ.baseManagers) | for (let base of gameState.ai.HQ.baseManagers) | ||||
{ | { | ||||
if (!base.anchor || !base.anchor.position()) | if (!base.anchor || !base.anchor.position()) | ||||
continue; | continue; | ||||
if (m.getLandAccess(gameState, base.anchor) != access) | if (PETRA.getLandAccess(gameState, base.anchor) != access) | ||||
continue; | continue; | ||||
let newdist = API3.SquareVectorDistance(this.position, base.anchor.position()); | let newdist = API3.SquareVectorDistance(this.position, base.anchor.position()); | ||||
if (newdist > dist) | if (newdist > dist) | ||||
continue; | continue; | ||||
dist = newdist; | dist = newdist; | ||||
rallyPoint = base.anchor.position(); | rallyPoint = base.anchor.position(); | ||||
} | } | ||||
} | } | ||||
Show All 11 Lines | PETRA.AttackPlan.prototype.Abort = function(gameState) | ||||
for (let unitCat in this.unitStat) | for (let unitCat in this.unitStat) | ||||
this.unit[unitCat].unregister(); | this.unit[unitCat].unregister(); | ||||
gameState.ai.queueManager.removeQueue("plan_" + this.name); | gameState.ai.queueManager.removeQueue("plan_" + this.name); | ||||
gameState.ai.queueManager.removeQueue("plan_" + this.name + "_champ"); | gameState.ai.queueManager.removeQueue("plan_" + this.name + "_champ"); | ||||
gameState.ai.queueManager.removeQueue("plan_" + this.name + "_siege"); | gameState.ai.queueManager.removeQueue("plan_" + this.name + "_siege"); | ||||
}; | }; | ||||
m.AttackPlan.prototype.removeUnit = function(ent, update) | PETRA.AttackPlan.prototype.removeUnit = function(ent, update) | ||||
{ | { | ||||
if (ent.getMetadata(PlayerID, "role") == "attack") | if (ent.getMetadata(PlayerID, "role") == "attack") | ||||
{ | { | ||||
if (ent.hasClass("CitizenSoldier")) | if (ent.hasClass("CitizenSoldier")) | ||||
ent.setMetadata(PlayerID, "role", "worker"); | ent.setMetadata(PlayerID, "role", "worker"); | ||||
else | else | ||||
ent.setMetadata(PlayerID, "role", undefined); | ent.setMetadata(PlayerID, "role", undefined); | ||||
ent.setMetadata(PlayerID, "subrole", undefined); | ent.setMetadata(PlayerID, "subrole", undefined); | ||||
} | } | ||||
ent.setMetadata(PlayerID, "plan", -1); | ent.setMetadata(PlayerID, "plan", -1); | ||||
if (update) | if (update) | ||||
this.unitCollection.updateEnt(ent); | this.unitCollection.updateEnt(ent); | ||||
}; | }; | ||||
m.AttackPlan.prototype.checkEvents = function(gameState, events) | PETRA.AttackPlan.prototype.checkEvents = function(gameState, events) | ||||
{ | { | ||||
for (let evt of events.EntityRenamed) | for (let evt of events.EntityRenamed) | ||||
{ | { | ||||
if (!this.target || this.target.id() != evt.entity) | if (!this.target || this.target.id() != evt.entity) | ||||
continue; | continue; | ||||
if (this.type == "Raid" && !this.isStarted()) | if (this.type == "Raid" && !this.isStarted()) | ||||
this.target = undefined; | this.target = undefined; | ||||
else | else | ||||
Show All 19 Lines | PETRA.AttackPlan.prototype.checkEvents = function(gameState, events) | ||||
// let's check if an enemy has built a structure at our access | // let's check if an enemy has built a structure at our access | ||||
for (let evt of events.Create) | for (let evt of events.Create) | ||||
{ | { | ||||
let ent = gameState.getEntityById(evt.entity); | let ent = gameState.getEntityById(evt.entity); | ||||
if (!ent || !ent.position() || !ent.hasClass("Structure")) | if (!ent || !ent.position() || !ent.hasClass("Structure")) | ||||
continue; | continue; | ||||
if (!gameState.isPlayerEnemy(ent.owner())) | if (!gameState.isPlayerEnemy(ent.owner())) | ||||
continue; | continue; | ||||
let access = m.getLandAccess(gameState, ent); | let access = PETRA.getLandAccess(gameState, ent); | ||||
for (let base of gameState.ai.HQ.baseManagers) | for (let base of gameState.ai.HQ.baseManagers) | ||||
{ | { | ||||
if (!base.anchor || !base.anchor.position()) | if (!base.anchor || !base.anchor.position()) | ||||
continue; | continue; | ||||
if (base.accessIndex != access) | if (base.accessIndex != access) | ||||
continue; | continue; | ||||
this.overseas = 0; | this.overseas = 0; | ||||
this.rallyPoint = base.anchor.position(); | this.rallyPoint = base.anchor.position(); | ||||
} | } | ||||
} | } | ||||
}; | }; | ||||
m.AttackPlan.prototype.waitingForTransport = function() | PETRA.AttackPlan.prototype.waitingForTransport = function() | ||||
{ | { | ||||
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) | ||||
return true; | return true; | ||||
return false; | return false; | ||||
}; | }; | ||||
m.AttackPlan.prototype.hasSiegeUnits = function() | PETRA.AttackPlan.prototype.hasSiegeUnits = function() | ||||
{ | { | ||||
for (let ent of this.unitCollection.values()) | for (let ent of this.unitCollection.values()) | ||||
if (m.isSiegeUnit(ent)) | if (PETRA.isSiegeUnit(ent)) | ||||
return true; | return true; | ||||
return false; | return false; | ||||
}; | }; | ||||
m.AttackPlan.prototype.hasForceOrder = function(data, value) | PETRA.AttackPlan.prototype.hasForceOrder = function(data, value) | ||||
{ | { | ||||
for (let ent of this.unitCollection.values()) | for (let ent of this.unitCollection.values()) | ||||
{ | { | ||||
if (data && +ent.getMetadata(PlayerID, data) !== value) | if (data && +ent.getMetadata(PlayerID, data) !== value) | ||||
continue; | continue; | ||||
let orders = ent.unitAIOrderData(); | let orders = ent.unitAIOrderData(); | ||||
for (let order of orders) | for (let order of orders) | ||||
if (order.force) | if (order.force) | ||||
return true; | return true; | ||||
} | } | ||||
return false; | return false; | ||||
}; | }; | ||||
/** | /** | ||||
* The center position of this attack may be in an inaccessible area. So we use the access | * The center position of this attack may be in an inaccessible area. So we use the access | ||||
* of the unit nearest to this center position. | * of the unit nearest to this center position. | ||||
*/ | */ | ||||
m.AttackPlan.prototype.getAttackAccess = function(gameState) | PETRA.AttackPlan.prototype.getAttackAccess = function(gameState) | ||||
{ | { | ||||
for (let ent of this.unitCollection.filterNearest(this.position, 1).values()) | for (let ent of this.unitCollection.filterNearest(this.position, 1).values()) | ||||
return m.getLandAccess(gameState, ent); | return PETRA.getLandAccess(gameState, ent); | ||||
return 0; | return 0; | ||||
}; | }; | ||||
m.AttackPlan.prototype.debugAttack = function() | PETRA.AttackPlan.prototype.debugAttack = function() | ||||
{ | { | ||||
API3.warn("---------- attack " + this.name); | API3.warn("---------- attack " + this.name); | ||||
for (let unitCat in this.unitStat) | for (let unitCat in this.unitStat) | ||||
{ | { | ||||
let Unit = this.unitStat[unitCat]; | let Unit = this.unitStat[unitCat]; | ||||
API3.warn(unitCat + " num=" + this.unit[unitCat].length + " min=" + Unit.minSize + " need=" + Unit.targetSize); | API3.warn(unitCat + " num=" + this.unit[unitCat].length + " min=" + Unit.minSize + " need=" + Unit.targetSize); | ||||
} | } | ||||
API3.warn("------------------------------"); | API3.warn("------------------------------"); | ||||
}; | }; | ||||
m.AttackPlan.prototype.Serialize = function() | PETRA.AttackPlan.prototype.Serialize = function() | ||||
{ | { | ||||
let properties = { | let properties = { | ||||
"name": this.name, | "name": this.name, | ||||
"type": this.type, | "type": this.type, | ||||
"state": this.state, | "state": this.state, | ||||
"forced": this.forced, | "forced": this.forced, | ||||
"rallyPoint": this.rallyPoint, | "rallyPoint": this.rallyPoint, | ||||
"overseas": this.overseas, | "overseas": this.overseas, | ||||
Show All 11 Lines | let properties = { | ||||
"targetPos": this.targetPos, | "targetPos": this.targetPos, | ||||
"uniqueTargetId": this.uniqueTargetId, | "uniqueTargetId": this.uniqueTargetId, | ||||
"path": this.path | "path": this.path | ||||
}; | }; | ||||
return { "properties": properties }; | return { "properties": properties }; | ||||
}; | }; | ||||
m.AttackPlan.prototype.Deserialize = function(gameState, data) | PETRA.AttackPlan.prototype.Deserialize = function(gameState, data) | ||||
{ | { | ||||
for (let key in data.properties) | for (let key in data.properties) | ||||
this[key] = data.properties[key]; | this[key] = data.properties[key]; | ||||
if (this.target) | if (this.target) | ||||
this.target = gameState.getEntityById(this.target); | this.target = gameState.getEntityById(this.target); | ||||
this.failed = undefined; | this.failed = undefined; | ||||
}; | }; | ||||
return m; | |||||
}(PETRA); |
Wildfire Games · Phabricator