1
0
forked from 0ad/0ad

AI completes the removal of the JS pathfinder and fixes naval transport

This was SVN commit r16819.
This commit is contained in:
mimo 2015-06-27 13:30:44 +00:00
parent 575d708fca
commit 3efa4be02c
6 changed files with 260 additions and 241 deletions

View File

@ -73,21 +73,10 @@ m.AttackManager.prototype.checkEvents = function(gameState, events)
other = attack.targetPlayer; other = attack.targetPlayer;
continue; continue;
} }
if (!attack.targetPlayer || attack.targetPlayer !== targetPlayer) if (!attack.targetPlayer || attack.targetPlayer !== targetPlayer)
{
let oldTargetPlayer = attack.targetPlayer;
let oldTarget = attack.target;
attack.targetPlayer = targetPlayer; attack.targetPlayer = targetPlayer;
attack.target = attack.getNearestTarget(gameState, attack.rallyPoint);
if (!attack.target)
{
attack.targetPlayer = oldTargetPlayer;
attack.target = oldTarget;
continue;
}
attack.targetPos = attack.target.position();
attack.resetPath();
}
if (attack.targetPlayer && attack.targetPlayer === targetPlayer) if (attack.targetPlayer && attack.targetPlayer === targetPlayer)
available += attack.unitCollection.length; available += attack.unitCollection.length;
} }
@ -153,7 +142,7 @@ m.AttackManager.prototype.update = function(gameState, queues, events)
if (attack.state === "unexecuted") if (attack.state === "unexecuted")
++unexecutedAttacks[attackType]; ++unexecutedAttacks[attackType];
} }
else if (updateStep === 0 || updateStep === 3) else if (updateStep === 0)
{ {
if (this.Config.debug > 1) if (this.Config.debug > 1)
API3.warn("Attack Manager: " + attack.getType() + " plan " + attack.getName() + " aborted."); API3.warn("Attack Manager: " + attack.getType() + " plan " + attack.getName() + " aborted.");

View File

@ -24,23 +24,25 @@ m.AttackPlan = function(gameState, Config, uniqueID, type, data)
{ {
this.target = undefined; this.target = undefined;
this.targetPos = undefined; this.targetPos = undefined;
this.targetPlayer = gameState.ai.HQ.attackManager.getEnemyPlayer(gameState, this); this.targetPlayer = undefined;
} }
if (this.targetPlayer === undefined)
{
this.failed = true;
return false;
}
// get a starting rallyPoint ... will be improved later // get a starting rallyPoint ... will be improved later
var rallyPoint; let rallyPoint;
let rallyAccess;
let allAccesses = {};
for (let base of gameState.ai.HQ.baseManagers) for (let base of gameState.ai.HQ.baseManagers)
{ {
if (!base.anchor || !base.anchor.position()) if (!base.anchor || !base.anchor.position())
continue; continue;
rallyPoint = base.anchor.position(); let access = gameState.ai.accessibility.getAccessValue(base.anchor.position());
break; if (!rallyPoint)
{
rallyPoint = base.anchor.position();
rallyAccess = access;
}
if (!allAccesses[access])
allAccesses[access] = base.anchor.position();
} }
if (!rallyPoint) // no base ? take the position of any of our entities if (!rallyPoint) // no base ? take the position of any of our entities
{ {
@ -48,7 +50,10 @@ m.AttackPlan = function(gameState, Config, uniqueID, type, data)
{ {
if (!ent.position()) if (!ent.position())
continue; continue;
let access = gameState.ai.accessibility.getAccessValue(ent.position());
rallyPoint = ent.position(); rallyPoint = ent.position();
rallyAccess = access;
allAccesses[access] = rallyPoint;
break; break;
} }
if (!rallyPoint) if (!rallyPoint)
@ -58,8 +63,30 @@ m.AttackPlan = function(gameState, Config, uniqueID, type, data)
} }
} }
this.rallyPoint = rallyPoint; this.rallyPoint = rallyPoint;
this.overseas = 0;
this.overseas = undefined; if (gameState.ai.HQ.navalMap)
{
for (let structure of gameState.getEnemyStructures().values())
{
if (!structure.position())
continue;
let access = gameState.ai.accessibility.getAccessValue(structure.position());
if (access in allAccesses)
{
this.overseas = 0;
this.rallyPoint = allAccesses[access];
break;
}
else if (!this.overseas)
{
let sea = gameState.ai.HQ.getSeaIndex(gameState, rallyAccess, access);
if (!sea)
continue;
this.overseas = sea;
gameState.ai.HQ.navalManager.setMinimalTransportShips(gameState, sea, 1);
}
}
}
this.paused = false; this.paused = false;
this.maxCompletingTurn = 0; this.maxCompletingTurn = 0;
@ -182,9 +209,9 @@ m.AttackPlan.prototype.init = function(gameState)
// defining the entity collections. Will look for units I own, that are part of this plan. // defining the entity collections. Will look for units I own, that are part of this plan.
// Also defining the buildOrders. // Also defining the buildOrders.
for (var cat in this.unitStat) for (let cat in this.unitStat)
{ {
var Unit = this.unitStat[cat]; let Unit = this.unitStat[cat];
this.unit[cat] = this.unitCollection.filter(API3.Filters.byClassesAnd(Unit["classes"])); this.unit[cat] = this.unitCollection.filter(API3.Filters.byClassesAnd(Unit["classes"]));
this.unit[cat].registerUpdates(); this.unit[cat].registerUpdates();
if (this.canBuildUnits) if (this.canBuildUnits)
@ -224,9 +251,9 @@ m.AttackPlan.prototype.canStart = function()
if (!this.canBuildUnits) if (!this.canBuildUnits)
return true; return true;
for (var unitCat in this.unitStat) for (let unitCat in this.unitStat)
{ {
var Unit = this.unitStat[unitCat]; let Unit = this.unitStat[unitCat];
if (this.unit[unitCat].length < Unit["minSize"]) if (this.unit[unitCat].length < Unit["minSize"])
return false; return false;
} }
@ -235,7 +262,7 @@ m.AttackPlan.prototype.canStart = function()
m.AttackPlan.prototype.mustStart = function() m.AttackPlan.prototype.mustStart = function()
{ {
if (this.isPaused() || this.path === undefined) if (this.isPaused())
return false; return false;
if (!this.canBuildUnits) if (!this.canBuildUnits)
@ -243,9 +270,9 @@ m.AttackPlan.prototype.mustStart = function()
var MaxReachedEverywhere = true; var MaxReachedEverywhere = true;
var MinReachedEverywhere = true; var MinReachedEverywhere = true;
for (var unitCat in this.unitStat) for (let unitCat in this.unitStat)
{ {
var Unit = this.unitStat[unitCat]; let Unit = this.unitStat[unitCat];
if (this.unit[unitCat].length < Unit["targetSize"]) if (this.unit[unitCat].length < Unit["targetSize"])
MaxReachedEverywhere = false; MaxReachedEverywhere = false;
if (this.unit[unitCat].length < Unit["minSize"]) if (this.unit[unitCat].length < Unit["minSize"])
@ -282,9 +309,8 @@ m.AttackPlan.prototype.addBuildOrder = function(gameState, name, unitStats, rese
{ {
// no minsize as we don't want the plan to fail at the last minute though. // no minsize as we don't want the plan to fail at the last minute though.
this.unitStat[name] = unitStats; this.unitStat[name] = unitStats;
var Unit = this.unitStat[name]; let Unit = this.unitStat[name];
var filter = API3.Filters.and(API3.Filters.byClassesAnd(Unit["classes"]), API3.Filters.byMetadata(PlayerID, "plan", this.name)); this.unit[name] = this.unitCollection.filter(API3.Filters.byClassesAnd(Unit["classes"]));
this.unit[name] = gameState.getOwnUnits().filter(filter);
this.unit[name].registerUpdates(); this.unit[name].registerUpdates();
this.buildOrder.push([0, Unit["classes"], this.unit[name], Unit, name]); this.buildOrder.push([0, Unit["classes"], this.unit[name], Unit, name]);
if (resetQueue) if (resetQueue)
@ -315,105 +341,33 @@ m.AttackPlan.prototype.addSiegeUnits = function(gameState)
}; };
// 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"
// 3 is a special case: no valid path returned. Right now I stop attacking alltogether.
m.AttackPlan.prototype.updatePreparation = function(gameState) m.AttackPlan.prototype.updatePreparation = function(gameState)
{ {
// the completing step is used to return resources and regroup the units // the completing step is used to return resources and regroup the units
// so we check that we have no more forced order before starting the attack // so we check that we have no more forced order before starting the attack
if (this.state === "completing") if (this.state === "completing")
{ {
// check that all units have finished with their transport if needed // if our target was destroyed, go back to "unexecuted" state
if (this.waitingForTransport()) if (!this.targetPlayer || !this.target || !gameState.getEntityById(this.target.id()))
return 1; {
// bloqued units which cannot finish their order should not stop the attack this.state === "unexecuted";
if (gameState.ai.playedTurn < this.maxCompletingTurn && this.hasForceOrder()) this.target = undefined;
return 1; }
return 2; else
{
// check that all units have finished with their transport if needed
if (this.waitingForTransport())
return 1;
// bloqued units which cannot finish their order should not stop the attack
if (gameState.ai.playedTurn < this.maxCompletingTurn && this.hasForceOrder())
return 1;
return 2;
}
} }
if (this.Config.debug > 3 && gameState.ai.playedTurn % 50 == 0) if (this.Config.debug > 3 && gameState.ai.playedTurn % 50 == 0)
this.debugAttack(); this.debugAttack();
// find our target (if not yet done or because our previous one was destroyed)
if (!this.targetPlayer)
{
this.targetPlayer = gameState.ai.HQ.attackManager.getEnemyPlayer(gameState, this);
if (!this.targetPlayer)
return 0;
if (this.target && this.target.owner() !== this.targetPlayer)
this.target = undefined;
}
if (!this.target || !gameState.getEntityById(this.target.id()))
{
this.target = this.getNearestTarget(gameState, this.rallyPoint);
if (!this.target)
{
// may-be all our previous enemey targets have been destroyed ?
this.targetPlayer = gameState.ai.HQ.attackManager.getEnemyPlayer(gameState, this);
if (this.targetPlayer !== undefined)
this.target = this.getNearestTarget(gameState, this.rallyPoint);
if (!this.target)
return 0;
}
this.targetPos = this.target.position();
// redefine a new rally point for this target if we have a base on the same land
// find a new one on the pseudo-nearest base (dist weighted by the size of the island)
var targetIndex = gameState.ai.accessibility.getAccessValue(this.targetPos);
var rallyIndex = gameState.ai.accessibility.getAccessValue(this.rallyPoint);
if (targetIndex !== rallyIndex)
{
var distminSame = Math.min();
var rallySame = undefined;
var distminDiff = Math.min();
var rallyDiff = undefined;
for (var base of gameState.ai.HQ.baseManagers)
{
var anchor = base.anchor;
if (!anchor || !anchor.position())
continue;
var dist = API3.SquareVectorDistance(anchor.position(), this.targetPos);
if (base.accessIndex === targetIndex)
{
if (dist < distminSame)
{
distminSame = dist;
rallySame = anchor.position();
}
}
else
{
dist = dist / Math.sqrt(gameState.ai.accessibility.regionSize[base.accessIndex]);
if (dist < distminDiff)
{
distminDiff = dist;
rallyDiff = anchor.position();
}
}
}
if (rallySame)
this.rallyPoint = rallySame;
else if (rallyDiff)
{
this.rallyPoint = rallyDiff;
this.overseas = gameState.ai.HQ.getSeaIndex(gameState, rallyIndex, targetIndex);
if (this.overseas)
gameState.ai.HQ.navalManager.setMinimalTransportShips(gameState, this.overseas, this.neededShips);
else
return 0;
}
}
// reset the path so that we recompute it for this new target
this.resetPath();
}
// when we have a target, we path to it.
if (!this.path || this.path === "toBeContinued")
{
let ret = this.getPathToTarget(gameState);
if (ret >= 0)
return ret;
}
// 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 1;
@ -484,6 +438,12 @@ m.AttackPlan.prototype.updatePreparation = function(gameState)
// if we're here, it means we must start // if we're here, it means we must start
this.state = "completing"; this.state = "completing";
if (!this.chooseTarget(gameState))
return 0;
if (!this.overseas)
this.getPathToTarget(gameState);
if (this.type === "Raid") if (this.type === "Raid")
this.maxCompletingTurn = gameState.ai.playedTurn + 20; this.maxCompletingTurn = gameState.ai.playedTurn + 20;
else else
@ -496,28 +456,28 @@ m.AttackPlan.prototype.updatePreparation = function(gameState)
var rallyPoint = this.rallyPoint; var rallyPoint = this.rallyPoint;
var rallyIndex = gameState.ai.accessibility.getAccessValue(rallyPoint); var rallyIndex = gameState.ai.accessibility.getAccessValue(rallyPoint);
for (var entity of this.unitCollection.values()) for (let ent of this.unitCollection.values())
{ {
// For the time being, if occupied in a transport, remove the unit from this plan TODO improve that // For the time being, if occupied in a transport, remove the unit from this plan TODO improve that
if (entity.getMetadata(PlayerID, "transport") !== undefined || entity.getMetadata(PlayerID, "transporter") !== undefined) if (ent.getMetadata(PlayerID, "transport") !== undefined || ent.getMetadata(PlayerID, "transporter") !== undefined)
{ {
entity.setMetadata(PlayerID, "plan", -1); ent.setMetadata(PlayerID, "plan", -1);
continue; continue;
} }
entity.setMetadata(PlayerID, "role", "attack"); ent.setMetadata(PlayerID, "role", "attack");
entity.setMetadata(PlayerID, "subrole", "completing"); ent.setMetadata(PlayerID, "subrole", "completing");
var queued = false; let queued = false;
if (entity.resourceCarrying() && entity.resourceCarrying().length) if (ent.resourceCarrying() && ent.resourceCarrying().length)
queued = m.returnResources(gameState, entity); queued = m.returnResources(gameState, ent);
var index = gameState.ai.accessibility.getAccessValue(entity.position()); let index = gameState.ai.accessibility.getAccessValue(ent.position());
if (index === rallyIndex) if (index === rallyIndex)
entity.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, entity, index, rallyIndex, rallyPoint); gameState.ai.HQ.navalManager.requireTransport(gameState, ent, index, rallyIndex, rallyPoint);
} }
// reset all queued units // reset all queued units
var plan = this.name; let plan = this.name;
gameState.ai.queueManager.removeQueue("plan_" + plan); gameState.ai.queueManager.removeQueue("plan_" + plan);
gameState.ai.queueManager.removeQueue("plan_" + plan + "_champ"); gameState.ai.queueManager.removeQueue("plan_" + plan + "_champ");
gameState.ai.queueManager.removeQueue("plan_" + plan + "_siege"); gameState.ai.queueManager.removeQueue("plan_" + plan + "_siege");
@ -540,10 +500,10 @@ m.AttackPlan.prototype.trainMoreUnits = function(gameState)
this.buildOrder[i][0] = this.buildOrder[i][2].length + aQueued; this.buildOrder[i][0] = this.buildOrder[i][2].length + aQueued;
} }
this.buildOrder.sort(function (a,b) { this.buildOrder.sort(function (a,b) {
var va = a[0]/a[3]["targetSize"] - a[3]["priority"]; let va = a[0]/a[3]["targetSize"] - a[3]["priority"];
if (a[0] >= a[3]["targetSize"]) if (a[0] >= a[3]["targetSize"])
va += 1000; va += 1000;
var vb = b[0]/b[3]["targetSize"] - b[3]["priority"]; let vb = b[0]/b[3]["targetSize"] - b[3]["priority"];
if (b[0] >= b[3]["targetSize"]) if (b[0] >= b[3]["targetSize"])
vb += 1000; vb += 1000;
return va - vb; return va - vb;
@ -743,6 +703,75 @@ m.AttackPlan.prototype.reassignCavUnit = function(gameState)
raid.unitCollection.updateEnt(found); raid.unitCollection.updateEnt(found);
}; };
m.AttackPlan.prototype.chooseTarget = function(gameState)
{
if (!this.targetPlayer)
{
this.targetPlayer = gameState.ai.HQ.attackManager.getEnemyPlayer(gameState, this);
if (!this.targetPlayer)
return false;
}
this.target = this.getNearestTarget(gameState, this.rallyPoint);
if (!this.target)
{
// may-be all our previous enemey target (if not recomputed here) have been destroyed ?
this.targetPlayer = gameState.ai.HQ.attackManager.getEnemyPlayer(gameState, this);
if (this.targetPlayer !== undefined)
this.target = this.getNearestTarget(gameState, this.rallyPoint);
if (!this.target)
return false;
}
this.targetPos = this.target.position();
// redefine a new rally point for this target if we have a base on the same land
// find a new one on the pseudo-nearest base (dist weighted by the size of the island)
let targetIndex = gameState.ai.accessibility.getAccessValue(this.targetPos);
let rallyIndex = gameState.ai.accessibility.getAccessValue(this.rallyPoint);
if (targetIndex !== rallyIndex)
{
let distminSame = Math.min();
let rallySame;
let distminDiff = Math.min();
let rallyDiff;
for (let base of gameState.ai.HQ.baseManagers)
{
let anchor = base.anchor;
if (!anchor || !anchor.position())
continue;
let dist = API3.SquareVectorDistance(anchor.position(), this.targetPos);
if (base.accessIndex === targetIndex)
{
if (dist >= distminSame)
continue;
distminSame = dist;
rallySame = anchor.position();
}
else
{
dist = dist / Math.sqrt(gameState.ai.accessibility.regionSize[base.accessIndex]);
if (dist >= distminDiff)
continue;
distminDiff = dist;
rallyDiff = anchor.position();
}
}
if (rallySame)
this.rallyPoint = rallySame;
else if (rallyDiff)
{
rallyIndex = gameState.ai.accessibility.getAccessValue(rallyDiff);
this.rallyPoint = rallyDiff;
this.overseas = gameState.ai.HQ.getSeaIndex(gameState, rallyIndex, targetIndex);
if (this.overseas)
gameState.ai.HQ.navalManager.setMinimalTransportShips(gameState, this.overseas, this.neededShips);
else
return false;
}
}
return true;
};
// sameLand true means that we look for a target for which we do not need to take a transport // sameLand true means that we look for a target for which we do not need to take a transport
m.AttackPlan.prototype.getNearestTarget = function(gameState, position, sameLand) m.AttackPlan.prototype.getNearestTarget = function(gameState, position, sameLand)
{ {
@ -759,7 +788,7 @@ m.AttackPlan.prototype.getNearestTarget = function(gameState, position, sameLand
// picking the nearest target // picking the nearest target
var minDist = -1; var minDist = -1;
var target = undefined; var target;
for (let ent of targets.values()) for (let ent of targets.values())
{ {
if (!ent.position()) if (!ent.position())
@ -783,7 +812,7 @@ m.AttackPlan.prototype.getNearestTarget = function(gameState, position, sameLand
// Default target finder aims for conquest critical targets // Default target finder aims for conquest critical targets
m.AttackPlan.prototype.defaultTargetFinder = function(gameState, playerEnemy) m.AttackPlan.prototype.defaultTargetFinder = function(gameState, playerEnemy)
{ {
var targets = undefined; var targets;
if (gameState.getGameType() === "wonder") if (gameState.getGameType() === "wonder")
{ {
targets = gameState.getEnemyStructures(playerEnemy).filter(API3.Filters.byClass("Wonder")); targets = gameState.getEnemyStructures(playerEnemy).filter(API3.Filters.byClass("Wonder"));
@ -791,7 +820,7 @@ m.AttackPlan.prototype.defaultTargetFinder = function(gameState, playerEnemy)
return targets; return targets;
} }
var targets = gameState.getEnemyStructures(playerEnemy).filter(API3.Filters.byClass("CivCentre")); targets = gameState.getEnemyStructures(playerEnemy).filter(API3.Filters.byClass("CivCentre"));
if (!targets.length) if (!targets.length)
targets = gameState.getEnemyStructures(playerEnemy).filter(API3.Filters.byClass("ConquestCritical")); targets = gameState.getEnemyStructures(playerEnemy).filter(API3.Filters.byClass("ConquestCritical"));
// If there's nothing, attack anything else that's less critical // If there's nothing, attack anything else that's less critical
@ -878,46 +907,49 @@ m.AttackPlan.prototype.raidTargetFinder = function(gameState)
m.AttackPlan.prototype.getPathToTarget = function(gameState) m.AttackPlan.prototype.getPathToTarget = function(gameState)
{ {
if (!this.path) let startAccess = gameState.ai.accessibility.getAccessValue(this.rallyPoint);
{ let endAccess = gameState.ai.accessibility.getAccessValue(this.targetPos);
Engine.ProfileStart("AI Compute path"); if (startAccess != endAccess)
let startPos = { "x": this.rallyPoint[0], "y": this.rallyPoint[1] }; return false;
let endPos = { "x": this.targetPos[0], "y": this.targetPos[1] };
let path = Engine.ComputePath(startPos, endPos, gameState.getPassabilityClassMask("siege-large"));
this.path = [];
this.path.push(this.targetPos);
for (let p in path)
this.path.push([path[p].x, path[p].y])
this.path.push(this.rallyPoint);
this.path.reverse();
// Change the rally point to something useful
this.setRallyPoint(gameState);
Engine.ProfileStop();
}
else if (this.path === "toBeContinued")
return 1; // carry on
return -1; Engine.ProfileStart("AI Compute path");
let startPos = { "x": this.rallyPoint[0], "y": this.rallyPoint[1] };
let endPos = { "x": this.targetPos[0], "y": this.targetPos[1] };
let path = Engine.ComputePath(startPos, endPos, gameState.getPassabilityClassMask("siege-large"));
this.path = [];
this.path.push(this.targetPos);
for (let p in path)
this.path.push([path[p].x, path[p].y])
this.path.push(this.rallyPoint);
this.path.reverse();
// Change the rally point to something useful
this.setRallyPoint(gameState);
Engine.ProfileStop();
return true;
}; };
// Set rally point at the border of our territory
m.AttackPlan.prototype.setRallyPoint = function(gameState) m.AttackPlan.prototype.setRallyPoint = function(gameState)
{ {
for (let i = 0; i < this.path.length; ++i) for (let i = 0; i < this.path.length; ++i)
{ {
let waypointPos = this.path[i]; if (gameState.ai.HQ.territoryMap.getOwner(this.path[i]) === PlayerID)
if (gameState.ai.HQ.territoryMap.getOwner(waypointPos) !== PlayerID) continue;
{
// Set rally point at the border of our territory
// or where we need to change transportation method.
if (i !== 0)
this.rallyPoint = this.path[i-1];
else
this.rallyPoint = this.path[0];
if (i >= 2) if (i == 0)
this.path.splice(0, i-1); this.rallyPoint = this.path[0];
break; else if (i > 1 && gameState.ai.HQ.isDangerousLocation(gameState, this.path[i-1], 20))
{
this.rallyPoint = this.path[i-2];
this.path.splice(0, i-2);
} }
else
{
this.rallyPoint = this.path[i-1];
this.path.splice(0, i-1);
}
break;
} }
}; };
@ -928,46 +960,15 @@ m.AttackPlan.prototype.StartAttack = function(gameState)
if (this.Config.debug > 1) if (this.Config.debug > 1)
API3.warn("start attack " + this.name + " with type " + this.type); API3.warn("start attack " + this.name + " with type " + this.type);
if (!this.targetPlayer) // if our target was destroyed during preparation, choose a new one
if (!this.targetPlayer || !this.target || !gameState.getEntityById(this.target.id()))
{ {
this.targetPlayer = gameState.ai.HQ.attackManager.getEnemyPlayer(gameState, this); if (!this.chooseTarget(gameState))
if (!this.targetPlayer)
return false; return false;
if (this.target && this.target.owner() !== this.targetPlayer)
this.target = undefined;
}
if (!this.target || !gameState.getEntityById(this.target.id())) // our target was destroyed during our preparation
{
if (!this.targetPos) // should not happen
return false;
var targetIndex = gameState.ai.accessibility.getAccessValue(this.targetPos);
var rallyIndex = gameState.ai.accessibility.getAccessValue(this.rallyPoint);
if (targetIndex === rallyIndex)
{
// If on the same index: if we are doing a raid, look for a better target,
// otherwise proceed with the previous target position
// and we will look for a better target there
if (this.type === "Raid")
{
this.target = this.getNearestTarget(gameState, this.rallyPoint);
if (!this.target)
return false;
this.targetPos = this.target.position();
}
}
else
{
// Not on the same index, do not loose time to go to previous targetPos if nothing there
// so directly look for a new target right now
this.target = this.getNearestTarget(gameState, this.rallyPoint);
if (!this.target)
return false;
this.targetPos = this.target.position();
}
} }
// check we have a target and a path. // check we have a target and a path.
if (this.targetPos && this.path !== undefined) if (this.targetPos && (this.overseas || this.path))
{ {
// erase our queue. This will stop any leftover unit from being trained. // erase our queue. This will stop any leftover unit from being trained.
gameState.ai.queueManager.removeQueue("plan_" + this.name); gameState.ai.queueManager.removeQueue("plan_" + this.name);
@ -1203,7 +1204,23 @@ m.AttackPlan.prototype.update = function(gameState, events)
// basic state of attacking. // basic state of attacking.
if (this.state === "") if (this.state === "")
{ {
// First update the target if needed: // First update the target position in case it's a unit (and check if it has garrisoned)
if (this.target && this.target.hasClass("Unit"))
{
this.targetPos = this.target.position();
if (!this.targetPos)
{
let holder = m.getHolder(gameState, this.target);
if (holder && gameState.isPlayerEnemy(holder.owner()))
{
this.target = holder;
this.targetPos = holder.position();
}
else
this.target = undefined;
}
}
// Then update the target if needed:
if (!this.targetPlayer) if (!this.targetPlayer)
{ {
this.targetPlayer = gameState.ai.HQ.attackManager.getEnemyPlayer(gameState, this); this.targetPlayer = gameState.ai.HQ.attackManager.getEnemyPlayer(gameState, this);
@ -1266,9 +1283,6 @@ m.AttackPlan.prototype.update = function(gameState, events)
} }
this.targetPos = this.target.position(); this.targetPos = this.target.position();
} }
// and regularly update the target position in case it's a unit.
if (this.target.hasClass("Unit"))
this.targetPos = this.target.position();
var time = gameState.ai.elapsedTime; var time = gameState.ai.elapsedTime;
for (var evt of events["Attacked"]) for (var evt of events["Attacked"])
@ -1678,12 +1692,6 @@ m.AttackPlan.prototype.removeUnit = function(ent, update)
this.unitCollection.updateEnt(ent); this.unitCollection.updateEnt(ent);
}; };
// Reset the path so that it can be recomputed for a new target
m.AttackPlan.prototype.resetPath = function()
{
this.path = undefined;
};
m.AttackPlan.prototype.checkEvents = function(gameState, events) m.AttackPlan.prototype.checkEvents = function(gameState, events)
{ {
for (let evt of events["EntityRenamed"]) for (let evt of events["EntityRenamed"])
@ -1706,6 +1714,28 @@ m.AttackPlan.prototype.checkEvents = function(gameState, events)
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")
return;
// let's check if an enemy has built a structure at our access
for (let evt of events["Create"])
{
let ent = gameState.getEntityById(evt.entity);
if (!ent || !ent.position() || !ent.hasClass("Structure"))
continue;
if (!gameState.isPlayerEnemy(ent.owner()))
continue;
let access = gameState.ai.accessibility.getAccessValue(ent.position());
for (let base of gameState.ai.HQ.baseManagers)
{
if (!base.anchor || !base.anchor.position())
continue;
if (base.accessIndex !== access)
continue;
this.overseas = 0;
this.rallyPoint = base.anchor.position();
}
}
}; };
m.AttackPlan.prototype.waitingForTransport = function() m.AttackPlan.prototype.waitingForTransport = function()

View File

@ -2088,6 +2088,10 @@ m.HQ.prototype.Deserialize = function(gameState, data)
this.baseManagers.push(newbase); this.baseManagers.push(newbase);
} }
this.navalManager = new m.NavalManager(this.Config);
this.navalManager.init(gameState, true);
this.navalManager.Deserialize(gameState, data.navalManager);
this.attackManager = new m.AttackManager(this.Config); this.attackManager = new m.AttackManager(this.Config);
this.attackManager.Deserialize(gameState, data.attackManager); this.attackManager.Deserialize(gameState, data.attackManager);
this.attackManager.init(gameState); this.attackManager.init(gameState);
@ -2100,10 +2104,6 @@ m.HQ.prototype.Deserialize = function(gameState, data)
this.tradeManager.init(gameState); this.tradeManager.init(gameState);
this.tradeManager.Deserialize(gameState, data.tradeManager); this.tradeManager.Deserialize(gameState, data.tradeManager);
this.navalManager = new m.NavalManager(this.Config);
this.navalManager.init(gameState, true);
this.navalManager.Deserialize(gameState, data.navalManager);
this.researchManager = new m.ResearchManager(this.Config); this.researchManager = new m.ResearchManager(this.Config);
this.researchManager.Deserialize(data.researchManager); this.researchManager.Deserialize(data.researchManager);

View File

@ -134,8 +134,8 @@ m.NavalManager.prototype.init = function(gameState, deserializing)
if (!this.landingZones[land]) if (!this.landingZones[land])
this.landingZones[land] = {}; this.landingZones[land] = {};
if (!this.landingZones[land][naval]) if (!this.landingZones[land][naval])
this.landingZones[land][naval] = []; this.landingZones[land][naval] = new Set();
this.landingZones[land][naval].push(i); this.landingZones[land][naval].add(i);
} }
// and keep only thoses with enough room around when possible // and keep only thoses with enough room around when possible
for (let land in this.landingZones) for (let land in this.landingZones)
@ -145,27 +145,25 @@ m.NavalManager.prototype.init = function(gameState, deserializing)
let landing = this.landingZones[land][sea]; let landing = this.landingZones[land][sea];
let nbaround = {}; let nbaround = {};
let nbcut = 0; let nbcut = 0;
for (let i = 0; i < landing.length; i++) for (let i of landing)
{ {
let j = landing[i];
let nb = 0; let nb = 0;
if (i > 0 && landing[i-1] == j-1) if (landing.has(i-1))
nb++; nb++;
if (i < landing.length-1 && landing[i+1] == j+1) if (landing.has(i+1))
nb++; nb++;
if (landing.indexOf(j+width) != -1) if (landing.has(i+width))
nb++; nb++;
if (landing.indexOf(j-width) != -1) if (landing.has(i-width))
nb++; nb++;
nbaround[j] = nb; nbaround[i] = nb;
nbcut = Math.max(nb, nbcut); nbcut = Math.max(nb, nbcut);
} }
nbcut = Math.min(2, nbcut); nbcut = Math.min(2, nbcut);
for (let i = 0; i < landing.length; i++) for (let i of landing)
{ {
let j = landing[i]; if (nbaround[i] < nbcut)
if (nbaround[j] < nbcut) landing.delete(i);
landing.splice(i--, 1);
} }
} }
} }

View File

@ -144,17 +144,17 @@ m.ConstructionPlan.prototype.findGoodPosition = function(gameState)
else else
return false; return false;
} }
else if (template.hasClass("Tower") || template.hasClass("Fortress")) else if (template.hasClass("Tower") || template.hasClass("Fortress") || template.hasClass("ArmyCamp"))
{ {
var pos = gameState.ai.HQ.findDefensiveLocation(gameState, template); var pos = gameState.ai.HQ.findDefensiveLocation(gameState, template);
if (pos) if (pos)
return { "x": pos[0], "z": pos[1], "angle": 3*Math.PI/4, "base": pos[2] }; return { "x": pos[0], "z": pos[1], "angle": 3*Math.PI/4, "base": pos[2] };
else if (!template.hasClass("Fortress") || gameState.civ() === "mace" || gameState.civ() === "maur" || else if (template.hasClass("Tower") || gameState.civ() === "mace" || gameState.civ() === "maur" ||
gameState.countEntitiesByType(gameState.applyCiv("structures/{civ}_fortress"), true) gameState.countEntitiesByType(gameState.applyCiv("structures/{civ}_fortress"), true)
+ gameState.countEntitiesByType(gameState.applyCiv("structures/{civ}_army_camp"), true) > 0) + gameState.countEntitiesByType(gameState.applyCiv("structures/{civ}_army_camp"), true) > 0)
// if this fortress is our first siege unit builder, just try the standard placement as we want siege units
return false; return false;
// if this fortress is our first siege unit builder, just try the standard placement as we want siege units
} }
else if (template.hasClass("Market")) // Docks (i.e. NavalMarket) are done before else if (template.hasClass("Market")) // Docks (i.e. NavalMarket) are done before
{ {

View File

@ -85,6 +85,8 @@ m.TransportPlan.prototype.init = function(gameState)
this.units.registerUpdates(); this.units.registerUpdates();
this.ships.registerUpdates(); this.ships.registerUpdates();
this.transportShips.registerUpdates(); this.transportShips.registerUpdates();
this.boardingRange = 18*18; // TODO compute it from the ship clearance and garrison range
}; };
// count available slots // count available slots
@ -274,7 +276,7 @@ m.TransportPlan.prototype.onBoarding = function(gameState)
else else
{ {
let distShip = API3.SquareVectorDistance(self.boardingPos[shipId], ship.position()); let distShip = API3.SquareVectorDistance(self.boardingPos[shipId], ship.position());
if (time - ship.getMetadata(PlayerID, "timeGarrison") > 8 && distShip > 225) if (time - ship.getMetadata(PlayerID, "timeGarrison") > 8 && distShip > self.boardingRange)
{ {
if (!self.nTry[shipId]) if (!self.nTry[shipId])
self.nTry[shipId] = 1; self.nTry[shipId] = 1;
@ -296,7 +298,7 @@ m.TransportPlan.prototype.onBoarding = function(gameState)
let newPos = ent.position(); let newPos = ent.position();
if (oldPos[0] === newPos[0] && oldPos[1] === newPos[1]) if (oldPos[0] === newPos[0] && oldPos[1] === newPos[1])
{ {
if (distShip < 225) // looks like we are blocked ... try to go out of this trap if (distShip < self.boardingRange) // looks like we are blocked ... try to go out of this trap
{ {
if (!self.nTry[ent.id()]) if (!self.nTry[ent.id()])
self.nTry[ent.id()] = 1; self.nTry[ent.id()] = 1;
@ -388,7 +390,7 @@ m.TransportPlan.prototype.getBoardingPos = function(gameState, ship, landIndex,
// require a small distance between all ships of the transport plan to avoid path finder problems // require a small distance between all ships of the transport plan to avoid path finder problems
// this is also used when the ship is blocked and we want to find a new boarding point // this is also used when the ship is blocked and we want to find a new boarding point
for (let shipId in this.boardingPos) for (let shipId in this.boardingPos)
if (this.boardingPos[shipId] !== undefined && API3.SquareVectorDistance(this.boardingPos[shipId], pos) < 225) if (this.boardingPos[shipId] !== undefined && API3.SquareVectorDistance(this.boardingPos[shipId], pos) < this.boardingRange)
dist += 1000000; dist += 1000000;
if (dist > distmin) if (dist > distmin)
continue; continue;
@ -549,7 +551,7 @@ m.TransportPlan.prototype.onSailing = function(gameState)
continue; continue;
} }
if (dist > 225) if (dist > this.boardingRange)
{ {
if (!this.nTry[shipId]) if (!this.nTry[shipId])
this.nTry[shipId] = 1; this.nTry[shipId] = 1;