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