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);
|