TrinityCore
ThreatManager.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "ThreatManager.h"
19#include "Creature.h"
20#include "CombatPackets.h"
21#include "CreatureAI.h"
22#include "CreatureGroups.h"
23#include "MapUtils.h"
24#include "MotionMaster.h"
25#include "ObjectAccessor.h"
26#include "Player.h"
27#include "SpellAuraEffects.h"
28#include "SpellMgr.h"
29#include "TemporarySummon.h"
30#include <boost/heap/fibonacci_heap.hpp>
31
33
34class ThreatManager::Heap : public boost::heap::fibonacci_heap<ThreatReference const*, boost::heap::compare<CompareThreatLessThan>>
35{
36};
37
39{
40 if (amount == 0.0f)
41 return;
42 _baseAmount = std::max<float>(_baseAmount + amount, 0.0f);
43 if (amount > 0.0f)
45 else
48}
49
51{
52 if (factor == 1.0f)
53 return;
54 _baseAmount *= factor;
55 if (factor > 1.0f)
57 else
60}
61
63{
64 bool const shouldBeOffline = ShouldBeOffline();
65 if (shouldBeOffline == IsOffline())
66 return;
67
68 if (shouldBeOffline)
69 {
73 }
74 else
75 {
79 }
80}
81
82/*static*/ bool ThreatReference::FlagsAllowFighting(Unit const* a, Unit const* b)
83{
84 if (a->GetTypeId() == TYPEID_UNIT && a->ToCreature()->IsTrigger())
85 return false;
87 {
89 return false;
90 }
91 else
92 {
94 return false;
95 }
96 return true;
97}
98
100{
102 return true;
104 return true;
106 return true;
107 return false;
108}
109
111{
112 if (IsTaunting()) // a taunting victim can never be suppressed
113 return false;
115 return true;
117 return true;
119 return true;
120 return false;
121}
122
124{
125 // Check for SPELL_AURA_MOD_DETAUNT (applied from owner to victim)
126 if (state < TAUNT_STATE_TAUNT && _victim->HasAuraTypeWithCaster(SPELL_AURA_MOD_DETAUNT, _owner->GetGUID()))
127 state = TAUNT_STATE_DETAUNT;
128
129 if (state == _taunted)
130 return;
131
132 std::swap(state, _taunted);
133
134 if (_taunted < state)
136 else
138
139 _mgr._needClientUpdate = true;
140}
141
143{
144 _mgr.ClearThreat(this);
145}
146
148{
151 delete this;
152}
153
155{
156public:
157 explicit ThreatReferenceImpl(ThreatManager* mgr, Unit* victim) : ThreatReference(mgr, victim) { }
158
159 ThreatManager::Heap::handle_type _handle;
160};
161
163{
164 _mgr._sortedThreatList->increase(static_cast<ThreatReferenceImpl*>(this)->_handle);
165}
166
168{
169 _mgr._sortedThreatList->decrease(static_cast<ThreatReferenceImpl*>(this)->_handle);
170}
171
172/*static*/ bool ThreatManager::CanHaveThreatList(Unit const* who)
173{
174 Creature const* cWho = who->ToCreature();
175 // only creatures can have threat list
176 if (!cWho)
177 return false;
178
179 // pets, totems and triggers cannot have threat list
180 if (cWho->IsPet() || cWho->IsTotem() || cWho->IsTrigger())
181 return false;
182
183 // summons cannot have a threat list if they were summoned by a player
185 if (TempSummon const* tWho = cWho->ToTempSummon())
186 if (tWho->GetSummonerGUID().IsPlayer())
187 return false;
188
189 return true;
190}
191
192ThreatManager::ThreatManager(Unit* owner) : _owner(owner), _ownerCanHaveThreatList(false), _needClientUpdate(false), _updateTimer(THREAT_UPDATE_INTERVAL),
193 _sortedThreatList(std::make_unique<Heap>()), _currentVictimRef(nullptr), _fixateRef(nullptr)
194{
195 for (int8 i = 0; i < MAX_SPELL_SCHOOL; ++i)
196 _singleSchoolModifiers[i] = 1.0f;
197}
198
200{
201 ASSERT(_myThreatListEntries.empty(), "ThreatManager::~ThreatManager - %s: we still have %zu things threatening us, one of them is %s.", _owner->GetGUID().ToString().c_str(), _myThreatListEntries.size(), _myThreatListEntries.begin()->first.ToString().c_str());
202 ASSERT(_sortedThreatList->empty(), "ThreatManager::~ThreatManager - %s: we still have %zu things threatening us, one of them is %s.", _owner->GetGUID().ToString().c_str(), _sortedThreatList->size(), (*_sortedThreatList->begin())->GetVictim()->GetGUID().ToString().c_str());
203 ASSERT(_threatenedByMe.empty(), "ThreatManager::~ThreatManager - %s: we are still threatening %zu things, one of them is %s.", _owner->GetGUID().ToString().c_str(), _threatenedByMe.size(), _threatenedByMe.begin()->first.ToString().c_str());
204}
205
207{
209}
210
212{
213 if (!CanHaveThreatList() || IsThreatListEmpty(true))
214 return;
215 if (_updateTimer <= tdiff)
216 {
217 UpdateVictim();
219 }
220 else
221 _updateTimer -= tdiff;
222}
223
225{
227 UpdateVictim();
229 return _currentVictimRef ? _currentVictimRef->GetVictim() : nullptr;
230}
231
233{
236 return nullptr;
237}
238
240{
241 for (ThreatReference const* ref : *_sortedThreatList)
242 if (!ref->IsOffline())
243 return ref->GetVictim();
244 return nullptr;
245}
246
247bool ThreatManager::IsThreatListEmpty(bool includeOffline) const
248{
249 if (includeOffline)
250 return _sortedThreatList->empty();
251 for (ThreatReference const* ref : *_sortedThreatList)
252 if (ref->IsAvailable())
253 return false;
254 return true;
255}
256
257bool ThreatManager::IsThreatenedBy(ObjectGuid const& who, bool includeOffline) const
258{
259 auto it = _myThreatListEntries.find(who);
260 if (it == _myThreatListEntries.end())
261 return false;
262 return (includeOffline || it->second->IsAvailable());
263}
264bool ThreatManager::IsThreatenedBy(Unit const* who, bool includeOffline) const { return IsThreatenedBy(who->GetGUID(), includeOffline); }
265
266float ThreatManager::GetThreat(Unit const* who, bool includeOffline) const
267{
268 auto it = _myThreatListEntries.find(who->GetGUID());
269 if (it == _myThreatListEntries.end())
270 return 0.0f;
271 return (includeOffline || it->second->IsAvailable()) ? it->second->GetThreat() : 0.0f;
272}
273
275{
276 return _sortedThreatList->size();
277}
278
280{
281 auto itr = _myThreatListEntries.begin();
282 auto end = _myThreatListEntries.end();
283 std::function<ThreatReference const* ()> generator = [itr, end]() mutable -> ThreatReference const*
284 {
285 if (itr == end)
286 return nullptr;
287
288 return (itr++)->second;
289 };
290 return { ThreatListIterator{ std::move(generator) }, nullptr };
291}
292
294{
295 auto itr = _sortedThreatList->ordered_begin();
296 auto end = _sortedThreatList->ordered_end();
297 std::function<ThreatReference const* ()> generator = [itr, end]() mutable -> ThreatReference const*
298 {
299 if (itr == end)
300 return nullptr;
301
302 return *(itr++);
303 };
304 return { ThreatListIterator{ std::move(generator) }, nullptr };
305}
306
307std::vector<ThreatReference*> ThreatManager::GetModifiableThreatList()
308{
309 std::vector<ThreatReference*> list;
310 list.reserve(_myThreatListEntries.size());
311 for (auto it = _sortedThreatList->ordered_begin(), end = _sortedThreatList->ordered_end(); it != end; ++it)
312 list.push_back(const_cast<ThreatReference*>(*it));
313 return list;
314}
315
316bool ThreatManager::IsThreateningAnyone(bool includeOffline) const
317{
318 if (includeOffline)
319 return !_threatenedByMe.empty();
320 for (auto const& pair : _threatenedByMe)
321 if (pair.second->IsAvailable())
322 return true;
323 return false;
324}
325
326bool ThreatManager::IsThreateningTo(ObjectGuid const& who, bool includeOffline) const
327{
328 auto it = _threatenedByMe.find(who);
329 if (it == _threatenedByMe.end())
330 return false;
331 return (includeOffline || it->second->IsAvailable());
332}
333bool ThreatManager::IsThreateningTo(Unit const* who, bool includeOffline) const { return IsThreateningTo(who->GetGUID(), includeOffline); }
334
336{
337 for (auto const& pair : _threatenedByMe)
338 {
339 bool const shouldBeSuppressed = pair.second->ShouldBeSuppressed();
340 if (pair.second->IsOnline() && shouldBeSuppressed)
341 {
342 pair.second->_online = ThreatReference::ONLINE_STATE_SUPPRESSED;
343 pair.second->HeapNotifyDecreased();
344 }
345 else if (canExpire && pair.second->IsSuppressed() && !shouldBeSuppressed)
346 {
347 pair.second->_online = ThreatReference::ONLINE_STATE_ONLINE;
348 pair.second->HeapNotifyIncreased();
349 }
350 }
351}
352
353void ThreatManager::AddThreat(Unit* target, float amount, SpellInfo const* spell, bool ignoreModifiers, bool ignoreRedirects)
354{
355 // step 1: we can shortcut if the spell has one of the NO_THREAT attrs set - nothing will happen
356 if (spell)
357 {
359 return;
361 return;
362 }
363
364 // while riding a vehicle, all threat goes to the vehicle, not the pilot
365 if (Unit* vehicle = target->GetVehicleBase())
366 {
367 AddThreat(vehicle, amount, spell, ignoreModifiers, ignoreRedirects);
368 if (target->HasUnitTypeMask(UNIT_MASK_ACCESSORY)) // accessories are fully treated as components of the parent and cannot have threat
369 return;
370 amount = 0.0f;
371 }
372
373 // If victim is personal spawn, redirect all aggro to summoner
374 if (target->IsPrivateObject() && (!GetOwner()->IsPrivateObject() || !GetOwner()->CheckPrivateObjectOwnerVisibility(target)))
375 {
376 if (Unit* privateObjectOwner = ObjectAccessor::GetUnit(*GetOwner(), target->GetPrivateObjectOwner()))
377 {
378 AddThreat(privateObjectOwner, amount, spell, ignoreModifiers, ignoreRedirects);
379 amount = 0.0f;
380 }
381 }
382
383 // if we cannot actually have a threat list, we instead just set combat state and avoid creating threat refs altogether
384 if (!CanHaveThreatList())
385 {
386 CombatManager& combatMgr = _owner->GetCombatManager();
387 if (!combatMgr.SetInCombatWith(target))
388 return;
389 // traverse redirects and put them in combat, too
390 for (auto const& pair : target->GetThreatManager()._redirectInfo)
391 if (!combatMgr.IsInCombatWith(pair.first))
392 if (Unit* redirTarget = ObjectAccessor::GetUnit(*_owner, pair.first))
393 combatMgr.SetInCombatWith(redirTarget);
394 return;
395 }
396
397 // apply threat modifiers to the amount
398 if (!ignoreModifiers)
399 amount = CalculateModifiedThreat(amount, target, spell);
400
401 // if we're increasing threat, send some/all of it to redirection targets instead if applicable
402 if (!ignoreRedirects && amount > 0.0f)
403 {
404 auto const& redirInfo = target->GetThreatManager()._redirectInfo;
405 if (!redirInfo.empty())
406 {
407 float const origAmount = amount;
408 // intentional iteration by index - there's a nested AddThreat call further down that might cause AI calls which might modify redirect info through spells
409 for (size_t i = 0; i < redirInfo.size(); ++i)
410 {
411 auto const pair = redirInfo[i]; // (victim,pct)
412 Unit* redirTarget = nullptr;
413 auto it = _myThreatListEntries.find(pair.first); // try to look it up in our threat list first (faster)
414 if (it != _myThreatListEntries.end())
415 redirTarget = it->second->_victim;
416 else
417 redirTarget = ObjectAccessor::GetUnit(*_owner, pair.first);
418
419 if (redirTarget)
420 {
421 float amountRedirected = CalculatePct(origAmount, pair.second);
422 AddThreat(redirTarget, amountRedirected, spell, true, true);
423 amount -= amountRedirected;
424 }
425 }
426 }
427 }
428
429 // ensure we're in combat (threat implies combat!)
430 if (!_owner->GetCombatManager().SetInCombatWith(target)) // if this returns false, we're not actually in combat, and thus cannot have threat!
431 return; // typical causes: bad scripts trying to add threat to GMs, dead targets etc
432
433 // ok, now we actually apply threat
434 // check if we already have an entry - if we do, just increase threat for that entry and we're done
435 auto it = _myThreatListEntries.find(target->GetGUID());
436 if (it != _myThreatListEntries.end())
437 {
438 ThreatReference* const ref = it->second;
439 // SUPPRESSED threat states don't go back to ONLINE until threat is caused by them (retail behavior)
441 if (!ref->ShouldBeSuppressed())
442 {
444 ref->HeapNotifyIncreased();
445 }
446
447 if (ref->IsOnline())
448 ref->AddThreat(amount);
449 return;
450 }
451
452 // ok, we're now in combat - create the threat list reference and push it to the respective managers
453 ThreatReference* ref = new ThreatReferenceImpl(this, target);
454 PutThreatListRef(target->GetGUID(), ref);
456
457 ref->UpdateOffline();
458 if (ref->IsOnline()) // we only add the threat if the ref is currently available
459 ref->AddThreat(amount);
460
462 UpdateVictim();
463 else
465}
466
467void ThreatManager::ScaleThreat(Unit* target, float factor)
468{
469 auto it = _myThreatListEntries.find(target->GetGUID());
470 if (it != _myThreatListEntries.end())
471 it->second->ScaleThreat(std::max<float>(factor,0.0f));
472}
473
475{
476 if (_sortedThreatList->empty())
477 return;
478
479 auto it = _sortedThreatList->ordered_begin(), end = _sortedThreatList->ordered_end();
480 ThreatReference const* highest = *it;
481 if (!highest->IsAvailable())
482 return;
483
484 if (highest->IsTaunting() && ((++it) != end)) // might need to skip this - max threat could be the preceding element (there is only one taunt element)
485 {
486 ThreatReference const* a = *it;
487 if (a->IsAvailable() && a->GetThreat() > highest->GetThreat())
488 highest = a;
489 }
490
491 AddThreat(target, highest->GetThreat() - GetThreat(target, true), nullptr, true, true);
492}
493
495{
497
498 uint32 tauntPriority = 0; // lowest is highest
499 std::unordered_map<ObjectGuid, uint32> tauntStates;
500 // Only the last taunt effect applied by something still on our threat list is considered
501 for (AuraEffect const* tauntEffect : tauntEffects)
502 tauntStates[tauntEffect->GetCasterGUID()] = ++tauntPriority;
503
504 for (auto const& pair : _myThreatListEntries)
505 {
506 auto it = tauntStates.find(pair.first);
507 if (it != tauntStates.end())
508 pair.second->UpdateTauntState(ThreatReference::TauntState(ThreatReference::TAUNT_STATE_TAUNT + tauntStates.size() - it->second));
509 else
510 pair.second->UpdateTauntState();
511 }
512
513 // taunt aura update also re-evaluates all suppressed states (retail behavior)
514 EvaluateSuppressed(true);
515}
516
518{
519 for (auto const& pair : _myThreatListEntries)
520 pair.second->ScaleThreat(0.0f);
521}
522
524{
525 auto it = _myThreatListEntries.find(target->GetGUID());
526 if (it != _myThreatListEntries.end())
527 ClearThreat(it->second);
528}
529
531{
533 ref->UnregisterAndFree();
535 UpdateVictim();
536}
537
539{
540 if (!_myThreatListEntries.empty())
541 {
543 do
544 _myThreatListEntries.begin()->second->UnregisterAndFree();
545 while (!_myThreatListEntries.empty());
546 }
547}
548
550{
551 if (target)
552 {
553 auto it = _myThreatListEntries.find(target->GetGUID());
554 if (it != _myThreatListEntries.end())
555 {
556 _fixateRef = it->second;
557 return;
558 }
559 }
560 _fixateRef = nullptr;
561}
562
564{
565 if (_fixateRef)
566 return _fixateRef->GetVictim();
567 else
568 return nullptr;
569}
570
572{
573 ThreatReference const* const newVictim = ReselectVictim();
574 bool const newHighest = newVictim && (newVictim != _currentVictimRef);
575
576 _currentVictimRef = newVictim;
577 if (newHighest || _needClientUpdate)
578 {
579 SendThreatListToClients(newHighest);
580 _needClientUpdate = false;
581 }
582
584}
585
587{
588 if (_sortedThreatList->empty())
589 return nullptr;
590
591 for (auto const& pair : _myThreatListEntries)
592 pair.second->UpdateOffline(); // AI notifies are processed in ::UpdateVictim caller
593
594 // fixated target is always preferred
596 return _fixateRef;
597
598 ThreatReference const* oldVictimRef = _currentVictimRef;
599 if (oldVictimRef && oldVictimRef->IsOffline())
600 oldVictimRef = nullptr;
601 // in 99% of cases - we won't need to actually look at anything beyond the first element
602 ThreatReference const* highest = _sortedThreatList->top();
603 // if the highest reference is offline, the entire list is offline, and we indicate this
604 if (!highest->IsAvailable())
605 return nullptr;
606 // if we have no old victim, or old victim is still highest, then highest is our target and we're done
607 if (!oldVictimRef || highest == oldVictimRef)
608 return highest;
609 // if highest threat doesn't break 110% of old victim, nothing below it is going to do so either; new victim = old victim and done
610 if (!ThreatManager::CompareReferencesLT(oldVictimRef, highest, 1.1f))
611 return oldVictimRef;
612 // if highest threat breaks 130%, it's our new target regardless of range (and we're done)
613 if (ThreatManager::CompareReferencesLT(oldVictimRef, highest, 1.3f))
614 return highest;
615 // if it doesn't break 130%, we need to check if it's melee - if yes, it breaks 110% (we checked earlier) and is our new target
616 if (_owner->IsWithinMeleeRange(highest->_victim))
617 return highest;
618 // If we get here, highest threat is ranged, but below 130% of current - there might be a melee that breaks 110% below us somewhere, so now we need to actually look at the next highest element
619 // luckily, this is a heap, so getting the next highest element is O(log n), and we're just gonna do that repeatedly until we've seen enough targets (or find a target)
620 auto it = _sortedThreatList->ordered_begin(), end = _sortedThreatList->ordered_end();
621 while (it != end)
622 {
623 ThreatReference const* next = *it;
624 // if we've found current victim, we're done (nothing above is higher, and nothing below can be higher)
625 if (next == oldVictimRef)
626 return next;
627 // if next isn't above 110% threat, then nothing below it can be either - we're done, old victim stays
628 if (!ThreatManager::CompareReferencesLT(oldVictimRef, next, 1.1f))
629 return oldVictimRef;
630 // if next is melee, he's above 110% and our new victim
632 return next;
633 // otherwise the next highest target may still be a melee above 110% and we need to look further
634 ++it;
635 }
636 // we should have found the old victim at some point in the loop above, so execution should never get to this point
637 ABORT_MSG("Current victim not found in sorted threat list even though it has a reference - manager desync!");
638 return nullptr;
639}
640
642{
644 std::vector<ObjectGuid> v(std::move(_needsAIUpdate)); // _needsAIUpdate is now empty in case this triggers a recursive call
645 if (!ai)
646 return;
647 for (ObjectGuid const& guid : v)
649 ai->JustStartedThreateningMe(ref->GetVictim());
650}
651
652// returns true if a is LOWER on the threat list than b
653/*static*/ bool ThreatManager::CompareReferencesLT(ThreatReference const* a, ThreatReference const* b, float aWeight)
654{
655 if (a->_online != b->_online) // online state precedence (ONLINE > SUPPRESSED > OFFLINE)
656 return a->_online < b->_online;
657 if (a->_taunted != b->_taunted) // taunt state precedence (TAUNT > NONE > DETAUNT)
658 return a->_taunted < b->_taunted;
659 return (a->GetThreat()*aWeight < b->GetThreat());
660}
661
662/*static*/ float ThreatManager::CalculateModifiedThreat(float threat, Unit const* victim, SpellInfo const* spell)
663{
664 // modifiers by spell
665 if (spell)
666 {
667 if (SpellThreatEntry const* threatEntry = sSpellMgr->GetSpellThreatEntry(spell->Id))
668 if (threatEntry->pctMod != 1.0f) // flat/AP modifiers handled in Spell::HandleThreatSpells
669 threat *= threatEntry->pctMod;
670
671 if (Player* modOwner = victim->GetSpellModOwner())
672 modOwner->ApplySpellMod(spell, SpellModOp::Hate, threat);
673 }
674
675 // modifiers by effect school
676 ThreatManager const& victimMgr = victim->GetThreatManager();
677 SpellSchoolMask const mask = spell ? spell->GetSchoolMask() : SPELL_SCHOOL_MASK_NORMAL;
678 switch (mask)
679 {
681 threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_NORMAL];
682 break;
684 threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_HOLY];
685 break;
687 threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_FIRE];
688 break;
690 threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_NATURE];
691 break;
693 threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_FROST];
694 break;
696 threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_SHADOW];
697 break;
699 threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_ARCANE];
700 break;
701 default:
702 {
703 auto it = victimMgr._multiSchoolModifiers.find(mask);
704 if (it != victimMgr._multiSchoolModifiers.end())
705 {
706 threat *= it->second;
707 break;
708 }
710 victimMgr._multiSchoolModifiers[mask] = mod;
711 threat *= mod;
712 break;
713 }
714 }
715 return threat;
716}
717
718void ThreatManager::ForwardThreatForAssistingMe(Unit* assistant, float baseAmount, SpellInfo const* spell, bool ignoreModifiers)
719{
720 if (spell && (spell->HasAttribute(SPELL_ATTR1_NO_THREAT) || spell->HasAttribute(SPELL_ATTR4_NO_HELPFUL_THREAT))) // shortcut, none of the calls would do anything
721 return;
722 if (_threatenedByMe.empty())
723 return;
724
725 std::vector<Creature*> canBeThreatened, cannotBeThreatened;
726 for (auto const& pair : _threatenedByMe)
727 {
728 Creature* owner = pair.second->GetOwner();
730 canBeThreatened.push_back(owner);
731 else
732 cannotBeThreatened.push_back(owner);
733 }
734
735 if (!canBeThreatened.empty()) // targets under CC cannot gain assist threat - split evenly among the rest
736 {
737 float const perTarget = baseAmount / canBeThreatened.size();
738 for (Creature* threatened : canBeThreatened)
739 threatened->GetThreatManager().AddThreat(assistant, perTarget, spell, ignoreModifiers);
740 }
741
742 for (Creature* threatened : cannotBeThreatened)
743 threatened->GetThreatManager().AddThreat(assistant, 0.0f, spell, true);
744}
745
746void ThreatManager::RemoveMeFromThreatLists(bool (*unitFilter)(Unit const* otherUnit))
747{
748 std::vector<ThreatReference*> threatReferencesToRemove;
749 threatReferencesToRemove.reserve(_threatenedByMe.size());
750 for (auto const& [guid, ref] : _threatenedByMe)
751 if (!unitFilter || unitFilter(ref->GetOwner()))
752 threatReferencesToRemove.push_back(ref);
753
754 for (ThreatReference* ref : threatReferencesToRemove)
755 ref->_mgr.ClearThreat(_owner);
756}
757
759{
760 int32 mod = 0;
762 mod += eff->GetAmount();
763
764 if (_threatenedByMe.empty())
765 return;
766
767 auto it = _threatenedByMe.begin();
768 bool const isIncrease = (it->second->_tempModifier < mod);
769 do
770 {
771 it->second->_tempModifier = mod;
772 if (isIncrease)
773 it->second->HeapNotifyIncreased();
774 else
775 it->second->HeapNotifyDecreased();
776 } while ((++it) != _threatenedByMe.end());
777}
778
780{
781 for (uint8 i = 0; i < MAX_SPELL_SCHOOL; ++i)
783 _multiSchoolModifiers.clear();
784}
785
787{
788 _redirectRegistry[spellId][victim] = pct;
790}
791
793{
794 auto it = _redirectRegistry.find(spellId);
795 if (it == _redirectRegistry.end())
796 return;
797 _redirectRegistry.erase(it);
799}
800
802{
803 auto it = _redirectRegistry.find(spellId);
804 if (it == _redirectRegistry.end())
805 return;
806 auto& victimMap = it->second;
807 auto it2 = victimMap.find(victim);
808 if (it2 == victimMap.end())
809 return;
810 victimMap.erase(it2);
812}
813
815{
816 if (Creature const* owner = _owner->ToCreature(); owner && owner->IsThreatFeedbackDisabled())
817 return;
818
820 threatClear.UnitGUID = _owner->GetGUID();
821 _owner->SendMessageToSet(threatClear.Write(), false);
822}
823
825{
826 if (Creature const* owner = _owner->ToCreature(); owner && owner->IsThreatFeedbackDisabled())
827 return;
828
830 threatRemove.UnitGUID = _owner->GetGUID();
831 threatRemove.AboutGUID = victim->GetGUID();
832 _owner->SendMessageToSet(threatRemove.Write(), false);
833}
834
835void ThreatManager::SendThreatListToClients(bool newHighest) const
836{
837 if (Creature const* owner = _owner->ToCreature(); owner && owner->IsThreatFeedbackDisabled())
838 return;
839
840 auto fillSharedPacketDataAndSend = [&](auto& packet)
841 {
842 packet.UnitGUID = _owner->GetGUID();
843 packet.ThreatList.reserve(_sortedThreatList->size());
844 for (ThreatReference const* ref : *_sortedThreatList)
845 {
846 if (!ref->IsAvailable())
847 continue;
848
850 threatInfo.UnitGUID = ref->GetVictim()->GetGUID();
851 threatInfo.Threat = int64(ref->GetThreat() * 100);
852 packet.ThreatList.push_back(threatInfo);
853 }
854 _owner->SendMessageToSet(packet.Write(), false);
855 };
856
857 if (newHighest)
858 {
860 highestThreatUpdate.HighestThreatGUID = _currentVictimRef->GetVictim()->GetGUID();
861 fillSharedPacketDataAndSend(highestThreatUpdate);
862 }
863 else
864 {
866 fillSharedPacketDataAndSend(threatUpdate);
867 }
868}
869
871{
872 _needClientUpdate = true;
873 auto& inMap = _myThreatListEntries[guid];
874 ASSERT(!inMap, "Duplicate threat reference at %p being inserted on %s for %s - memory leak!", ref, _owner->GetGUID().ToString().c_str(), guid.ToString().c_str());
875 inMap = ref;
876 static_cast<ThreatReferenceImpl*>(ref)->_handle = _sortedThreatList->push(ref);
877}
878
880{
881 auto it = _myThreatListEntries.find(guid);
882 if (it == _myThreatListEntries.end())
883 return;
884 ThreatReference* ref = it->second;
885 _myThreatListEntries.erase(it);
886 _sortedThreatList->erase(static_cast<ThreatReferenceImpl*>(ref)->_handle);
887
888 if (_fixateRef == ref)
889 _fixateRef = nullptr;
890 if (_currentVictimRef == ref)
891 _currentVictimRef = nullptr;
892}
893
895{
896 auto& inMap = _threatenedByMe[guid];
897 ASSERT(!inMap, "Duplicate threatened-by-me reference at %p being inserted on %s for %s - memory leak!", ref, _owner->GetGUID().ToString().c_str(), guid.ToString().c_str());
898 inMap = ref;
899}
900
902{
903 auto it = _threatenedByMe.find(guid);
904 if (it != _threatenedByMe.end())
905 _threatenedByMe.erase(it);
906}
907
909{
910 _redirectInfo.clear();
911 uint32 totalPct = 0;
912 for (auto const& pair : _redirectRegistry) // (spellid, victim -> pct)
913 for (auto const& victimPair : pair.second) // (victim,pct)
914 {
915 uint32 thisPct = std::min<uint32>(100 - totalPct, victimPair.second);
916 if (thisPct > 0)
917 {
918 _redirectInfo.push_back({ victimPair.first, thisPct });
919 totalPct += thisPct;
920 ASSERT(totalPct <= 100);
921 if (totalPct == 100)
922 return;
923 }
924 }
925}
uint8_t uint8
Definition: Define.h:144
int64_t int64
Definition: Define.h:137
int8_t int8
Definition: Define.h:140
int32_t int32
Definition: Define.h:138
uint32_t uint32
Definition: Define.h:142
#define ABORT_MSG
Definition: Errors.h:75
#define ASSERT_NOTNULL(pointer)
Definition: Errors.h:84
#define ASSERT
Definition: Errors.h:68
@ TYPEID_UNIT
Definition: ObjectGuid.h:40
SpellSchoolMask
@ SPELL_SCHOOL_MASK_NORMAL
@ SPELL_SCHOOL_MASK_SHADOW
@ SPELL_SCHOOL_MASK_ARCANE
@ SPELL_SCHOOL_MASK_NATURE
@ SPELL_SCHOOL_MASK_HOLY
@ SPELL_SCHOOL_MASK_FIRE
@ SPELL_SCHOOL_MASK_FROST
@ SPELL_ATTR2_NO_INITIAL_THREAT
@ SPELL_ATTR1_NO_THREAT
@ SPELL_SCHOOL_SHADOW
@ SPELL_SCHOOL_NORMAL
@ SPELL_SCHOOL_NATURE
@ SPELL_SCHOOL_FROST
@ SPELL_SCHOOL_ARCANE
@ SPELL_SCHOOL_FIRE
@ SPELL_SCHOOL_HOLY
@ MAX_SPELL_SCHOOL
@ SPELL_ATTR4_NO_HELPFUL_THREAT
@ SPELL_AURA_MOD_TOTAL_THREAT
@ SPELL_AURA_MOD_THREAT
@ SPELL_AURA_MOD_DETAUNT
@ SPELL_AURA_MOD_TAUNT
@ SPELL_AURA_MOD_CONFUSE
@ SPELL_AURA_MOD_STUN
#define sSpellMgr
Definition: SpellMgr.h:849
@ UNIT_FLAG_IMMUNE_TO_NPC
Definition: UnitDefines.h:153
@ UNIT_FLAG_IMMUNE_TO_PC
Definition: UnitDefines.h:152
@ UNIT_FLAG_PLAYER_CONTROLLED
Definition: UnitDefines.h:147
@ UNIT_MASK_ACCESSORY
Definition: Unit.h:359
@ UNIT_MASK_GUARDIAN
Definition: Unit.h:352
@ UNIT_MASK_MINION
Definition: Unit.h:351
@ UNIT_STATE_CONTROLLED
Definition: Unit.h:295
T CalculatePct(T base, U pct)
Definition: Util.h:72
bool SetInCombatWith(Unit *who, bool addSecondUnitSuppressed=false)
bool IsInCombatWith(ObjectGuid const &who) const
virtual void JustStartedThreateningMe(Unit *who)
Definition: CreatureAI.h:96
bool IsTrigger() const
Definition: Creature.h:113
bool _IsTargetAcceptable(Unit const *target) const
Definition: Creature.cpp:2639
bool IsThreatFeedbackDisabled() const
Definition: Creature.h:441
SpellSchoolMask GetMeleeDamageSchoolMask(WeaponAttackType=BASE_ATTACK) const override
Definition: Creature.h:216
bool CanCreatureAttack(Unit const *victim, bool force=true) const
Definition: Creature.cpp:2686
std::string ToString() const
Definition: ObjectGuid.cpp:554
static Creature * ToCreature(Object *o)
Definition: Object.h:219
TypeID GetTypeId() const
Definition: Object.h:173
static ObjectGuid GetGUID(Object const *o)
Definition: Object.h:159
uint32 const Id
Definition: SpellInfo.h:325
SpellSchoolMask GetSchoolMask() const
Definition: SpellInfo.cpp:2465
bool HasAttribute(SpellAttr0 attribute) const
Definition: SpellInfo.h:449
std::unordered_map< ObjectGuid, ThreatReference * > _threatenedByMe
bool CanHaveThreatList() const
ThreatManager(Unit *owner)
static float CalculateModifiedThreat(float threat, Unit const *victim, SpellInfo const *spell)
uint32 _updateTimer
void SendClearAllThreatToClients() const
void EvaluateSuppressed(bool canExpire=false)
void UnregisterRedirectThreat(uint32 spellId)
void ForwardThreatForAssistingMe(Unit *assistant, float baseAmount, SpellInfo const *spell=nullptr, bool ignoreModifiers=false)
== AFFECT OTHERS' THREAT LISTS ==
std::vector< std::pair< ObjectGuid, uint32 > > _redirectInfo
bool IsThreatenedBy(ObjectGuid const &who, bool includeOffline=false) const
void PutThreatListRef(ObjectGuid const &guid, ThreatReference *ref)
== MY THREAT LIST ==
bool IsThreateningTo(ObjectGuid const &who, bool includeOffline=false) const
void RemoveMeFromThreatLists(bool(*unitFilter)(Unit const *otherUnit))
Unit * GetCurrentVictim()
static const uint32 THREAT_UPDATE_INTERVAL
Definition: ThreatManager.h:86
void PutThreatenedByMeRef(ObjectGuid const &guid, ThreatReference *ref)
== OTHERS' THREAT LISTS ==
void RegisterForAIUpdate(ObjectGuid const &guid)
void SendThreatListToClients(bool newHighest) const
void UpdateRedirectInfo()
std::array< float, MAX_SPELL_SCHOOL > _singleSchoolModifiers
void UpdateMySpellSchoolModifiers()
static bool CompareReferencesLT(ThreatReference const *a, ThreatReference const *b, float aWeight)
Trinity::IteratorPair< ThreatListIterator, std::nullptr_t > GetUnsortedThreatList() const
std::unordered_map< uint32, std::unordered_map< ObjectGuid, uint32 > > _redirectRegistry
static const CompareThreatLessThan CompareThreat
std::unique_ptr< Heap > _sortedThreatList
void ScaleThreat(Unit *target, float factor)
void ClearThreat(Unit *target)
ThreatReference const * _currentVictimRef
Unit * GetAnyTarget() const
Unit * GetLastVictim() const
std::unordered_map< ObjectGuid, ThreatReference * > _myThreatListEntries
bool _ownerCanHaveThreatList
void FixateTarget(Unit *target)
bool IsThreatListEmpty(bool includeOffline=false) const
std::vector< ObjectGuid > _needsAIUpdate
void Update(uint32 tdiff)
bool _needClientUpdate
void PurgeThreatListRef(ObjectGuid const &guid)
Unit *const _owner
void AddThreat(Unit *target, float amount, SpellInfo const *spell=nullptr, bool ignoreModifiers=false, bool ignoreRedirects=false)
== AFFECT MY THREAT LIST ==
bool IsThreateningAnyone(bool includeOffline=false) const
ThreatReference const * _fixateRef
void ProcessAIUpdates()
ThreatReference const * ReselectVictim()
void SendRemoveToClients(Unit const *victim) const
Trinity::IteratorPair< ThreatListIterator, std::nullptr_t > GetSortedThreatList() const
std::vector< ThreatReference * > GetModifiableThreatList()
void RegisterRedirectThreat(uint32 spellId, ObjectGuid const &victim, uint32 pct)
== REDIRECT SYSTEM ==
std::unordered_map< std::underlying_type< SpellSchoolMask >::type, float > _multiSchoolModifiers
Unit * GetFixateTarget() const
Unit * GetOwner() const
friend class ThreatReferenceImpl
void UpdateMyTempModifiers()
void PurgeThreatenedByMeRef(ObjectGuid const &guid)
float GetThreat(Unit const *who, bool includeOffline=false) const
void MatchUnitThreatToHighestThreat(Unit *target)
size_t GetThreatListSize() const
ThreatManager::Heap::handle_type _handle
ThreatReferenceImpl(ThreatManager *mgr, Unit *victim)
static bool FlagsAllowFighting(Unit const *a, Unit const *b)
void HeapNotifyDecreased()
void ScaleThreat(float factor)
bool IsTaunting() const
void HeapNotifyIncreased()
bool ShouldBeSuppressed() const
ThreatManager & _mgr
OnlineState GetOnlineState() const
Creature *const _owner
bool IsOnline() const
bool ShouldBeOffline() const
Unit *const _victim
TauntState _taunted
bool IsOffline() const
float GetThreat() const
OnlineState _online
void UpdateTauntState(TauntState state=TAUNT_STATE_NONE)
Unit * GetVictim() const
void AddThreat(float amount)
bool IsAvailable() const
Utility class to enable range for loop syntax for multimap.equal_range uses.
Definition: IteratorPair.h:32
Definition: Unit.h:627
bool IsWithinMeleeRange(Unit const *obj) const
Definition: Unit.h:699
AuraEffectList const & GetAuraEffectsByType(AuraType type) const
Definition: Unit.h:1321
ThreatManager & GetThreatManager()
Definition: Unit.h:1063
std::forward_list< AuraEffect * > AuraEffectList
Definition: Unit.h:644
Unit * GetVehicleBase() const
Definition: Unit.cpp:11480
bool IsPet() const
Definition: Unit.h:740
bool HasUnitFlag(UnitFlags flags) const
Definition: Unit.h:832
TempSummon * ToTempSummon()
Definition: Unit.h:1756
bool HasAuraType(AuraType auraType) const
Definition: Unit.cpp:4674
Unit * GetVictim() const
Definition: Unit.h:715
bool IsImmunedToDamage(SpellSchoolMask meleeSchoolMask) const
Definition: Unit.cpp:7376
float GetTotalAuraMultiplierByMiscMask(AuraType auraType, uint32 misc_mask) const
Definition: Unit.cpp:4959
bool HasUnitState(const uint32 f) const
Definition: Unit.h:732
uint32 HasUnitTypeMask(uint32 mask) const
Definition: Unit.h:736
bool HasBreakableByDamageAuraType(AuraType type, uint32 excludeAura=0) const
Definition: Unit.cpp:724
CombatManager & GetCombatManager()
Definition: Unit.h:1023
bool IsTotem() const
Definition: Unit.h:742
virtual bool IsEngaged() const
Definition: Unit.h:1019
virtual void SendMessageToSet(WorldPacket const *data, bool self) const
Definition: Object.cpp:1744
ObjectGuid GetPrivateObjectOwner() const
Definition: Object.h:785
Unit * GetOwner() const
Definition: Object.cpp:2229
bool IsPrivateObject() const
Definition: Object.h:784
bool CanSeeOrDetect(WorldObject const *obj, bool implicitDetect=false, bool distanceCheck=false, bool checkAlert=false) const
Definition: Object.cpp:1514
Player * GetSpellModOwner() const
Definition: Object.cpp:2272
WorldPacket const * Write() override
WorldPacket const * Write() override
TC_GAME_API Unit * GetUnit(WorldObject const &, ObjectGuid const &guid)
auto MapGetValuePtr(M &map, typename M::key_type const &key)
Definition: MapUtils.h:29
STL namespace.