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

          Line data    Source code
       1           0 : var API3 = function(m)
       2             : {
       3             : 
       4             : // defines a template.
       5           0 : m.Template = m.Class({
       6             : 
       7             :         "_init": function(sharedAI, templateName, template)
       8             :         {
       9           0 :                 this._templateName = templateName;
      10           0 :                 this._template = template;
      11             :                 // save a reference to the template tech modifications
      12           0 :                 if (!sharedAI._templatesModifications[this._templateName])
      13           0 :                         sharedAI._templatesModifications[this._templateName] = {};
      14           0 :                 this._templateModif = sharedAI._templatesModifications[this._templateName];
      15           0 :                 this._tpCache = new Map();
      16             :         },
      17             : 
      18             :         // Helper function to return a template value, adjusting for tech.
      19             :         "get": function(string)
      20             :         {
      21           0 :                 if (this._entityModif && this._entityModif.has(string))
      22           0 :                         return this._entityModif.get(string);
      23           0 :                 else if (this._templateModif)
      24             :                 {
      25           0 :                         let owner = this._entity ? this._entity.owner : PlayerID;
      26           0 :                         if (this._templateModif[owner] && this._templateModif[owner].has(string))
      27           0 :                                 return this._templateModif[owner].get(string);
      28             :                 }
      29             : 
      30           0 :                 if (!this._tpCache.has(string))
      31             :                 {
      32           0 :                         let value = this._template;
      33           0 :                         let args = string.split("/");
      34           0 :                         for (let arg of args)
      35             :                         {
      36           0 :                                 value = value[arg];
      37           0 :                                 if (value == undefined)
      38           0 :                                         break;
      39             :                         }
      40           0 :                         this._tpCache.set(string, value);
      41             :                 }
      42           0 :                 return this._tpCache.get(string);
      43             :         },
      44             : 
      45           0 :         "templateName": function() { return this._templateName; },
      46             : 
      47           0 :         "genericName": function() { return this.get("Identity/GenericName"); },
      48             : 
      49           0 :         "civ": function() { return this.get("Identity/Civ"); },
      50             : 
      51             :         "matchLimit": function() {
      52           0 :                 if (!this.get("TrainingRestrictions"))
      53           0 :                         return undefined;
      54           0 :                 return this.get("TrainingRestrictions/MatchLimit");
      55             :         },
      56             : 
      57             :         "classes": function() {
      58           0 :                 let template = this.get("Identity");
      59           0 :                 if (!template)
      60           0 :                         return undefined;
      61           0 :                 return GetIdentityClasses(template);
      62             :         },
      63             : 
      64             :         "hasClass": function(name) {
      65           0 :                 if (!this._classes)
      66           0 :                         this._classes = this.classes();
      67           0 :                 return this._classes && this._classes.indexOf(name) != -1;
      68             :         },
      69             : 
      70             :         "hasClasses": function(array) {
      71           0 :                 if (!this._classes)
      72           0 :                         this._classes = this.classes();
      73           0 :                 return this._classes && MatchesClassList(this._classes, array);
      74             :         },
      75             : 
      76             :         "requirements": function() {
      77           0 :                 return this.get("Identity/Requirements");
      78             :         },
      79             : 
      80             :         "available": function(gameState) {
      81           0 :                 const requirements = this.requirements();
      82           0 :                 return !requirements || Sim.RequirementsHelper.AreRequirementsMet(requirements, PlayerID);
      83             :         },
      84             : 
      85             :         "cost": function(productionQueue) {
      86           0 :                 if (!this.get("Cost"))
      87           0 :                         return {};
      88             : 
      89           0 :                 let ret = {};
      90           0 :                 for (let type in this.get("Cost/Resources"))
      91           0 :                         ret[type] = +this.get("Cost/Resources/" + type);
      92           0 :                 return ret;
      93             :         },
      94             : 
      95             :         "costSum": function(productionQueue) {
      96           0 :                 let cost = this.cost(productionQueue);
      97           0 :                 if (!cost)
      98           0 :                         return 0;
      99           0 :                 let ret = 0;
     100           0 :                 for (let type in cost)
     101           0 :                         ret += cost[type];
     102           0 :                 return ret;
     103             :         },
     104             : 
     105             :         "techCostMultiplier": function(type) {
     106           0 :                 return +(this.get("Researcher/TechCostMultiplier/"+type) || 1);
     107             :         },
     108             : 
     109             :         /**
     110             :          * Returns { "max": max, "min": min } or undefined if no obstruction.
     111             :          * max: radius of the outer circle surrounding this entity's obstruction shape
     112             :          * min: radius of the inner circle
     113             :          */
     114             :         "obstructionRadius": function() {
     115           0 :                 if (!this.get("Obstruction"))
     116           0 :                         return undefined;
     117             : 
     118           0 :                 if (this.get("Obstruction/Static"))
     119             :                 {
     120           0 :                         let w = +this.get("Obstruction/Static/@width");
     121           0 :                         let h = +this.get("Obstruction/Static/@depth");
     122           0 :                         return { "max": Math.sqrt(w * w + h * h) / 2, "min": Math.min(h, w) / 2 };
     123             :                 }
     124             : 
     125           0 :                 if (this.get("Obstruction/Unit"))
     126             :                 {
     127           0 :                         let r = +this.get("Obstruction/Unit/@radius");
     128           0 :                         return { "max": r, "min": r };
     129             :                 }
     130             : 
     131           0 :                 let right = this.get("Obstruction/Obstructions/Right");
     132           0 :                 let left = this.get("Obstruction/Obstructions/Left");
     133           0 :                 if (left && right)
     134             :                 {
     135           0 :                         let w = +right["@x"] + right["@width"] / 2 - left["@x"] + left["@width"] / 2;
     136           0 :                         let h = Math.max(+right["@z"] + right["@depth"] / 2, +left["@z"] + left["@depth"] / 2) -
     137             :                                 Math.min(+right["@z"] - right["@depth"] / 2, +left["@z"] - left["@depth"] / 2);
     138           0 :                         return { "max": Math.sqrt(w * w + h * h) / 2, "min": Math.min(h, w) / 2 };
     139             :                 }
     140             : 
     141           0 :                 return { "max": 0, "min": 0 }; // Units have currently no obstructions
     142             :         },
     143             : 
     144             :         /**
     145             :          * Returns the radius of a circle surrounding this entity's footprint.
     146             :          */
     147             :         "footprintRadius": function() {
     148           0 :                 if (!this.get("Footprint"))
     149           0 :                         return undefined;
     150             : 
     151           0 :                 if (this.get("Footprint/Square"))
     152             :                 {
     153           0 :                         let w = +this.get("Footprint/Square/@width");
     154           0 :                         let h = +this.get("Footprint/Square/@depth");
     155           0 :                         return Math.sqrt(w * w + h * h) / 2;
     156             :                 }
     157             : 
     158           0 :                 if (this.get("Footprint/Circle"))
     159           0 :                         return +this.get("Footprint/Circle/@radius");
     160             : 
     161           0 :                 return 0; // this should never happen
     162             :         },
     163             : 
     164           0 :         "maxHitpoints": function() { return +(this.get("Health/Max") || 0); },
     165             : 
     166             :         "isHealable": function()
     167             :         {
     168           0 :                 if (this.get("Health") !== undefined)
     169           0 :                         return this.get("Health/Unhealable") !== "true";
     170           0 :                 return false;
     171             :         },
     172             : 
     173           0 :         "isRepairable": function() { return this.get("Repairable") !== undefined; },
     174             : 
     175             :         "getPopulationBonus": function() {
     176           0 :                 if (!this.get("Population"))
     177           0 :                         return 0;
     178             : 
     179           0 :                 return +this.get("Population/Bonus");
     180             :         },
     181             : 
     182             :         "resistanceStrengths": function() {
     183           0 :                 let resistanceTypes = this.get("Resistance");
     184           0 :                 if (!resistanceTypes || !resistanceTypes.Entity)
     185           0 :                         return undefined;
     186             : 
     187           0 :                 let resistance = {};
     188           0 :                 if (resistanceTypes.Entity.Capture)
     189           0 :                         resistance.Capture = +this.get("Resistance/Entity/Capture");
     190             : 
     191           0 :                 if (resistanceTypes.Entity.Damage)
     192             :                 {
     193           0 :                         resistance.Damage = {};
     194           0 :                         for (let damageType in resistanceTypes.Entity.Damage)
     195           0 :                                 resistance.Damage[damageType] = +this.get("Resistance/Entity/Damage/" + damageType);
     196             :                 }
     197             : 
     198             :                 // ToDo: Resistance to StatusEffects.
     199             : 
     200           0 :                 return resistance;
     201             :         },
     202             : 
     203             :         "attackTypes": function() {
     204           0 :                 let attack = this.get("Attack");
     205           0 :                 if (!attack)
     206           0 :                         return undefined;
     207             : 
     208           0 :                 let ret = [];
     209           0 :                 for (let type in attack)
     210           0 :                         ret.push(type);
     211           0 :                 return ret;
     212             :         },
     213             : 
     214             :         "attackRange": function(type) {
     215           0 :                 if (!this.get("Attack/" + type))
     216           0 :                         return undefined;
     217             : 
     218           0 :                 return {
     219             :                         "max": +this.get("Attack/" + type +"/MaxRange"),
     220             :                         "min": +(this.get("Attack/" + type +"/MinRange") || 0)
     221             :                 };
     222             :         },
     223             : 
     224             :         "attackStrengths": function(type) {
     225           0 :                 let attackDamageTypes = this.get("Attack/" + type + "/Damage");
     226           0 :                 if (!attackDamageTypes)
     227           0 :                         return undefined;
     228             : 
     229           0 :                 let damage = {};
     230           0 :                 for (let damageType in attackDamageTypes)
     231           0 :                         damage[damageType] = +attackDamageTypes[damageType];
     232             : 
     233           0 :                 return damage;
     234             :         },
     235             : 
     236             :         "captureStrength": function() {
     237           0 :                 if (!this.get("Attack/Capture"))
     238           0 :                         return undefined;
     239             : 
     240           0 :                 return +this.get("Attack/Capture/Capture") || 0;
     241             :         },
     242             : 
     243             :         "attackTimes": function(type) {
     244           0 :                 if (!this.get("Attack/" + type))
     245           0 :                         return undefined;
     246             : 
     247           0 :                 return {
     248             :                         "prepare": +(this.get("Attack/" + type + "/PrepareTime") || 0),
     249             :                         "repeat": +(this.get("Attack/" + type + "/RepeatTime") || 1000)
     250             :                 };
     251             :         },
     252             : 
     253             :         // returns the classes this templates counters:
     254             :         // Return type is [ [-neededClasses- , multiplier], … ].
     255             :         "getCounteredClasses": function() {
     256           0 :                 let attack = this.get("Attack");
     257           0 :                 if (!attack)
     258           0 :                         return undefined;
     259             : 
     260           0 :                 let Classes = [];
     261           0 :                 for (let type in attack)
     262             :                 {
     263           0 :                         let bonuses = this.get("Attack/" + type + "/Bonuses");
     264           0 :                         if (!bonuses)
     265           0 :                                 continue;
     266           0 :                         for (let b in bonuses)
     267             :                         {
     268           0 :                                 let bonusClasses = this.get("Attack/" + type + "/Bonuses/" + b + "/Classes");
     269           0 :                                 if (bonusClasses)
     270           0 :                                         Classes.push([bonusClasses.split(" "), +this.get("Attack/" + type +"/Bonuses/" + b +"/Multiplier")]);
     271             :                         }
     272             :                 }
     273           0 :                 return Classes;
     274             :         },
     275             : 
     276             :         // returns true if the entity counters the target entity.
     277             :         // TODO: refine using the multiplier
     278             :         "counters": function(target) {
     279           0 :                 let attack = this.get("Attack");
     280           0 :                 if (!attack)
     281           0 :                         return false;
     282           0 :                 let mcounter = [];
     283           0 :                 for (let type in attack)
     284             :                 {
     285           0 :                         let bonuses = this.get("Attack/" + type + "/Bonuses");
     286           0 :                         if (!bonuses)
     287           0 :                                 continue;
     288           0 :                         for (let b in bonuses)
     289             :                         {
     290           0 :                                 let bonusClasses = this.get("Attack/" + type + "/Bonuses/" + b + "/Classes");
     291           0 :                                 if (bonusClasses)
     292           0 :                                         mcounter.concat(bonusClasses.split(" "));
     293             :                         }
     294             :                 }
     295           0 :                 return target.hasClasses(mcounter);
     296             :         },
     297             : 
     298             :         // returns, if it exists, the multiplier from each attack against a given class
     299             :         "getMultiplierAgainst": function(type, againstClass) {
     300           0 :                 if (!this.get("Attack/" + type +""))
     301           0 :                         return undefined;
     302             : 
     303           0 :                 let bonuses = this.get("Attack/" + type + "/Bonuses");
     304           0 :                 if (bonuses)
     305             :                 {
     306           0 :                         for (let b in bonuses)
     307             :                         {
     308           0 :                                 let bonusClasses = this.get("Attack/" + type + "/Bonuses/" + b + "/Classes");
     309           0 :                                 if (!bonusClasses)
     310           0 :                                         continue;
     311           0 :                                 for (let bcl of bonusClasses.split(" "))
     312           0 :                                         if (bcl == againstClass)
     313           0 :                                                 return +this.get("Attack/" + type + "/Bonuses/" + b + "/Multiplier");
     314             :                         }
     315             :                 }
     316           0 :                 return 1;
     317             :         },
     318             : 
     319             :         "buildableEntities": function(civ) {
     320           0 :                 let templates = this.get("Builder/Entities/_string");
     321           0 :                 if (!templates)
     322           0 :                         return [];
     323           0 :                 return templates.replace(/\{native\}/g, this.civ()).replace(/\{civ\}/g, civ).split(/\s+/);
     324             :         },
     325             : 
     326             :         "trainableEntities": function(civ) {
     327           0 :                 const templates = this.get("Trainer/Entities/_string");
     328           0 :                 if (!templates)
     329           0 :                         return undefined;
     330           0 :                 return templates.replace(/\{native\}/g, this.civ()).replace(/\{civ\}/g, civ).split(/\s+/);
     331             :         },
     332             : 
     333             :         "researchableTechs": function(gameState, civ) {
     334           0 :                 const templates = this.get("Researcher/Technologies/_string");
     335           0 :                 if (!templates)
     336           0 :                         return undefined;
     337           0 :                 let techs = templates.split(/\s+/);
     338           0 :                 for (let i = 0; i < techs.length; ++i)
     339             :                 {
     340           0 :                         let tech = techs[i];
     341           0 :                         if (tech.indexOf("{civ}") == -1)
     342           0 :                                 continue;
     343           0 :                         let civTech = tech.replace("{civ}", civ);
     344           0 :                         techs[i] = TechnologyTemplates.Has(civTech) ?
     345             :                                    civTech : tech.replace("{civ}", "generic");
     346             :                 }
     347           0 :                 return techs;
     348             :         },
     349             : 
     350             :         "resourceSupplyType": function() {
     351           0 :                 if (!this.get("ResourceSupply"))
     352           0 :                         return undefined;
     353           0 :                 let [type, subtype] = this.get("ResourceSupply/Type").split('.');
     354           0 :                 return { "generic": type, "specific": subtype };
     355             :         },
     356             : 
     357             :         "getResourceType": function() {
     358           0 :                 if (!this.get("ResourceSupply"))
     359           0 :                         return undefined;
     360           0 :                 return this.get("ResourceSupply/Type").split('.')[0];
     361             :         },
     362             : 
     363           0 :         "getDiminishingReturns": function() { return +(this.get("ResourceSupply/DiminishingReturns") || 1); },
     364             : 
     365           0 :         "resourceSupplyMax": function() { return +this.get("ResourceSupply/Max"); },
     366             : 
     367           0 :         "maxGatherers": function() { return +(this.get("ResourceSupply/MaxGatherers") || 0); },
     368             : 
     369             :         "resourceGatherRates": function() {
     370           0 :                 if (!this.get("ResourceGatherer"))
     371           0 :                         return undefined;
     372           0 :                 let ret = {};
     373           0 :                 let baseSpeed = +this.get("ResourceGatherer/BaseSpeed");
     374           0 :                 for (let r in this.get("ResourceGatherer/Rates"))
     375           0 :                         ret[r] = +this.get("ResourceGatherer/Rates/" + r) * baseSpeed;
     376           0 :                 return ret;
     377             :         },
     378             : 
     379             :         "resourceDropsiteTypes": function() {
     380           0 :                 if (!this.get("ResourceDropsite"))
     381           0 :                         return undefined;
     382             : 
     383           0 :                 let types = this.get("ResourceDropsite/Types");
     384           0 :                 return types ? types.split(/\s+/) : [];
     385             :         },
     386             : 
     387             :         "isResourceDropsite": function(resourceType) {
     388           0 :                 const types = this.resourceDropsiteTypes();
     389           0 :                 return types && (!resourceType || types.indexOf(resourceType) !== -1);
     390             :         },
     391             : 
     392           0 :         "isTreasure": function() { return this.get("Treasure") !== undefined; },
     393             : 
     394             :         "treasureResources": function() {
     395           0 :                 if (!this.get("Treasure"))
     396           0 :                         return undefined;
     397           0 :                 let ret = {};
     398           0 :                 for (let r in this.get("Treasure/Resources"))
     399           0 :                         ret[r] = +this.get("Treasure/Resources/" + r);
     400           0 :                 return ret;
     401             :         },
     402             : 
     403             : 
     404           0 :         "garrisonableClasses": function() { return this.get("GarrisonHolder/List/_string"); },
     405             : 
     406           0 :         "garrisonMax": function() { return this.get("GarrisonHolder/Max"); },
     407             : 
     408           0 :         "garrisonSize": function() { return this.get("Garrisonable/Size"); },
     409             : 
     410           0 :         "garrisonEjectHealth": function() { return +this.get("GarrisonHolder/EjectHealth"); },
     411             : 
     412           0 :         "getDefaultArrow": function() { return +this.get("BuildingAI/DefaultArrowCount"); },
     413             : 
     414           0 :         "getArrowMultiplier": function() { return +this.get("BuildingAI/GarrisonArrowMultiplier"); },
     415             : 
     416             :         "getGarrisonArrowClasses": function() {
     417           0 :                 if (!this.get("BuildingAI"))
     418           0 :                         return undefined;
     419           0 :                 return this.get("BuildingAI/GarrisonArrowClasses").split(/\s+/);
     420             :         },
     421             : 
     422           0 :         "buffHeal": function() { return +this.get("GarrisonHolder/BuffHeal"); },
     423             : 
     424           0 :         "promotion": function() { return this.get("Promotion/Entity"); },
     425             : 
     426           0 :         "isPackable": function() { return this.get("Pack") != undefined; },
     427             : 
     428             :         "isHuntable": function() {
     429             :                 // Do not hunt retaliating animals (dead animals can be used).
     430             :                 // Assume entities which can attack, will attack.
     431           0 :                 return this.get("ResourceSupply/KillBeforeGather") &&
     432             :                         (!this.get("Health") || !this.get("Attack"));
     433             :         },
     434             : 
     435           0 :         "walkSpeed": function() { return +this.get("UnitMotion/WalkSpeed"); },
     436             : 
     437           0 :         "trainingCategory": function() { return this.get("TrainingRestrictions/Category"); },
     438             : 
     439             :         "buildTime": function(researcher) {
     440           0 :                 let time = +this.get("Cost/BuildTime");
     441           0 :                 if (researcher)
     442           0 :                         time *= researcher.techCostMultiplier("time");
     443           0 :                 return time;
     444             :         },
     445             : 
     446           0 :         "buildCategory": function() { return this.get("BuildRestrictions/Category"); },
     447             : 
     448             :         "buildDistance": function() {
     449           0 :                 let distance = this.get("BuildRestrictions/Distance");
     450           0 :                 if (!distance)
     451           0 :                         return undefined;
     452           0 :                 let ret = {};
     453           0 :                 for (let key in distance)
     454           0 :                         ret[key] = this.get("BuildRestrictions/Distance/" + key);
     455           0 :                 return ret;
     456             :         },
     457             : 
     458           0 :         "buildPlacementType": function() { return this.get("BuildRestrictions/PlacementType"); },
     459             : 
     460             :         "buildTerritories": function() {
     461           0 :                 if (!this.get("BuildRestrictions"))
     462           0 :                         return undefined;
     463           0 :                 let territory = this.get("BuildRestrictions/Territory");
     464           0 :                 return !territory ? undefined : territory.split(/\s+/);
     465             :         },
     466             : 
     467             :         "hasBuildTerritory": function(territory) {
     468           0 :                 let territories = this.buildTerritories();
     469           0 :                 return territories && territories.indexOf(territory) != -1;
     470             :         },
     471             : 
     472             :         "hasTerritoryInfluence": function() {
     473           0 :                 return this.get("TerritoryInfluence") !== undefined;
     474             :         },
     475             : 
     476             :         "hasDefensiveFire": function() {
     477           0 :                 if (!this.get("Attack/Ranged"))
     478           0 :                         return false;
     479           0 :                 return this.getDefaultArrow() || this.getArrowMultiplier();
     480             :         },
     481             : 
     482             :         "territoryInfluenceRadius": function() {
     483           0 :                 if (this.get("TerritoryInfluence") !== undefined)
     484           0 :                         return +this.get("TerritoryInfluence/Radius");
     485           0 :                 return -1;
     486             :         },
     487             : 
     488             :         "territoryInfluenceWeight": function() {
     489           0 :                 if (this.get("TerritoryInfluence") !== undefined)
     490           0 :                         return +this.get("TerritoryInfluence/Weight");
     491           0 :                 return -1;
     492             :         },
     493             : 
     494             :         "territoryDecayRate": function() {
     495           0 :                 return +(this.get("TerritoryDecay/DecayRate") || 0);
     496             :         },
     497             : 
     498             :         "defaultRegenRate": function() {
     499           0 :                 return +(this.get("Capturable/RegenRate") || 0);
     500             :         },
     501             : 
     502             :         "garrisonRegenRate": function() {
     503           0 :                 return +(this.get("Capturable/GarrisonRegenRate") || 0);
     504             :         },
     505             : 
     506           0 :         "visionRange": function() { return +this.get("Vision/Range"); },
     507             : 
     508           0 :         "gainMultiplier": function() { return +this.get("Trader/GainMultiplier"); },
     509             : 
     510           0 :         "isBuilder": function() { return this.get("Builder") !== undefined; },
     511             : 
     512           0 :         "isGatherer": function() { return this.get("ResourceGatherer") !== undefined; },
     513             : 
     514             :         "canGather": function(type) {
     515           0 :                 let gatherRates = this.get("ResourceGatherer/Rates");
     516           0 :                 if (!gatherRates)
     517           0 :                         return false;
     518           0 :                 for (let r in gatherRates)
     519           0 :                         if (r.split('.')[0] === type)
     520           0 :                                 return true;
     521           0 :                 return false;
     522             :         },
     523             : 
     524           0 :         "isGarrisonHolder": function() { return this.get("GarrisonHolder") !== undefined; },
     525             : 
     526           0 :         "isTurretHolder": function() { return this.get("TurretHolder") !== undefined; },
     527             : 
     528             :         /**
     529             :          * returns true if the tempalte can capture the given target entity
     530             :          * if no target is given, returns true if the template has the Capture attack
     531             :          */
     532             :         "canCapture": function(target)
     533             :         {
     534           0 :                 if (!this.get("Attack/Capture"))
     535           0 :                         return false;
     536           0 :                 if (!target)
     537           0 :                         return true;
     538           0 :                 if (!target.get("Capturable"))
     539           0 :                         return false;
     540           0 :                 let restrictedClasses = this.get("Attack/Capture/RestrictedClasses/_string");
     541           0 :                 return !restrictedClasses || !target.hasClasses(restrictedClasses);
     542             :         },
     543             : 
     544           0 :         "isCapturable": function() { return this.get("Capturable") !== undefined; },
     545             : 
     546           0 :         "canGuard": function() { return this.get("UnitAI/CanGuard") === "true"; },
     547             : 
     548           0 :         "canGarrison": function() { return "Garrisonable" in this._template; },
     549             : 
     550           0 :         "canOccupyTurret": function() { return "Turretable" in this._template; },
     551             : 
     552           0 :         "isTreasureCollector": function() { return this.get("TreasureCollector") !== undefined; },
     553             : });
     554             : 
     555             : 
     556             : // defines an entity, with a super Template.
     557             : // also redefines several of the template functions where the only change is applying aura and tech modifications.
     558           0 : m.Entity = m.Class({
     559             :         "_super": m.Template,
     560             : 
     561             :         "_init": function(sharedAI, entity)
     562             :         {
     563           0 :                 this._super.call(this, sharedAI, entity.template, sharedAI.GetTemplate(entity.template));
     564             : 
     565           0 :                 this._entity = entity;
     566           0 :                 this._ai = sharedAI;
     567             :                 // save a reference to the template tech modifications
     568           0 :                 if (!sharedAI._templatesModifications[this._templateName])
     569           0 :                         sharedAI._templatesModifications[this._templateName] = {};
     570           0 :                 this._templateModif = sharedAI._templatesModifications[this._templateName];
     571             :                 // save a reference to the entity tech/aura modifications
     572           0 :                 if (!sharedAI._entitiesModifications.has(entity.id))
     573           0 :                         sharedAI._entitiesModifications.set(entity.id, new Map());
     574           0 :                 this._entityModif = sharedAI._entitiesModifications.get(entity.id);
     575             :         },
     576             : 
     577           0 :         "queryInterface": function(iid) { return SimEngine.QueryInterface(this.id(), iid) },
     578             : 
     579           0 :         "toString": function() { return "[Entity " + this.id() + " " + this.templateName() + "]"; },
     580             : 
     581           0 :         "id": function() { return this._entity.id; },
     582             : 
     583             :         /**
     584             :          * Returns extra data that the AI scripts have associated with this entity,
     585             :          * for arbitrary local annotations.
     586             :          * (This data should not be shared with any other AI scripts.)
     587             :          */
     588           0 :         "getMetadata": function(player, key) { return this._ai.getMetadata(player, this, key); },
     589             : 
     590             :         /**
     591             :          * Sets extra data to be associated with this entity.
     592             :          */
     593           0 :         "setMetadata": function(player, key, value) { this._ai.setMetadata(player, this, key, value); },
     594             : 
     595           0 :         "deleteAllMetadata": function(player) { delete this._ai._entityMetadata[player][this.id()]; },
     596             : 
     597           0 :         "deleteMetadata": function(player, key) { this._ai.deleteMetadata(player, this, key); },
     598             : 
     599           0 :         "position": function() { return this._entity.position; },
     600           0 :         "angle": function() { return this._entity.angle; },
     601             : 
     602           0 :         "isIdle": function() { return this._entity.idle; },
     603             : 
     604           0 :         "getStance": function() { return this._entity.stance; },
     605           0 :         "unitAIState": function() { return this._entity.unitAIState; },
     606           0 :         "unitAIOrderData": function() { return this._entity.unitAIOrderData; },
     607             : 
     608           0 :         "hitpoints": function() { return this._entity.hitpoints; },
     609           0 :         "isHurt": function() { return this.hitpoints() < this.maxHitpoints(); },
     610           0 :         "healthLevel": function() { return this.hitpoints() / this.maxHitpoints(); },
     611           0 :         "needsHeal": function() { return this.isHurt() && this.isHealable(); },
     612           0 :         "needsRepair": function() { return this.isHurt() && this.isRepairable(); },
     613           0 :         "decaying": function() { return this._entity.decaying; },
     614           0 :         "capturePoints": function() {return this._entity.capturePoints; },
     615           0 :         "isInvulnerable": function() { return this._entity.invulnerability || false; },
     616             : 
     617           0 :         "isSharedDropsite": function() { return this._entity.sharedDropsite === true; },
     618             : 
     619             :         /**
     620             :          * Returns the current training queue state, of the form
     621             :          * [ { "id": 0, "template": "...", "count": 1, "progress": 0.5, "metadata": ... }, ... ]
     622             :          */
     623             :         "trainingQueue": function() {
     624           0 :                 return this._entity.trainingQueue;
     625             :         },
     626             : 
     627             :         "trainingQueueTime": function() {
     628           0 :                 let queue = this._entity.trainingQueue;
     629           0 :                 if (!queue)
     630           0 :                         return undefined;
     631           0 :                 let time = 0;
     632           0 :                 for (let item of queue)
     633           0 :                         time += item.timeRemaining;
     634           0 :                 return time / 1000;
     635             :         },
     636             : 
     637             :         "foundationProgress": function() {
     638           0 :                 return this._entity.foundationProgress;
     639             :         },
     640             : 
     641             :         "getBuilders": function() {
     642           0 :                 if (this._entity.foundationProgress === undefined)
     643           0 :                         return undefined;
     644           0 :                 if (this._entity.foundationBuilders === undefined)
     645           0 :                         return [];
     646           0 :                 return this._entity.foundationBuilders;
     647             :         },
     648             : 
     649             :         "getBuildersNb": function() {
     650           0 :                 if (this._entity.foundationProgress === undefined)
     651           0 :                         return undefined;
     652           0 :                 if (this._entity.foundationBuilders === undefined)
     653           0 :                         return 0;
     654           0 :                 return this._entity.foundationBuilders.length;
     655             :         },
     656             : 
     657             :         "owner": function() {
     658           0 :                 return this._entity.owner;
     659             :         },
     660             : 
     661             :         "isOwn": function(player) {
     662           0 :                 if (typeof this._entity.owner === "undefined")
     663           0 :                         return false;
     664           0 :                 return this._entity.owner === player;
     665             :         },
     666             : 
     667             :         "resourceSupplyAmount": function() {
     668           0 :                 return this.queryInterface(Sim.IID_ResourceSupply)?.GetCurrentAmount();
     669             :         },
     670             : 
     671             :         "resourceSupplyNumGatherers": function()
     672             :         {
     673           0 :                 return this.queryInterface(Sim.IID_ResourceSupply)?.GetNumGatherers();
     674             :         },
     675             : 
     676             :         "isFull": function()
     677             :         {
     678           0 :                 let numGatherers = this.resourceSupplyNumGatherers();
     679           0 :                 if (numGatherers)
     680           0 :                         return this.maxGatherers() === numGatherers;
     681             : 
     682           0 :                 return undefined;
     683             :         },
     684             : 
     685             :         "resourceCarrying": function() {
     686           0 :                 return this.queryInterface(Sim.IID_ResourceGatherer)?.GetCarryingStatus();
     687             :         },
     688             : 
     689             :         "currentGatherRate": function() {
     690             :                 // returns the gather rate for the current target if applicable.
     691           0 :                 if (!this.get("ResourceGatherer"))
     692           0 :                         return undefined;
     693             : 
     694           0 :                 if (this.unitAIOrderData().length &&
     695             :                         this.unitAIState().split(".")[1] == "GATHER")
     696             :                 {
     697             :                         let res;
     698             :                         // this is an abuse of "_ai" but it works.
     699           0 :                         if (this.unitAIState().split(".")[1] == "GATHER" && this.unitAIOrderData()[0].target !== undefined)
     700           0 :                                 res = this._ai._entities.get(this.unitAIOrderData()[0].target);
     701           0 :                         else if (this.unitAIOrderData()[1] !== undefined && this.unitAIOrderData()[1].target !== undefined)
     702           0 :                                 res = this._ai._entities.get(this.unitAIOrderData()[1].target);
     703           0 :                         if (!res)
     704           0 :                                 return 0;
     705           0 :                         let type = res.resourceSupplyType();
     706           0 :                         if (!type)
     707           0 :                                 return 0;
     708             : 
     709           0 :                         let tstring = type.generic + "." + type.specific;
     710           0 :                         let rate = +this.get("ResourceGatherer/BaseSpeed");
     711           0 :                         rate *= +this.get("ResourceGatherer/Rates/" +tstring);
     712           0 :                         if (rate)
     713           0 :                                 return rate;
     714           0 :                         return 0;
     715             :                 }
     716           0 :                 return undefined;
     717             :         },
     718             : 
     719             :         "garrisonHolderID": function() {
     720           0 :                 return this._entity.garrisonHolderID;
     721             :         },
     722             : 
     723           0 :         "garrisoned": function() { return this._entity.garrisoned; },
     724             : 
     725             :         "garrisonedSlots": function() {
     726           0 :                 let count = 0;
     727             : 
     728           0 :                 if (this._entity.garrisoned)
     729           0 :                         for (let ent of this._entity.garrisoned)
     730           0 :                                 count += +this._ai._entities.get(ent).garrisonSize();
     731             : 
     732           0 :                 return count;
     733             :         },
     734             : 
     735             :         "canGarrisonInside": function()
     736             :         {
     737           0 :                 return this.garrisonedSlots() < this.garrisonMax();
     738             :         },
     739             : 
     740             :         /**
     741             :          * returns true if the entity can attack (including capture) the given class.
     742             :          */
     743             :         "canAttackClass": function(aClass)
     744             :         {
     745           0 :                 let attack = this.get("Attack");
     746           0 :                 if (!attack)
     747           0 :                         return false;
     748             : 
     749           0 :                 for (let type in attack)
     750             :                 {
     751           0 :                         if (type == "Slaughter")
     752           0 :                                 continue;
     753           0 :                         let restrictedClasses = this.get("Attack/" + type + "/RestrictedClasses/_string");
     754           0 :                         if (!restrictedClasses || !MatchesClassList([aClass], restrictedClasses))
     755           0 :                                 return true;
     756             :                 }
     757           0 :                 return false;
     758             :         },
     759             : 
     760             :         /**
     761             :          * Derived from Attack.js' similary named function.
     762             :          * @return {boolean} - Whether an entity can attack a given target.
     763             :          */
     764             :         "canAttackTarget": function(target, allowCapture)
     765             :         {
     766           0 :                 let attackTypes = this.get("Attack");
     767           0 :                 if (!attackTypes)
     768           0 :                         return false;
     769             : 
     770           0 :                 let canCapture = allowCapture && this.canCapture(target);
     771           0 :                 let health = target.get("Health");
     772           0 :                 if (!health)
     773           0 :                         return canCapture;
     774             : 
     775           0 :                 for (let type in attackTypes)
     776             :                 {
     777           0 :                         if (type == "Capture" ? !canCapture : target.isInvulnerable())
     778           0 :                                 continue;
     779             : 
     780           0 :                         let restrictedClasses = this.get("Attack/" + type + "/RestrictedClasses/_string");
     781           0 :                         if (!restrictedClasses || !target.hasClasses(restrictedClasses))
     782           0 :                                 return true;
     783             :                 }
     784             : 
     785           0 :                 return false;
     786             :         },
     787             : 
     788             :         "move": function(x, z, queued = false, pushFront = false) {
     789           0 :                 Engine.PostCommand(PlayerID, { "type": "walk", "entities": [this.id()], "x": x, "z": z, "queued": queued, "pushFront": pushFront });
     790           0 :                 return this;
     791             :         },
     792             : 
     793             :         "moveToRange": function(x, z, min, max, queued = false, pushFront = false) {
     794           0 :                 Engine.PostCommand(PlayerID, { "type": "walk-to-range", "entities": [this.id()], "x": x, "z": z, "min": min, "max": max, "queued": queued, "pushFront": pushFront });
     795           0 :                 return this;
     796             :         },
     797             : 
     798             :         "attackMove": function(x, z, targetClasses, allowCapture = true, queued = false, pushFront = false) {
     799           0 :                 Engine.PostCommand(PlayerID, { "type": "attack-walk", "entities": [this.id()], "x": x, "z": z, "targetClasses": targetClasses, "allowCapture": allowCapture, "queued": queued, "pushFront": pushFront });
     800           0 :                 return this;
     801             :         },
     802             : 
     803             :         // violent, aggressive, defensive, passive, standground
     804             :         "setStance": function(stance) {
     805           0 :                 if (this.getStance() === undefined)
     806           0 :                         return undefined;
     807           0 :                 Engine.PostCommand(PlayerID, { "type": "stance", "entities": [this.id()], "name": stance});
     808           0 :                 return this;
     809             :         },
     810             : 
     811             :         "stopMoving": function() {
     812           0 :                 Engine.PostCommand(PlayerID, { "type": "stop", "entities": [this.id()], "queued": false, "pushFront": false });
     813             :         },
     814             : 
     815             :         "unload": function(id) {
     816           0 :                 if (!this.get("GarrisonHolder"))
     817           0 :                         return undefined;
     818           0 :                 Engine.PostCommand(PlayerID, { "type": "unload", "garrisonHolder": this.id(), "entities": [id] });
     819           0 :                 return this;
     820             :         },
     821             : 
     822             :         // Unloads all owned units, don't unload allies
     823             :         "unloadAll": function() {
     824           0 :                 if (!this.get("GarrisonHolder"))
     825           0 :                         return undefined;
     826           0 :                 Engine.PostCommand(PlayerID, { "type": "unload-all-by-owner", "garrisonHolders": [this.id()] });
     827           0 :                 return this;
     828             :         },
     829             : 
     830             :         "garrison": function(target, queued = false, pushFront = false) {
     831           0 :                 Engine.PostCommand(PlayerID, { "type": "garrison", "entities": [this.id()], "target": target.id(), "queued": queued, "pushFront": pushFront });
     832           0 :                 return this;
     833             :         },
     834             : 
     835             :         "occupy-turret": function(target, queued = false, pushFront = false) {
     836           0 :                 Engine.PostCommand(PlayerID, { "type": "occupy-turret", "entities": [this.id()], "target": target.id(), "queued": queued, "pushFront": pushFront });
     837           0 :                 return this;
     838             :         },
     839             : 
     840             :         "attack": function(unitId, allowCapture = true, queued = false, pushFront = false) {
     841           0 :                 Engine.PostCommand(PlayerID, { "type": "attack", "entities": [this.id()], "target": unitId, "allowCapture": allowCapture, "queued": queued, "pushFront": pushFront });
     842           0 :                 return this;
     843             :         },
     844             : 
     845             :         "collectTreasure": function(target, queued = false, pushFront = false) {
     846           0 :                 Engine.PostCommand(PlayerID, {
     847             :                         "type": "collect-treasure",
     848             :                         "entities": [this.id()],
     849             :                         "target": target.id(),
     850             :                         "queued": queued,
     851             :                         "pushFront": pushFront
     852             :                 });
     853           0 :                 return this;
     854             :         },
     855             : 
     856             :         // moveApart from a point in the opposite direction with a distance dist
     857             :         "moveApart": function(point, dist) {
     858           0 :                 if (this.position() !== undefined)
     859             :                 {
     860           0 :                         let direction = [this.position()[0] - point[0], this.position()[1] - point[1]];
     861           0 :                         let norm = m.VectorDistance(point, this.position());
     862           0 :                         if (norm === 0)
     863           0 :                                 direction = [1, 0];
     864             :                         else
     865             :                         {
     866           0 :                                 direction[0] /= norm;
     867           0 :                                 direction[1] /= norm;
     868             :                         }
     869           0 :                         Engine.PostCommand(PlayerID, { "type": "walk", "entities": [this.id()], "x": this.position()[0] + direction[0]*dist, "z": this.position()[1] + direction[1]*dist, "queued": false, "pushFront": false });
     870             :                 }
     871           0 :                 return this;
     872             :         },
     873             : 
     874             :         // Flees from a unit in the opposite direction.
     875             :         "flee": function(unitToFleeFrom) {
     876           0 :                 if (this.position() !== undefined && unitToFleeFrom.position() !== undefined)
     877             :                 {
     878           0 :                         let FleeDirection = [this.position()[0] - unitToFleeFrom.position()[0],
     879             :                                              this.position()[1] - unitToFleeFrom.position()[1]];
     880           0 :                         let dist = m.VectorDistance(unitToFleeFrom.position(), this.position());
     881           0 :                         FleeDirection[0] = 40 * FleeDirection[0] / dist;
     882           0 :                         FleeDirection[1] = 40 * FleeDirection[1] / dist;
     883             : 
     884           0 :                         Engine.PostCommand(PlayerID, { "type": "walk", "entities": [this.id()], "x": this.position()[0] + FleeDirection[0], "z": this.position()[1] + FleeDirection[1], "queued": false, "pushFront": false });
     885             :                 }
     886           0 :                 return this;
     887             :         },
     888             : 
     889             :         "gather": function(target, queued = false, pushFront = false) {
     890           0 :                 Engine.PostCommand(PlayerID, { "type": "gather", "entities": [this.id()], "target": target.id(), "queued": queued, "pushFront": pushFront });
     891           0 :                 return this;
     892             :         },
     893             : 
     894             :         "repair": function(target, autocontinue = false, queued = false, pushFront = false) {
     895           0 :                 Engine.PostCommand(PlayerID, { "type": "repair", "entities": [this.id()], "target": target.id(), "autocontinue": autocontinue, "queued": queued, "pushFront": pushFront });
     896           0 :                 return this;
     897             :         },
     898             : 
     899             :         "returnResources": function(target, queued = false, pushFront = false) {
     900           0 :                 Engine.PostCommand(PlayerID, { "type": "returnresource", "entities": [this.id()], "target": target.id(), "queued": queued, "pushFront": pushFront });
     901           0 :                 return this;
     902             :         },
     903             : 
     904             :         "destroy": function() {
     905           0 :                 Engine.PostCommand(PlayerID, { "type": "delete-entities", "entities": [this.id()] });
     906           0 :                 return this;
     907             :         },
     908             : 
     909             :         "barter": function(buyType, sellType, amount) {
     910           0 :                 Engine.PostCommand(PlayerID, { "type": "barter", "sell": sellType, "buy": buyType, "amount": amount });
     911           0 :                 return this;
     912             :         },
     913             : 
     914             :         "tradeRoute": function(target, source) {
     915           0 :                 Engine.PostCommand(PlayerID, { "type": "setup-trade-route", "entities": [this.id()], "target": target.id(), "source": source.id(), "route": undefined, "queued": false, "pushFront": false });
     916           0 :                 return this;
     917             :         },
     918             : 
     919             :         "setRallyPoint": function(target, command) {
     920           0 :                 let data = { "command": command, "target": target.id() };
     921           0 :                 Engine.PostCommand(PlayerID, { "type": "set-rallypoint", "entities": [this.id()], "x": target.position()[0], "z": target.position()[1], "data": data });
     922           0 :                 return this;
     923             :         },
     924             : 
     925             :         "unsetRallyPoint": function() {
     926           0 :                 Engine.PostCommand(PlayerID, { "type": "unset-rallypoint", "entities": [this.id()] });
     927           0 :                 return this;
     928             :         },
     929             : 
     930             :         "train": function(civ, type, count, metadata, pushFront = false)
     931             :         {
     932           0 :                 let trainable = this.trainableEntities(civ);
     933           0 :                 if (!trainable)
     934             :                 {
     935           0 :                         error("Called train("+type+", "+count+") on non-training entity "+this);
     936           0 :                         return this;
     937             :                 }
     938           0 :                 if (trainable.indexOf(type) == -1)
     939             :                 {
     940           0 :                         error("Called train("+type+", "+count+") on entity "+this+" which can't train that");
     941           0 :                         return this;
     942             :                 }
     943             : 
     944           0 :                 Engine.PostCommand(PlayerID, {
     945             :                         "type": "train",
     946             :                         "entities": [this.id()],
     947             :                         "template": type,
     948             :                         "count": count,
     949             :                         "metadata": metadata,
     950             :                         "pushFront": pushFront
     951             :                 });
     952           0 :                 return this;
     953             :         },
     954             : 
     955             :         "construct": function(template, x, z, angle, metadata) {
     956             :                 // TODO: verify this unit can construct this, just for internal
     957             :                 // sanity-checking and error reporting
     958             : 
     959           0 :                 Engine.PostCommand(PlayerID, {
     960             :                         "type": "construct",
     961             :                         "entities": [this.id()],
     962             :                         "template": template,
     963             :                         "x": x,
     964             :                         "z": z,
     965             :                         "angle": angle,
     966             :                         "autorepair": false,
     967             :                         "autocontinue": false,
     968             :                         "queued": false,
     969             :                         "pushFront": false,
     970             :                         "metadata": metadata  // can be undefined
     971             :                 });
     972           0 :                 return this;
     973             :         },
     974             : 
     975             :         "research": function(template, pushFront = false) {
     976           0 :                 Engine.PostCommand(PlayerID, {
     977             :                         "type": "research",
     978             :                         "entity": this.id(),
     979             :                         "template": template,
     980             :                         "pushFront": pushFront
     981             :                 });
     982           0 :                 return this;
     983             :         },
     984             : 
     985             :         "stopProduction": function(id) {
     986           0 :                 Engine.PostCommand(PlayerID, { "type": "stop-production", "entity": this.id(), "id": id });
     987           0 :                 return this;
     988             :         },
     989             : 
     990             :         "stopAllProduction": function(percentToStopAt) {
     991           0 :                 let queue = this._entity.trainingQueue;
     992           0 :                 if (!queue)
     993           0 :                         return true;    // no queue, so technically we stopped all production.
     994           0 :                 for (let item of queue)
     995           0 :                         if (item.progress < percentToStopAt)
     996           0 :                                 Engine.PostCommand(PlayerID, { "type": "stop-production", "entity": this.id(), "id": item.id });
     997           0 :                 return this;
     998             :         },
     999             : 
    1000             :         "guard": function(target, queued = false, pushFront = false) {
    1001           0 :                 Engine.PostCommand(PlayerID, { "type": "guard", "entities": [this.id()], "target": target.id(), "queued": queued, "pushFront": pushFront });
    1002           0 :                 return this;
    1003             :         },
    1004             : 
    1005             :         "removeGuard": function() {
    1006           0 :                 Engine.PostCommand(PlayerID, { "type": "remove-guard", "entities": [this.id()] });
    1007           0 :                 return this;
    1008             :         }
    1009             : });
    1010             : 
    1011           0 : return m;
    1012             : 
    1013             : }(API3);

Generated by: LCOV version 1.14