LCOV - code coverage report
Current view: top level - simulation/ai/petra - attackManager.js (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 471 0.0 %
Date: 2023-04-02 12:52:40 Functions: 0 22 0.0 %

          Line data    Source code
       1             : /**
       2             :  * Attack Manager
       3             :  */
       4           0 : PETRA.AttackManager = function(Config)
       5             : {
       6           0 :         this.Config = Config;
       7             : 
       8           0 :         this.totalNumber = 0;
       9           0 :         this.attackNumber = 0;
      10           0 :         this.rushNumber = 0;
      11           0 :         this.raidNumber = 0;
      12           0 :         this.upcomingAttacks = {
      13             :                 [PETRA.AttackPlan.TYPE_RUSH]: [],
      14             :                 [PETRA.AttackPlan.TYPE_RAID]: [],
      15             :                 [PETRA.AttackPlan.TYPE_DEFAULT]: [],
      16             :                 [PETRA.AttackPlan.TYPE_HUGE_ATTACK]: []
      17             :         };
      18           0 :         this.startedAttacks = {
      19             :                 [PETRA.AttackPlan.TYPE_RUSH]: [],
      20             :                 [PETRA.AttackPlan.TYPE_RAID]: [],
      21             :                 [PETRA.AttackPlan.TYPE_DEFAULT]: [],
      22             :                 [PETRA.AttackPlan.TYPE_HUGE_ATTACK]: []
      23             :         };
      24           0 :         this.bombingAttacks = new Map();// Temporary attacks for siege units while waiting their current attack to start
      25           0 :         this.debugTime = 0;
      26           0 :         this.maxRushes = 0;
      27           0 :         this.rushSize = [];
      28           0 :         this.currentEnemyPlayer = undefined; // enemy player we are currently targeting
      29           0 :         this.defeated = {};
      30             : };
      31             : 
      32             : /** More initialisation for stuff that needs the gameState */
      33           0 : PETRA.AttackManager.prototype.init = function(gameState)
      34             : {
      35           0 :         this.outOfPlan = gameState.getOwnUnits().filter(API3.Filters.byMetadata(PlayerID, "plan", -1));
      36           0 :         this.outOfPlan.registerUpdates();
      37             : };
      38             : 
      39           0 : PETRA.AttackManager.prototype.setRushes = function(allowed)
      40             : {
      41           0 :         if (this.Config.personality.aggressive > this.Config.personalityCut.strong && allowed > 2)
      42             :         {
      43           0 :                 this.maxRushes = 3;
      44           0 :                 this.rushSize = [ 16, 20, 24 ];
      45             :         }
      46           0 :         else if (this.Config.personality.aggressive > this.Config.personalityCut.medium && allowed > 1)
      47             :         {
      48           0 :                 this.maxRushes = 2;
      49           0 :                 this.rushSize = [ 18, 22 ];
      50             :         }
      51           0 :         else if (this.Config.personality.aggressive > this.Config.personalityCut.weak && allowed > 0)
      52             :         {
      53           0 :                 this.maxRushes = 1;
      54           0 :                 this.rushSize = [ 20 ];
      55             :         }
      56             : };
      57             : 
      58           0 : PETRA.AttackManager.prototype.checkEvents = function(gameState, events)
      59             : {
      60           0 :         for (let evt of events.PlayerDefeated)
      61           0 :                 this.defeated[evt.playerId] = true;
      62             : 
      63           0 :         let answer = "decline";
      64             :         let other;
      65             :         let targetPlayer;
      66           0 :         for (let evt of events.AttackRequest)
      67             :         {
      68           0 :                 if (evt.source === PlayerID || !gameState.isPlayerAlly(evt.source) || !gameState.isPlayerEnemy(evt.player))
      69           0 :                         continue;
      70           0 :                 targetPlayer = evt.player;
      71           0 :                 let available = 0;
      72           0 :                 for (let attackType in this.upcomingAttacks)
      73             :                 {
      74           0 :                         for (let attack of this.upcomingAttacks[attackType])
      75             :                         {
      76           0 :                                 if (attack.state === PETRA.AttackPlan.STATE_COMPLETING)
      77             :                                 {
      78           0 :                                         if (attack.targetPlayer === targetPlayer)
      79           0 :                                                 available += attack.unitCollection.length;
      80           0 :                                         else if (attack.targetPlayer !== undefined && attack.targetPlayer !== targetPlayer)
      81           0 :                                                 other = attack.targetPlayer;
      82           0 :                                         continue;
      83             :                                 }
      84             : 
      85           0 :                                 attack.targetPlayer = targetPlayer;
      86             : 
      87           0 :                                 if (attack.unitCollection.length > 2)
      88           0 :                                         available += attack.unitCollection.length;
      89             :                         }
      90             :                 }
      91             : 
      92           0 :                 if (available > 12)  // launch the attack immediately
      93             :                 {
      94           0 :                         for (let attackType in this.upcomingAttacks)
      95             :                         {
      96           0 :                                 for (let attack of this.upcomingAttacks[attackType])
      97             :                                 {
      98           0 :                                         if (attack.state === PETRA.AttackPlan.STATE_COMPLETING ||
      99             :                                                 attack.targetPlayer !== targetPlayer ||
     100             :                                                 attack.unitCollection.length < 3)
     101           0 :                                                 continue;
     102           0 :                                         attack.forceStart();
     103           0 :                                         attack.requested = true;
     104             :                                 }
     105             :                         }
     106           0 :                         answer = "join";
     107             :                 }
     108           0 :                 else if (other !== undefined)
     109           0 :                         answer = "other";
     110           0 :                 break;  // take only the first attack request into account
     111             :         }
     112           0 :         if (targetPlayer !== undefined)
     113           0 :                 PETRA.chatAnswerRequestAttack(gameState, targetPlayer, answer, other);
     114             : 
     115           0 :         for (let evt of events.EntityRenamed)   // take care of packing units in bombing attacks
     116             :         {
     117           0 :                 for (let [targetId, unitIds] of this.bombingAttacks)
     118             :                 {
     119           0 :                         if (targetId == evt.entity)
     120             :                         {
     121           0 :                                 this.bombingAttacks.set(evt.newentity, unitIds);
     122           0 :                                 this.bombingAttacks.delete(evt.entity);
     123             :                         }
     124           0 :                         else if (unitIds.has(evt.entity))
     125             :                         {
     126           0 :                                 unitIds.add(evt.newentity);
     127           0 :                                 unitIds.delete(evt.entity);
     128             :                         }
     129             :                 }
     130             :         }
     131             : };
     132             : 
     133             : /**
     134             :  * Check for any structure in range from within our territory, and bomb it
     135             :  */
     136           0 : PETRA.AttackManager.prototype.assignBombers = function(gameState)
     137             : {
     138             :         // First some cleaning of current bombing attacks
     139           0 :         for (let [targetId, unitIds] of this.bombingAttacks)
     140             :         {
     141           0 :                 let target = gameState.getEntityById(targetId);
     142           0 :                 if (!target || !gameState.isPlayerEnemy(target.owner()))
     143           0 :                         this.bombingAttacks.delete(targetId);
     144             :                 else
     145             :                 {
     146           0 :                         for (let entId of unitIds.values())
     147             :                         {
     148           0 :                                 let ent = gameState.getEntityById(entId);
     149           0 :                                 if (ent && ent.owner() == PlayerID)
     150             :                                 {
     151           0 :                                         let plan = ent.getMetadata(PlayerID, "plan");
     152           0 :                                         let orders = ent.unitAIOrderData();
     153           0 :                                         let lastOrder = orders && orders.length ? orders[orders.length-1] : null;
     154           0 :                                         if (lastOrder && lastOrder.target && lastOrder.target == targetId && plan != -2 && plan != -3)
     155           0 :                                                 continue;
     156             :                                 }
     157           0 :                                 unitIds.delete(entId);
     158             :                         }
     159           0 :                         if (!unitIds.size)
     160           0 :                                 this.bombingAttacks.delete(targetId);
     161             :                 }
     162             :         }
     163             : 
     164           0 :         const bombers = gameState.updatingCollection("bombers", API3.Filters.byClasses(["BoltShooter", "StoneThrower"]), gameState.getOwnUnits());
     165           0 :         for (let ent of bombers.values())
     166             :         {
     167           0 :                 if (!ent.position() || !ent.isIdle() || !ent.attackRange("Ranged"))
     168           0 :                         continue;
     169           0 :                 if (ent.getMetadata(PlayerID, "plan") == -2 || ent.getMetadata(PlayerID, "plan") == -3)
     170           0 :                         continue;
     171           0 :                 if (ent.getMetadata(PlayerID, "plan") !== undefined && ent.getMetadata(PlayerID, "plan") != -1)
     172             :                 {
     173           0 :                         let subrole = ent.getMetadata(PlayerID, "subrole");
     174           0 :                         if (subrole && (subrole === PETRA.Worker.SUBROLE_COMPLETING || subrole === PETRA.Worker.SUBROLE_WALKING || subrole === PETRA.Worker.SUBROLE_ATTACKING))
     175           0 :                                 continue;
     176             :                 }
     177           0 :                 let alreadyBombing = false;
     178           0 :                 for (let unitIds of this.bombingAttacks.values())
     179             :                 {
     180           0 :                         if (!unitIds.has(ent.id()))
     181           0 :                                 continue;
     182           0 :                         alreadyBombing = true;
     183           0 :                         break;
     184             :                 }
     185           0 :                 if (alreadyBombing)
     186           0 :                         break;
     187             : 
     188           0 :                 let range = ent.attackRange("Ranged").max;
     189           0 :                 let entPos = ent.position();
     190           0 :                 let access = PETRA.getLandAccess(gameState, ent);
     191           0 :                 for (let struct of gameState.getEnemyStructures().values())
     192             :                 {
     193           0 :                         if (!ent.canAttackTarget(struct, PETRA.allowCapture(gameState, ent, struct)))
     194           0 :                                 continue;
     195             : 
     196           0 :                         let structPos = struct.position();
     197             :                         let x;
     198             :                         let z;
     199           0 :                         if (struct.hasClass("Field"))
     200             :                         {
     201           0 :                                 if (!struct.resourceSupplyNumGatherers() ||
     202             :                                     !gameState.isPlayerEnemy(gameState.ai.HQ.territoryMap.getOwner(structPos)))
     203           0 :                                         continue;
     204             :                         }
     205           0 :                         let dist = API3.VectorDistance(entPos, structPos);
     206           0 :                         if (dist > range)
     207             :                         {
     208           0 :                                 let safety = struct.footprintRadius() + 30;
     209           0 :                                 x = structPos[0] + (entPos[0] - structPos[0]) * safety / dist;
     210           0 :                                 z = structPos[1] + (entPos[1] - structPos[1]) * safety / dist;
     211           0 :                                 let owner = gameState.ai.HQ.territoryMap.getOwner([x, z]);
     212           0 :                                 if (owner != 0 && gameState.isPlayerEnemy(owner))
     213           0 :                                         continue;
     214           0 :                                 x = structPos[0] + (entPos[0] - structPos[0]) * range / dist;
     215           0 :                                 z = structPos[1] + (entPos[1] - structPos[1]) * range / dist;
     216           0 :                                 if (gameState.ai.HQ.territoryMap.getOwner([x, z]) != PlayerID ||
     217             :                                     gameState.ai.accessibility.getAccessValue([x, z]) != access)
     218           0 :                                         continue;
     219             :                         }
     220             :                         let attackingUnits;
     221           0 :                         for (let [targetId, unitIds] of this.bombingAttacks)
     222             :                         {
     223           0 :                                 if (targetId != struct.id())
     224           0 :                                         continue;
     225           0 :                                 attackingUnits = unitIds;
     226           0 :                                 break;
     227             :                         }
     228           0 :                         if (attackingUnits && attackingUnits.size > 4)
     229           0 :                                 continue;       // already enough units against that target
     230           0 :                         if (!attackingUnits)
     231             :                         {
     232           0 :                                 attackingUnits = new Set();
     233           0 :                                 this.bombingAttacks.set(struct.id(), attackingUnits);
     234             :                         }
     235           0 :                         attackingUnits.add(ent.id());
     236           0 :                         if (dist > range)
     237           0 :                                 ent.move(x, z);
     238           0 :                         ent.attack(struct.id(), false, dist > range);
     239           0 :                         break;
     240             :                 }
     241             :         }
     242             : };
     243             : 
     244             : /**
     245             :  * Some functions are run every turn
     246             :  * Others once in a while
     247             :  */
     248           0 : PETRA.AttackManager.prototype.update = function(gameState, queues, events)
     249             : {
     250           0 :         if (this.Config.debug > 2 && gameState.ai.elapsedTime > this.debugTime + 60)
     251             :         {
     252           0 :                 this.debugTime = gameState.ai.elapsedTime;
     253           0 :                 API3.warn(" upcoming attacks =================");
     254           0 :                 for (let attackType in this.upcomingAttacks)
     255           0 :                         for (let attack of this.upcomingAttacks[attackType])
     256           0 :                                 API3.warn(" plan " + attack.name + " type " + attackType + " state " + attack.state + " units " + attack.unitCollection.length);
     257           0 :                 API3.warn(" started attacks ==================");
     258           0 :                 for (let attackType in this.startedAttacks)
     259           0 :                         for (let attack of this.startedAttacks[attackType])
     260           0 :                                 API3.warn(" plan " + attack.name + " type " + attackType + " state " + attack.state + " units " + attack.unitCollection.length);
     261           0 :                 API3.warn(" ==================================");
     262             :         }
     263             : 
     264           0 :         this.checkEvents(gameState, events);
     265           0 :         const unexecutedAttacks = {
     266             :                 [PETRA.AttackPlan.TYPE_RUSH]: 0,
     267             :                 [PETRA.AttackPlan.TYPE_RAID]: 0,
     268             :                 [PETRA.AttackPlan.TYPE_DEFAULT]: 0,
     269             :                 [PETRA.AttackPlan.TYPE_HUGE_ATTACK]: 0
     270             :         };
     271           0 :         for (let attackType in this.upcomingAttacks)
     272             :         {
     273           0 :                 for (let i = 0; i < this.upcomingAttacks[attackType].length; ++i)
     274             :                 {
     275           0 :                         let attack = this.upcomingAttacks[attackType][i];
     276           0 :                         attack.checkEvents(gameState, events);
     277             : 
     278           0 :                         if (attack.isStarted())
     279           0 :                                 API3.warn("Petra problem in attackManager: attack in preparation has already started ???");
     280             : 
     281           0 :                         let updateStep = attack.updatePreparation(gameState);
     282             :                         // now we're gonna check if the preparation time is over
     283           0 :                         if (updateStep === PETRA.AttackPlan.PREPARATION_KEEP_GOING || attack.isPaused())
     284             :                         {
     285             :                                 // just chillin'
     286           0 :                                 if (attack.state === PETRA.AttackPlan.STATE_UNEXECUTED)
     287           0 :                                         ++unexecutedAttacks[attackType];
     288             :                         }
     289           0 :                         else if (updateStep === PETRA.AttackPlan.PREPARATION_FAILED)
     290             :                         {
     291           0 :                                 if (this.Config.debug > 1)
     292           0 :                                         API3.warn("Attack Manager: " + attack.getType() + " plan " + attack.getName() + " aborted.");
     293           0 :                                 attack.Abort(gameState);
     294           0 :                                 this.upcomingAttacks[attackType].splice(i--, 1);
     295             :                         }
     296           0 :                         else if (updateStep === PETRA.AttackPlan.PREPARATION_START)
     297             :                         {
     298           0 :                                 if (attack.StartAttack(gameState))
     299             :                                 {
     300           0 :                                         if (this.Config.debug > 1)
     301           0 :                                                 API3.warn("Attack Manager: Starting " + attack.getType() + " plan " + attack.getName());
     302           0 :                                         if (this.Config.chat)
     303           0 :                                                 PETRA.chatLaunchAttack(gameState, attack.targetPlayer, attack.getType());
     304           0 :                                         this.startedAttacks[attackType].push(attack);
     305             :                                 }
     306             :                                 else
     307           0 :                                         attack.Abort(gameState);
     308           0 :                                 this.upcomingAttacks[attackType].splice(i--, 1);
     309             :                         }
     310             :                 }
     311             :         }
     312             : 
     313           0 :         for (let attackType in this.startedAttacks)
     314             :         {
     315           0 :                 for (let i = 0; i < this.startedAttacks[attackType].length; ++i)
     316             :                 {
     317           0 :                         let attack = this.startedAttacks[attackType][i];
     318           0 :                         attack.checkEvents(gameState, events);
     319             :                         // okay so then we'll update the attack.
     320           0 :                         if (attack.isPaused())
     321           0 :                                 continue;
     322           0 :                         let remaining = attack.update(gameState, events);
     323           0 :                         if (!remaining)
     324             :                         {
     325           0 :                                 if (this.Config.debug > 1)
     326           0 :                                         API3.warn("Military Manager: " + attack.getType() + " plan " + attack.getName() + " is finished with remaining " + remaining);
     327           0 :                                 attack.Abort(gameState);
     328           0 :                                 this.startedAttacks[attackType].splice(i--, 1);
     329             :                         }
     330             :                 }
     331             :         }
     332             : 
     333             :         // creating plans after updating because an aborted plan might be reused in that case.
     334             : 
     335           0 :         let barracksNb = gameState.getOwnEntitiesByClass("Barracks", true).filter(API3.Filters.isBuilt()).length;
     336           0 :         if (this.rushNumber < this.maxRushes && barracksNb >= 1)
     337             :         {
     338           0 :                 if (unexecutedAttacks[PETRA.AttackPlan.TYPE_RUSH] === 0)
     339             :                 {
     340             :                         // we have a barracks and we want to rush, rush.
     341           0 :                         let data = { "targetSize": this.rushSize[this.rushNumber] };
     342           0 :                         const attackPlan = new PETRA.AttackPlan(gameState, this.Config, this.totalNumber, PETRA.AttackPlan.TYPE_RUSH, data);
     343           0 :                         if (!attackPlan.failed)
     344             :                         {
     345           0 :                                 if (this.Config.debug > 1)
     346           0 :                                         API3.warn("Military Manager: Rushing plan " + this.totalNumber + " with maxRushes " + this.maxRushes);
     347           0 :                                 this.totalNumber++;
     348           0 :                                 attackPlan.init(gameState);
     349           0 :                                 this.upcomingAttacks[PETRA.AttackPlan.TYPE_RUSH].push(attackPlan);
     350             :                         }
     351           0 :                         this.rushNumber++;
     352             :                 }
     353             :         }
     354           0 :         else if (unexecutedAttacks[PETRA.AttackPlan.TYPE_DEFAULT] == 0 && unexecutedAttacks[PETRA.AttackPlan.TYPE_HUGE_ATTACK] == 0 &&
     355             :                 this.startedAttacks[PETRA.AttackPlan.TYPE_DEFAULT].length + this.startedAttacks[PETRA.AttackPlan.TYPE_HUGE_ATTACK].length <
     356             :                         Math.min(2, 1 + Math.round(gameState.getPopulationMax() / 100)) &&
     357             :                 (this.startedAttacks[PETRA.AttackPlan.TYPE_DEFAULT].length + this.startedAttacks[PETRA.AttackPlan.TYPE_HUGE_ATTACK].length == 0 ||
     358             :                 gameState.getPopulationMax() - gameState.getPopulation() > 12))
     359             :         {
     360           0 :                 if (barracksNb >= 1 && (gameState.currentPhase() > 1 || gameState.isResearching(gameState.getPhaseName(2))) ||
     361             :                         !gameState.ai.HQ.hasPotentialBase())    // if we have no base ... nothing else to do than attack
     362             :                 {
     363           0 :                         const type = this.attackNumber < 2 || this.startedAttacks[PETRA.AttackPlan.TYPE_HUGE_ATTACK].length > 0 ? PETRA.AttackPlan.TYPE_DEFAULT : PETRA.AttackPlan.TYPE_HUGE_ATTACK;
     364           0 :                         let attackPlan = new PETRA.AttackPlan(gameState, this.Config, this.totalNumber, type);
     365           0 :                         if (attackPlan.failed)
     366           0 :                                 this.attackPlansEncounteredWater = true; // hack
     367             :                         else
     368             :                         {
     369           0 :                                 if (this.Config.debug > 1)
     370           0 :                                         API3.warn("Military Manager: Creating the plan " + type + "  " + this.totalNumber);
     371           0 :                                 this.totalNumber++;
     372           0 :                                 attackPlan.init(gameState);
     373           0 :                                 this.upcomingAttacks[type].push(attackPlan);
     374             :                         }
     375           0 :                         this.attackNumber++;
     376             :                 }
     377             :         }
     378             : 
     379           0 :         if (unexecutedAttacks[PETRA.AttackPlan.TYPE_RAID] === 0 && gameState.ai.HQ.defenseManager.targetList.length)
     380             :         {
     381             :                 let target;
     382           0 :                 for (let targetId of gameState.ai.HQ.defenseManager.targetList)
     383             :                 {
     384           0 :                         target = gameState.getEntityById(targetId);
     385           0 :                         if (!target)
     386           0 :                                 continue;
     387           0 :                         if (gameState.isPlayerEnemy(target.owner()))
     388           0 :                                 break;
     389           0 :                         target = undefined;
     390             :                 }
     391           0 :                 if (target) // prepare a raid against this target
     392           0 :                         this.raidTargetEntity(gameState, target);
     393             :         }
     394             : 
     395             :         // Check if we have some unused ranged siege unit which could do something useful while waiting
     396           0 :         if (this.Config.difficulty > PETRA.DIFFICULTY_VERY_EASY && gameState.ai.playedTurn % 5 == 0)
     397           0 :                 this.assignBombers(gameState);
     398             : };
     399             : 
     400           0 : PETRA.AttackManager.prototype.getPlan = function(planName)
     401             : {
     402           0 :         for (let attackType in this.upcomingAttacks)
     403             :         {
     404           0 :                 for (let attack of this.upcomingAttacks[attackType])
     405           0 :                         if (attack.getName() == planName)
     406           0 :                                 return attack;
     407             :         }
     408           0 :         for (let attackType in this.startedAttacks)
     409             :         {
     410           0 :                 for (let attack of this.startedAttacks[attackType])
     411           0 :                         if (attack.getName() == planName)
     412           0 :                                 return attack;
     413             :         }
     414           0 :         return undefined;
     415             : };
     416             : 
     417           0 : PETRA.AttackManager.prototype.pausePlan = function(planName)
     418             : {
     419           0 :         let attack = this.getPlan(planName);
     420           0 :         if (attack)
     421           0 :                 attack.setPaused(true);
     422             : };
     423             : 
     424           0 : PETRA.AttackManager.prototype.unpausePlan = function(planName)
     425             : {
     426           0 :         let attack = this.getPlan(planName);
     427           0 :         if (attack)
     428           0 :                 attack.setPaused(false);
     429             : };
     430             : 
     431           0 : PETRA.AttackManager.prototype.pauseAllPlans = function()
     432             : {
     433           0 :         for (let attackType in this.upcomingAttacks)
     434           0 :                 for (let attack of this.upcomingAttacks[attackType])
     435           0 :                         attack.setPaused(true);
     436             : 
     437           0 :         for (let attackType in this.startedAttacks)
     438           0 :                 for (let attack of this.startedAttacks[attackType])
     439           0 :                         attack.setPaused(true);
     440             : };
     441             : 
     442           0 : PETRA.AttackManager.prototype.unpauseAllPlans = function()
     443             : {
     444           0 :         for (let attackType in this.upcomingAttacks)
     445           0 :                 for (let attack of this.upcomingAttacks[attackType])
     446           0 :                         attack.setPaused(false);
     447             : 
     448           0 :         for (let attackType in this.startedAttacks)
     449           0 :                 for (let attack of this.startedAttacks[attackType])
     450           0 :                         attack.setPaused(false);
     451             : };
     452             : 
     453           0 : PETRA.AttackManager.prototype.getAttackInPreparation = function(type)
     454             : {
     455           0 :         return this.upcomingAttacks[type].length ? this.upcomingAttacks[type][0] : undefined;
     456             : };
     457             : 
     458             : /**
     459             :  * Determine which player should be attacked: when called when starting the attack,
     460             :  * attack.targetPlayer is undefined and in that case, we keep track of the chosen target
     461             :  * for future attacks.
     462             :  */
     463           0 : PETRA.AttackManager.prototype.getEnemyPlayer = function(gameState, attack)
     464             : {
     465             :         let enemyPlayer;
     466             : 
     467             :         // First check if there is a preferred enemy based on our victory conditions.
     468             :         // If both wonder and relic, choose randomly between them TODO should combine decisions
     469             : 
     470           0 :         if (gameState.getVictoryConditions().has("wonder"))
     471           0 :                 enemyPlayer = this.getWonderEnemyPlayer(gameState, attack);
     472             : 
     473           0 :         if (gameState.getVictoryConditions().has("capture_the_relic"))
     474           0 :                 if (!enemyPlayer || randBool())
     475           0 :                         enemyPlayer = this.getRelicEnemyPlayer(gameState, attack) || enemyPlayer;
     476             : 
     477           0 :         if (enemyPlayer)
     478           0 :                 return enemyPlayer;
     479             : 
     480           0 :         let veto = {};
     481           0 :         for (let i in this.defeated)
     482           0 :                 veto[i] = true;
     483             :         // No rush if enemy too well defended (i.e. iberians)
     484           0 :         if (attack.type === PETRA.AttackPlan.TYPE_RUSH)
     485             :         {
     486           0 :                 for (let i = 1; i < gameState.sharedScript.playersData.length; ++i)
     487             :                 {
     488           0 :                         if (!gameState.isPlayerEnemy(i) || veto[i])
     489           0 :                                 continue;
     490           0 :                         if (this.defeated[i])
     491           0 :                                 continue;
     492           0 :                         let enemyDefense = 0;
     493           0 :                         for (let ent of gameState.getEnemyStructures(i).values())
     494           0 :                                 if (ent.hasClasses(["Tower", "WallTower", "Fortress"]))
     495           0 :                                         enemyDefense++;
     496           0 :                         if (enemyDefense > 6)
     497           0 :                                 veto[i] = true;
     498             :                 }
     499             :         }
     500             : 
     501             :         // then if not a huge attack, continue attacking our previous target as long as it has some entities,
     502             :         // otherwise target the most accessible one
     503           0 :         if (attack.type !== PETRA.AttackPlan.TYPE_HUGE_ATTACK)
     504             :         {
     505           0 :                 if (attack.targetPlayer === undefined && this.currentEnemyPlayer !== undefined &&
     506             :                         !this.defeated[this.currentEnemyPlayer] &&
     507             :                         gameState.isPlayerEnemy(this.currentEnemyPlayer) &&
     508             :                         gameState.getEntities(this.currentEnemyPlayer).hasEntities())
     509           0 :                         return this.currentEnemyPlayer;
     510             : 
     511             :                 let distmin;
     512             :                 let ccmin;
     513           0 :                 let ccEnts = gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre"));
     514           0 :                 for (let ourcc of ccEnts.values())
     515             :                 {
     516           0 :                         if (ourcc.owner() != PlayerID)
     517           0 :                                 continue;
     518           0 :                         let ourPos = ourcc.position();
     519           0 :                         let access = PETRA.getLandAccess(gameState, ourcc);
     520           0 :                         for (let enemycc of ccEnts.values())
     521             :                         {
     522           0 :                                 if (veto[enemycc.owner()])
     523           0 :                                         continue;
     524           0 :                                 if (!gameState.isPlayerEnemy(enemycc.owner()))
     525           0 :                                         continue;
     526           0 :                                 if (access !== PETRA.getLandAccess(gameState, enemycc))
     527           0 :                                         continue;
     528           0 :                                 let dist = API3.SquareVectorDistance(ourPos, enemycc.position());
     529           0 :                                 if (distmin && dist > distmin)
     530           0 :                                         continue;
     531           0 :                                 ccmin = enemycc;
     532           0 :                                 distmin = dist;
     533             :                         }
     534             :                 }
     535           0 :                 if (ccmin)
     536             :                 {
     537           0 :                         enemyPlayer = ccmin.owner();
     538           0 :                         if (attack.targetPlayer === undefined)
     539           0 :                                 this.currentEnemyPlayer = enemyPlayer;
     540           0 :                         return enemyPlayer;
     541             :                 }
     542             :         }
     543             : 
     544             :         // then let's target our strongest enemy (basically counting enemies units)
     545             :         // with priority to enemies with civ center
     546           0 :         let max = 0;
     547           0 :         for (let i = 1; i < gameState.sharedScript.playersData.length; ++i)
     548             :         {
     549           0 :                 if (veto[i])
     550           0 :                         continue;
     551           0 :                 if (!gameState.isPlayerEnemy(i))
     552           0 :                         continue;
     553           0 :                 let enemyCount = 0;
     554           0 :                 let enemyCivCentre = false;
     555           0 :                 for (let ent of gameState.getEntities(i).values())
     556             :                 {
     557           0 :                         enemyCount++;
     558           0 :                         if (ent.hasClass("CivCentre"))
     559           0 :                                 enemyCivCentre = true;
     560             :                 }
     561           0 :                 if (enemyCivCentre)
     562           0 :                         enemyCount += 500;
     563           0 :                 if (!enemyCount || enemyCount < max)
     564           0 :                         continue;
     565           0 :                 max = enemyCount;
     566           0 :                 enemyPlayer = i;
     567             :         }
     568           0 :         if (attack.targetPlayer === undefined)
     569           0 :                 this.currentEnemyPlayer = enemyPlayer;
     570           0 :         return enemyPlayer;
     571             : };
     572             : 
     573             : /**
     574             :  * Target the player with the most advanced wonder.
     575             :  * TODO currently the first built wonder is kept, should chek on the minimum wonderDuration left instead.
     576             :  */
     577           0 : PETRA.AttackManager.prototype.getWonderEnemyPlayer = function(gameState, attack)
     578             : {
     579             :         let enemyPlayer;
     580             :         let enemyWonder;
     581             :         let moreAdvanced;
     582           0 :         for (let wonder of gameState.getEnemyStructures().filter(API3.Filters.byClass("Wonder")).values())
     583             :         {
     584           0 :                 if (wonder.owner() == 0)
     585           0 :                         continue;
     586           0 :                 let progress = wonder.foundationProgress();
     587           0 :                 if (progress === undefined)
     588             :                 {
     589           0 :                         enemyWonder = wonder;
     590           0 :                         break;
     591             :                 }
     592           0 :                 if (enemyWonder && moreAdvanced > progress)
     593           0 :                         continue;
     594           0 :                 enemyWonder = wonder;
     595           0 :                 moreAdvanced = progress;
     596             :         }
     597           0 :         if (enemyWonder)
     598             :         {
     599           0 :                 enemyPlayer = enemyWonder.owner();
     600           0 :                 if (attack.targetPlayer === undefined)
     601           0 :                         this.currentEnemyPlayer = enemyPlayer;
     602             :         }
     603           0 :         return enemyPlayer;
     604             : };
     605             : 
     606             : /**
     607             :  * Target the player with the most relics (including gaia).
     608             :  */
     609           0 : PETRA.AttackManager.prototype.getRelicEnemyPlayer = function(gameState, attack)
     610             : {
     611             :         let enemyPlayer;
     612           0 :         let allRelics = gameState.updatingGlobalCollection("allRelics", API3.Filters.byClass("Relic"));
     613           0 :         let maxRelicsOwned = 0;
     614           0 :         for (let i = 0; i < gameState.sharedScript.playersData.length; ++i)
     615             :         {
     616           0 :                 if (!gameState.isPlayerEnemy(i) || this.defeated[i] ||
     617             :                     i == 0 && !gameState.ai.HQ.victoryManager.tryCaptureGaiaRelic)
     618           0 :                         continue;
     619             : 
     620           0 :                 let relicsCount = allRelics.filter(relic => relic.owner() == i).length;
     621           0 :                 if (relicsCount <= maxRelicsOwned)
     622           0 :                         continue;
     623           0 :                 maxRelicsOwned = relicsCount;
     624           0 :                 enemyPlayer = i;
     625             :         }
     626           0 :         if (enemyPlayer !== undefined)
     627             :         {
     628           0 :                 if (attack.targetPlayer === undefined)
     629           0 :                         this.currentEnemyPlayer = enemyPlayer;
     630           0 :                 if (enemyPlayer == 0)
     631           0 :                         gameState.ai.HQ.victoryManager.resetCaptureGaiaRelic(gameState);
     632             :         }
     633           0 :         return enemyPlayer;
     634             : };
     635             : 
     636             : /** f.e. if we have changed diplomacy with another player. */
     637           0 : PETRA.AttackManager.prototype.cancelAttacksAgainstPlayer = function(gameState, player)
     638             : {
     639           0 :         for (let attackType in this.upcomingAttacks)
     640           0 :                 for (let attack of this.upcomingAttacks[attackType])
     641           0 :                         if (attack.targetPlayer === player)
     642           0 :                                 attack.targetPlayer = undefined;
     643             : 
     644           0 :         for (let attackType in this.startedAttacks)
     645           0 :                 for (let i = 0; i < this.startedAttacks[attackType].length; ++i)
     646             :                 {
     647           0 :                         let attack = this.startedAttacks[attackType][i];
     648           0 :                         if (attack.targetPlayer === player)
     649             :                         {
     650           0 :                                 attack.Abort(gameState);
     651           0 :                                 this.startedAttacks[attackType].splice(i--, 1);
     652             :                         }
     653             :                 }
     654             : };
     655             : 
     656           0 : PETRA.AttackManager.prototype.raidTargetEntity = function(gameState, ent)
     657             : {
     658           0 :         let data = { "target": ent };
     659           0 :         const attackPlan = new PETRA.AttackPlan(gameState, this.Config, this.totalNumber, PETRA.AttackPlan.TYPE_RAID, data);
     660           0 :         if (attackPlan.failed)
     661           0 :                 return null;
     662           0 :         if (this.Config.debug > 1)
     663           0 :                 API3.warn("Military Manager: Raiding plan " + this.totalNumber);
     664           0 :         this.raidNumber++;
     665           0 :         this.totalNumber++;
     666           0 :         attackPlan.init(gameState);
     667           0 :         this.upcomingAttacks[PETRA.AttackPlan.TYPE_RAID].push(attackPlan);
     668           0 :         return attackPlan;
     669             : };
     670             : 
     671             : /**
     672             :  * Return the number of units from any of our attacking armies around this position
     673             :  */
     674           0 : PETRA.AttackManager.prototype.numAttackingUnitsAround = function(pos, dist)
     675             : {
     676           0 :         let num = 0;
     677           0 :         for (let attackType in this.startedAttacks)
     678           0 :                 for (let attack of this.startedAttacks[attackType])
     679             :                 {
     680           0 :                         if (!attack.position)   // this attack may be inside a transport
     681           0 :                                 continue;
     682           0 :                         if (API3.SquareVectorDistance(pos, attack.position) < dist*dist)
     683           0 :                                 num += attack.unitCollection.length;
     684             :                 }
     685           0 :         return num;
     686             : };
     687             : 
     688             : /**
     689             :  * Switch defense armies into an attack one against the given target
     690             :  * data.range: transform all defense armies inside range of the target into a new attack
     691             :  * data.armyID: transform only the defense army ID into a new attack
     692             :  * data.uniqueTarget: the attack will stop when the target is destroyed or captured
     693             :  */
     694           0 : PETRA.AttackManager.prototype.switchDefenseToAttack = function(gameState, target, data)
     695             : {
     696           0 :         if (!target || !target.position())
     697           0 :                 return false;
     698           0 :         if (!data.range && !data.armyID)
     699             :         {
     700           0 :                 API3.warn(" attackManager.switchDefenseToAttack inconsistent data " + uneval(data));
     701           0 :                 return false;
     702             :         }
     703           0 :         let attackData = data.uniqueTarget ? { "uniqueTargetId": target.id() } : undefined;
     704           0 :         let pos = target.position();
     705           0 :         const attackType = PETRA.AttackPlan.TYPE_DEFAULT;
     706           0 :         let attackPlan = new PETRA.AttackPlan(gameState, this.Config, this.totalNumber, attackType, attackData);
     707           0 :         if (attackPlan.failed)
     708           0 :                 return false;
     709           0 :         this.totalNumber++;
     710           0 :         attackPlan.init(gameState);
     711           0 :         this.startedAttacks[attackType].push(attackPlan);
     712             : 
     713           0 :         let targetAccess = PETRA.getLandAccess(gameState, target);
     714           0 :         for (let army of gameState.ai.HQ.defenseManager.armies)
     715             :         {
     716           0 :                 if (data.range)
     717             :                 {
     718           0 :                         army.recalculatePosition(gameState);
     719           0 :                         if (API3.SquareVectorDistance(pos, army.foePosition) > data.range * data.range)
     720           0 :                                 continue;
     721             :                 }
     722           0 :                 else if (army.ID != +data.armyID)
     723           0 :                         continue;
     724             : 
     725           0 :                 while (army.foeEntities.length > 0)
     726           0 :                         army.removeFoe(gameState, army.foeEntities[0]);
     727           0 :                 while (army.ownEntities.length > 0)
     728             :                 {
     729           0 :                         let unitId = army.ownEntities[0];
     730           0 :                         army.removeOwn(gameState, unitId);
     731           0 :                         let unit = gameState.getEntityById(unitId);
     732           0 :                         let accessOk = unit.getMetadata(PlayerID, "transport") !== undefined ||
     733             :                                        unit.position() && PETRA.getLandAccess(gameState, unit) == targetAccess;
     734           0 :                         if (unit && accessOk && attackPlan.isAvailableUnit(gameState, unit))
     735             :                         {
     736           0 :                                 unit.setMetadata(PlayerID, "plan", attackPlan.name);
     737           0 :                                 unit.setMetadata(PlayerID, "role", PETRA.Worker.ROLE_ATTACK);
     738           0 :                                 attackPlan.unitCollection.updateEnt(unit);
     739             :                         }
     740             :                 }
     741             :         }
     742           0 :         if (!attackPlan.unitCollection.hasEntities())
     743             :         {
     744           0 :                 attackPlan.Abort(gameState);
     745           0 :                 return false;
     746             :         }
     747           0 :         for (let unit of attackPlan.unitCollection.values())
     748           0 :                 unit.setMetadata(PlayerID, "role", PETRA.Worker.ROLE_ATTACK);
     749           0 :         attackPlan.targetPlayer = target.owner();
     750           0 :         attackPlan.targetPos = pos;
     751           0 :         attackPlan.target = target;
     752           0 :         attackPlan.state = PETRA.AttackPlan.STATE_ARRIVED;
     753           0 :         return true;
     754             : };
     755             : 
     756           0 : PETRA.AttackManager.prototype.Serialize = function()
     757             : {
     758           0 :         let properties = {
     759             :                 "totalNumber": this.totalNumber,
     760             :                 "attackNumber": this.attackNumber,
     761             :                 "rushNumber": this.rushNumber,
     762             :                 "raidNumber": this.raidNumber,
     763             :                 "debugTime": this.debugTime,
     764             :                 "maxRushes": this.maxRushes,
     765             :                 "rushSize": this.rushSize,
     766             :                 "currentEnemyPlayer": this.currentEnemyPlayer,
     767             :                 "defeated": this.defeated
     768             :         };
     769             : 
     770           0 :         let upcomingAttacks = {};
     771           0 :         for (let key in this.upcomingAttacks)
     772             :         {
     773           0 :                 upcomingAttacks[key] = [];
     774           0 :                 for (let attack of this.upcomingAttacks[key])
     775           0 :                         upcomingAttacks[key].push(attack.Serialize());
     776             :         }
     777             : 
     778           0 :         let startedAttacks = {};
     779           0 :         for (let key in this.startedAttacks)
     780             :         {
     781           0 :                 startedAttacks[key] = [];
     782           0 :                 for (let attack of this.startedAttacks[key])
     783           0 :                         startedAttacks[key].push(attack.Serialize());
     784             :         }
     785             : 
     786           0 :         return { "properties": properties, "upcomingAttacks": upcomingAttacks, "startedAttacks": startedAttacks };
     787             : };
     788             : 
     789           0 : PETRA.AttackManager.prototype.Deserialize = function(gameState, data)
     790             : {
     791           0 :         for (let key in data.properties)
     792           0 :                 this[key] = data.properties[key];
     793             : 
     794           0 :         this.upcomingAttacks = {};
     795           0 :         for (let key in data.upcomingAttacks)
     796             :         {
     797           0 :                 this.upcomingAttacks[key] = [];
     798           0 :                 for (let dataAttack of data.upcomingAttacks[key])
     799             :                 {
     800           0 :                         let attack = new PETRA.AttackPlan(gameState, this.Config, dataAttack.properties.name);
     801           0 :                         attack.Deserialize(gameState, dataAttack);
     802           0 :                         attack.init(gameState);
     803           0 :                         this.upcomingAttacks[key].push(attack);
     804             :                 }
     805             :         }
     806             : 
     807           0 :         this.startedAttacks = {};
     808           0 :         for (let key in data.startedAttacks)
     809             :         {
     810           0 :                 this.startedAttacks[key] = [];
     811           0 :                 for (let dataAttack of data.startedAttacks[key])
     812             :                 {
     813           0 :                         let attack = new PETRA.AttackPlan(gameState, this.Config, dataAttack.properties.name);
     814           0 :                         attack.Deserialize(gameState, dataAttack);
     815           0 :                         attack.init(gameState);
     816           0 :                         this.startedAttacks[key].push(attack);
     817             :                 }
     818             :         }
     819             : };

Generated by: LCOV version 1.14