LCOV - code coverage report
Current view: top level - simulation/components - GarrisonHolder.js (source / functions) Hit Total Coverage
Test: lcov.info Lines: 164 213 77.0 %
Date: 2023-04-02 12:52:40 Functions: 37 45 82.2 %

          Line data    Source code
       1             : function GarrisonHolder() {}
       2             : 
       3           2 : GarrisonHolder.prototype.Schema =
       4             :         "<element name='Max' a:help='Maximum number of entities which can be garrisoned in this holder'>" +
       5             :                 "<data type='positiveInteger'/>" +
       6             :         "</element>" +
       7             :         "<element name='List' a:help='Classes of entities which are allowed to garrison in this holder (from Identity)'>" +
       8             :                 "<attribute name='datatype'>" +
       9             :                         "<value>tokens</value>" +
      10             :                 "</attribute>" +
      11             :                 "<text/>" +
      12             :         "</element>" +
      13             :         "<element name='EjectClassesOnDestroy' a:help='Classes of entities to be ejected on destroy. Others are killed'>" +
      14             :                 "<attribute name='datatype'>" +
      15             :                         "<value>tokens</value>" +
      16             :                 "</attribute>" +
      17             :                 "<text/>" +
      18             :         "</element>" +
      19             :         "<element name='BuffHeal' a:help='Number of hitpoints that will be restored to this holder&apos;s garrisoned units each second'>" +
      20             :                 "<ref name='nonNegativeDecimal'/>" +
      21             :         "</element>" +
      22             :         "<element name='LoadingRange' a:help='The maximum distance from this holder at which entities are allowed to garrison. Should be about 2.0 for land entities and preferably greater for ships'>" +
      23             :                 "<ref name='nonNegativeDecimal'/>" +
      24             :         "</element>" +
      25             :         "<optional>" +
      26             :                 "<element name='EjectHealth' a:help='Percentage of maximum health below which this holder no longer allows garrisoning'>" +
      27             :                         "<ref name='nonNegativeDecimal'/>" +
      28             :                 "</element>" +
      29             :         "</optional>" +
      30             :         "<optional>" +
      31             :                 "<element name='Pickup' a:help='This garrisonHolder will move to pick up units to be garrisoned'>" +
      32             :                         "<data type='boolean'/>" +
      33             :                 "</element>" +
      34             :         "</optional>";
      35             : 
      36             : /**
      37             :  * Time between heals.
      38             :  */
      39           2 : GarrisonHolder.prototype.HEAL_TIMEOUT = 1000;
      40             : 
      41             : /**
      42             :  * Initialize GarrisonHolder Component
      43             :  * Garrisoning when loading a map is set in the script of the map, by setting initGarrison
      44             :  * which should contain the array of garrisoned entities.
      45             :  */
      46           2 : GarrisonHolder.prototype.Init = function()
      47             : {
      48           4 :         this.entities = [];
      49           4 :         this.allowedClasses = ApplyValueModificationsToEntity("GarrisonHolder/List/_string", this.template.List._string, this.entity);
      50             : };
      51             : 
      52             : /**
      53             :  * @param {number} entity - The entity to verify.
      54             :  * @return {boolean} - Whether the given entity is garrisoned in this GarrisonHolder.
      55             :  */
      56           2 : GarrisonHolder.prototype.IsGarrisoned = function(entity)
      57             : {
      58           0 :         return this.entities.indexOf(entity) != -1;
      59             : };
      60             : 
      61             : /**
      62             :  * @return {Object} max and min range at which entities can garrison the holder.
      63             :  */
      64           2 : GarrisonHolder.prototype.LoadingRange = function()
      65             : {
      66           1 :         return { "max": +this.template.LoadingRange, "min": 0 };
      67             : };
      68             : 
      69           2 : GarrisonHolder.prototype.CanPickup = function(ent)
      70             : {
      71           4 :         if (!this.template.Pickup || this.IsFull())
      72           4 :                 return false;
      73           0 :         let cmpOwner = Engine.QueryInterface(this.entity, IID_Ownership);
      74           0 :         return !!cmpOwner && IsOwnedByPlayer(cmpOwner.GetOwner(), ent);
      75             : };
      76             : 
      77           2 : GarrisonHolder.prototype.GetEntities = function()
      78             : {
      79          16 :         return this.entities;
      80             : };
      81             : 
      82             : /**
      83             :  * @return {Array} unit classes which can be garrisoned inside this
      84             :  * particular entity. Obtained from the entity's template.
      85             :  */
      86           2 : GarrisonHolder.prototype.GetAllowedClasses = function()
      87             : {
      88           2 :         return this.allowedClasses;
      89             : };
      90             : 
      91           2 : GarrisonHolder.prototype.GetCapacity = function()
      92             : {
      93          57 :         return ApplyValueModificationsToEntity("GarrisonHolder/Max", +this.template.Max, this.entity);
      94             : };
      95             : 
      96           2 : GarrisonHolder.prototype.IsFull = function()
      97             : {
      98           7 :         return this.OccupiedSlots() >= this.GetCapacity();
      99             : };
     100             : 
     101           2 : GarrisonHolder.prototype.GetHealRate = function()
     102             : {
     103           5 :         return ApplyValueModificationsToEntity("GarrisonHolder/BuffHeal", +this.template.BuffHeal, this.entity);
     104             : };
     105             : 
     106             : /**
     107             :  * Set this entity to allow or disallow garrisoning in the entity.
     108             :  * Every component calling this function should do it with its own ID, and as long as one
     109             :  * component doesn't allow this entity to garrison, it can't be garrisoned
     110             :  * When this entity already contains garrisoned soldiers,
     111             :  * these will not be able to ungarrison until the flag is set to true again.
     112             :  *
     113             :  * This more useful for modern-day features. For example you can't garrison or ungarrison
     114             :  * a driving vehicle or plane.
     115             :  * @param {boolean} allow - Whether the entity should be garrisonable.
     116             :  */
     117           2 : GarrisonHolder.prototype.AllowGarrisoning = function(allow, callerID)
     118             : {
     119           3 :         if (!this.allowGarrisoning)
     120           1 :                 this.allowGarrisoning = new Map();
     121           3 :         this.allowGarrisoning.set(callerID, allow);
     122             : };
     123             : 
     124             : /**
     125             :  * @return {boolean} - Whether (un)garrisoning is allowed.
     126             :  */
     127           2 : GarrisonHolder.prototype.IsGarrisoningAllowed = function()
     128             : {
     129          87 :         return !this.allowGarrisoning ||
     130          16 :                 Array.from(this.allowGarrisoning.values()).every(allow => allow);
     131             : };
     132             : 
     133           2 : GarrisonHolder.prototype.GetGarrisonedEntitiesCount = function()
     134             : {
     135          10 :         let count = this.entities.length;
     136          10 :         for (let ent of this.entities)
     137             :         {
     138          18 :                 let cmpGarrisonHolder = Engine.QueryInterface(ent, IID_GarrisonHolder);
     139          18 :                 if (cmpGarrisonHolder)
     140           0 :                         count += cmpGarrisonHolder.GetGarrisonedEntitiesCount();
     141             :         }
     142          10 :         return count;
     143             : };
     144             : 
     145           2 : GarrisonHolder.prototype.OccupiedSlots = function()
     146             : {
     147          56 :         let count = 0;
     148          56 :         for (let ent of this.entities)
     149             :         {
     150         160 :                 let cmpGarrisonable = Engine.QueryInterface(ent, IID_Garrisonable);
     151         160 :                 if (cmpGarrisonable)
     152         160 :                         count += cmpGarrisonable.TotalSize();
     153             :         }
     154          56 :         return count;
     155             : };
     156             : 
     157           2 : GarrisonHolder.prototype.IsAllowedToGarrison = function(entity)
     158             : {
     159          51 :         if (!this.IsGarrisoningAllowed())
     160           1 :                 return false;
     161             : 
     162          50 :         let cmpGarrisonable = Engine.QueryInterface(entity, IID_Garrisonable);
     163          50 :         if (!cmpGarrisonable || this.OccupiedSlots() + cmpGarrisonable.TotalSize() > this.GetCapacity())
     164           5 :                 return false;
     165             : 
     166          45 :         return this.IsAllowedToBeGarrisoned(entity);
     167             : };
     168             : 
     169           2 : GarrisonHolder.prototype.IsAllowedToBeGarrisoned = function(entity)
     170             : {
     171          46 :         if (!IsOwnedByMutualAllyOfEntity(entity, this.entity))
     172           3 :                 return false;
     173             : 
     174          43 :         let cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
     175          43 :         return cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), this.allowedClasses);
     176             : };
     177             : 
     178             : /**
     179             :  * @param {number} entity - The entityID to garrison.
     180             :  * @return {boolean} - Whether the entity was garrisoned.
     181             :  */
     182           2 : GarrisonHolder.prototype.Garrison = function(entity)
     183             : {
     184          42 :         if (!this.IsAllowedToGarrison(entity))
     185           8 :                 return false;
     186             : 
     187          34 :         if (!this.HasEnoughHealth())
     188           1 :                 return false;
     189             : 
     190          33 :         if (!this.timer && this.GetHealRate())
     191           4 :                 this.StartTimer();
     192             : 
     193          33 :         this.entities.push(entity);
     194          33 :         this.UpdateGarrisonFlag();
     195             : 
     196          33 :         Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, {
     197             :                 "added": [entity],
     198             :                 "removed": []
     199             :         });
     200             : 
     201          33 :         return true;
     202             : };
     203             : 
     204             : /**
     205             :  * @param {number} entity - The entity ID of the entity to eject.
     206             :  * @param {boolean} forced - Whether eject is forced (e.g. if building is destroyed).
     207             :  * @return {boolean} Whether the entity was ejected.
     208             :  */
     209           2 : GarrisonHolder.prototype.Eject = function(entity, forced)
     210             : {
     211          34 :         if (!this.IsGarrisoningAllowed() && !forced)
     212           1 :                 return false;
     213             : 
     214          33 :         let entityIndex = this.entities.indexOf(entity);
     215             :         // Error: invalid entity ID, usually it's already been ejected, assume success.
     216          33 :         if (entityIndex == -1)
     217           4 :                 return true;
     218             : 
     219          29 :         this.entities.splice(entityIndex, 1);
     220          29 :         this.UpdateGarrisonFlag();
     221          29 :         Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, {
     222             :                 "added": [],
     223             :                 "removed": [entity]
     224             :         });
     225             : 
     226          29 :         return true;
     227             : };
     228             : 
     229             : /**
     230             :  * Tell unit to unload from this entity.
     231             :  * @param {number} entity - The entity to unload.
     232             :  * @return {boolean} Whether the command was successful.
     233             :  */
     234           2 : GarrisonHolder.prototype.Unload = function(entity)
     235             : {
     236          31 :         let cmpGarrisonable = Engine.QueryInterface(entity, IID_Garrisonable);
     237          31 :         return cmpGarrisonable && cmpGarrisonable.UnGarrison();
     238             : };
     239             : 
     240             : /**
     241             :  * Tell units to unload from this entity.
     242             :  * @param {number[]} entities - The entities to unload.
     243             :  * @return {boolean} - Whether all unloads were successful.
     244             :  */
     245           2 : GarrisonHolder.prototype.UnloadEntities = function(entities)
     246             : {
     247           7 :         let success = true;
     248           7 :         for (let entity of entities)
     249          21 :                 if (!this.Unload(entity))
     250           0 :                         success = false;
     251           7 :         return success;
     252             : };
     253             : 
     254             : /**
     255             :  * Unload one or all units that match a template and owner from us.
     256             :  * @param {string} template - Type of units that should be ejected.
     257             :  * @param {number} owner - Id of the player whose units should be ejected.
     258             :  * @param {boolean} all - Whether all units should be ejected.
     259             :  * @return {boolean} Whether the unloading was successful.
     260             :  */
     261           2 : GarrisonHolder.prototype.UnloadTemplate = function(template, owner, all)
     262             : {
     263           2 :         let entities = [];
     264           2 :         let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
     265           2 :         for (let entity of this.entities)
     266             :         {
     267          20 :                 let cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
     268             : 
     269             :                 // Units with multiple ranks are grouped together.
     270          20 :                 let name = cmpIdentity.GetSelectionGroupName() || cmpTemplateManager.GetCurrentTemplateName(entity);
     271          20 :                 if (name != template || owner != Engine.QueryInterface(entity, IID_Ownership).GetOwner())
     272          18 :                         continue;
     273             : 
     274           2 :                 entities.push(entity);
     275             : 
     276             :                 // If 'all' is false, only ungarrison the first matched unit.
     277           2 :                 if (!all)
     278           2 :                         break;
     279             :         }
     280             : 
     281           2 :         return this.UnloadEntities(entities);
     282             : };
     283             : 
     284             : /**
     285             :  * Unload all units, that belong to certain player
     286             :  * and order all own units to move to the rally point.
     287             :  * @param {number} owner - Id of the player whose units should be ejected.
     288             :  * @return {boolean} Whether the unloading was successful.
     289             :  */
     290           2 : GarrisonHolder.prototype.UnloadAllByOwner = function(owner)
     291             : {
     292           2 :         let entities = this.entities.filter(ent => {
     293          18 :                 let cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
     294          18 :                 return cmpOwnership && cmpOwnership.GetOwner() == owner;
     295             :         });
     296           2 :         return this.UnloadEntities(entities);
     297             : };
     298             : 
     299             : /**
     300             :  * Unload all units from the entity and order them to move to the rally point.
     301             :  * @return {boolean} Whether the unloading was successful.
     302             :  */
     303           2 : GarrisonHolder.prototype.UnloadAll = function()
     304             : {
     305           3 :         return this.UnloadEntities(this.entities.slice());
     306             : };
     307             : 
     308             : /**
     309             :  * Used to check if the garrisoning entity's health has fallen below
     310             :  * a certain limit after which all garrisoned units are unloaded.
     311             :  */
     312           2 : GarrisonHolder.prototype.OnHealthChanged = function(msg)
     313             : {
     314           0 :         if (!this.HasEnoughHealth() && this.entities.length)
     315           0 :                 this.EjectOrKill(this.entities.slice());
     316             : };
     317             : 
     318           2 : GarrisonHolder.prototype.HasEnoughHealth = function()
     319             : {
     320             :         // 0 is a valid value so explicitly check for undefined.
     321          37 :         if (this.template.EjectHealth === undefined)
     322          13 :                 return true;
     323             : 
     324          24 :         let cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
     325          24 :         return !cmpHealth || cmpHealth.GetHitpoints() > Math.floor(+this.template.EjectHealth * cmpHealth.GetMaxHitpoints());
     326             : };
     327             : 
     328           2 : GarrisonHolder.prototype.StartTimer = function()
     329             : {
     330           4 :         if (this.timer)
     331           0 :                 return;
     332           4 :         let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     333           4 :         this.timer = cmpTimer.SetInterval(this.entity, IID_GarrisonHolder, "HealTimeout", this.HEAL_TIMEOUT, this.HEAL_TIMEOUT, null);
     334             : };
     335             : 
     336           2 : GarrisonHolder.prototype.StopTimer = function()
     337             : {
     338           0 :         if (!this.timer)
     339           0 :                 return;
     340           0 :         let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     341           0 :         cmpTimer.CancelTimer(this.timer);
     342           0 :         delete this.timer;
     343             : };
     344             : 
     345             : /**
     346             :  * @params data and lateness are unused.
     347             :  */
     348           2 : GarrisonHolder.prototype.HealTimeout = function(data, lateness)
     349             : {
     350           0 :         let healRate = this.GetHealRate();
     351           0 :         if (!this.entities.length || !healRate)
     352             :         {
     353           0 :                 this.StopTimer();
     354           0 :                 return;
     355             :         }
     356             : 
     357           0 :         for (let entity of this.entities)
     358             :         {
     359           0 :                 let cmpHealth = Engine.QueryInterface(entity, IID_Health);
     360           0 :                 if (cmpHealth && !cmpHealth.IsUnhealable())
     361           0 :                         cmpHealth.Increase(healRate);
     362             :         }
     363             : };
     364             : 
     365             : /**
     366             :  * Updates the garrison flag depending whether something is garrisoned in the entity.
     367             :  */
     368           2 : GarrisonHolder.prototype.UpdateGarrisonFlag = function()
     369             : {
     370          65 :         let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
     371          65 :         if (!cmpVisual)
     372          65 :                 return;
     373             : 
     374           0 :         cmpVisual.SetVariant("garrison", this.entities.length ? "garrisoned" : "ungarrisoned");
     375             : };
     376             : 
     377             : /**
     378             :  * Cancel timer when destroyed.
     379             :  */
     380           2 : GarrisonHolder.prototype.OnDestroy = function()
     381             : {
     382           0 :         if (this.timer)
     383             :         {
     384           0 :                 let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     385           0 :                 cmpTimer.CancelTimer(this.timer);
     386             :         }
     387             : };
     388             : 
     389             : /**
     390             :  * If a garrisoned entity is captured, or about to be killed (so its owner changes to '-1'),
     391             :  * remove it from the building so we only ever contain valid entities.
     392             :  */
     393           2 : GarrisonHolder.prototype.OnGlobalOwnershipChanged = function(msg)
     394             : {
     395             :         // The ownership change may be on the garrisonholder
     396           1 :         if (this.entity == msg.entity)
     397             :         {
     398           0 :                 let entities = this.entities.filter(ent => msg.to == INVALID_PLAYER || !IsOwnedByMutualAllyOfEntity(this.entity, ent));
     399             : 
     400           0 :                 if (entities.length)
     401           0 :                         this.EjectOrKill(entities);
     402             : 
     403           0 :                 return;
     404             :         }
     405             : 
     406             :         // or on some of its garrisoned units
     407           1 :         let entityIndex = this.entities.indexOf(msg.entity);
     408           1 :         if (entityIndex != -1 && (msg.to == INVALID_PLAYER || !IsOwnedByMutualAllyOfEntity(this.entity, msg.entity)))
     409           1 :                 this.EjectOrKill([msg.entity]);
     410             : };
     411             : 
     412             : /**
     413             :  * Update list of garrisoned entities when a game inits.
     414             :  */
     415           2 : GarrisonHolder.prototype.OnGlobalSkirmishReplacerReplaced = function(msg)
     416             : {
     417           0 :         if (!this.initGarrison)
     418           0 :                 return;
     419             : 
     420           0 :         if (msg.entity == this.entity)
     421             :         {
     422           0 :                 let cmpGarrisonHolder = Engine.QueryInterface(msg.newentity, IID_GarrisonHolder);
     423           0 :                 if (cmpGarrisonHolder)
     424           0 :                         cmpGarrisonHolder.initGarrison = this.initGarrison;
     425             :         }
     426             :         else
     427             :         {
     428           0 :                 let entityIndex = this.initGarrison.indexOf(msg.entity);
     429           0 :                 if (entityIndex != -1)
     430           0 :                         this.initGarrison[entityIndex] = msg.newentity;
     431             :         }
     432             : };
     433             : 
     434             : /**
     435             :  * Eject all foreign garrisoned entities which are no more allied.
     436             :  */
     437           2 : GarrisonHolder.prototype.OnDiplomacyChanged = function()
     438             : {
     439           4 :         this.EjectOrKill(this.entities.filter(ent => !IsOwnedByMutualAllyOfEntity(this.entity, ent)));
     440             : };
     441             : 
     442             : /**
     443             :  * Eject or kill a garrisoned unit which can no more be garrisoned
     444             :  * (garrisonholder's health too small or ownership changed).
     445             :  */
     446           2 : GarrisonHolder.prototype.EjectOrKill = function(entities)
     447             : {
     448           3 :         let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     449             :         // Eject the units which can be ejected (if not in world, it generally means this holder
     450             :         // is inside a holder which kills its entities, so do not eject)
     451           3 :         if (cmpPosition && cmpPosition.IsInWorld())
     452             :         {
     453           0 :                 let ejectables = entities.filter(ent => this.IsEjectable(ent));
     454           0 :                 if (ejectables.length)
     455           0 :                         this.UnloadEntities(ejectables);
     456             :         }
     457             : 
     458             :         // And destroy all remaining entities
     459           3 :         let killedEntities = [];
     460           3 :         for (let entity of entities)
     461             :         {
     462           3 :                 let entityIndex = this.entities.indexOf(entity);
     463           3 :                 if (entityIndex == -1)
     464           0 :                         continue;
     465           3 :                 let cmpHealth = Engine.QueryInterface(entity, IID_Health);
     466           3 :                 if (cmpHealth)
     467           0 :                         cmpHealth.Kill();
     468             :                 else
     469           3 :                         Engine.DestroyEntity(entity);
     470           3 :                 this.entities.splice(entityIndex, 1);
     471           3 :                 killedEntities.push(entity);
     472             :         }
     473             : 
     474           3 :         if (killedEntities.length)
     475             :         {
     476           3 :                 Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, {
     477             :                         "added": [],
     478             :                         "removed": killedEntities
     479             :                 });
     480           3 :                 this.UpdateGarrisonFlag();
     481             :         }
     482             : };
     483             : 
     484             : /**
     485             :  * Whether an entity is ejectable.
     486             :  * @param {number} entity - The entity-ID to be tested.
     487             :  * @return {boolean} - Whether the entity is ejectable.
     488             :  */
     489           2 : GarrisonHolder.prototype.IsEjectable = function(entity)
     490             : {
     491          10 :         if (!this.entities.find(ent => ent == entity))
     492           2 :                 return false;
     493             : 
     494           2 :         let ejectableClasses = this.template.EjectClassesOnDestroy._string;
     495           2 :         let entityClasses = Engine.QueryInterface(entity, IID_Identity).GetClassesList();
     496             : 
     497           2 :         return MatchesClassList(entityClasses, ejectableClasses);
     498             : };
     499             : 
     500             : /**
     501             :  * Sets the intitGarrison to the specified entities. Used by the mapreader.
     502             :  *
     503             :  * @param {number[]} entities - The entity IDs to garrison on init.
     504             :  */
     505           2 : GarrisonHolder.prototype.SetInitGarrison = function(entities)
     506             : {
     507           1 :         this.initGarrison = clone(entities);
     508             : };
     509             : 
     510             : /**
     511             :  * Initialise the garrisoned units.
     512             :  */
     513           2 : GarrisonHolder.prototype.OnGlobalInitGame = function(msg)
     514             : {
     515           1 :         if (!this.initGarrison)
     516           0 :                 return;
     517             : 
     518           1 :         for (let ent of this.initGarrison)
     519             :         {
     520           4 :                 let cmpGarrisonable = Engine.QueryInterface(ent, IID_Garrisonable);
     521           4 :                 if (cmpGarrisonable)
     522           4 :                         cmpGarrisonable.Garrison(this.entity);
     523             :         }
     524           1 :         delete this.initGarrison;
     525             : };
     526             : 
     527           2 : GarrisonHolder.prototype.OnValueModification = function(msg)
     528             : {
     529           1 :         if (msg.component != "GarrisonHolder")
     530           0 :                 return;
     531             : 
     532           1 :         if (msg.valueNames.indexOf("GarrisonHolder/List/_string") !== -1)
     533             :         {
     534           1 :                 this.allowedClasses = ApplyValueModificationsToEntity("GarrisonHolder/List/_string", this.template.List._string, this.entity);
     535           1 :                 this.EjectOrKill(this.entities.filter(entity => !this.IsAllowedToBeGarrisoned(entity)));
     536             :         }
     537             : 
     538           1 :         if (msg.valueNames.indexOf("GarrisonHolder/BuffHeal") === -1)
     539           1 :                 return;
     540             : 
     541           0 :         if (this.timer && !this.GetHealRate())
     542           0 :                 this.StopTimer();
     543           0 :         else if (!this.timer && this.GetHealRate())
     544           0 :                 this.StartTimer();
     545             : };
     546             : 
     547           2 : Engine.RegisterComponentType(IID_GarrisonHolder, "GarrisonHolder", GarrisonHolder);

Generated by: LCOV version 1.14