Core/Spells: Implemented personal summons (#19231)
[trinitycore] / src / server / game / Combat / ThreatManager.cpp
1 /*
2 * Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/>
3 * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "ThreatManager.h"
20 #include "Unit.h"
21 #include "Creature.h"
22 #include "Map.h"
23 #include "Player.h"
24 #include "ObjectAccessor.h"
25 #include "UnitEvents.h"
26 #include "SpellAuras.h"
27 #include "SpellMgr.h"
28 #include "TemporarySummon.h"
29
30 //==============================================================
31 //================= ThreatCalcHelper ===========================
32 //==============================================================
33
34 // The hatingUnit is not used yet
35 float ThreatCalcHelper::calcThreat(Unit* hatedUnit, Unit* /*hatingUnit*/, float threat, SpellSchoolMask schoolMask, SpellInfo const* threatSpell /*= nullptr*/)
36 {
37 if (threatSpell)
38 {
39 if (SpellThreatEntry const* threatEntry = sSpellMgr->GetSpellThreatEntry(threatSpell->Id))
40 if (threatEntry->pctMod != 1.0f)
41 threat *= threatEntry->pctMod;
42
43 // Energize is not affected by Mods
44 for (SpellEffectInfo const* effect : threatSpell->GetEffectsForDifficulty(hatedUnit->GetMap()->GetDifficultyID()))
45 if (effect && (effect->Effect == SPELL_EFFECT_ENERGIZE || effect->ApplyAuraName == SPELL_AURA_PERIODIC_ENERGIZE))
46 return threat;
47
48 if (Player* modOwner = hatedUnit->GetSpellModOwner())
49 modOwner->ApplySpellMod(threatSpell->Id, SPELLMOD_THREAT, threat);
50 }
51
52 return hatedUnit->ApplyTotalThreatModifier(threat, schoolMask);
53 }
54
55 bool ThreatCalcHelper::isValidProcess(Unit* hatedUnit, Unit* hatingUnit, SpellInfo const* threatSpell /*= nullptr*/)
56 {
57 //function deals with adding threat and adding players and pets into ThreatList
58 //mobs, NPCs, guards have ThreatList and HateOfflineList
59 //players and pets have only InHateListOf
60 //HateOfflineList is used co contain unattackable victims (in-flight, in-water, GM etc.)
61
62 if (!hatedUnit || !hatingUnit)
63 return false;
64
65 // not to self
66 if (hatedUnit == hatingUnit)
67 return false;
68
69 // not to GM
70 if (hatedUnit->GetTypeId() == TYPEID_PLAYER && hatedUnit->ToPlayer()->IsGameMaster())
71 return false;
72
73 // not to dead and not for dead
74 if (!hatedUnit->IsAlive() || !hatingUnit->IsAlive())
75 return false;
76
77 // not in same map or phase
78 if (!hatedUnit->IsInMap(hatingUnit) || !hatedUnit->IsInPhase(hatingUnit))
79 return false;
80
81 // spell not causing threat
82 if (threatSpell && threatSpell->HasAttribute(SPELL_ATTR1_NO_THREAT))
83 return false;
84
85 ASSERT(hatingUnit->GetTypeId() == TYPEID_UNIT);
86
87 return true;
88 }
89
90 //============================================================
91 //================= HostileReference ==========================
92 //============================================================
93
94 HostileReference::HostileReference(Unit* refUnit, ThreatManager* threatManager, float threat)
95 {
96 iThreat = threat;
97 iTempThreatModifier = 0.0f;
98 link(refUnit, threatManager);
99 iUnitGuid = refUnit->GetGUID();
100 iOnline = true;
101 iAccessible = true;
102 }
103
104 //============================================================
105 // Tell our refTo (target) object that we have a link
106 void HostileReference::targetObjectBuildLink()
107 {
108 getTarget()->addHatedBy(this);
109 }
110
111 //============================================================
112 // Tell our refTo (taget) object, that the link is cut
113 void HostileReference::targetObjectDestroyLink()
114 {
115 getTarget()->removeHatedBy(this);
116 }
117
118 //============================================================
119 // Tell our refFrom (source) object, that the link is cut (Target destroyed)
120
121 void HostileReference::sourceObjectDestroyLink()
122 {
123 setOnlineOfflineState(false);
124 }
125
126 //============================================================
127 // Inform the source, that the status of the reference changed
128
129 void HostileReference::fireStatusChanged(ThreatRefStatusChangeEvent& threatRefStatusChangeEvent)
130 {
131 if (GetSource())
132 GetSource()->processThreatEvent(&threatRefStatusChangeEvent);
133 }
134
135 //============================================================
136
137 void HostileReference::addThreat(float modThreat)
138 {
139 if (!modThreat)
140 return;
141
142 iThreat += modThreat;
143
144 // the threat is changed. Source and target unit have to be available
145 // if the link was cut before relink it again
146 if (!isOnline())
147 updateOnlineStatus();
148
149 ThreatRefStatusChangeEvent event(UEV_THREAT_REF_THREAT_CHANGE, this, modThreat);
150 fireStatusChanged(event);
151
152 if (isValid() && modThreat > 0.0f)
153 {
154 Unit* victimOwner = getTarget()->GetCharmerOrOwner();
155 if (victimOwner && victimOwner->IsAlive())
156 GetSource()->addThreat(victimOwner, 0.0f); // create a threat to the owner of a pet, if the pet attacks
157 }
158 }
159
160 void HostileReference::addThreatPercent(int32 percent)
161 {
162 addThreat(CalculatePct(iThreat, percent));
163 }
164
165 //============================================================
166 // check, if source can reach target and set the status
167
168 void HostileReference::updateOnlineStatus()
169 {
170 bool online = false;
171 bool accessible = false;
172
173 if (!isValid())
174 if (Unit* target = ObjectAccessor::GetUnit(*GetSourceUnit(), getUnitGuid()))
175 link(target, GetSource());
176
177 // only check for online status if
178 // ref is valid
179 // target is no player or not gamemaster
180 // target is not in flight
181 if (isValid()
182 && (getTarget()->GetTypeId() != TYPEID_PLAYER || !getTarget()->ToPlayer()->IsGameMaster())
183 && !getTarget()->HasUnitState(UNIT_STATE_IN_FLIGHT)
184 && getTarget()->IsInMap(GetSourceUnit())
185 && getTarget()->IsInPhase(GetSourceUnit())
186 )
187 {
188 Creature* creature = GetSourceUnit()->ToCreature();
189 online = getTarget()->isInAccessiblePlaceFor(creature);
190 if (!online)
191 {
192 if (creature->IsWithinCombatRange(getTarget(), creature->m_CombatDistance))
193 online = true; // not accessible but stays online
194 }
195 else
196 accessible = true;
197 }
198
199 setAccessibleState(accessible);
200 setOnlineOfflineState(online);
201 }
202
203 //============================================================
204 // set the status and fire the event on status change
205
206 void HostileReference::setOnlineOfflineState(bool isOnline)
207 {
208 if (iOnline != isOnline)
209 {
210 iOnline = isOnline;
211 if (!iOnline)
212 setAccessibleState(false); // if not online that not accessable as well
213
214 ThreatRefStatusChangeEvent event(UEV_THREAT_REF_ONLINE_STATUS, this);
215 fireStatusChanged(event);
216 }
217 }
218
219 //============================================================
220
221 void HostileReference::setAccessibleState(bool isAccessible)
222 {
223 if (iAccessible != isAccessible)
224 {
225 iAccessible = isAccessible;
226
227 ThreatRefStatusChangeEvent event(UEV_THREAT_REF_ACCESSIBLE_STATUS, this);
228 fireStatusChanged(event);
229 }
230 }
231
232 //============================================================
233 // prepare the reference for deleting
234 // this is called be the target
235
236 void HostileReference::removeReference()
237 {
238 invalidate();
239
240 ThreatRefStatusChangeEvent event(UEV_THREAT_REF_REMOVE_FROM_LIST, this);
241 fireStatusChanged(event);
242 }
243
244 //============================================================
245
246 Unit* HostileReference::GetSourceUnit()
247 {
248 return (GetSource()->GetOwner());
249 }
250
251 //============================================================
252 //================ ThreatContainer ===========================
253 //============================================================
254
255 void ThreatContainer::clearReferences()
256 {
257 for (ThreatContainer::StorageType::const_iterator i = iThreatList.begin(); i != iThreatList.end(); ++i)
258 {
259 (*i)->unlink();
260 delete (*i);
261 }
262
263 iThreatList.clear();
264 }
265
266 //============================================================
267 // Return the HostileReference of NULL, if not found
268 HostileReference* ThreatContainer::getReferenceByTarget(Unit* victim) const
269 {
270 if (!victim)
271 return NULL;
272
273 ObjectGuid guid = victim->GetGUID();
274 for (ThreatContainer::StorageType::const_iterator i = iThreatList.begin(); i != iThreatList.end(); ++i)
275 {
276 HostileReference* ref = (*i);
277 if (ref && ref->getUnitGuid() == guid)
278 return ref;
279 }
280
281 return NULL;
282 }
283
284 //============================================================
285 // Add the threat, if we find the reference
286
287 HostileReference* ThreatContainer::addThreat(Unit* victim, float threat)
288 {
289 HostileReference* ref = getReferenceByTarget(victim);
290 if (ref)
291 ref->addThreat(threat);
292 return ref;
293 }
294
295 //============================================================
296
297 void ThreatContainer::modifyThreatPercent(Unit* victim, int32 percent)
298 {
299 if (HostileReference* ref = getReferenceByTarget(victim))
300 ref->addThreatPercent(percent);
301 }
302
303 //============================================================
304 // Check if the list is dirty and sort if necessary
305
306 void ThreatContainer::update()
307 {
308 if (iDirty && iThreatList.size() > 1)
309 iThreatList.sort(Trinity::ThreatOrderPred());
310
311 iDirty = false;
312 }
313
314 //============================================================
315 // return the next best victim
316 // could be the current victim
317
318 HostileReference* ThreatContainer::selectNextVictim(Creature* attacker, HostileReference* currentVictim) const
319 {
320 HostileReference* currentRef = NULL;
321 bool found = false;
322 bool noPriorityTargetFound = false;
323
324 ThreatContainer::StorageType::const_iterator lastRef = iThreatList.end();
325 --lastRef;
326
327 for (ThreatContainer::StorageType::const_iterator iter = iThreatList.begin(); iter != iThreatList.end() && !found;)
328 {
329 currentRef = (*iter);
330
331 Unit* target = currentRef->getTarget();
332 ASSERT(target); // if the ref has status online the target must be there !
333
334 // some units are prefered in comparison to others
335 if (!noPriorityTargetFound && (target->IsImmunedToDamage(attacker->GetMeleeDamageSchoolMask()) || target->HasNegativeAuraWithInterruptFlag(AURA_INTERRUPT_FLAG_TAKE_DAMAGE)))
336 {
337 if (iter != lastRef)
338 {
339 // current victim is a second choice target, so don't compare threat with it below
340 if (currentRef == currentVictim)
341 currentVictim = nullptr;
342 ++iter;
343 continue;
344 }
345 else
346 {
347 // if we reached to this point, everyone in the threatlist is a second choice target. In such a situation the target with the highest threat should be attacked.
348 noPriorityTargetFound = true;
349 iter = iThreatList.begin();
350 continue;
351 }
352 }
353
354 if (attacker->CanCreatureAttack(target)) // skip non attackable currently targets
355 {
356 if (currentVictim) // select 1.3/1.1 better target in comparison current target
357 {
358 // list sorted and and we check current target, then this is best case
359 if (currentVictim == currentRef || currentRef->getThreat() <= 1.1f * currentVictim->getThreat())
360 {
361 if (currentVictim != currentRef && attacker->CanCreatureAttack(currentVictim->getTarget()))
362 currentRef = currentVictim; // for second case, if currentvictim is attackable
363
364 found = true;
365 break;
366 }
367
368 if (currentRef->getThreat() > 1.3f * currentVictim->getThreat() ||
369 (currentRef->getThreat() > 1.1f * currentVictim->getThreat() &&
370 attacker->IsWithinMeleeRange(target)))
371 { //implement 110% threat rule for targets in melee range
372 found = true; //and 130% rule for targets in ranged distances
373 break; //for selecting alive targets
374 }
375 }
376 else // select any
377 {
378 found = true;
379 break;
380 }
381 }
382 ++iter;
383 }
384 if (!found)
385 currentRef = NULL;
386
387 return currentRef;
388 }
389
390 //============================================================
391 //=================== ThreatManager ==========================
392 //============================================================
393
394 ThreatManager::ThreatManager(Unit* owner) : iCurrentVictim(NULL), iOwner(owner), iUpdateTimer(THREAT_UPDATE_INTERVAL) { }
395
396 //============================================================
397
398 void ThreatManager::clearReferences()
399 {
400 iThreatContainer.clearReferences();
401 iThreatOfflineContainer.clearReferences();
402 iCurrentVictim = NULL;
403 iUpdateTimer = THREAT_UPDATE_INTERVAL;
404 }
405
406 //============================================================
407
408 void ThreatManager::addThreat(Unit* victim, float threat, SpellSchoolMask schoolMask, SpellInfo const* threatSpell)
409 {
410 if (!ThreatCalcHelper::isValidProcess(victim, GetOwner(), threatSpell))
411 return;
412
413 doAddThreat(victim, ThreatCalcHelper::calcThreat(victim, iOwner, threat, schoolMask, threatSpell));
414 }
415
416 void ThreatManager::doAddThreat(Unit* victim, float threat)
417 {
418 uint32 redirectThreadPct = victim->GetRedirectThreatPercent();
419 Unit* redirectTarget = victim->GetRedirectThreatTarget();
420
421 // If victim is personnal spawn, redirect all aggro to summoner
422 if (TempSummon* tempSummonVictim = victim->ToTempSummon())
423 {
424 if (tempSummonVictim->IsVisibleBySummonerOnly())
425 {
426 // Personnal Spawns from same summoner can aggro each other
427 if (!GetOwner()->ToTempSummon() ||
428 !GetOwner()->ToTempSummon()->IsVisibleBySummonerOnly() ||
429 tempSummonVictim->GetSummonerGUID() != GetOwner()->ToTempSummon()->GetSummonerGUID())
430 {
431 redirectThreadPct = 100;
432 redirectTarget = tempSummonVictim->GetSummoner();
433 }
434 }
435 }
436
437 // must check > 0.0f, otherwise dead loop
438 if (threat > 0.0f && redirectThreadPct)
439 {
440 if (redirectTarget)
441 {
442 float redirectThreat = CalculatePct(threat, redirectThreadPct);
443 threat -= redirectThreat;
444 _addThreat(redirectTarget, redirectThreat);
445 }
446 }
447
448 _addThreat(victim, threat);
449 }
450
451 void ThreatManager::_addThreat(Unit* victim, float threat)
452 {
453 HostileReference* ref = iThreatContainer.addThreat(victim, threat);
454 // Ref is not in the online refs, search the offline refs next
455 if (!ref)
456 ref = iThreatOfflineContainer.addThreat(victim, threat);
457
458 if (!ref) // there was no ref => create a new one
459 {
460 bool isFirst = iThreatContainer.empty();
461 // threat has to be 0 here
462 HostileReference* hostileRef = new HostileReference(victim, this, 0);
463 iThreatContainer.addReference(hostileRef);
464 hostileRef->addThreat(threat); // now we add the real threat
465 if (victim->GetTypeId() == TYPEID_PLAYER && victim->ToPlayer()->IsGameMaster())
466 hostileRef->setOnlineOfflineState(false); // GM is always offline
467 else if (isFirst)
468 setCurrentVictim(hostileRef);
469 }
470 }
471
472 //============================================================
473
474 void ThreatManager::modifyThreatPercent(Unit* victim, int32 percent)
475 {
476 iThreatContainer.modifyThreatPercent(victim, percent);
477 }
478
479 //============================================================
480
481 Unit* ThreatManager::getHostilTarget()
482 {
483 iThreatContainer.update();
484 HostileReference* nextVictim = iThreatContainer.selectNextVictim(GetOwner()->ToCreature(), getCurrentVictim());
485 setCurrentVictim(nextVictim);
486 return getCurrentVictim() != NULL ? getCurrentVictim()->getTarget() : NULL;
487 }
488
489 //============================================================
490
491 float ThreatManager::getThreat(Unit* victim, bool alsoSearchOfflineList)
492 {
493 float threat = 0.0f;
494 HostileReference* ref = iThreatContainer.getReferenceByTarget(victim);
495 if (!ref && alsoSearchOfflineList)
496 ref = iThreatOfflineContainer.getReferenceByTarget(victim);
497 if (ref)
498 threat = ref->getThreat();
499 return threat;
500 }
501
502 //============================================================
503
504 void ThreatManager::tauntApply(Unit* taunter)
505 {
506 HostileReference* ref = iThreatContainer.getReferenceByTarget(taunter);
507 if (getCurrentVictim() && ref && (ref->getThreat() < getCurrentVictim()->getThreat()))
508 {
509 if (ref->getTempThreatModifier() == 0.0f) // Ok, temp threat is unused
510 ref->setTempThreat(getCurrentVictim()->getThreat());
511 }
512 }
513
514 //============================================================
515
516 void ThreatManager::tauntFadeOut(Unit* taunter)
517 {
518 HostileReference* ref = iThreatContainer.getReferenceByTarget(taunter);
519 if (ref)
520 ref->resetTempThreat();
521 }
522
523 //============================================================
524
525 void ThreatManager::setCurrentVictim(HostileReference* pHostileReference)
526 {
527 if (pHostileReference && pHostileReference != iCurrentVictim)
528 {
529 iOwner->SendChangeCurrentVictimOpcode(pHostileReference);
530 }
531 iCurrentVictim = pHostileReference;
532 }
533
534 //============================================================
535 // The hated unit is gone, dead or deleted
536 // return true, if the event is consumed
537
538 void ThreatManager::processThreatEvent(ThreatRefStatusChangeEvent* threatRefStatusChangeEvent)
539 {
540 threatRefStatusChangeEvent->setThreatManager(this); // now we can set the threat manager
541
542 HostileReference* hostilRef = threatRefStatusChangeEvent->getReference();
543
544 switch (threatRefStatusChangeEvent->getType())
545 {
546 case UEV_THREAT_REF_THREAT_CHANGE:
547 if ((getCurrentVictim() == hostilRef && threatRefStatusChangeEvent->getFValue()<0.0f) ||
548 (getCurrentVictim() != hostilRef && threatRefStatusChangeEvent->getFValue()>0.0f))
549 setDirty(true); // the order in the threat list might have changed
550 break;
551 case UEV_THREAT_REF_ONLINE_STATUS:
552 if (!hostilRef->isOnline())
553 {
554 if (hostilRef == getCurrentVictim())
555 {
556 setCurrentVictim(NULL);
557 setDirty(true);
558 }
559 iOwner->SendRemoveFromThreatListOpcode(hostilRef);
560 iThreatContainer.remove(hostilRef);
561 iThreatOfflineContainer.addReference(hostilRef);
562 }
563 else
564 {
565 if (getCurrentVictim() && hostilRef->getThreat() > (1.1f * getCurrentVictim()->getThreat()))
566 setDirty(true);
567 iThreatContainer.addReference(hostilRef);
568 iThreatOfflineContainer.remove(hostilRef);
569 }
570 break;
571 case UEV_THREAT_REF_REMOVE_FROM_LIST:
572 if (hostilRef == getCurrentVictim())
573 {
574 setCurrentVictim(NULL);
575 setDirty(true);
576 }
577 iOwner->SendRemoveFromThreatListOpcode(hostilRef);
578 if (hostilRef->isOnline())
579 iThreatContainer.remove(hostilRef);
580 else
581 iThreatOfflineContainer.remove(hostilRef);
582 break;
583 }
584 }
585
586 bool ThreatManager::isNeedUpdateToClient(uint32 time)
587 {
588 if (isThreatListEmpty())
589 return false;
590
591 if (time >= iUpdateTimer)
592 {
593 iUpdateTimer = THREAT_UPDATE_INTERVAL;
594 return true;
595 }
596 iUpdateTimer -= time;
597 return false;
598 }
599
600 // Reset all aggro without modifying the threatlist.
601 void ThreatManager::resetAllAggro()
602 {
603 ThreatContainer::StorageType &threatList = iThreatContainer.iThreatList;
604 if (threatList.empty())
605 return;
606
607 for (ThreatContainer::StorageType::iterator itr = threatList.begin(); itr != threatList.end(); ++itr)
608 (*itr)->setThreat(0);
609
610 setDirty(true);
611 }