2013-12-30 11:04:59 +01:00
var AEGIS = function ( m )
{
2013-08-17 15:59:53 +02:00
/ * T h i s i s a n a t t a c k p l a n ( d e s p i t e t h e n a m e , i t ' s a r e l i c o f o l d e r t i m e s ) .
* It deals with everything in an attack , from picking a target to picking a path to it
* To making sure units rae built , and pushing elements to the queue manager otherwise
* It also handles the actual attack , though much work is needed on that .
* These should be extremely flexible with only minimal work .
* There is a basic support for naval expeditions here .
* /
2013-12-30 11:04:59 +01:00
m . CityAttack = function CityAttack ( gameState , HQ , Config , uniqueID , targetEnemy , type , targetFinder ) {
2013-08-17 15:59:53 +02:00
2013-12-30 11:04:59 +01:00
this . Config = Config ;
2013-08-17 15:59:53 +02:00
//This is the list of IDs of the units in the plan
this . idList = [ ] ;
this . state = "unexecuted" ;
this . targetPlayer = targetEnemy ;
if ( this . targetPlayer === - 1 || this . targetPlayer === undefined ) {
// let's find our prefered target, basically counting our enemies units.
var enemyCount = { } ;
for ( var i = 1 ; i <= 8 ; i ++ )
enemyCount [ i ] = 0 ;
gameState . getEntities ( ) . forEach ( function ( ent ) { if ( gameState . isEntityEnemy ( ent ) && ent . owner ( ) !== 0 ) { enemyCount [ ent . owner ( ) ] ++ ; } } ) ;
var max = 0 ;
for ( var i in enemyCount )
if ( enemyCount [ i ] > max && + i !== PlayerID )
{
this . targetPlayer = + i ;
max = enemyCount [ i ] ;
}
}
if ( this . targetPlayer === undefined || this . targetPlayer === - 1 )
{
this . failed = true ;
return false ;
}
2014-01-12 02:07:07 +01:00
var CCs = gameState . getOwnStructures ( ) . filter ( API3 . Filters . byClass ( "CivCentre" ) ) ;
2013-08-17 15:59:53 +02:00
if ( CCs . length === 0 )
{
this . failed = true ;
return false ;
}
2013-12-30 11:04:59 +01:00
m . debug ( "Target (" + PlayerID + ") = " + this . targetPlayer ) ;
2013-08-17 15:59:53 +02:00
this . targetFinder = targetFinder || this . defaultTargetFinder ;
this . type = type || "normal" ;
this . name = uniqueID ;
this . healthRecord = [ ] ;
this . timeOfPlanStart = gameState . getTimeElapsed ( ) ; // we get the time at which we decided to start the attack
this . maxPreparationTime = 210 * 1000 ;
// in this case we want to have the attack ready by the 13th minute. Countdown. Minimum 2 minutes.
2013-12-30 11:04:59 +01:00
if ( type !== "superSized" && this . Config . difficulty >= 1 )
2013-08-17 15:59:53 +02:00
this . maxPreparationTime = 780000 - gameState . getTimeElapsed ( ) < 120000 ? 120000 : 780000 - gameState . getTimeElapsed ( ) ;
this . pausingStart = 0 ;
this . totalPausingTime = 0 ;
this . paused = false ;
this . onArrivalReaction = "proceedOnTargets" ;
// priority is relative. If all are 0, the only relevant criteria is "currentsize/targetsize".
// if not, this is a "bonus". The higher the priority, the faster this unit will get built.
// Should really be clamped to [0.1-1.5] (assuming 1 is default/the norm)
// Eg: if all are priority 1, and the siege is 0.5, the siege units will get built
// only once every other category is at least 50% of its target size.
// note: siege build order is currently added by the military manager if a fortress is there.
this . unitStat = { } ;
this . unitStat [ "RangedInfantry" ] = { "priority" : 1 , "minSize" : 4 , "targetSize" : 10 , "batchSize" : 5 , "classes" : [ "Infantry" , "Ranged" ] ,
"interests" : [ [ "canGather" , 2 ] , [ "strength" , 2 ] , [ "cost" , 1 ] ] , "templates" : [ ] } ;
this . unitStat [ "MeleeInfantry" ] = { "priority" : 1 , "minSize" : 4 , "targetSize" : 10 , "batchSize" : 5 , "classes" : [ "Infantry" , "Melee" ] ,
"interests" : [ [ "canGather" , 2 ] , [ "strength" , 2 ] , [ "cost" , 1 ] ] , "templates" : [ ] } ;
this . unitStat [ "MeleeCavalry" ] = { "priority" : 1 , "minSize" : 3 , "targetSize" : 8 , "batchSize" : 3 , "classes" : [ "Cavalry" , "Melee" ] ,
"interests" : [ [ "strength" , 2 ] , [ "cost" , 1 ] ] , "templates" : [ ] } ;
this . unitStat [ "RangedCavalry" ] = { "priority" : 1 , "minSize" : 3 , "targetSize" : 8 , "batchSize" : 3 , "classes" : [ "Cavalry" , "Ranged" ] ,
"interests" : [ [ "strength" , 2 ] , [ "cost" , 1 ] ] , "templates" : [ ] } ;
var priority = 50 ;
if ( type === "rush" ) {
// we have 3 minutes to train infantry.
delete this . unitStat [ "RangedInfantry" ] ;
delete this . unitStat [ "MeleeInfantry" ] ;
delete this . unitStat [ "MeleeCavalry" ] ;
delete this . unitStat [ "RangedCavalry" ] ;
this . unitStat [ "Infantry" ] = { "priority" : 1 , "minSize" : 10 , "targetSize" : 30 , "batchSize" : 1 , "classes" : [ "Infantry" ] , "interests" : [ [ "strength" , 1 ] , [ "cost" , 1 ] ] , "templates" : [ ] } ;
this . maxPreparationTime = 150 * 1000 ;
priority = 120 ;
} else if ( type === "superSized" ) {
// our first attack has started worst case at the 14th minute, we want to attack another time by the 21th minute, so we rock 6.5 minutes
this . maxPreparationTime = 480000 ;
// basically we want a mix of citizen soldiers so our barracks have a purpose, and champion units.
this . unitStat [ "RangedInfantry" ] = { "priority" : 1 , "minSize" : 5 , "targetSize" : 20 , "batchSize" : 5 , "classes" : [ "Infantry" , "Ranged" , "CitizenSoldier" ] ,
"interests" : [ [ "strength" , 3 ] , [ "cost" , 1 ] ] , "templates" : [ ] } ;
this . unitStat [ "MeleeInfantry" ] = { "priority" : 1 , "minSize" : 5 , "targetSize" : 20 , "batchSize" : 5 , "classes" : [ "Infantry" , "Melee" , "CitizenSoldier" ] ,
"interests" : [ [ "strength" , 3 ] , [ "cost" , 1 ] ] , "templates" : [ ] } ;
this . unitStat [ "ChampRangedInfantry" ] = { "priority" : 1 , "minSize" : 5 , "targetSize" : 15 , "batchSize" : 5 , "classes" : [ "Infantry" , "Ranged" , "Champion" ] ,
"interests" : [ [ "strength" , 3 ] , [ "cost" , 1 ] ] , "templates" : [ ] } ;
this . unitStat [ "ChampMeleeInfantry" ] = { "priority" : 1 , "minSize" : 5 , "targetSize" : 15 , "batchSize" : 5 , "classes" : [ "Infantry" , "Melee" , "Champion" ] ,
"interests" : [ [ "strength" , 3 ] , [ "cost" , 1 ] ] , "templates" : [ ] } ;
this . unitStat [ "MeleeCavalry" ] = { "priority" : 1 , "minSize" : 3 , "targetSize" : 18 , "batchSize" : 3 , "classes" : [ "Cavalry" , "Melee" , "CitizenSoldier" ] ,
"interests" : [ [ "strength" , 2 ] , [ "cost" , 1 ] ] , "templates" : [ ] } ;
this . unitStat [ "RangedCavalry" ] = { "priority" : 1 , "minSize" : 3 , "targetSize" : 18 , "batchSize" : 3 , "classes" : [ "Cavalry" , "Ranged" , "CitizenSoldier" ] ,
"interests" : [ [ "strength" , 2 ] , [ "cost" , 1 ] ] , "templates" : [ ] } ;
this . unitStat [ "ChampMeleeInfantry" ] = { "priority" : 0.8 , "minSize" : 3 , "targetSize" : 12 , "batchSize" : 3 , "classes" : [ "Infantry" , "Melee" , "Champion" ] ,
"interests" : [ [ "strength" , 3 ] , [ "cost" , 1 ] ] , "templates" : [ ] } ;
this . unitStat [ "ChampMeleeCavalry" ] = { "priority" : 0.8 , "minSize" : 3 , "targetSize" : 12 , "batchSize" : 3 , "classes" : [ "Cavalry" , "Melee" , "Champion" ] ,
"interests" : [ [ "strength" , 2 ] , [ "cost" , 1 ] ] , "templates" : [ ] } ;
priority = 70 ;
}
// TODO: there should probably be one queue per type of training building
gameState . ai . queueManager . addQueue ( "plan_" + this . name , priority ) ;
this . queue = gameState . ai . queues [ "plan_" + this . name ] ;
gameState . ai . queueManager . addQueue ( "plan_" + this . name + "_champ" , priority ) ;
this . queueChamp = gameState . ai . queues [ "plan_" + this . name + "_champ" ] ;
/ *
this . unitStat [ "Siege" ] [ "filter" ] = function ( ent ) {
var strength = [ ent . attackStrengths ( "Melee" ) [ "crush" ] , ent . attackStrengths ( "Ranged" ) [ "crush" ] ] ;
return ( strength [ 0 ] > 15 || strength [ 1 ] > 15 ) ;
} ; * /
2013-12-30 11:04:59 +01:00
var filter = API3 . Filters . and ( API3 . Filters . byMetadata ( PlayerID , "plan" , this . name ) , API3 . Filters . byOwner ( PlayerID ) ) ;
2014-01-12 02:07:07 +01:00
this . unitCollection = gameState . getOwnUnits ( ) . filter ( filter ) ;
2013-08-17 15:59:53 +02:00
this . unitCollection . registerUpdates ( ) ;
this . unitCollection . length ;
this . unit = { } ;
// each array is [ratio, [associated classes], associated EntityColl, associated unitStat, name ]
this . buildOrder = [ ] ;
// defining the entity collections. Will look for units I own, that are part of this plan.
// Also defining the buildOrders.
for ( var unitCat in this . unitStat ) {
var cat = unitCat ;
var Unit = this . unitStat [ cat ] ;
2013-12-30 11:04:59 +01:00
filter = API3 . Filters . and ( API3 . Filters . byClassesAnd ( Unit [ "classes" ] ) , API3 . Filters . and ( API3 . Filters . byMetadata ( PlayerID , "plan" , this . name ) , API3 . Filters . byOwner ( PlayerID ) ) ) ;
2014-01-12 02:07:07 +01:00
this . unit [ cat ] = gameState . getOwnUnits ( ) . filter ( filter ) ;
2013-08-17 15:59:53 +02:00
this . unit [ cat ] . registerUpdates ( ) ;
this . unit [ cat ] . length ;
this . buildOrder . push ( [ 0 , Unit [ "classes" ] , this . unit [ cat ] , Unit , cat ] ) ;
}
/*if (gameState.getTimeElapsed() > 900000) / / 15 minutes
{
this . unitStat . Cavalry . Ranged [ "minSize" ] = 5 ;
this . unitStat . Cavalry . Melee [ "minSize" ] = 5 ;
this . unitStat . Infantry . Ranged [ "minSize" ] = 10 ;
this . unitStat . Infantry . Melee [ "minSize" ] = 10 ;
this . unitStat . Cavalry . Ranged [ "targetSize" ] = 10 ;
this . unitStat . Cavalry . Melee [ "targetSize" ] = 10 ;
this . unitStat . Infantry . Ranged [ "targetSize" ] = 20 ;
this . unitStat . Infantry . Melee [ "targetSize" ] = 20 ;
this . unitStat . Siege [ "targetSize" ] = 5 ;
this . unitStat . Siege [ "minSize" ] = 2 ;
} else {
this . maxPreparationTime = 180000 ;
} * /
// todo: REACTIVATE (in all caps)
if ( type === "harass_raid" && 0 == 1 )
{
this . targetFinder = this . raidingTargetFinder ;
this . onArrivalReaction = "huntVillagers" ;
this . type = "harass_raid" ;
// This is a Cavalry raid against villagers. A Cavalry Swordsman has a bonus against these. Only build these
this . maxPreparationTime = 180000 ; // 3 minutes.
if ( gameState . playerData . civ === "hele" ) // hellenes have an ealry Cavalry Swordsman
{
this . unitCount . Cavalry . Melee = { "subCat" : [ "Swordsman" ] , "usesSubcategories" : true , "Swordsman" : undefined , "priority" : 1 , "currentAmount" : 0 , "minimalAmount" : 0 , "preferedAmount" : 0 } ;
this . unitCount . Cavalry . Melee . Swordsman = { "priority" : 1 , "currentAmount" : 0 , "minimalAmount" : 4 , "preferedAmount" : 7 , "fallback" : "abort" } ;
} else {
this . unitCount . Cavalry . Melee = { "subCat" : undefined , "usesSubcategories" : false , "priority" : 1 , "currentAmount" : 0 , "minimalAmount" : 4 , "preferedAmount" : 7 } ;
}
this . unitCount . Cavalry . Ranged [ "minimalAmount" ] = 0 ;
this . unitCount . Cavalry . Ranged [ "preferedAmount" ] = 0 ;
this . unitCount . Infantry . Ranged [ "minimalAmount" ] = 0 ;
this . unitCount . Infantry . Ranged [ "preferedAmount" ] = 0 ;
this . unitCount . Infantry . Melee [ "minimalAmount" ] = 0 ;
this . unitCount . Infantry . Melee [ "preferedAmount" ] = 0 ;
this . unitCount . Siege [ "preferedAmount" ] = 0 ;
}
this . anyNotMinimal = true ; // used for support plans
2013-12-30 11:04:59 +01:00
var myFortresses = gameState . getOwnTrainingFacilities ( ) . filter ( API3 . Filters . byClass ( "GarrisonFortress" ) ) ;
2013-08-17 15:59:53 +02:00
if ( myFortresses . length !== 0 )
{
// make this our rallypoint
for ( var i in myFortresses . _entities )
{
if ( myFortresses . _entities [ i ] . position ( ) )
{
this . rallyPoint = myFortresses . _entities [ i ] . position ( ) ;
break ;
}
}
} else {
if ( gameState . ai . pathsToMe . length > 1 )
var position = [ ( gameState . ai . pathsToMe [ 0 ] [ 0 ] + gameState . ai . pathsToMe [ 1 ] [ 0 ] ) / 2.0 , ( gameState . ai . pathsToMe [ 0 ] [ 1 ] + gameState . ai . pathsToMe [ 1 ] [ 1 ] ) / 2.0 ] ;
else if ( gameState . ai . pathsToMe . length !== 0 )
var position = [ gameState . ai . pathsToMe [ 0 ] [ 0 ] , gameState . ai . pathsToMe [ 0 ] [ 1 ] ] ;
else
var position = [ - 1 , - 1 ] ;
if ( gameState . ai . accessibility . getAccessValue ( position ) !== gameState . ai . myIndex )
var position = [ - 1 , - 1 ] ;
var nearestCCArray = CCs . filterNearest ( position , 1 ) . toEntityArray ( ) ;
var CCpos = nearestCCArray [ 0 ] . position ( ) ;
this . rallyPoint = [ 0 , 0 ] ;
if ( position [ 0 ] !== - 1 ) {
this . rallyPoint [ 0 ] = position [ 0 ] ;
this . rallyPoint [ 1 ] = position [ 1 ] ;
} else {
this . rallyPoint [ 0 ] = CCpos [ 0 ] ;
this . rallyPoint [ 1 ] = CCpos [ 1 ] ;
}
if ( type == 'harass_raid' )
{
this . rallyPoint [ 0 ] = ( position [ 0 ] * 3.9 + 0.1 * CCpos [ 0 ] ) / 4.0 ;
this . rallyPoint [ 1 ] = ( position [ 1 ] * 3.9 + 0.1 * CCpos [ 1 ] ) / 4.0 ;
}
}
// some variables for during the attack
this . position5TurnsAgo = [ 0 , 0 ] ;
this . lastPosition = [ 0 , 0 ] ;
this . position = [ 0 , 0 ] ;
this . threatList = [ ] ; // sounds so FBI
this . tactics = undefined ;
this . assignUnits ( gameState ) ;
2013-12-30 11:04:59 +01:00
//m.debug ("Before");
2013-08-17 15:59:53 +02:00
//Engine.DumpHeap();
// get a good path to an estimated target.
2013-12-30 11:04:59 +01:00
this . pathFinder = new API3 . aStarPath ( gameState , false , false , this . targetPlayer ) ;
2013-08-17 15:59:53 +02:00
//Engine.DumpImage("widthmap.png", this.pathFinder.widthMap, this.pathFinder.width,this.pathFinder.height,255);
this . pathWidth = 6 ; // prefer a path far from entities. This will avoid units getting stuck in trees and also results in less straight paths.
this . pathSampling = 2 ;
this . onBoat = false ; // tells us if our units are loaded on boats.
this . needsShip = false ;
2013-12-30 11:04:59 +01:00
//m.debug ("after");
2013-08-17 15:59:53 +02:00
//Engine.DumpHeap();
return true ;
} ;
2013-12-30 11:04:59 +01:00
m . CityAttack . prototype . getName = function ( ) {
2013-08-17 15:59:53 +02:00
return this . name ;
} ;
2013-12-30 11:04:59 +01:00
m . CityAttack . prototype . getType = function ( ) {
2013-08-17 15:59:53 +02:00
return this . type ;
} ;
// Returns true if the attack can be executed at the current time
// Basically his checks we have enough units.
// We run a count of our units.
2013-12-30 11:04:59 +01:00
m . CityAttack . prototype . canStart = function ( gameState ) {
2013-08-17 15:59:53 +02:00
for ( var unitCat in this . unitStat ) {
var Unit = this . unitStat [ unitCat ] ;
if ( this . unit [ unitCat ] . length < Unit [ "minSize" ] )
return false ;
}
return true ;
// TODO: check if our target is valid and a few other stuffs (good moment to attack?)
} ;
2013-12-30 11:04:59 +01:00
m . CityAttack . prototype . isStarted = function ( ) {
2013-08-17 15:59:53 +02:00
if ( ( this . state !== "unexecuted" ) )
2013-12-30 11:04:59 +01:00
m . debug ( "Attack plan already started" ) ;
2013-08-17 15:59:53 +02:00
return ! ( this . state == "unexecuted" ) ;
} ;
2013-12-30 11:04:59 +01:00
m . CityAttack . prototype . isPaused = function ( ) {
2013-08-17 15:59:53 +02:00
return this . paused ;
} ;
2013-12-30 11:04:59 +01:00
m . CityAttack . prototype . setPaused = function ( gameState , boolValue ) {
2013-08-17 15:59:53 +02:00
if ( ! this . paused && boolValue === true ) {
this . pausingStart = gameState . getTimeElapsed ( ) ;
this . paused = true ;
2013-12-30 11:04:59 +01:00
m . debug ( "Pausing attack plan " + this . name ) ;
2013-08-17 15:59:53 +02:00
} else if ( this . paused && boolValue === false ) {
this . totalPausingTime += gameState . getTimeElapsed ( ) - this . pausingStart ;
this . paused = false ;
2013-12-30 11:04:59 +01:00
m . debug ( "Unpausing attack plan " + this . name ) ;
2013-08-17 15:59:53 +02:00
}
} ;
2013-12-30 11:04:59 +01:00
m . CityAttack . prototype . mustStart = function ( gameState ) {
2013-08-17 15:59:53 +02:00
if ( this . isPaused ( ) || this . path === undefined )
return false ;
var MaxReachedEverywhere = true ;
for ( var unitCat in this . unitStat ) {
var Unit = this . unitStat [ unitCat ] ;
if ( this . unit [ unitCat ] . length < Unit [ "targetSize" ] ) {
MaxReachedEverywhere = false ;
}
}
if ( MaxReachedEverywhere || ( gameState . getPopulationMax ( ) - gameState . getPopulation ( ) < 10 && this . canStart ( gameState ) ) )
return true ;
return ( this . maxPreparationTime + this . timeOfPlanStart + this . totalPausingTime < gameState . getTimeElapsed ( ) ) ;
} ;
// Adds a build order. If resetQueue is true, this will reset the queue.
2013-12-30 11:04:59 +01:00
m . CityAttack . prototype . addBuildOrder = function ( gameState , name , unitStats , resetQueue ) {
2013-08-17 15:59:53 +02:00
if ( ! this . isStarted ( ) )
{
2013-12-30 11:04:59 +01:00
m . debug ( "Adding a build order for " + name ) ;
2013-08-17 15:59:53 +02:00
// no minsize as we don't want the plan to fail at the last minute though.
this . unitStat [ name ] = unitStats ;
var Unit = this . unitStat [ name ] ;
2013-12-30 11:04:59 +01:00
var filter = API3 . Filters . and ( API3 . Filters . byClassesAnd ( Unit [ "classes" ] ) , API3 . Filters . and ( API3 . Filters . byMetadata ( PlayerID , "plan" , this . name ) , API3 . Filters . byOwner ( PlayerID ) ) ) ;
2014-01-12 02:07:07 +01:00
this . unit [ name ] = gameState . getOwnUnits ( ) . filter ( filter ) ;
2013-08-17 15:59:53 +02:00
this . unit [ name ] . registerUpdates ( ) ;
this . buildOrder . push ( [ 0 , Unit [ "classes" ] , this . unit [ name ] , Unit , name ] ) ;
if ( resetQueue )
{
this . queue . empty ( ) ;
this . queueChamp . empty ( ) ;
}
}
} ;
// 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.
2013-12-30 11:04:59 +01:00
m . CityAttack . prototype . updatePreparation = function ( gameState , HQ , events ) {
2013-08-17 15:59:53 +02:00
var self = this ;
if ( this . path == undefined || this . target == undefined || this . path === "toBeContinued" ) {
// find our target
if ( this . target == undefined )
{
2013-09-29 15:32:52 +02:00
var targets = this . targetFinder ( gameState , HQ ) ;
2013-08-17 15:59:53 +02:00
if ( targets . length === 0 )
2013-09-29 15:32:52 +02:00
targets = this . defaultTargetFinder ( gameState , HQ ) ;
2013-08-17 15:59:53 +02:00
if ( targets . length !== 0 ) {
2013-12-30 11:04:59 +01:00
m . debug ( "Aiming for " + targets ) ;
2013-08-17 15:59:53 +02:00
// picking a target
var maxDist = - 1 ;
var index = 0 ;
for ( var i in targets . _entities )
{
// we're sure it has a position has TargetFinder already checks that.
2013-12-30 11:04:59 +01:00
var dist = API3 . SquareVectorDistance ( targets . _entities [ i ] . position ( ) , this . rallyPoint ) ;
2013-08-17 15:59:53 +02:00
if ( dist < maxDist || maxDist === - 1 )
{
maxDist = dist ;
index = i ;
}
}
this . target = targets . _entities [ index ] ;
this . targetPos = this . target . position ( ) ;
}
}
// when we have a target, we path to it.
// I'd like a good high width sampling first.
// Thus I will not do everything at once.
// It will probably carry over a few turns but that's no issue.
if ( this . path === undefined )
2013-09-29 15:32:52 +02:00
this . path = this . pathFinder . getPath ( this . rallyPoint , this . targetPos , this . pathSampling , this . pathWidth , 175 ) ; //,gameState);
2013-08-17 15:59:53 +02:00
else if ( this . path === "toBeContinued" )
this . path = this . pathFinder . continuePath ( ) ; //gameState);
if ( this . path === undefined ) {
if ( this . pathWidth == 6 )
{
this . pathWidth = 2 ;
delete this . path ;
} else {
delete this . pathFinder ;
return 3 ; // no path.
}
} else if ( this . path === "toBeContinued" ) {
// carry on.
} else if ( this . path [ 1 ] === true && this . pathWidth == 2 ) {
// okay so we need a ship.
// Basically we'll add it as a new class to train compulsorily, and we'll recompute our path.
2013-09-29 15:32:52 +02:00
if ( ! gameState . ai . HQ . waterMap )
2013-08-17 15:59:53 +02:00
{
2013-12-30 11:04:59 +01:00
m . debug ( "This is actually a water map." ) ;
2013-09-29 15:32:52 +02:00
gameState . ai . HQ . waterMap = true ;
return 0 ;
2013-08-17 15:59:53 +02:00
}
2013-12-30 11:04:59 +01:00
m . debug ( "We need a ship." ) ;
2013-08-17 15:59:53 +02:00
this . needsShip = true ;
this . pathWidth = 3 ;
this . pathSampling = 3 ;
this . path = this . path [ 0 ] . reverse ( ) ;
delete this . pathFinder ;
// Change the rally point to something useful (should avoid rams getting stuck in houses in my territory, which is dumb.)
for ( var i = 0 ; i < this . path . length ; ++ i )
{
// my pathfinder returns arrays in arrays in arrays.
var waypointPos = this . path [ i ] [ 0 ] ;
2013-12-30 11:04:59 +01:00
var territory = m . createTerritoryMap ( gameState ) ;
2013-08-17 15:59:53 +02:00
if ( territory . getOwner ( waypointPos ) !== PlayerID || this . path [ i ] [ 1 ] === true )
{
// if we're suddenly out of our territory or this is the point where we change transportation method.
if ( i !== 0 )
this . rallyPoint = this . path [ i - 1 ] [ 0 ] ;
else
this . rallyPoint = this . path [ 0 ] [ 0 ] ;
2013-09-29 15:32:52 +02:00
if ( i >= 1 )
this . path . splice ( 0 , i - 1 ) ;
2013-08-17 15:59:53 +02:00
break ;
}
}
} else if ( this . path [ 1 ] === true && this . pathWidth == 6 ) {
// retry with a smaller pathwidth:
this . pathWidth = 2 ;
delete this . path ;
} else {
this . path = this . path [ 0 ] . reverse ( ) ;
delete this . pathFinder ;
// Change the rally point to something useful (should avoid rams getting stuck in houses in my territory, which is dumb.)
for ( var i = 0 ; i < this . path . length ; ++ i )
{
// my pathfinder returns arrays in arrays in arrays.
var waypointPos = this . path [ i ] [ 0 ] ;
2013-12-30 11:04:59 +01:00
var territory = m . createTerritoryMap ( gameState ) ;
2013-08-17 15:59:53 +02:00
if ( territory . getOwner ( waypointPos ) !== PlayerID || this . path [ i ] [ 1 ] === true )
{
// if we're suddenly out of our territory or this is the point where we change transportation method.
if ( i !== 0 )
{
this . rallyPoint = this . path [ i - 1 ] [ 0 ] ;
} else
this . rallyPoint = this . path [ 0 ] [ 0 ] ;
if ( i >= 1 )
this . path . splice ( 0 , i - 1 ) ;
break ;
}
}
}
}
Engine . ProfileStart ( "Update Preparation" ) ;
// special case: if we're reached max pop, and we can start the plan, start it.
if ( ( gameState . getPopulationMax ( ) - gameState . getPopulation ( ) < 10 ) && this . canStart ( ) )
{
this . assignUnits ( gameState ) ;
this . queue . empty ( ) ;
this . queueChamp . empty ( ) ;
if ( gameState . ai . playedTurn % 5 == 0 )
this . AllToRallyPoint ( gameState , true ) ;
} else if ( this . mustStart ( gameState ) && ( gameState . countOwnQueuedEntitiesWithMetadata ( "plan" , + this . name ) > 0 ) ) {
// keep on while the units finish being trained, then we'll start
this . assignUnits ( gameState ) ;
this . queue . empty ( ) ;
this . queueChamp . empty ( ) ;
if ( gameState . ai . playedTurn % 5 == 0 ) {
this . AllToRallyPoint ( gameState , true ) ;
// TODO: should use this time to let gatherers deposit resources.
}
Engine . ProfileStop ( ) ;
return 1 ;
} else if ( ! this . mustStart ( gameState ) ) {
// We still have time left to recruit units and do stuffs.
// let's sort by training advancement, ie 'current size / target size'
// count the number of queued units too.
// substract priority.
this . buildOrder . sort ( function ( a , b ) { //}) {
var aQueued = gameState . countOwnQueuedEntitiesWithMetadata ( "special" , "Plan_" + self . name + "_" + a [ 4 ] ) ;
2013-09-29 15:32:52 +02:00
aQueued += self . queue . countQueuedUnitsWithMetadata ( "special" , "Plan_" + self . name + "_" + a [ 4 ] ) ;
aQueued += self . queueChamp . countQueuedUnitsWithMetadata ( "special" , "Plan_" + self . name + "_" + a [ 4 ] ) ;
2013-08-17 15:59:53 +02:00
a [ 0 ] = ( a [ 2 ] . length + aQueued ) / a [ 3 ] [ "targetSize" ] ;
var bQueued = gameState . countOwnQueuedEntitiesWithMetadata ( "special" , "Plan_" + self . name + "_" + b [ 4 ] ) ;
2013-09-29 15:32:52 +02:00
bQueued += self . queue . countQueuedUnitsWithMetadata ( "special" , "Plan_" + self . name + "_" + b [ 4 ] ) ;
bQueued += self . queueChamp . countQueuedUnitsWithMetadata ( "special" , "Plan_" + self . name + "_" + b [ 4 ] ) ;
2013-08-17 15:59:53 +02:00
b [ 0 ] = ( b [ 2 ] . length + bQueued ) / b [ 3 ] [ "targetSize" ] ;
a [ 0 ] -= a [ 3 ] [ "priority" ] ;
b [ 0 ] -= b [ 3 ] [ "priority" ] ;
return ( a [ 0 ] ) - ( b [ 0 ] ) ;
} ) ;
this . assignUnits ( gameState ) ;
if ( gameState . ai . playedTurn % 5 == 0 ) {
this . AllToRallyPoint ( gameState , false ) ;
this . unitCollection . setStance ( "standground" ) ; // make sure units won't disperse out of control
}
Engine . ProfileStart ( "Creating units." ) ;
// gets the number in training of the same kind as the first one.
var specialData = "Plan_" + this . name + "_" + this . buildOrder [ 0 ] [ 4 ] ;
var inTraining = gameState . countOwnQueuedEntitiesWithMetadata ( "special" , specialData ) ;
2013-09-29 15:32:52 +02:00
var queued = this . queue . countQueuedUnitsWithMetadata ( "special" , specialData ) + this . queueChamp . countQueuedUnitsWithMetadata ( "special" , specialData )
2013-08-17 15:59:53 +02:00
if ( queued + inTraining + this . buildOrder [ 0 ] [ 2 ] . length <= this . buildOrder [ 0 ] [ 3 ] [ "targetSize" ] ) {
// find the actual queue we want
var queue = this . queue ;
if ( this . buildOrder [ 0 ] [ 3 ] [ "classes" ] . indexOf ( "Champion" ) !== - 1 )
queue = this . queueChamp ;
if ( this . buildOrder [ 0 ] [ 0 ] < 1 && queue . length ( ) <= 5 ) {
2013-09-29 15:32:52 +02:00
var template = HQ . findBestTrainableSoldier ( gameState , this . buildOrder [ 0 ] [ 1 ] , this . buildOrder [ 0 ] [ 3 ] [ "interests" ] ) ;
2013-12-30 11:04:59 +01:00
//m.debug ("tried " + uneval(this.buildOrder[0][1]) +", and " + template);
2013-08-17 15:59:53 +02:00
// HACK (TODO replace) : if we have no trainable template... Then we'll simply remove the buildOrder, effectively removing the unit from the plan.
if ( template === undefined ) {
// TODO: this is a complete hack.
delete this . unitStat [ this . buildOrder [ 0 ] [ 4 ] ] ; // deleting the associated unitstat.
this . buildOrder . splice ( 0 , 1 ) ;
} else {
var max = this . buildOrder [ 0 ] [ 3 ] [ "batchSize" ] ;
// TODO: this should be plan dependant.
if ( gameState . getTimeElapsed ( ) > 1800000 )
max *= 2 ;
2013-09-29 15:32:52 +02:00
if ( gameState . getTemplate ( template ) . hasClass ( "CitizenSoldier" ) )
2013-12-30 11:04:59 +01:00
queue . addItem ( new m . TrainingPlan ( gameState , template , { "role" : "worker" , "plan" : this . name , "special" : specialData , "base" : 1 } , this . buildOrder [ 0 ] [ 3 ] [ "batchSize" ] , max ) ) ;
2013-08-17 15:59:53 +02:00
else
2013-12-30 11:04:59 +01:00
queue . addItem ( new m . TrainingPlan ( gameState , template , { "role" : "attack" , "plan" : this . name , "special" : specialData , "base" : 1 } , this . buildOrder [ 0 ] [ 3 ] [ "batchSize" ] , max ) ) ;
2013-08-17 15:59:53 +02:00
}
}
}
/ *
if ( ! this . startedPathing && this . path === undefined ) {
// find our target
2013-09-29 15:32:52 +02:00
var targets = this . targetFinder ( gameState , HQ ) ;
2013-08-17 15:59:53 +02:00
if ( targets . length === 0 ) {
2013-09-29 15:32:52 +02:00
targets = this . defaultTargetFinder ( gameState , HQ ) ;
2013-08-17 15:59:53 +02:00
}
if ( targets . length ) {
this . targetPos = undefined ;
var count = 0 ;
while ( ! this . targetPos ) {
var rand = Math . floor ( ( Math . random ( ) * targets . length ) ) ;
var target = targets . toEntityArray ( ) [ rand ] ;
this . targetPos = target . position ( ) ;
count ++ ;
if ( count > 1000 ) {
2013-12-30 11:04:59 +01:00
m . debug ( "No target with a valid position found" ) ;
2013-08-17 15:59:53 +02:00
return false ;
}
}
this . startedPathing = true ;
// Start pathfinding using the optimized version, with a minimal sampling of 2
this . pathFinder . getPath ( this . rallyPoint , this . targetPos , false , 2 , gameState ) ;
}
} else if ( this . startedPathing ) {
var path = this . pathFinder . continuePath ( gameState ) ;
if ( path !== "toBeContinued" ) {
this . startedPathing = false ;
this . path = path ;
2013-12-30 11:04:59 +01:00
m . debug ( "Pathing ended" ) ;
2013-08-17 15:59:53 +02:00
}
}
* /
Engine . ProfileStop ( ) ;
Engine . ProfileStop ( ) ;
// can happen for now
if ( this . buildOrder . length === 0 ) {
2013-12-30 11:04:59 +01:00
m . debug ( "Ending plan: no build orders" ) ;
2013-08-17 15:59:53 +02:00
return 0 ; // will abort the plan, should return something else
}
return 1 ;
}
this . unitCollection . forEach ( function ( entity ) { entity . setMetadata ( PlayerID , "role" , "attack" ) ; } ) ;
Engine . ProfileStop ( ) ;
// if we're here, it means we must start (and have no units in training left).
// if we can, do, else, abort.
if ( this . canStart ( gameState ) )
return 2 ;
else
return 0 ;
return 0 ;
} ;
2013-12-30 11:04:59 +01:00
m . CityAttack . prototype . assignUnits = function ( gameState ) {
2013-08-17 15:59:53 +02:00
var self = this ;
// TODO: assign myself units that fit only, right now I'm getting anything.
// Assign all no-roles that fit (after a plan aborts, for example).
2014-01-12 02:07:07 +01:00
var NoRole = gameState . getOwnEntitiesByRole ( undefined , false ) ;
2013-08-17 15:59:53 +02:00
if ( this . type === "rush" )
2014-01-12 02:07:07 +01:00
NoRole = gameState . getOwnEntitiesByRole ( "worker" , true ) ;
2013-08-17 15:59:53 +02:00
NoRole . forEach ( function ( ent ) {
if ( ent . hasClass ( "Unit" ) && ent . attackTypes ( ) !== undefined )
{
if ( ent . hasClasses ( [ "CitizenSoldier" , "Infantry" ] ) )
ent . setMetadata ( PlayerID , "role" , "worker" ) ;
else
ent . setMetadata ( PlayerID , "role" , "attack" ) ;
ent . setMetadata ( PlayerID , "plan" , self . name ) ;
}
} ) ;
} ;
// this sends a unit by ID back to the "rally point"
2013-12-30 11:04:59 +01:00
m . CityAttack . prototype . ToRallyPoint = function ( gameState , id )
2013-08-17 15:59:53 +02:00
{
// Move back to nearest rallypoint
gameState . getEntityById ( id ) . move ( this . rallyPoint [ 0 ] , this . rallyPoint [ 1 ] ) ;
}
// this sends all units back to the "rally point" by entity collections.
// It doesn't disturb ones that could be currently defending, even if the plan is not (yet) paused.
2013-12-30 11:04:59 +01:00
m . CityAttack . prototype . AllToRallyPoint = function ( gameState , evenWorkers ) {
2013-08-17 15:59:53 +02:00
var self = this ;
if ( evenWorkers ) {
for ( var unitCat in this . unit ) {
this . unit [ unitCat ] . forEach ( function ( ent ) {
2013-09-29 15:32:52 +02:00
if ( ent . getMetadata ( PlayerID , "role" ) != "defence" )
2013-08-17 15:59:53 +02:00
{
ent . setMetadata ( PlayerID , "role" , "attack" ) ;
ent . move ( self . rallyPoint [ 0 ] , self . rallyPoint [ 1 ] ) ;
}
} ) ;
}
} else {
for ( var unitCat in this . unit ) {
this . unit [ unitCat ] . forEach ( function ( ent ) {
2013-09-29 15:32:52 +02:00
if ( ent . getMetadata ( PlayerID , "role" ) != "worker" && ent . getMetadata ( PlayerID , "role" ) != "defence" )
2013-08-17 15:59:53 +02:00
ent . move ( self . rallyPoint [ 0 ] , self . rallyPoint [ 1 ] ) ;
} ) ;
}
}
}
// Default target finder aims for conquest critical targets
2013-12-30 11:04:59 +01:00
m . CityAttack . prototype . defaultTargetFinder = function ( gameState , HQ ) {
2013-08-17 15:59:53 +02:00
var targets = undefined ;
2014-01-12 18:34:18 +01:00
targets = gameState . getEnemyStructures ( this . targetPlayer ) . filter ( API3 . Filters . byClass ( "CivCentre" ) ) ;
2013-08-17 15:59:53 +02:00
if ( targets . length == 0 ) {
2014-01-12 18:34:18 +01:00
targets = gameState . getEnemyStructures ( this . targetPlayer ) . filter ( API3 . Filters . byClass ( "ConquestCritical" ) ) ;
2013-08-17 15:59:53 +02:00
}
// If there's nothing, attack anything else that's less critical
if ( targets . length == 0 ) {
2014-01-12 18:34:18 +01:00
targets = gameState . getEnemyStructures ( this . targetPlayer ) . filter ( API3 . Filters . byClass ( "Town" ) ) ;
2013-08-17 15:59:53 +02:00
}
if ( targets . length == 0 ) {
2014-01-12 18:34:18 +01:00
targets = gameState . getEnemyStructures ( this . targetPlayer ) . filter ( API3 . Filters . byClass ( "Village" ) ) ;
2013-08-17 15:59:53 +02:00
}
// no buildings, attack anything conquest critical, even units (it's assuming it won't move).
if ( targets . length == 0 ) {
2014-01-12 18:34:18 +01:00
targets = gameState . getEnemyEntities ( this . targetPlayer ) . filter ( API3 . Filters . byClass ( "ConquestCritical" ) ) ;
2013-08-17 15:59:53 +02:00
}
return targets ;
} ;
// tupdate
2013-12-30 11:04:59 +01:00
m . CityAttack . prototype . raidingTargetFinder = function ( gameState , HQ , Target ) {
2013-08-17 15:59:53 +02:00
var targets = undefined ;
if ( Target == "villager" )
{
// let's aim for any resource dropsite. We assume villagers are in the neighborhood (note: the human player could certainly troll us... small (scouting) TODO here.)
targets = gameState . entities . filter ( function ( ent ) {
return ( ent . hasClass ( "Structure" ) && ent . resourceDropsiteTypes ( ) !== undefined && ! ent . hasClass ( "CivCentre" ) && ent . owner ( ) === this . targetPlayer && ent . position ( ) ) ;
} ) ;
if ( targets . length == 0 ) {
targets = gameState . entities . filter ( function ( ent ) {
return ( ent . hasClass ( "CivCentre" ) && ent . resourceDropsiteTypes ( ) !== undefined && ent . owner ( ) === this . targetPlayer && ent . position ( ) ) ;
} ) ;
}
if ( targets . length == 0 ) {
// if we're here, it means they also don't have no CC... So I'll just take any building at this point.
targets = gameState . entities . filter ( function ( ent ) {
return ( ent . hasClass ( "Structure" ) && ent . owner ( ) === this . targetPlayer && ent . position ( ) ) ;
} ) ;
}
return targets ;
} else {
2013-09-29 15:32:52 +02:00
return this . defaultTargetFinder ( gameState , HQ ) ;
2013-08-17 15:59:53 +02:00
}
} ;
// 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 in our IDlist enough units.
// now the IDlist units are treated turn by turn
2013-12-30 11:04:59 +01:00
m . CityAttack . prototype . StartAttack = function ( gameState , HQ ) {
2013-08-17 15:59:53 +02:00
// check we have a target and a path.
if ( this . targetPos && this . path !== undefined ) {
// 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 + "_champ" ) ;
var curPos = this . unitCollection . getCentrePosition ( ) ;
this . unitCollection . forEach ( function ( ent ) { ent . setMetadata ( PlayerID , "subrole" , "walking" ) ; ent . setMetadata ( PlayerID , "role" , "attack" ) ; } ) ;
2013-09-29 15:32:52 +02:00
// optimize our collection now.
this . unitCollection . freeze ( ) ;
this . unitCollection . allowQuickIter ( ) ;
2013-08-17 15:59:53 +02:00
2013-09-29 15:32:52 +02:00
this . unitCollection . move ( this . path [ 0 ] [ 0 ] [ 0 ] , this . path [ 0 ] [ 0 ] [ 1 ] ) ;
2013-08-17 15:59:53 +02:00
this . unitCollection . setStance ( "aggressive" ) ;
2013-12-30 11:04:59 +01:00
this . unitCollection . filter ( API3 . Filters . byClass ( "Siege" ) ) . setStance ( "defensive" ) ;
2013-08-17 15:59:53 +02:00
this . state = "walking" ;
} else {
gameState . ai . gameFinished = true ;
2013-12-30 11:04:59 +01:00
m . debug ( "I do not have any target. So I'll just assume I won the game." ) ;
2013-08-17 15:59:53 +02:00
return false ;
}
return true ;
} ;
// Runs every turn after the attack is executed
2013-12-30 11:04:59 +01:00
m . CityAttack . prototype . update = function ( gameState , HQ , events ) {
2013-08-17 15:59:53 +02:00
var self = this ;
Engine . ProfileStart ( "Update Attack" ) ;
// we're marching towards the target
// Check for attacked units in our band.
var bool _attacked = false ;
// raids don't care about attacks much
if ( this . unitCollection . length === 0 ) {
Engine . ProfileStop ( ) ;
return 0 ;
}
this . position = this . unitCollection . getCentrePosition ( ) ;
var IDs = this . unitCollection . toIdArray ( ) ;
// this actually doesn't do anything right now.
if ( this . state === "walking" ) {
var attackedNB = 0 ;
var toProcess = { } ;
var armyToProcess = { } ;
// 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/builing
// or if we reached the enemy base. Different plans may react differently.
2014-01-10 02:46:27 +01:00
var attackedEvents = events [ "Attacked" ] ;
for ( var key in attackedEvents ) {
var e = attackedEvents [ key ] ;
if ( IDs . indexOf ( e . target ) !== - 1 ) {
var attacker = gameState . getEntityById ( e . attacker ) ;
var ourUnit = gameState . getEntityById ( e . target ) ;
if ( attacker && attacker . position ( ) && attacker . hasClass ( "Unit" ) && attacker . owner ( ) != 0 && attacker . owner ( ) != PlayerID ) {
var territoryMap = m . createTerritoryMap ( gameState ) ;
if ( + territoryMap . point ( attacker . position ( ) ) - 64 === + this . targetPlayer )
{
attackedNB ++ ;
2013-08-17 15:59:53 +02:00
}
2014-01-10 02:46:27 +01:00
//if (HQ.enemyWatchers[attacker.owner()]) {
//toProcess[attacker.id()] = attacker;
//var armyID = HQ.enemyWatchers[attacker.owner()].getArmyFromMember(attacker.id());
//armyToProcess[armyID[0]] = armyID[1];
//}
2013-08-17 15:59:53 +02:00
}
2014-01-10 02:46:27 +01:00
// if we're being attacked by a building, flee.
if ( attacker && ourUnit && attacker . hasClass ( "Structure" ) ) {
ourUnit . flee ( attacker ) ;
}
2013-08-17 15:59:53 +02:00
}
}
if ( attackedNB > 4 ) {
2013-12-30 11:04:59 +01:00
m . debug ( "Attack Plan " + this . type + " " + this . name + " has arrived to destination." ) ;
2013-08-17 15:59:53 +02:00
// we must assume we've arrived at the end of the trail.
this . state = "arrived" ;
}
/ *
} && this . type !== "harass_raid" ) { // walking toward the target
var sumAttackerPos = [ 0 , 0 ] ;
var numAttackers = 0 ;
// let's check if one of our unit is not under attack, by any chance.
for ( var key in events ) {
var e = events [ key ] ;
if ( e . type === "Attacked" && e . msg ) {
if ( this . unitCollection . toIdArray ( ) . indexOf ( e . msg . target ) !== - 1 ) {
var attacker = HeadQuarters . entity ( e . msg . attacker ) ;
if ( attacker && attacker . position ( ) ) {
sumAttackerPos [ 0 ] += attacker . position ( ) [ 0 ] ;
sumAttackerPos [ 1 ] += attacker . position ( ) [ 1 ] ;
numAttackers += 1 ;
bool _attacked = true ;
// todo: differentiate depending on attacker type... If it's a ship, let's not do anythin, a building, depends on the attack type/
if ( this . threatList . indexOf ( e . msg . attacker ) === - 1 )
{
var enemySoldiers = HeadQuarters . getEnemySoldiers ( ) . toEntityArray ( ) ;
for ( var j in enemySoldiers )
{
var enemy = enemySoldiers [ j ] ;
if ( enemy . position ( ) === undefined ) // likely garrisoned
continue ;
2013-12-30 11:04:59 +01:00
if ( m . inRange ( enemy . position ( ) , attacker . position ( ) , 1000 ) && this . threatList . indexOf ( enemy . id ( ) ) === - 1 )
2013-08-17 15:59:53 +02:00
this . threatList . push ( enemy . id ( ) ) ;
}
this . threatList . push ( e . msg . attacker ) ;
}
}
}
}
}
if ( bool _attacked > 0 ) {
var avgAttackerPos = [ sumAttackerPos [ 0 ] / numAttackers , sumAttackerPos [ 1 ] / numAttackers ] ;
units . move ( avgAttackerPos [ 0 ] , avgAttackerPos [ 1 ] ) ; // let's run towards it.
this . tactics = new Tactics ( gameState , HeadQuarters , this . idList , this . threatList , true ) ;
this . state = "attacking_threat" ;
}
} else if ( this . state === "attacking_threat" ) {
this . tactics . eventMetadataCleanup ( events , HeadQuarters ) ;
var removeList = this . tactics . removeTheirDeads ( HeadQuarters ) ;
this . tactics . removeMyDeads ( HeadQuarters ) ;
for ( var i in removeList ) {
this . threatList . splice ( this . threatList . indexOf ( removeList [ i ] ) , 1 ) ;
}
if ( this . threatList . length <= 0 )
{
this . tactics . disband ( HeadQuarters , events ) ;
this . tactics = undefined ;
this . state = "walking" ;
units . move ( this . path [ 0 ] [ 0 ] , this . path [ 0 ] [ 1 ] ) ;
} else
{
this . tactics . reassignAttacks ( HeadQuarters ) ;
}
} * /
}
if ( this . state === "walking" ) {
2013-09-29 15:32:52 +02:00
this . position = this . unitCollection . getCentrePosition ( ) ;
2013-08-17 15:59:53 +02:00
// probably not too good.
if ( ! this . position ) {
Engine . ProfileStop ( ) ;
return undefined ; // should spawn an error.
}
// basically haven't moved an inch: very likely stuck)
2013-12-30 11:04:59 +01:00
if ( API3 . SquareVectorDistance ( this . position , this . position5TurnsAgo ) < 10 && this . path . length > 0 && gameState . ai . playedTurn % 5 === 0 ) {
2013-08-17 15:59:53 +02:00
// check for stuck siege units
2013-12-30 11:04:59 +01:00
var sieges = this . unitCollection . filter ( API3 . Filters . byClass ( "Siege" ) ) ;
2013-08-17 15:59:53 +02:00
var farthest = 0 ;
var farthestEnt = - 1 ;
sieges . forEach ( function ( ent ) {
2013-12-30 11:04:59 +01:00
if ( API3 . SquareVectorDistance ( ent . position ( ) , self . position ) > farthest )
2013-08-17 15:59:53 +02:00
{
2013-12-30 11:04:59 +01:00
farthest = API3 . SquareVectorDistance ( ent . position ( ) , self . position ) ;
2013-08-17 15:59:53 +02:00
farthestEnt = ent ;
}
} ) ;
if ( farthestEnt !== - 1 )
farthestEnt . destroy ( ) ;
}
if ( gameState . ai . playedTurn % 5 === 0 )
this . position5TurnsAgo = this . position ;
2013-12-30 11:04:59 +01:00
if ( this . lastPosition && API3 . SquareVectorDistance ( this . position , this . lastPosition ) < 20 && this . path . length > 0 ) {
2013-09-29 15:32:52 +02:00
this . unitCollection . moveIndiv ( this . path [ 0 ] [ 0 ] [ 0 ] , this . path [ 0 ] [ 0 ] [ 1 ] ) ;
2013-08-17 15:59:53 +02:00
// 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.
2013-12-30 11:04:59 +01:00
var walls = gameState . getEnemyEntities ( ) . filter ( API3 . Filters . and ( API3 . Filters . byOwner ( this . targetPlayer ) , API3 . Filters . byClass ( "StoneWall" ) ) ) ;
2013-08-17 15:59:53 +02:00
var nexttoWalls = false ;
walls . forEach ( function ( ent ) {
2013-12-30 11:04:59 +01:00
if ( ! nexttoWalls && API3 . SquareVectorDistance ( self . position , ent . position ( ) ) < 800 )
2013-08-17 15:59:53 +02:00
nexttoWalls = true ;
} ) ;
// there are walls but we can attack
2013-12-30 11:04:59 +01:00
if ( nexttoWalls && this . unitCollection . filter ( API3 . Filters . byCanAttack ( "StoneWall" ) ) . length !== 0 )
2013-08-17 15:59:53 +02:00
{
2013-12-30 11:04:59 +01:00
m . debug ( "Attack Plan " + this . type + " " + this . name + " has met walls and is not happy." ) ;
2013-08-17 15:59:53 +02:00
this . state = "arrived" ;
} else if ( nexttoWalls ) {
// abort plan.
2013-12-30 11:04:59 +01:00
m . debug ( "Attack Plan " + this . type + " " + this . name + " has met walls and gives up." ) ;
2013-08-17 15:59:53 +02:00
Engine . ProfileStop ( ) ;
return 0 ;
}
}
// check if our land units are close enough from the next waypoint.
2013-12-30 11:04:59 +01:00
if ( API3 . SquareVectorDistance ( this . unitCollection . getCentrePosition ( ) , this . targetPos ) < 7500 ||
API3 . SquareVectorDistance ( this . unitCollection . getCentrePosition ( ) , this . path [ 0 ] [ 0 ] ) < 650 ) {
if ( this . unitCollection . filter ( API3 . Filters . byClass ( "Siege" ) ) . length !== 0
&& API3 . SquareVectorDistance ( this . unitCollection . getCentrePosition ( ) , this . targetPos ) >= 7500
&& API3 . SquareVectorDistance ( this . unitCollection . filter ( API3 . Filters . byClass ( "Siege" ) ) . getCentrePosition ( ) , this . path [ 0 ] [ 0 ] ) >= 650 )
2013-08-17 15:59:53 +02:00
{
} else {
// okay so here basically two cases. The first one is "we need a boat at this point".
// the second one is "we need to unload at this point". The third is "normal".
if ( this . path [ 0 ] [ 1 ] !== true )
{
this . path . shift ( ) ;
if ( this . path . length > 0 ) {
2013-09-29 15:32:52 +02:00
this . unitCollection . move ( this . path [ 0 ] [ 0 ] [ 0 ] , this . path [ 0 ] [ 0 ] [ 1 ] ) ;
2013-08-17 15:59:53 +02:00
} else {
2013-12-30 11:04:59 +01:00
m . debug ( "Attack Plan " + this . type + " " + this . name + " has arrived to destination." ) ;
2013-08-17 15:59:53 +02:00
// we must assume we've arrived at the end of the trail.
this . state = "arrived" ;
}
2013-09-29 15:32:52 +02:00
} else
2013-08-17 15:59:53 +02:00
{
2013-09-29 15:32:52 +02:00
// TODO: make this require an escort later on.
this . path . shift ( ) ;
if ( this . path . length === 0 ) {
2013-12-30 11:04:59 +01:00
m . debug ( "Attack Plan " + this . type + " " + this . name + " has arrived to destination." ) ;
2013-09-29 15:32:52 +02:00
// we must assume we've arrived at the end of the trail.
this . state = "arrived" ;
} else {
/ *
2013-12-30 11:04:59 +01:00
var plan = new m . TransportPlan ( gameState , this . unitCollection . toIdArray ( ) , this . path [ 0 ] [ 0 ] , false ) ;
2013-09-29 15:32:52 +02:00
this . tpPlanID = plan . ID ;
HQ . navalManager . transportPlans . push ( plan ) ;
2013-12-30 11:04:59 +01:00
m . debug ( "Transporting over sea" ) ;
2013-09-29 15:32:52 +02:00
this . state = "transporting" ;
* /
// TODO: fix this above
//right now we'll abort.
2013-08-17 15:59:53 +02:00
Engine . ProfileStop ( ) ;
2013-09-29 15:32:52 +02:00
return 0 ;
2013-08-17 15:59:53 +02:00
}
}
}
}
2013-09-29 15:32:52 +02:00
} else if ( this . state === "transporting" ) {
// check that we haven't finished transporting, ie the plan
if ( ! HQ . navalManager . checkActivePlan ( this . tpPlanID ) )
{
this . state = "walking" ;
2013-08-17 15:59:53 +02:00
}
}
// todo: re-implement raiding
if ( this . state === "arrived" ) {
// let's proceed on with whatever happens now.
// There's a ton of TODOs on this part.
if ( this . onArrivalReaction == "proceedOnTargets" ) {
this . state = "" ;
this . unitCollection . forEach ( function ( ent ) { //}) {
ent . stopMoving ( ) ;
ent . setMetadata ( PlayerID , "subrole" , "attacking" ) ;
} ) ;
} else if ( this . onArrivalReaction == "huntVillagers" ) {
// let's get any villager and target them with a tactics manager
var enemyCitizens = gameState . entities . filter ( function ( ent ) {
return ( gameState . isEntityEnemy ( ent ) && ent . hasClass ( "Support" ) && ent . owner ( ) !== 0 && ent . position ( ) ) ;
} ) ;
var targetList = [ ] ;
enemyCitizens . forEach ( function ( enemy ) {
2013-12-30 11:04:59 +01:00
if ( m . inRange ( enemy . position ( ) , units . getCentrePosition ( ) , 2500 ) && targetList . indexOf ( enemy . id ( ) ) === - 1 )
2013-08-17 15:59:53 +02:00
targetList . push ( enemy . id ( ) ) ;
} ) ;
if ( targetList . length > 0 )
{
this . tactics = new Tactics ( gameState , HeadQuarters , this . idList , targetList ) ;
this . state = "huntVillagers" ;
var arrivedthisTurn = true ;
} else {
this . state = "" ;
var arrivedthisTurn = true ;
}
}
}
if ( this . state === "" ) {
// Units attacked will target their attacker unless they're siege. Then we take another non-siege unit to attack them.
2014-01-10 02:46:27 +01:00
var attackedEvents = events [ "Attacked" ] ;
for ( var key in attackedEvents ) {
var e = attackedEvents [ key ] ;
if ( IDs . indexOf ( e . target ) !== - 1 ) {
var attacker = gameState . getEntityById ( e . attacker ) ;
var ourUnit = gameState . getEntityById ( e . target ) ;
if ( attacker && attacker . position ( ) && attacker . hasClass ( "Unit" ) && attacker . owner ( ) != 0 && attacker . owner ( ) != PlayerID ) {
if ( ourUnit . hasClass ( "Siege" ) )
{
var collec = this . unitCollection . filterNearest ( ourUnit . position ( ) , 8 ) . filter ( Filters . not ( Filters . byClass ( "Siege" ) ) ) . toEntityArray ( ) ;
if ( collec . length !== 0 )
2013-08-17 15:59:53 +02:00
{
2014-01-10 02:46:27 +01:00
collec [ 0 ] . attack ( attacker . id ( ) ) ;
if ( collec . length !== 1 )
2013-09-29 15:32:52 +02:00
{
2014-01-10 02:46:27 +01:00
collec [ 1 ] . attack ( attacker . id ( ) ) ;
if ( collec . length !== 2 )
2013-09-29 15:32:52 +02:00
{
2014-01-10 02:46:27 +01:00
collec [ 2 ] . attack ( attacker . id ( ) ) ;
2013-09-29 15:32:52 +02:00
}
}
2013-08-17 15:59:53 +02:00
}
2014-01-10 02:46:27 +01:00
} else {
ourUnit . attack ( attacker . id ( ) ) ;
2013-08-17 15:59:53 +02:00
}
}
}
}
2013-09-29 15:32:52 +02:00
var enemyUnits = gameState . getGEC ( "player-" + this . targetPlayer + "-units" ) ;
var enemyStructures = gameState . getGEC ( "player-" + this . targetPlayer + "-structures" ) ;
2013-08-17 15:59:53 +02:00
if ( this . unitCollUpdateArray === undefined || this . unitCollUpdateArray . length === 0 )
{
2013-09-29 15:32:52 +02:00
this . unitCollUpdateArray = this . unitCollection . toIdArray ( ) ;
2013-08-17 15:59:53 +02:00
} else {
2013-09-29 15:32:52 +02:00
// some stuffs for locality and speed
2013-12-30 11:04:59 +01:00
var territoryMap = m . createTerritoryMap ( gameState ) ;
2013-09-29 15:32:52 +02:00
var timeElapsed = gameState . getTimeElapsed ( ) ;
2013-08-17 15:59:53 +02:00
// Let's check a few units each time we update. Currently 10
2013-09-29 15:32:52 +02:00
var lgth = Math . min ( this . unitCollUpdateArray . length , 10 ) ;
for ( var check = 0 ; check < lgth ; check ++ )
2013-08-17 15:59:53 +02:00
{
2013-09-29 15:32:52 +02:00
var ent = gameState . getEntityById ( this . unitCollUpdateArray [ 0 ] ) ;
if ( ! ent )
continue ;
var orderData = ent . unitAIOrderData ( ) ;
if ( orderData . length !== 0 )
orderData = orderData [ 0 ] ;
else
orderData = undefined ;
// if the unit is in my territory, make it move.
2013-08-17 15:59:53 +02:00
if ( territoryMap . point ( ent . position ( ) ) - 64 === PlayerID )
ent . move ( this . targetPos [ 0 ] , this . targetPos [ 1 ] ) ;
2013-09-29 15:32:52 +02:00
2013-08-17 15:59:53 +02:00
// update it.
var needsUpdate = false ;
if ( ent . isIdle ( ) )
needsUpdate = true ;
2013-09-29 15:32:52 +02:00
if ( ent . hasClass ( "Siege" ) && ( ! orderData || ! orderData [ "target" ] || ! gameState . getEntityById ( orderData [ "target" ] ) || ! gameState . getEntityById ( orderData [ "target" ] ) . hasClass ( "ConquestCritical" ) ) )
2013-08-17 15:59:53 +02:00
needsUpdate = true ;
2013-09-29 15:32:52 +02:00
else if ( ! ent . hasClass ( "Siege" ) && orderData && orderData [ "target" ] && gameState . getEntityById ( orderData [ "target" ] ) && gameState . getEntityById ( orderData [ "target" ] ) . hasClass ( "Structure" ) )
2013-08-17 15:59:53 +02:00
needsUpdate = true ; // try to make it attack a unit instead
2013-09-29 15:32:52 +02:00
if ( timeElapsed - ent . getMetadata ( PlayerID , "lastAttackPlanUpdateTime" ) < 10000 )
2013-08-17 15:59:53 +02:00
needsUpdate = false ;
2013-09-29 15:32:52 +02:00
if ( needsUpdate === true || arrivedthisTurn )
2013-08-17 15:59:53 +02:00
{
2013-09-29 15:32:52 +02:00
ent . setMetadata ( PlayerID , "lastAttackPlanUpdateTime" , timeElapsed ) ;
2013-08-17 15:59:53 +02:00
var mStruct = enemyStructures . filter ( function ( enemy ) { //}){
if ( ! enemy . position ( ) || ( enemy . hasClass ( "StoneWall" ) && ent . canAttackClass ( "StoneWall" ) ) ) {
return false ;
}
2013-12-30 11:04:59 +01:00
if ( API3 . SquareVectorDistance ( enemy . position ( ) , ent . position ( ) ) > 3000 ) {
2013-08-17 15:59:53 +02:00
return false ;
}
return true ;
} ) ;
var mUnit ;
2013-09-29 15:32:52 +02:00
if ( ent . hasClass ( "Cavalry" ) && ent . countersClasses ( [ "Support" ] ) ) {
2013-08-17 15:59:53 +02:00
mUnit = enemyUnits . filter ( function ( enemy ) { //}){
if ( ! enemy . position ( ) ) {
return false ;
}
if ( ! enemy . hasClass ( "Support" ) )
return false ;
2013-12-30 11:04:59 +01:00
if ( API3 . SquareVectorDistance ( enemy . position ( ) , ent . position ( ) ) > 10000 ) {
2013-08-17 15:59:53 +02:00
return false ;
}
return true ;
} ) ;
}
2013-09-29 15:32:52 +02:00
if ( ! ( ent . hasClass ( "Cavalry" ) && ent . countersClasses ( [ "Support" ] ) ) || mUnit . length === 0 ) {
2013-08-17 15:59:53 +02:00
mUnit = enemyUnits . filter ( function ( enemy ) { //}){
if ( ! enemy . position ( ) ) {
return false ;
}
2013-12-30 11:04:59 +01:00
if ( API3 . SquareVectorDistance ( enemy . position ( ) , ent . position ( ) ) > 10000 ) {
2013-08-17 15:59:53 +02:00
return false ;
}
return true ;
} ) ;
}
var isGate = false ;
mUnit = mUnit . toEntityArray ( ) ;
mStruct = mStruct . toEntityArray ( ) ;
if ( ent . hasClass ( "Siege" ) ) {
mStruct . sort ( function ( structa , structb ) { //}){
var vala = structa . costSum ( ) ;
if ( structa . hasClass ( "Gates" ) && ent . canAttackClass ( "StoneWall" ) ) // we hate gates
{
isGate = true ;
vala += 10000 ;
} else if ( structa . hasClass ( "ConquestCritical" ) )
vala += 200 ;
var valb = structb . costSum ( ) ;
if ( structb . hasClass ( "Gates" ) && ent . canAttackClass ( "StoneWall" ) ) // we hate gates
{
isGate = true ;
valb += 10000 ;
} else if ( structb . hasClass ( "ConquestCritical" ) )
valb += 200 ;
//warn ("Structure " +structa.genericName() + " is worth " +vala);
//warn ("Structure " +structb.genericName() + " is worth " +valb);
return ( valb - vala ) ;
} ) ;
2013-09-29 15:32:52 +02:00
// TODO: handle ballistas here
2013-08-17 15:59:53 +02:00
if ( mStruct . length !== 0 ) {
if ( isGate )
ent . attack ( mStruct [ 0 ] . id ( ) ) ;
else
{
var rand = Math . floor ( Math . random ( ) * mStruct . length * 0.1 ) ;
ent . attack ( mStruct [ + rand ] . id ( ) ) ;
2013-12-30 11:04:59 +01:00
//m.debug ("Siege units attacking a structure from " +mStruct[+rand].owner() + " , " +mStruct[+rand].templateName());
2013-08-17 15:59:53 +02:00
}
2013-12-30 11:04:59 +01:00
} else if ( API3 . SquareVectorDistance ( self . targetPos , ent . position ( ) ) > 900 ) {
//m.debug ("Siege units moving to " + uneval(self.targetPos));
2013-08-17 15:59:53 +02:00
ent . move ( self . targetPos [ 0 ] , self . targetPos [ 1 ] ) ;
}
} else {
2013-09-29 15:32:52 +02:00
if ( mUnit . length !== 0 ) {
2013-08-17 15:59:53 +02:00
var rand = Math . floor ( Math . random ( ) * mUnit . length * 0.99 ) ;
ent . attack ( mUnit [ ( + rand ) ] . id ( ) ) ;
2013-12-30 11:04:59 +01:00
//m.debug ("Units attacking a unit from " +mUnit[+rand].owner() + " , " +mUnit[+rand].templateName());
} else if ( API3 . SquareVectorDistance ( self . targetPos , ent . position ( ) ) > 900 ) {
//m.debug ("Units moving to " + uneval(self.targetPos));
2013-09-29 15:32:52 +02:00
ent . move ( self . targetPos [ 0 ] , self . targetPos [ 1 ] ) ;
2013-08-17 15:59:53 +02:00
} else if ( mStruct . length !== 0 ) {
mStruct . sort ( function ( structa , structb ) { //}){
var vala = structa . costSum ( ) ;
if ( structa . hasClass ( "Gates" ) && ent . canAttackClass ( "StoneWall" ) ) // we hate gates
{
isGate = true ;
vala += 10000 ;
} else if ( structa . hasClass ( "ConquestCritical" ) )
vala += 100 ;
var valb = structb . costSum ( ) ;
if ( structb . hasClass ( "Gates" ) && ent . canAttackClass ( "StoneWall" ) ) // we hate gates
{
isGate = true ;
valb += 10000 ;
} else if ( structb . hasClass ( "ConquestCritical" ) )
valb += 100 ;
return ( valb - vala ) ;
} ) ;
if ( isGate )
ent . attack ( mStruct [ 0 ] . id ( ) ) ;
else
{
var rand = Math . floor ( Math . random ( ) * mStruct . length * 0.1 ) ;
ent . attack ( mStruct [ + rand ] . id ( ) ) ;
2013-12-30 11:04:59 +01:00
//m.debug ("Units attacking a structure from " +mStruct[+rand].owner() + " , " +mStruct[+rand].templateName());
2013-08-17 15:59:53 +02:00
}
}
}
}
}
2013-09-29 15:32:52 +02:00
this . unitCollUpdateArray . splice ( 0 , 10 ) ;
2013-08-17 15:59:53 +02:00
}
// updating targets.
if ( ! gameState . getEntityById ( this . target . id ( ) ) )
{
2013-09-29 15:32:52 +02:00
var targets = this . targetFinder ( gameState , HQ ) ;
2013-08-17 15:59:53 +02:00
if ( targets . length === 0 ) {
2013-09-29 15:32:52 +02:00
targets = this . defaultTargetFinder ( gameState , HQ ) ;
2013-08-17 15:59:53 +02:00
}
if ( targets . length ) {
2013-12-30 11:04:59 +01:00
m . debug ( "Seems like our target has been destroyed. Switching." ) ;
m . debug ( "Aiming for " + targets ) ;
2013-08-17 15:59:53 +02:00
// picking a target
this . targetPos = undefined ;
var count = 0 ;
while ( ! this . targetPos ) {
var rand = Math . floor ( ( Math . random ( ) * targets . length ) ) ;
this . target = targets . toEntityArray ( ) [ rand ] ;
this . targetPos = this . target . position ( ) ;
count ++ ;
if ( count > 1000 ) {
2013-12-30 11:04:59 +01:00
m . debug ( "No target with a valid position found" ) ;
2013-08-17 15:59:53 +02:00
Engine . ProfileStop ( ) ;
return false ;
}
}
}
}
// regularly update the target position in case it's a unit.
if ( this . target . hasClass ( "Unit" ) )
this . targetPos = this . target . position ( ) ;
}
/ *
if ( this . state === "huntVillagers" )
{
this . tactics . eventMetadataCleanup ( events , HeadQuarters ) ;
this . tactics . removeTheirDeads ( HeadQuarters ) ;
this . tactics . removeMyDeads ( HeadQuarters ) ;
if ( this . tactics . isBattleOver ( ) )
{
this . tactics . disband ( HeadQuarters , events ) ;
this . tactics = undefined ;
this . state = "" ;
return 0 ; // assume over
} else
this . tactics . reassignAttacks ( HeadQuarters ) ;
} * /
this . lastPosition = this . position ;
Engine . ProfileStop ( ) ;
return this . unitCollection . length ;
} ;
2013-12-30 11:04:59 +01:00
m . CityAttack . prototype . totalCountUnits = function ( gameState ) {
2013-08-17 15:59:53 +02:00
var totalcount = 0 ;
for ( var i in this . idList )
{
totalcount ++ ;
}
return totalcount ;
} ;
// reset any units
2013-12-30 11:04:59 +01:00
m . CityAttack . prototype . Abort = function ( gameState ) {
2013-08-17 15:59:53 +02:00
this . unitCollection . forEach ( function ( ent ) {
ent . setMetadata ( PlayerID , "role" , undefined ) ;
ent . setMetadata ( PlayerID , "subrole" , undefined ) ;
ent . setMetadata ( PlayerID , "plan" , undefined ) ;
} ) ;
for ( var unitCat in this . unitStat ) {
delete this . unitStat [ unitCat ] ;
delete this . unit [ unitCat ] ;
}
delete this . unitCollection ;
gameState . ai . queueManager . removeQueue ( "plan_" + this . name ) ;
gameState . ai . queueManager . removeQueue ( "plan_" + this . name + "_champ" ) ;
} ;
2013-12-30 11:04:59 +01:00
return m ;
} ( AEGIS ) ;