LCOV - code coverage report
Current view: top level - simulation/components - TurretHolder.js (source / functions) Hit Total Coverage
Test: lcov.info Lines: 61 135 45.2 %
Date: 2023-04-02 12:52:40 Functions: 17 28 60.7 %

          Line data    Source code
       1             : /**
       2             :  * This class holds the functions regarding entities being visible on
       3             :  * another entity, but tied to their parents location.
       4             :  */
       5             : class TurretHolder
       6             : {
       7             :         Init()
       8             :         {
       9           2 :                 this.turretPoints = [];
      10             : 
      11           2 :                 let points = this.template.TurretPoints;
      12           2 :                 for (let point in points)
      13           5 :                         this.turretPoints.push({
      14             :                                 "name": point,
      15             :                                 "offset": {
      16             :                                         "x": +points[point].X,
      17             :                                         "y": +points[point].Y,
      18             :                                         "z": +points[point].Z
      19             :                                 },
      20             :                                 "allowedClasses": points[point].AllowedClasses?._string,
      21             :                                 "angle": points[point].Angle ? +points[point].Angle * Math.PI / 180 : null,
      22             :                                 "entity": null,
      23             :                                 "template": points[point].Template,
      24             :                                 "ejectable": "Ejectable" in points[point] ? points[point].Ejectable == "true" : true
      25             :                         });
      26             :         }
      27             : 
      28             :         /**
      29             :          * Add a subunit as specified in the template.
      30             :          * This function creates an entity and places it on the turret point.
      31             :          *
      32             :          * @param {Object} turretPoint - A turret point to (re)create the predefined subunit for.
      33             :          *
      34             :          * @return {boolean} - Whether the turret creation has succeeded.
      35             :          */
      36             :         CreateSubunit(turretPointName)
      37             :         {
      38           0 :                 let turretPoint = this.TurretPointByName(turretPointName);
      39           0 :                 if (!turretPoint || turretPoint.entity ||
      40             :                         this.initTurrets?.has(turretPointName) ||
      41             :                         this.reservedTurrets?.has(turretPointName))
      42           0 :                         return false;
      43             : 
      44           0 :                 let ent = Engine.AddEntity(turretPoint.template);
      45             : 
      46           0 :                 let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
      47           0 :                 if (cmpOwnership)
      48             :                 {
      49           0 :                         let cmpEntOwnership = Engine.QueryInterface(ent, IID_Ownership);
      50           0 :                         cmpEntOwnership?.SetOwner(cmpOwnership.GetOwner());
      51             :                 }
      52             : 
      53           0 :                 let cmpTurretable = Engine.QueryInterface(ent, IID_Turretable);
      54           0 :                 return cmpTurretable?.OccupyTurret(this.entity, turretPoint.name, turretPoint.ejectable) || Engine.DestroyEntity(ent);
      55             :         }
      56             : 
      57             :         /**
      58             :          * @param {string} name - The name of a turret point to reserve, e.g. for promotion.
      59             :          */
      60             :         SetReservedTurretPoint(name)
      61             :         {
      62           0 :                 if (!this.reservedTurrets)
      63           0 :                         this.reservedTurrets = new Set();
      64           0 :                 this.reservedTurrets.add(name);
      65             :         }
      66             : 
      67             :         /**
      68             :          * @return {Object[]} - An array of the turret points this entity has.
      69             :          */
      70             :         GetTurretPoints()
      71             :         {
      72           0 :                 return this.turretPoints;
      73             :         }
      74             : 
      75             :         /**
      76             :          * @param {number} entity - The entity to check for.
      77             :          * @param {Object} turretPoint - The turret point to use.
      78             :          *
      79             :          * @return {boolean} - Whether the entity is allowed to occupy the specified turret point.
      80             :          */
      81             :         AllowedToOccupyTurretPoint(entity, turretPoint)
      82             :         {
      83          30 :                 if (!turretPoint || turretPoint.entity)
      84           3 :                         return false;
      85             : 
      86          27 :                 if (!IsOwnedByMutualAllyOfEntity(entity, this.entity))
      87           0 :                         return false;
      88             : 
      89          27 :                 if (!turretPoint.allowedClasses)
      90          17 :                         return true;
      91             : 
      92          10 :                 let cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
      93          10 :                 return cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), turretPoint.allowedClasses);
      94             :         }
      95             : 
      96             :         /**
      97             :          * @param {number} entity - The entity to check for.
      98             :          * @return {boolean} - Whether the entity is allowed to occupy any turret point.
      99             :          */
     100             :         CanOccupy(entity)
     101             :         {
     102           8 :                 return !!this.turretPoints.find(turretPoint => this.AllowedToOccupyTurretPoint(entity, turretPoint));
     103             :         }
     104             : 
     105             :         /**
     106             :          * Occupy a turret point with the given entity.
     107             :          * @param {number} entity - The entity to use.
     108             :          * @param {Object} requestedTurretPoint - Optionally the specific turret point to occupy.
     109             :          *
     110             :          * @return {boolean} - Whether the occupation was successful.
     111             :          */
     112             :         OccupyTurretPoint(entity, requestedTurretPoint)
     113             :         {
     114          10 :                 let cmpPositionOccupant = Engine.QueryInterface(entity, IID_Position);
     115          10 :                 if (!cmpPositionOccupant)
     116           0 :                         return false;
     117             : 
     118          10 :                 let cmpPositionSelf = Engine.QueryInterface(this.entity, IID_Position);
     119          10 :                 if (!cmpPositionSelf)
     120           0 :                         return false;
     121             : 
     122          10 :                 if (this.OccupiesTurretPoint(entity))
     123           0 :                         return false;
     124             : 
     125             :                 let turretPoint;
     126          10 :                 if (requestedTurretPoint)
     127             :                 {
     128           6 :                         if (this.AllowedToOccupyTurretPoint(entity, requestedTurretPoint))
     129           4 :                                 turretPoint = requestedTurretPoint;
     130             :                 }
     131             :                 else
     132           5 :                         turretPoint = this.turretPoints.find(turret => !turret.entity && this.AllowedToOccupyTurretPoint(entity, turret));
     133             : 
     134          10 :                 if (!turretPoint)
     135           2 :                         return false;
     136             : 
     137           8 :                 turretPoint.entity = entity;
     138             : 
     139             :                 // Angle of turrets:
     140             :                 // Renamed entities (turretPoint != undefined) should keep their angle.
     141             :                 // Otherwise if an angle is given in the turretPoint, use it.
     142             :                 // If no such angle given (usually walls for which outside/inside not well defined), we keep
     143             :                 // the current angle as it was used for garrisoning and thus quite often was from inside to
     144             :                 // outside, except when garrisoning from outWorld where we take as default PI.
     145           8 :                 if (!turretPoint && turretPoint.angle != null)
     146           0 :                         cmpPositionOccupant.SetYRotation(cmpPositionSelf.GetRotation().y + turretPoint.angle);
     147           8 :                 else if (!turretPoint && !cmpPosition.IsInWorld())
     148           0 :                         cmpPositionOccupant.SetYRotation(cmpPositionSelf.GetRotation().y + Math.PI);
     149             : 
     150           8 :                 cmpPositionOccupant.SetTurretParent(this.entity, turretPoint.offset);
     151             : 
     152           8 :                 Engine.PostMessage(this.entity, MT_TurretsChanged, {
     153             :                         "added": [entity],
     154             :                         "removed": []
     155             :                 });
     156             : 
     157           8 :                 return true;
     158             :         }
     159             : 
     160             :         /**
     161             :          * @param {number} entity - The entityID of the entity.
     162             :          * @param {String} turretName - The name of the turret point to occupy.
     163             :          * @return {boolean} - Whether the occupation has succeeded.
     164             :          */
     165             :         OccupyNamedTurretPoint(entity, turretName)
     166             :         {
     167           6 :                 return this.OccupyTurretPoint(entity, this.TurretPointByName(turretName));
     168             :         }
     169             : 
     170             :         /**
     171             :          * @param {string} turretPointName - The name of the requested turret point.
     172             :          * @return {Object} - The requested turret point.
     173             :          */
     174             :         TurretPointByName(turretPointName)
     175             :         {
     176          14 :                 return this.turretPoints.find(turret => turret.name == turretPointName);
     177             :         }
     178             : 
     179             :         /**
     180             :          * Remove the entity from a turret.
     181             :          * @param {number} entity - The specific entity to eject.
     182             :          * @param {boolean} forced - Whether ejection is forced (e.g. due to death or renaming).
     183             :          * @param {Object} turret - Optionally the turret to abandon.
     184             :          *
     185             :          * @return {boolean} - Whether the entity succesfully left us.
     186             :          */
     187             :         LeaveTurretPoint(entity, forced, requestedTurretPoint)
     188             :         {
     189             :                 let turretPoint;
     190           8 :                 if (requestedTurretPoint)
     191             :                 {
     192           2 :                         if (requestedTurretPoint.entity == entity)
     193           1 :                                 turretPoint = requestedTurretPoint;
     194             :                 }
     195             :                 else
     196           6 :                         turretPoint = this.GetOccupiedTurretPoint(entity);
     197             : 
     198           8 :                 if (!turretPoint || (!turretPoint.ejectable && !forced))
     199           2 :                         return false;
     200             : 
     201           6 :                 turretPoint.entity = null;
     202             : 
     203           6 :                 Engine.PostMessage(this.entity, MT_TurretsChanged, {
     204             :                         "added": [],
     205             :                         "removed": [entity]
     206             :                 });
     207             : 
     208           6 :                 return true;
     209             :         }
     210             : 
     211             :         /**
     212             :          * @param {number} entity - The entity's id.
     213             :          * @param {Object} turret - Optionally the turret to check.
     214             :          *
     215             :          * @return {boolean} - Whether the entity is positioned on a turret of this entity.
     216             :          */
     217             :         OccupiesTurretPoint(entity, requestedTurretPoint)
     218             :         {
     219          14 :                 return requestedTurretPoint ? requestedTurretPoint.entity == entity :
     220             :                         !!this.GetOccupiedTurretPoint(entity);
     221             :         }
     222             : 
     223             :         /**
     224             :          * @param {number} entity - The entity's id.
     225             :          * @return {Object} - The turret this entity is positioned on, if applicable.
     226             :          */
     227             :         GetOccupiedTurretPoint(entity)
     228             :         {
     229          45 :                 return this.turretPoints.find(turretPoint => turretPoint.entity == entity);
     230             :         }
     231             : 
     232             :         /**
     233             :          * @param {number} entity - The entity's id.
     234             :          * @return {Object} - The turret this entity is positioned on, if applicable.
     235             :          */
     236             :         GetOccupiedTurretPointName(entity)
     237             :         {
     238           3 :                 let turret = this.GetOccupiedTurretPoint(entity);
     239           3 :                 return turret ? turret.name : "";
     240             :         }
     241             : 
     242             :         /**
     243             :          * @return {number[]} - The turretted entityIDs.
     244             :          */
     245             :         GetEntities()
     246             :         {
     247           3 :                 let entities = [];
     248           3 :                 for (let turretPoint of this.turretPoints)
     249           6 :                         if (turretPoint.entity)
     250           3 :                                 entities.push(turretPoint.entity);
     251           3 :                 return entities;
     252             :         }
     253             : 
     254             :         /**
     255             :          * @return {boolean} - Whether all the turret points are occupied.
     256             :          */
     257             :         IsFull()
     258             :         {
     259           0 :                 return !!this.turretPoints.find(turretPoint => turretPoint.entity == null);
     260             :         }
     261             : 
     262             :         /**
     263             :          * @return {Object} - Max and min ranges at which entities can occupy any turret.
     264             :          */
     265             :         LoadingRange()
     266             :         {
     267           0 :                 return { "min": 0, "max": +(this.template.LoadingRange || 2) };
     268             :         }
     269             : 
     270             :         /**
     271             :          * @param {number} ent - The entity ID of the turret to be potentially picked up.
     272             :          * @return {boolean} - Whether this entity can pick the specified entity up.
     273             :          */
     274             :         CanPickup(ent)
     275             :         {
     276           0 :                 if (!this.template.Pickup || this.IsFull())
     277           0 :                         return false;
     278           0 :                 let cmpOwner = Engine.QueryInterface(this.entity, IID_Ownership);
     279           0 :                 return !!cmpOwner && IsOwnedByPlayer(cmpOwner.GetOwner(), ent);
     280             :         }
     281             : 
     282             :         /**
     283             :          * @param {number[]} entities - The entities to ask to leave or to kill.
     284             :          */
     285             :         EjectOrKill(entities)
     286             :         {
     287           0 :                 let removedEntities = [];
     288           0 :                 for (let entity of entities)
     289             :                 {
     290           0 :                         let cmpTurretable = Engine.QueryInterface(entity, IID_Turretable);
     291           0 :                         if (!cmpTurretable || !cmpTurretable.LeaveTurret(true))
     292             :                         {
     293           0 :                                 let cmpHealth = Engine.QueryInterface(entity, IID_Health);
     294           0 :                                 if (cmpHealth)
     295           0 :                                         cmpHealth.Kill();
     296             :                                 else
     297           0 :                                         Engine.DestroyEntity(entity);
     298           0 :                                 removedEntities.push(entity);
     299             :                         }
     300             :                 }
     301           0 :                 if (removedEntities.length)
     302           0 :                         Engine.PostMessage(this.entity, MT_TurretsChanged, {
     303             :                                 "added": [],
     304             :                                 "removed": removedEntities
     305             :                         });
     306             :         }
     307             : 
     308             :         /**
     309             :          * Sets an init turret, present from game start. (E.g. set in Atlas.)
     310             :          * @param {String} turretName - The name of the turret point to be used.
     311             :          * @param {number} entity - The entity-ID to be placed.
     312             :          */
     313             :         SetInitEntity(turretName, entity)
     314             :         {
     315           2 :                 if (!this.initTurrets)
     316           1 :                         this.initTurrets = new Map();
     317             : 
     318           2 :                 if (this.initTurrets.has(turretName))
     319           0 :                         warn("The turret position " + turretName + " of entity " +
     320             :                                 this.entity + " is already set! Overwriting.");
     321             : 
     322           2 :                 this.initTurrets.set(turretName, entity);
     323             :         }
     324             : 
     325             :         /**
     326             :          * Update list of turreted entities when a game inits.
     327             :          */
     328             :         OnGlobalSkirmishReplacerReplaced(msg)
     329             :         {
     330           0 :                 if (!this.initTurrets)
     331           0 :                         return;
     332             : 
     333           0 :                 if (msg.entity == this.entity)
     334             :                 {
     335           0 :                         let cmpTurretHolder = Engine.QueryInterface(msg.newentity, IID_TurretHolder);
     336           0 :                         if (cmpTurretHolder)
     337           0 :                                 cmpTurretHolder.initTurrets = this.initTurrets;
     338             :                 }
     339             :                 else
     340             :                 {
     341           0 :                         let entityIndex = this.initTurrets.indexOf(msg.entity);
     342           0 :                         if (entityIndex != -1)
     343           0 :                                 this.initTurrets[entityIndex] = msg.newentity;
     344             :                 }
     345             :         }
     346             : 
     347             :         /**
     348             :          * Initialise turreted units.
     349             :          */
     350             :         OnGlobalInitGame(msg)
     351             :         {
     352           1 :                 if (!this.initTurrets)
     353           0 :                         return;
     354             : 
     355           1 :                 for (let [turretPointName, entity] of this.initTurrets)
     356             :                 {
     357           2 :                         let cmpTurretable = Engine.QueryInterface(entity, IID_Turretable);
     358           2 :                         if (!cmpTurretable || !cmpTurretable.OccupyTurret(this.entity, turretPointName, this.TurretPointByName(turretPointName).ejectable))
     359           0 :                                 warn("Entity " + entity + " could not occupy the turret point " +
     360             :                                         turretPointName + " of turret holder " + this.entity + ".");
     361             :                 }
     362             : 
     363           1 :                 delete this.initTurrets;
     364             :         }
     365             : 
     366             :         /**
     367             :          * @param {Object} msg - { "entity": number, "newentity": number }.
     368             :          */
     369             :         OnEntityRenamed(msg)
     370             :         {
     371           0 :                 for (let entity of this.GetEntities())
     372             :                 {
     373           0 :                         let cmpTurretable = Engine.QueryInterface(entity, IID_Turretable);
     374           0 :                         if (!cmpTurretable)
     375           0 :                                 continue;
     376           0 :                         let currentPoint = this.GetOccupiedTurretPointName(entity);
     377           0 :                         cmpTurretable.LeaveTurret(true);
     378           0 :                         cmpTurretable.OccupyTurret(msg.newentity, currentPoint);
     379             :                 }
     380             :         }
     381             : 
     382             :         /**
     383             :          * @param {Object} msg - { "entity": number, "from": number, "to": number }.
     384             :          */
     385             :         OnOwnershipChanged(msg)
     386             :         {
     387           0 :                 if (msg.to === INVALID_PLAYER)
     388             :                 {
     389           0 :                         this.EjectOrKill(this.GetEntities());
     390           0 :                         return;
     391             :                 }
     392           0 :                 for (let point of this.turretPoints)
     393             :                 {
     394             :                         // If we were created, create any subunits now.
     395             :                         // This has to be done here (instead of on Init)
     396             :                         // for Ownership ought to be initialised.
     397           0 :                         if (point.template && msg.from === INVALID_PLAYER)
     398             :                         {
     399           0 :                                 this.CreateSubunit(point.name);
     400           0 :                                 continue;
     401             :                         }
     402           0 :                         if (!point.entity)
     403           0 :                                 continue;
     404           0 :                         if (!point.ejectable)
     405             :                         {
     406           0 :                                 let cmpTurretOwnership = Engine.QueryInterface(point.entity, IID_Ownership);
     407           0 :                                 if (cmpTurretOwnership)
     408           0 :                                         cmpTurretOwnership.SetOwner(msg.to);
     409             :                         }
     410           0 :                         else if (!IsOwnedByMutualAllyOfEntity(point.entity, this.entity))
     411             :                         {
     412           0 :                                 let cmpTurretable = Engine.QueryInterface(point.entity, IID_Turretable);
     413           0 :                                 if (cmpTurretable)
     414           0 :                                         cmpTurretable.LeaveTurret();
     415             :                         }
     416             :                 }
     417           0 :                 delete this.reservedTurrets;
     418             :         }
     419             : }
     420             : 
     421           2 : TurretHolder.prototype.Schema =
     422             :         "<element name='TurretPoints' a:help='Points that will be used to visibly garrison a unit.'>" +
     423             :                 "<oneOrMore>" +
     424             :                         "<element a:help='Element containing the offset coordinates.'>" +
     425             :                                 "<anyName/>" +
     426             :                                 "<interleave>" +
     427             :                                         "<element name='X'>" +
     428             :                                                 "<data type='decimal'/>" +
     429             :                                         "</element>" +
     430             :                                         "<element name='Y'>" +
     431             :                                                 "<data type='decimal'/>" +
     432             :                                         "</element>" +
     433             :                                         "<element name='Z'>" +
     434             :                                                 "<data type='decimal'/>" +
     435             :                                         "</element>" +
     436             :                                         "<optional>" +
     437             :                                                 "<interleave>" +
     438             :                                                         "<element name='Template'>" +
     439             :                                                                 "<text/>" +
     440             :                                                         "</element>" +
     441             :                                                         "<element name='Ejectable' a:help='Whether this template is tied to the turret position (i.e. not allowed to leave the turret point).'>" +
     442             :                                                                 "<data type='boolean'/>" +
     443             :                                                         "</element>" +
     444             :                                                 "</interleave>" +
     445             :                                         "</optional>" +
     446             :                                         "<optional>" +
     447             :                                                 "<element name='AllowedClasses' a:help='If specified, only entities matching the given classes will be able to use this turret.'>" +
     448             :                                                         "<attribute name='datatype'>" +
     449             :                                                                 "<value>tokens</value>" +
     450             :                                                         "</attribute>" +
     451             :                                                         "<text/>" +
     452             :                                                 "</element>" +
     453             :                                         "</optional>"+
     454             :                                         "<optional>" +
     455             :                                                 "<element name='Angle' a:help='Angle in degrees relative to the turretHolder direction.'>" +
     456             :                                                         "<data type='decimal'/>" +
     457             :                                                 "</element>" +
     458             :                                         "</optional>" +
     459             :                                 "</interleave>" +
     460             :                         "</element>" +
     461             :                 "</oneOrMore>" +
     462             :         "</element>" +
     463             :         "<optional>" +
     464             :                 "<element name='LoadingRange' a:help='The maximum distance from this holder at which entities are allowed to occupy a turret point. Should be about 2.0 for land entities and preferably greater for ships.'>" +
     465             :                         "<ref name='nonNegativeDecimal'/>" +
     466             :                 "</element>" +
     467             :         "</optional>"
     468           2 :         "<optional>" +
     469             :                 "<element name='Pickup' a:help='This entity will try to move to pick up units to be turreted.'>" +
     470             :                         "<data type='boolean'/>" +
     471             :                 "</element>" +
     472             :         "</optional>";
     473             : 
     474           2 : Engine.RegisterComponentType(IID_TurretHolder, "TurretHolder", TurretHolder);

Generated by: LCOV version 1.14