2 * Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/>
3 * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
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.
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
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/>.
19 #include "ThreatManager.h"
24 #include "ObjectAccessor.h"
25 #include "UnitEvents.h"
26 #include "SpellAuras.h"
29 //==============================================================
30 //================= ThreatCalcHelper ===========================
31 //==============================================================
33 // The hatingUnit is not used yet
34 float ThreatCalcHelper::calcThreat(Unit
* hatedUnit
, Unit
* /*hatingUnit*/, float threat
, SpellSchoolMask schoolMask
, SpellInfo
const* threatSpell
/*= nullptr*/)
38 if (SpellThreatEntry
const* threatEntry
= sSpellMgr
->GetSpellThreatEntry(threatSpell
->Id
))
39 if (threatEntry
->pctMod
!= 1.0f
)
40 threat
*= threatEntry
->pctMod
;
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
))
47 if (Player
* modOwner
= hatedUnit
->GetSpellModOwner())
48 modOwner
->ApplySpellMod(threatSpell
->Id
, SPELLMOD_THREAT
, threat
);
51 return hatedUnit
->ApplyTotalThreatModifier(threat
, schoolMask
);
54 bool ThreatCalcHelper::isValidProcess(Unit
* hatedUnit
, Unit
* hatingUnit
, SpellInfo
const* threatSpell
/*= nullptr*/)
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.)
61 if (!hatedUnit
|| !hatingUnit
)
65 if (hatedUnit
== hatingUnit
)
69 if (hatedUnit
->GetTypeId() == TYPEID_PLAYER
&& hatedUnit
->ToPlayer()->IsGameMaster())
72 // not to dead and not for dead
73 if (!hatedUnit
->IsAlive() || !hatingUnit
->IsAlive())
76 // not in same map or phase
77 if (!hatedUnit
->IsInMap(hatingUnit
) || !hatedUnit
->IsInPhase(hatingUnit
))
80 // spell not causing threat
81 if (threatSpell
&& threatSpell
->HasAttribute(SPELL_ATTR1_NO_THREAT
))
84 ASSERT(hatingUnit
->GetTypeId() == TYPEID_UNIT
);
89 //============================================================
90 //================= HostileReference ==========================
91 //============================================================
93 HostileReference::HostileReference(Unit
* refUnit
, ThreatManager
* threatManager
, float threat
)
96 iTempThreatModifier
= 0.0f
;
97 link(refUnit
, threatManager
);
98 iUnitGuid
= refUnit
->GetGUID();
103 //============================================================
104 // Tell our refTo (target) object that we have a link
105 void HostileReference::targetObjectBuildLink()
107 getTarget()->addHatedBy(this);
110 //============================================================
111 // Tell our refTo (taget) object, that the link is cut
112 void HostileReference::targetObjectDestroyLink()
114 getTarget()->removeHatedBy(this);
117 //============================================================
118 // Tell our refFrom (source) object, that the link is cut (Target destroyed)
120 void HostileReference::sourceObjectDestroyLink()
122 setOnlineOfflineState(false);
125 //============================================================
126 // Inform the source, that the status of the reference changed
128 void HostileReference::fireStatusChanged(ThreatRefStatusChangeEvent
& threatRefStatusChangeEvent
)
131 GetSource()->processThreatEvent(&threatRefStatusChangeEvent
);
134 //============================================================
136 void HostileReference::addThreat(float modThreat
)
141 iThreat
+= modThreat
;
143 // the threat is changed. Source and target unit have to be available
144 // if the link was cut before relink it again
146 updateOnlineStatus();
148 ThreatRefStatusChangeEvent
event(UEV_THREAT_REF_THREAT_CHANGE
, this, modThreat
);
149 fireStatusChanged(event
);
151 if (isValid() && modThreat
> 0.0f
)
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
159 void HostileReference::addThreatPercent(int32 percent
)
161 addThreat(CalculatePct(iThreat
, percent
));
164 //============================================================
165 // check, if source can reach target and set the status
167 void HostileReference::updateOnlineStatus()
170 bool accessible
= false;
173 if (Unit
* target
= ObjectAccessor::GetUnit(*GetSourceUnit(), getUnitGuid()))
174 link(target
, GetSource());
176 // only check for online status if
178 // target is no player or not gamemaster
179 // target is not in flight
181 && (getTarget()->GetTypeId() != TYPEID_PLAYER
|| !getTarget()->ToPlayer()->IsGameMaster())
182 && !getTarget()->HasUnitState(UNIT_STATE_IN_FLIGHT
)
183 && getTarget()->IsInMap(GetSourceUnit())
184 && getTarget()->IsInPhase(GetSourceUnit())
187 Creature
* creature
= GetSourceUnit()->ToCreature();
188 online
= getTarget()->isInAccessiblePlaceFor(creature
);
191 if (creature
->IsWithinCombatRange(getTarget(), creature
->m_CombatDistance
))
192 online
= true; // not accessible but stays online
198 setAccessibleState(accessible
);
199 setOnlineOfflineState(online
);
202 //============================================================
203 // set the status and fire the event on status change
205 void HostileReference::setOnlineOfflineState(bool isOnline
)
207 if (iOnline
!= isOnline
)
211 setAccessibleState(false); // if not online that not accessable as well
213 ThreatRefStatusChangeEvent
event(UEV_THREAT_REF_ONLINE_STATUS
, this);
214 fireStatusChanged(event
);
218 //============================================================
220 void HostileReference::setAccessibleState(bool isAccessible
)
222 if (iAccessible
!= isAccessible
)
224 iAccessible
= isAccessible
;
226 ThreatRefStatusChangeEvent
event(UEV_THREAT_REF_ACCESSIBLE_STATUS
, this);
227 fireStatusChanged(event
);
231 //============================================================
232 // prepare the reference for deleting
233 // this is called be the target
235 void HostileReference::removeReference()
239 ThreatRefStatusChangeEvent
event(UEV_THREAT_REF_REMOVE_FROM_LIST
, this);
240 fireStatusChanged(event
);
243 //============================================================
245 Unit
* HostileReference::GetSourceUnit()
247 return (GetSource()->GetOwner());
250 //============================================================
251 //================ ThreatContainer ===========================
252 //============================================================
254 void ThreatContainer::clearReferences()
256 for (ThreatContainer::StorageType::const_iterator i
= iThreatList
.begin(); i
!= iThreatList
.end(); ++i
)
265 //============================================================
266 // Return the HostileReference of NULL, if not found
267 HostileReference
* ThreatContainer::getReferenceByTarget(Unit
* victim
) const
272 ObjectGuid guid
= victim
->GetGUID();
273 for (ThreatContainer::StorageType::const_iterator i
= iThreatList
.begin(); i
!= iThreatList
.end(); ++i
)
275 HostileReference
* ref
= (*i
);
276 if (ref
&& ref
->getUnitGuid() == guid
)
283 //============================================================
284 // Add the threat, if we find the reference
286 HostileReference
* ThreatContainer::addThreat(Unit
* victim
, float threat
)
288 HostileReference
* ref
= getReferenceByTarget(victim
);
290 ref
->addThreat(threat
);
294 //============================================================
296 void ThreatContainer::modifyThreatPercent(Unit
* victim
, int32 percent
)
298 if (HostileReference
* ref
= getReferenceByTarget(victim
))
299 ref
->addThreatPercent(percent
);
302 //============================================================
303 // Check if the list is dirty and sort if necessary
305 void ThreatContainer::update()
307 if (iDirty
&& iThreatList
.size() > 1)
308 iThreatList
.sort(Trinity::ThreatOrderPred());
313 //============================================================
314 // return the next best victim
315 // could be the current victim
317 HostileReference
* ThreatContainer::selectNextVictim(Creature
* attacker
, HostileReference
* currentVictim
) const
319 HostileReference
* currentRef
= NULL
;
321 bool noPriorityTargetFound
= false;
323 ThreatContainer::StorageType::const_iterator lastRef
= iThreatList
.end();
326 for (ThreatContainer::StorageType::const_iterator iter
= iThreatList
.begin(); iter
!= iThreatList
.end() && !found
;)
328 currentRef
= (*iter
);
330 Unit
* target
= currentRef
->getTarget();
331 ASSERT(target
); // if the ref has status online the target must be there !
333 // some units are prefered in comparison to others
334 if (!noPriorityTargetFound
&& (target
->IsImmunedToDamage(attacker
->GetMeleeDamageSchoolMask()) || target
->HasNegativeAuraWithInterruptFlag(AURA_INTERRUPT_FLAG_TAKE_DAMAGE
)))
338 // current victim is a second choice target, so don't compare threat with it below
339 if (currentRef
== currentVictim
)
340 currentVictim
= nullptr;
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();
353 if (attacker
->CanCreatureAttack(target
)) // skip non attackable currently targets
355 if (currentVictim
) // select 1.3/1.1 better target in comparison current target
357 // list sorted and and we check current target, then this is best case
358 if (currentVictim
== currentRef
|| currentRef
->getThreat() <= 1.1f
* currentVictim
->getThreat())
360 if (currentVictim
!= currentRef
&& attacker
->CanCreatureAttack(currentVictim
->getTarget()))
361 currentRef
= currentVictim
; // for second case, if currentvictim is attackable
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
389 //============================================================
390 //=================== ThreatManager ==========================
391 //============================================================
393 ThreatManager::ThreatManager(Unit
* owner
) : iCurrentVictim(NULL
), iOwner(owner
), iUpdateTimer(THREAT_UPDATE_INTERVAL
) { }
395 //============================================================
397 void ThreatManager::clearReferences()
399 iThreatContainer
.clearReferences();
400 iThreatOfflineContainer
.clearReferences();
401 iCurrentVictim
= NULL
;
402 iUpdateTimer
= THREAT_UPDATE_INTERVAL
;
405 //============================================================
407 void ThreatManager::addThreat(Unit
* victim
, float threat
, SpellSchoolMask schoolMask
, SpellInfo
const* threatSpell
)
409 if (!ThreatCalcHelper::isValidProcess(victim
, GetOwner(), threatSpell
))
412 doAddThreat(victim
, ThreatCalcHelper::calcThreat(victim
, iOwner
, threat
, schoolMask
, threatSpell
));
415 void ThreatManager::doAddThreat(Unit
* victim
, float threat
)
417 uint32 redirectThreadPct
= victim
->GetRedirectThreatPercent();
419 // must check > 0.0f, otherwise dead loop
420 if (threat
> 0.0f
&& redirectThreadPct
)
422 if (Unit
* redirectTarget
= victim
->GetRedirectThreatTarget())
424 float redirectThreat
= CalculatePct(threat
, redirectThreadPct
);
425 threat
-= redirectThreat
;
426 _addThreat(redirectTarget
, redirectThreat
);
430 _addThreat(victim
, threat
);
433 void ThreatManager::_addThreat(Unit
* victim
, float threat
)
435 HostileReference
* ref
= iThreatContainer
.addThreat(victim
, threat
);
436 // Ref is not in the online refs, search the offline refs next
438 ref
= iThreatOfflineContainer
.addThreat(victim
, threat
);
440 if (!ref
) // there was no ref => create a new one
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
450 setCurrentVictim(hostileRef
);
454 //============================================================
456 void ThreatManager::modifyThreatPercent(Unit
* victim
, int32 percent
)
458 iThreatContainer
.modifyThreatPercent(victim
, percent
);
461 //============================================================
463 Unit
* ThreatManager::getHostilTarget()
465 iThreatContainer
.update();
466 HostileReference
* nextVictim
= iThreatContainer
.selectNextVictim(GetOwner()->ToCreature(), getCurrentVictim());
467 setCurrentVictim(nextVictim
);
468 return getCurrentVictim() != NULL
? getCurrentVictim()->getTarget() : NULL
;
471 //============================================================
473 float ThreatManager::getThreat(Unit
* victim
, bool alsoSearchOfflineList
)
476 HostileReference
* ref
= iThreatContainer
.getReferenceByTarget(victim
);
477 if (!ref
&& alsoSearchOfflineList
)
478 ref
= iThreatOfflineContainer
.getReferenceByTarget(victim
);
480 threat
= ref
->getThreat();
484 //============================================================
486 void ThreatManager::tauntApply(Unit
* taunter
)
488 HostileReference
* ref
= iThreatContainer
.getReferenceByTarget(taunter
);
489 if (getCurrentVictim() && ref
&& (ref
->getThreat() < getCurrentVictim()->getThreat()))
491 if (ref
->getTempThreatModifier() == 0.0f
) // Ok, temp threat is unused
492 ref
->setTempThreat(getCurrentVictim()->getThreat());
496 //============================================================
498 void ThreatManager::tauntFadeOut(Unit
* taunter
)
500 HostileReference
* ref
= iThreatContainer
.getReferenceByTarget(taunter
);
502 ref
->resetTempThreat();
505 //============================================================
507 void ThreatManager::setCurrentVictim(HostileReference
* pHostileReference
)
509 if (pHostileReference
&& pHostileReference
!= iCurrentVictim
)
511 iOwner
->SendChangeCurrentVictimOpcode(pHostileReference
);
513 iCurrentVictim
= pHostileReference
;
516 //============================================================
517 // The hated unit is gone, dead or deleted
518 // return true, if the event is consumed
520 void ThreatManager::processThreatEvent(ThreatRefStatusChangeEvent
* threatRefStatusChangeEvent
)
522 threatRefStatusChangeEvent
->setThreatManager(this); // now we can set the threat manager
524 HostileReference
* hostilRef
= threatRefStatusChangeEvent
->getReference();
526 switch (threatRefStatusChangeEvent
->getType())
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
533 case UEV_THREAT_REF_ONLINE_STATUS
:
534 if (!hostilRef
->isOnline())
536 if (hostilRef
== getCurrentVictim())
538 setCurrentVictim(NULL
);
541 iOwner
->SendRemoveFromThreatListOpcode(hostilRef
);
542 iThreatContainer
.remove(hostilRef
);
543 iThreatOfflineContainer
.addReference(hostilRef
);
547 if (getCurrentVictim() && hostilRef
->getThreat() > (1.1f
* getCurrentVictim()->getThreat()))
549 iThreatContainer
.addReference(hostilRef
);
550 iThreatOfflineContainer
.remove(hostilRef
);
553 case UEV_THREAT_REF_REMOVE_FROM_LIST
:
554 if (hostilRef
== getCurrentVictim())
556 setCurrentVictim(NULL
);
559 iOwner
->SendRemoveFromThreatListOpcode(hostilRef
);
560 if (hostilRef
->isOnline())
561 iThreatContainer
.remove(hostilRef
);
563 iThreatOfflineContainer
.remove(hostilRef
);
568 bool ThreatManager::isNeedUpdateToClient(uint32 time
)
570 if (isThreatListEmpty())
573 if (time
>= iUpdateTimer
)
575 iUpdateTimer
= THREAT_UPDATE_INTERVAL
;
578 iUpdateTimer
-= time
;
582 // Reset all aggro without modifying the threatlist.
583 void ThreatManager::resetAllAggro()
585 ThreatContainer::StorageType
&threatList
= iThreatContainer
.iThreatList
;
586 if (threatList
.empty())
589 for (ThreatContainer::StorageType::iterator itr
= threatList
.begin(); itr
!= threatList
.end(); ++itr
)
590 (*itr
)->setThreat(0);