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

          Line data    Source code
       1             : /** returns true if this unit should be considered as a siege unit */
       2           0 : PETRA.isSiegeUnit = function(ent)
       3             : {
       4           0 :         return ent.hasClasses(["Siege", "Elephant+Melee"]);
       5             : };
       6             : 
       7             : /** returns true if this unit should be considered as "fast". */
       8           0 : PETRA.isFastMoving = function(ent)
       9             : {
      10             :         // TODO: use clever logic based on walkspeed comparisons.
      11           0 :         return ent.hasClass("FastMoving");
      12             : };
      13             : 
      14             : /** returns some sort of DPS * health factor. If you specify a class, it'll use the modifiers against that class too. */
      15           0 : PETRA.getMaxStrength = function(ent, debugLevel, DamageTypeImportance, againstClass)
      16             : {
      17           0 :         let strength = 0;
      18           0 :         let attackTypes = ent.attackTypes();
      19           0 :         let damageTypes = Object.keys(DamageTypeImportance);
      20           0 :         if (!attackTypes)
      21           0 :                 return strength;
      22             : 
      23           0 :         for (let type of attackTypes)
      24             :         {
      25           0 :                 if (type == "Slaughter")
      26           0 :                         continue;
      27             : 
      28           0 :                 let attackStrength = ent.attackStrengths(type);
      29           0 :                 for (let str in attackStrength)
      30             :                 {
      31           0 :                         let val = parseFloat(attackStrength[str]);
      32           0 :                         if (againstClass)
      33           0 :                                 val *= ent.getMultiplierAgainst(type, againstClass);
      34           0 :                         if (DamageTypeImportance[str])
      35           0 :                                 strength += DamageTypeImportance[str] * val / damageTypes.length;
      36           0 :                         else if (debugLevel > 0)
      37           0 :                                 API3.warn("Petra: " + str + " unknown attackStrength in getMaxStrength (please add " + str + "  to config.js).");
      38             :                 }
      39             : 
      40           0 :                 let attackRange = ent.attackRange(type);
      41           0 :                 if (attackRange)
      42           0 :                         strength += attackRange.max * 0.0125;
      43             : 
      44           0 :                 let attackTimes = ent.attackTimes(type);
      45           0 :                 for (let str in attackTimes)
      46             :                 {
      47           0 :                         let val = parseFloat(attackTimes[str]);
      48           0 :                         switch (str)
      49             :                         {
      50             :                         case "repeat":
      51           0 :                                 strength += val / 100000;
      52           0 :                                 break;
      53             :                         case "prepare":
      54           0 :                                 strength -= val / 100000;
      55           0 :                                 break;
      56             :                         default:
      57           0 :                                 API3.warn("Petra: " + str + " unknown attackTimes in getMaxStrength");
      58             :                         }
      59             :                 }
      60             :         }
      61             : 
      62           0 :         let resistanceStrength = ent.resistanceStrengths();
      63             : 
      64           0 :         if (resistanceStrength.Damage)
      65           0 :                 for (let str in resistanceStrength.Damage)
      66             :                 {
      67           0 :                         let val = +resistanceStrength.Damage[str];
      68           0 :                         if (DamageTypeImportance[str])
      69           0 :                                 strength += DamageTypeImportance[str] * val / damageTypes.length;
      70           0 :                         else if (debugLevel > 0)
      71           0 :                                 API3.warn("Petra: " + str + " unknown resistanceStrength in getMaxStrength (please add " + str + "  to config.js).");
      72             :                 }
      73             : 
      74             :         // ToDo: Add support for StatusEffects and Capture.
      75             : 
      76           0 :         return strength * ent.maxHitpoints() / 100.0;
      77             : };
      78             : 
      79             : /** Get access and cache it (except for units as it can change) in metadata if not already done */
      80           0 : PETRA.getLandAccess = function(gameState, ent)
      81             : {
      82           0 :         if (ent.hasClass("Unit"))
      83             :         {
      84           0 :                 let pos = ent.position();
      85           0 :                 if (!pos)
      86             :                 {
      87           0 :                         let holder = PETRA.getHolder(gameState, ent);
      88           0 :                         if (holder)
      89           0 :                                 return PETRA.getLandAccess(gameState, holder);
      90             : 
      91           0 :                         API3.warn("Petra error: entity without position, but not garrisoned");
      92           0 :                         PETRA.dumpEntity(ent);
      93           0 :                         return undefined;
      94             :                 }
      95           0 :                 return gameState.ai.accessibility.getAccessValue(pos);
      96             :         }
      97             : 
      98           0 :         let access = ent.getMetadata(PlayerID, "access");
      99           0 :         if (!access)
     100             :         {
     101           0 :                 access = gameState.ai.accessibility.getAccessValue(ent.position());
     102             :                 // Docks are sometimes not as expected
     103           0 :                 if (access < 2 && ent.buildPlacementType() == "shore")
     104             :                 {
     105           0 :                         let halfDepth = 0;
     106           0 :                         if (ent.get("Footprint/Square"))
     107           0 :                                 halfDepth = +ent.get("Footprint/Square/@depth") / 2;
     108           0 :                         else if (ent.get("Footprint/Circle"))
     109           0 :                                 halfDepth = +ent.get("Footprint/Circle/@radius");
     110           0 :                         let entPos = ent.position();
     111           0 :                         let cosa = Math.cos(ent.angle());
     112           0 :                         let sina = Math.sin(ent.angle());
     113           0 :                         for (let d = 3; d < halfDepth; d += 3)
     114             :                         {
     115           0 :                                 let pos = [ entPos[0] - d * sina,
     116             :                                             entPos[1] - d * cosa];
     117           0 :                                 access = gameState.ai.accessibility.getAccessValue(pos);
     118           0 :                                 if (access > 1)
     119           0 :                                         break;
     120             :                         }
     121             :                 }
     122           0 :                 ent.setMetadata(PlayerID, "access", access);
     123             :         }
     124           0 :         return access;
     125             : };
     126             : 
     127             : /** Sea access always cached as it never changes */
     128           0 : PETRA.getSeaAccess = function(gameState, ent)
     129             : {
     130           0 :         let sea = ent.getMetadata(PlayerID, "sea");
     131           0 :         if (!sea)
     132             :         {
     133           0 :                 sea = gameState.ai.accessibility.getAccessValue(ent.position(), true);
     134             :                 // Docks are sometimes not as expected
     135           0 :                 if (sea < 2 && ent.buildPlacementType() == "shore")
     136             :                 {
     137           0 :                         let entPos = ent.position();
     138           0 :                         let cosa = Math.cos(ent.angle());
     139           0 :                         let sina = Math.sin(ent.angle());
     140           0 :                         for (let d = 3; d < 15; d += 3)
     141             :                         {
     142           0 :                                 let pos = [ entPos[0] + d * sina,
     143             :                                             entPos[1] + d * cosa];
     144           0 :                                 sea = gameState.ai.accessibility.getAccessValue(pos, true);
     145           0 :                                 if (sea > 1)
     146           0 :                                         break;
     147             :                         }
     148             :                 }
     149           0 :                 ent.setMetadata(PlayerID, "sea", sea);
     150             :         }
     151           0 :         return sea;
     152             : };
     153             : 
     154           0 : PETRA.setSeaAccess = function(gameState, ent)
     155             : {
     156           0 :         PETRA.getSeaAccess(gameState, ent);
     157             : };
     158             : 
     159             : /** Decide if we should try to capture (returns true) or destroy (return false) */
     160           0 : PETRA.allowCapture = function(gameState, ent, target)
     161             : {
     162           0 :         if (!target.isCapturable() || !ent.canCapture(target))
     163           0 :                 return false;
     164           0 :         if (target.isInvulnerable())
     165           0 :                 return true;
     166             :         // always try to recapture capture points from an allied, except if it's decaying
     167           0 :         if (gameState.isPlayerAlly(target.owner()))
     168           0 :                 return !target.decaying();
     169             : 
     170           0 :         let antiCapture = target.defaultRegenRate();
     171           0 :         if (target.isGarrisonHolder())
     172             :         {
     173           0 :                 const garrisonRegenRate = target.garrisonRegenRate();
     174           0 :                 for (const garrisonedEntity of target.garrisoned())
     175           0 :                         antiCapture += garrisonRegenRate * (gameState.getEntityById(garrisonedEntity)?.captureStrength() || 0);
     176             :         }
     177             : 
     178           0 :         if (target.decaying())
     179           0 :                 antiCapture -= target.territoryDecayRate();
     180             : 
     181             :         let capture;
     182           0 :         let capturableTargets = gameState.ai.HQ.capturableTargets;
     183           0 :         if (!capturableTargets.has(target.id()))
     184             :         {
     185           0 :                 capture = ent.captureStrength() * PETRA.getAttackBonus(ent, target, "Capture");
     186           0 :                 capturableTargets.set(target.id(), { "strength": capture, "ents": new Set([ent.id()]) });
     187             :         }
     188             :         else
     189             :         {
     190           0 :                 let capturable = capturableTargets.get(target.id());
     191           0 :                 if (!capturable.ents.has(ent.id()))
     192             :                 {
     193           0 :                         capturable.strength += ent.captureStrength() * PETRA.getAttackBonus(ent, target, "Capture");
     194           0 :                         capturable.ents.add(ent.id());
     195             :                 }
     196           0 :                 capture = capturable.strength;
     197             :         }
     198           0 :         capture *= 1 / (0.1 + 0.9*target.healthLevel());
     199           0 :         let sumCapturePoints = target.capturePoints().reduce((a, b) => a + b);
     200           0 :         if (target.hasDefensiveFire() && target.isGarrisonHolder() && target.garrisoned())
     201           0 :                 return capture > antiCapture + sumCapturePoints/50;
     202           0 :         return capture > antiCapture + sumCapturePoints/80;
     203             : };
     204             : 
     205           0 : PETRA.getAttackBonus = function(ent, target, type)
     206             : {
     207           0 :         let attackBonus = 1;
     208           0 :         if (!ent.get("Attack/" + type) || !ent.get("Attack/" + type + "/Bonuses"))
     209           0 :                 return attackBonus;
     210           0 :         let bonuses = ent.get("Attack/" + type + "/Bonuses");
     211           0 :         for (let key in bonuses)
     212             :         {
     213           0 :                 let bonus = bonuses[key];
     214           0 :                 if (bonus.Civ && bonus.Civ !== target.civ())
     215           0 :                         continue;
     216           0 :                 if (!bonus.Classes || target.hasClasses(bonus.Classes))
     217           0 :                         attackBonus *= bonus.Multiplier;
     218             :         }
     219           0 :         return attackBonus;
     220             : };
     221             : 
     222             : /** Makes the worker deposit the currently carried resources at the closest accessible dropsite */
     223           0 : PETRA.returnResources = function(gameState, ent)
     224             : {
     225           0 :         if (!ent.resourceCarrying() || !ent.resourceCarrying().length || !ent.position())
     226           0 :                 return false;
     227             : 
     228           0 :         let resource = ent.resourceCarrying()[0].type;
     229             : 
     230             :         let closestDropsite;
     231           0 :         let distmin = Math.min();
     232           0 :         let access = PETRA.getLandAccess(gameState, ent);
     233           0 :         let dropsiteCollection = gameState.playerData.hasSharedDropsites ?
     234             :                                  gameState.getAnyDropsites(resource) : gameState.getOwnDropsites(resource);
     235           0 :         for (let dropsite of dropsiteCollection.values())
     236             :         {
     237           0 :                 if (!dropsite.position())
     238           0 :                         continue;
     239           0 :                 let owner = dropsite.owner();
     240             :                 // owner !== PlayerID can only happen when hasSharedDropsites === true, so no need to test it again
     241           0 :                 if (owner !== PlayerID && (!dropsite.isSharedDropsite() || !gameState.isPlayerMutualAlly(owner)))
     242           0 :                         continue;
     243           0 :                 if (PETRA.getLandAccess(gameState, dropsite) != access)
     244           0 :                         continue;
     245           0 :                 let dist = API3.SquareVectorDistance(ent.position(), dropsite.position());
     246           0 :                 if (dist > distmin)
     247           0 :                         continue;
     248           0 :                 distmin = dist;
     249           0 :                 closestDropsite = dropsite;
     250             :         }
     251             : 
     252           0 :         if (!closestDropsite)
     253           0 :                 return false;
     254           0 :         ent.returnResources(closestDropsite);
     255           0 :         return true;
     256             : };
     257             : 
     258             : /** is supply full taking into account gatherers affected during this turn */
     259           0 : PETRA.IsSupplyFull = function(gameState, ent)
     260             : {
     261           0 :         return ent.isFull() === true ||
     262             :                 ent.resourceSupplyNumGatherers() + gameState.ai.HQ.basesManager.GetTCGatherer(ent.id()) >= ent.maxGatherers();
     263             : };
     264             : 
     265             : /**
     266             :  * Get the best base (in terms of distance and accessIndex) for an entity.
     267             :  * It should be on the same accessIndex for structures.
     268             :  * If nothing found, return the noBase for units and undefined for structures.
     269             :  * If exclude is given, we exclude the base with ID = exclude.
     270             :  */
     271           0 : PETRA.getBestBase = function(gameState, ent, onlyConstructedBase = false, exclude = false)
     272             : {
     273           0 :         let pos = ent.position();
     274             :         let accessIndex;
     275           0 :         if (!pos)
     276             :         {
     277           0 :                 let holder = PETRA.getHolder(gameState, ent);
     278           0 :                 if (!holder || !holder.position())
     279             :                 {
     280           0 :                         API3.warn("Petra error: entity without position, but not garrisoned");
     281           0 :                         PETRA.dumpEntity(ent);
     282           0 :                         return gameState.ai.HQ.basesManager.baselessBase();
     283             :                 }
     284           0 :                 pos = holder.position();
     285           0 :                 accessIndex = PETRA.getLandAccess(gameState, holder);
     286             :         }
     287             :         else
     288           0 :                 accessIndex = PETRA.getLandAccess(gameState, ent);
     289             : 
     290           0 :         let distmin = Math.min();
     291             :         let dist;
     292             :         let bestbase;
     293           0 :         for (const base of gameState.ai.HQ.baseManagers())
     294             :         {
     295           0 :                 if (base.ID == gameState.ai.HQ.basesManager.baselessBase().ID || exclude && base.ID == exclude)
     296           0 :                         continue;
     297           0 :                 if (onlyConstructedBase && (!base.anchor || base.anchor.foundationProgress() !== undefined))
     298           0 :                         continue;
     299           0 :                 if (ent.hasClass("Structure") && base.accessIndex != accessIndex)
     300           0 :                         continue;
     301           0 :                 if (base.anchor && base.anchor.position())
     302           0 :                         dist = API3.SquareVectorDistance(base.anchor.position(), pos);
     303             :                 else
     304             :                 {
     305           0 :                         let found = false;
     306           0 :                         for (let structure of base.buildings.values())
     307             :                         {
     308           0 :                                 if (!structure.position())
     309           0 :                                         continue;
     310           0 :                                 dist = API3.SquareVectorDistance(structure.position(), pos);
     311           0 :                                 found = true;
     312           0 :                                 break;
     313             :                         }
     314           0 :                         if (!found)
     315           0 :                                 continue;
     316             :                 }
     317           0 :                 if (base.accessIndex != accessIndex)
     318           0 :                         dist += 50000000;
     319           0 :                 if (!base.anchor)
     320           0 :                         dist += 50000000;
     321           0 :                 if (dist > distmin)
     322           0 :                         continue;
     323           0 :                 distmin = dist;
     324           0 :                 bestbase = base;
     325             :         }
     326           0 :         if (!bestbase && !ent.hasClass("Structure"))
     327           0 :                 bestbase = gameState.ai.HQ.basesManager.baselessBase();
     328           0 :         return bestbase;
     329             : };
     330             : 
     331           0 : PETRA.getHolder = function(gameState, ent)
     332             : {
     333           0 :         for (let holder of gameState.getEntities().values())
     334             :         {
     335           0 :                 if (holder.isGarrisonHolder() && holder.garrisoned().indexOf(ent.id()) !== -1)
     336           0 :                         return holder;
     337             :         }
     338           0 :         return undefined;
     339             : };
     340             : 
     341             : /** return the template of the built foundation if a foundation, otherwise return the entity itself */
     342           0 : PETRA.getBuiltEntity = function(gameState, ent)
     343             : {
     344           0 :         if (ent.foundationProgress() !== undefined)
     345           0 :                 return gameState.getBuiltTemplate(ent.templateName());
     346             : 
     347           0 :         return ent;
     348             : };
     349             : 
     350             : /**
     351             :  * return true if it is not worth finishing this building (it would surely decay)
     352             :  * TODO implement the other conditions
     353             :  */
     354           0 : PETRA.isNotWorthBuilding = function(gameState, ent)
     355             : {
     356           0 :         if (gameState.ai.HQ.territoryMap.getOwner(ent.position()) !== PlayerID)
     357             :         {
     358           0 :                 let buildTerritories = ent.buildTerritories();
     359           0 :                 if (buildTerritories && (!buildTerritories.length || buildTerritories.length === 1 && buildTerritories[0] === "own"))
     360           0 :                         return true;
     361             :         }
     362           0 :         return false;
     363             : };
     364             : 
     365             : /**
     366             :  * Check if the straight line between the two positions crosses an enemy territory
     367             :  */
     368           0 : PETRA.isLineInsideEnemyTerritory = function(gameState, pos1, pos2, step=70)
     369             : {
     370           0 :         let n = Math.floor(Math.sqrt(API3.SquareVectorDistance(pos1, pos2))/step) + 1;
     371           0 :         let stepx = (pos2[0] - pos1[0]) / n;
     372           0 :         let stepy = (pos2[1] - pos1[1]) / n;
     373           0 :         for (let i = 1; i < n; ++i)
     374             :         {
     375           0 :                 let pos = [pos1[0]+i*stepx, pos1[1]+i*stepy];
     376           0 :                 let owner = gameState.ai.HQ.territoryMap.getOwner(pos);
     377           0 :                 if (owner && gameState.isPlayerEnemy(owner))
     378           0 :                         return true;
     379             :         }
     380           0 :         return false;
     381             : };
     382             : 
     383           0 : PETRA.gatherTreasure = function(gameState, ent, water = false)
     384             : {
     385           0 :         if (!gameState.ai.HQ.treasures.hasEntities())
     386           0 :                 return false;
     387           0 :         if (!ent || !ent.position())
     388           0 :                 return false;
     389           0 :         if (!ent.isTreasureCollector())
     390           0 :                 return false;
     391             :         let treasureFound;
     392           0 :         let distmin = Math.min();
     393           0 :         let access = water ? PETRA.getSeaAccess(gameState, ent) : PETRA.getLandAccess(gameState, ent);
     394           0 :         for (let treasure of gameState.ai.HQ.treasures.values())
     395             :         {
     396             :                 // let some time for the previous gatherer to reach the treasure before trying again
     397           0 :                 let lastGathered = treasure.getMetadata(PlayerID, "lastGathered");
     398           0 :                 if (lastGathered && gameState.ai.elapsedTime - lastGathered < 20)
     399           0 :                         continue;
     400           0 :                 if (!water && access != PETRA.getLandAccess(gameState, treasure))
     401           0 :                         continue;
     402           0 :                 if (water && access != PETRA.getSeaAccess(gameState, treasure))
     403           0 :                         continue;
     404           0 :                 let territoryOwner = gameState.ai.HQ.territoryMap.getOwner(treasure.position());
     405           0 :                 if (territoryOwner != 0 && !gameState.isPlayerAlly(territoryOwner))
     406           0 :                         continue;
     407           0 :                 let dist = API3.SquareVectorDistance(ent.position(), treasure.position());
     408           0 :                 if (dist > 120000 || territoryOwner != PlayerID && dist > 14000) // AI has no LOS, so restrict it a bit
     409           0 :                         continue;
     410           0 :                 if (dist > distmin)
     411           0 :                         continue;
     412           0 :                 distmin = dist;
     413           0 :                 treasureFound = treasure;
     414             :         }
     415           0 :         if (!treasureFound)
     416           0 :                 return false;
     417           0 :         treasureFound.setMetadata(PlayerID, "lastGathered", gameState.ai.elapsedTime);
     418           0 :         ent.collectTreasure(treasureFound);
     419           0 :         ent.setMetadata(PlayerID, "treasure", treasureFound.id());
     420           0 :         return true;
     421             : };
     422             : 
     423           0 : PETRA.dumpEntity = function(ent)
     424             : {
     425           0 :         if (!ent)
     426           0 :                 return;
     427           0 :         API3.warn(" >>> id " + ent.id() + " name " + ent.genericName() + " pos " + ent.position() +
     428             :                   " state " + ent.unitAIState());
     429           0 :         API3.warn(" base " + ent.getMetadata(PlayerID, "base") + " >>> role " + ent.getMetadata(PlayerID, "role") +
     430             :                   " subrole " + ent.getMetadata(PlayerID, "subrole"));
     431           0 :         API3.warn("owner " + ent.owner() + " health " + ent.hitpoints() + " healthMax " + ent.maxHitpoints() +
     432             :                   " foundationProgress " + ent.foundationProgress());
     433           0 :         API3.warn(" garrisoning " + ent.getMetadata(PlayerID, "garrisoning") +
     434             :                   " garrisonHolder " + ent.getMetadata(PlayerID, "garrisonHolder") +
     435             :                   " plan " + ent.getMetadata(PlayerID, "plan")      + " transport " + ent.getMetadata(PlayerID, "transport"));
     436           0 :         API3.warn(" stance " + ent.getStance() + " transporter " + ent.getMetadata(PlayerID, "transporter") +
     437             :                   " gather-type " + ent.getMetadata(PlayerID, "gather-type") +
     438             :                   " target-foundation " + ent.getMetadata(PlayerID, "target-foundation") +
     439             :                   " PartOfArmy " + ent.getMetadata(PlayerID, "PartOfArmy"));
     440             : };

Generated by: LCOV version 1.14