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