LCOV - code coverage report
Current view: top level - simulation/components - Auras.js (source / functions) Hit Total Coverage
Test: lcov.info Lines: 188 252 74.6 %
Date: 2023-04-02 12:52:40 Functions: 47 58 81.0 %

          Line data    Source code
       1             : function Auras() {}
       2             : 
       3           1 : Auras.prototype.Schema =
       4             :         "<attribute name='datatype'>" +
       5             :                 "<value>tokens</value>" +
       6             :         "</attribute>" +
       7             :         "<text a:help='A whitespace-separated list of aura files, placed under simulation/data/auras/'/>";
       8             : 
       9           1 : Auras.prototype.Init = function()
      10             : {
      11           8 :         this.affectedPlayers = {};
      12             : 
      13           8 :         for (let name of this.GetAuraNames())
      14           8 :                 this.affectedPlayers[name] = [];
      15             : 
      16             :         // In case of autogarrisoning, this component can be called before ownership is set.
      17             :         // So it needs to be completely initialised from the start.
      18           8 :         this.Clean();
      19             : };
      20             : 
      21             : // We can modify identifier if we want stackable auras in some case.
      22           1 : Auras.prototype.GetModifierIdentifier = function(name)
      23             : {
      24          12 :         if (AuraTemplates.Get(name).stackable)
      25           0 :                 return "aura/" + name + this.entity;
      26          12 :         return "aura/" + name;
      27             : };
      28             : 
      29           1 : Auras.prototype.GetDescriptions = function()
      30             : {
      31           0 :         var ret = {};
      32           0 :         for (let auraID of this.GetAuraNames())
      33             :         {
      34           0 :                 let aura = AuraTemplates.Get(auraID);
      35           0 :                 ret[auraID] = {
      36             :                         "name": {
      37             :                                 "generic": aura.auraName
      38             :                         },
      39             :                         "description": aura.auraDescription || null,
      40             :                         "radius": this.GetRange(auraID) || null
      41             :                 };
      42             :         }
      43           0 :         return ret;
      44             : };
      45             : 
      46           1 : Auras.prototype.GetAuraNames = function()
      47             : {
      48          28 :         return this.template._string.split(/\s+/);
      49             : };
      50             : 
      51           1 : Auras.prototype.GetOverlayIcon = function(name)
      52             : {
      53          11 :         return AuraTemplates.Get(name).overlayIcon || "";
      54             : };
      55             : 
      56           1 : Auras.prototype.GetAffectedEntities = function(name)
      57             : {
      58           0 :         return this[name].targetUnits;
      59             : };
      60             : 
      61           1 : Auras.prototype.GetRange = function(name)
      62             : {
      63           1 :         if (this.IsRangeAura(name))
      64           1 :                 return +AuraTemplates.Get(name).radius;
      65           0 :         return undefined;
      66             : };
      67             : 
      68           1 : Auras.prototype.GetClasses = function(name)
      69             : {
      70          29 :         return AuraTemplates.Get(name).affects;
      71             : };
      72             : 
      73           1 : Auras.prototype.GetModifications = function(name)
      74             : {
      75          12 :         return AuraTemplates.Get(name).modifications;
      76             : };
      77             : 
      78           1 : Auras.prototype.GetAffectedPlayers = function(name)
      79             : {
      80          11 :         return this.affectedPlayers[name];
      81             : };
      82             : 
      83           1 : Auras.prototype.GetRangeOverlays = function()
      84             : {
      85           0 :         let rangeOverlays = [];
      86             : 
      87           0 :         for (let name of this.GetAuraNames())
      88             :         {
      89           0 :                 if (!this.IsRangeAura(name) || !this[name].isApplied)
      90           0 :                         continue;
      91             : 
      92           0 :                 let rangeOverlay = AuraTemplates.Get(name).rangeOverlay;
      93             : 
      94           0 :                 rangeOverlays.push(
      95             :                         rangeOverlay ?
      96             :                                 {
      97             :                                         "radius": this.GetRange(name),
      98             :                                         "texture": rangeOverlay.lineTexture,
      99             :                                         "textureMask": rangeOverlay.lineTextureMask,
     100             :                                         "thickness": rangeOverlay.lineThickness
     101             :                                 } :
     102             :                                 // Specify default in order not to specify it in about 40 auras
     103             :                                 {
     104             :                                         "radius": this.GetRange(name),
     105             :                                         "texture": "outline_border.png",
     106             :                                         "textureMask": "outline_border_mask.png",
     107             :                                         "thickness": 0.2
     108             :                                 });
     109             :         }
     110             : 
     111           0 :         return rangeOverlays;
     112             : };
     113             : 
     114           1 : Auras.prototype.CalculateAffectedPlayers = function(name)
     115             : {
     116           9 :         var affectedPlayers = AuraTemplates.Get(name).affectedPlayers || ["Player"];
     117           9 :         this.affectedPlayers[name] = [];
     118             : 
     119           9 :         var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
     120           9 :         if (!cmpPlayer)
     121           8 :                 cmpPlayer = QueryOwnerInterface(this.entity);
     122             : 
     123           9 :         if (!cmpPlayer || cmpPlayer.IsDefeated())
     124           2 :                 return;
     125             : 
     126           7 :         let cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
     127           7 :         for (let i of cmpPlayerManager.GetAllPlayers())
     128             :         {
     129          21 :                 let cmpAffectedPlayer = QueryPlayerIDInterface(i);
     130          21 :                 if (!cmpAffectedPlayer || cmpAffectedPlayer.IsDefeated())
     131           7 :                         continue;
     132             : 
     133          14 :                 if (affectedPlayers.some(p => p == "Player" ? cmpPlayer.GetPlayerID() == i : cmpPlayer["Is" + p](i)))
     134          14 :                         this.affectedPlayers[name].push(i);
     135             :         }
     136             : };
     137             : 
     138           1 : Auras.prototype.CanApply = function(name)
     139             : {
     140           9 :         if (!AuraTemplates.Get(name).requiredTechnology)
     141           9 :                 return true;
     142             : 
     143           0 :         let cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager);
     144           0 :         if (!cmpTechnologyManager)
     145           0 :                 return false;
     146             : 
     147           0 :         return cmpTechnologyManager.IsTechnologyResearched(AuraTemplates.Get(name).requiredTechnology);
     148             : };
     149             : 
     150           1 : Auras.prototype.HasFormationAura = function()
     151             : {
     152           1 :         return this.GetAuraNames().some(n => this.IsFormationAura(n));
     153             : };
     154             : 
     155           1 : Auras.prototype.HasGarrisonAura = function()
     156             : {
     157           1 :         return this.GetAuraNames().some(n => this.IsGarrisonAura(n));
     158             : };
     159             : 
     160           1 : Auras.prototype.HasGarrisonedUnitsAura = function()
     161             : {
     162           0 :         return this.GetAuraNames().some(n => this.IsGarrisonedUnitsAura(n));
     163             : };
     164             : 
     165           1 : Auras.prototype.GetType = function(name)
     166             : {
     167          40 :         return AuraTemplates.Get(name).type;
     168             : };
     169             : 
     170           1 : Auras.prototype.IsFormationAura = function(name)
     171             : {
     172           3 :         return this.GetType(name) == "formation";
     173             : };
     174             : 
     175           1 : Auras.prototype.IsGarrisonAura = function(name)
     176             : {
     177           3 :         return this.GetType(name) == "garrison";
     178             : };
     179             : 
     180           1 : Auras.prototype.IsGarrisonedUnitsAura = function(name)
     181             : {
     182           2 :         return this.GetType(name) == "garrisonedUnits";
     183             : };
     184             : 
     185           1 : Auras.prototype.IsTurretedUnitsAura = function(name)
     186             : {
     187           0 :         return this.GetType(name) == "turretedUnits";
     188             : };
     189             : 
     190           1 : Auras.prototype.IsRangeAura = function(name)
     191             : {
     192           7 :         return this.GetType(name) == "range";
     193             : };
     194             : 
     195           1 : Auras.prototype.IsGlobalAura = function(name)
     196             : {
     197          20 :         return this.GetType(name) == "global";
     198             : };
     199             : 
     200           1 : Auras.prototype.IsPlayerAura = function(name)
     201             : {
     202           5 :         return this.GetType(name) == "player";
     203             : };
     204             : 
     205             : /**
     206             :  * clean all bonuses. Remove the old ones and re-apply the new ones
     207             :  */
     208           1 : Auras.prototype.Clean = function()
     209             : {
     210           9 :         var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     211           9 :         var auraNames = this.GetAuraNames();
     212           9 :         let targetUnitsClone = {};
     213           9 :         let needVisualizationUpdate = false;
     214             :         // remove all bonuses
     215           9 :         for (let name of auraNames)
     216             :         {
     217           9 :                 targetUnitsClone[name] = [];
     218           9 :                 if (!this[name])
     219           8 :                         continue;
     220             : 
     221           1 :                 if (this.IsRangeAura(name))
     222           0 :                         needVisualizationUpdate = true;
     223             : 
     224           1 :                 if (this[name].targetUnits)
     225           1 :                         targetUnitsClone[name] = this[name].targetUnits.slice();
     226             : 
     227           1 :                 if (this.IsGlobalAura(name))
     228           1 :                         this.RemoveTemplateAura(name);
     229             : 
     230           1 :                 this.RemoveAura(name, this[name].targetUnits);
     231             : 
     232           1 :                 if (this[name].rangeQuery)
     233           0 :                         cmpRangeManager.DestroyActiveQuery(this[name].rangeQuery);
     234             :         }
     235             : 
     236           9 :         for (let name of auraNames)
     237             :         {
     238             :                 // only calculate the affected players on re-applying the bonuses
     239             :                 // this makes sure the template bonuses are removed from the correct players
     240           9 :                 this.CalculateAffectedPlayers(name);
     241             :                 // initialise range query
     242           9 :                 this[name] = {};
     243           9 :                 this[name].targetUnits = [];
     244           9 :                 this[name].isApplied = this.CanApply(name);
     245           9 :                 var affectedPlayers = this.GetAffectedPlayers(name);
     246             : 
     247           9 :                 if (!affectedPlayers.length)
     248           2 :                         continue;
     249             : 
     250           7 :                 if (this.IsGlobalAura(name))
     251             :                 {
     252           2 :                         this.ApplyTemplateAura(name, affectedPlayers);
     253             :                         // Only need to call ApplyAura for the aura icons, so skip it if there are none.
     254           2 :                         if (this.GetOverlayIcon(name))
     255           0 :                                 for (let player of affectedPlayers)
     256           0 :                                         this.ApplyAura(name, cmpRangeManager.GetEntitiesByPlayer(player));
     257           2 :                         continue;
     258             :                 }
     259             : 
     260           5 :                 if (this.IsPlayerAura(name))
     261             :                 {
     262           1 :                         let cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
     263           2 :                         this.ApplyAura(name, affectedPlayers.map(p => cmpPlayerManager.GetPlayerByID(p)));
     264           1 :                         continue;
     265             :                 }
     266             : 
     267           4 :                 if (!this.IsRangeAura(name))
     268             :                 {
     269           3 :                         this.ApplyAura(name, targetUnitsClone[name]);
     270           3 :                         continue;
     271             :                 }
     272             : 
     273           1 :                 needVisualizationUpdate = true;
     274             : 
     275           1 :                 if (this[name].isApplied && (this.IsRangeAura(name) || this.IsGlobalAura(name) && !!this.GetOverlayIcon(name)))
     276             :                 {
     277             :                         // Do not account for entity sizes: structures can have various sizes
     278             :                         // and we currently prefer auras to not depend on the source size
     279             :                         // (this is generally irrelevant for units).
     280           1 :                         this[name].rangeQuery = cmpRangeManager.CreateActiveQuery(
     281             :                                 this.entity,
     282             :                                 0,
     283             :                                 this.GetRange(name),
     284             :                                 affectedPlayers,
     285             :                                 IID_Identity,
     286             :                                 cmpRangeManager.GetEntityFlagMask("normal"),
     287             :                                 false
     288             :                         );
     289           1 :                         cmpRangeManager.EnableActiveQuery(this[name].rangeQuery);
     290             :                 }
     291             :         }
     292             : 
     293           9 :         if (needVisualizationUpdate)
     294             :         {
     295           1 :                 let cmpRangeOverlayManager = Engine.QueryInterface(this.entity, IID_RangeOverlayManager);
     296           1 :                 if (cmpRangeOverlayManager)
     297             :                 {
     298           0 :                         cmpRangeOverlayManager.UpdateRangeOverlays("Auras");
     299           0 :                         cmpRangeOverlayManager.RegenerateRangeOverlays(false);
     300             :                 }
     301             :         }
     302             : };
     303             : 
     304           1 : Auras.prototype.GiveMembersWithValidClass = function(auraName, entityList)
     305             : {
     306          17 :         var match = this.GetClasses(auraName);
     307          17 :         return entityList.filter(ent => {
     308          10 :                 let cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
     309          10 :                 return cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), match);
     310             :         });
     311             : };
     312             : 
     313           1 : Auras.prototype.OnRangeUpdate = function(msg)
     314             : {
     315           2 :         for (let name of this.GetAuraNames().filter(n => this[n] && msg.tag == this[n].rangeQuery))
     316             :         {
     317           2 :                 this.ApplyAura(name, msg.added);
     318           2 :                 this.RemoveAura(name, msg.removed);
     319             :         }
     320             : };
     321             : 
     322           1 : Auras.prototype.OnGarrisonedUnitsChanged = function(msg)
     323             : {
     324           2 :         for (let name of this.GetAuraNames().filter(n => this.IsGarrisonedUnitsAura(n)))
     325             :         {
     326           2 :                 this.ApplyAura(name, msg.added);
     327           2 :                 this.RemoveAura(name, msg.removed);
     328             :         }
     329             : };
     330             : 
     331           1 : Auras.prototype.OnTurretsChanged = function(msg)
     332             : {
     333           0 :         for (let name of this.GetAuraNames().filter(n => this.IsTurretedUnitsAura(n)))
     334             :         {
     335           0 :                 this.ApplyAura(name, msg.added);
     336           0 :                 this.RemoveAura(name, msg.removed);
     337             :         }
     338             : };
     339             : 
     340           1 : Auras.prototype.ApplyFormationAura = function(memberList)
     341             : {
     342           1 :         for (let name of this.GetAuraNames().filter(n => this.IsFormationAura(n)))
     343           1 :                 this.ApplyAura(name, memberList);
     344             : };
     345             : 
     346           1 : Auras.prototype.ApplyGarrisonAura = function(structure)
     347             : {
     348           1 :         for (let name of this.GetAuraNames().filter(n => this.IsGarrisonAura(n)))
     349           1 :                 this.ApplyAura(name, [structure]);
     350             : };
     351             : 
     352           1 : Auras.prototype.ApplyTemplateAura = function(name, players)
     353             : {
     354           2 :         if (!this[name].isApplied)
     355           0 :                 return;
     356             : 
     357           2 :         if (!this.IsGlobalAura(name))
     358           0 :                 return;
     359             : 
     360           2 :         let derivedModifiers = DeriveModificationsFromTech({
     361             :                 "modifications": this.GetModifications(name),
     362             :                 "affects": this.GetClasses(name)
     363             :         });
     364           2 :         let cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager);
     365           2 :         let cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
     366             : 
     367           2 :         let modifName = this.GetModifierIdentifier(name);
     368           2 :         for (let player of players)
     369           4 :                 cmpModifiersManager.AddModifiers(modifName, derivedModifiers, cmpPlayerManager.GetPlayerByID(player));
     370             : };
     371             : 
     372           1 : Auras.prototype.RemoveFormationAura = function(memberList)
     373             : {
     374           1 :         for (let name of this.GetAuraNames().filter(n => this.IsFormationAura(n)))
     375           1 :                 this.RemoveAura(name, memberList);
     376             : };
     377             : 
     378           1 : Auras.prototype.RemoveGarrisonAura = function(structure)
     379             : {
     380           1 :         for (let name of this.GetAuraNames().filter(n => this.IsGarrisonAura(n)))
     381           1 :                 this.RemoveAura(name, [structure]);
     382             : };
     383             : 
     384           1 : Auras.prototype.RemoveTemplateAura = function(name)
     385             : {
     386           1 :         if (!this[name].isApplied)
     387           0 :                 return;
     388             : 
     389           1 :         if (!this.IsGlobalAura(name))
     390           0 :                 return;
     391             : 
     392           1 :         let derivedModifiers = DeriveModificationsFromTech({
     393             :                 "modifications": this.GetModifications(name),
     394             :                 "affects": this.GetClasses(name)
     395             :         });
     396           1 :         let cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager);
     397           1 :         let cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
     398             : 
     399           1 :         let modifName = this.GetModifierIdentifier(name);
     400           1 :         for (let player of this.GetAffectedPlayers(name))
     401             :         {
     402           2 :                 let playerId = cmpPlayerManager.GetPlayerByID(player);
     403           2 :                 for (let modifierPath in derivedModifiers)
     404           2 :                         cmpModifiersManager.RemoveModifier(modifierPath, modifName, playerId);
     405             :         }
     406             : };
     407             : 
     408           1 : Auras.prototype.ApplyAura = function(name, ents)
     409             : {
     410          10 :         var validEnts = this.GiveMembersWithValidClass(name, ents);
     411          10 :         if (!validEnts.length)
     412           5 :                 return;
     413             : 
     414           5 :         this[name].targetUnits = this[name].targetUnits.concat(validEnts);
     415             : 
     416           5 :         if (!this[name].isApplied)
     417           0 :                 return;
     418             : 
     419             :         // update status bars if this has an icon
     420           5 :         if (this.GetOverlayIcon(name))
     421           0 :                 for (let ent of validEnts)
     422             :                 {
     423           0 :                         let cmpStatusBars = Engine.QueryInterface(ent, IID_StatusBars);
     424           0 :                         if (cmpStatusBars)
     425           0 :                                 cmpStatusBars.AddAuraSource(this.entity, name);
     426             :                 }
     427             : 
     428             :         // Global aura modifications are handled at the player level by the modification manager,
     429             :         // so stop after icons have been applied.
     430           5 :         if (this.IsGlobalAura(name))
     431           0 :                 return;
     432             : 
     433           5 :         let cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager);
     434             : 
     435           5 :         let derivedModifiers = DeriveModificationsFromTech({
     436             :                 "modifications": this.GetModifications(name),
     437             :                 "affects": this.GetClasses(name)
     438             :         });
     439             : 
     440           5 :         let modifName = this.GetModifierIdentifier(name);
     441           5 :         for (let ent of validEnts)
     442           5 :                 cmpModifiersManager.AddModifiers(modifName, derivedModifiers, ent);
     443             : };
     444             : 
     445           1 : Auras.prototype.RemoveAura = function(name, ents, skipModifications = false)
     446             : {
     447           7 :         var validEnts = this.GiveMembersWithValidClass(name, ents);
     448           7 :         if (!validEnts.length)
     449           3 :                 return;
     450             : 
     451           4 :         this[name].targetUnits = this[name].targetUnits.filter(v => validEnts.indexOf(v) == -1);
     452             : 
     453           4 :         if (!this[name].isApplied)
     454           0 :                 return;
     455             : 
     456             :         // update status bars if this has an icon
     457           4 :         if (this.GetOverlayIcon(name))
     458           0 :                 for (let ent of validEnts)
     459             :                 {
     460           0 :                         let cmpStatusBars = Engine.QueryInterface(ent, IID_StatusBars);
     461           0 :                         if (cmpStatusBars)
     462           0 :                                 cmpStatusBars.RemoveAuraSource(this.entity, name);
     463             :                 }
     464             : 
     465             :         // Global aura modifications are handled at the player level by the modification manager,
     466             :         // so stop after icons have been removed.
     467           4 :         if (this.IsGlobalAura(name))
     468           0 :                 return;
     469             : 
     470           4 :         let cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager);
     471             : 
     472           4 :         let derivedModifiers = DeriveModificationsFromTech({
     473             :                 "modifications": this.GetModifications(name),
     474             :                 "affects": this.GetClasses(name)
     475             :         });
     476             : 
     477           4 :         let modifName = this.GetModifierIdentifier(name);
     478           4 :         for (let ent of ents)
     479           4 :                 for (let modifierPath in derivedModifiers)
     480           4 :                         cmpModifiersManager.RemoveModifier(modifierPath, modifName, ent);
     481             : };
     482             : 
     483           1 : Auras.prototype.OnOwnershipChanged = function(msg)
     484             : {
     485           1 :         this.Clean();
     486             : };
     487             : 
     488           1 : Auras.prototype.OnDiplomacyChanged = function(msg)
     489             : {
     490           0 :         var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
     491           0 :         if (cmpPlayer && (cmpPlayer.GetPlayerID() == msg.player || cmpPlayer.GetPlayerID() == msg.otherPlayer) ||
     492             :            IsOwnedByPlayer(msg.player, this.entity) ||
     493             :            IsOwnedByPlayer(msg.otherPlayer, this.entity))
     494           0 :                 this.Clean();
     495             : };
     496             : 
     497           1 : Auras.prototype.OnGlobalResearchFinished = function(msg)
     498             : {
     499           0 :         var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
     500           0 :         if ((!cmpPlayer || cmpPlayer.GetPlayerID() != msg.player) && !IsOwnedByPlayer(msg.player, this.entity))
     501           0 :                 return;
     502           0 :         for (let name of this.GetAuraNames())
     503             :         {
     504           0 :                 let requiredTech = AuraTemplates.Get(name).requiredTechnology;
     505           0 :                 if (requiredTech && requiredTech == msg.tech)
     506             :                 {
     507           0 :                         this.Clean();
     508           0 :                         return;
     509             :                 }
     510             :         }
     511             : };
     512             : 
     513             : /**
     514             :  * Update auras of the player entity and entities affecting player entities that didn't change ownership.
     515             :  */
     516           1 : Auras.prototype.OnGlobalPlayerDefeated = function(msg)
     517             : {
     518           1 :         let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
     519           1 :         if (cmpPlayer && cmpPlayer.GetPlayerID() == msg.playerId ||
     520           1 :                 this.GetAuraNames().some(name => this.GetAffectedPlayers(name).indexOf(msg.playerId) != -1))
     521           0 :                 this.Clean();
     522             : };
     523             : 
     524           1 : Auras.prototype.OnGarrisonedStateChanged = function(msg)
     525             : {
     526           0 :         if (!this.HasGarrisonAura())
     527           0 :                 return;
     528             : 
     529           0 :         if (msg.holderID != INVALID_ENTITY)
     530           0 :                 this.ApplyGarrisonAura(msg.holderID);
     531           0 :         if (msg.oldHolder != INVALID_ENTITY)
     532           0 :                 this.RemoveGarrisonAura(msg.oldHolder);
     533             : };
     534             : 
     535           1 : Engine.RegisterComponentType(IID_Auras, "Auras", Auras);

Generated by: LCOV version 1.14