source: ps/trunk/binaries/data/mods/public/simulation/components/Auras.js

Last change on this file was 27722, checked in by Freagarach, 13 months ago

Pull Diplomacy out of cmpPlayer.

Who says only players should be able to conduct diplomacy?
Also separation of concerns, more maintainable files.

Differential revision: https://code.wildfiregames.com/D4921
Comments by: @elexis, @Stan
Refs. #5894

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