TrinityCore
Loading...
Searching...
No Matches
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 "CombatPackets.h"
20#include "Creature.h"
21#include "CreatureAI.h"
22#include "CreatureGroups.h"
23#include "MapUtils.h"
24#include "MotionMaster.h"
25#include "Player.h"
26#include "TemporarySummon.h"
27#include "Unit.h"
28#include "UnitAI.h"
29#include "UnitDefines.h"
30#include "SpellAuraEffects.h"
31#include "SpellInfo.h"
32#include "SpellMgr.h"
33#include "ObjectAccessor.h"
34#include "WorldPacket.h"
35#include <algorithm>
36#include <boost/heap/fibonacci_heap.hpp>
37
39
40class ThreatManager::Heap : public boost::heap::fibonacci_heap<ThreatReference const*, boost::heap::compare<CompareThreatLessThan>>
41{
42};
43
45{
46 if (amount == 0.0f)
47 return;
48 _baseAmount = std::max<float>(_baseAmount + amount, 0.0f);
49 if (amount > 0.0f)
51 else
53
55}
56
58{
59 if (factor == 1.0f)
60 return;
61 _baseAmount *= factor;
62 if (factor > 1.0f)
64 else
66
68}
69
71{
72 bool const shouldBeOffline = ShouldBeOffline();
73 if (shouldBeOffline == IsOffline())
74 return;
75
76 if (shouldBeOffline)
77 {
81 }
82 else
83 {
88 }
89}
90
91/*static*/ bool ThreatReference::FlagsAllowFighting(Unit const* a, Unit const* b)
92{
93 if (a->GetTypeId() == TYPEID_UNIT && a->ToCreature()->IsTrigger())
94 return false;
96 {
98 return false;
99 }
100 else
101 {
103 return false;
104 }
105 return true;
106}
107
109{
111 return true;
113 return true;
115 return true;
116 return false;
117}
118
120{
121 if (IsTaunting()) // a taunting victim can never be suppressed
122 return false;
124 return true;
126 return true;
128 return true;
129 return false;
130}
131
133{
134 // Check for SPELL_AURA_MOD_DETAUNT (applied from owner to victim)
135 if (state < TAUNT_STATE_TAUNT && _victim->HasAuraTypeWithCaster(SPELL_AURA_MOD_DETAUNT, _owner->GetGUID()))
136 state = TAUNT_STATE_DETAUNT;
137
138 if (state == _taunted)
139 return;
140
141 std::swap(state, _taunted);
142
143 if (_taunted < state)
145 else
147
149}
150
155
162
164{
165public:
166 explicit ThreatReferenceImpl(ThreatManager* mgr, Unit* victim) : ThreatReference(mgr, victim) { }
167
168 ThreatManager::Heap::handle_type _handle;
169};
170
172{
173 _mgr._sortedThreatList->increase(static_cast<ThreatReferenceImpl*>(this)->_handle);
174}
175
177{
178 _mgr._sortedThreatList->decrease(static_cast<ThreatReferenceImpl*>(this)->_handle);
179}
180
181/*static*/ bool ThreatManager::CanHaveThreatList(Unit const* who)
182{
183 Creature const* cWho = who->ToCreature();
184 // only creatures can have threat list
185 if (!cWho)
186 return false;
187
188 // pets, totems and triggers cannot have threat list
189 if (cWho->IsPet() || cWho->IsTotem() || cWho->IsTrigger())
190 return false;
191
192 // summons cannot have a threat list if they were summoned by a player
194 if (TempSummon const* tWho = cWho->ToTempSummon())
195 if (tWho->GetSummonerGUID().IsPlayer())
196 return false;
197
198 return true;
199}
200
201ThreatManager::ThreatManager(Unit* owner) : _owner(owner), _ownerCanHaveThreatList(false), _updateFlags(ThreatListClientUpdateFlags::None), _updateTimer(THREAT_UPDATE_INTERVAL),
202 _sortedThreatList(std::make_unique<Heap>()), _currentVictimRef(nullptr), _fixateRef(nullptr)
203{
204 for (int8 i = 0; i < MAX_SPELL_SCHOOL; ++i)
205 _singleSchoolModifiers[i] = 1.0f;
206}
207
209{
210 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());
211 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());
212 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());
213}
214
219
221{
222 if (!CanHaveThreatList() || IsThreatListEmpty(true))
223 return;
224 if (_updateTimer <= tdiff)
225 {
226 UpdateVictim();
228 }
229 else
230 _updateTimer -= tdiff;
231}
232
240
242{
245 return nullptr;
246}
247
249{
250 for (ThreatReference const* ref : *_sortedThreatList)
251 if (!ref->IsOffline())
252 return ref->GetVictim();
253 return nullptr;
254}
255
256bool ThreatManager::IsThreatListEmpty(bool includeOffline) const
257{
258 if (includeOffline)
259 return _sortedThreatList->empty();
260 for (ThreatReference const* ref : *_sortedThreatList)
261 if (ref->IsAvailable())
262 return false;
263 return true;
264}
265
266bool ThreatManager::IsThreatenedBy(ObjectGuid const& who, bool includeOffline) const
267{
268 auto it = _myThreatListEntries.find(who);
269 if (it == _myThreatListEntries.end())
270 return false;
271 return (includeOffline || it->second->IsAvailable());
272}
273bool ThreatManager::IsThreatenedBy(Unit const* who, bool includeOffline) const { return IsThreatenedBy(who->GetGUID(), includeOffline); }
274
275float ThreatManager::GetThreat(Unit const* who, bool includeOffline) const
276{
277 auto it = _myThreatListEntries.find(who->GetGUID());
278 if (it == _myThreatListEntries.end())
279 return 0.0f;
280 return (includeOffline || it->second->IsAvailable()) ? it->second->GetThreat() : 0.0f;
281}
282
284{
285 return _sortedThreatList->size();
286}
287
288uint32 ThreatManager::GetThreatListPlayerCount(bool includeOffline/* = false*/) const
289{
290 if (includeOffline)
291 return uint32(_sortedThreatList->size());
292 uint32 returnValue = 0;
293 for (ThreatReference const* ref : *_sortedThreatList)
294 if (ref->IsAvailable() && ref->GetVictim()->GetTypeId() == TYPEID_PLAYER)
295 ++returnValue;
296 return returnValue;
297}
298
300{
301 auto itr = _myThreatListEntries.begin();
302 auto end = _myThreatListEntries.end();
303 std::function<ThreatReference const* ()> generator = [itr, end]() mutable -> ThreatReference const*
304 {
305 if (itr == end)
306 return nullptr;
307
308 return (itr++)->second;
309 };
310 return { ThreatListIterator{ std::move(generator) }, nullptr };
311}
312
314{
315 auto itr = _sortedThreatList->ordered_begin();
316 auto end = _sortedThreatList->ordered_end();
317 std::function<ThreatReference const* ()> generator = [itr, end]() mutable -> ThreatReference const*
318 {
319 if (itr == end)
320 return nullptr;
321
322 return *(itr++);
323 };
324 return { ThreatListIterator{ std::move(generator) }, nullptr };
325}
326
327std::vector<ThreatReference*> ThreatManager::GetModifiableThreatList()
328{
329 std::vector<ThreatReference*> list;
330 list.reserve(_myThreatListEntries.size());
331 for (auto it = _sortedThreatList->ordered_begin(), end = _sortedThreatList->ordered_end(); it != end; ++it)
332 list.push_back(const_cast<ThreatReference*>(*it));
333 return list;
334}
335
336bool ThreatManager::IsThreateningAnyone(bool includeOffline) const
337{
338 if (includeOffline)
339 return !_threatenedByMe.empty();
340 for (auto const& pair : _threatenedByMe)
341 if (pair.second->IsAvailable())
342 return true;
343 return false;
344}
345
346bool ThreatManager::IsThreateningTo(ObjectGuid const& who, bool includeOffline) const
347{
348 auto it = _threatenedByMe.find(who);
349 if (it == _threatenedByMe.end())
350 return false;
351 return (includeOffline || it->second->IsAvailable());
352}
353bool ThreatManager::IsThreateningTo(Unit const* who, bool includeOffline) const { return IsThreateningTo(who->GetGUID(), includeOffline); }
354
356{
357 for (auto const& pair : _threatenedByMe)
358 {
359 bool const shouldBeSuppressed = pair.second->ShouldBeSuppressed();
360 if (pair.second->IsOnline() && shouldBeSuppressed)
361 {
362 pair.second->_online = ThreatReference::ONLINE_STATE_SUPPRESSED;
363 pair.second->HeapNotifyDecreased();
364 }
365 else if (canExpire && pair.second->IsSuppressed() && !shouldBeSuppressed)
366 {
367 pair.second->_online = ThreatReference::ONLINE_STATE_ONLINE;
368 pair.second->HeapNotifyIncreased();
369 }
370 }
371}
372
373void ThreatManager::AddThreat(Unit* target, float amount, SpellInfo const* spell, bool ignoreModifiers, bool ignoreRedirects)
374{
375 // step 1: we can shortcut if the spell has one of the NO_THREAT attrs set - nothing will happen
376 if (spell)
377 {
379 return;
381 return;
382 }
383
384 // while riding a vehicle, all threat goes to the vehicle, not the pilot
385 if (Unit* vehicle = target->GetVehicleBase())
386 {
387 AddThreat(vehicle, amount, spell, ignoreModifiers, ignoreRedirects);
388 if (target->HasUnitTypeMask(UNIT_MASK_ACCESSORY)) // accessories are fully treated as components of the parent and cannot have threat
389 return;
390 amount = 0.0f;
391 }
392
393 // If victim is personal spawn, redirect all aggro to summoner
394 if (target->IsPrivateObject() && (!GetOwner()->IsPrivateObject() || !GetOwner()->CheckPrivateObjectOwnerVisibility(target)))
395 {
396 if (Unit* privateObjectOwner = ObjectAccessor::GetUnit(*GetOwner(), target->GetPrivateObjectOwner()))
397 {
398 AddThreat(privateObjectOwner, amount, spell, ignoreModifiers, ignoreRedirects);
399 amount = 0.0f;
400 }
401 }
402
403 // if we cannot actually have a threat list, we instead just set combat state and avoid creating threat refs altogether
404 if (!CanHaveThreatList())
405 {
406 CombatManager& combatMgr = _owner->GetCombatManager();
407 if (!combatMgr.SetInCombatWith(target))
408 return;
409 // traverse redirects and put them in combat, too
410 for (auto const& pair : target->GetThreatManager()._redirectInfo)
411 if (!combatMgr.IsInCombatWith(pair.first))
412 if (Unit* redirTarget = ObjectAccessor::GetUnit(*_owner, pair.first))
413 combatMgr.SetInCombatWith(redirTarget);
414 return;
415 }
416
417 // apply threat modifiers to the amount
418 if (!ignoreModifiers)
419 amount = CalculateModifiedThreat(amount, target, spell);
420
421 // if we're increasing threat, send some/all of it to redirection targets instead if applicable
422 if (!ignoreRedirects && amount > 0.0f)
423 {
424 auto const& redirInfo = target->GetThreatManager()._redirectInfo;
425 if (!redirInfo.empty())
426 {
427 float const origAmount = amount;
428 // intentional iteration by index - there's a nested AddThreat call further down that might cause AI calls which might modify redirect info through spells
429 for (size_t i = 0; i < redirInfo.size(); ++i)
430 {
431 auto const pair = redirInfo[i]; // (victim,pct)
432 Unit* redirTarget = nullptr;
433 auto it = _myThreatListEntries.find(pair.first); // try to look it up in our threat list first (faster)
434 if (it != _myThreatListEntries.end())
435 redirTarget = it->second->_victim;
436 else
437 redirTarget = ObjectAccessor::GetUnit(*_owner, pair.first);
438
439 if (redirTarget)
440 {
441 float amountRedirected = CalculatePct(origAmount, pair.second);
442 AddThreat(redirTarget, amountRedirected, spell, true, true);
443 amount -= amountRedirected;
444 }
445 }
446 }
447 }
448
449 // ensure we're in combat (threat implies combat!)
450 if (!_owner->GetCombatManager().SetInCombatWith(target)) // if this returns false, we're not actually in combat, and thus cannot have threat!
451 return; // typical causes: bad scripts trying to add threat to GMs, dead targets etc
452
453 // ok, now we actually apply threat
454 // check if we already have an entry - if we do, just increase threat for that entry and we're done
455 auto it = _myThreatListEntries.find(target->GetGUID());
456 if (it != _myThreatListEntries.end())
457 {
458 ThreatReference* const ref = it->second;
459 // SUPPRESSED threat states don't go back to ONLINE until threat is caused by them (retail behavior)
461 if (!ref->ShouldBeSuppressed())
462 {
464 ref->HeapNotifyIncreased();
465 }
466
467 if (ref->IsOnline())
468 ref->AddThreat(amount);
469 return;
470 }
471
472 // ok, we're now in combat - create the threat list reference and push it to the respective managers
473 ThreatReference* ref = new ThreatReferenceImpl(this, target);
474 PutThreatListRef(target->GetGUID(), ref);
476
477 ref->UpdateOffline();
478 if (ref->IsOnline()) // we only add the threat if the ref is currently available
479 ref->AddThreat(amount);
480
482 UpdateVictim();
483 else
485}
486
487void ThreatManager::ScaleThreat(Unit* target, float factor)
488{
489 auto it = _myThreatListEntries.find(target->GetGUID());
490 if (it != _myThreatListEntries.end())
491 it->second->ScaleThreat(std::max<float>(factor,0.0f));
492}
493
495{
496 if (_sortedThreatList->empty())
497 return;
498
499 auto it = _sortedThreatList->ordered_begin(), end = _sortedThreatList->ordered_end();
500 ThreatReference const* highest = *it;
501 if (!highest->IsAvailable())
502 return;
503
504 if (highest->IsTaunting() && ((++it) != end)) // might need to skip this - max threat could be the preceding element (there is only one taunt element)
505 {
506 ThreatReference const* a = *it;
507 if (a->IsAvailable() && a->GetThreat() > highest->GetThreat())
508 highest = a;
509 }
510
511 AddThreat(target, highest->GetThreat() - GetThreat(target, true), nullptr, true, true);
512}
513
515{
516 std::list<AuraEffect*> const& tauntEffects = _owner->GetAuraEffectsByType(SPELL_AURA_MOD_TAUNT);
517
519 std::unordered_map<ObjectGuid, ThreatReference::TauntState> tauntStates;
520 // Only the last taunt effect applied by something still on our threat list is considered
521 for (auto it = tauntEffects.begin(), end = tauntEffects.end(); it != end; ++it)
522 tauntStates[(*it)->GetCasterGUID()] = ThreatReference::TauntState(state++);
523
524 for (auto const& pair : _myThreatListEntries)
525 {
526 auto it = tauntStates.find(pair.first);
527 if (it != tauntStates.end())
528 pair.second->UpdateTauntState(it->second);
529 else
530 pair.second->UpdateTauntState();
531 }
532
533 // taunt aura update also re-evaluates all suppressed states (retail behavior)
534 EvaluateSuppressed(true);
535}
536
538{
539 for (auto const& pair : _myThreatListEntries)
540 pair.second->ScaleThreat(0.0f);
541}
542
544{
545 auto it = _myThreatListEntries.find(target->GetGUID());
546 if (it != _myThreatListEntries.end())
547 ClearThreat(it->second);
548}
549
557
559{
560 if (!_myThreatListEntries.empty())
561 {
563 do
564 _myThreatListEntries.begin()->second->UnregisterAndFree();
565 while (!_myThreatListEntries.empty());
566 }
567}
568
570{
571 if (target)
572 {
573 auto it = _myThreatListEntries.find(target->GetGUID());
574 if (it != _myThreatListEntries.end())
575 {
576 _fixateRef = it->second;
577 return;
578 }
579 }
580 _fixateRef = nullptr;
581}
582
584{
585 if (_fixateRef)
586 return _fixateRef->GetVictim();
587 else
588 return nullptr;
589}
590
592{
593 ThreatReference const* const newVictim = ReselectVictim();
594 bool const newHighest = newVictim && ((newVictim != _currentVictimRef) || _updateFlags.HasFlag(ThreatListClientUpdateFlags::ForceHighestUpdate));
595
596 _currentVictimRef = newVictim;
598 {
599 SendThreatListToClients(newHighest);
601 }
602
604}
605
607{
608 if (_sortedThreatList->empty())
609 return nullptr;
610
611 for (auto const& pair : _myThreatListEntries)
612 pair.second->UpdateOffline(); // AI notifies are processed in ::UpdateVictim caller
613
614 // fixated target is always preferred
616 return _fixateRef;
617
618 ThreatReference const* oldVictimRef = _currentVictimRef;
619 if (oldVictimRef && oldVictimRef->IsOffline())
620 oldVictimRef = nullptr;
621 // in 99% of cases - we won't need to actually look at anything beyond the first element
622 ThreatReference const* highest = _sortedThreatList->top();
623 // if the highest reference is offline, the entire list is offline, and we indicate this
624 if (!highest->IsAvailable())
625 return nullptr;
626 // if we have no old victim, or old victim is still highest, then highest is our target and we're done
627 if (!oldVictimRef || highest == oldVictimRef)
628 return highest;
629 // 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
630 if (!ThreatManager::CompareReferencesLT(oldVictimRef, highest, 1.1f))
631 return oldVictimRef;
632 // if highest threat breaks 130%, it's our new target regardless of range (and we're done)
633 if (ThreatManager::CompareReferencesLT(oldVictimRef, highest, 1.3f))
634 return highest;
635 // 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
636 if (_owner->IsWithinMeleeRange(highest->_victim))
637 return highest;
638 // 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
639 // 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)
640 auto it = _sortedThreatList->ordered_begin(), end = _sortedThreatList->ordered_end();
641 while (it != end)
642 {
643 ThreatReference const* next = *it;
644 // if we've found current victim, we're done (nothing above is higher, and nothing below can be higher)
645 if (next == oldVictimRef)
646 return next;
647 // if next isn't above 110% threat, then nothing below it can be either - we're done, old victim stays
648 if (!ThreatManager::CompareReferencesLT(oldVictimRef, next, 1.1f))
649 return oldVictimRef;
650 // if next is melee, he's above 110% and our new victim
652 return next;
653 // otherwise the next highest target may still be a melee above 110% and we need to look further
654 ++it;
655 }
656 // we should have found the old victim at some point in the loop above, so execution should never get to this point
657 ABORT_MSG("Current victim not found in sorted threat list even though it has a reference - manager desync!");
658 return nullptr;
659}
660
662{
664 std::vector<ObjectGuid> v(std::move(_needsAIUpdate)); // _needsAIUpdate is now empty in case this triggers a recursive call
665 if (!ai)
666 return;
667 for (ObjectGuid const& guid : v)
669 ai->JustStartedThreateningMe(ref->GetVictim());
670}
671
672// returns true if a is LOWER on the threat list than b
673/*static*/ bool ThreatManager::CompareReferencesLT(ThreatReference const* a, ThreatReference const* b, float aWeight)
674{
675 if (a->_online != b->_online) // online state precedence (ONLINE > SUPPRESSED > OFFLINE)
676 return a->_online < b->_online;
677 if (a->_taunted != b->_taunted) // taunt state precedence (TAUNT > NONE > DETAUNT)
678 return a->_taunted < b->_taunted;
679 return (a->GetThreat()*aWeight < b->GetThreat());
680}
681
682/*static*/ float ThreatManager::CalculateModifiedThreat(float threat, Unit const* victim, SpellInfo const* spell)
683{
684 // modifiers by spell
685 if (spell)
686 {
687 if (SpellThreatEntry const* threatEntry = sSpellMgr->GetSpellThreatEntry(spell->Id))
688 if (threatEntry->pctMod != 1.0f) // flat/AP modifiers handled in Spell::HandleThreatSpells
689 threat *= threatEntry->pctMod;
690
691 if (Player* modOwner = victim->GetSpellModOwner())
692 modOwner->ApplySpellMod(spell->Id, SPELLMOD_THREAT, threat);
693 }
694
695 // modifiers by effect school
696 ThreatManager const& victimMgr = victim->GetThreatManager();
697 SpellSchoolMask const mask = spell ? spell->GetSchoolMask() : SPELL_SCHOOL_MASK_NORMAL;
698 switch (mask)
699 {
701 threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_NORMAL];
702 break;
704 threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_HOLY];
705 break;
707 threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_FIRE];
708 break;
710 threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_NATURE];
711 break;
713 threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_FROST];
714 break;
716 threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_SHADOW];
717 break;
719 threat *= victimMgr._singleSchoolModifiers[SPELL_SCHOOL_ARCANE];
720 break;
721 default:
722 {
723 auto it = victimMgr._multiSchoolModifiers.find(mask);
724 if (it != victimMgr._multiSchoolModifiers.end())
725 {
726 threat *= it->second;
727 break;
728 }
730 victimMgr._multiSchoolModifiers[mask] = mod;
731 threat *= mod;
732 break;
733 }
734 }
735 return threat;
736}
737
738void ThreatManager::ForwardThreatForAssistingMe(Unit* assistant, float baseAmount, SpellInfo const* spell, bool ignoreModifiers)
739{
740 if (spell && spell->HasAttribute(SPELL_ATTR1_NO_THREAT)) // shortcut, none of the calls would do anything
741 return;
742 if (_threatenedByMe.empty())
743 return;
744
745 std::vector<Creature*> canBeThreatened, cannotBeThreatened;
746 for (auto const& pair : _threatenedByMe)
747 {
748 Creature* owner = pair.second->GetOwner();
750 canBeThreatened.push_back(owner);
751 else
752 cannotBeThreatened.push_back(owner);
753 }
754
755 if (!canBeThreatened.empty()) // targets under CC cannot gain assist threat - split evenly among the rest
756 {
757 float const perTarget = baseAmount / canBeThreatened.size();
758 for (Creature* threatened : canBeThreatened)
759 threatened->GetThreatManager().AddThreat(assistant, perTarget, spell, ignoreModifiers);
760 }
761
762 for (Creature* threatened : cannotBeThreatened)
763 threatened->GetThreatManager().AddThreat(assistant, 0.0f, spell, true);
764}
765
767{
768 while (!_threatenedByMe.empty())
769 {
770 auto& ref = _threatenedByMe.begin()->second;
771 ref->_mgr.ClearThreat(_owner);
772 }
773}
774
776{
777 int32 mod = 0;
779 mod += eff->GetAmount();
780
781 if (_threatenedByMe.empty())
782 return;
783
784 auto it = _threatenedByMe.begin();
785 bool const isIncrease = (it->second->_tempModifier < mod);
786 do
787 {
788 it->second->_tempModifier = mod;
789 if (isIncrease)
790 it->second->HeapNotifyIncreased();
791 else
792 it->second->HeapNotifyDecreased();
793 } while ((++it) != _threatenedByMe.end());
794}
795
802
804{
805 _redirectRegistry[spellId][victim] = pct;
807}
808
810{
811 auto it = _redirectRegistry.find(spellId);
812 if (it == _redirectRegistry.end())
813 return;
814 _redirectRegistry.erase(it);
816}
817
819{
820 auto it = _redirectRegistry.find(spellId);
821 if (it == _redirectRegistry.end())
822 return;
823 auto& victimMap = it->second;
824 auto it2 = victimMap.find(victim);
825 if (it2 == victimMap.end())
826 return;
827 victimMap.erase(it2);
829}
830
832{
834 data << _owner->GetPackGUID();
835 _owner->SendMessageToSet(&data, false);
836}
837
839{
841 threatRemove.UnitGUID = _owner->GetGUID();
842 threatRemove.AboutGUID = victim->GetGUID();
843 _owner->SendMessageToSet(threatRemove.Write(), false);
844}
845
846void ThreatManager::SendThreatListToClients(bool newHighest) const
847{
848 auto fillSharedPacketDataAndSend = [&](auto& packet)
849 {
850 packet.UnitGUID = _owner->GetGUID();
851 packet.ThreatList.reserve(_sortedThreatList->size());
852 for (ThreatReference const* ref : *_sortedThreatList)
853 {
854 if (!ref->IsAvailable())
855 continue;
856
858 threatInfo.UnitGUID = ref->GetVictim()->GetGUID();
859 threatInfo.Threat = int32(ref->GetThreat() * 100);
860 packet.ThreatList.push_back(threatInfo);
861 }
862 _owner->SendMessageToSet(packet.Write(), false);
863 };
864
865 if (newHighest)
866 {
868 highestThreatUpdate.HighestThreatGUID = _currentVictimRef->GetVictim()->GetGUID();
869 fillSharedPacketDataAndSend(highestThreatUpdate);
870 }
871 else
872 {
874 fillSharedPacketDataAndSend(threatUpdate);
875 }
876}
877
879{
881 auto& inMap = _myThreatListEntries[guid];
882 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());
883 inMap = ref;
884 static_cast<ThreatReferenceImpl*>(ref)->_handle = _sortedThreatList->push(ref);
885}
886
888{
889 auto it = _myThreatListEntries.find(guid);
890 if (it == _myThreatListEntries.end())
891 return;
892 ThreatReference* ref = it->second;
893 _myThreatListEntries.erase(it);
894 _sortedThreatList->erase(static_cast<ThreatReferenceImpl*>(ref)->_handle);
895
896 if (_fixateRef == ref)
897 _fixateRef = nullptr;
898 if (_currentVictimRef == ref)
899 _currentVictimRef = nullptr;
900}
901
903{
904 auto& inMap = _threatenedByMe[guid];
905 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());
906 inMap = ref;
907}
908
910{
911 auto it = _threatenedByMe.find(guid);
912 if (it != _threatenedByMe.end())
913 _threatenedByMe.erase(it);
914}
915
917{
918 _redirectInfo.clear();
919 uint32 totalPct = 0;
920 for (auto const& pair : _redirectRegistry) // (spellid, victim -> pct)
921 for (auto const& victimPair : pair.second) // (victim,pct)
922 {
923 uint32 thisPct = std::min<uint32>(100 - totalPct, victimPair.second);
924 if (thisPct > 0)
925 {
926 _redirectInfo.push_back({ victimPair.first, thisPct });
927 totalPct += thisPct;
928 ASSERT(totalPct <= 100);
929 if (totalPct == 100)
930 return;
931 }
932 }
933}
uint8_t uint8
Definition Define.h:135
int8_t int8
Definition Define.h:131
int32_t int32
Definition Define.h:129
uint32_t uint32
Definition Define.h:133
#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:38
@ TYPEID_PLAYER
Definition ObjectGuid.h:39
@ SPELL_SCHOOL_SHADOW
@ SPELL_SCHOOL_NORMAL
@ SPELL_SCHOOL_NATURE
@ SPELL_SCHOOL_FROST
@ SPELL_SCHOOL_ARCANE
@ SPELL_SCHOOL_FIRE
@ SPELL_SCHOOL_HOLY
@ MAX_SPELL_SCHOOL
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_ATTR1_NO_THREAT
@ SPELL_ATTR3_NO_INITIAL_AGGRO
@ 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
@ SPELLMOD_THREAT
#define sSpellMgr
Definition SpellMgr.h:738
ThreatListClientUpdateFlags
@ UNIT_FLAG_IMMUNE_TO_NPC
@ UNIT_FLAG_IMMUNE_TO_PC
@ UNIT_FLAG_PLAYER_CONTROLLED
@ UNIT_MASK_ACCESSORY
Definition Unit.h:387
@ UNIT_MASK_GUARDIAN
Definition Unit.h:380
@ UNIT_MASK_MINION
Definition Unit.h:379
@ UNIT_STATE_CONTROLLED
Definition Unit.h:274
T CalculatePct(T base, U pct)
Definition Util.h:71
bool SetInCombatWith(Unit *who, bool addSecondUnitSuppressed=false)
bool IsInCombatWith(ObjectGuid const &who) const
virtual void JustStartedThreateningMe(Unit *who)
Definition CreatureAI.h:124
bool IsTrigger() const
Definition Creature.h:107
bool _IsTargetAcceptable(Unit const *target) const
bool CanCreatureAttack(Unit const *victim, bool force=true) const
SpellSchoolMask GetMeleeDamageSchoolMask(WeaponAttackType=BASE_ATTACK, uint8=0) const override
Definition Creature.h:167
constexpr bool HasFlag(T flag) const
Definition EnumFlag.h:106
std::string ToString() const
static Creature * ToCreature(Object *o)
Definition Object.h:189
PackedGuid const & GetPackGUID() const
Definition Object.h:80
TypeID GetTypeId() const
Definition Object.h:93
static ObjectGuid GetGUID(Object const *o)
Definition Object.h:78
uint32 Id
Definition SpellInfo.h:289
SpellSchoolMask GetSchoolMask() const
bool HasAttribute(SpellAttr0 attribute) const
Definition SpellInfo.h:375
std::unordered_map< ObjectGuid, ThreatReference * > _threatenedByMe
bool CanHaveThreatList() const
ThreatManager(Unit *owner)
static float CalculateModifiedThreat(float threat, Unit const *victim, SpellInfo const *spell)
void SendClearAllThreatToClients() const
EnumFlag< ThreatListClientUpdateFlags > _updateFlags
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 RemoveMeFromThreatLists()
void PutThreatListRef(ObjectGuid const &guid, ThreatReference *ref)
== MY THREAT LIST ==
bool IsThreateningTo(ObjectGuid const &who, bool includeOffline=false) const
Unit * GetCurrentVictim()
static const uint32 THREAT_UPDATE_INTERVAL
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)
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
ThreatReference const * ReselectVictim()
void SendRemoveToClients(Unit const *victim) const
uint32 GetThreatListPlayerCount(bool includeOffline=false) 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 ScaleThreat(float factor)
bool IsTaunting() const
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.
constexpr iterator begin() const
Definition Unit.h:783
bool IsWithinMeleeRange(Unit const *obj) const
Definition Unit.h:858
AuraEffectList const & GetAuraEffectsByType(AuraType type) const
Definition Unit.h:1397
ThreatManager & GetThreatManager()
Definition Unit.h:1169
Unit * GetVehicleBase() const
Definition Unit.cpp:11797
bool IsPet() const
Definition Unit.h:898
bool HasUnitFlag(UnitFlags flags) const
Definition Unit.h:967
TempSummon * ToTempSummon()
Definition Unit.h:1810
bool HasAuraType(AuraType auraType) const
Definition Unit.cpp:4565
Unit * GetVictim() const
Definition Unit.h:873
bool IsImmunedToDamage(SpellSchoolMask meleeSchoolMask) const
Definition Unit.cpp:7579
float GetTotalAuraMultiplierByMiscMask(AuraType auraType, uint32 misc_mask) const
Definition Unit.cpp:4845
bool HasUnitState(const uint32 f) const
Definition Unit.h:890
uint32 HasUnitTypeMask(uint32 mask) const
Definition Unit.h:894
bool HasBreakableByDamageAuraType(AuraType type, uint32 excludeAura=0) const
Definition Unit.cpp:691
CombatManager & GetCombatManager()
Definition Unit.h:1144
bool IsTotem() const
Definition Unit.h:900
virtual bool IsEngaged() const
Definition Unit.h:1140
virtual void SendMessageToSet(WorldPacket const *data, bool self) const
Definition Object.cpp:1784
ObjectGuid GetPrivateObjectOwner() const
Definition Object.h:597
Unit * GetOwner() const
Definition Object.cpp:2181
bool IsPrivateObject() const
Definition Object.h:596
bool CanSeeOrDetect(WorldObject const *obj, bool implicitDetect=false, bool distanceCheck=false, bool checkAlert=false) const
Definition Object.cpp:1548
Player * GetSpellModOwner() const
Definition Object.cpp:2224
WorldPacket const * Write() override
@ SMSG_THREAT_CLEAR
Definition Opcodes.h:1186
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.