aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTreeston <treeston.mmoc@gmail.com>2019-07-21 01:49:58 +0200
committerTreeston <treeston.mmoc@gmail.com>2019-07-21 01:50:52 +0200
commit0e7c66cb4c7ff7d44e232d0b50703a48605ffd24 (patch)
tree4ffe19100d1173ff3641bf6d4be82d826d729924
parenteba10f309eb541a7246754285bea6b821710d9fd (diff)
Core/AI: Finally move the "is creature engaged" flag to be a property of the creature AI, where it honestly always belonged. Fixes #17981 and #23602 for real this time.
-rw-r--r--src/server/game/AI/CoreAI/GuardAI.cpp4
-rw-r--r--src/server/game/AI/CoreAI/PassiveAI.h5
-rw-r--r--src/server/game/AI/CoreAI/TotemAI.cpp11
-rw-r--r--src/server/game/AI/CoreAI/TotemAI.h6
-rw-r--r--src/server/game/AI/CoreAI/UnitAI.cpp2
-rw-r--r--src/server/game/AI/CreatureAI.cpp44
-rw-r--r--src/server/game/AI/CreatureAI.h15
-rw-r--r--src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp3
-rw-r--r--src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp6
-rw-r--r--src/server/game/Combat/CombatManager.cpp13
-rw-r--r--src/server/game/Combat/ThreatManager.cpp39
-rw-r--r--src/server/game/Combat/ThreatManager.h17
-rw-r--r--src/server/game/Entities/Creature/Creature.cpp7
-rw-r--r--src/server/game/Entities/Creature/Creature.h1
-rw-r--r--src/server/game/Entities/Unit/Unit.cpp2
-rw-r--r--src/server/game/Entities/Unit/Unit.h10
-rw-r--r--src/server/scripts/Commands/cs_debug.cpp2
-rw-r--r--src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/instance_blackrock_depths.cpp11
-rw-r--r--src/server/scripts/EasternKingdoms/Karazhan/bosses_opera.cpp2
-rw-r--r--src/server/scripts/EasternKingdoms/ScarletEnclave/chapter5.cpp11
-rw-r--r--src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/hyjalAI.cpp5
-rw-r--r--src/server/scripts/Kalimdor/zone_desolace.cpp4
-rw-r--r--src/server/scripts/Kalimdor/zone_the_barrens.cpp6
-rw-r--r--src/server/scripts/Northrend/IcecrownCitadel/boss_icecrown_gunship_battle.cpp7
-rw-r--r--src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp2
-rw-r--r--src/server/scripts/Northrend/Naxxramas/boss_patchwerk.cpp2
-rw-r--r--src/server/scripts/Northrend/Nexus/Oculus/boss_urom.cpp2
-rw-r--r--src/server/scripts/Northrend/Ulduar/Ulduar/boss_freya.cpp2
-rw-r--r--src/server/scripts/Northrend/zone_borean_tundra.cpp1
-rw-r--r--src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_leotheras_the_blind.cpp2
-rw-r--r--src/server/scripts/Outland/zone_hellfire_peninsula.cpp4
-rw-r--r--src/server/scripts/Outland/zone_terokkar_forest.cpp2
-rw-r--r--src/server/scripts/Pet/pet_priest.cpp2
33 files changed, 152 insertions, 100 deletions
diff --git a/src/server/game/AI/CoreAI/GuardAI.cpp b/src/server/game/AI/CoreAI/GuardAI.cpp
index 547d074e7d6..222ccf2cda0 100644
--- a/src/server/game/AI/CoreAI/GuardAI.cpp
+++ b/src/server/game/AI/CoreAI/GuardAI.cpp
@@ -57,7 +57,7 @@ void GuardAI::EnterEvadeMode(EvadeReason /*why*/)
{
me->GetMotionMaster()->MoveIdle();
me->CombatStop(true);
- me->GetThreatManager().NotifyDisengaged();
+ EngagementOver();
return;
}
@@ -65,7 +65,7 @@ void GuardAI::EnterEvadeMode(EvadeReason /*why*/)
me->RemoveAllAuras();
me->CombatStop(true);
- me->GetThreatManager().NotifyDisengaged();
+ EngagementOver();
me->GetMotionMaster()->MoveTargetedHome();
}
diff --git a/src/server/game/AI/CoreAI/PassiveAI.h b/src/server/game/AI/CoreAI/PassiveAI.h
index 0ccd10ad10b..8282ddd5509 100644
--- a/src/server/game/AI/CoreAI/PassiveAI.h
+++ b/src/server/game/AI/CoreAI/PassiveAI.h
@@ -40,6 +40,9 @@ class TC_GAME_API PossessedAI : public CreatureAI
void MoveInLineOfSight(Unit*) override { }
void AttackStart(Unit* target) override;
+ void JustEnteredCombat(Unit* who) override { EngagementStart(who); }
+ void JustExitedCombat() override { EngagementOver(); }
+ void JustStartedThreateningMe(Unit*) override { }
void UpdateAI(uint32) override;
void EnterEvadeMode(EvadeReason /*why*/) override { }
@@ -56,6 +59,8 @@ class TC_GAME_API NullCreatureAI : public CreatureAI
void MoveInLineOfSight(Unit*) override { }
void AttackStart(Unit*) override { }
+ void JustStartedThreateningMe(Unit*) override { }
+ void JustEnteredCombat(Unit*) override { }
void UpdateAI(uint32) override { }
void JustAppeared() override { }
void EnterEvadeMode(EvadeReason /*why*/) override { }
diff --git a/src/server/game/AI/CoreAI/TotemAI.cpp b/src/server/game/AI/CoreAI/TotemAI.cpp
index 5e603cc78e9..e2e193065d6 100644
--- a/src/server/game/AI/CoreAI/TotemAI.cpp
+++ b/src/server/game/AI/CoreAI/TotemAI.cpp
@@ -34,16 +34,9 @@ int32 TotemAI::Permissible(Creature const* creature)
return PERMIT_BASE_NO;
}
-TotemAI::TotemAI(Creature* creature) : CreatureAI(creature), _victimGUID()
+TotemAI::TotemAI(Creature* creature) : NullCreatureAI(creature), _victimGUID()
{
- ASSERT(creature->IsTotem(), "TotemAI: AI assigned to a no-totem creature (%s)!", creature->GetGUID().ToString().c_str());
-}
-
-void TotemAI::MoveInLineOfSight(Unit* /*who*/) { }
-
-void TotemAI::EnterEvadeMode(EvadeReason /*why*/)
-{
- me->CombatStop(true);
+ ASSERT(creature->IsTotem(), "TotemAI: AI assigned to a non-totem creature (%s)!", creature->GetGUID().ToString().c_str());
}
void TotemAI::UpdateAI(uint32 /*diff*/)
diff --git a/src/server/game/AI/CoreAI/TotemAI.h b/src/server/game/AI/CoreAI/TotemAI.h
index a9f237a865b..048e7ef51cf 100644
--- a/src/server/game/AI/CoreAI/TotemAI.h
+++ b/src/server/game/AI/CoreAI/TotemAI.h
@@ -20,20 +20,18 @@
#define TRINITY_TOTEMAI_H
#include "CreatureAI.h"
+#include "PassiveAI.h"
#include "Timer.h"
class Creature;
class Totem;
-class TC_GAME_API TotemAI : public CreatureAI
+class TC_GAME_API TotemAI : public NullCreatureAI
{
public:
explicit TotemAI(Creature* creature);
- void MoveInLineOfSight(Unit* who) override;
void AttackStart(Unit* victim) override;
- void JustAppeared() override { }
- void EnterEvadeMode(EvadeReason /*why*/) override;
void UpdateAI(uint32 diff) override;
static int32 Permissible(Creature const* creature);
diff --git a/src/server/game/AI/CoreAI/UnitAI.cpp b/src/server/game/AI/CoreAI/UnitAI.cpp
index b1b8045c2a7..ae02c2c5262 100644
--- a/src/server/game/AI/CoreAI/UnitAI.cpp
+++ b/src/server/game/AI/CoreAI/UnitAI.cpp
@@ -255,7 +255,7 @@ std::string UnitAI::GetDebugInfo() const
}
DefaultTargetSelector::DefaultTargetSelector(Unit const* unit, float dist, bool playerOnly, bool withTank, int32 aura)
- : _me(unit), _dist(dist), _playerOnly(playerOnly), _exception(!withTank ? unit->GetThreatManager().GetCurrentVictim() : nullptr), _aura(aura)
+ : _me(unit), _dist(dist), _playerOnly(playerOnly), _exception(!withTank ? unit->GetThreatManager().GetLastVictim() : nullptr), _aura(aura)
{
}
diff --git a/src/server/game/AI/CreatureAI.cpp b/src/server/game/AI/CreatureAI.cpp
index 784cf560592..93c0076a2fa 100644
--- a/src/server/game/AI/CreatureAI.cpp
+++ b/src/server/game/AI/CreatureAI.cpp
@@ -37,7 +37,7 @@
AISpellInfoType* UnitAI::AISpellInfo;
AISpellInfoType* GetAISpellInfo(uint32 i) { return &UnitAI::AISpellInfo[i]; }
-CreatureAI::CreatureAI(Creature* creature) : UnitAI(creature), me(creature), _boundary(nullptr), _negateBoundary(false), _moveInLOSLocked(false)
+CreatureAI::CreatureAI(Creature* creature) : UnitAI(creature), me(creature), _boundary(nullptr), _negateBoundary(false), _isEngaged(false), _moveInLOSLocked(false)
{
}
@@ -200,6 +200,12 @@ void CreatureAI::JustAppeared()
}
}
+void CreatureAI::JustEnteredCombat(Unit* who)
+{
+ if (!IsEngaged() && !me->CanHaveThreatList())
+ EngagementStart(who);
+}
+
void CreatureAI::EnterEvadeMode(EvadeReason why)
{
if (!_EnterEvadeMode(why))
@@ -228,7 +234,7 @@ void CreatureAI::EnterEvadeMode(EvadeReason why)
bool CreatureAI::UpdateVictim()
{
- if (!me->IsEngaged())
+ if (!IsEngaged())
return false;
if (!me->HasReactState(REACT_PASSIVE))
@@ -250,23 +256,53 @@ bool CreatureAI::UpdateVictim()
return true;
}
+void CreatureAI::EngagementStart(Unit* who)
+{
+ if (_isEngaged)
+ {
+ TC_LOG_ERROR("scripts.ai", "CreatureAI::EngagementStart called even though creature is already engaged. Creature debug info:\n%s", me->GetDebugInfo().c_str());
+ return;
+ }
+ _isEngaged = true;
+
+ me->AtEngage(who);
+}
+
+void CreatureAI::EngagementOver()
+{
+ if (!_isEngaged)
+ {
+ TC_LOG_ERROR("scripts.ai", "CreatureAI::EngagementOver called even though creature is not currently engaged. Creature debug info:\n%s", me->GetDebugInfo().c_str());
+ return;
+ }
+ _isEngaged = false;
+
+ me->AtDisengage();
+}
+
bool CreatureAI::_EnterEvadeMode(EvadeReason /*why*/)
{
+ if (me->IsInEvadeMode())
+ return false;
+
if (!me->IsAlive())
+ {
+ EngagementOver();
return false;
+ }
me->RemoveAurasOnEvade();
me->CombatStop(true);
- me->GetThreatManager().NotifyDisengaged();
me->LoadCreaturesAddon();
me->SetLootRecipient(nullptr);
me->ResetPlayerDamageReq();
me->SetLastDamagedTime(0);
me->SetCannotReachTarget(false);
me->DoNotReacquireTarget();
+ EngagementOver();
- return !me->IsInEvadeMode();
+ return true;
}
static const uint32 BOUNDARY_VISUALIZE_CREATURE = 15425;
diff --git a/src/server/game/AI/CreatureAI.h b/src/server/game/AI/CreatureAI.h
index b8626ab4615..4f3ade72364 100644
--- a/src/server/game/AI/CreatureAI.h
+++ b/src/server/game/AI/CreatureAI.h
@@ -101,6 +101,8 @@ class TC_GAME_API CreatureAI : public UnitAI
virtual ~CreatureAI();
+ bool IsEngaged() const { return _isEngaged; }
+
void Talk(uint8 id, WorldObject const* whisperTarget = nullptr);
/// == Reactions At =================================
@@ -114,11 +116,17 @@ class TC_GAME_API CreatureAI : public UnitAI
// Called for reaction at stopping attack at no attackers or targets
virtual void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER);
+ // Called for reaction whenever we start being in combat (overridden from base UnitAI)
+ void JustEnteredCombat(Unit* /*who*/) override;
+
+ // Called for reaction whenever a new non-offline unit is added to the threat list
+ virtual void JustStartedThreateningMe(Unit* who) { if (!IsEngaged()) EngagementStart(who); }
+
// Called for reaction when initially engaged - this will always happen _after_ JustEnteredCombat
virtual void JustEngagedWith(Unit* /*who*/) { }
// Called when the creature is killed
- virtual void JustDied(Unit* /*killer*/) { }
+ virtual void JustDied(Unit* /*killer*/) { if (IsEngaged()) EngagementOver(); }
// Called when the creature kills a unit
virtual void KilledUnit(Unit* /*victim*/) { }
@@ -226,6 +234,8 @@ class TC_GAME_API CreatureAI : public UnitAI
// intended for encounter design/debugging. do not use for other purposes. expensive.
int32 VisualizeBoundary(uint32 duration, Unit* owner = nullptr, bool fill = false) const;
+
+ // boundary system methods
virtual bool CheckInRoom();
CreatureBoundary const* GetBoundary() const { return _boundary; }
void SetBoundary(CreatureBoundary const* boundary, bool negativeBoundaries = false);
@@ -234,6 +244,8 @@ class TC_GAME_API CreatureAI : public UnitAI
bool IsInBoundary(Position const* who = nullptr) const;
protected:
+ void EngagementStart(Unit* who);
+ void EngagementOver();
virtual void MoveInLineOfSight(Unit* /*who*/);
bool _EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER);
@@ -244,6 +256,7 @@ class TC_GAME_API CreatureAI : public UnitAI
private:
void OnOwnerCombatInteraction(Unit* target);
+ bool _isEngaged;
bool _moveInLOSLocked;
};
diff --git a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp
index dcbdd631edf..cd42ffa5157 100644
--- a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp
+++ b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp
@@ -97,8 +97,9 @@ void EscortAI::EnterEvadeMode(EvadeReason /*why*/)
{
me->RemoveAllAuras();
me->CombatStop(true);
- me->GetThreatManager().NotifyDisengaged();
me->SetLootRecipient(nullptr);
+
+ EngagementOver();
if (HasEscortState(STATE_ESCORT_ESCORTING))
{
diff --git a/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp b/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp
index 04c6f0a3376..6d9c9d6fe69 100644
--- a/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp
+++ b/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp
@@ -104,14 +104,18 @@ void FollowerAI::JustAppeared()
void FollowerAI::EnterEvadeMode(EvadeReason /*why*/)
{
if (!me->IsAlive())
+ {
+ EngagementOver();
return;
+ }
me->RemoveAllAuras();
me->CombatStop(true);
- me->GetThreatManager().NotifyDisengaged();
me->SetLootRecipient(nullptr);
me->SetCannotReachTarget(false);
me->DoNotReacquireTarget();
+
+ EngagementOver();
if (HasFollowState(STATE_FOLLOW_INPROGRESS))
{
diff --git a/src/server/game/Combat/CombatManager.cpp b/src/server/game/Combat/CombatManager.cpp
index bd2f1f5b7ac..b36eb2ddd6e 100644
--- a/src/server/game/Combat/CombatManager.cpp
+++ b/src/server/game/Combat/CombatManager.cpp
@@ -294,13 +294,8 @@ void CombatManager::EndAllPvPCombat()
/*static*/ void CombatManager::NotifyAICombat(Unit* me, Unit* other)
{
- if (!me->IsAIEnabled())
- return;
- me->GetAI()->JustEnteredCombat(other);
-
- if (Creature* cMe = me->ToCreature())
- if (!cMe->CanHaveThreatList())
- cMe->AI()->JustEngagedWith(other);
+ if (UnitAI* ai = me->GetAI())
+ ai->JustEnteredCombat(other);
}
void CombatManager::PutReference(ObjectGuid const& guid, CombatReference* ref)
@@ -337,14 +332,14 @@ bool CombatManager::UpdateOwnerCombatState() const
{
_owner->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IN_COMBAT);
_owner->AtEnterCombat();
- if (!_owner->CanHaveThreatList() && !_owner->IsEngaged())
+ if (_owner->GetTypeId() == TYPEID_UNIT)
_owner->AtEngage(GetAnyTarget());
}
else
{
_owner->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IN_COMBAT);
_owner->AtExitCombat();
- if (_owner->IsEngaged() && !(_owner->ToCreature() && _owner->CanHaveThreatList() && _owner->ToCreature()->IsAIEnabled()))
+ if (_owner->GetTypeId() != TYPEID_UNIT)
_owner->AtDisengage();
}
diff --git a/src/server/game/Combat/ThreatManager.cpp b/src/server/game/Combat/ThreatManager.cpp
index 626d42042b5..c0998b8e33c 100644
--- a/src/server/game/Combat/ThreatManager.cpp
+++ b/src/server/game/Combat/ThreatManager.cpp
@@ -73,6 +73,7 @@ void ThreatReference::UpdateOffline()
{
_online = ShouldBeSuppressed() ? ONLINE_STATE_SUPPRESSED : ONLINE_STATE_ONLINE;
HeapNotifyIncreased();
+ _mgr.RegisterForAIUpdate(this);
}
}
@@ -201,10 +202,11 @@ Unit* ThreatManager::GetCurrentVictim()
{
if (!_currentVictimRef || _currentVictimRef->ShouldBeOffline())
UpdateVictim();
- return const_cast<ThreatManager const*>(this)->GetCurrentVictim();
+ ASSERT(!_currentVictimRef || _currentVictimRef->IsAvailable());
+ return _currentVictimRef ? _currentVictimRef->GetVictim() : nullptr;
}
-Unit* ThreatManager::GetCurrentVictim() const
+Unit* ThreatManager::GetLastVictim() const
{
if (_currentVictimRef && !_currentVictimRef->ShouldBeOffline())
return _currentVictimRef->GetVictim();
@@ -246,7 +248,7 @@ float ThreatManager::GetThreat(Unit const* who, bool includeOffline) const
return (includeOffline || it->second->IsAvailable()) ? it->second->GetThreat() : 0.0f;
}
-std::vector<ThreatReference*> ThreatManager::GetModifiableThreatList() const
+std::vector<ThreatReference*> ThreatManager::GetModifiableThreatList()
{
std::vector<ThreatReference*> list;
list.reserve(_myThreatListEntries.size());
@@ -386,16 +388,14 @@ void ThreatManager::AddThreat(Unit* target, float amount, SpellInfo const* spell
PutThreatListRef(target->GetGUID(), ref);
target->GetThreatManager().PutThreatenedByMeRef(_owner->GetGUID(), ref);
- // afterwards, we evaluate whether this is an online reference (it might not be an acceptable target, but we need to add it to our threat list before we check!)
ref->UpdateOffline();
- if (ref->IsOnline()) // ...and if the ref is online it also gets the threat it should have
+ if (ref->IsOnline()) // we only add the threat if the ref is currently available
ref->AddThreat(amount);
- if (!_owner->IsEngaged())
- {
- _owner->AtEngage(target);
+ if (!_currentVictimRef)
UpdateVictim();
- }
+ else
+ ProcessAIUpdates();
}
void ThreatManager::ScaleThreat(Unit* target, float factor)
@@ -480,14 +480,6 @@ void ThreatManager::ClearAllThreat()
}
}
-void ThreatManager::NotifyDisengaged()
-{
- // note: i don't really like having this here
- // (maybe engage flag should be in creature ai? it's inherently an AI property...)
- if (_owner->IsEngaged())
- _owner->AtDisengage();
-}
-
void ThreatManager::FixateTarget(Unit* target)
{
if (target)
@@ -522,6 +514,7 @@ void ThreatManager::UpdateVictim()
_needClientUpdate = false;
}
+ ProcessAIUpdates();
}
ThreatReference const* ThreatManager::ReselectVictim()
@@ -530,7 +523,7 @@ ThreatReference const* ThreatManager::ReselectVictim()
return nullptr;
for (auto const& pair : _myThreatListEntries)
- pair.second->UpdateOffline();
+ pair.second->UpdateOffline(); // AI notifies are processed in ::UpdateVictim caller
// fixated target is always preferred
if (_fixateRef && _fixateRef->IsAvailable())
@@ -579,6 +572,16 @@ ThreatReference const* ThreatManager::ReselectVictim()
return nullptr;
}
+void ThreatManager::ProcessAIUpdates()
+{
+ CreatureAI* ai = ASSERT_NOTNULL(_owner->ToCreature())->AI();
+ std::vector<ThreatReference const*> v(std::move(_needsAIUpdate)); // _needClientUpdate is now empty in case this triggers a recursive call
+ if (!ai)
+ return;
+ for (ThreatReference const* ref : v)
+ ai->JustStartedThreateningMe(ref->GetVictim());
+}
+
// returns true if a is LOWER on the threat list than b
/*static*/ bool ThreatManager::CompareReferencesLT(ThreatReference const* a, ThreatReference const* b, float aWeight)
{
diff --git a/src/server/game/Combat/ThreatManager.h b/src/server/game/Combat/ThreatManager.h
index 1eda7d01e15..bb745ad3e1b 100644
--- a/src/server/game/Combat/ThreatManager.h
+++ b/src/server/game/Combat/ThreatManager.h
@@ -103,7 +103,7 @@ class TC_GAME_API ThreatManager
bool CanHaveThreatList() const { return _ownerCanHaveThreatList; }
// returns the current victim - this can be nullptr if owner's threat list is empty, or has only offline targets
Unit* GetCurrentVictim();
- Unit* GetCurrentVictim() const;
+ Unit* GetLastVictim() const;
// returns an arbitrary non-offline victim from owner's threat list if one exists, nullptr otherwise
Unit* GetAnyTarget() const;
@@ -121,10 +121,10 @@ class TC_GAME_API ThreatManager
Trinity::IteratorPair<ThreatListIterator> GetUnsortedThreatList() const { return { _myThreatListEntries.begin(), _myThreatListEntries.end() }; }
// slightly slower than GetUnsorted, but, well...sorted - only use it if you need the sorted property, of course
// this iterator pair will invalidate on any modification (even indirect) of the threat list; spell casts and similar can all induce this!
- // note: current tank is NOT guaranteed to be the first entry in this list - check GetCurrentVictim separately if you want that!
+ // note: current tank is NOT guaranteed to be the first entry in this list - check GetLastVictim separately if you want that!
Trinity::IteratorPair<threat_list_heap::ordered_iterator> GetSortedThreatList() const { return { _sortedThreatList.ordered_begin(), _sortedThreatList.ordered_end() }; }
// slowest of the three threat list getters (by far), but lets you modify the threat references - this is also sorted
- std::vector<ThreatReference*> GetModifiableThreatList() const;
+ std::vector<ThreatReference*> GetModifiableThreatList();
// does any unit have a threat list entry with victim == this.owner?
bool IsThreateningAnyone(bool includeOffline = false) const;
@@ -154,9 +154,6 @@ class TC_GAME_API ThreatManager
void ClearThreat(ThreatReference* ref);
// Removes all targets from the threat list (will cause evade in UpdateVictim if called)
void ClearAllThreat();
- // THIS SHOULD ONLY BE CALLED FROM A CREATURE'S AI (typically in EnterEvadeMode)
- // notify the unit that the AI has disengaged
- void NotifyDisengaged();
// Fixate on the passed target; this target will always be selected until the fixate is cleared
// (if the target is not in the threat list, does nothing)
@@ -204,6 +201,12 @@ class TC_GAME_API ThreatManager
threat_list_heap _sortedThreatList;
std::unordered_map<ObjectGuid, ThreatReference*> _myThreatListEntries;
+ // AI notifies are delayed to ensure we are in a consistent state before we call out to arbitrary logic
+ // threat references might register themselves here when ::UpdateOffline() is called - MAKE SURE THIS IS PROCESSED JUST BEFORE YOU EXIT THREATMANAGER LOGIC
+ void ProcessAIUpdates();
+ void RegisterForAIUpdate(ThreatReference const* ref) { _needsAIUpdate.push_back(ref); }
+ std::vector<ThreatReference const*> _needsAIUpdate;
+
// picks a new victim - called from ::Update periodically
void UpdateVictim();
ThreatReference const* ReselectVictim();
@@ -278,7 +281,7 @@ class TC_GAME_API ThreatReference
_owner(reinterpret_cast<Creature*>(mgr->_owner)), _mgr(*mgr), _victim(victim),
_baseAmount(0.0f), _tempModifier(0), _taunted(TAUNT_STATE_NONE)
{
- _online = ShouldBeSuppressed() ? ONLINE_STATE_SUPPRESSED : ONLINE_STATE_ONLINE;
+ _online = ONLINE_STATE_OFFLINE;
}
void UnregisterAndFree();
diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp
index 60a428c8dd4..930ba1ad866 100644
--- a/src/server/game/Entities/Creature/Creature.cpp
+++ b/src/server/game/Entities/Creature/Creature.cpp
@@ -3232,6 +3232,13 @@ bool Creature::CanGiveExperience() const
&& !(GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_XP_AT_KILL);
}
+bool Creature::IsEngaged() const
+{
+ if (CreatureAI const* ai = AI())
+ return ai->IsEngaged();
+ return false;
+}
+
void Creature::AtEngage(Unit* target)
{
Unit::AtEngage(target);
diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h
index 55386730641..e2016f09cc4 100644
--- a/src/server/game/Entities/Creature/Creature.h
+++ b/src/server/game/Entities/Creature/Creature.h
@@ -359,6 +359,7 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma
bool CanGiveExperience() const;
+ bool IsEngaged() const override;
void AtEngage(Unit* target) override;
void AtDisengage() override;
diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp
index a8271cd64bd..dee11e65a3b 100644
--- a/src/server/game/Entities/Unit/Unit.cpp
+++ b/src/server/game/Entities/Unit/Unit.cpp
@@ -294,7 +294,7 @@ Unit::Unit(bool isWorldObject) :
m_ControlledByPlayer(false), m_AutoRepeatFirstCast(false), m_procDeep(0), m_transformSpell(0),
m_removedAurasCount(0), m_unitMovedByMe(nullptr), m_playerMovingMe(nullptr), m_charmer(nullptr), m_charmed(nullptr),
i_motionMaster(new MotionMaster(this)), m_regenTimer(0), m_vehicle(nullptr), m_vehicleKit(nullptr),
- m_unitTypeMask(UNIT_MASK_NONE), m_Diminishing(), m_isEngaged(false), m_combatManager(this), m_threatManager(this),
+ m_unitTypeMask(UNIT_MASK_NONE), m_Diminishing(), m_combatManager(this), m_threatManager(this),
m_aiLocked(false), m_comboTarget(nullptr), m_comboPoints(0), m_spellHistory(new SpellHistory(this))
{
m_objectType |= TYPEMASK_UNIT;
diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h
index 2e262910b6a..16d1ffb5e34 100644
--- a/src/server/game/Entities/Unit/Unit.h
+++ b/src/server/game/Entities/Unit/Unit.h
@@ -1027,10 +1027,10 @@ class TC_GAME_API Unit : public WorldObject
/// ====================== THREAT & COMBAT ====================
bool CanHaveThreatList() const { return m_threatManager.CanHaveThreatList(); }
- // This value can be different from IsInCombat:
+ // This value can be different from IsInCombat, for example:
// - when a projectile spell is midair against a creature (combat on launch - threat+aggro on impact)
// - when the creature has no targets left, but the AI has not yet ceased engaged logic
- bool IsEngaged() const { return m_isEngaged; }
+ virtual bool IsEngaged() const { return IsInCombat(); }
bool IsEngagedBy(Unit const* who) const { return CanHaveThreatList() ? IsThreatenedBy(who) : IsInCombatWith(who); }
void EngageWithTarget(Unit* who); // Adds target to threat list if applicable, otherwise just sets combat state
// Combat handling
@@ -1769,11 +1769,12 @@ class TC_GAME_API Unit : public WorldObject
void ProcessPositionDataChanged(PositionFullTerrainStatus const& data) override;
virtual void ProcessTerrainStatusUpdate(ZLiquidStatus status, Optional<LiquidData> const& liquidData);
+ // notifiers
virtual void AtEnterCombat() { }
virtual void AtExitCombat();
- virtual void AtEngage(Unit* /*target*/) { m_isEngaged = true; }
- virtual void AtDisengage() { m_isEngaged = false; }
+ virtual void AtEngage(Unit* /*target*/) {}
+ virtual void AtDisengage() {}
private:
@@ -1800,7 +1801,6 @@ class TC_GAME_API Unit : public WorldObject
DiminishingReturn m_Diminishing[DIMINISHING_MAX];
// Threat+combat management
- bool m_isEngaged;
friend class CombatManager;
CombatManager m_combatManager;
friend class ThreatManager;
diff --git a/src/server/scripts/Commands/cs_debug.cpp b/src/server/scripts/Commands/cs_debug.cpp
index fba83d2edfb..12385a46865 100644
--- a/src/server/scripts/Commands/cs_debug.cpp
+++ b/src/server/scripts/Commands/cs_debug.cpp
@@ -868,7 +868,7 @@ public:
ThreatManager& mgr = target->GetThreatManager();
if (!target->IsAlive())
{
- handler->PSendSysMessage("%s (GUID %u) is not alive.", target->GetName().c_str(), target->GetGUID().GetCounter());
+ handler->PSendSysMessage("%s (GUID %u) is not alive.%s", target->GetName().c_str(), target->GetGUID().GetCounter(), target->IsEngaged() ? " (It is, however, engaged. Huh?)" : "");
return true;
}
diff --git a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/instance_blackrock_depths.cpp b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/instance_blackrock_depths.cpp
index 64f8dd07af0..e40f750154b 100644
--- a/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/instance_blackrock_depths.cpp
+++ b/src/server/scripts/EasternKingdoms/BlackrockMountain/BlackrockDepths/instance_blackrock_depths.cpp
@@ -395,16 +395,9 @@ public:
if (Creature* boss = instance->GetCreature(TombBossGUIDs[i]))
{
if (!boss->IsAlive())
- {//do not call EnterEvadeMode(), it will create infinit loops
boss->Respawn();
- boss->RemoveAllAuras();
- boss->CombatStop(true);
- boss->GetThreatManager().NotifyDisengaged();
- boss->LoadCreaturesAddon();
- boss->GetMotionMaster()->MoveTargetedHome();
- boss->SetLootRecipient(nullptr);
- }
- boss->SetFaction(FACTION_FRIENDLY);
+ else
+ boss->SetFaction(FACTION_FRIENDLY);
}
}
GhostKillCount = 0;
diff --git a/src/server/scripts/EasternKingdoms/Karazhan/bosses_opera.cpp b/src/server/scripts/EasternKingdoms/Karazhan/bosses_opera.cpp
index c46ac193ab6..cff444a3650 100644
--- a/src/server/scripts/EasternKingdoms/Karazhan/bosses_opera.cpp
+++ b/src/server/scripts/EasternKingdoms/Karazhan/bosses_opera.cpp
@@ -1258,7 +1258,6 @@ public:
Julianne->GetMotionMaster()->Clear();
Julianne->setDeathState(JUST_DIED);
Julianne->CombatStop(true);
- Julianne->GetThreatManager().NotifyDisengaged();
Julianne->SetUInt32Value(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE);
}
return;
@@ -1528,7 +1527,6 @@ void boss_julianne::boss_julianneAI::DamageTaken(Unit* /*done_by*/, uint32 &dama
Romulo->GetMotionMaster()->Clear();
Romulo->setDeathState(JUST_DIED);
Romulo->CombatStop(true);
- Romulo->GetThreatManager().NotifyDisengaged();
Romulo->SetUInt32Value(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE);
}
diff --git a/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter5.cpp b/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter5.cpp
index 931df7115d0..d701f0f4300 100644
--- a/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter5.cpp
+++ b/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter5.cpp
@@ -1387,9 +1387,10 @@ public:
me->RemoveAurasDueToSpell(SPELL_THE_MIGHT_OF_MOGRAINE);
me->RemoveAllAuras();
me->CombatStop(true);
- me->GetThreatManager().NotifyDisengaged();
me->InterruptNonMeleeSpells(false);
me->SetWalk(false);
+
+ EngagementOver();
for (uint8 i = 0; i < ENCOUNTER_DEFENDER_NUMBER; ++i)
DespawnNPC(uiDefenderGUID[i]);
@@ -1406,9 +1407,9 @@ public:
if (Creature* temp = ObjectAccessor::GetCreature(*me, uiKorfaxGUID))
{
+ temp->AI()->EnterEvadeMode();
temp->RemoveAllAuras();
temp->CombatStop(true);
- temp->GetThreatManager().NotifyDisengaged();
temp->AttackStop();
temp->SetFaction(me->GetFaction());
temp->SetWalk(false);
@@ -1427,9 +1428,9 @@ public:
if (Creature* temp = ObjectAccessor::GetCreature(*me, uiEligorGUID))
{
+ temp->AI()->EnterEvadeMode();
temp->RemoveAllAuras();
temp->CombatStop(true);
- temp->GetThreatManager().NotifyDisengaged();
temp->AttackStop();
temp->SetFaction(me->GetFaction());
temp->SetWalk(false);
@@ -1439,9 +1440,9 @@ public:
if (Creature* temp = ObjectAccessor::GetCreature(*me, uiKoltiraGUID))
{
+ temp->AI()->EnterEvadeMode();
temp->RemoveAllAuras();
temp->CombatStop(true);
- temp->GetThreatManager().NotifyDisengaged();
temp->AttackStop();
temp->SetFaction(me->GetFaction());
temp->SetWalk(false);
@@ -1454,9 +1455,9 @@ public:
if (Creature* temp = ObjectAccessor::GetCreature(*me, uiThassarianGUID))
{
+ temp->AI()->EnterEvadeMode();
temp->RemoveAllAuras();
temp->CombatStop(true);
- temp->GetThreatManager().NotifyDisengaged();
temp->AttackStop();
temp->SetFaction(me->GetFaction());
temp->SetWalk(false);
diff --git a/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/hyjalAI.cpp b/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/hyjalAI.cpp
index 9abbf1269c5..c5b67fdd915 100644
--- a/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/hyjalAI.cpp
+++ b/src/server/scripts/Kalimdor/CavernsOfTime/BattleForMountHyjal/hyjalAI.cpp
@@ -424,9 +424,10 @@ void hyjalAI::EnterEvadeMode(EvadeReason /*why*/)
if (me->GetEntry() != JAINA)
me->RemoveAllAuras();
me->CombatStop(true);
- me->GetThreatManager().NotifyDisengaged();
+
+ EngagementOver();
+
me->LoadCreaturesAddon();
-
if (me->IsAlive())
me->GetMotionMaster()->MoveTargetedHome();
diff --git a/src/server/scripts/Kalimdor/zone_desolace.cpp b/src/server/scripts/Kalimdor/zone_desolace.cpp
index e57fb68463c..ffe447d5cfa 100644
--- a/src/server/scripts/Kalimdor/zone_desolace.cpp
+++ b/src/server/scripts/Kalimdor/zone_desolace.cpp
@@ -90,9 +90,11 @@ public:
me->UpdateEntry(NPC_TAMED_KODO);
me->CombatStop();
- me->GetThreatManager().NotifyDisengaged();
me->SetFaction(FACTION_FRIENDLY);
me->SetSpeedRate(MOVE_RUN, 0.6f);
+
+ EngagementOver();
+
me->GetMotionMaster()->MoveFollow(caster, PET_FOLLOW_DIST, me->GetFollowAngle());
me->setActive(true);
me->RemoveFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_GOSSIP);
diff --git a/src/server/scripts/Kalimdor/zone_the_barrens.cpp b/src/server/scripts/Kalimdor/zone_the_barrens.cpp
index 90ce8c130f6..3bf0afe1c7d 100644
--- a/src/server/scripts/Kalimdor/zone_the_barrens.cpp
+++ b/src/server/scripts/Kalimdor/zone_the_barrens.cpp
@@ -221,11 +221,11 @@ public:
{
me->RemoveAllAuras();
me->CombatStop(true);
- me->GetThreatManager().NotifyDisengaged();
-
me->StopMoving();
+
+ EngagementOver();
+
me->GetMotionMaster()->MoveIdle();
-
me->SetFaction(FACTION_FRIENDLY_F);
me->HandleEmoteCommand(EMOTE_ONESHOT_SALUTE);
}
diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_icecrown_gunship_battle.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_icecrown_gunship_battle.cpp
index 45b64917dc4..291b5a8b395 100644
--- a/src/server/scripts/Northrend/IcecrownCitadel/boss_icecrown_gunship_battle.cpp
+++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_icecrown_gunship_battle.cpp
@@ -580,7 +580,7 @@ struct gunship_npc_AI : public ScriptedAI
return;
me->CombatStop(true);
- me->GetThreatManager().NotifyDisengaged();
+ EngagementOver();
me->GetMotionMaster()->MoveTargetedHome();
}
@@ -725,7 +725,6 @@ class npc_gunship : public CreatureScript
Creature* stalker = *itr;
stalker->RemoveAllAuras();
stalker->CombatStop(true);
- stalker->GetThreatManager().NotifyDisengaged();
}
uint32 explosionSpell = isVictory ? SPELL_EXPLOSION_VICTORY : SPELL_EXPLOSION_WIPE;
@@ -884,7 +883,7 @@ class npc_high_overlord_saurfang_igb : public CreatureScript
return;
me->CombatStop(true);
- me->GetThreatManager().NotifyDisengaged();
+ EngagementOver();
me->GetMotionMaster()->MoveTargetedHome();
Reset();
@@ -1148,7 +1147,7 @@ class npc_muradin_bronzebeard_igb : public CreatureScript
return;
me->CombatStop(true);
- me->GetThreatManager().NotifyDisengaged();
+ EngagementOver();
me->GetMotionMaster()->MoveTargetedHome();
Reset();
diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp
index 086b0eeeca2..e0cfd7c7f67 100644
--- a/src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp
+++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_the_lich_king.cpp
@@ -1801,7 +1801,7 @@ class npc_terenas_menethil : public CreatureScript
return;
me->CombatStop(false);
- me->GetThreatManager().NotifyDisengaged();
+ EngagementOver();
}
void DamageTaken(Unit* /*attacker*/, uint32& damage) override
diff --git a/src/server/scripts/Northrend/Naxxramas/boss_patchwerk.cpp b/src/server/scripts/Northrend/Naxxramas/boss_patchwerk.cpp
index 76257a25b44..c658e5985e8 100644
--- a/src/server/scripts/Northrend/Naxxramas/boss_patchwerk.cpp
+++ b/src/server/scripts/Northrend/Naxxramas/boss_patchwerk.cpp
@@ -122,7 +122,7 @@ public:
ThreatReference* secondThreat = nullptr;
ThreatReference* thirdThreat = nullptr;
- ThreatManager const& mgr = me->GetThreatManager();
+ ThreatManager& mgr = me->GetThreatManager();
Unit* currentVictim = mgr.GetCurrentVictim();
auto list = mgr.GetModifiableThreatList();
auto it = list.begin(), end = list.end();
diff --git a/src/server/scripts/Northrend/Nexus/Oculus/boss_urom.cpp b/src/server/scripts/Northrend/Nexus/Oculus/boss_urom.cpp
index eebed61e227..2b1a7853139 100644
--- a/src/server/scripts/Northrend/Nexus/Oculus/boss_urom.cpp
+++ b/src/server/scripts/Northrend/Nexus/Oculus/boss_urom.cpp
@@ -292,7 +292,7 @@ class boss_urom : public CreatureScript
{
me->RemoveAllAuras();
me->CombatStop(false);
- me->GetThreatManager().NotifyDisengaged();
+ EngagementOver();
}
void SpellHit(Unit* /*caster*/, SpellInfo const* spellInfo) override
diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_freya.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_freya.cpp
index b41f7639b90..dd75d8c9906 100644
--- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_freya.cpp
+++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_freya.cpp
@@ -634,7 +634,7 @@ class boss_freya : public CreatureScript
Elder->RemoveAllAuras();
Elder->AttackStop();
Elder->CombatStop(true);
- Elder->GetThreatManager().NotifyDisengaged();
+ EngagementOver();
Elder->AI()->DoAction(ACTION_ELDER_FREYA_KILLED);
}
}
diff --git a/src/server/scripts/Northrend/zone_borean_tundra.cpp b/src/server/scripts/Northrend/zone_borean_tundra.cpp
index 7993913252f..396f5d52c5d 100644
--- a/src/server/scripts/Northrend/zone_borean_tundra.cpp
+++ b/src/server/scripts/Northrend/zone_borean_tundra.cpp
@@ -753,7 +753,6 @@ public:
Creature* owner = GetOwner()->ToCreature();
owner->RemoveAllAurasExceptType(SPELL_AURA_DUMMY);
owner->CombatStop(true);
- owner->GetThreatManager().NotifyDisengaged();
owner->GetMotionMaster()->Clear();
owner->GetMotionMaster()->MoveFollow(GetCaster(), 4.0f, 0.0f);
owner->CastSpell(owner, SPELL_SUBDUED, true);
diff --git a/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_leotheras_the_blind.cpp b/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_leotheras_the_blind.cpp
index ee12d3f7772..33e1bf0d82b 100644
--- a/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_leotheras_the_blind.cpp
+++ b/src/server/scripts/Outland/CoilfangReservoir/SerpentShrine/boss_leotheras_the_blind.cpp
@@ -525,7 +525,7 @@ public:
{
ThreatManager const& mgr = me->GetThreatManager();
std::list<Unit*> TargetList;
- Unit* currentVictim = mgr.GetCurrentVictim();
+ Unit* currentVictim = mgr.GetLastVictim();
for (ThreatReference const* ref : mgr.GetSortedThreatList())
{
if (Player* tempTarget = ref->GetVictim()->ToPlayer())
diff --git a/src/server/scripts/Outland/zone_hellfire_peninsula.cpp b/src/server/scripts/Outland/zone_hellfire_peninsula.cpp
index 676e2798d79..68be3572bf9 100644
--- a/src/server/scripts/Outland/zone_hellfire_peninsula.cpp
+++ b/src/server/scripts/Outland/zone_hellfire_peninsula.cpp
@@ -102,7 +102,7 @@ public:
me->SetFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_QUESTGIVER);
me->RemoveAllAuras();
me->CombatStop(true);
- me->GetThreatManager().NotifyDisengaged();
+ EngagementOver();
Talk(SAY_FREE);
return;
}
@@ -974,7 +974,7 @@ public:
me->RestoreFaction();
me->RemoveAllAuras();
me->CombatStop(true);
- me->GetThreatManager().NotifyDisengaged();
+ EngagementOver();
me->SetFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_QUESTGIVER);
me->SetImmuneToPC(true);
Talk(SAY_DEFEATED);
diff --git a/src/server/scripts/Outland/zone_terokkar_forest.cpp b/src/server/scripts/Outland/zone_terokkar_forest.cpp
index 9a000906d7c..c9a30cff9af 100644
--- a/src/server/scripts/Outland/zone_terokkar_forest.cpp
+++ b/src/server/scripts/Outland/zone_terokkar_forest.cpp
@@ -92,7 +92,7 @@ public:
me->SetStandState(UNIT_STAND_STATE_SIT);
me->RemoveAllAuras();
me->CombatStop(true);
- me->GetThreatManager().NotifyDisengaged();
+ EngagementOver();
UnkorUnfriendly_Timer = 60000;
}
diff --git a/src/server/scripts/Pet/pet_priest.cpp b/src/server/scripts/Pet/pet_priest.cpp
index 573dd9750ea..3ef8383e53c 100644
--- a/src/server/scripts/Pet/pet_priest.cpp
+++ b/src/server/scripts/Pet/pet_priest.cpp
@@ -50,7 +50,7 @@ class npc_pet_pri_lightwell : public CreatureScript
return;
me->CombatStop(true);
- me->GetThreatManager().NotifyDisengaged();
+ EngagementOver();
me->ResetPlayerDamageReq();
}
};