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"
28 #include "TemporarySummon.h"
30 //==============================================================
31 //================= ThreatCalcHelper ===========================
32 //==============================================================
34 // The hatingUnit is not used yet
35 float ThreatCalcHelper::calcThreat(Unit
* hatedUnit
, Unit
* /*hatingUnit*/, float threat
, SpellSchoolMask schoolMask
, SpellInfo
const* threatSpell
/*= nullptr*/)
39 if (SpellThreatEntry
const* threatEntry
= sSpellMgr
->GetSpellThreatEntry(threatSpell
->Id
))
40 if (threatEntry
->pctMod
!= 1.0f
)
41 threat
*= threatEntry
->pctMod
;
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
))
48 if (Player
* modOwner
= hatedUnit
->GetSpellModOwner())
49 modOwner
->ApplySpellMod(threatSpell
->Id
, SPELLMOD_THREAT
, threat
);
52 return hatedUnit
->ApplyTotalThreatModifier(threat
, schoolMask
);
55 bool ThreatCalcHelper::isValidProcess(Unit
* hatedUnit
, Unit
* hatingUnit
, SpellInfo
const* threatSpell
/*= nullptr*/)
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.)
62 if (!hatedUnit
|| !hatingUnit
)
66 if (hatedUnit
== hatingUnit
)
70 if (hatedUnit
->GetTypeId() == TYPEID_PLAYER
&& hatedUnit
->ToPlayer()->IsGameMaster())
73 // not to dead and not for dead
74 if (!hatedUnit
->IsAlive() || !hatingUnit
->IsAlive())
77 // not in same map or phase
78 if (!hatedUnit
->IsInMap(hatingUnit
) || !hatedUnit
->IsInPhase(hatingUnit
))
81 // spell not causing threat
82 if (threatSpell
&& threatSpell
->HasAttribute(SPELL_ATTR1_NO_THREAT
))
85 ASSERT(hatingUnit
->GetTypeId() == TYPEID_UNIT
);
90 //============================================================
91 //================= HostileReference ==========================
92 //============================================================
94 HostileReference::HostileReference(Unit
* refUnit
, ThreatManager
* threatManager
, float threat
)
97 iTempThreatModifier
= 0.0f
;
98 link(refUnit
, threatManager
);
99 iUnitGuid
= refUnit
->GetGUID();
104 //============================================================
105 // Tell our refTo (target) object that we have a link
106 void HostileReference::targetObjectBuildLink()
108 getTarget()->addHatedBy(this);
111 //============================================================
112 // Tell our refTo (taget) object, that the link is cut
113 void HostileReference::targetObjectDestroyLink()
115 getTarget()->removeHatedBy(this);
118 //============================================================
119 // Tell our refFrom (source) object, that the link is cut (Target destroyed)
121 void HostileReference::sourceObjectDestroyLink()
123 setOnlineOfflineState(false);
126 //============================================================
127 // Inform the source, that the status of the reference changed
129 void HostileReference::fireStatusChanged(ThreatRefStatusChangeEvent
& threatRefStatusChangeEvent
)
132 GetSource()->processThreatEvent(&threatRefStatusChangeEvent
);
135 //============================================================
137 void HostileReference::addThreat(float modThreat
)
142 iThreat
+= modThreat
;
144 // the threat is changed. Source and target unit have to be available
145 // if the link was cut before relink it again
147 updateOnlineStatus();
149 ThreatRefStatusChangeEvent
event(UEV_THREAT_REF_THREAT_CHANGE
, this, modThreat
);
150 fireStatusChanged(event
);
152 if (isValid() && modThreat
> 0.0f
)
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
160 void HostileReference::addThreatPercent(int32 percent
)
162 addThreat(CalculatePct(iThreat
, percent
));
165 //============================================================
166 // check, if source can reach target and set the status
168 void HostileReference::updateOnlineStatus()
171 bool accessible
= false;
174 if (Unit
* target
= ObjectAccessor::GetUnit(*GetSourceUnit(), getUnitGuid()))
175 link(target
, GetSource());
177 // only check for online status if
179 // target is no player or not gamemaster
180 // target is not in flight
182 && (getTarget()->GetTypeId() != TYPEID_PLAYER
|| !getTarget()->ToPlayer()->IsGameMaster())
183 && !getTarget()->HasUnitState(UNIT_STATE_IN_FLIGHT
)
184 && getTarget()->IsInMap(GetSourceUnit())
185 && getTarget()->IsInPhase(GetSourceUnit())
188 Creature
* creature
= GetSourceUnit()->ToCreature();
189 online
= getTarget()->isInAccessiblePlaceFor(creature
);
192 if (creature
->IsWithinCombatRange(getTarget(), creature
->m_CombatDistance
))
193 online
= true; // not accessible but stays online
199 setAccessibleState(accessible
);
200 setOnlineOfflineState(online
);
203 //============================================================
204 // set the status and fire the event on status change
206 void HostileReference::setOnlineOfflineState(bool isOnline
)
208 if (iOnline
!= isOnline
)
212 setAccessibleState(false); // if not online that not accessable as well
214 ThreatRefStatusChangeEvent
event(UEV_THREAT_REF_ONLINE_STATUS
, this);
215 fireStatusChanged(event
);
219 //============================================================
221 void HostileReference::setAccessibleState(bool isAccessible
)
223 if (iAccessible
!= isAccessible
)
225 iAccessible
= isAccessible
;
227 ThreatRefStatusChangeEvent
event(UEV_THREAT_REF_ACCESSIBLE_STATUS
, this);
228 fireStatusChanged(event
);
232 //============================================================
233 // prepare the reference for deleting
234 // this is called be the target
236 void HostileReference::removeReference()
240 ThreatRefStatusChangeEvent
event(UEV_THREAT_REF_REMOVE_FROM_LIST
, this);
241 fireStatusChanged(event
);
244 //============================================================
246 Unit
* HostileReference::GetSourceUnit()
248 return (GetSource()->GetOwner());
251 //============================================================
252 //================ ThreatContainer ===========================
253 //============================================================
255 void ThreatContainer::clearReferences()
257 for (ThreatContainer::StorageType::const_iterator i
= iThreatList
.begin(); i
!= iThreatList
.end(); ++i
)
266 //============================================================
267 // Return the HostileReference of NULL, if not found
268 HostileReference
* ThreatContainer::getReferenceByTarget(Unit
* victim
) const
273 ObjectGuid guid
= victim
->GetGUID();
274 for (ThreatContainer::StorageType::const_iterator i
= iThreatList
.begin(); i
!= iThreatList
.end(); ++i
)
276 HostileReference
* ref
= (*i
);
277 if (ref
&& ref
->getUnitGuid() == guid
)
284 //============================================================
285 // Add the threat, if we find the reference
287 HostileReference
* ThreatContainer::addThreat(Unit
* victim
, float threat
)
289 HostileReference
* ref
= getReferenceByTarget(victim
);
291 ref
->addThreat(threat
);
295 //============================================================
297 void ThreatContainer::modifyThreatPercent(Unit
* victim
, int32 percent
)
299 if (HostileReference
* ref
= getReferenceByTarget(victim
))
300 ref
->addThreatPercent(percent
);
303 //============================================================
304 // Check if the list is dirty and sort if necessary
306 void ThreatContainer::update()
308 if (iDirty
&& iThreatList
.size() > 1)
309 iThreatList
.sort(Trinity::ThreatOrderPred());
314 //============================================================
315 // return the next best victim
316 // could be the current victim
318 HostileReference
* ThreatContainer::selectNextVictim(Creature
* attacker
, HostileReference
* currentVictim
) const
320 HostileReference
* currentRef
= NULL
;
322 bool noPriorityTargetFound
= false;
324 ThreatContainer::StorageType::const_iterator lastRef
= iThreatList
.end();
327 for (ThreatContainer::StorageType::const_iterator iter
= iThreatList
.begin(); iter
!= iThreatList
.end() && !found
;)
329 currentRef
= (*iter
);
331 Unit
* target
= currentRef
->getTarget();
332 ASSERT(target
); // if the ref has status online the target must be there !
334 // some units are prefered in comparison to others
335 if (!noPriorityTargetFound
&& (target
->IsImmunedToDamage(attacker
->GetMeleeDamageSchoolMask()) || target
->HasNegativeAuraWithInterruptFlag(AURA_INTERRUPT_FLAG_TAKE_DAMAGE
)))
339 // current victim is a second choice target, so don't compare threat with it below
340 if (currentRef
== currentVictim
)
341 currentVictim
= nullptr;
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();
354 if (attacker
->CanCreatureAttack(target
)) // skip non attackable currently targets
356 if (currentVictim
) // select 1.3/1.1 better target in comparison current target
358 // list sorted and and we check current target, then this is best case
359 if (currentVictim
== currentRef
|| currentRef
->getThreat() <= 1.1f
* currentVictim
->getThreat())
361 if (currentVictim
!= currentRef
&& attacker
->CanCreatureAttack(currentVictim
->getTarget()))
362 currentRef
= currentVictim
; // for second case, if currentvictim is attackable
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
390 //============================================================
391 //=================== ThreatManager ==========================
392 //============================================================
394 ThreatManager::ThreatManager(Unit
* owner
) : iCurrentVictim(NULL
), iOwner(owner
), iUpdateTimer(THREAT_UPDATE_INTERVAL
) { }
396 //============================================================
398 void ThreatManager::clearReferences()
400 iThreatContainer
.clearReferences();
401 iThreatOfflineContainer
.clearReferences();
402 iCurrentVictim
= NULL
;
403 iUpdateTimer
= THREAT_UPDATE_INTERVAL
;
406 //============================================================
408 void ThreatManager::addThreat(Unit
* victim
, float threat
, SpellSchoolMask schoolMask
, SpellInfo
const* threatSpell
)
410 if (!ThreatCalcHelper::isValidProcess(victim
, GetOwner(), threatSpell
))
413 doAddThreat(victim
, ThreatCalcHelper::calcThreat(victim
, iOwner
, threat
, schoolMask
, threatSpell
));
416 void ThreatManager::doAddThreat(Unit
* victim
, float threat
)
418 uint32 redirectThreadPct
= victim
->GetRedirectThreatPercent();
419 Unit
* redirectTarget
= victim
->GetRedirectThreatTarget();
421 // If victim is personnal spawn, redirect all aggro to summoner
422 if (TempSummon
* tempSummonVictim
= victim
->ToTempSummon())
424 if (tempSummonVictim
->IsVisibleBySummonerOnly())
426 // Personnal Spawns from same summoner can aggro each other
427 if (!GetOwner()->ToTempSummon() ||
428 !GetOwner()->ToTempSummon()->IsVisibleBySummonerOnly() ||
429 tempSummonVictim
->GetSummonerGUID() != GetOwner()->ToTempSummon()->GetSummonerGUID())
431 redirectThreadPct
= 100;
432 redirectTarget
= tempSummonVictim
->GetSummoner();
437 // must check > 0.0f, otherwise dead loop
438 if (threat
> 0.0f
&& redirectThreadPct
)
442 float redirectThreat
= CalculatePct(threat
, redirectThreadPct
);
443 threat
-= redirectThreat
;
444 _addThreat(redirectTarget
, redirectThreat
);
448 _addThreat(victim
, threat
);
451 void ThreatManager::_addThreat(Unit
* victim
, float threat
)
453 HostileReference
* ref
= iThreatContainer
.addThreat(victim
, threat
);
454 // Ref is not in the online refs, search the offline refs next
456 ref
= iThreatOfflineContainer
.addThreat(victim
, threat
);
458 if (!ref
) // there was no ref => create a new one
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
468 setCurrentVictim(hostileRef
);
472 //============================================================
474 void ThreatManager::modifyThreatPercent(Unit
* victim
, int32 percent
)
476 iThreatContainer
.modifyThreatPercent(victim
, percent
);
479 //============================================================
481 Unit
* ThreatManager::getHostilTarget()
483 iThreatContainer
.update();
484 HostileReference
* nextVictim
= iThreatContainer
.selectNextVictim(GetOwner()->ToCreature(), getCurrentVictim());
485 setCurrentVictim(nextVictim
);
486 return getCurrentVictim() != NULL
? getCurrentVictim()->getTarget() : NULL
;
489 //============================================================
491 float ThreatManager::getThreat(Unit
* victim
, bool alsoSearchOfflineList
)
494 HostileReference
* ref
= iThreatContainer
.getReferenceByTarget(victim
);
495 if (!ref
&& alsoSearchOfflineList
)
496 ref
= iThreatOfflineContainer
.getReferenceByTarget(victim
);
498 threat
= ref
->getThreat();
502 //============================================================
504 void ThreatManager::tauntApply(Unit
* taunter
)
506 HostileReference
* ref
= iThreatContainer
.getReferenceByTarget(taunter
);
507 if (getCurrentVictim() && ref
&& (ref
->getThreat() < getCurrentVictim()->getThreat()))
509 if (ref
->getTempThreatModifier() == 0.0f
) // Ok, temp threat is unused
510 ref
->setTempThreat(getCurrentVictim()->getThreat());
514 //============================================================
516 void ThreatManager::tauntFadeOut(Unit
* taunter
)
518 HostileReference
* ref
= iThreatContainer
.getReferenceByTarget(taunter
);
520 ref
->resetTempThreat();
523 //============================================================
525 void ThreatManager::setCurrentVictim(HostileReference
* pHostileReference
)
527 if (pHostileReference
&& pHostileReference
!= iCurrentVictim
)
529 iOwner
->SendChangeCurrentVictimOpcode(pHostileReference
);
531 iCurrentVictim
= pHostileReference
;
534 //============================================================
535 // The hated unit is gone, dead or deleted
536 // return true, if the event is consumed
538 void ThreatManager::processThreatEvent(ThreatRefStatusChangeEvent
* threatRefStatusChangeEvent
)
540 threatRefStatusChangeEvent
->setThreatManager(this); // now we can set the threat manager
542 HostileReference
* hostilRef
= threatRefStatusChangeEvent
->getReference();
544 switch (threatRefStatusChangeEvent
->getType())
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
551 case UEV_THREAT_REF_ONLINE_STATUS
:
552 if (!hostilRef
->isOnline())
554 if (hostilRef
== getCurrentVictim())
556 setCurrentVictim(NULL
);
559 iOwner
->SendRemoveFromThreatListOpcode(hostilRef
);
560 iThreatContainer
.remove(hostilRef
);
561 iThreatOfflineContainer
.addReference(hostilRef
);
565 if (getCurrentVictim() && hostilRef
->getThreat() > (1.1f
* getCurrentVictim()->getThreat()))
567 iThreatContainer
.addReference(hostilRef
);
568 iThreatOfflineContainer
.remove(hostilRef
);
571 case UEV_THREAT_REF_REMOVE_FROM_LIST
:
572 if (hostilRef
== getCurrentVictim())
574 setCurrentVictim(NULL
);
577 iOwner
->SendRemoveFromThreatListOpcode(hostilRef
);
578 if (hostilRef
->isOnline())
579 iThreatContainer
.remove(hostilRef
);
581 iThreatOfflineContainer
.remove(hostilRef
);
586 bool ThreatManager::isNeedUpdateToClient(uint32 time
)
588 if (isThreatListEmpty())
591 if (time
>= iUpdateTimer
)
593 iUpdateTimer
= THREAT_UPDATE_INTERVAL
;
596 iUpdateTimer
-= time
;
600 // Reset all aggro without modifying the threatlist.
601 void ThreatManager::resetAllAggro()
603 ThreatContainer::StorageType
&threatList
= iThreatContainer
.iThreatList
;
604 if (threatList
.empty())
607 for (ThreatContainer::StorageType::iterator itr
= threatList
.begin(); itr
!= threatList
.end(); ++itr
)
608 (*itr
)->setThreat(0);