Changeset View
Standalone View
binaries/data/mods/public/simulation/ai/petra/attackPlan.js
/** | /** | |||||||||||||
* 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. | |||||||||||||
*/ | */ | |||||||||||||
PETRA.AttackPlan = function(gameState, Config, uniqueID, type, data) | PETRA.AttackPlan = function(gameState, Config, uniqueID, type, data) | |||||||||||||
Stan: Interesting that JavaScript allows you to have optional parameters before non optional ones… | ||||||||||||||
Done Inline ActionsAll variables are optional in JS. ;P Freagarach: All variables are optional in JS. ;P | ||||||||||||||
{ | { | |||||||||||||
this.Config = Config; | this.Config = Config; | |||||||||||||
this.name = uniqueID; | this.name = uniqueID; | |||||||||||||
this.type = type || "Attack"; | this.type = type || PETRA.AttackPlan.TYPE_DEFAULT; | |||||||||||||
this.state = "unexecuted"; | this.state = PETRA.AttackPlan.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) | |||||||||||||
{ | { | |||||||||||||
this.target = data.target; | this.target = data.target; | |||||||||||||
this.targetPos = this.target.position(); | this.targetPos = this.target.position(); | |||||||||||||
this.targetPlayer = this.target.owner(); | this.targetPlayer = this.target.owner(); | |||||||||||||
} | } | |||||||||||||
▲ Show 20 Lines • Show All 87 Lines • ▼ Show 20 Lines | PETRA.AttackPlan = function(gameState, Config, uniqueID, type, data) | |||||||||||||
// if not, this is a "bonus". The higher the priority, the faster this unit will get built. | // if not, this is a "bonus". The higher the priority, the faster this unit will get built. | |||||||||||||
// Should really be clamped to [0.1-1.5] (assuming 1 is default/the norm) | // Should really be clamped to [0.1-1.5] (assuming 1 is default/the norm) | |||||||||||||
// Eg: if all are priority 1, and the siege is 0.5, the siege units will get built | // Eg: if all are priority 1, and the siege is 0.5, the siege units will get built | |||||||||||||
// only once every other category is at least 50% of its target size. | // only once every other category is at least 50% of its target size. | |||||||||||||
// note: siege build order is currently added by the military manager if a fortress is there. | // note: siege build order is currently added by the military manager if a fortress is there. | |||||||||||||
this.unitStat = {}; | this.unitStat = {}; | |||||||||||||
// neededShips is the minimal number of ships which should be available for transport | // neededShips is the minimal number of ships which should be available for transport | |||||||||||||
if (type == "Rush") | if (type == PETRA.AttackPlan.TYPE_RUSH) | |||||||||||||
{ | { | |||||||||||||
priority = 250; | priority = 250; | |||||||||||||
Done Inline Actionsnot sure if this is really needed, I would not replace this, @Freagarach your opinion? Silier: not sure if this is really needed, I would not replace this, @Freagarach your opinion? | ||||||||||||||
Done Inline ActionsSince we end up on values that are not in the enum, I would say we should not replace this. Freagarach: Since we end up on values that are not in the enum, I would say we should not replace this. | ||||||||||||||
this.unitStat.Infantry = { "priority": 1, "minSize": 10, "targetSize": 20, "batchSize": 2, "classes": ["Infantry"], | this.unitStat.Infantry = { "priority": 1, "minSize": 10, "targetSize": 20, "batchSize": 2, "classes": ["Infantry"], | |||||||||||||
"interests": [["strength", 1], ["costsResource", 0.5, "stone"], ["costsResource", 0.6, "metal"]] }; | "interests": [["strength", 1], ["costsResource", 0.5, "stone"], ["costsResource", 0.6, "metal"]] }; | |||||||||||||
this.unitStat.FastMoving = { "priority": 1, "minSize": 2, "targetSize": 4, "batchSize": 2, "classes": ["FastMoving+CitizenSoldier"], | this.unitStat.FastMoving = { "priority": 1, "minSize": 2, "targetSize": 4, "batchSize": 2, "classes": ["FastMoving+CitizenSoldier"], | |||||||||||||
"interests": [["strength", 1]] }; | "interests": [["strength", 1]] }; | |||||||||||||
if (data && data.targetSize) | if (data && data.targetSize) | |||||||||||||
this.unitStat.Infantry.targetSize = data.targetSize; | this.unitStat.Infantry.targetSize = data.targetSize; | |||||||||||||
this.neededShips = 1; | this.neededShips = 1; | |||||||||||||
} | } | |||||||||||||
else if (type == "Raid") | else if (type == PETRA.AttackPlan.TYPE_RAID) | |||||||||||||
{ | { | |||||||||||||
priority = 150; | priority = 150; | |||||||||||||
this.unitStat.FastMoving = { "priority": 1, "minSize": 3, "targetSize": 4, "batchSize": 2, "classes": ["FastMoving+CitizenSoldier"], | this.unitStat.FastMoving = { "priority": 1, "minSize": 3, "targetSize": 4, "batchSize": 2, "classes": ["FastMoving+CitizenSoldier"], | |||||||||||||
"interests": [ ["strength", 1] ] }; | "interests": [ ["strength", 1] ] }; | |||||||||||||
this.neededShips = 1; | this.neededShips = 1; | |||||||||||||
} | } | |||||||||||||
else if (type == "HugeAttack") | else if (type == PETRA.AttackPlan.TYPE_HUGE_ATTACK) | |||||||||||||
{ | { | |||||||||||||
priority = 90; | priority = 90; | |||||||||||||
// basically we want a mix of citizen soldiers so our barracks have a purpose, and champion units. | // basically we want a mix of citizen soldiers so our barracks have a purpose, and champion units. | |||||||||||||
this.unitStat.RangedInfantry = { "priority": 0.7, "minSize": 5, "targetSize": 20, "batchSize": 5, "classes": ["Infantry+Ranged+CitizenSoldier"], | this.unitStat.RangedInfantry = { "priority": 0.7, "minSize": 5, "targetSize": 20, "batchSize": 5, "classes": ["Infantry+Ranged+CitizenSoldier"], | |||||||||||||
"interests": [["strength", 3]] }; | "interests": [["strength", 3]] }; | |||||||||||||
this.unitStat.MeleeInfantry = { "priority": 0.7, "minSize": 5, "targetSize": 20, "batchSize": 5, "classes": ["Infantry+Melee+CitizenSoldier"], | this.unitStat.MeleeInfantry = { "priority": 0.7, "minSize": 5, "targetSize": 20, "batchSize": 5, "classes": ["Infantry+Melee+CitizenSoldier"], | |||||||||||||
"interests": [["strength", 3]] }; | "interests": [["strength", 3]] }; | |||||||||||||
this.unitStat.ChampRangedInfantry = { "priority": 1, "minSize": 3, "targetSize": 18, "batchSize": 3, "classes": ["Infantry+Ranged+Champion"], | this.unitStat.ChampRangedInfantry = { "priority": 1, "minSize": 3, "targetSize": 18, "batchSize": 3, "classes": ["Infantry+Ranged+Champion"], | |||||||||||||
▲ Show 20 Lines • Show All 67 Lines • ▼ Show 20 Lines | PETRA.AttackPlan = function(gameState, Config, uniqueID, type, data) | |||||||||||||
// TODO: there should probably be one queue per type of training building | // TODO: there should probably be one queue per type of training building | |||||||||||||
gameState.ai.queueManager.addQueue("plan_" + this.name, priority); | gameState.ai.queueManager.addQueue("plan_" + this.name, priority); | |||||||||||||
gameState.ai.queueManager.addQueue("plan_" + this.name +"_champ", priority+1); | gameState.ai.queueManager.addQueue("plan_" + this.name +"_champ", priority+1); | |||||||||||||
gameState.ai.queueManager.addQueue("plan_" + this.name +"_siege", priority); | gameState.ai.queueManager.addQueue("plan_" + this.name +"_siege", priority); | |||||||||||||
// each array is [ratio, [associated classes], associated EntityColl, associated unitStat, name ] | // each array is [ratio, [associated classes], associated EntityColl, associated unitStat, name ] | |||||||||||||
this.buildOrders = []; | this.buildOrders = []; | |||||||||||||
this.canBuildUnits = gameState.ai.HQ.canBuildUnits; | this.canBuildUnits = gameState.ai.HQ.canBuildUnits; | |||||||||||||
this.siegeState = 0; // 0 = not yet tested, 1 = not yet any siege trainer, 2 = siege added in build orders | this.siegeState = PETRA.AttackPlan.SIEGE_NOT_TESTED; | |||||||||||||
// some variables used during the attack | // some variables used during the attack | |||||||||||||
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; | |||||||||||||
}; | }; | |||||||||||||
PETRA.AttackPlan.PREPARATION_FAILED = 0; | ||||||||||||||
PETRA.AttackPlan.PREPARATION_KEEP_GOING = 1; | ||||||||||||||
PETRA.AttackPlan.PREPARATION_START = 2; | ||||||||||||||
/** | ||||||||||||||
* Not yet tested | ||||||||||||||
*/ | ||||||||||||||
PETRA.AttackPlan.SIEGE_NOT_TESTED = 0; | ||||||||||||||
/** | ||||||||||||||
* Not yet any siege trainer | ||||||||||||||
*/ | ||||||||||||||
PETRA.AttackPlan.SIEGE_NO_TRAINER = 1; // Doesn't seem to be used. | ||||||||||||||
/** | ||||||||||||||
* Siege added in build orders | ||||||||||||||
*/ | ||||||||||||||
PETRA.AttackPlan.SIEGE_ADDED = 2; | ||||||||||||||
PETRA.AttackPlan.STATE_UNEXECUTED = "unexecuted"; | ||||||||||||||
StanUnsubmitted Done Inline Actions@Silier & @Freagarach maybe we should replace the strings by numbers to test if it's faster. It doesn't really make sense anymore to use strings with those variable names does it? Stan: @Silier & @Freagarach maybe we should replace the strings by numbers to test if it's faster. | ||||||||||||||
JCWasmx86AuthorUnsubmitted Done Inline ActionsWe already discussed it on IRC and here: Log messages and warnings would be near unreadable, except if I would add a function variable -> string. JCWasmx86: We already discussed it on IRC and here: Log messages and warnings would be near unreadable… | ||||||||||||||
StanUnsubmitted Done Inline ActionsTechnically all variables Tthose variables have keys which are in turn strings :D Stan: Technically all variables Tthose variables have keys which are in turn strings :D | ||||||||||||||
PETRA.AttackPlan.STATE_COMPLETING = "completing"; | ||||||||||||||
PETRA.AttackPlan.STATE_ARRIVED = "arrived"; | ||||||||||||||
PETRA.AttackPlan.TYPE_DEFAULT = "Attack"; | ||||||||||||||
PETRA.AttackPlan.TYPE_HUGE_ATTACK = "HugeAttack"; | ||||||||||||||
PETRA.AttackPlan.TYPE_RAID = "Raid"; | ||||||||||||||
PETRA.AttackPlan.TYPE_RUSH = "Rush"; | ||||||||||||||
PETRA.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(); | |||||||||||||
Show All 19 Lines | ||||||||||||||
PETRA.AttackPlan.prototype.getType = function() | PETRA.AttackPlan.prototype.getType = function() | |||||||||||||
{ | { | |||||||||||||
return this.type; | return this.type; | |||||||||||||
}; | }; | |||||||||||||
PETRA.AttackPlan.prototype.isStarted = function() | PETRA.AttackPlan.prototype.isStarted = function() | |||||||||||||
{ | { | |||||||||||||
return this.state !== "unexecuted" && this.state !== "completing"; | return this.state !== PETRA.AttackPlan.STATE_UNEXECUTED && this.state !== PETRA.AttackPlan.STATE_COMPLETING; | |||||||||||||
}; | }; | |||||||||||||
PETRA.AttackPlan.prototype.isPaused = function() | PETRA.AttackPlan.prototype.isPaused = function() | |||||||||||||
{ | { | |||||||||||||
return this.paused; | return this.paused; | |||||||||||||
}; | }; | |||||||||||||
PETRA.AttackPlan.prototype.setPaused = function(boolValue) | PETRA.AttackPlan.prototype.setPaused = function(boolValue) | |||||||||||||
Show All 37 Lines | if (this.unit[unitCat].length < Unit.minSize) | |||||||||||||
MinReachedEverywhere = false; | MinReachedEverywhere = false; | |||||||||||||
break; | break; | |||||||||||||
} | } | |||||||||||||
} | } | |||||||||||||
if (MaxReachedEverywhere) | if (MaxReachedEverywhere) | |||||||||||||
return true; | return true; | |||||||||||||
if (MinReachedEverywhere) | if (MinReachedEverywhere) | |||||||||||||
return this.type == "Raid" && this.target && this.target.foundationProgress() && | return this.type == PETRA.AttackPlan.TYPE_RAID && this.target && this.target.foundationProgress() && | |||||||||||||
this.target.foundationProgress() > 50; | this.target.foundationProgress() > 50; | |||||||||||||
return false; | return false; | |||||||||||||
}; | }; | |||||||||||||
PETRA.AttackPlan.prototype.forceStart = function() | PETRA.AttackPlan.prototype.forceStart = function() | |||||||||||||
{ | { | |||||||||||||
for (let unitCat in this.unitStat) | for (let unitCat in this.unitStat) | |||||||||||||
{ | { | |||||||||||||
Show All 31 Lines | if (!this.isStarted()) | |||||||||||||
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.emptyQueues(); | this.emptyQueues(); | |||||||||||||
} | } | |||||||||||||
}; | }; | |||||||||||||
PETRA.AttackPlan.prototype.addSiegeUnits = function(gameState) | PETRA.AttackPlan.prototype.addSiegeUnits = function(gameState) | |||||||||||||
{ | { | |||||||||||||
if (this.siegeState == 2 || this.state !== "unexecuted") | if (this.siegeState == PETRA.AttackPlan.SIEGE_ADDED || this.state !== PETRA.AttackPlan.STATE_UNEXECUTED) | |||||||||||||
return false; | return false; | |||||||||||||
let civ = gameState.getPlayerCiv(); | let civ = gameState.getPlayerCiv(); | |||||||||||||
const classes = [["Siege+Melee"], ["Siege+Ranged"], ["Elephant+Melee"]]; | const classes = [["Siege+Melee"], ["Siege+Ranged"], ["Elephant+Melee"]]; | |||||||||||||
let hasTrainer = [false, false, false]; | let hasTrainer = [false, false, false]; | |||||||||||||
for (let ent of gameState.getOwnTrainingFacilities().values()) | for (let ent of gameState.getOwnTrainingFacilities().values()) | |||||||||||||
{ | { | |||||||||||||
let trainables = ent.trainableEntities(civ); | let trainables = ent.trainableEntities(civ); | |||||||||||||
if (!trainables) | if (!trainables) | |||||||||||||
continue; | continue; | |||||||||||||
for (let trainable of trainables) | for (let trainable of trainables) | |||||||||||||
{ | { | |||||||||||||
if (gameState.isTemplateDisabled(trainable)) | if (gameState.isTemplateDisabled(trainable)) | |||||||||||||
continue; | continue; | |||||||||||||
let template = gameState.getTemplate(trainable); | let template = gameState.getTemplate(trainable); | |||||||||||||
if (!template || !template.available(gameState)) | if (!template || !template.available(gameState)) | |||||||||||||
continue; | continue; | |||||||||||||
for (let i = 0; i < classes.length; ++i) | for (let i = 0; i < classes.length; ++i) | |||||||||||||
if (template.hasClasses(classes[i])) | if (template.hasClasses(classes[i])) | |||||||||||||
hasTrainer[i] = true; | hasTrainer[i] = true; | |||||||||||||
} | } | |||||||||||||
} | } | |||||||||||||
if (hasTrainer.every(e => !e)) | if (hasTrainer.every(e => !e)) | |||||||||||||
return false; | return false; | |||||||||||||
Not Done Inline Actions
But that is a behavioural change and should be done in another diff. Freagarach: But that is a behavioural change and should be done in another diff. | ||||||||||||||
let i = this.name % classes.length; | let i = this.name % classes.length; | |||||||||||||
for (let k = 0; k < classes.length; ++k) | for (let k = 0; k < classes.length; ++k) | |||||||||||||
{ | { | |||||||||||||
if (hasTrainer[i]) | if (hasTrainer[i]) | |||||||||||||
break; | break; | |||||||||||||
i = ++i % classes.length; | i = ++i % classes.length; | |||||||||||||
} | } | |||||||||||||
this.siegeState = 2; | this.siegeState = PETRA.AttackPlan.SIEGE_ADDED; | |||||||||||||
let targetSize; | let targetSize; | |||||||||||||
if (this.Config.difficulty < 3) | if (this.Config.difficulty < 3) | |||||||||||||
targetSize = this.type == "HugeAttack" ? Math.max(this.Config.difficulty, 1) : Math.max(this.Config.difficulty - 1, 0); | targetSize = this.type == PETRA.AttackPlan.TYPE_HUGE_ATTACK ? Math.max(this.Config.difficulty, 1) : Math.max(this.Config.difficulty - 1, 0); | |||||||||||||
else | else | |||||||||||||
targetSize = this.type == "HugeAttack" ? this.Config.difficulty + 1 : this.Config.difficulty - 1; | targetSize = this.type == PETRA.AttackPlan.TYPE_HUGE_ATTACK ? this.Config.difficulty + 1 : this.Config.difficulty - 1; | |||||||||||||
targetSize = Math.max(Math.round(this.Config.popScaling * targetSize), this.type == "HugeAttack" ? 1 : 0); | targetSize = Math.max(Math.round(this.Config.popScaling * targetSize), this.type == PETRA.AttackPlan.TYPE_HUGE_ATTACK ? 1 : 0); | |||||||||||||
if (!targetSize) | if (!targetSize) | |||||||||||||
return true; | return true; | |||||||||||||
// 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". */ | |||||||||||||
PETRA.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 == PETRA.AttackPlan.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())) | |||||||||||||
{ | { | |||||||||||||
this.state = "unexecuted"; | this.state = PETRA.AttackPlan.STATE_UNEXECUTED; | |||||||||||||
this.target = undefined; | this.target = undefined; | |||||||||||||
} | } | |||||||||||||
else | else | |||||||||||||
{ | { | |||||||||||||
// check that all units have finished with their transport if needed | // check that all units have finished with their transport if needed | |||||||||||||
if (this.waitingForTransport()) | if (this.waitingForTransport()) | |||||||||||||
return 1; | return PETRA.AttackPlan.PREPARATION_KEEP_GOING; | |||||||||||||
// bloqued units which cannot finish their order should not stop the attack | // bloqued units which cannot finish their order should not stop the attack | |||||||||||||
if (gameState.ai.elapsedTime < this.maxCompletingTime && this.hasForceOrder()) | if (gameState.ai.elapsedTime < this.maxCompletingTime && this.hasForceOrder()) | |||||||||||||
return 1; | return PETRA.AttackPlan.PREPARATION_KEEP_GOING; | |||||||||||||
return 2; | return PETRA.AttackPlan.PREPARATION_START; | |||||||||||||
} | } | |||||||||||||
} | } | |||||||||||||
if (this.Config.debug > 3 && gameState.ai.playedTurn % 50 === 0) | if (this.Config.debug > 3 && gameState.ai.playedTurn % 50 === 0) | |||||||||||||
this.debugAttack(); | this.debugAttack(); | |||||||||||||
// if we need a transport, wait for some transport ships | // if we need a transport, wait for some transport ships | |||||||||||||
if (this.overseas && !gameState.ai.HQ.navalManager.seaTransportShips[this.overseas].length) | if (this.overseas && !gameState.ai.HQ.navalManager.seaTransportShips[this.overseas].length) | |||||||||||||
return 1; | return PETRA.AttackPlan.PREPARATION_KEEP_GOING; | |||||||||||||
if (this.type != "Raid" || !this.forced) // Forced Raids have special purposes (as relic capture) | if (this.type != PETRA.AttackPlan.TYPE_RAID || !this.forced) // Forced Raids have special purposes (as relic capture) | |||||||||||||
this.assignUnits(gameState); | this.assignUnits(gameState); | |||||||||||||
if (this.type != "Raid" && gameState.ai.HQ.attackManager.getAttackInPreparation("Raid") !== undefined) | if (this.type != PETRA.AttackPlan.TYPE_RAID && gameState.ai.HQ.attackManager.getAttackInPreparation(PETRA.AttackPlan.TYPE_RAID) !== undefined) | |||||||||||||
this.reassignFastUnit(gameState); // reassign some fast units (if any) to fasten raid preparations | this.reassignFastUnit(gameState); // reassign some fast units (if any) to fasten raid preparations | |||||||||||||
// Fasten the end game. | // Fasten the end game. | |||||||||||||
if (gameState.ai.playedTurn % 5 == 0 && this.hasSiegeUnits()) | if (gameState.ai.playedTurn % 5 == 0 && this.hasSiegeUnits()) | |||||||||||||
{ | { | |||||||||||||
let totEnemies = 0; | let totEnemies = 0; | |||||||||||||
let hasEnemies = false; | let hasEnemies = false; | |||||||||||||
for (let i = 1; i < gameState.sharedScript.playersData.length; ++i) | for (let i = 1; i < gameState.sharedScript.playersData.length; ++i) | |||||||||||||
Show All 17 Lines | if (gameState.getPopulationMax() - gameState.getPopulation() < 5) | |||||||||||||
{ | { | |||||||||||||
this.emptyQueues(); | this.emptyQueues(); | |||||||||||||
} | } | |||||||||||||
else // Abort the plan so that its units will be reassigned to other plans. | else // Abort the plan so that its units will be reassigned to other plans. | |||||||||||||
{ | { | |||||||||||||
if (this.Config.debug > 1) | if (this.Config.debug > 1) | |||||||||||||
{ | { | |||||||||||||
let am = gameState.ai.HQ.attackManager; | let am = gameState.ai.HQ.attackManager; | |||||||||||||
API3.warn(" attacks upcoming: raid " + am.upcomingAttacks.Raid.length + | API3.warn(" attacks upcoming: raid " + am.upcomingAttacks[PETRA.AttackPlan.TYPE_RAID].length + | |||||||||||||
" rush " + am.upcomingAttacks.Rush.length + | " rush " + am.upcomingAttacks[PETRA.AttackPlan.TYPE_RUSH].length + | |||||||||||||
" attack " + am.upcomingAttacks.Attack.length + | " attack " + am.upcomingAttacks[PETRA.AttackPlan.TYPE_DEFAULT].length + | |||||||||||||
" huge " + am.upcomingAttacks.HugeAttack.length); | " huge " + am.upcomingAttacks[PETRA.AttackPlan.TYPE_HUGE_ATTACK].length); | |||||||||||||
API3.warn(" attacks started: raid " + am.startedAttacks.Raid.length + | API3.warn(" attacks started: raid " + am.startedAttacks[PETRA.AttackPlan.TYPE_RAID].length + | |||||||||||||
" rush " + am.startedAttacks.Rush.length + | " rush " + am.startedAttacks[PETRA.AttackPlan.TYPE_RUSH].length + | |||||||||||||
" attack " + am.startedAttacks.Attack.length + | " attack " + am.startedAttacks[PETRA.AttackPlan.TYPE_DEFAULT].length + | |||||||||||||
" huge " + am.startedAttacks.HugeAttack.length); | " huge " + am.startedAttacks[PETRA.AttackPlan.TYPE_HUGE_ATTACK].length); | |||||||||||||
Done Inline ActionsWonder if it's not less readable, but those are logs so... Stan: Wonder if it's not less readable, but those are logs so... | ||||||||||||||
} | } | |||||||||||||
return 0; | return PETRA.AttackPlan.PREPARATION_FAILED; | |||||||||||||
} | } | |||||||||||||
} | } | |||||||||||||
else if (this.mustStart()) | else if (this.mustStart()) | |||||||||||||
{ | { | |||||||||||||
if (gameState.countOwnQueuedEntitiesWithMetadata("plan", +this.name) > 0) | if (gameState.countOwnQueuedEntitiesWithMetadata("plan", +this.name) > 0) | |||||||||||||
{ | { | |||||||||||||
// keep on while the units finish being trained, then we'll start | // keep on while the units finish being trained, then we'll start | |||||||||||||
this.emptyQueues(); | this.emptyQueues(); | |||||||||||||
return 1; | return PETRA.AttackPlan.PREPARATION_KEEP_GOING; | |||||||||||||
} | } | |||||||||||||
} | } | |||||||||||||
else | else | |||||||||||||
{ | { | |||||||||||||
if (this.canBuildUnits) | if (this.canBuildUnits) | |||||||||||||
{ | { | |||||||||||||
// We still have time left to recruit units and do stuffs. | // We still have time left to recruit units and do stuffs. | |||||||||||||
if (this.siegeState == 0 || this.siegeState == 1 && gameState.ai.playedTurn % 5 == 0) | if (this.siegeState == PETRA.AttackPlan.SIEGE_NOT_TESTED || | |||||||||||||
this.siegeState == PETRA.AttackPlan.SIEGE_NO_TRAINER && gameState.ai.playedTurn % 5 == 0) | ||||||||||||||
Done Inline Actionsthis is wrong replacement, right side will always result in 1 for comparison Silier: this is wrong replacement, right side will always result in 1 for comparison | ||||||||||||||
this.addSiegeUnits(gameState); | this.addSiegeUnits(gameState); | |||||||||||||
this.trainMoreUnits(gameState); | this.trainMoreUnits(gameState); | |||||||||||||
// may happen if we have no more training facilities and build orders are canceled | // may happen if we have no more training facilities and build orders are canceled | |||||||||||||
if (!this.buildOrders.length) | if (!this.buildOrders.length) | |||||||||||||
return 0; // will abort the plan | return PETRA.AttackPlan.PREPARATION_FAILED; // will abort the plan | |||||||||||||
} | } | |||||||||||||
return 1; | return PETRA.AttackPlan.PREPARATION_KEEP_GOING; | |||||||||||||
} | } | |||||||||||||
// if we're here, it means we must start | // if we're here, it means we must start | |||||||||||||
this.state = "completing"; | this.state = PETRA.AttackPlan.STATE_COMPLETING; | |||||||||||||
// Raids have their predefined target | // Raids have their predefined target | |||||||||||||
if (!this.target && !this.chooseTarget(gameState)) | if (!this.target && !this.chooseTarget(gameState)) | |||||||||||||
return 0; | return PETRA.AttackPlan.PREPARATION_FAILED; | |||||||||||||
if (!this.overseas) | if (!this.overseas) | |||||||||||||
this.getPathToTarget(gameState); | this.getPathToTarget(gameState); | |||||||||||||
if (this.type == "Raid") | if (this.type == PETRA.AttackPlan.TYPE_RAID) | |||||||||||||
this.maxCompletingTime = this.forced ? 0 : gameState.ai.elapsedTime + 20; | this.maxCompletingTime = this.forced ? 0 : gameState.ai.elapsedTime + 20; | |||||||||||||
else | else | |||||||||||||
{ | { | |||||||||||||
if (this.type == "Rush" || this.forced) | if (this.type == PETRA.AttackPlan.TYPE_RUSH || this.forced) | |||||||||||||
this.maxCompletingTime = gameState.ai.elapsedTime + 40; | this.maxCompletingTime = gameState.ai.elapsedTime + 40; | |||||||||||||
else | else | |||||||||||||
this.maxCompletingTime = gameState.ai.elapsedTime + 60; | this.maxCompletingTime = gameState.ai.elapsedTime + 60; | |||||||||||||
// warn our allies so that they can help if possible | // warn our allies so that they can help if possible | |||||||||||||
if (!this.requested) | if (!this.requested) | |||||||||||||
Engine.PostCommand(PlayerID, { "type": "attack-request", "source": PlayerID, "player": this.targetPlayer }); | Engine.PostCommand(PlayerID, { "type": "attack-request", "source": PlayerID, "player": this.targetPlayer }); | |||||||||||||
} | } | |||||||||||||
Show All 29 Lines | for (let ent of this.unitCollection.values()) | |||||||||||||
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 | |||||||||||||
this.removeQueues(gameState); | this.removeQueues(gameState); | |||||||||||||
return 1; | return PETRA.AttackPlan.PREPARATION_KEEP_GOING; | |||||||||||||
}; | }; | |||||||||||||
PETRA.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) | |||||||||||||
▲ Show 20 Lines • Show All 88 Lines • ▼ Show 20 Lines | for (let ent of gameState.getOwnUnits().values()) | |||||||||||||
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; | |||||||||||||
} | } | |||||||||||||
if (this.type == "Raid") | if (this.type == PETRA.AttackPlan.TYPE_RAID) | |||||||||||||
{ | { | |||||||||||||
// Raids are quick attacks: assign all FastMoving soldiers except some for hunting. | // Raids are quick attacks: assign all FastMoving soldiers except some for hunting. | |||||||||||||
let num = 0; | let num = 0; | |||||||||||||
for (let ent of gameState.getOwnUnits().values()) | for (let ent of gameState.getOwnUnits().values()) | |||||||||||||
{ | { | |||||||||||||
if (!ent.hasClass("FastMoving") || !this.isAvailableUnit(gameState, ent)) | if (!ent.hasClass("FastMoving") || !this.isAvailableUnit(gameState, ent)) | |||||||||||||
continue; | continue; | |||||||||||||
if (num++ < 2) | if (num++ < 2) | |||||||||||||
Show All 29 Lines | PETRA.AttackPlan.prototype.assignUnits = function(gameState) | |||||||||||||
// Finally add also some workers for the higher difficulties, | // Finally add also some workers for the higher difficulties, | |||||||||||||
// If Rush, assign all kind of workers, keeping only a minimum number of defenders | // If Rush, assign all kind of workers, keeping only a minimum number of defenders | |||||||||||||
// Otherwise, assign only some idle workers if too much of them | // Otherwise, assign only some idle workers if too much of them | |||||||||||||
if (this.Config.difficulty <= 2) | if (this.Config.difficulty <= 2) | |||||||||||||
return added; | return added; | |||||||||||||
let num = 0; | let num = 0; | |||||||||||||
const numbase = {}; | const numbase = {}; | |||||||||||||
let keep = this.type != "Rush" ? | let keep = this.type != PETRA.AttackPlan.TYPE_RUSH ? | |||||||||||||
6 + 4 * gameState.getNumPlayerEnemies() + 8 * this.Config.personality.defensive : 8; | 6 + 4 * gameState.getNumPlayerEnemies() + 8 * this.Config.personality.defensive : 8; | |||||||||||||
keep = Math.round(this.Config.popScaling * keep); | keep = Math.round(this.Config.popScaling * keep); | |||||||||||||
for (const ent of gameState.getOwnEntitiesByRole("worker", true).values()) | for (const 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; | |||||||||||||
const baseID = ent.getMetadata(PlayerID, "base"); | const 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 "); | |||||||||||||
PETRA.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 != PETRA.AttackPlan.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; | |||||||||||||
}; | }; | |||||||||||||
Show All 13 Lines | ||||||||||||||
PETRA.AttackPlan.prototype.reassignFastUnit = function(gameState) | PETRA.AttackPlan.prototype.reassignFastUnit = 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.hasClasses(["FastMoving", "CitizenSoldier"])) | if (!ent.hasClasses(["FastMoving", "CitizenSoldier"])) | |||||||||||||
continue; | continue; | |||||||||||||
let raid = gameState.ai.HQ.attackManager.getAttackInPreparation("Raid"); | const raid = gameState.ai.HQ.attackManager.getAttackInPreparation(PETRA.AttackPlan.TYPE_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; | |||||||||||||
} | } | |||||||||||||
}; | }; | |||||||||||||
PETRA.AttackPlan.prototype.chooseTarget = function(gameState) | PETRA.AttackPlan.prototype.chooseTarget = function(gameState) | |||||||||||||
▲ Show 20 Lines • Show All 95 Lines • ▼ Show 20 Lines | PETRA.AttackPlan.prototype.getNearestTarget = function(gameState, position, sameLand) | |||||||||||||
{ | { | |||||||||||||
targets = new API3.EntityCollection(gameState.sharedScript); | targets = new API3.EntityCollection(gameState.sharedScript); | |||||||||||||
let ent = gameState.getEntityById(this.uniqueTargetId); | let ent = gameState.getEntityById(this.uniqueTargetId); | |||||||||||||
if (ent) | if (ent) | |||||||||||||
targets.addEnt(ent); | targets.addEnt(ent); | |||||||||||||
} | } | |||||||||||||
else | else | |||||||||||||
{ | { | |||||||||||||
if (this.type == "Raid") | if (this.type == PETRA.AttackPlan.TYPE_RAID) | |||||||||||||
targets = this.raidTargetFinder(gameState); | targets = this.raidTargetFinder(gameState); | |||||||||||||
else if (this.type == "Rush" || this.type == "Attack") | else if (this.type == PETRA.AttackPlan.TYPE_RUSH || this.type == PETRA.AttackPlan.TYPE_DEFAULT) | |||||||||||||
{ | { | |||||||||||||
targets = this.rushTargetFinder(gameState, this.targetPlayer); | targets = this.rushTargetFinder(gameState, this.targetPlayer); | |||||||||||||
if (!targets.hasEntities() && (this.hasSiegeUnits() || this.forced)) | if (!targets.hasEntities() && (this.hasSiegeUnits() || this.forced)) | |||||||||||||
targets = this.defaultTargetFinder(gameState, this.targetPlayer); | targets = this.defaultTargetFinder(gameState, this.targetPlayer); | |||||||||||||
} | } | |||||||||||||
else | else | |||||||||||||
targets = this.defaultTargetFinder(gameState, this.targetPlayer); | targets = this.defaultTargetFinder(gameState, this.targetPlayer); | |||||||||||||
} | } | |||||||||||||
if (!targets.hasEntities()) | if (!targets.hasEntities()) | |||||||||||||
return undefined; | return undefined; | |||||||||||||
// picking the nearest target | // picking the nearest target | |||||||||||||
let target; | let target; | |||||||||||||
let minDist = Math.min(); | let minDist = Math.min(); | |||||||||||||
for (let ent of targets.values()) | for (let ent of targets.values()) | |||||||||||||
{ | { | |||||||||||||
if (this.targetPlayer == 0 && gameState.getVictoryConditions().has("capture_the_relic") && | if (this.targetPlayer == 0 && gameState.getVictoryConditions().has("capture_the_relic") && | |||||||||||||
(!ent.hasClass("Relic") || gameState.ai.HQ.victoryManager.targetedGaiaRelics.has(ent.id()))) | (!ent.hasClass("Relic") || gameState.ai.HQ.victoryManager.targetedGaiaRelics.has(ent.id()))) | |||||||||||||
continue; | continue; | |||||||||||||
// Do not bother with some pointless targets | // Do not bother with some pointless targets | |||||||||||||
if (!this.isValidTarget(ent)) | if (!this.isValidTarget(ent)) | |||||||||||||
continue; | continue; | |||||||||||||
let dist = API3.SquareVectorDistance(ent.position(), position); | let dist = API3.SquareVectorDistance(ent.position(), position); | |||||||||||||
// In normal attacks, disfavor fields | // In normal attacks, disfavor fields | |||||||||||||
if (this.type != "Rush" && this.type != "Raid" && ent.hasClass("Field")) | if (this.type != PETRA.AttackPlan.TYPE_RUSH && this.type != PETRA.AttackPlan.TYPE_RAID && ent.hasClass("Field")) | |||||||||||||
dist += 100000; | dist += 100000; | |||||||||||||
if (dist < minDist) | if (dist < minDist) | |||||||||||||
{ | { | |||||||||||||
minDist = dist; | minDist = dist; | |||||||||||||
target = ent; | target = ent; | |||||||||||||
} | } | |||||||||||||
} | } | |||||||||||||
if (!target) | if (!target) | |||||||||||||
▲ Show 20 Lines • Show All 103 Lines • ▼ Show 20 Lines | for (let building of buildings) | |||||||||||||
if (dist > minDist) | if (dist > minDist) | |||||||||||||
continue; | continue; | |||||||||||||
minDist = dist; | minDist = dist; | |||||||||||||
target = building; | target = building; | |||||||||||||
} | } | |||||||||||||
if (target) | if (target) | |||||||||||||
targets.addEnt(target); | targets.addEnt(target); | |||||||||||||
if (!targets.hasEntities() && this.type == "Rush" && playerEnemy) | if (!targets.hasEntities() && this.type == PETRA.AttackPlan.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 */ | |||||||||||||
PETRA.AttackPlan.prototype.raidTargetFinder = function(gameState) | PETRA.AttackPlan.prototype.raidTargetFinder = function(gameState) | |||||||||||||
{ | { | |||||||||||||
▲ Show 20 Lines • Show All 234 Lines • ▼ Show 20 Lines | if (this.state == "transporting") | |||||||||||||
this.UpdateTransporting(gameState, events); | this.UpdateTransporting(gameState, events); | |||||||||||||
if (this.state == "walking" && !this.UpdateWalking(gameState, events)) | if (this.state == "walking" && !this.UpdateWalking(gameState, events)) | |||||||||||||
{ | { | |||||||||||||
Engine.ProfileStop(); | Engine.ProfileStop(); | |||||||||||||
return 0; | return 0; | |||||||||||||
} | } | |||||||||||||
if (this.state == "arrived") | if (this.state == PETRA.AttackPlan.STATE_ARRIVED) | |||||||||||||
{ | { | |||||||||||||
// let's proceed on with whatever happens now. | // let's proceed on with whatever happens now. | |||||||||||||
this.state = ""; | this.state = ""; | |||||||||||||
this.startingAttack = true; | this.startingAttack = true; | |||||||||||||
this.unitCollection.forEach(ent => { | this.unitCollection.forEach(ent => { | |||||||||||||
ent.stopMoving(); | ent.stopMoving(); | |||||||||||||
ent.setMetadata(PlayerID, "subrole", "attacking"); | ent.setMetadata(PlayerID, "subrole", "attacking"); | |||||||||||||
}); | }); | |||||||||||||
if (this.type == "Rush") // try to find a better target for rush | if (this.type == PETRA.AttackPlan.TYPE_RUSH) // try to find a better target for rush | |||||||||||||
{ | { | |||||||||||||
let newtarget = this.getNearestTarget(gameState, this.position); | let newtarget = this.getNearestTarget(gameState, this.position); | |||||||||||||
if (newtarget) | if (newtarget) | |||||||||||||
{ | { | |||||||||||||
this.target = newtarget; | this.target = newtarget; | |||||||||||||
this.targetPos = this.target.position(); | this.targetPos = this.target.position(); | |||||||||||||
} | } | |||||||||||||
} | } | |||||||||||||
▲ Show 20 Lines • Show All 139 Lines • ▼ Show 20 Lines | if (this.state == "") | |||||||||||||
} | } | |||||||||||||
let veto = {}; | let veto = {}; | |||||||||||||
for (let target in unitTargets) | for (let target in unitTargets) | |||||||||||||
if (unitTargets[target] > 0) | if (unitTargets[target] > 0) | |||||||||||||
veto[target] = true; | veto[target] = true; | |||||||||||||
let targetClassesUnit; | let targetClassesUnit; | |||||||||||||
let targetClassesSiege; | let targetClassesSiege; | |||||||||||||
if (this.type == "Rush") | if (this.type == PETRA.AttackPlan.TYPE_RUSH) | |||||||||||||
targetClassesUnit = { "attack": ["Unit", "Structure"], "avoid": ["Palisade", "Wall", "Tower", "Fortress"], "vetoEntities": veto }; | targetClassesUnit = { "attack": ["Unit", "Structure"], "avoid": ["Palisade", "Wall", "Tower", "Fortress"], "vetoEntities": veto }; | |||||||||||||
else | else | |||||||||||||
{ | { | |||||||||||||
if (this.target.hasClass("Fortress")) | if (this.target.hasClass("Fortress")) | |||||||||||||
targetClassesUnit = { "attack": ["Unit", "Structure"], "avoid": ["Palisade", "Wall"], "vetoEntities": veto }; | targetClassesUnit = { "attack": ["Unit", "Structure"], "avoid": ["Palisade", "Wall"], "vetoEntities": veto }; | |||||||||||||
else if (this.target.hasClasses(["Palisade", "Wall"])) | else if (this.target.hasClasses(["Palisade", "Wall"])) | |||||||||||||
targetClassesUnit = { "attack": ["Unit", "Structure"], "avoid": ["Fortress"], "vetoEntities": veto }; | targetClassesUnit = { "attack": ["Unit", "Structure"], "avoid": ["Fortress"], "vetoEntities": veto }; | |||||||||||||
else | else | |||||||||||||
▲ Show 20 Lines • Show All 300 Lines • ▼ Show 20 Lines | for (let ent of this.unitCollection.values()) | |||||||||||||
if (!done) | if (!done) | |||||||||||||
continue; | continue; | |||||||||||||
if (ent.getMetadata(PlayerID, "transport") !== undefined) | if (ent.getMetadata(PlayerID, "transport") !== undefined) | |||||||||||||
done = false; | done = false; | |||||||||||||
} | } | |||||||||||||
if (done) | if (done) | |||||||||||||
{ | { | |||||||||||||
this.state = "arrived"; | this.state = PETRA.AttackPlan.STATE_ARRIVED; | |||||||||||||
return; | return; | |||||||||||||
} | } | |||||||||||||
// if we are attacked while waiting the rest of the army, retaliate | // if we are attacked while waiting the rest of the army, retaliate | |||||||||||||
for (let evt of events.Attacked) | for (let evt of events.Attacked) | |||||||||||||
{ | { | |||||||||||||
if (!this.unitCollection.hasEntId(evt.target)) | if (!this.unitCollection.hasEntId(evt.target)) | |||||||||||||
continue; | continue; | |||||||||||||
Show All 34 Lines | if (attacker && (attacker.owner() !== 0 || this.targetPlayer === 0)) | |||||||||||||
attackedUnitNB++; | attackedUnitNB++; | |||||||||||||
} | } | |||||||||||||
} | } | |||||||||||||
// Are we arrived at destination ? | // Are we arrived at destination ? | |||||||||||||
if (attackedNB > 1 && (attackedUnitNB || this.hasSiegeUnits())) | if (attackedNB > 1 && (attackedUnitNB || this.hasSiegeUnits())) | |||||||||||||
{ | { | |||||||||||||
if (gameState.ai.HQ.territoryMap.getOwner(this.position) === this.targetPlayer || attackedNB > 3) | if (gameState.ai.HQ.territoryMap.getOwner(this.position) === this.targetPlayer || attackedNB > 3) | |||||||||||||
{ | { | |||||||||||||
this.state = "arrived"; | this.state = PETRA.AttackPlan.STATE_ARRIVED; | |||||||||||||
return true; | return true; | |||||||||||||
} | } | |||||||||||||
} | } | |||||||||||||
// basically haven't moved an inch: very likely stuck) | // basically haven't moved an inch: very likely stuck) | |||||||||||||
if (API3.SquareVectorDistance(this.position, this.position5TurnsAgo) < 10 && this.path.length > 0 && gameState.ai.playedTurn % 5 === 0) | if (API3.SquareVectorDistance(this.position, this.position5TurnsAgo) < 10 && this.path.length > 0 && gameState.ai.playedTurn % 5 === 0) | |||||||||||||
{ | { | |||||||||||||
// check for stuck siege units | // check for stuck siege units | |||||||||||||
Show All 23 Lines | for (let ent of gameState.getEnemyStructures().filter(API3.Filters.byClass(["Palisade", "Wall"])).values()) | |||||||||||||
if (API3.SquareVectorDistance(this.position, ent.position()) > 800) | if (API3.SquareVectorDistance(this.position, ent.position()) > 800) | |||||||||||||
continue; | continue; | |||||||||||||
let enemyClass = ent.hasClass("Wall") ? "Wall" : "Palisade"; | let enemyClass = ent.hasClass("Wall") ? "Wall" : "Palisade"; | |||||||||||||
// there are walls, so check if we can attack | // there are walls, so check if we can attack | |||||||||||||
if (this.unitCollection.filter(API3.Filters.byCanAttackClass(enemyClass)).hasEntities()) | if (this.unitCollection.filter(API3.Filters.byCanAttackClass(enemyClass)).hasEntities()) | |||||||||||||
{ | { | |||||||||||||
if (this.Config.debug > 1) | if (this.Config.debug > 1) | |||||||||||||
API3.warn("Attack Plan " + this.type + " " + this.name + " has met walls and is not happy."); | API3.warn("Attack Plan " + this.type + " " + this.name + " has met walls and is not happy."); | |||||||||||||
this.state = "arrived"; | this.state = PETRA.AttackPlan.STATE_ARRIVED; | |||||||||||||
return true; | return true; | |||||||||||||
} | } | |||||||||||||
// abort plan | // abort plan | |||||||||||||
if (this.Config.debug > 1) | if (this.Config.debug > 1) | |||||||||||||
API3.warn("Attack Plan " + this.type + " " + this.name + " has met walls and gives up."); | API3.warn("Attack Plan " + this.type + " " + this.name + " has met walls and gives up."); | |||||||||||||
return false; | return false; | |||||||||||||
} | } | |||||||||||||
// this.unitCollection.move(this.path[0][0], this.path[0][1]); | // this.unitCollection.move(this.path[0][0], this.path[0][1]); | |||||||||||||
this.unitCollection.moveIndiv(this.path[0][0], this.path[0][1]); | this.unitCollection.moveIndiv(this.path[0][0], this.path[0][1]); | |||||||||||||
} | } | |||||||||||||
// check if our units are close enough from the next waypoint. | // check if our units are close enough from the next waypoint. | |||||||||||||
if (API3.SquareVectorDistance(this.position, this.targetPos) < 10000) | if (API3.SquareVectorDistance(this.position, this.targetPos) < 10000) | |||||||||||||
{ | { | |||||||||||||
if (this.Config.debug > 1) | if (this.Config.debug > 1) | |||||||||||||
API3.warn("Attack Plan " + this.type + " " + this.name + " has arrived to destination."); | API3.warn("Attack Plan " + this.type + " " + this.name + " has arrived to destination."); | |||||||||||||
this.state = "arrived"; | this.state = PETRA.AttackPlan.STATE_ARRIVED; | |||||||||||||
return true; | return true; | |||||||||||||
} | } | |||||||||||||
else if (this.path.length && API3.SquareVectorDistance(this.position, this.path[0]) < 1600) | else if (this.path.length && API3.SquareVectorDistance(this.position, this.path[0]) < 1600) | |||||||||||||
{ | { | |||||||||||||
this.path.shift(); | this.path.shift(); | |||||||||||||
if (this.path.length) | if (this.path.length) | |||||||||||||
this.unitCollection.moveToRange(this.path[0][0], this.path[0][1], 0, 15); | this.unitCollection.moveToRange(this.path[0][0], this.path[0][1], 0, 15); | |||||||||||||
else | else | |||||||||||||
{ | { | |||||||||||||
if (this.Config.debug > 1) | if (this.Config.debug > 1) | |||||||||||||
API3.warn("Attack Plan " + this.type + " " + this.name + " has arrived to destination."); | API3.warn("Attack Plan " + this.type + " " + this.name + " has arrived to destination."); | |||||||||||||
this.state = "arrived"; | this.state = PETRA.AttackPlan.STATE_ARRIVED; | |||||||||||||
return true; | return true; | |||||||||||||
} | } | |||||||||||||
} | } | |||||||||||||
return true; | return true; | |||||||||||||
}; | }; | |||||||||||||
PETRA.AttackPlan.prototype.UpdateTarget = function(gameState) | PETRA.AttackPlan.prototype.UpdateTarget = function(gameState) | |||||||||||||
▲ Show 20 Lines • Show All 147 Lines • ▼ Show 20 Lines | ||||||||||||||
}; | }; | |||||||||||||
PETRA.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 == PETRA.AttackPlan.TYPE_RAID && !this.isStarted()) | |||||||||||||
this.target = undefined; | this.target = undefined; | |||||||||||||
else | else | |||||||||||||
this.target = gameState.getEntityById(evt.newentity); | this.target = gameState.getEntityById(evt.newentity); | |||||||||||||
if (this.target) | if (this.target) | |||||||||||||
this.targetPos = this.target.position(); | this.targetPos = this.target.position(); | |||||||||||||
} | } | |||||||||||||
for (let evt of events.OwnershipChanged) // capture event | for (let evt of events.OwnershipChanged) // capture event | |||||||||||||
if (this.target && this.target.id() == evt.entity && gameState.isPlayerAlly(evt.to)) | if (this.target && this.target.id() == evt.entity && gameState.isPlayerAlly(evt.to)) | |||||||||||||
this.target = undefined; | this.target = undefined; | |||||||||||||
for (let evt of events.PlayerDefeated) | for (let evt of events.PlayerDefeated) | |||||||||||||
{ | { | |||||||||||||
if (this.targetPlayer !== evt.playerId) | if (this.targetPlayer !== evt.playerId) | |||||||||||||
continue; | continue; | |||||||||||||
this.targetPlayer = gameState.ai.HQ.attackManager.getEnemyPlayer(gameState, this); | this.targetPlayer = gameState.ai.HQ.attackManager.getEnemyPlayer(gameState, this); | |||||||||||||
this.target = undefined; | this.target = undefined; | |||||||||||||
} | } | |||||||||||||
if (!this.overseas || this.state !== "unexecuted") | if (!this.overseas || this.state !== PETRA.AttackPlan.STATE_UNEXECUTED) | |||||||||||||
return; | return; | |||||||||||||
// 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())) | |||||||||||||
▲ Show 20 Lines • Show All 105 Lines • Show Last 20 Lines |
Interesting that JavaScript allows you to have optional parameters before non optional ones, thus kinda forcing you to always specify all of them.