petra: wip changes to make it less dumb against walls
and start splitting the huge attack function in small parts This was SVN commit r18257.
This commit is contained in:
parent
ba071f8b4e
commit
f1610ce4fa
@ -1,7 +1,8 @@
|
||||
var PETRA = function(m)
|
||||
{
|
||||
|
||||
/* This is an attack plan:
|
||||
/**
|
||||
* This is an attack plan:
|
||||
* It deals with everything in an attack, from picking a target to picking a path to it
|
||||
* 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.
|
||||
@ -199,6 +200,7 @@ m.AttackPlan = function(gameState, Config, uniqueID, type, data)
|
||||
this.captureStrength = 0;
|
||||
this.captureTime = -1000;
|
||||
this.noCapture = new Set(); // list of structure we won't try to capture
|
||||
this.isBlocked = false; // true when this attack faces walls
|
||||
|
||||
return true;
|
||||
};
|
||||
@ -251,8 +253,10 @@ m.AttackPlan.prototype.setPaused = function(boolValue)
|
||||
this.paused = boolValue;
|
||||
};
|
||||
|
||||
// Returns true if the attack can be executed at the current time
|
||||
// Basically it checks we have enough units.
|
||||
/**
|
||||
* Returns true if the attack can be executed at the current time
|
||||
* Basically it checks we have enough units.
|
||||
*/
|
||||
m.AttackPlan.prototype.canStart = function()
|
||||
{
|
||||
if (!this.canBuildUnits)
|
||||
@ -307,7 +311,7 @@ m.AttackPlan.prototype.forceStart = function()
|
||||
}
|
||||
};
|
||||
|
||||
// Adds a build order. If resetQueue is true, this will reset the queue.
|
||||
/** Adds a build order. If resetQueue is true, this will reset the queue.*/
|
||||
m.AttackPlan.prototype.addBuildOrder = function(gameState, name, unitStats, resetQueue)
|
||||
{
|
||||
if (!this.isStarted())
|
||||
@ -345,7 +349,7 @@ m.AttackPlan.prototype.addSiegeUnits = function(gameState)
|
||||
return true;
|
||||
};
|
||||
|
||||
// Three returns possible: 1 is "keep going", 0 is "failed plan", 2 is "start"
|
||||
/** Three returns possible: 1 is "keep going", 0 is "failed plan", 2 is "start" */
|
||||
m.AttackPlan.prototype.updatePreparation = function(gameState)
|
||||
{
|
||||
// the completing step is used to return resources and regroup the units
|
||||
@ -691,10 +695,10 @@ m.AttackPlan.prototype.assignUnits = function(gameState)
|
||||
return added;
|
||||
};
|
||||
|
||||
// Reassign one (at each turn) Cav unit to fasten raid preparation
|
||||
/** Reassign one (at each turn) Cav unit to fasten raid preparation */
|
||||
m.AttackPlan.prototype.reassignCavUnit = function(gameState)
|
||||
{
|
||||
var found;
|
||||
let found;
|
||||
for (let ent of this.unitCollection.values())
|
||||
{
|
||||
if (!ent.position() || ent.getMetadata(PlayerID, "transport") !== undefined)
|
||||
@ -786,10 +790,14 @@ m.AttackPlan.prototype.chooseTarget = function(gameState)
|
||||
|
||||
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)
|
||||
{
|
||||
var targets;
|
||||
this.isBlocked = false;
|
||||
|
||||
let targets;
|
||||
if (this.type === "Raid")
|
||||
targets = this.raidTargetFinder(gameState);
|
||||
else if (this.type === "Rush" || this.type === "Attack")
|
||||
@ -799,27 +807,33 @@ m.AttackPlan.prototype.getNearestTarget = function(gameState, position, sameLand
|
||||
if (!targets.hasEntities())
|
||||
return undefined;
|
||||
|
||||
var land = gameState.ai.accessibility.getAccessValue(position);
|
||||
let land = gameState.ai.accessibility.getAccessValue(position);
|
||||
|
||||
// picking the nearest target
|
||||
var minDist = -1;
|
||||
var target;
|
||||
let target;
|
||||
let minDist = Math.min();
|
||||
for (let ent of targets.values())
|
||||
{
|
||||
if (!ent.position())
|
||||
continue;
|
||||
if (sameLand && gameState.ai.accessibility.getAccessValue(ent.position()) != land)
|
||||
if (sameLand && gameState.ai.accessibility.getAccessValue(ent.position()) !== land)
|
||||
continue;
|
||||
let dist = API3.SquareVectorDistance(ent.position(), position);
|
||||
// in normal attacks, disfavor fields
|
||||
if (this.type !== "Rush" && this.type !== "Raid" && ent.hasClass("Field"))
|
||||
dist += 100000;
|
||||
if (dist < minDist || minDist == -1)
|
||||
if (dist < minDist)
|
||||
{
|
||||
minDist = dist;
|
||||
target = ent;
|
||||
}
|
||||
}
|
||||
if (!target)
|
||||
return undefined;
|
||||
|
||||
// Check that we can reach this target
|
||||
target = this.checkTargetObstruction(gameState, target, position);
|
||||
|
||||
if (!target)
|
||||
return undefined;
|
||||
// Rushes can change their enemy target if nothing found with the preferred enemy
|
||||
@ -827,10 +841,10 @@ m.AttackPlan.prototype.getNearestTarget = function(gameState, position, sameLand
|
||||
return target;
|
||||
};
|
||||
|
||||
// Default target finder aims for conquest critical targets
|
||||
/** Default target finder aims for conquest critical targets */
|
||||
m.AttackPlan.prototype.defaultTargetFinder = function(gameState, playerEnemy)
|
||||
{
|
||||
var targets;
|
||||
let targets;
|
||||
if (gameState.getGameType() === "wonder")
|
||||
{
|
||||
targets = gameState.getEnemyStructures(playerEnemy).filter(API3.Filters.byClass("Wonder"));
|
||||
@ -852,11 +866,11 @@ m.AttackPlan.prototype.defaultTargetFinder = function(gameState, playerEnemy)
|
||||
return targets;
|
||||
};
|
||||
|
||||
// Rush target finder aims at isolated non-defended buildings
|
||||
/** Rush target finder aims at isolated non-defended buildings */
|
||||
m.AttackPlan.prototype.rushTargetFinder = function(gameState, playerEnemy)
|
||||
{
|
||||
var targets = new API3.EntityCollection(gameState.sharedScript);
|
||||
var buildings;
|
||||
let targets = new API3.EntityCollection(gameState.sharedScript);
|
||||
let buildings;
|
||||
if (playerEnemy !== undefined)
|
||||
buildings = gameState.getEnemyStructures(playerEnemy).toEntityArray();
|
||||
else
|
||||
@ -868,8 +882,8 @@ m.AttackPlan.prototype.rushTargetFinder = function(gameState, playerEnemy)
|
||||
if (!this.position)
|
||||
this.position = this.rallyPoint;
|
||||
|
||||
var minDist = Math.min();
|
||||
var target;
|
||||
let target;
|
||||
let minDist = Math.min();
|
||||
for (let building of buildings)
|
||||
{
|
||||
if (building.owner() === 0)
|
||||
@ -911,10 +925,10 @@ m.AttackPlan.prototype.rushTargetFinder = function(gameState, playerEnemy)
|
||||
return targets;
|
||||
};
|
||||
|
||||
// Raid target finder aims at destructing foundations from which our defenseManager has attacked the builders
|
||||
/** Raid target finder aims at destructing foundations from which our defenseManager has attacked the builders */
|
||||
m.AttackPlan.prototype.raidTargetFinder = function(gameState)
|
||||
{
|
||||
var targets = new API3.EntityCollection(gameState.sharedScript);
|
||||
let targets = new API3.EntityCollection(gameState.sharedScript);
|
||||
for (let targetId of gameState.ai.HQ.defenseManager.targetList)
|
||||
{
|
||||
let target = gameState.getEntityById(targetId);
|
||||
@ -924,6 +938,127 @@ m.AttackPlan.prototype.raidTargetFinder = function(gameState)
|
||||
return targets;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check that we can have a path to this target
|
||||
* otherwise we may be blocked by walls and try to react accordingly
|
||||
* This is done only when attacker and target are on the same land
|
||||
*/
|
||||
m.AttackPlan.prototype.checkTargetObstruction = function(gameState, target, position)
|
||||
{
|
||||
|
||||
let targetPos = target.position();
|
||||
if (gameState.ai.accessibility.getAccessValue(targetPos) !== gameState.ai.accessibility.getAccessValue(position))
|
||||
return target;
|
||||
|
||||
let startPos = { "x": position[0], "y": position[1] };
|
||||
let endPos = { "x": targetPos[0], "y": targetPos[1] };
|
||||
let blocker;
|
||||
let path = Engine.ComputePath(startPos, endPos, gameState.getPassabilityClassMask("default"));
|
||||
if (!path.length)
|
||||
return undefined;
|
||||
|
||||
let pathPos = [path[0].x, path[0].y];
|
||||
let dist = API3.VectorDistance(pathPos, targetPos);
|
||||
let radius = target.obstructionRadius();
|
||||
for (let struct of gameState.getEnemyStructures().values())
|
||||
{
|
||||
if (!struct.position() || !struct.get("Obstruction") || struct.hasClass("Field"))
|
||||
continue;
|
||||
// we consider that we can reach the target, but nenetheless check that we did not cross any enemy gate
|
||||
if (dist < radius + 10 && !struct.hasClass("Gates"))
|
||||
continue;
|
||||
// Check that we are really blocked by this structure, i.e. advancing by 1+0.8(clearance)m
|
||||
// in the target direction would bring us inside its obstruction.
|
||||
let structPos = struct.position();
|
||||
let x = pathPos[0] - structPos[0] + 1.8 * (targetPos[0] - pathPos[0]) / dist;
|
||||
let y = pathPos[1] - structPos[1] + 1.8 * (targetPos[1] - pathPos[1]) / dist;
|
||||
|
||||
if (struct.get("Obstruction/Static"))
|
||||
{
|
||||
if (!struct.angle())
|
||||
continue;
|
||||
let angle = struct.angle();
|
||||
let width = +struct.get("Obstruction/Static/@width");
|
||||
let depth = +struct.get("Obstruction/Static/@depth");
|
||||
let cosa = Math.cos(angle);
|
||||
let sina = Math.sin(angle);
|
||||
let u = x * cosa - y * sina;
|
||||
let v = x * sina + y * cosa;
|
||||
if (Math.abs(u) < width/2 && Math.abs(v) < depth/2)
|
||||
{
|
||||
blocker = struct;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (struct.get("Obstruction/Obstructions"))
|
||||
{
|
||||
if (!struct.angle())
|
||||
continue;
|
||||
let angle = struct.angle();
|
||||
let width = +struct.get("Obstruction/Obstructions/Door/@width");
|
||||
let depth = +struct.get("Obstruction/Obstructions/Door/@depth");
|
||||
let doorHalfWidth = width / 2;
|
||||
width += +struct.get("Obstruction/Obstructions/Left/@width");
|
||||
depth = Math.max(depth, +struct.get("Obstruction/Obstructions/Left/@depth"));
|
||||
width += +struct.get("Obstruction/Obstructions/Right/@width");
|
||||
depth = Math.max(depth, +struct.get("Obstruction/Obstructions/Right/@depth"));
|
||||
let cosa = Math.cos(angle);
|
||||
let sina = Math.sin(angle);
|
||||
let u = x * cosa - y * sina;
|
||||
let v = x * sina + y * cosa;
|
||||
if (Math.abs(u) < width/2 && Math.abs(v) < depth/2)
|
||||
{
|
||||
blocker = struct;
|
||||
break;
|
||||
}
|
||||
// check that the path does not cross this gate (could happen if not locked)
|
||||
for (let i = 1; i < path.length; ++i)
|
||||
{
|
||||
let u1 = (path[i-1].x - structPos[0]) * cosa - (path[i-1].y - structPos[1]) * sina;
|
||||
let v1 = (path[i-1].x - structPos[0]) * sina + (path[i-1].y - structPos[1]) * cosa;
|
||||
let u2 = (path[i].x - structPos[0]) * cosa - (path[i].y - structPos[1]) * sina;
|
||||
let v2 = (path[i].x - structPos[0]) * sina + (path[i].y - structPos[1]) * cosa;
|
||||
if (v1 * v2 < 0)
|
||||
{
|
||||
let u0 = (u1*v2 - u2*v1) / (v2-v1);
|
||||
if (Math.abs(u0) > doorHalfWidth)
|
||||
continue;
|
||||
blocker = struct;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (blocker)
|
||||
break;
|
||||
}
|
||||
else if (struct.get("Obstruction/Unit"))
|
||||
{
|
||||
let r = +this.get("Obstruction/Unit/@radius");
|
||||
if (x*x + y*y < r*r)
|
||||
{
|
||||
blocker = struct;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (blocker && blocker.hasClass("StoneWall"))
|
||||
{
|
||||
/* if (this.hasSiegeUnits(gameState))
|
||||
{ */
|
||||
this.isBlocked = true;
|
||||
return blocker;
|
||||
/* }
|
||||
return undefined; */
|
||||
}
|
||||
else if (blocker)
|
||||
{
|
||||
this.isBlocked = true;
|
||||
return blocker;
|
||||
}
|
||||
|
||||
return target;
|
||||
};
|
||||
|
||||
m.AttackPlan.prototype.getPathToTarget = function(gameState)
|
||||
{
|
||||
let startAccess = gameState.ai.accessibility.getAccessValue(this.rallyPoint);
|
||||
@ -948,7 +1083,7 @@ m.AttackPlan.prototype.getPathToTarget = function(gameState)
|
||||
return true;
|
||||
};
|
||||
|
||||
// Set rally point at the border of our territory
|
||||
/** Set rally point at the border of our territory */
|
||||
m.AttackPlan.prototype.setRallyPoint = function(gameState)
|
||||
{
|
||||
for (let i = 0; i < this.path.length; ++i)
|
||||
@ -972,8 +1107,10 @@ m.AttackPlan.prototype.setRallyPoint = function(gameState)
|
||||
}
|
||||
};
|
||||
|
||||
// Executes the attack plan, after this is executed the update function will be run every turn
|
||||
// If we're here, it's because we have enough units.
|
||||
/**
|
||||
* Executes the attack plan, after this is executed the update function will be run every turn
|
||||
* If we're here, it's because we have enough units.
|
||||
*/
|
||||
m.AttackPlan.prototype.StartAttack = function(gameState)
|
||||
{
|
||||
if (this.Config.debug > 1)
|
||||
@ -1030,7 +1167,7 @@ m.AttackPlan.prototype.StartAttack = function(gameState)
|
||||
return true;
|
||||
};
|
||||
|
||||
// Runs every turn after the attack is executed
|
||||
/** Runs every turn after the attack is executed */
|
||||
m.AttackPlan.prototype.update = function(gameState, events)
|
||||
{
|
||||
if (!this.unitCollection.hasEntities())
|
||||
@ -1046,153 +1183,12 @@ m.AttackPlan.prototype.update = function(gameState, events)
|
||||
// we are transporting our units, let's wait
|
||||
// TODO instead of state "arrived", made a state "walking" with a new path
|
||||
if (this.state === "transporting")
|
||||
this.UpdateTransporting(gameState, events, IDs);
|
||||
|
||||
if (this.state === "walking" && !this.UpdateWalking(gameState, events, IDs))
|
||||
{
|
||||
let done = true;
|
||||
for (let ent of this.unitCollection.values())
|
||||
{
|
||||
if (this.Config.debug > 1 && ent.getMetadata(PlayerID, "transport") !== undefined)
|
||||
Engine.PostCommand(PlayerID,{"type": "set-shading-color", "entities": [ent.id()], "rgb": [2,2,0]});
|
||||
else if (this.Config.debug > 1)
|
||||
Engine.PostCommand(PlayerID,{"type": "set-shading-color", "entities": [ent.id()], "rgb": [1,1,1]});
|
||||
if (!done)
|
||||
continue;
|
||||
if (ent.getMetadata(PlayerID, "transport") !== undefined)
|
||||
done = false;
|
||||
}
|
||||
|
||||
if (done)
|
||||
this.state = "arrived";
|
||||
else
|
||||
{
|
||||
// if we are attacked while waiting the rest of the army, retaliate
|
||||
for (let evt of events.Attacked)
|
||||
{
|
||||
if (IDs.indexOf(evt.target) == -1)
|
||||
continue;
|
||||
let attacker = gameState.getEntityById(evt.attacker);
|
||||
if (!attacker || !gameState.getEntityById(evt.target))
|
||||
continue;
|
||||
for (let ent of this.unitCollection.values())
|
||||
{
|
||||
if (ent.getMetadata(PlayerID, "transport") !== undefined)
|
||||
continue;
|
||||
if (!ent.isIdle())
|
||||
continue;
|
||||
ent.attack(attacker.id(), !this.noCapture.has(attacker.id()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this actually doesn't do anything right now.
|
||||
if (this.state === "walking")
|
||||
{
|
||||
// we're marching towards the target
|
||||
// Let's check if any of our unit has been attacked.
|
||||
// In case yes, we'll determine if we're simply off against an enemy army, a lone unit/building
|
||||
// or if we reached the enemy base. Different plans may react differently.
|
||||
let attackedNB = 0;
|
||||
let attackedUnitNB = 0;
|
||||
for (let evt of events.Attacked)
|
||||
{
|
||||
if (IDs.indexOf(evt.target) === -1)
|
||||
continue;
|
||||
let attacker = gameState.getEntityById(evt.attacker);
|
||||
if (attacker && (attacker.owner() !== 0 || this.targetPlayer === 0))
|
||||
{
|
||||
attackedNB++;
|
||||
if (attacker.hasClass("Unit"))
|
||||
attackedUnitNB++;
|
||||
}
|
||||
}
|
||||
// Are we arrived at destination ?
|
||||
let maybe = true;
|
||||
if (!attackedUnitNB)
|
||||
{
|
||||
let siegeNB = 0;
|
||||
for (let ent of this.unitCollection.values())
|
||||
if (this.isSiegeUnit(gameState, ent))
|
||||
siegeNB++;
|
||||
if (!siegeNB)
|
||||
maybe = false;
|
||||
}
|
||||
if (maybe && ((gameState.ai.HQ.territoryMap.getOwner(this.position) === this.targetPlayer && attackedNB > 1) || attackedNB > 3))
|
||||
this.state = "arrived";
|
||||
}
|
||||
|
||||
if (this.state === "walking")
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
// check for stuck siege units
|
||||
let farthest = 0;
|
||||
let farthestEnt;
|
||||
this.unitCollection.filter(API3.Filters.byClass("Siege")).forEach (function (ent) {
|
||||
let dist = API3.SquareVectorDistance(ent.position(), self.position);
|
||||
if (dist < farthest)
|
||||
return;
|
||||
farthest = dist;
|
||||
farthestEnt = ent;
|
||||
});
|
||||
if (farthestEnt)
|
||||
farthestEnt.destroy();
|
||||
}
|
||||
if (gameState.ai.playedTurn % 5 === 0)
|
||||
this.position5TurnsAgo = this.position;
|
||||
|
||||
if (this.lastPosition && API3.SquareVectorDistance(this.position, this.lastPosition) < 20 && this.path.length > 0)
|
||||
{
|
||||
if (!this.path[0][0] || !this.path[0][1])
|
||||
API3.warn("Start: Problem with path " + uneval(this.path));
|
||||
// We're stuck, presumably. Check if there are no walls just close to us. If so, we're arrived, and we're gonna tear down some serious stone.
|
||||
let nexttoWalls = false;
|
||||
gameState.getEnemyStructures().filter(API3.Filters.byClass("StoneWall")).forEach( function (ent) {
|
||||
if (!nexttoWalls && API3.SquareVectorDistance(self.position, ent.position()) < 800)
|
||||
nexttoWalls = true;
|
||||
});
|
||||
// there are walls but we can attack
|
||||
if (nexttoWalls && this.unitCollection.filter(API3.Filters.byCanAttack("StoneWall")).hasEntities())
|
||||
{
|
||||
if (this.Config.debug > 1)
|
||||
API3.warn("Attack Plan " + this.type + " " + this.name + " has met walls and is not happy.");
|
||||
this.state = "arrived";
|
||||
}
|
||||
else if (nexttoWalls) // abort plan
|
||||
{
|
||||
if (this.Config.debug > 1)
|
||||
API3.warn("Attack Plan " + this.type + " " + this.name + " has met walls and gives up.");
|
||||
Engine.ProfileStop();
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
//this.unitCollection.move(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.
|
||||
if (this.state === "walking")
|
||||
{
|
||||
if (API3.SquareVectorDistance(this.position, this.targetPos) < 10000)
|
||||
{
|
||||
if (this.Config.debug > 1)
|
||||
API3.warn("Attack Plan " + this.type + " " + this.name + " has arrived to destination.");
|
||||
this.state = "arrived";
|
||||
}
|
||||
else if (this.path.length && API3.SquareVectorDistance(this.position, this.path[0]) < 1600)
|
||||
{
|
||||
this.path.shift();
|
||||
if (this.path.length)
|
||||
this.unitCollection.move(this.path[0][0], this.path[0][1]);
|
||||
else
|
||||
{
|
||||
if (this.Config.debug > 1)
|
||||
API3.warn("Attack Plan " + this.type + " " + this.name + " has arrived to destination.");
|
||||
this.state = "arrived";
|
||||
}
|
||||
}
|
||||
Engine.ProfileStop();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (this.state === "arrived")
|
||||
@ -1326,7 +1322,13 @@ m.AttackPlan.prototype.update = function(gameState, events)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.isSiegeUnit(gameState, attacker))
|
||||
if (this.isBlocked && !ourUnit.hasClass("Ranged") && attacker.hasClass("Ranged"))
|
||||
{
|
||||
// do not react if our melee units are attacked by ranged one and we are blocked by walls
|
||||
// TODO check that the attacker is from behind the wall
|
||||
continue;
|
||||
}
|
||||
else if (this.isSiegeUnit(gameState, attacker))
|
||||
{ // if our unit is attacked by a siege unit, we'll send some melee units to help it.
|
||||
let collec = this.unitCollection.filter(API3.Filters.byClass("Melee")).filterNearest(ourUnit.position(), 5);
|
||||
for (let ent of collec.values())
|
||||
@ -1480,7 +1482,16 @@ m.AttackPlan.prototype.update = function(gameState, events)
|
||||
ent.setMetadata(PlayerID, "lastAttackPlanUpdateTime", time);
|
||||
let range = 60;
|
||||
let attackTypes = ent.attackTypes();
|
||||
if (attackTypes && attackTypes.indexOf("Ranged") !== -1)
|
||||
if (this.isBlocked)
|
||||
{
|
||||
if (attackTypes && attackTypes.indexOf("Ranged") !== -1)
|
||||
range = ent.attackRange("Ranged").max;
|
||||
else if (attackTypes && attackTypes.indexOf("Melee") !== -1)
|
||||
range = ent.attackRange("Melee").max;
|
||||
else
|
||||
range = 10;
|
||||
}
|
||||
else if (attackTypes && attackTypes.indexOf("Ranged") !== -1)
|
||||
range = 30 + ent.attackRange("Ranged").max;
|
||||
else if (ent.hasClass("Cavalry"))
|
||||
range += 30;
|
||||
@ -1587,6 +1598,8 @@ m.AttackPlan.prototype.update = function(gameState, events)
|
||||
let newTargetId = mUnit[rand].id();
|
||||
ent.attack(newTargetId, !this.noCapture.has(newTargetId));
|
||||
}
|
||||
else if (this.isBlocked)
|
||||
ent.attack(this.target.id(), false);
|
||||
else if (API3.SquareVectorDistance(this.targetPos, ent.position()) > 2500 )
|
||||
{
|
||||
let targetClasses = targetClassesUnit;
|
||||
@ -1604,6 +1617,8 @@ m.AttackPlan.prototype.update = function(gameState, events)
|
||||
else
|
||||
{
|
||||
let mStruct = enemyStructures.filter(function (enemy) {
|
||||
if (self.isBlocked && enemy.id() !== this.target.id())
|
||||
return false;
|
||||
if (!enemy.position() || (enemy.hasClass("StoneWall") && !ent.canAttackClass("StoneWall")))
|
||||
return false;
|
||||
if (API3.SquareVectorDistance(enemy.position(), ent.position()) > range)
|
||||
@ -1672,7 +1687,153 @@ m.AttackPlan.prototype.update = function(gameState, events)
|
||||
return this.unitCollection.length;
|
||||
};
|
||||
|
||||
// reset any units
|
||||
m.AttackPlan.prototype.UpdateTransporting = function(gameState, events, IDs)
|
||||
{
|
||||
let done = true;
|
||||
for (let ent of this.unitCollection.values())
|
||||
{
|
||||
if (this.Config.debug > 1 && ent.getMetadata(PlayerID, "transport") !== undefined)
|
||||
Engine.PostCommand(PlayerID,{"type": "set-shading-color", "entities": [ent.id()], "rgb": [2,2,0]});
|
||||
else if (this.Config.debug > 1)
|
||||
Engine.PostCommand(PlayerID,{"type": "set-shading-color", "entities": [ent.id()], "rgb": [1,1,1]});
|
||||
if (!done)
|
||||
continue;
|
||||
if (ent.getMetadata(PlayerID, "transport") !== undefined)
|
||||
done = false;
|
||||
}
|
||||
|
||||
if (done)
|
||||
{
|
||||
this.state = "arrived";
|
||||
return;
|
||||
}
|
||||
|
||||
// if we are attacked while waiting the rest of the army, retaliate
|
||||
for (let evt of events.Attacked)
|
||||
{
|
||||
if (IDs.indexOf(evt.target) == -1)
|
||||
continue;
|
||||
let attacker = gameState.getEntityById(evt.attacker);
|
||||
if (!attacker || !gameState.getEntityById(evt.target))
|
||||
continue;
|
||||
for (let ent of this.unitCollection.values())
|
||||
{
|
||||
if (ent.getMetadata(PlayerID, "transport") !== undefined)
|
||||
continue;
|
||||
if (!ent.isIdle())
|
||||
continue;
|
||||
ent.attack(attacker.id(), !this.noCapture.has(attacker.id()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
m.AttackPlan.prototype.UpdateWalking = function(gameState, events, IDs)
|
||||
{
|
||||
// we're marching towards the target
|
||||
// Let's check if any of our unit has been attacked.
|
||||
// In case yes, we'll determine if we're simply off against an enemy army, a lone unit/building
|
||||
// or if we reached the enemy base. Different plans may react differently.
|
||||
let attackedNB = 0;
|
||||
let attackedUnitNB = 0;
|
||||
for (let evt of events.Attacked)
|
||||
{
|
||||
if (IDs.indexOf(evt.target) === -1)
|
||||
continue;
|
||||
let attacker = gameState.getEntityById(evt.attacker);
|
||||
if (attacker && (attacker.owner() !== 0 || this.targetPlayer === 0))
|
||||
{
|
||||
attackedNB++;
|
||||
if (attacker.hasClass("Unit"))
|
||||
attackedUnitNB++;
|
||||
}
|
||||
}
|
||||
// Are we arrived at destination ?
|
||||
if (attackedNB > 1 && (attackedUnitNB || this.hasSiegeUnits(gameState)))
|
||||
{
|
||||
if (gameState.ai.HQ.territoryMap.getOwner(this.position) === this.targetPlayer || attackedNB > 3)
|
||||
{
|
||||
this.state = "arrived";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
// check for stuck siege units
|
||||
let farthest = 0;
|
||||
let farthestEnt;
|
||||
for (let ent of this.unitCollection.filter(API3.Filters.byClass("Siege")).values())
|
||||
{
|
||||
let dist = API3.SquareVectorDistance(ent.position(), this.position);
|
||||
if (dist < farthest)
|
||||
continue;
|
||||
farthest = dist;
|
||||
farthestEnt = ent;
|
||||
}
|
||||
if (farthestEnt)
|
||||
farthestEnt.destroy();
|
||||
}
|
||||
if (gameState.ai.playedTurn % 5 === 0)
|
||||
this.position5TurnsAgo = this.position;
|
||||
|
||||
if (this.lastPosition && API3.SquareVectorDistance(this.position, this.lastPosition) < 20 && this.path.length > 0)
|
||||
{
|
||||
if (!this.path[0][0] || !this.path[0][1])
|
||||
API3.warn("Start: Problem with path " + uneval(this.path));
|
||||
// We're stuck, presumably. Check if there are no walls just close to us. If so, we're arrived, and we're gonna tear down some serious stone.
|
||||
let nexttoWalls = false;
|
||||
for (let ent of gameState.getEnemyStructures().filter(API3.Filters.byClass("StoneWall")).values())
|
||||
{
|
||||
if (!nexttoWalls && API3.SquareVectorDistance(this.position, ent.position()) < 800)
|
||||
nexttoWalls = true;
|
||||
}
|
||||
// there are walls but we can attack
|
||||
if (nexttoWalls && this.unitCollection.filter(API3.Filters.byCanAttack("StoneWall")).hasEntities())
|
||||
{
|
||||
if (this.Config.debug > 1)
|
||||
API3.warn("Attack Plan " + this.type + " " + this.name + " has met walls and is not happy.");
|
||||
this.state = "arrived";
|
||||
return true;
|
||||
}
|
||||
else if (nexttoWalls) // abort plan
|
||||
{
|
||||
if (this.Config.debug > 1)
|
||||
API3.warn("Attack Plan " + this.type + " " + this.name + " has met walls and gives up.");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
//this.unitCollection.move(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.
|
||||
if (API3.SquareVectorDistance(this.position, this.targetPos) < 10000)
|
||||
{
|
||||
if (this.Config.debug > 1)
|
||||
API3.warn("Attack Plan " + this.type + " " + this.name + " has arrived to destination.");
|
||||
this.state = "arrived";
|
||||
return true;
|
||||
}
|
||||
else if (this.path.length && API3.SquareVectorDistance(this.position, this.path[0]) < 1600)
|
||||
{
|
||||
this.path.shift();
|
||||
if (this.path.length)
|
||||
this.unitCollection.move(this.path[0][0], this.path[0][1]);
|
||||
else
|
||||
{
|
||||
if (this.Config.debug > 1)
|
||||
API3.warn("Attack Plan " + this.type + " " + this.name + " has arrived to destination.");
|
||||
this.state = "arrived";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/** reset any units */
|
||||
m.AttackPlan.prototype.Abort = function(gameState)
|
||||
{
|
||||
this.unitCollection.unregister();
|
||||
@ -1765,6 +1926,14 @@ m.AttackPlan.prototype.waitingForTransport = function()
|
||||
return false;
|
||||
};
|
||||
|
||||
m.AttackPlan.prototype.hasSiegeUnits = function(gameState)
|
||||
{
|
||||
for (let ent of this.unitCollection.values())
|
||||
if (this.isSiegeUnit(gameState, ent))
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
m.AttackPlan.prototype.hasForceOrder = function(data, value)
|
||||
{
|
||||
for (let ent of this.unitCollection.values())
|
||||
@ -1891,6 +2060,7 @@ m.AttackPlan.prototype.Serialize = function()
|
||||
"captureStrength": this.captureStrength,
|
||||
"captureTime": this.captureTime,
|
||||
"noCapture": this.noCapture,
|
||||
"isBlocked": this.isBlocked,
|
||||
"targetPlayer": this.targetPlayer,
|
||||
"target": this.target !== undefined ? this.target.id() : undefined,
|
||||
"targetPos": this.targetPos,
|
||||
|
Loading…
Reference in New Issue
Block a user