1
0
forked from 0ad/0ad

[PetraAI] - Use enum-like variables instead of magic values. -- [1]

Improved readability. Easier find-and-replace.

This commit is focused on:
- BaseManager
- AttackPlan
- GarrisonManager

Patch by: @JCWasmx86
Differential revision: https://code.wildfiregames.com/D4334
Refs. #6256
Comments by: @Silier, @Stan
This was SVN commit r26029.
This commit is contained in:
Freagarach 2021-12-04 08:47:05 +00:00
parent 5a7aa37cd1
commit 5d3902498f
9 changed files with 184 additions and 127 deletions

View File

@ -9,8 +9,18 @@ PETRA.AttackManager = function(Config)
this.attackNumber = 0;
this.rushNumber = 0;
this.raidNumber = 0;
this.upcomingAttacks = { "Rush": [], "Raid": [], "Attack": [], "HugeAttack": [] };
this.startedAttacks = { "Rush": [], "Raid": [], "Attack": [], "HugeAttack": [] };
this.upcomingAttacks = {
[PETRA.AttackPlan.TYPE_RUSH]: [],
[PETRA.AttackPlan.TYPE_RAID]: [],
[PETRA.AttackPlan.TYPE_DEFAULT]: [],
[PETRA.AttackPlan.TYPE_HUGE_ATTACK]: []
};
this.startedAttacks = {
[PETRA.AttackPlan.TYPE_RUSH]: [],
[PETRA.AttackPlan.TYPE_RAID]: [],
[PETRA.AttackPlan.TYPE_DEFAULT]: [],
[PETRA.AttackPlan.TYPE_HUGE_ATTACK]: []
};
this.bombingAttacks = new Map();// Temporary attacks for siege units while waiting their current attack to start
this.debugTime = 0;
this.maxRushes = 0;
@ -63,7 +73,7 @@ PETRA.AttackManager.prototype.checkEvents = function(gameState, events)
{
for (let attack of this.upcomingAttacks[attackType])
{
if (attack.state === "completing")
if (attack.state === PETRA.AttackPlan.STATE_COMPLETING)
{
if (attack.targetPlayer === targetPlayer)
available += attack.unitCollection.length;
@ -85,7 +95,7 @@ PETRA.AttackManager.prototype.checkEvents = function(gameState, events)
{
for (let attack of this.upcomingAttacks[attackType])
{
if (attack.state === "completing" ||
if (attack.state === PETRA.AttackPlan.STATE_COMPLETING ||
attack.targetPlayer !== targetPlayer ||
attack.unitCollection.length < 3)
continue;
@ -252,8 +262,12 @@ PETRA.AttackManager.prototype.update = function(gameState, queues, events)
}
this.checkEvents(gameState, events);
let unexecutedAttacks = { "Rush": 0, "Raid": 0, "Attack": 0, "HugeAttack": 0 };
const unexecutedAttacks = {
[PETRA.AttackPlan.TYPE_RUSH]: 0,
[PETRA.AttackPlan.TYPE_RAID]: 0,
[PETRA.AttackPlan.TYPE_DEFAULT]: 0,
[PETRA.AttackPlan.TYPE_HUGE_ATTACK]: 0
};
for (let attackType in this.upcomingAttacks)
{
for (let i = 0; i < this.upcomingAttacks[attackType].length; ++i)
@ -266,20 +280,20 @@ PETRA.AttackManager.prototype.update = function(gameState, queues, events)
let updateStep = attack.updatePreparation(gameState);
// now we're gonna check if the preparation time is over
if (updateStep == 1 || attack.isPaused())
if (updateStep === PETRA.AttackPlan.PREPARATION_KEEP_GOING || attack.isPaused())
{
// just chillin'
if (attack.state == "unexecuted")
if (attack.state === PETRA.AttackPlan.STATE_UNEXECUTED)
++unexecutedAttacks[attackType];
}
else if (updateStep == 0)
else if (updateStep === PETRA.AttackPlan.PREPARATION_FAILED)
{
if (this.Config.debug > 1)
API3.warn("Attack Manager: " + attack.getType() + " plan " + attack.getName() + " aborted.");
attack.Abort(gameState);
this.upcomingAttacks[attackType].splice(i--, 1);
}
else if (updateStep == 2)
else if (updateStep === PETRA.AttackPlan.PREPARATION_START)
{
if (attack.StartAttack(gameState))
{
@ -321,30 +335,32 @@ PETRA.AttackManager.prototype.update = function(gameState, queues, events)
let barracksNb = gameState.getOwnEntitiesByClass("Barracks", true).filter(API3.Filters.isBuilt()).length;
if (this.rushNumber < this.maxRushes && barracksNb >= 1)
{
if (unexecutedAttacks.Rush === 0)
if (unexecutedAttacks[PETRA.AttackPlan.TYPE_RUSH] === 0)
{
// we have a barracks and we want to rush, rush.
let data = { "targetSize": this.rushSize[this.rushNumber] };
let attackPlan = new PETRA.AttackPlan(gameState, this.Config, this.totalNumber, "Rush", data);
const attackPlan = new PETRA.AttackPlan(gameState, this.Config, this.totalNumber, PETRA.AttackPlan.TYPE_RUSH, data);
if (!attackPlan.failed)
{
if (this.Config.debug > 1)
API3.warn("Military Manager: Rushing plan " + this.totalNumber + " with maxRushes " + this.maxRushes);
this.totalNumber++;
attackPlan.init(gameState);
this.upcomingAttacks.Rush.push(attackPlan);
this.upcomingAttacks[PETRA.AttackPlan.TYPE_RUSH].push(attackPlan);
}
this.rushNumber++;
}
}
else if (unexecutedAttacks.Attack == 0 && unexecutedAttacks.HugeAttack == 0 &&
this.startedAttacks.Attack.length + this.startedAttacks.HugeAttack.length < Math.min(2, 1 + Math.round(gameState.getPopulationMax()/100)) &&
(this.startedAttacks.Attack.length + this.startedAttacks.HugeAttack.length == 0 || gameState.getPopulationMax() - gameState.getPopulation() > 12))
else if (unexecutedAttacks[PETRA.AttackPlan.TYPE_DEFAULT] == 0 && unexecutedAttacks[PETRA.AttackManager.TYPE_HUGE_ATTACK] == 0 &&
this.startedAttacks[PETRA.AttackPlan.TYPE_DEFAULT].length + this.startedAttacks[PETRA.AttackManager.TYPE_HUGE_ATTACK].length <
Math.min(2, 1 + Math.round(gameState.getPopulationMax() / 100)) &&
(this.startedAttacks[PETRA.AttackPlan.TYPE_DEFAULT].length + this.startedAttacks[PETRA.AttackManager.TYPE_HUGE_ATTACK].length == 0 ||
gameState.getPopulationMax() - gameState.getPopulation() > 12))
{
if (barracksNb >= 1 && (gameState.currentPhase() > 1 || gameState.isResearching(gameState.getPhaseName(2))) ||
!gameState.ai.HQ.hasPotentialBase()) // if we have no base ... nothing else to do than attack
{
let type = this.attackNumber < 2 || this.startedAttacks.HugeAttack.length > 0 ? "Attack" : "HugeAttack";
const type = this.attackNumber < 2 || this.startedAttacks[PETRA.AttackPlan.TYPE_HUGE_ATTACK].length > 0 ? PETRA.AttackPlan.TYPE_DEFAULT : PETRA.AttackPlan.TYPE_HUGE_ATTACK;
let attackPlan = new PETRA.AttackPlan(gameState, this.Config, this.totalNumber, type);
if (attackPlan.failed)
this.attackPlansEncounteredWater = true; // hack
@ -360,7 +376,7 @@ PETRA.AttackManager.prototype.update = function(gameState, queues, events)
}
}
if (unexecutedAttacks.Raid === 0 && gameState.ai.HQ.defenseManager.targetList.length)
if (unexecutedAttacks[PETRA.AttackPlan.TYPE_RAID] === 0 && gameState.ai.HQ.defenseManager.targetList.length)
{
let target;
for (let targetId of gameState.ai.HQ.defenseManager.targetList)
@ -465,7 +481,7 @@ PETRA.AttackManager.prototype.getEnemyPlayer = function(gameState, attack)
for (let i in this.defeated)
veto[i] = true;
// No rush if enemy too well defended (i.e. iberians)
if (attack.type == "Rush")
if (attack.type === PETRA.AttackPlan.TYPE_RUSH)
{
for (let i = 1; i < gameState.sharedScript.playersData.length; ++i)
{
@ -484,7 +500,7 @@ PETRA.AttackManager.prototype.getEnemyPlayer = function(gameState, attack)
// then if not a huge attack, continue attacking our previous target as long as it has some entities,
// otherwise target the most accessible one
if (attack.type != "HugeAttack")
if (attack.type !== PETRA.AttackPlan.TYPE_HUGE_ATTACK)
{
if (attack.targetPlayer === undefined && this.currentEnemyPlayer !== undefined &&
!this.defeated[this.currentEnemyPlayer] &&
@ -507,7 +523,7 @@ PETRA.AttackManager.prototype.getEnemyPlayer = function(gameState, attack)
continue;
if (!gameState.isPlayerEnemy(enemycc.owner()))
continue;
if (access != PETRA.getLandAccess(gameState, enemycc))
if (access !== PETRA.getLandAccess(gameState, enemycc))
continue;
let dist = API3.SquareVectorDistance(ourPos, enemycc.position());
if (distmin && dist > distmin)
@ -640,7 +656,7 @@ PETRA.AttackManager.prototype.cancelAttacksAgainstPlayer = function(gameState, p
PETRA.AttackManager.prototype.raidTargetEntity = function(gameState, ent)
{
let data = { "target": ent };
let attackPlan = new PETRA.AttackPlan(gameState, this.Config, this.totalNumber, "Raid", data);
const attackPlan = new PETRA.AttackPlan(gameState, this.Config, this.totalNumber, PETRA.AttackPlan.TYPE_RAID, data);
if (attackPlan.failed)
return null;
if (this.Config.debug > 1)
@ -648,7 +664,7 @@ PETRA.AttackManager.prototype.raidTargetEntity = function(gameState, ent)
this.raidNumber++;
this.totalNumber++;
attackPlan.init(gameState);
this.upcomingAttacks.Raid.push(attackPlan);
this.upcomingAttacks[PETRA.AttackPlan.TYPE_RAID].push(attackPlan);
return attackPlan;
};
@ -686,7 +702,7 @@ PETRA.AttackManager.prototype.switchDefenseToAttack = function(gameState, target
}
let attackData = data.uniqueTarget ? { "uniqueTargetId": target.id() } : undefined;
let pos = target.position();
let attackType = "Attack";
const attackType = PETRA.AttackPlan.TYPE_DEFAULT;
let attackPlan = new PETRA.AttackPlan(gameState, this.Config, this.totalNumber, attackType, attackData);
if (attackPlan.failed)
return false;
@ -733,7 +749,7 @@ PETRA.AttackManager.prototype.switchDefenseToAttack = function(gameState, target
attackPlan.targetPlayer = target.owner();
attackPlan.targetPos = pos;
attackPlan.target = target;
attackPlan.state = "arrived";
attackPlan.state = PETRA.AttackPlan.STATE_ARRIVED;
return true;
};

View File

@ -4,13 +4,12 @@
* 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.
*/
PETRA.AttackPlan = function(gameState, Config, uniqueID, type, data)
PETRA.AttackPlan = function(gameState, Config, uniqueID, type = PETRA.AttackPlan.TYPE_DEFAULT, data)
{
this.Config = Config;
this.name = uniqueID;
this.type = type || "Attack";
this.state = "unexecuted";
this.type = type;
this.state = PETRA.AttackPlan.STATE_UNEXECUTED;
this.forced = false; // true when this attacked has been forced to help an ally
if (data && data.target)
@ -114,7 +113,7 @@ PETRA.AttackPlan = function(gameState, Config, uniqueID, type, data)
this.unitStat = {};
// neededShips is the minimal number of ships which should be available for transport
if (type == "Rush")
if (type === PETRA.AttackPlan.TYPE_RUSH)
{
priority = 250;
this.unitStat.Infantry = { "priority": 1, "minSize": 10, "targetSize": 20, "batchSize": 2, "classes": ["Infantry"],
@ -125,14 +124,14 @@ PETRA.AttackPlan = function(gameState, Config, uniqueID, type, data)
this.unitStat.Infantry.targetSize = data.targetSize;
this.neededShips = 1;
}
else if (type == "Raid")
else if (type === PETRA.AttackPlan.TYPE_RAID)
{
priority = 150;
this.unitStat.FastMoving = { "priority": 1, "minSize": 3, "targetSize": 4, "batchSize": 2, "classes": ["FastMoving+CitizenSoldier"],
"interests": [ ["strength", 1] ] };
this.neededShips = 1;
}
else if (type == "HugeAttack")
else if (type === PETRA.AttackPlan.TYPE_HUGE_ATTACK)
{
priority = 90;
// basically we want a mix of citizen soldiers so our barracks have a purpose, and champion units.
@ -216,7 +215,7 @@ PETRA.AttackPlan = function(gameState, Config, uniqueID, type, data)
// each array is [ratio, [associated classes], associated EntityColl, associated unitStat, name ]
this.buildOrders = [];
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
this.position5TurnsAgo = [0, 0];
@ -227,6 +226,27 @@ PETRA.AttackPlan = function(gameState, Config, uniqueID, type, data)
return true;
};
PETRA.AttackPlan.PREPARATION_FAILED = 0;
PETRA.AttackPlan.PREPARATION_KEEP_GOING = 1;
PETRA.AttackPlan.PREPARATION_START = 2;
PETRA.AttackPlan.SIEGE_NOT_TESTED = 0;
PETRA.AttackPlan.SIEGE_NO_TRAINER = 1;
/**
* Siege added in build orders
*/
PETRA.AttackPlan.SIEGE_ADDED = 2;
PETRA.AttackPlan.STATE_UNEXECUTED = "unexecuted";
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)
{
this.queue = gameState.ai.queues["plan_" + this.name];
@ -262,7 +282,7 @@ PETRA.AttackPlan.prototype.getType = 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()
@ -316,7 +336,7 @@ PETRA.AttackPlan.prototype.mustStart = function()
if (MaxReachedEverywhere)
return true;
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;
return false;
};
@ -364,7 +384,7 @@ PETRA.AttackPlan.prototype.addBuildOrder = function(gameState, name, unitStats,
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;
let civ = gameState.getPlayerCiv();
@ -397,13 +417,13 @@ PETRA.AttackPlan.prototype.addSiegeUnits = function(gameState)
i = ++i % classes.length;
}
this.siegeState = 2;
this.siegeState = PETRA.AttackPlan.SIEGE_ADDED;
let targetSize;
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
targetSize = this.type == "HugeAttack" ? this.Config.difficulty + 1 : this.Config.difficulty - 1;
targetSize = Math.max(Math.round(this.Config.popScaling * targetSize), this.type == "HugeAttack" ? 1 : 0);
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 === PETRA.AttackPlan.TYPE_HUGE_ATTACK ? 1 : 0);
if (!targetSize)
return true;
// no minsize as we don't want the plan to fail at the last minute though.
@ -418,23 +438,23 @@ PETRA.AttackPlan.prototype.updatePreparation = function(gameState)
{
// 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
if (this.state == "completing")
if (this.state === PETRA.AttackPlan.STATE_COMPLETING)
{
// if our target was destroyed, go back to "unexecuted" state
if (this.targetPlayer === undefined || !this.target || !gameState.getEntityById(this.target.id()))
{
this.state = "unexecuted";
this.state = PETRA.AttackPlan.STATE_UNEXECUTED;
this.target = undefined;
}
else
{
// check that all units have finished with their transport if needed
if (this.waitingForTransport())
return 1;
return PETRA.AttackPlan.PREPARATION_KEEP_GOING;
// bloqued units which cannot finish their order should not stop the attack
if (gameState.ai.elapsedTime < this.maxCompletingTime && this.hasForceOrder())
return 1;
return 2;
return PETRA.AttackPlan.PREPARATION_KEEP_GOING;
return PETRA.AttackPlan.PREPARATION_START;
}
}
@ -443,11 +463,11 @@ PETRA.AttackPlan.prototype.updatePreparation = function(gameState)
// if we need a transport, wait for some transport ships
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);
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
// Fasten the end game.
@ -481,16 +501,16 @@ PETRA.AttackPlan.prototype.updatePreparation = function(gameState)
if (this.Config.debug > 1)
{
let am = gameState.ai.HQ.attackManager;
API3.warn(" attacks upcoming: raid " + am.upcomingAttacks.Raid.length +
" rush " + am.upcomingAttacks.Rush.length +
" attack " + am.upcomingAttacks.Attack.length +
" huge " + am.upcomingAttacks.HugeAttack.length);
API3.warn(" attacks started: raid " + am.startedAttacks.Raid.length +
" rush " + am.startedAttacks.Rush.length +
" attack " + am.startedAttacks.Attack.length +
" huge " + am.startedAttacks.HugeAttack.length);
API3.warn(" attacks upcoming: raid " + am.upcomingAttacks[PETRA.AttackPlan.TYPE_RAID].length +
" rush " + am.upcomingAttacks[PETRA.AttackPlan.TYPE_RUSH].length +
" attack " + am.upcomingAttacks[PETRA.AttackPlan.TYPE_DEFAULT].length +
" huge " + am.upcomingAttacks[PETRA.AttackPlan.TYPE_HUGE_ATTACK].length);
API3.warn(" attacks started: raid " + am.startedAttacks[PETRA.AttackPlan.TYPE_RAID].length +
" rush " + am.startedAttacks[PETRA.AttackPlan.TYPE_RUSH].length +
" attack " + am.startedAttacks[PETRA.AttackPlan.TYPE_DEFAULT].length +
" huge " + am.startedAttacks[PETRA.AttackPlan.TYPE_HUGE_ATTACK].length);
}
return 0;
return PETRA.AttackPlan.PREPARATION_FAILED;
}
}
else if (this.mustStart())
@ -499,7 +519,7 @@ PETRA.AttackPlan.prototype.updatePreparation = function(gameState)
{
// keep on while the units finish being trained, then we'll start
this.emptyQueues();
return 1;
return PETRA.AttackPlan.PREPARATION_KEEP_GOING;
}
}
else
@ -507,30 +527,31 @@ PETRA.AttackPlan.prototype.updatePreparation = function(gameState)
if (this.canBuildUnits)
{
// 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)
this.addSiegeUnits(gameState);
this.trainMoreUnits(gameState);
// may happen if we have no more training facilities and build orders are canceled
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
this.state = "completing";
this.state = PETRA.AttackPlan.STATE_COMPLETING;
// Raids have their predefined target
if (!this.target && !this.chooseTarget(gameState))
return 0;
return PETRA.AttackPlan.PREPARATION_FAILED;
if (!this.overseas)
this.getPathToTarget(gameState);
if (this.type == "Raid")
if (this.type === PETRA.AttackPlan.TYPE_RAID)
this.maxCompletingTime = this.forced ? 0 : gameState.ai.elapsedTime + 20;
else
{
if (this.type == "Rush" || this.forced)
if (this.type === PETRA.AttackPlan.TYPE_RUSH || this.forced)
this.maxCompletingTime = gameState.ai.elapsedTime + 40;
else
this.maxCompletingTime = gameState.ai.elapsedTime + 60;
@ -576,7 +597,7 @@ PETRA.AttackPlan.prototype.updatePreparation = function(gameState)
// reset all queued units
this.removeQueues(gameState);
return 1;
return PETRA.AttackPlan.PREPARATION_KEEP_GOING;
};
PETRA.AttackPlan.prototype.trainMoreUnits = function(gameState)
@ -681,7 +702,7 @@ PETRA.AttackPlan.prototype.assignUnits = function(gameState)
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.
let num = 0;
@ -727,7 +748,7 @@ PETRA.AttackPlan.prototype.assignUnits = function(gameState)
let num = 0;
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;
keep = Math.round(this.Config.popScaling * keep);
for (const ent of gameState.getOwnEntitiesByRole("worker", true).values())
@ -745,7 +766,7 @@ PETRA.AttackPlan.prototype.assignUnits = function(gameState)
}
if (num++ < keep || numbase[baseID] < 5)
continue;
if (this.type != "Rush" && ent.getMetadata(PlayerID, "subrole") != "idle")
if (this.type !== PETRA.AttackPlan.TYPE_RUSH && ent.getMetadata(PlayerID, "subrole") != "idle")
continue;
ent.setMetadata(PlayerID, "plan", plan);
this.unitCollection.updateEnt(ent);
@ -775,7 +796,7 @@ PETRA.AttackPlan.prototype.reassignFastUnit = function(gameState)
continue;
if (!ent.hasClasses(["FastMoving", "CitizenSoldier"]))
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);
this.unitCollection.updateEnt(ent);
raid.unitCollection.updateEnt(ent);
@ -887,9 +908,9 @@ PETRA.AttackPlan.prototype.getNearestTarget = function(gameState, position, same
}
else
{
if (this.type == "Raid")
if (this.type === PETRA.AttackPlan.TYPE_RAID)
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);
if (!targets.hasEntities() && (this.hasSiegeUnits() || this.forced))
@ -914,7 +935,7 @@ PETRA.AttackPlan.prototype.getNearestTarget = function(gameState, position, same
continue;
let dist = API3.SquareVectorDistance(ent.position(), position);
// 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;
if (dist < minDist)
{
@ -1034,7 +1055,7 @@ PETRA.AttackPlan.prototype.rushTargetFinder = function(gameState, playerEnemy)
if (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);
return targets;
@ -1285,7 +1306,7 @@ PETRA.AttackPlan.prototype.update = function(gameState, events)
return 0;
}
if (this.state == "arrived")
if (this.state === PETRA.AttackPlan.STATE_ARRIVED)
{
// let's proceed on with whatever happens now.
this.state = "";
@ -1294,7 +1315,7 @@ PETRA.AttackPlan.prototype.update = function(gameState, events)
ent.stopMoving();
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);
if (newtarget)
@ -1450,7 +1471,7 @@ PETRA.AttackPlan.prototype.update = function(gameState, events)
let targetClassesUnit;
let targetClassesSiege;
if (this.type == "Rush")
if (this.type === PETRA.AttackPlan.TYPE_RUSH)
targetClassesUnit = { "attack": ["Unit", "Structure"], "avoid": ["Palisade", "Wall", "Tower", "Fortress"], "vetoEntities": veto };
else
{
@ -1767,7 +1788,7 @@ PETRA.AttackPlan.prototype.UpdateTransporting = function(gameState, events)
if (done)
{
this.state = "arrived";
this.state = PETRA.AttackPlan.STATE_ARRIVED;
return;
}
@ -1818,7 +1839,7 @@ PETRA.AttackPlan.prototype.UpdateWalking = function(gameState, events)
{
if (gameState.ai.HQ.territoryMap.getOwner(this.position) === this.targetPlayer || attackedNB > 3)
{
this.state = "arrived";
this.state = PETRA.AttackPlan.STATE_ARRIVED;
return true;
}
}
@ -1858,7 +1879,7 @@ PETRA.AttackPlan.prototype.UpdateWalking = function(gameState, events)
{
if (this.Config.debug > 1)
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;
}
// abort plan
@ -1876,7 +1897,7 @@ PETRA.AttackPlan.prototype.UpdateWalking = function(gameState, events)
{
if (this.Config.debug > 1)
API3.warn("Attack Plan " + this.type + " " + this.name + " has arrived to destination.");
this.state = "arrived";
this.state = PETRA.AttackPlan.STATE_ARRIVED;
return true;
}
else if (this.path.length && API3.SquareVectorDistance(this.position, this.path[0]) < 1600)
@ -1888,7 +1909,7 @@ PETRA.AttackPlan.prototype.UpdateWalking = function(gameState, events)
{
if (this.Config.debug > 1)
API3.warn("Attack Plan " + this.type + " " + this.name + " has arrived to destination.");
this.state = "arrived";
this.state = PETRA.AttackPlan.STATE_ARRIVED;
return true;
}
}
@ -2052,7 +2073,7 @@ PETRA.AttackPlan.prototype.checkEvents = function(gameState, events)
{
if (!this.target || this.target.id() != evt.entity)
continue;
if (this.type == "Raid" && !this.isStarted())
if (this.type === PETRA.AttackPlan.TYPE_RAID && !this.isStarted())
this.target = undefined;
else
this.target = gameState.getEntityById(evt.newentity);
@ -2072,7 +2093,7 @@ PETRA.AttackPlan.prototype.checkEvents = function(gameState, events)
this.target = undefined;
}
if (!this.overseas || this.state !== "unexecuted")
if (!this.overseas || this.state !== PETRA.AttackPlan.STATE_UNEXECUTED)
return;
// let's check if an enemy has built a structure at our access
for (let evt of events.Create)

View File

@ -35,11 +35,29 @@ PETRA.BaseManager = function(gameState, basesManager)
this.timeNextIdleCheck = 0;
};
PETRA.BaseManager.STATE_WITH_ANCHOR = "anchored";
/**
* New base with a foundation anchor.
*/
PETRA.BaseManager.STATE_UNCONSTRUCTED = "unconstructed";
/**
* Captured base with an anchor.
*/
PETRA.BaseManager.STATE_CAPTURED = "captured";
/**
* Anchorless base, currently with dock.
*/
PETRA.BaseManager.STATE_ANCHORLESS = "anchorless";
PETRA.BaseManager.prototype.init = function(gameState, state)
{
if (state == "unconstructed")
if (state === PETRA.BaseManager.STATE_UNCONSTRUCTED)
this.constructing = true;
else if (state != "captured")
else if (state !== PETRA.BaseManager.STATE_CAPTURED)
this.neededDefenders = 0;
this.workerObject = new PETRA.Worker(this);
// entitycollections
@ -66,12 +84,12 @@ PETRA.BaseManager.prototype.init = function(gameState, state)
PETRA.BaseManager.prototype.reset = function(gameState, state)
{
if (state == "unconstructed")
if (state === PETRA.BaseManager.STATE_UNCONSTRUCTED)
this.constructing = true;
else
this.constructing = false;
if (state != "captured" || this.Config.difficulty < 3)
if (state !== PETRA.BaseManager.STATE_CAPTURED || this.Config.difficulty < 3)
this.neededDefenders = 0;
else
this.neededDefenders = 3 + 2 * (this.Config.difficulty - 3);
@ -122,7 +140,7 @@ PETRA.BaseManager.prototype.setAnchorlessEntity = function(gameState, ent)
API3.warn("Error: Petra base " + this.ID + " has been assigned " + ent.templateName() + " as origin.");
this.accessIndex = PETRA.getLandAccess(gameState, ent);
}
else if (this.accessIndex != PETRA.getLandAccess(gameState, ent))
else if (this.accessIndex !== PETRA.getLandAccess(gameState, ent))
API3.warn(" Error: Petra base " + this.ID + " with access " + this.accessIndex +
" has been assigned " + ent.templateName() + " with access" + PETRA.getLandAccess(gameState, ent));

View File

@ -25,14 +25,14 @@ PETRA.BasesManager.prototype.init = function(gameState)
this.basesMap = new API3.Map(gameState.sharedScript, "territory");
this.noBase = new PETRA.BaseManager(gameState, this);
this.noBase.init(gameState);
this.noBase.init(gameState, PETRA.BaseManager.STATE_WITH_ANCHOR);
this.noBase.accessIndex = 0;
for (const cc of gameState.getOwnStructures().filter(API3.Filters.byClass("CivCentre")).values())
if (cc.foundationProgress() === undefined)
this.createBase(gameState, cc);
this.createBase(gameState, cc, PETRA.BaseManager.STATE_WITH_ANCHOR);
else
this.createBase(gameState, cc, "unconstructed");
this.createBase(gameState, cc, PETRA.BaseManager.STATE_UNCONSTRUCTED);
};
/**
@ -65,12 +65,8 @@ PETRA.BasesManager.prototype.postinit = function(gameState)
* If an existing one without anchor already exist, use it.
* Otherwise create a new one.
* TODO when buildings, criteria should depend on distance
* allowedType: undefined => new base with an anchor
* "unconstructed" => new base with a foundation anchor
* "captured" => captured base with an anchor
* "anchorless" => anchorless base, currently with dock
*/
PETRA.BasesManager.prototype.createBase = function(gameState, ent, type)
PETRA.BasesManager.prototype.createBase = function(gameState, ent, type = PETRA.BaseManager.STATE_WITH_ANCHOR)
{
const access = PETRA.getLandAccess(gameState, ent);
let newbase;
@ -78,9 +74,9 @@ PETRA.BasesManager.prototype.createBase = function(gameState, ent, type)
{
if (base.accessIndex != access)
continue;
if (type != "anchorless" && base.anchor)
if (type !== PETRA.BaseManager.STATE_ANCHORLESS && base.anchor)
continue;
if (type != "anchorless")
if (type !== PETRA.BaseManager.STATE_ANCHORLESS)
{
// TODO we keep the first one, we should rather use the nearest if buildings
// and possibly also cut on distance
@ -116,7 +112,7 @@ PETRA.BasesManager.prototype.createBase = function(gameState, ent, type)
else
newbase.reset(type);
if (type != "anchorless")
if (type !== PETRA.BaseManager.STATE_ANCHORLESS)
newbase.setAnchor(gameState, ent);
else
newbase.setAnchorlessEntity(gameState, ent);
@ -172,7 +168,7 @@ PETRA.BasesManager.prototype.checkEvents = function(gameState, events)
if (ent.getMetadata(PlayerID, "base") == -1) // Standard base around a cc
{
// Okay so let's try to create a new base around this.
const newbase = this.createBase(gameState, ent, "unconstructed");
const newbase = this.createBase(gameState, ent, PETRA.BaseManager.STATE_UNCONSTRUCTED);
// Let's get a few units from other bases there to build this.
const builders = this.bulkPickWorkers(gameState, newbase, 10);
if (builders !== false)
@ -186,7 +182,7 @@ PETRA.BasesManager.prototype.checkEvents = function(gameState, events)
}
else if (ent.getMetadata(PlayerID, "base") == -2) // anchorless base around a dock
{
const newbase = this.createBase(gameState, ent, "anchorless");
const newbase = this.createBase(gameState, ent, PETRA.BaseManager.STATE_ANCHORLESS);
// Let's get a few units from other bases there to build this.
const builders = this.bulkPickWorkers(gameState, newbase, 4);
if (builders != false)
@ -250,10 +246,10 @@ PETRA.BasesManager.prototype.checkEvents = function(gameState, events)
{
let newbase;
if (ent.foundationProgress() !== undefined)
newbase = this.createBase(gameState, ent, "unconstructed");
newbase = this.createBase(gameState, ent, PETRA.BaseManager.STATE_UNCONSTRUCTED);
else
{
newbase = this.createBase(gameState, ent, "captured");
newbase = this.createBase(gameState, ent, PETRA.BaseManager.STATE_CAPTURED);
addBase = true;
}
newbase.assignEntity(gameState, ent);
@ -263,7 +259,7 @@ PETRA.BasesManager.prototype.checkEvents = function(gameState, events)
let base;
// If dropsite on new island, create a base around it
if (!ent.decaying() && ent.resourceDropsiteTypes())
base = this.createBase(gameState, ent, "anchorless");
base = this.createBase(gameState, ent, PETRA.BaseManager.STATE_ANCHORLESS);
else
base = PETRA.getBestBase(gameState, ent) || this.noBase;
base.assignEntity(gameState, ent);
@ -582,7 +578,7 @@ PETRA.BasesManager.prototype.assignEntity = function(gameState, ent, territoryIn
if (!bestbase) // entity outside our territory
{
if (ent.hasClass("Structure") && !ent.decaying() && ent.resourceDropsiteTypes())
bestbase = this.createBase(gameState, ent, "anchorless");
bestbase = this.createBase(gameState, ent, PETRA.BaseManager.STATE_ANCHORLESS);
else
bestbase = PETRA.getBestBase(gameState, ent) || this.noBase;
bestbase.assignEntity(gameState, ent);
@ -775,7 +771,7 @@ PETRA.BasesManager.prototype.Deserialize = function(gameState, data)
this.noBase = new PETRA.BaseManager(gameState, this);
this.noBase.Deserialize(gameState, data.noBase);
this.noBase.init(gameState);
this.noBase.init(gameState, PETRA.BaseManager.STATE_WITH_ANCHOR);
this.noBase.Deserialize(gameState, data.noBase);
this.baseManagers = [];
@ -784,7 +780,7 @@ PETRA.BasesManager.prototype.Deserialize = function(gameState, data)
// The first call to deserialize set the ID base needed by entitycollections.
const newbase = new PETRA.BaseManager(gameState, this);
newbase.Deserialize(gameState, basedata);
newbase.init(gameState);
newbase.init(gameState, PETRA.BaseManager.STATE_WITH_ANCHOR);
newbase.Deserialize(gameState, basedata);
this.baseManagers.push(newbase);
}

View File

@ -136,7 +136,7 @@ PETRA.chatLaunchAttack = function(gameState, player, type)
{
Engine.PostCommand(PlayerID, {
"type": "aichat",
"message": "/allies " + pickRandom(this.launchAttackMessages[type === "HugeAttack" ? "hugeAttack" : "other"]),
"message": "/allies " + pickRandom(this.launchAttackMessages[type === PETRA.AttackPlan.TYPE_HUGE_ATTACK ? "hugeAttack" : "other"]),
"translateMessage": true,
"translateParameters": ["_player_"],
"parameters": { "_player_": player }

View File

@ -606,7 +606,7 @@ PETRA.DefenseManager.prototype.checkEvents = function(gameState, events)
if (plan !== undefined && plan >= 0)
{
let attack = gameState.ai.HQ.attackManager.getPlan(plan);
if (attack && attack.state != "unexecuted")
if (attack && attack.state != PETRA.AttackPlan.STATE_UNEXECUTED)
continue;
}
@ -752,7 +752,7 @@ PETRA.DefenseManager.prototype.garrisonUnitsInside = function(gameState, target,
let access = PETRA.getLandAccess(gameState, target);
let garrisonManager = gameState.ai.HQ.garrisonManager;
let garrisonArrowClasses = target.getGarrisonArrowClasses();
let typeGarrison = data.type || "protection";
const typeGarrison = data.type || PETRA.GarrisonManager.TYPE_PROTECTION;
let allowMelee = gameState.ai.HQ.garrisonManager.allowMelee(target);
if (allowMelee === undefined)
{
@ -767,7 +767,7 @@ PETRA.DefenseManager.prototype.garrisonUnitsInside = function(gameState, target,
return false;
if (!ent.hasClasses(garrisonArrowClasses))
return false;
if (typeGarrison != "decay" && !allowMelee && ent.attackTypes().indexOf("Melee") != -1)
if (typeGarrison !== PETRA.GarrisonManager.TYPE_DECAY && !allowMelee && ent.attackTypes().indexOf("Melee") != -1)
return false;
if (ent.getMetadata(PlayerID, "transport") !== undefined)
return false;
@ -778,7 +778,7 @@ PETRA.DefenseManager.prototype.garrisonUnitsInside = function(gameState, target,
{
let subrole = ent.getMetadata(PlayerID, "subrole");
// When structure decaying (usually because we've just captured it in enemy territory), also allow units from an attack plan.
if (typeGarrison != "decay" && subrole && (subrole == "completing" || subrole == "walking" || subrole == "attacking"))
if (typeGarrison !== PETRA.GarrisonManager.TYPE_DECAY && subrole && (subrole == "completing" || subrole == "walking" || subrole == "attacking"))
return false;
}
if (PETRA.getLandAccess(gameState, ent) != access)
@ -832,7 +832,7 @@ PETRA.DefenseManager.prototype.garrisonSiegeUnit = function(gameState, unit)
nearest = ent;
}
if (nearest)
garrisonManager.garrison(gameState, unit, nearest, "protection");
garrisonManager.garrison(gameState, unit, nearest, PETRA.GarrisonManager.TYPE_PROTECTION);
return nearest !== undefined;
};
@ -873,13 +873,13 @@ PETRA.DefenseManager.prototype.garrisonAttackedUnit = function(gameState, unit,
if (!emergency)
{
garrisonManager.garrison(gameState, unit, nearest, "protection");
garrisonManager.garrison(gameState, unit, nearest, PETRA.GarrisonManager.TYPE_PROTECTION);
return true;
}
if (garrisonManager.numberOfGarrisonedSlots(nearest) >= nearest.garrisonMax()) // make room for this ent
nearest.unload(nearest.garrisoned()[0]);
garrisonManager.garrison(gameState, unit, nearest, nearest.buffHeal() ? "protection" : "emergency");
garrisonManager.garrison(gameState, unit, nearest, nearest.buffHeal() ? PETRA.GarrisonManager.TYPE_PROTECTION : PETRA.GarrisonManager.TYPE_EMERGENCY);
return true;
};

View File

@ -13,6 +13,12 @@ PETRA.GarrisonManager = function(Config)
this.decayingStructures = new Map();
};
PETRA.GarrisonManager.TYPE_FORCE = "force";
PETRA.GarrisonManager.TYPE_TRADE = "trade";
PETRA.GarrisonManager.TYPE_PROTECTION = "protection";
PETRA.GarrisonManager.TYPE_DECAY = "decay";
PETRA.GarrisonManager.TYPE_EMERGENCY = "emergency";
PETRA.GarrisonManager.prototype.update = function(gameState, events)
{
// First check for possible upgrade of a structure
@ -194,7 +200,7 @@ PETRA.GarrisonManager.prototype.update = function(gameState, events)
if (!ent || ent.owner() !== PlayerID)
this.decayingStructures.delete(id);
else if (this.numberOfGarrisonedSlots(ent) < gmin)
gameState.ai.HQ.defenseManager.garrisonUnitsInside(gameState, ent, { "min": gmin, "type": "decay" });
gameState.ai.HQ.defenseManager.garrisonUnitsInside(gameState, ent, { "min": gmin, "type": PETRA.GarrisonManager.TYPE_DECAY });
}
};
@ -284,11 +290,11 @@ PETRA.GarrisonManager.prototype.keepGarrisoned = function(ent, holder, around)
{
switch (ent.getMetadata(PlayerID, "garrisonType"))
{
case 'force': // force the ungarrisoning
case PETRA.GarrisonManager.TYPE_FORCE: // force the ungarrisoning
return false;
case 'trade': // trader garrisoned in ship
case PETRA.GarrisonManager.TYPE_TRADE: // trader garrisoned in ship
return true;
case 'protection': // hurt unit for healing or infantry for defense
case PETRA.GarrisonManager.TYPE_PROTECTION: // hurt unit for healing or infantry for defense
if (holder.buffHeal() && ent.isHealable() && ent.healthLevel() < this.Config.garrisonHealthLevel.high)
return true;
let capture = ent.capturePoints();
@ -309,9 +315,9 @@ PETRA.GarrisonManager.prototype.keepGarrisoned = function(ent, holder, around)
if (PETRA.isSiegeUnit(ent))
return around.meleeSiege;
return holder.buffHeal() && ent.needsHeal();
case 'decay':
case PETRA.GarrisonManager.TYPE_DECAY:
return this.decayingStructures.has(holder.id());
case 'emergency': // f.e. hero in regicide mode
case PETRA.GarrisonManager.TYPE_EMERGENCY: // f.e. hero in regicide mode
if (holder.buffHeal() && ent.isHealable() && ent.healthLevel() < this.Config.garrisonHealthLevel.high)
return true;
if (around.unit || around.defenseStructure || around.meleeSiege ||
@ -324,7 +330,7 @@ PETRA.GarrisonManager.prototype.keepGarrisoned = function(ent, holder, around)
API3.warn("unknown type in garrisonManager " + ent.getMetadata(PlayerID, "garrisonType") +
" for " + ent.genericName() + " id " + ent.id() +
" inside " + holder.genericName() + " id " + holder.id());
ent.setMetadata(PlayerID, "garrisonType", "protection");
ent.setMetadata(PlayerID, "garrisonType", PETRA.GarrisonManager.TYPE_PROTECTION);
return true;
}
};

View File

@ -210,7 +210,7 @@ PETRA.HQ.prototype.checkEvents = function(gameState, events)
if (plan !== undefined && plan >= 0)
{
let attack = this.attackManager.getPlan(plan);
if (!attack || attack.state != "unexecuted")
if (!attack || attack.state !== PETRA.AttackPlan.STATE_UNEXECUTED)
ent.setMetadata(PlayerID, "plan", -1);
}
}
@ -441,7 +441,7 @@ PETRA.HQ.prototype.trainMoreWorkers = function(gameState, queues)
let alpha = 0.85;
if (!gameState.isTemplateAvailable(gameState.applyCiv("structures/{civ}/field")))
supportRatio = Math.min(this.supportRatio, 0.1);
if (this.attackManager.rushNumber < this.attackManager.maxRushes || this.attackManager.upcomingAttacks.Rush.length)
if (this.attackManager.rushNumber < this.attackManager.maxRushes || this.attackManager.upcomingAttacks[PETRA.AttackPlan.TYPE_RUSH].length)
alpha = 0.7;
if (gameState.isCeasefireActive())
alpha += (1 - alpha) * Math.min(Math.max(gameState.ceasefireTimeRemaining - 120, 0), 180) / 180;
@ -1869,7 +1869,7 @@ PETRA.HQ.prototype.trainEmergencyUnits = function(gameState, positions)
}
let metadata = { "role": "worker", "base": nearestAnchor.getMetadata(PlayerID, "base"), "plan": -1, "trainer": nearestAnchor.id() };
if (autogarrison)
metadata.garrisonType = "protection";
metadata.garrisonType = PETRA.GarrisonManager.TYPE_PROTECTION;
gameState.ai.queues.emergency.addPlan(new PETRA.TrainingPlan(gameState, templateFound[0], metadata, 1, 1));
return true;
};

View File

@ -679,7 +679,7 @@ PETRA.VictoryManager.prototype.captureGaiaRelic = function(gameState, relic)
if (plan !== undefined && plan >= 0)
{
let attack = gameState.ai.HQ.attackManager.getPlan(plan);
if (attack && (attack.state != "unexecuted" || attack.type == "Raid"))
if (attack && (attack.state !== PETRA.AttackPlan.STATE_UNEXECUTED || attack.type === PETRA.AttackPlan.TYPE_RAID))
return false;
}
if (PETRA.getLandAccess(gameState, ent) != access)