LCOV - code coverage report
Current view: top level - simulation/components - ModifiersManager.js (source / functions) Hit Total Coverage
Test: lcov.info Lines: 122 132 92.4 %
Date: 2023-04-02 12:52:40 Functions: 27 30 90.0 %

          Line data    Source code
       1             : function ModifiersManager() {}
       2             : 
       3           3 : ModifiersManager.prototype.Schema =
       4             :         "<empty/>";
       5             : 
       6           3 : ModifiersManager.prototype.Init = function()
       7             : {
       8             :         // TODO:
       9             :         //  - add a way to show an icon for a given modifier ID
      10             :         //    > Note that aura code shows icons when the source is selected, so that's specific to them.
      11             :         //  - support stacking modifiers (MultiKeyMap handles it but not this manager).
      12             : 
      13             :         // The cache computes values lazily when they are needed.
      14             :         // Helper functions remove items that have been changed to ensure we stay up-to-date.
      15          21 :         this.cachedValues = new Map(); // Keyed by property name, entity ID, original values.
      16             : 
      17             :         // When changing global modifiers, all entity-local caches are invalidated. This helps with that.
      18             :         // TODO: it might be worth keying by classes here.
      19          21 :         this.playerEntitiesCached = new Map(); // Keyed by player ID, property name, entity ID.
      20             : 
      21          21 :         this.modifiersStorage = new MultiKeyMap(); // Keyed by property name, entity.
      22             : 
      23          37 :         this.modifiersStorage._OnItemModified = (prim, sec, itemID) => this.ModifiersChanged.apply(this, [prim, sec, itemID]);
      24             : };
      25             : 
      26           3 : ModifiersManager.prototype.Serialize = function()
      27             : {
      28             :         // The value cache will be affected by property reads from the GUI and other places so we shouldn't serialize it.
      29             :         // Furthermore it is cyclically self-referencing.
      30             :         // We need to store the player for the Player-Entities cache.
      31           4 :         let players = [];
      32           4 :         this.playerEntitiesCached.forEach((_, player) => players.push(player));
      33           4 :         return {
      34             :                 "modifiersStorage": this.modifiersStorage.Serialize(),
      35             :                 "players": players
      36             :         };
      37             : };
      38             : 
      39           3 : ModifiersManager.prototype.Deserialize = function(data)
      40             : {
      41           4 :         this.Init();
      42           4 :         this.modifiersStorage.Deserialize(data.modifiersStorage);
      43           4 :         data.players.forEach(player => this.playerEntitiesCached.set(player, new Map()));
      44             : };
      45             : 
      46             : /**
      47             :  * Inform entities that we have changed possibly all values affected by that property.
      48             :  * It's not hugely efficient and would be nice to batch.
      49             :  * Invalidate caches where relevant.
      50             :  */
      51           3 : ModifiersManager.prototype.ModifiersChanged = function(propertyName, entity)
      52             : {
      53          37 :         let playerCache = this.playerEntitiesCached.get(entity);
      54          37 :         this.InvalidateCache(propertyName, entity, playerCache);
      55             : 
      56          37 :         if (playerCache)
      57             :         {
      58          15 :                 let cmpPlayer = Engine.QueryInterface(entity, IID_Player);
      59          15 :                 if (cmpPlayer)
      60          15 :                         this.SendPlayerModifierMessages(propertyName, cmpPlayer.GetPlayerID());
      61             :         }
      62             :         else
      63          22 :                 Engine.PostMessage(entity, MT_ValueModification, { "entities": [entity], "component": propertyName.split("/")[0], "valueNames": [propertyName] });
      64             : };
      65             : 
      66           3 : ModifiersManager.prototype.SendPlayerModifierMessages = function(propertyName, player)
      67             : {
      68             :         // TODO: it would be preferable to be able to batch this (i.e. one message for several properties)
      69          15 :         Engine.PostMessage(SYSTEM_ENTITY, MT_TemplateModification, { "player": player, "component": propertyName.split("/")[0], "valueNames": [propertyName] });
      70             :         // AIInterface wants the entities potentially affected.
      71             :         // TODO: improve on this
      72          15 :         let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
      73          15 :         let ents = cmpRangeManager.GetEntitiesByPlayer(player);
      74          15 :         Engine.BroadcastMessage(MT_ValueModification, { "entities": ents, "component": propertyName.split("/")[0], "valueNames": [propertyName] });
      75             : };
      76             : 
      77           3 : ModifiersManager.prototype.InvalidatePlayerEntCache = function(valueCache, propertyName, entsMap)
      78             : {
      79           6 :         entsMap = entsMap.get(propertyName);
      80           6 :         if (entsMap)
      81             :         {
      82             :                 // Invalidate all local caches directly (for simplicity in ApplyModifiers).
      83           7 :                 entsMap.forEach(ent => valueCache.set(ent, new Map()));
      84           4 :                 entsMap.clear();
      85             :         }
      86             : };
      87             : 
      88           3 : ModifiersManager.prototype.InvalidateCache = function(propertyName, entity, playerCache)
      89             : {
      90          38 :         let valueCache = this.cachedValues.get(propertyName);
      91          38 :         if (!valueCache)
      92          20 :                 return;
      93             : 
      94          18 :         if (playerCache)
      95           6 :                 this.InvalidatePlayerEntCache(valueCache, propertyName, playerCache);
      96          18 :         valueCache.set(entity, new Map());
      97             : };
      98             : 
      99             : /**
     100             :  * @returns originalValue after modifiers.
     101             :  */
     102           3 : ModifiersManager.prototype.FetchModifiedProperty = function(classesList, propertyName, originalValue, target)
     103             : {
     104          70 :         let modifs = this.modifiersStorage.GetItems(propertyName, target);
     105          70 :         if (!modifs.length)
     106          36 :                 return originalValue;
     107             :         // Flatten the list of modifications
     108          34 :         let modifications = [];
     109          68 :         modifs.forEach(item => { modifications = modifications.concat(item.value); });
     110          34 :         return GetTechModifiedProperty(modifications, classesList, originalValue);
     111             : };
     112             : 
     113             : /**
     114             :  * @returns originalValue after modifiers
     115             :  */
     116           3 : ModifiersManager.prototype.Cache = function(classesList, propertyName, originalValue, entity)
     117             : {
     118          36 :         let cache = this.cachedValues.get(propertyName);
     119          36 :         if (!cache)
     120          14 :                 cache = this.cachedValues.set(propertyName, new Map()).get(propertyName);
     121             : 
     122          36 :         let cache2 = cache.get(entity);
     123          36 :         if (!cache2)
     124          19 :                 cache2 = cache.set(entity, new Map()).get(entity);
     125             : 
     126          36 :         let value = this.FetchModifiedProperty(classesList, propertyName, originalValue, entity);
     127          36 :         cache2.set(originalValue, value);
     128          36 :         return value;
     129             : };
     130             : 
     131             : /**
     132             :  * Caching system in front of FetchModifiedProperty(), as calling that every time is quite slow.
     133             :  * This recomputes lazily.
     134             :  * Applies per-player modifiers before per-entity modifiers, so the latter take priority;
     135             :  * @param propertyName - Handle of a technology property (eg Attack/Ranged/Pierce) that was changed.
     136             :  * @param originalValue - template/raw/before-modifiers value.
     137             :                 Note that if this is supposed to be a number (i.e. you call add/multiply on it)
     138             :                 You must make sure to pass a number and not a string (by using + if necessary)
     139             :  * @param ent - ID of the target entity
     140             :  * @returns originalValue after the modifiers
     141             :  */
     142           3 : ModifiersManager.prototype.ApplyModifiers = function(propertyName, originalValue, entity)
     143             : {
     144          39 :         let newValue = this.cachedValues.get(propertyName);
     145          39 :         if (newValue)
     146             :         {
     147          25 :                 newValue = newValue.get(entity);
     148          25 :                 if (newValue)
     149             :                 {
     150          20 :                         newValue = newValue.get(originalValue);
     151          20 :                         if (newValue)
     152           3 :                                 return newValue;
     153             :                 }
     154             :         }
     155             : 
     156             :         // Get the entity ID of the player / owner of the entity, since we use that to store per-player modifiers
     157             :         // (this prevents conflicts between player ID and entity ID).
     158          36 :         let ownerEntity = QueryOwnerEntityID(entity);
     159          36 :         if (ownerEntity == entity)
     160           3 :                 ownerEntity = null;
     161             : 
     162          36 :         newValue = originalValue;
     163             : 
     164          36 :         let cmpIdentity = QueryMiragedInterface(entity, IID_Identity);
     165          36 :         if (!cmpIdentity)
     166           0 :                 return originalValue;
     167          36 :         let classesList = cmpIdentity.GetClassesList();
     168             : 
     169             :         // Apply player-wide modifiers before entity-local modifiers.
     170          36 :         if (ownerEntity)
     171             :         {
     172          27 :                 let pc = this.playerEntitiesCached.get(ownerEntity).get(propertyName);
     173          27 :                 if (!pc)
     174          13 :                         pc = this.playerEntitiesCached.get(ownerEntity).set(propertyName, new Set()).get(propertyName);
     175          27 :                 pc.add(entity);
     176          27 :                 newValue = this.FetchModifiedProperty(classesList, propertyName, newValue, ownerEntity);
     177             :         }
     178          36 :         newValue = this.Cache(classesList, propertyName, newValue, entity);
     179             : 
     180          36 :         return newValue;
     181             : };
     182             : 
     183             : /**
     184             :  * Alternative version of ApplyModifiers, applies to templates instead of entities.
     185             :  * Only needs to handle global modifiers.
     186             :  */
     187           3 : ModifiersManager.prototype.ApplyTemplateModifiers = function(propertyName, originalValue, template, player)
     188             : {
     189           7 :         if (!template || !template.Identity)
     190           0 :                 return originalValue;
     191             : 
     192           7 :         let cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
     193           7 :         return this.FetchModifiedProperty(GetIdentityClasses(template.Identity), propertyName, originalValue, cmpPlayerManager.GetPlayerByID(player));
     194             : };
     195             : 
     196             : /**
     197             :  * For efficiency in InvalidateCache, keep playerEntitiesCached updated.
     198             :  */
     199           3 : ModifiersManager.prototype.OnGlobalPlayerEntityChanged = function(msg)
     200             : {
     201          19 :         if (msg.to != INVALID_PLAYER && !this.playerEntitiesCached.has(msg.to))
     202          19 :                 this.playerEntitiesCached.set(msg.to, new Map());
     203             : 
     204          19 :         if (msg.from != INVALID_PLAYER && this.playerEntitiesCached.has(msg.from))
     205             :         {
     206           0 :                 this.playerEntitiesCached.get(msg.from).forEach(propName => this.InvalidateCache(propName, msg.from));
     207           0 :                 this.playerEntitiesCached.delete(msg.from);
     208             :         }
     209             : };
     210             : 
     211             : /**
     212             :  * Handle modifiers when an entity changes owner.
     213             :  * We do not retain the original modifiers for now.
     214             :  */
     215           3 : ModifiersManager.prototype.OnGlobalOwnershipChanged = function(msg)
     216             : {
     217           1 :         if (msg.to == INVALID_PLAYER)
     218           0 :                 return;
     219             : 
     220             :         // Invalidate all caches.
     221           1 :         for (let propName of this.cachedValues.keys())
     222           1 :                 this.InvalidateCache(propName, msg.entity);
     223             : 
     224           1 :         let owner = QueryOwnerEntityID(msg.entity);
     225           1 :         if (!owner)
     226           0 :                 return;
     227             : 
     228           1 :         let cmpIdentity = Engine.QueryInterface(msg.entity, IID_Identity);
     229           1 :         if (!cmpIdentity)
     230           0 :                 return;
     231             : 
     232           1 :         let classes = cmpIdentity.GetClassesList();
     233             : 
     234             :         // Warn entities that our values have changed.
     235             :         // Local modifiers will be added by the relevant components, so no need to check for them here.
     236           1 :         let modifiedComponents = {};
     237           1 :         let playerModifs = this.modifiersStorage.GetAllItems(owner);
     238           1 :         for (let propertyName in playerModifs)
     239             :         {
     240             :                 // We only need to find one one tech per component for a match.
     241           1 :                 let component = propertyName.split("/")[0];
     242             :                 // Only inform if the modifier actually applies to the entity as an optimisation.
     243             :                 // TODO: would it be better to call FetchModifiedProperty here and compare values?
     244           1 :                 playerModifs[propertyName].forEach(item => item.value.forEach(modif => {
     245           1 :                         if (!DoesModificationApply(modif, classes))
     246           0 :                                 return;
     247           1 :                         if (!modifiedComponents[component])
     248           1 :                                 modifiedComponents[component] = [];
     249           1 :                         modifiedComponents[component].push(propertyName);
     250             :                 }));
     251             :         }
     252             : 
     253           1 :         for (let component in modifiedComponents)
     254           1 :                 Engine.PostMessage(msg.entity, MT_ValueModification, { "entities": [msg.entity], "component": component, "valueNames": modifiedComponents[component] });
     255             : };
     256             : 
     257             : /**
     258             :  * The following functions simply proxy MultiKeyMap's interface.
     259             :  */
     260           3 : ModifiersManager.prototype.AddModifier = function(propName, ModifID, Modif, entity, stackable = false) {
     261          12 :         return this.modifiersStorage.AddItem(propName, ModifID, Modif, entity, stackable);
     262             : };
     263             : 
     264           3 : ModifiersManager.prototype.AddModifiers = function(ModifID, Modifs, entity, stackable = false) {
     265          13 :         return this.modifiersStorage.AddItems(ModifID, Modifs, entity, stackable);
     266             : };
     267             : 
     268           3 : ModifiersManager.prototype.RemoveModifier = function(propName, ModifID, entity, stackable = false) {
     269           7 :         return this.modifiersStorage.RemoveItem(propName, ModifID, entity, stackable);
     270             : };
     271             : 
     272           3 : ModifiersManager.prototype.RemoveAllModifiers = function(ModifID, entity, stackable = false) {
     273           4 :         return this.modifiersStorage.RemoveAllItems(ModifID, entity, stackable);
     274             : };
     275             : 
     276           3 : ModifiersManager.prototype.HasModifier = function(propName, ModifID, entity) {
     277           4 :         return this.modifiersStorage.HasItem(propName, ModifID, entity);
     278             : };
     279             : 
     280           3 : ModifiersManager.prototype.HasAnyModifier = function(ModifID, entity) {
     281           2 :         return this.modifiersStorage.HasAnyItem(ModifID, entity);
     282             : };
     283             : 
     284           3 : ModifiersManager.prototype.GetModifiers = function(propName, entity, stackable = false) {
     285           0 :         return this.modifiersStorage.GetItems(propName, entity, stackable);
     286             : };
     287             : 
     288           3 : ModifiersManager.prototype.GetAllModifiers = function(entity, stackable = false) {
     289           0 :         return this.modifiersStorage.GetAllItems(entity, stackable);
     290             : };
     291             : 
     292           3 : Engine.RegisterSystemComponentType(IID_ModifiersManager, "ModifiersManager", ModifiersManager);

Generated by: LCOV version 1.14