Core: Combat/threat system rewrite (PR #19930)

- PvE combat is now always mutual. UNIT_FLAG_IN_COMBAT is backed by actual references to the units we're in combat with.
- PvP combat is now also tracked, and almost always mutual; spells like Vanish and Feign Death can break this rule. That means we can easily determine a list of players we're fighting.
- By extension, IsInCombatWith now has sensible behavior when invoked on nonplayers.
- Threat and combat systems are no longer the same.
  - They still have an enforced relationship (threat implies combat - clearing combat clears threat)...
  - ...but we can have combat without threat. A creature (with threat list) isn't considered to be engaged until it has an entry on its threat list...
  - ...which means we can now faithfully replicate retail engage behavior. Combat on projectile launch - engagement start on projectile impact. Yay for progress!
- AI method refactor, as already ported in 6113b9d - `JustEngagedWith`, `JustEnteredCombat` and `JustExitedCombat`.
- Vehicle threat is now properly pooled on the main vehicle body (fixes #16542).
- Various edge case bug fixes for threat redirects (Misdirection "cancelling" Vigilance and similar).
- Target re-selection is now significantly faster.
- Fixed a ton of other smaller edge case bugs, probably.

Closes #7951 and #19998.

(cherry picked from commit 532ab1c7f8653d1a2e48aa1f1f8a9ba1041d4bb7)
This commit is contained in:
Treeston
2021-05-16 21:56:01 +02:00
committed by Shauren
parent 5158136ee8
commit 34c7810fe5
57 changed files with 2159 additions and 2311 deletions
+2 -9
View File
@@ -69,7 +69,6 @@ void PetAI::_stopAttack()
me->GetMotionMaster()->Clear();
me->GetMotionMaster()->MoveIdle();
me->CombatStop();
me->getHostileRefManager().deleteReferences();
return;
}
@@ -459,8 +458,7 @@ void PetAI::HandleReturnMovement()
me->GetMotionMaster()->MoveFollow(me->GetCharmerOrOwner(), PET_FOLLOW_DIST, me->GetFollowAngle());
}
}
me->ClearInPetCombat();
me->RemoveUnitFlag(UNIT_FLAG_PET_IN_COMBAT); // on player pets, this flag indicates that we're actively going after a target - we're returning, so remove it
}
void PetAI::DoAttack(Unit* target, bool chase)
@@ -470,12 +468,7 @@ void PetAI::DoAttack(Unit* target, bool chase)
if (me->Attack(target, true))
{
// properly fix fake combat after pet is sent to attack
if (Unit* owner = me->GetOwner())
owner->AddUnitFlag(UNIT_FLAG_PET_IN_COMBAT);
me->AddUnitFlag(UNIT_FLAG_PET_IN_COMBAT);
me->AddUnitFlag(UNIT_FLAG_PET_IN_COMBAT); // on player pets, this flag indicates we're actively going after a target - that's what we're doing, so set it
// Play sound to let the player know the pet is attacking something it picked on its own
if (me->HasReactState(REACT_AGGRESSIVE) && !me->GetCharmInfo()->IsCommandAttack())
me->SendPetAIReaction(me->GetGUID());
+1
View File
@@ -19,6 +19,7 @@
#define TRINITY_UNITAI_H
#include "Containers.h"
#include "Errors.h"
#include "EventMap.h"
#include "ObjectGuid.h"
#include "SpellDefines.h"
+30 -40
View File
@@ -67,61 +67,49 @@ void CreatureAI::DoZoneInCombat(Creature* creature /*= nullptr*/, float maxRange
if (!creature)
creature = me;
if (!creature->CanHaveThreatList())
return;
Map* map = creature->GetMap();
if (!map->IsDungeon()) //use IsDungeon instead of Instanceable, in case battlegrounds will be instantiated
if (creature->CanHaveThreatList())
{
TC_LOG_ERROR("misc", "DoZoneInCombat call for map that isn't an instance (creature entry = %d)", creature->GetTypeId() == TYPEID_UNIT ? creature->ToCreature()->GetEntry() : 0);
return;
}
if (!creature->HasReactState(REACT_PASSIVE) && !creature->GetVictim())
{
if (Unit* nearTarget = creature->SelectNearestTarget(maxRangeToNearestTarget))
creature->AI()->AttackStart(nearTarget);
else if (creature->IsSummon())
if (!map->IsDungeon()) //use IsDungeon instead of Instanceable, in case battlegrounds will be instantiated
{
if (Unit* summoner = creature->ToTempSummon()->GetSummoner())
TC_LOG_ERROR("misc", "DoZoneInCombat call for map that isn't an instance (creature entry = %d)", creature->GetTypeId() == TYPEID_UNIT ? creature->ToCreature()->GetEntry() : 0);
return;
}
if (!creature->HasReactState(REACT_PASSIVE) && !creature->GetVictim())
{
if (Unit* nearTarget = creature->SelectNearestTarget(maxRangeToNearestTarget))
creature->AI()->AttackStart(nearTarget);
else if (creature->IsSummon())
{
Unit* target = summoner->getAttackerForHelper();
if (!target && summoner->CanHaveThreatList() && !summoner->GetThreatManager().IsThreatListEmpty())
target = summoner->GetThreatManager().GetAnyTarget();
if (target && (creature->IsFriendlyTo(summoner) || creature->IsHostileTo(target)))
creature->AI()->AttackStart(target);
if (Unit* summoner = creature->ToTempSummon()->GetSummoner())
{
Unit* target = summoner->getAttackerForHelper();
if (!target && !summoner->GetThreatManager().IsThreatListEmpty())
target = summoner->GetThreatManager().GetAnyTarget();
if (target && (creature->IsFriendlyTo(summoner) || creature->IsHostileTo(target)))
creature->AI()->AttackStart(target);
}
}
}
// Intended duplicated check, the code above this should select a victim
// If it can't find a suitable attack target then we should error out.
if (!creature->HasReactState(REACT_PASSIVE) && !creature->GetVictim())
{
TC_LOG_ERROR("misc.dozoneincombat", "DoZoneInCombat called for creature that has empty threat list (creature entry = %u)", creature->GetEntry());
return;
}
}
// Intended duplicated check, the code above this should select a victim
// If it can't find a suitable attack target then we should error out.
if (!creature->HasReactState(REACT_PASSIVE) && !creature->GetVictim())
{
TC_LOG_ERROR("misc.dozoneincombat", "DoZoneInCombat called for creature that has empty threat list (creature entry = %u)", creature->GetEntry());
return;
}
Map::PlayerList const& playerList = map->GetPlayers();
if (playerList.isEmpty())
return;
for (Map::PlayerList::const_iterator itr = playerList.begin(); itr != playerList.end(); ++itr)
{
if (Player* player = itr->GetSource())
{
if (player->IsGameMaster())
continue;
if (player->IsAlive())
{
creature->SetInCombatWith(player);
player->SetInCombatWith(creature);
creature->GetThreatManager().AddThreat(player, 0.0f, nullptr, true, true);
}
}
}
}
// scripts does not take care about MoveInLineOfSight loops
@@ -249,11 +237,13 @@ bool CreatureAI::UpdateVictim()
return me->GetVictim() != nullptr;
}
else if (me->GetThreatManager().IsThreatListEmpty())
else if (me->GetThreatManager().IsThreatListEmpty(true))
{
EnterEvadeMode(EVADE_REASON_NO_HOSTILES);
return false;
}
else
me->AttackStop();
return true;
}
+1
View File
@@ -20,6 +20,7 @@
#include "UnitAI.h"
class Creature;
class Spell;
class TC_GAME_API PlayerAI : public UnitAI
@@ -504,11 +504,12 @@ void BossAI::TeleportCheaters()
float x, y, z;
me->GetPosition(x, y, z);
ThreatContainer::StorageType threatList = me->GetThreatManager().getThreatList();
for (ThreatContainer::StorageType::const_iterator itr = threatList.begin(); itr != threatList.end(); ++itr)
if (Unit* target = (*itr)->getTarget())
if (target->GetTypeId() == TYPEID_PLAYER && !CheckBoundary(target))
target->NearTeleportTo(x, y, z, 0);
for (auto const& pair : me->GetCombatManager().GetPvECombatRefs())
{
Unit* target = pair.second->GetOther(me);
if (target->IsControlledByPlayer() && !CheckBoundary(target))
target->NearTeleportTo(x, y, z, 0);
}
}
void BossAI::JustSummoned(Creature* summon)
@@ -184,7 +184,7 @@ struct TC_GAME_API ScriptedAI : public CreatureAI
void Reset() override { }
//Called at creature aggro either by MoveInLOS or Attack Start
void JustEngagedWith(Unit* /*victim*/) override { }
void JustEngagedWith(Unit* /*who*/) override { }
// Called before JustEngagedWith even before the creature is in combat.
void AttackStart(Unit* /*target*/) override;
@@ -2254,6 +2254,8 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
}
case SMART_ACTION_ADD_THREAT:
{
if (!me->CanHaveThreatList())
break;
for (WorldObject* const target : targets)
if (IsUnit(target))
me->GetThreatManager().AddThreat(target->ToUnit(), float(e.action.threatPCT.threatINC) - float(e.action.threatPCT.threatDEC), nullptr, true, true);
@@ -768,11 +768,8 @@ void Battleground::EndBattleground(uint32 winner)
player->SpawnCorpseBones();
}
else
{
//needed cause else in av some creatures will kill the players at the end
player->CombatStop();
player->getHostileRefManager().deleteReferences();
}
// remove temporary currency bonus auras before rewarding player
player->RemoveAura(SPELL_HONORABLE_DEFENDER_25Y);
+350
View File
@@ -0,0 +1,350 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "CombatManager.h"
#include "Creature.h"
#include "CreatureAI.h"
#include "Player.h"
/*static*/ bool CombatManager::CanBeginCombat(Unit const* a, Unit const* b)
{
// Checks combat validity before initial reference creation.
// For the combat to be valid...
// ...the two units need to be different
if (a == b)
return false;
// ...the two units need to be in the world
if (!a->IsInWorld() || !b->IsInWorld())
return false;
// ...the two units need to both be alive
if (!a->IsAlive() || !b->IsAlive())
return false;
// ...the two units need to be on the same map
if (a->GetMap() != b->GetMap())
return false;
// ...the two units need to be in the same phase
if (!WorldObject::InSamePhase(a, b))
return false;
if (a->HasUnitState(UNIT_STATE_EVADE) || b->HasUnitState(UNIT_STATE_EVADE))
return false;
if (a->HasUnitState(UNIT_STATE_IN_FLIGHT) || b->HasUnitState(UNIT_STATE_IN_FLIGHT))
return false;
if (a->IsControlledByPlayer() || b->IsControlledByPlayer())
{ // PvSomething, only block friendly fire
if (a->IsFriendlyTo(b) || b->IsFriendlyTo(a))
return false;
}
else
{ // CvC, need hostile reaction to start a fight
if (!a->IsHostileTo(b) && !b->IsHostileTo(a))
return false;
}
Player const* playerA = a->GetCharmerOrOwnerPlayerOrPlayerItself();
Player const* playerB = b->GetCharmerOrOwnerPlayerOrPlayerItself();
// ...neither of the two units must be (owned by) a player with .gm on
if ((playerA && playerA->IsGameMaster()) || (playerB && playerB->IsGameMaster()))
return false;
return true;
}
void CombatReference::EndCombat()
{
// sequencing matters here - AI might do nasty stuff, so make sure refs are in a consistent state before you hand off!
// first, get rid of any threat that still exists...
first->GetThreatManager().ClearThreat(second);
second->GetThreatManager().ClearThreat(first);
// ...then, remove the references from both managers...
first->GetCombatManager().PurgeReference(second->GetGUID(), _isPvP);
second->GetCombatManager().PurgeReference(first->GetGUID(), _isPvP);
// ...update the combat state, which will potentially remove IN_COMBAT...
bool const needFirstAI = first->GetCombatManager().UpdateOwnerCombatState();
bool const needSecondAI = second->GetCombatManager().UpdateOwnerCombatState();
// ...and if that happened, also notify the AI of it...
if (needFirstAI && first->IsAIEnabled)
first->GetAI()->JustExitedCombat();
if (needSecondAI && second->IsAIEnabled)
second->GetAI()->JustExitedCombat();
// ...and finally clean up the reference object
delete this;
}
bool PvPCombatReference::Update(uint32 tdiff)
{
if (_combatTimer <= tdiff)
return false;
_combatTimer -= tdiff;
return true;
}
void PvPCombatReference::Refresh()
{
_combatTimer = PVP_COMBAT_TIMEOUT;
bool needFirstAI = false, needSecondAI = false;
if (_suppressFirst)
{
_suppressFirst = false;
needFirstAI = first->GetCombatManager().UpdateOwnerCombatState();
}
if (_suppressSecond)
{
_suppressSecond = false;
needSecondAI = second->GetCombatManager().UpdateOwnerCombatState();
}
if (needFirstAI)
CombatManager::NotifyAICombat(first, second);
if (needSecondAI)
CombatManager::NotifyAICombat(second, first);
}
void PvPCombatReference::SuppressFor(Unit* who)
{
Suppress(who);
if (who->GetCombatManager().UpdateOwnerCombatState())
if (who->IsAIEnabled)
who->GetAI()->JustExitedCombat();
}
void CombatManager::Update(uint32 tdiff)
{
auto it = _pvpRefs.begin(), end = _pvpRefs.end();
while (it != end)
{
PvPCombatReference* const ref = it->second;
if (ref->first == _owner && !ref->Update(tdiff)) // only update if we're the first unit involved (otherwise double decrement)
{
it = _pvpRefs.erase(it), end = _pvpRefs.end(); // remove it from our refs first to prevent invalidation
ref->EndCombat(); // this will remove it from the other side
}
else
++it;
}
}
bool CombatManager::HasPvPCombat() const
{
for (auto const& pair : _pvpRefs)
if (!pair.second->IsSuppressedFor(_owner))
return true;
return false;
}
Unit* CombatManager::GetAnyTarget() const
{
if (!_pveRefs.empty())
return _pveRefs.begin()->second->GetOther(_owner);
for (auto const& pair : _pvpRefs)
if (!pair.second->IsSuppressedFor(_owner))
return pair.second->GetOther(_owner);
return nullptr;
}
bool CombatManager::SetInCombatWith(Unit* who)
{
// Are we already in combat? If yes, refresh pvp combat
auto it = _pvpRefs.find(who->GetGUID());
if (it != _pvpRefs.end())
{
it->second->Refresh();
return true;
}
else if (_pveRefs.find(who->GetGUID()) != _pveRefs.end())
return true;
// Otherwise, check validity...
if (!CombatManager::CanBeginCombat(_owner, who))
return false;
// ...then create new reference
CombatReference* ref;
if (_owner->IsControlledByPlayer() && who->IsControlledByPlayer())
ref = new PvPCombatReference(_owner, who);
else
ref = new CombatReference(_owner, who);
// ...and insert it into both managers
PutReference(who->GetGUID(), ref);
who->GetCombatManager().PutReference(_owner->GetGUID(), ref);
// now, sequencing is important - first we update the combat state, which will set both units in combat and do non-AI combat start stuff
bool const needSelfAI = UpdateOwnerCombatState();
bool const needOtherAI = who->GetCombatManager().UpdateOwnerCombatState();
// then, we finally notify the AI (if necessary) and let it safely do whatever it feels like
if (needSelfAI)
NotifyAICombat(_owner, who);
if (needOtherAI)
NotifyAICombat(who, _owner);
return true;
}
bool CombatManager::IsInCombatWith(ObjectGuid const& guid) const
{
return (_pveRefs.find(guid) != _pveRefs.end()) || (_pvpRefs.find(guid) != _pvpRefs.end());
}
bool CombatManager::IsInCombatWith(Unit const* who) const
{
return IsInCombatWith(who->GetGUID());
}
void CombatManager::InheritCombatStatesFrom(Unit const* who)
{
CombatManager const& mgr = who->GetCombatManager();
for (auto& ref : mgr._pveRefs)
{
if (!IsInCombatWith(ref.first))
{
Unit* target = ref.second->GetOther(who);
if ((_owner->IsImmuneToPC() && target->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE)) ||
(_owner->IsImmuneToNPC() && !target->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE)))
continue;
SetInCombatWith(target);
}
}
for (auto& ref : mgr._pvpRefs)
{
if (!IsInCombatWith(ref.first))
{
Unit* target = ref.second->GetOther(who);
if ((_owner->IsImmuneToPC() && target->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE)) ||
(_owner->IsImmuneToNPC() && !target->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE)))
continue;
SetInCombatWith(target);
}
}
}
void CombatManager::EndCombatBeyondRange(float range, bool includingPvP)
{
auto it = _pveRefs.begin(), end = _pveRefs.end();
while (it != end)
{
CombatReference* const ref = it->second;
if (!ref->first->IsWithinDistInMap(ref->second, range))
{
it = _pveRefs.erase(it), end = _pveRefs.end(); // erase manually here to avoid iterator invalidation
ref->EndCombat();
}
else
++it;
}
if (!includingPvP)
return;
auto it2 = _pvpRefs.begin(), end2 = _pvpRefs.end();
while (it2 != end2)
{
CombatReference* const ref = it2->second;
if (!ref->first->IsWithinDistInMap(ref->second, range))
{
it2 = _pvpRefs.erase(it2), end2 = _pvpRefs.end(); // erase manually here to avoid iterator invalidation
ref->EndCombat();
}
else
++it2;
}
}
void CombatManager::SuppressPvPCombat()
{
for (auto const& pair : _pvpRefs)
pair.second->Suppress(_owner);
if (UpdateOwnerCombatState())
if (_owner->IsAIEnabled)
_owner->GetAI()->JustExitedCombat();
}
void CombatManager::EndAllPvECombat()
{
// cannot have threat without combat
_owner->GetThreatManager().RemoveMeFromThreatLists();
_owner->GetThreatManager().ClearAllThreat();
while (!_pveRefs.empty())
_pveRefs.begin()->second->EndCombat();
}
void CombatManager::EndAllPvPCombat()
{
while (!_pvpRefs.empty())
_pvpRefs.begin()->second->EndCombat();
}
/*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);
}
void CombatManager::PutReference(ObjectGuid const& guid, CombatReference* ref)
{
if (ref->_isPvP)
{
auto& inMap = _pvpRefs[guid];
ASSERT(!inMap && "Duplicate combat state detected - memory leak!");
inMap = static_cast<PvPCombatReference*>(ref);
}
else
{
auto& inMap = _pveRefs[guid];
ASSERT(!inMap && "Duplicate combat state detected - memory leak!");
inMap = ref;
}
}
void CombatManager::PurgeReference(ObjectGuid const& guid, bool pvp)
{
if (pvp)
_pvpRefs.erase(guid);
else
_pveRefs.erase(guid);
}
bool CombatManager::UpdateOwnerCombatState() const
{
bool const combatState = HasCombat();
if (combatState == _owner->IsInCombat())
return false;
if (combatState)
{
_owner->AddUnitFlag(UNIT_FLAG_IN_COMBAT);
_owner->AtEnterCombat();
}
else
{
_owner->RemoveUnitFlag(UNIT_FLAG_IN_COMBAT);
_owner->AtExitCombat();
}
if (Unit* master = _owner->GetCharmerOrOwner())
master->UpdatePetCombatState();
return true;
}
+143
View File
@@ -0,0 +1,143 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TRINITY_COMBATMANAGER_H
#define TRINITY_COMBATMANAGER_H
#include "Common.h"
#include "ObjectGuid.h"
#include <unordered_map>
class Unit;
/********************************************************************************************************************************************************\
* DEV DOCUMENTATION: COMBAT SYSTEM *
* (future devs: please keep this up-to-date if you change the system) *
* CombatManager maintains a list of dynamically allocated CombatReference entries. Each entry represents a combat state between two distinct units. *
* A unit is "in combat" iff it has one or more non-suppressed CombatReference entries in its CombatManager. No exceptions. *
* - Note: Only PvP combat references can be suppressed, and only because Vanish is a very silly spell. Sue Blizzard. *
* *
* A CombatReference object carries the following implicit guarantees by existing: *
* - Both CombatReference.first and CombatReference.second are valid Units, distinct, not nullptr and currently in the world. *
* - If the CombatReference was retrieved from the CombatManager of Unit* A, then exactly one of .first and .second is equal to A. *
* - Note: Use CombatReference::GetOther to quickly get the other unit for a given reference. *
* - Both .first and .second are currently in combat (IsInCombat will always be true) if either of the following hold: *
* - The reference is a PvE combat reference (_isPvP is false) *
* - IsSuppressedFor returns false for the respective unit *
* *
* To end combat between two units, find their CombatReference and call EndCombat. *
* - Keep in mind that this modifies the CombatRefs maps on both ends, which may cause iterators to be invalidated. *
* *
* To put two units in combat with each other, call SetInCombatWith. Note that this is not guaranteed to succeed. *
* - The return value of SetInCombatWith is the new combat state between the units (identical to calling IsInCombatWith at that time). *
* *
* Note that (threat => combat) is a strong guarantee provided in conjunction with ThreatManager. Thus: *
* - Ending combat between two units will also delete any threat references that may exist between them. *
* - Adding threat will also create a combat reference between the units if one doesn't exist yet. *
\********************************************************************************************************************************************************/
// Please check Game/Combat/CombatManager.h for documentation on how this class works!
struct TC_GAME_API CombatReference
{
Unit* const first;
Unit* const second;
bool const _isPvP;
Unit* GetOther(Unit const* me) const { return (first == me) ? second : first; }
void EndCombat();
CombatReference(CombatReference const&) = delete;
CombatReference& operator=(CombatReference const&) = delete;
protected:
CombatReference(Unit* a, Unit* b, bool pvp = false) : first(a), second(b), _isPvP(pvp) { }
friend class CombatManager;
};
// Please check Game/Combat/CombatManager.h for documentation on how this class works!
struct TC_GAME_API PvPCombatReference : public CombatReference
{
static const uint32 PVP_COMBAT_TIMEOUT = 5 * IN_MILLISECONDS;
// suppressed combat refs do not generate a combat state for one side of the relation
// (used by: vanish, feign death)
void SuppressFor(Unit* who);
bool IsSuppressedFor(Unit const* who) const { return (who == first) ? _suppressFirst : _suppressSecond; }
private:
PvPCombatReference(Unit* first, Unit* second) : CombatReference(first, second, true) { }
bool Update(uint32 tdiff);
void Refresh();
void Suppress(Unit* who) { (who == first ? _suppressFirst : _suppressSecond) = true; }
uint32 _combatTimer = PVP_COMBAT_TIMEOUT;
bool _suppressFirst = false;
bool _suppressSecond = false;
friend class CombatManager;
};
// please check Game/Combat/CombatManager.h for documentation on how this class works!
class TC_GAME_API CombatManager
{
public:
static bool CanBeginCombat(Unit const* a, Unit const* b);
CombatManager(Unit* owner) : _owner(owner) { }
void Update(uint32 tdiff); // called from Unit::Update
Unit* GetOwner() const { return _owner; }
bool HasCombat() const { return HasPvECombat() || HasPvPCombat(); }
bool HasPvECombat() const { return !_pveRefs.empty(); }
std::unordered_map<ObjectGuid, CombatReference*> const& GetPvECombatRefs() const { return _pveRefs; }
bool HasPvPCombat() const;
std::unordered_map<ObjectGuid, PvPCombatReference*> const& GetPvPCombatRefs() const { return _pvpRefs; }
// If the Unit is in combat, returns an arbitrary Unit that it's in combat with. Otherwise, returns nullptr.
Unit* GetAnyTarget() const;
// return value is the same as calling IsInCombatWith immediately after this returns
bool SetInCombatWith(Unit* who);
bool IsInCombatWith(ObjectGuid const& who) const;
bool IsInCombatWith(Unit const* who) const;
void InheritCombatStatesFrom(Unit const* who);
void EndCombatBeyondRange(float range, bool includingPvP = false);
// flags any pvp refs for suppression on owner's side - these refs will not generate combat until refreshed
void SuppressPvPCombat();
void EndAllPvECombat();
void EndAllPvPCombat();
void EndAllCombat() { EndAllPvECombat(); EndAllPvPCombat(); }
CombatManager(CombatManager const&) = delete;
CombatManager& operator=(CombatManager const&) = delete;
private:
static void NotifyAICombat(Unit* me, Unit* other);
void PutReference(ObjectGuid const& guid, CombatReference* ref);
void PurgeReference(ObjectGuid const& guid, bool pvp);
bool UpdateOwnerCombatState() const;
Unit* const _owner;
std::unordered_map<ObjectGuid, CombatReference*> _pveRefs;
std::unordered_map<ObjectGuid, PvPCombatReference*> _pvpRefs;
friend struct CombatReference;
friend struct PvPCombatReference;
};
#endif
@@ -1,216 +0,0 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "HostileRefManager.h"
#include "DB2Structure.h"
#include "SpellInfo.h"
#include "ThreatManager.h"
#include "Unit.h"
HostileRefManager::~HostileRefManager()
{
deleteReferences();
}
//=================================================
// send threat to all my haters for the victim
// The victim is then hated by them as well
// use for buffs and healing threat functionality
void HostileRefManager::threatAssist(Unit* victim, float baseThreat, SpellInfo const* threatSpell)
{
if (getSize() == 0)
return;
float threat = ThreatCalcHelper::calcThreat(victim, iOwner, baseThreat, (threatSpell ? threatSpell->GetSchoolMask() : SPELL_SCHOOL_MASK_NORMAL), threatSpell);
threat /= getSize();
HostileReference* ref = getFirst();
while (ref)
{
if (ThreatCalcHelper::isValidProcess(victim, ref->GetSource()->GetOwner(), threatSpell))
ref->GetSource()->doAddThreat(victim, threat);
ref = ref->next();
}
}
//=================================================
void HostileRefManager::addTempThreat(float threat, bool apply)
{
HostileReference* ref = getFirst();
while (ref)
{
if (apply)
{
if (ref->getTempThreatModifier() == 0.0f)
ref->addTempThreat(threat);
}
else
ref->resetTempThreat();
ref = ref->next();
}
}
//=================================================
void HostileRefManager::addThreatPercent(int32 percent)
{
HostileReference* ref = getFirst();
while (ref)
{
ref->addThreatPercent(percent);
ref = ref->next();
}
}
//=================================================
// The online / offline status is given to the method. The calculation has to be done before
void HostileRefManager::setOnlineOfflineState(bool isOnline)
{
HostileReference* ref = getFirst();
while (ref)
{
ref->setOnlineOfflineState(isOnline);
ref = ref->next();
}
}
//=================================================
// The online / offline status is calculated and set
void HostileRefManager::updateThreatTables()
{
HostileReference* ref = getFirst();
while (ref)
{
ref->updateOnlineStatus();
ref = ref->next();
}
}
//=================================================
// The references are not needed anymore
// tell the source to remove them from the list and free the mem
void HostileRefManager::deleteReferences()
{
HostileReference* ref = getFirst();
while (ref)
{
HostileReference* nextRef = ref->next();
ref->removeReference();
delete ref;
ref = nextRef;
}
}
//=================================================
// delete one reference, defined by faction
void HostileRefManager::deleteReferencesForFaction(uint32 faction)
{
HostileReference* ref = getFirst();
while (ref)
{
HostileReference* nextRef = ref->next();
if (ref->GetSource()->GetOwner()->GetFactionTemplateEntry()->Faction == faction)
{
ref->removeReference();
delete ref;
}
ref = nextRef;
}
}
//=================================================
// delete all references out of specified range
void HostileRefManager::deleteReferencesOutOfRange(float range)
{
HostileReference* ref = getFirst();
range = range*range;
while (ref)
{
HostileReference* nextRef = ref->next();
Unit* owner = ref->GetSource()->GetOwner();
if (!owner->isActiveObject() && owner->GetExactDist2dSq(GetOwner()) > range)
{
ref->removeReference();
delete ref;
}
ref = nextRef;
}
}
//=================================================
// delete one reference, defined by Unit
void HostileRefManager::deleteReference(Unit* creature)
{
HostileReference* ref = getFirst();
while (ref)
{
HostileReference* nextRef = ref->next();
if (ref->GetSource()->GetOwner() == creature)
{
ref->removeReference();
delete ref;
break;
}
ref = nextRef;
}
}
//=================================================
// set state for one reference, defined by Unit
void HostileRefManager::setOnlineOfflineState(Unit* creature, bool isOnline)
{
HostileReference* ref = getFirst();
while (ref)
{
HostileReference* nextRef = ref->next();
if (ref->GetSource()->GetOwner() == creature)
{
ref->setOnlineOfflineState(isOnline);
break;
}
ref = nextRef;
}
}
//=================================================
void HostileRefManager::UpdateVisibility()
{
HostileReference* ref = getFirst();
while (ref)
{
HostileReference* nextRef = ref->next();
if (!ref->GetSource()->GetOwner()->CanSeeOrDetect(GetOwner()))
{
nextRef = ref->next();
ref->removeReference();
delete ref;
}
ref = nextRef;
}
}
@@ -1,76 +0,0 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _HOSTILEREFMANAGER
#define _HOSTILEREFMANAGER
#include "Common.h"
#include "RefManager.h"
class Unit;
class ThreatManager;
class HostileReference;
class SpellInfo;
//=================================================
class TC_GAME_API HostileRefManager : public RefManager<Unit, ThreatManager>
{
public:
explicit HostileRefManager(Unit* owner) : iOwner(owner) { }
~HostileRefManager();
Unit* GetOwner() const { return iOwner; }
// send threat to all my hateres for the victim
// The victim is hated than by them as well
// use for buffs and healing threat functionality
void threatAssist(Unit* victim, float baseThreat, SpellInfo const* threatSpell = nullptr);
void addTempThreat(float threat, bool apply);
void addThreatPercent(int32 percent);
// The references are not needed anymore
// tell the source to remove them from the list and free the mem
void deleteReferences();
// Remove specific faction references
void deleteReferencesForFaction(uint32 faction);
// for combat bugs
void deleteReferencesOutOfRange(float range);
HostileReference* getFirst() { return ((HostileReference*) RefManager<Unit, ThreatManager>::getFirst()); }
void updateThreatTables();
void setOnlineOfflineState(bool isOnline);
// set state for one reference, defined by Unit
void setOnlineOfflineState(Unit* creature, bool isOnline);
// delete one reference, defined by Unit
void deleteReference(Unit* creature);
void UpdateVisibility();
private:
Unit* iOwner;
};
//=================================================
#endif
File diff suppressed because it is too large Load Diff
+236 -295
View File
@@ -15,320 +15,261 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _THREATMANAGER
#define _THREATMANAGER
#ifndef TRINITY_THREATMANAGER_H
#define TRINITY_THREATMANAGER_H
#include "Common.h"
#include "SharedDefines.h"
#include "LinkedReference/Reference.h"
#include "UnitEvents.h"
#include "ObjectGuid.h"
#include "IteratorPair.h"
#include <list>
//==============================================================
#include "ObjectGuid.h"
#include "SharedDefines.h"
#include <boost/heap/fibonacci_heap.hpp>
#include <unordered_map>
#include <vector>
class Unit;
class Creature;
class ThreatManager;
class SpellInfo;
#define THREAT_UPDATE_INTERVAL 1 * IN_MILLISECONDS // Server should send threat update to client periodically each second
/********************************************************************************************************************************************************\
* DEV DOCUMENTATION: THREAT SYSTEM *
* (future devs: please keep this up-to-date if you change the system) *
* The threat system works based on dynamically allocated threat list entries. *
* *
* Each such entry is a ThreatReference object, which is always stored in exactly three places: *
* - The threatened unit's (from now: reference "owner") sorted and unsorted threat lists *
* - The threatening unit's (from now: reference "victim") threatened-by-me list *
* A ThreatReference object carries the following implicit guarantees: *
* - Both owner and victim are valid units, which are currently in the world. Neither can be nullptr. *
* - There is an active combat reference between owner and victim. *
* *
* ThreatManager also keeps track of whether its owner is engaged (a boolean flag). *
* - If a (non-offline) threat list entry is added to a not-yet-engaged ThreatManager, it calls JustEngagedWith on its owner's AI. *
* - The engaged state is cleared in ClearAllThreat (which is invoked on evade). *
* - This flag can be accessed through the IsEngaged method. For creatures that can have a threat list, this is equal to Unit::IsEngaged. *
* *
* Note that (threat => combat) is a strong guarantee provided in conjunction with CombatManager. Thus: *
* - Adding threat will also create a combat reference between the units if one doesn't exist yet (even if the owner can't have a threat list!) *
* - Ending combat between two units will also delete any threat references that may exist between them. *
* *
* To manage a creature's threat list, ThreatManager maintains a heap of threat reference const pointers. *
* This heap is kept well-structured in all methods that modify ThreatReference, and is used to select the next target. *
* *
* Selection uses the following properties on ThreatReference, in order: *
* - Online state (one of ONLINE, SUPPRESSED, OFFLINE): *
* - ONLINE: Normal threat state, target is valid and attackable *
* - SUPPRESSED: Target is attackable, but fully immuned. This is used for targets under HoP, Divine Shield, Ice Block etc. *
* Targets with SUPPRESSED threat can still be valid targets, but any target with ONLINE threat will be preferred. *
* - OFFLINE: The target is, for whatever reason, not valid at this time (for example, IMMUNE_TO_X flags or game master state). *
* These targets can never be selected by SelectVictim, which will return nullptr if all targets are OFFLINE (typically causing evade). *
* - Related methods: GetOnlineState, IsOnline, IsAvailable, IsOffline *
* - Taunt state (one of TAUNT, NONE, DETAUNT), the names speak for themselves *
* - Related methods: GetTauntState, IsTaunting, IsDetaunted *
* - Actual threat value (GetThreat) *
* *
* The current (= last selected) victim can be accessed using GetCurrentVictim. SelectVictim selects a (potentially new) victim. *
* Beyond that, ThreatManager has a variety of helpers and notifiers, which are documented inline below. *
* *
* SPECIAL NOTE: Please be aware that any heap iterator may be invalidated if you modify a ThreatReference. The heap holds const pointers for a reason. *
* If you need to modify multiple ThreatReference objects, then use GetModifiableThreatList(), which is safe to modify! *
\********************************************************************************************************************************************************/
//==============================================================
// Class to calculate the real threat based
struct TC_GAME_API ThreatCalcHelper
class ThreatReference;
struct CompareThreatLessThan
{
static float calcThreat(Unit* hatedUnit, Unit* hatingUnit, float threat, SpellSchoolMask schoolMask = SPELL_SCHOOL_MASK_NORMAL, SpellInfo const* threatSpell = nullptr);
static bool isValidProcess(Unit* hatedUnit, Unit* hatingUnit, SpellInfo const* threatSpell = nullptr);
CompareThreatLessThan() {}
bool operator()(ThreatReference const* a, ThreatReference const* b) const;
};
//==============================================================
class TC_GAME_API HostileReference : public Reference<Unit, ThreatManager>
{
public:
HostileReference(Unit* refUnit, ThreatManager* threatManager, float threat);
// -- compatibility layer for combat rewrite (PR #19930)
Unit* GetOwner() const;
Unit* GetVictim() const { return getTarget(); }
void AddThreat(float amt) { addThreat(amt); }
void SetThreat(float amt) { setThreat(amt); }
void ModifyThreatByPercent(int32 pct) { addThreatPercent(pct); }
void ScaleThreat(float factor) { setThreat(iThreat*factor); }
bool IsOnline() const { return iOnline; }
bool IsAvailable() const { return iOnline; }
bool IsOffline() const { return !iOnline; }
float GetThreat() const { return getThreat(); }
void ClearThreat() { removeReference(); }
//=================================================
void addThreat(float modThreat);
void setThreat(float threat) { addThreat(threat - iThreat); }
void addThreatPercent(int32 percent);
float getThreat() const { return iThreat + iTempThreatModifier; }
bool isOnline() const { return iOnline; }
// The Unit might be in water and the creature can not enter the water, but has range attack
// in this case online = true, but accessible = false
bool isAccessible() const { return iAccessible; }
// used for temporary setting a threat and reducing it later again.
// the threat modification is stored
void setTempThreat(float threat)
{
addTempThreat(threat - iTempThreatModifier);
}
void addTempThreat(float threat)
{
if (!threat)
return;
iTempThreatModifier += threat;
ThreatRefStatusChangeEvent event(UEV_THREAT_REF_THREAT_CHANGE, this, threat);
fireStatusChanged(event);
}
void resetTempThreat()
{
addTempThreat(-iTempThreatModifier);
}
float getTempThreatModifier() { return iTempThreatModifier; }
//=================================================
// check, if source can reach target and set the status
void updateOnlineStatus();
void setOnlineOfflineState(bool isOnline);
void setAccessibleState(bool isAccessible);
//=================================================
bool operator==(HostileReference const& hostileRef) const { return hostileRef.getUnitGuid() == getUnitGuid(); }
//=================================================
ObjectGuid getUnitGuid() const { return iUnitGuid; }
//=================================================
// reference is not needed anymore. realy delete it !
void removeReference();
//=================================================
HostileReference* next() { return static_cast<HostileReference*>(Reference<Unit, ThreatManager>::next()); }
//=================================================
// Tell our refTo (target) object that we have a link
void targetObjectBuildLink() override;
// Tell our refTo (taget) object, that the link is cut
void targetObjectDestroyLink() override;
// Tell our refFrom (source) object, that the link is cut (Target destroyed)
void sourceObjectDestroyLink() override;
private:
// Inform the source, that the status of that reference was changed
void fireStatusChanged(ThreatRefStatusChangeEvent& threatRefStatusChangeEvent);
Unit* GetSourceUnit();
private:
float iThreat;
float iTempThreatModifier; // used for SPELL_AURA_MOD_TOTAL_THREAT
ObjectGuid iUnitGuid;
bool iOnline;
bool iAccessible;
};
//==============================================================
class ThreatManager;
class TC_GAME_API ThreatContainer
{
friend class ThreatManager;
public:
typedef std::list<HostileReference*> StorageType;
ThreatContainer(): iDirty(false) { }
~ThreatContainer() { clearReferences(); }
HostileReference* addThreat(Unit* victim, float threat);
void ModifyThreatByPercent(Unit* victim, int32 percent);
HostileReference* selectNextVictim(Creature* attacker, HostileReference* currentVictim) const;
void setDirty(bool isDirty) { iDirty = isDirty; }
bool isDirty() const { return iDirty; }
bool empty() const
{
return iThreatList.empty();
}
HostileReference* getMostHated() const
{
return iThreatList.empty() ? nullptr : iThreatList.front();
}
HostileReference* getReferenceByTarget(Unit const* victim) const;
StorageType const & getThreatList() const { return iThreatList; }
private:
void remove(HostileReference* hostileRef)
{
iThreatList.remove(hostileRef);
}
void addReference(HostileReference* hostileRef)
{
iThreatList.push_back(hostileRef);
}
void clearReferences();
// Sort the list if necessary
void update();
StorageType iThreatList;
bool iDirty;
};
//=================================================
typedef HostileReference ThreatReference;
// Please check Game/Combat/ThreatManager.h for documentation on how this class works!
class TC_GAME_API ThreatManager
{
public:
// -- compatibility layer for combat rewrite (PR #19930)
Trinity::IteratorPair<std::list<ThreatReference*>::const_iterator> GetSortedThreatList() const { auto& list = iThreatContainer.getThreatList(); return { list.cbegin(), list.cend() }; }
Trinity::IteratorPair<std::list<ThreatReference*>::const_iterator> GetUnsortedThreatList() const { return GetSortedThreatList(); }
std::list<ThreatReference*> GetModifiableThreatList() const { return iThreatContainer.getThreatList(); }
Unit* SelectVictim() { return getHostilTarget(); }
Unit* GetCurrentVictim() const { if (ThreatReference* ref = getCurrentVictim()) return ref->GetVictim(); else return nullptr; }
bool IsThreatListEmpty(bool includeOffline = false) const { return includeOffline ? areThreatListsEmpty() : isThreatListEmpty(); }
bool IsThreatenedBy(Unit const* who, bool includeOffline = false) const { return (FindReference(who, includeOffline) != nullptr); }
size_t GetThreatListSize() const { return iThreatContainer.iThreatList.size(); }
void ForwardThreatForAssistingMe(Unit* victim, float amount, SpellInfo const* spell, bool ignoreModifiers = false, bool ignoreRedirection = false);
Unit* GetAnyTarget() const { auto const& list = getThreatList(); if (!list.empty()) return list.front()->getTarget(); return nullptr; }
void ResetThreat(Unit const* who) { if (auto* ref = FindReference(who, true)) ref->setThreat(0.0f); }
void ResetAllThreat() { resetAllAggro(); }
float GetThreat(Unit const* who, bool includeOffline = false) const { if (auto* ref = FindReference(who, includeOffline)) return ref->GetThreat(); return 0.0f; }
void ClearThreat(Unit const* who) { if (auto* ref = FindReference(who, true)) ref->removeReference(); }
typedef boost::heap::fibonacci_heap<ThreatReference const*, boost::heap::compare<CompareThreatLessThan>> threat_list_heap;
static const uint32 CLIENT_THREAT_UPDATE_INTERVAL = 1000u;
static bool CanHaveThreatList(Unit const* who);
ThreatManager(Unit* owner);
// called from ::Create methods just after construction (once all fields on owner have been populated)
// should not be called from anywhere else
void Initialize();
// called from Creature::Update (only creatures can have their own threat list)
// should not be called from anywhere else
void Update(uint32 tdiff);
// never nullptr
Unit* GetOwner() const { return _owner; }
// can our owner have a threat list?
// identical to ThreatManager::CanHaveThreatList(GetOwner())
bool CanHaveThreatList() const { return _ownerCanHaveThreatList; }
// returns the victim selected by the last SelectVictim call - this can be nullptr
Unit* GetCurrentVictim() const;
// returns an arbitrary non-offline victim from owner's threat list if one exists, nullptr otherwise
Unit* GetAnyTarget() const;
// selects a (potentially new) victim from the threat list and returns it - this can be nullptr
Unit* SelectVictim();
bool IsEngaged() const { return _ownerEngaged; }
// are there any entries in owner's threat list?
bool IsThreatListEmpty(bool includeOffline = false) const;
// is there a threat list entry on owner's threat list with victim == who?
bool IsThreatenedBy(ObjectGuid const& who, bool includeOffline = false) const;
// is there a threat list entry on owner's threat list with victim == who?
bool IsThreatenedBy(Unit const* who, bool includeOffline = false) const;
// returns ThreatReference amount if a ref exists, 0.0f otherwise
float GetThreat(Unit const* who, bool includeOffline = false) const;
size_t GetThreatListSize() const { return _sortedThreatList.size(); }
// fastest of the three threat list getters - gets the threat list in "arbitrary" order
Trinity::IteratorPair<threat_list_heap::const_iterator> GetUnsortedThreatList() const { return { _sortedThreatList.begin(), _sortedThreatList.end() }; }
// slightly slower than GetUnsorted, but, well...sorted - only use it if you need the sorted property, of course
// note: current tank is NOT guaranteed to be the first entry in this list - check GetCurrentVictim 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
std::vector<ThreatReference*> GetModifiableThreatList() const;
// does any unit have a threat list entry with victim == this.owner?
bool IsThreateningAnyone(bool includeOffline = false) const;
// is there a threat list entry on who's threat list for this.owner?
bool IsThreateningTo(ObjectGuid const& who, bool includeOffline = false) const;
// is there a threat list entry on who's threat list for this.owner?
bool IsThreateningTo(Unit const* who, bool includeOffline = false) const;
auto const& GetThreatenedByMeList() const { return _threatenedByMe; }
// Notify the ThreatManager that a condition changed that may impact refs' online state so it can re-evaluate
void UpdateOnlineStates(bool meThreateningOthers = true, bool othersThreateningMe = true);
///== AFFECT MY THREAT LIST ==
void AddThreat(Unit* target, float amount, SpellInfo const* spell = nullptr, bool ignoreModifiers = false, bool ignoreRedirects = false);
void ScaleThreat(Unit* target, float factor);
// Modify target's threat by +percent%
void ModifyThreatByPercent(Unit* target, int32 percent) { if (percent) ScaleThreat(target, 0.01f*float(100 + percent)); }
// Resets the specified unit's threat to zero
void ResetThreat(Unit* target) { ScaleThreat(target, 0.0f); }
// Sets the specified unit's threat to be equal to the highest entry on the threat list
void MatchUnitThreatToHighestThreat(Unit* target);
// Notify the ThreatManager that we have a new taunt aura (or a taunt aura expired)
void TauntUpdate();
// Sets all threat refs in owner's threat list to have zero threat
void ResetAllThreat();
// Removes specified target from the threat list
void ClearThreat(Unit* target);
// Removes all targets from the threat list (will cause evade in UpdateVictim if called)
void ClearAllThreat();
void AddThreat(Unit* victim, float amount, SpellInfo const* spell = nullptr, bool ignoreModifiers = false, bool ignoreRedirection = false);
// sends SMSG_THREAT_UPDATE to all nearby clients (used by client to forward threat list info to addons)
void SendThreatListToClients() const;
///== AFFECT OTHERS' THREAT LISTS ==
// what it says on the tin - call AddThreat on everything that's threatened by us with the specified params
void ForwardThreatForAssistingMe(Unit* assistant, float baseAmount, SpellInfo const* spell = nullptr, bool ignoreModifiers = false);
// delete all ThreatReferences with victim == owner
void RemoveMeFromThreatLists();
// re-calculates the temporary threat modifier from auras on myself
void UpdateMyTempModifiers();
// re-calculate SPELL_AURA_MOD_THREAT modifiers
void UpdateMySpellSchoolModifiers();
///== REDIRECT SYSTEM ==
// Register a redirection effect that redirects pct% of threat generated by owner to victim
void RegisterRedirectThreat(uint32 spellId, ObjectGuid const& victim, uint32 pct);
// Unregister a redirection effort for all victims
void UnregisterRedirectThreat(uint32 spellId);
// Unregister a redirection effect for a specific victim
void UnregisterRedirectThreat(uint32 spellId, ObjectGuid const& victim);
private:
HostileReference* FindReference(Unit const* who, bool includeOffline) const { if (auto* ref = iThreatContainer.getReferenceByTarget(who)) return ref; if (includeOffline) if (auto* ref = iThreatOfflineContainer.getReferenceByTarget(who)) return ref; return nullptr; }
Unit* const _owner;
bool _ownerCanHaveThreatList;
bool _ownerEngaged;
static const CompareThreatLessThan CompareThreat;
static bool CompareReferencesLT(ThreatReference const* a, ThreatReference const* b, float aWeight);
static float CalculateModifiedThreat(float threat, Unit const* victim, SpellInfo const* spell);
// send opcodes (all for my own threat list)
void SendClearAllThreatToClients() const;
void SendRemoveToClients(Unit const* victim) const;
void SendNewVictimToClients(ThreatReference const* victimRef) const;
///== MY THREAT LIST ==
void PutThreatListRef(ObjectGuid const& guid, ThreatReference* ref);
void PurgeThreatListRef(ObjectGuid const& guid, bool sendRemove);
uint32 _updateClientTimer;
threat_list_heap _sortedThreatList;
std::unordered_map<ObjectGuid, ThreatReference*> _myThreatListEntries;
ThreatReference const* _currentVictimRef;
ThreatReference const* ReselectVictim();
///== OTHERS' THREAT LISTS ==
void PutThreatenedByMeRef(ObjectGuid const& guid, ThreatReference* ref);
void PurgeThreatenedByMeRef(ObjectGuid const& guid);
std::unordered_map<ObjectGuid, ThreatReference*> _threatenedByMe; // these refs are entries for myself on other units' threat lists
float _singleSchoolModifiers[MAX_SPELL_SCHOOL]; // most spells are single school - we pre-calculate these and store them
mutable std::unordered_map<std::underlying_type<SpellSchoolMask>::type, float> _multiSchoolModifiers; // these are calculated on demand
// redirect system (is kind of dumb, but that's because none of the redirection spells actually have any aura effect associated with them, so spellscript needs to deal with it)
void UpdateRedirectInfo();
std::vector<std::pair<ObjectGuid, uint32>> _redirectInfo; // current redirection targets and percentages (updated from registry in ThreatManager::UpdateRedirectInfo)
std::unordered_map<uint32, std::unordered_map<ObjectGuid, uint32>> _redirectRegistry; // spellid -> (victim -> pct); all redirection effects on us (removal individually managed by spell scripts because blizzard is dumb)
public:
ThreatManager(ThreatManager const&) = delete;
ThreatManager& operator=(ThreatManager const&) = delete;
friend class HostileReference;
explicit ThreatManager(Unit* owner);
~ThreatManager() { clearReferences(); }
void clearReferences();
void addThreat(Unit* victim, float threat, SpellSchoolMask schoolMask = SPELL_SCHOOL_MASK_NORMAL, SpellInfo const* threatSpell = nullptr);
void doAddThreat(Unit* victim, float threat);
void ModifyThreatByPercent(Unit* victim, int32 percent);
float getThreat(Unit* victim, bool alsoSearchOfflineList = false);
bool isThreatListEmpty() const { return iThreatContainer.empty(); }
bool areThreatListsEmpty() const { return iThreatContainer.empty() && iThreatOfflineContainer.empty(); }
void processThreatEvent(ThreatRefStatusChangeEvent* threatRefStatusChangeEvent);
bool isNeedUpdateToClient(uint32 time);
HostileReference* getCurrentVictim() const { return iCurrentVictim; }
Unit* GetOwner() const { return iOwner; }
Unit* getHostilTarget();
void tauntApply(Unit* taunter);
void tauntFadeOut(Unit* taunter);
void setCurrentVictim(HostileReference* hostileRef);
void setDirty(bool isDirty) { iThreatContainer.setDirty(isDirty); }
// Reset all aggro without modifying the threadlist.
void resetAllAggro();
// Reset all aggro of unit in threadlist satisfying the predicate.
template<class PREDICATE> void resetAggro(PREDICATE predicate)
{
ThreatContainer::StorageType &threatList = iThreatContainer.iThreatList;
if (threatList.empty())
return;
for (ThreatContainer::StorageType::iterator itr = threatList.begin(); itr != threatList.end(); ++itr)
{
HostileReference* ref = (*itr);
if (predicate(ref->getTarget()))
{
ref->setThreat(0);
setDirty(true);
}
}
}
// methods to access the lists from the outside to do some dirty manipulation (scriping and such)
// I hope they are used as little as possible.
ThreatContainer::StorageType const & getThreatList() const { return iThreatContainer.getThreatList(); }
ThreatContainer::StorageType const & getOfflineThreatList() const { return iThreatOfflineContainer.getThreatList(); }
ThreatContainer& getOnlineContainer() { return iThreatContainer; }
ThreatContainer& getOfflineContainer() { return iThreatOfflineContainer; }
private:
void _addThreat(Unit* victim, float threat);
HostileReference* iCurrentVictim;
Unit* iOwner;
uint32 iUpdateTimer;
ThreatContainer iThreatContainer;
ThreatContainer iThreatOfflineContainer;
friend class ThreatReference;
friend struct CompareThreatLessThan;
};
//=================================================
namespace Trinity
// Please check Game/Combat/ThreatManager.h for documentation on how this class works!
class TC_GAME_API ThreatReference
{
// Binary predicate for sorting HostileReferences based on threat value
class ThreatOrderPred
{
public:
ThreatOrderPred(bool ascending = false) : m_ascending(ascending) { }
bool operator() (HostileReference const* a, HostileReference const* b) const
{
return m_ascending ? a->getThreat() < b->getThreat() : a->getThreat() > b->getThreat();
}
private:
const bool m_ascending;
};
}
#endif
public:
enum TauntState { TAUNT_STATE_DETAUNT = -1, TAUNT_STATE_NONE = 0, TAUNT_STATE_TAUNT = 1 };
enum OnlineState { ONLINE_STATE_ONLINE = 2, ONLINE_STATE_SUPPRESSED = 1, ONLINE_STATE_OFFLINE = 0 };
Unit* GetOwner() const { return _owner; }
Unit* GetVictim() const { return _victim; }
float GetThreat() const { return std::max<float>(_baseAmount + (float)_tempModifier, 0.0f); }
OnlineState GetOnlineState() const { return _online; }
bool IsOnline() const { return (_online >= ONLINE_STATE_ONLINE); }
bool IsAvailable() const { return (_online > ONLINE_STATE_OFFLINE); }
bool IsOffline() const { return (_online <= ONLINE_STATE_OFFLINE); }
TauntState GetTauntState() const { return _taunted; }
bool IsTaunting() const { return _taunted == TAUNT_STATE_TAUNT; }
bool IsDetaunted() const { return _taunted == TAUNT_STATE_DETAUNT; }
void SetThreat(float amount) { _baseAmount = amount; HeapNotifyChanged(); }
void AddThreat(float amount);
void ScaleThreat(float factor);
void ModifyThreatByPercent(int32 percent) { if (percent) ScaleThreat(0.01f*float(100 + percent)); }
void UpdateOnlineState();
void ClearThreat(bool sendRemove = true); // dealloc's this
private:
ThreatReference(ThreatManager* mgr, Unit* victim, float amount) : _owner(mgr->_owner), _mgr(mgr), _victim(victim), _baseAmount(amount), _tempModifier(0), _online(SelectOnlineState()), _taunted(TAUNT_STATE_NONE) { }
static bool FlagsAllowFighting(Unit const* a, Unit const* b);
OnlineState SelectOnlineState();
void UpdateTauntState(bool victimIsTaunting);
Unit* const _owner;
ThreatManager* const _mgr;
void HeapNotifyIncreased() { _mgr->_sortedThreatList.increase(_handle); }
void HeapNotifyDecreased() { _mgr->_sortedThreatList.decrease(_handle); }
void HeapNotifyChanged() { _mgr->_sortedThreatList.update(_handle); }
Unit* const _victim;
float _baseAmount;
int32 _tempModifier; // Temporary effects (auras with SPELL_AURA_MOD_TOTAL_THREAT) - set from victim's threatmanager in ThreatManager::UpdateMyTempModifiers
OnlineState _online;
TauntState _taunted;
ThreatManager::threat_list_heap::handle_type _handle;
public:
ThreatReference(ThreatReference const&) = delete;
ThreatReference& operator=(ThreatReference const&) = delete;
friend class ThreatManager;
friend struct CompareThreatLessThan;
};
inline bool CompareThreatLessThan::operator()(ThreatReference const* a, ThreatReference const* b) const { return ThreatManager::CompareReferencesLT(a, b, 1.0f); }
#endif
-121
View File
@@ -1,121 +0,0 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _UNITEVENTS
#define _UNITEVENTS
#include "Common.h"
class ThreatContainer;
class ThreatManager;
class HostileReference;
//==============================================================
//==============================================================
enum UNIT_EVENT_TYPE
{
// Player/Pet changed on/offline status
UEV_THREAT_REF_ONLINE_STATUS = 1<<0,
// Threat for Player/Pet changed
UEV_THREAT_REF_THREAT_CHANGE = 1<<1,
// Player/Pet will be removed from list (dead) [for internal use]
UEV_THREAT_REF_REMOVE_FROM_LIST = 1<<2,
// Player/Pet entered/left water or some other place where it is/was not accessible for the creature
UEV_THREAT_REF_ACCESSIBLE_STATUS = 1<<3,
// Threat list is going to be sorted (if dirty flag is set)
UEV_THREAT_SORT_LIST = 1<<4,
// New target should be fetched, could tbe the current target as well
UEV_THREAT_SET_NEXT_TARGET = 1<<5,
// A new victim (target) was set. Could be NULL
UEV_THREAT_VICTIM_CHANGED = 1<<6
// Future use
//UEV_UNIT_KILLED = 1<<7,
//Future use
//UEV_UNIT_HEALTH_CHANGE = 1<<8,
};
#define UEV_THREAT_REF_EVENT_MASK (UEV_THREAT_REF_ONLINE_STATUS | UEV_THREAT_REF_THREAT_CHANGE | UEV_THREAT_REF_REMOVE_FROM_LIST | UEV_THREAT_REF_ACCESSIBLE_STATUS)
#define UEV_THREAT_MANAGER_EVENT_MASK (UEV_THREAT_SORT_LIST | UEV_THREAT_SET_NEXT_TARGET | UEV_THREAT_VICTIM_CHANGED)
#define UEV_ALL_EVENT_MASK (0xffffffff)
// Future use
//#define UEV_UNIT_EVENT_MASK (UEV_UNIT_KILLED | UEV_UNIT_HEALTH_CHANGE)
//==============================================================
class UnitBaseEvent
{
public:
explicit UnitBaseEvent(uint32 pType) { iType = pType; }
uint32 getType() const { return iType; }
bool matchesTypeMask(uint32 pMask) const { return (iType & pMask) != 0; }
private:
uint32 iType;
protected:
~UnitBaseEvent() { }
};
//==============================================================
class TC_GAME_API ThreatRefStatusChangeEvent : public UnitBaseEvent
{
private:
HostileReference* iHostileReference;
union
{
float iFValue;
int32 iIValue;
bool iBValue;
};
ThreatManager* iThreatManager;
public:
explicit ThreatRefStatusChangeEvent(uint32 pType) : UnitBaseEvent(pType), iHostileReference(nullptr), iThreatManager(nullptr) { }
ThreatRefStatusChangeEvent(uint32 pType, HostileReference* pHostileReference) : UnitBaseEvent(pType), iHostileReference(pHostileReference), iThreatManager(nullptr) { }
ThreatRefStatusChangeEvent(uint32 pType, HostileReference* pHostileReference, float pValue) : UnitBaseEvent(pType), iHostileReference(pHostileReference), iFValue(pValue), iThreatManager(nullptr) { }
ThreatRefStatusChangeEvent(uint32 pType, HostileReference* pHostileReference, bool pValue) : UnitBaseEvent(pType), iHostileReference(pHostileReference), iBValue(pValue), iThreatManager(nullptr) { }
int32 getIValue() const { return iIValue; }
float getFValue() const { return iFValue; }
bool getBValue() const { return iBValue; }
void setBValue(bool pValue) { iBValue = pValue; }
HostileReference* getReference() const { return iHostileReference; }
void setThreatManager(ThreatManager* pThreatManager) { iThreatManager = pThreatManager; }
ThreatManager* GetThreatManager() const { return iThreatManager; }
};
#endif
@@ -671,6 +671,8 @@ bool Creature::UpdateEntry(uint32 entry, CreatureData const* data /*= nullptr*/,
UpdateMovementFlags();
LoadCreaturesAddon();
LoadTemplateImmunities();
GetThreatManager().UpdateOnlineStates(true, true);
return true;
}
@@ -771,6 +773,8 @@ void Creature::Update(uint32 diff)
if (!IsAlive())
break;
GetThreatManager().Update(diff);
if (m_shouldReacquireTarget && !IsFocusing(nullptr, true))
{
SetTarget(m_suppressedTarget);
@@ -1161,6 +1165,8 @@ bool Creature::Create(ObjectGuid::LowType guidlow, Map* map, uint32 entry, Posit
ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK_DEST, true);
}
GetThreatManager().Initialize();
return true;
}
@@ -1198,6 +1204,103 @@ Creature* Creature::CreateCreatureFromDB(ObjectGuid::LowType spawnId, Map* map,
return creature;
}
Unit* Creature::SelectVictim()
{
Unit* target = nullptr;
ThreatManager& mgr = GetThreatManager();
if (mgr.CanHaveThreatList())
{
target = mgr.SelectVictim();
while (!target)
{
Unit* newTarget = nullptr;
// nothing found to attack - try to find something we're in combat with (but don't have a threat entry for yet) and start attacking it
for (auto const& pair : GetCombatManager().GetPvECombatRefs())
{
newTarget = pair.second->GetOther(this);
if (!mgr.IsThreatenedBy(newTarget, true))
{
mgr.AddThreat(newTarget, 0.0f, nullptr, true, true);
break;
}
else
newTarget = nullptr;
}
if (!newTarget)
break;
target = mgr.SelectVictim();
}
}
else if (!HasReactState(REACT_PASSIVE))
{
// We're a player pet, probably
target = getAttackerForHelper();
if (!target && IsSummon())
{
if (Unit* owner = ToTempSummon()->GetOwner())
{
if (owner->IsInCombat())
target = owner->getAttackerForHelper();
if (!target)
{
for (ControlList::const_iterator itr = owner->m_Controlled.begin(); itr != owner->m_Controlled.end(); ++itr)
{
if ((*itr)->IsInCombat())
{
target = (*itr)->getAttackerForHelper();
if (target)
break;
}
}
}
}
}
}
else
return nullptr;
if (target && _IsTargetAcceptable(target) && CanCreatureAttack(target))
{
if (!IsFocusing(nullptr, true))
SetInFront(target);
return target;
}
/// @todo a vehicle may eat some mob, so mob should not evade
if (GetVehicle())
return nullptr;
// search nearby enemy before enter evade mode
if (HasReactState(REACT_AGGRESSIVE))
{
target = SelectNearestTargetInAttackDistance(m_CombatDistance ? m_CombatDistance : ATTACK_DISTANCE);
if (target && _IsTargetAcceptable(target) && CanCreatureAttack(target))
return target;
}
Unit::AuraEffectList const& iAuras = GetAuraEffectsByType(SPELL_AURA_MOD_INVISIBILITY);
if (!iAuras.empty())
{
for (Unit::AuraEffectList::const_iterator itr = iAuras.begin(); itr != iAuras.end(); ++itr)
{
if ((*itr)->GetBase()->IsPermanent())
{
AI()->EnterEvadeMode(CreatureAI::EVADE_REASON_OTHER);
break;
}
}
return nullptr;
}
// enter in evade mode in other case
AI()->EnterEvadeMode(CreatureAI::EVADE_REASON_NO_HOSTILES);
return nullptr;
}
void Creature::InitializeReactState()
{
if (IsTotem() || IsTrigger() || IsCritter() || IsSpiritService())
@@ -3286,6 +3389,37 @@ bool Creature::CanGiveExperience() const
&& !(GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_XP_AT_KILL);
}
void Creature::AtEnterCombat()
{
Unit::AtEnterCombat();
if (!(GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_MOUNTED_COMBAT_ALLOWED))
Dismount();
if (IsPet() || IsGuardian()) // update pets' speed for catchup OOC speed
{
UpdateSpeed(MOVE_RUN);
UpdateSpeed(MOVE_SWIM);
UpdateSpeed(MOVE_FLIGHT);
}
}
void Creature::AtExitCombat()
{
Unit::AtExitCombat();
ClearUnitState(UNIT_STATE_ATTACK_PLAYER);
if (HasDynamicFlag(UNIT_DYNFLAG_TAPPED))
SetDynamicFlags(GetCreatureTemplate()->dynamicflags);
if (IsPet() || IsGuardian()) // update pets' speed for catchup OOC speed
{
UpdateSpeed(MOVE_RUN);
UpdateSpeed(MOVE_SWIM);
UpdateSpeed(MOVE_FLIGHT);
}
}
bool Creature::IsEscortNPC(bool onlyIfActive)
{
if (!IsAIEnabled)
+15 -2
View File
@@ -102,11 +102,23 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma
bool IsDungeonBoss() const { return (GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_DUNGEON_BOSS) != 0; }
bool IsAffectedByDiminishingReturns() const override { return Unit::IsAffectedByDiminishingReturns() || (GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_ALL_DIMINISH) != 0; }
Unit* SelectVictim();
void SetReactState(ReactStates st) { m_reactState = st; }
ReactStates GetReactState() const { return m_reactState; }
bool HasReactState(ReactStates state) const { return (m_reactState == state); }
void InitializeReactState();
using Unit::IsImmuneToAll;
using Unit::SetImmuneToAll;
void SetImmuneToAll(bool apply) override { Unit::SetImmuneToAll(apply, HasReactState(REACT_PASSIVE)); }
using Unit::IsImmuneToPC;
using Unit::SetImmuneToPC;
void SetImmuneToPC(bool apply) override { Unit::SetImmuneToPC(apply, HasReactState(REACT_PASSIVE)); }
using Unit::IsImmuneToNPC;
using Unit::SetImmuneToNPC;
void SetImmuneToNPC(bool apply) override { Unit::SetImmuneToNPC(apply, HasReactState(REACT_PASSIVE)); }
/// @todo Rename these properly
bool isCanInteractWithBattleMaster(Player* player, bool msg) const;
bool CanResetTalents(Player* player) const;
@@ -303,8 +315,6 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma
void SignalFormationMovement(Position const& destination, uint32 id = 0, uint32 moveType = 0, bool orientation = false);
bool IsFormationLeaderMoveAllowed() const;
Unit* SelectVictim();
void SetDisableReputationGain(bool disable) { DisableReputationGain = disable; }
bool IsReputationGainDisabled() const { return DisableReputationGain; }
bool IsDamageEnoughForLootingAndReward() const { return (m_creatureInfo->flags_extra & CREATURE_FLAG_EXTRA_NO_PLAYER_DAMAGE_REQ) || (m_PlayerDamageReq == 0); }
@@ -346,6 +356,9 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma
bool CanGiveExperience() const;
void AtEnterCombat() override;
void AtExitCombat() override;
protected:
bool CreateFromProto(ObjectGuid::LowType guidlow, uint32 entry, CreatureData const* data = nullptr, uint32 vehId = 0);
bool InitEntry(uint32 entry, CreatureData const* data = nullptr);
+4
View File
@@ -428,6 +428,10 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation
{
return GetPhaseShift().CanSee(obj->GetPhaseShift());
}
static bool InSamePhase(WorldObject const* a, WorldObject const* b)
{
return a && b && a->IsInPhase(b);
}
PhaseShift& GetPhaseShift() { return _phaseShift; }
PhaseShift const& GetPhaseShift() const { return _phaseShift; }
+2
View File
@@ -1630,6 +1630,8 @@ bool Pet::Create(ObjectGuid::LowType guidlow, Map* map, uint32 Entry)
AddUnitFlag2(UNIT_FLAG2_REGENERATE_POWER);
SetSheath(SHEATH_STATE_MELEE);
GetThreatManager().Initialize();
return true;
}
+20 -21
View File
@@ -568,6 +568,8 @@ bool Player::Create(ObjectGuid::LowType guidlow, WorldPackets::Character::Charac
SetPrimarySpecialization(defaultSpec->ID);
}
GetThreatManager().Initialize();
return true;
}
@@ -946,7 +948,7 @@ void Player::Update(uint32 p_time)
UpdateAfkReport(now);
if (GetCombatTimer()) // Only set when in pvp combat
if (GetCombatManager().HasPvPCombat())
if (Aura* aura = GetAura(SPELL_PVP_RULES_ENABLED))
if (!aura->IsPermanent())
aura->SetDuration(aura->GetSpellInfo()->GetMaxDuration());
@@ -1220,7 +1222,7 @@ void Player::Update(uint32 p_time)
{
m_hostileReferenceCheckTimer = 15 * IN_MILLISECONDS;
if (!GetMap()->IsDungeon())
getHostileRefManager().deleteReferencesOutOfRange(GetVisibilityRange());
GetCombatManager().EndCombatBeyondRange(GetVisibilityRange(), true);
}
else
m_hostileReferenceCheckTimer -= p_time;
@@ -2060,9 +2062,6 @@ void Player::SetInWater(bool inWater)
// Call base
Unit::SetInWater(inWater);
// Update threat tables
getHostileRefManager().updateThreatTables();
}
bool Player::IsInAreaTriggerRadius(AreaTriggerEntry const* trigger) const
@@ -2104,15 +2103,11 @@ void Player::SetGameMaster(bool on)
AddUnitFlag2(UNIT_FLAG2_ALLOW_CHEAT_SPELLS);
if (Pet* pet = GetPet())
{
pet->SetFaction(FACTION_FRIENDLY);
pet->getHostileRefManager().setOnlineOfflineState(false);
}
RemovePvpFlag(UNIT_BYTE2_FLAG_FFA_PVP);
ResetContestedPvP();
getHostileRefManager().setOnlineOfflineState(false);
CombatStopWithPets();
PhasingHandler::SetAlwaysVisible(this, true, false);
@@ -2130,7 +2125,7 @@ void Player::SetGameMaster(bool on)
if (Pet* pet = GetPet())
{
pet->SetFaction(GetFaction());
pet->getHostileRefManager().setOnlineOfflineState(true);
pet->GetThreatManager().UpdateOnlineStates();
}
// restore FFA PvP Server state
@@ -2140,7 +2135,6 @@ void Player::SetGameMaster(bool on)
// restore FFA PvP area state, remove not allowed for GM mounts
UpdateArea(m_areaUpdateId);
getHostileRefManager().setOnlineOfflineState(true);
m_serverSideVisibilityDetect.SetValue(SERVERSIDE_VISIBILITY_GM, SEC_PLAYER);
}
@@ -22666,7 +22660,6 @@ void Player::CleanupAfterTaxiFlight()
m_taxi.ClearTaxiDestinations(); // not destinations, clear source node
Dismount();
RemoveUnitFlag(UnitFlags(UNIT_FLAG_REMOVE_CLIENT_CONTROL | UNIT_FLAG_TAXI_FLIGHT));
getHostileRefManager().setOnlineOfflineState(true);
}
void Player::ContinueTaxiFlight() const
@@ -25837,6 +25830,20 @@ void Player::ProcessTerrainStatusUpdate(ZLiquidStatus status, Optional<LiquidDat
m_MirrorTimerFlags &= ~(UNDERWATER_INWATER | UNDERWATER_INLAVA | UNDERWATER_INSLIME | UNDERWATER_INDARKWATER);
}
void Player::AtEnterCombat()
{
Unit::AtEnterCombat();
if (GetCombatManager().HasPvPCombat())
EnablePvpRules(true);
}
void Player::AtExitCombat()
{
Unit::AtExitCombat();
UpdatePotionCooldown();
m_combatExitTime = getMSTime();
}
float Player::GetBlockPercent(uint8 attackerLevel) const
{
float blockArmor = float(*m_activePlayerData->ShieldBlock);
@@ -26812,7 +26819,7 @@ void Player::DisablePvpRules()
if (IsInAreaThatActivatesPvpTalents())
return;
if (!GetCombatTimer())
if (!GetCombatManager().HasPvPCombat())
{
RemoveAurasDueToSpell(SPELL_PVP_RULES_ENABLED);
UpdateItemLevelAreaBasedScaling();
@@ -27983,14 +27990,6 @@ VoidStorageItem* Player::GetVoidStorageItem(uint64 id, uint8& slot) const
return nullptr;
}
void Player::OnCombatExit()
{
Unit::OnCombatExit();
UpdatePotionCooldown();
m_combatExitTime = getMSTime();
}
void Player::CreateGarrison(uint32 garrSiteId)
{
std::unique_ptr<Garrison> garrison(new Garrison(this));
+2 -2
View File
@@ -2012,6 +2012,8 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
bool UpdatePosition(float x, float y, float z, float orientation, bool teleport = false) override;
bool UpdatePosition(Position const& pos, bool teleport = false) override { return UpdatePosition(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), teleport); }
void ProcessTerrainStatusUpdate(ZLiquidStatus status, Optional<LiquidData> const& liquidData) override;
void AtEnterCombat() override;
void AtExitCombat() override;
void SendMessageToSet(WorldPacket const* data, bool self) const override { SendMessageToSetInRange(data, GetVisibilityRange(), self); }
void SendMessageToSetInRange(WorldPacket const* data, float dist, bool self) const override;
@@ -2528,8 +2530,6 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
VoidStorageItem* GetVoidStorageItem(uint8 slot) const;
VoidStorageItem* GetVoidStorageItem(uint64 id, uint8& slot) const;
void OnCombatExit() override;
void CreateGarrison(uint32 garrSiteId);
void DeleteGarrison();
Garrison* GetGarrison() const { return _garrison.get(); }
+176 -563
View File
@@ -295,13 +295,13 @@ SpellNonMeleeDamage::SpellNonMeleeDamage(Unit* _attacker, Unit* _target, SpellIn
Unit::Unit(bool isWorldObject) :
WorldObject(isWorldObject), m_playerMovingMe(nullptr), m_lastSanctuaryTime(0),
IsAIEnabled(false), NeedChangeAI(false), LastCharmerGUID(),
m_ControlledByPlayer(false), movespline(new Movement::MoveSpline()),
i_AI(nullptr), i_disabledAI(nullptr), m_AutoRepeatFirstCast(false), m_procDeep(0),
m_removedAurasCount(0), m_interruptMask(SpellAuraInterruptFlags::None), m_interruptMask2(SpellAuraInterruptFlags2::None),
i_motionMaster(new MotionMaster(this)), m_regenTimer(0), m_ThreatManager(this),
m_vehicle(nullptr), m_vehicleKit(nullptr), m_unitTypeMask(UNIT_MASK_NONE), m_Diminishing(),
m_HostileRefManager(this), _aiAnimKitId(0), _movementAnimKitId(0), _meleeAnimKitId(0),
IsAIEnabled(false), NeedChangeAI(false), LastCharmerGUID(), m_ControlledByPlayer(false),
movespline(new Movement::MoveSpline()), i_AI(nullptr), i_disabledAI(nullptr),
m_AutoRepeatFirstCast(false), m_procDeep(0), m_removedAurasCount(0),
m_interruptMask(SpellAuraInterruptFlags::None), m_interruptMask2(SpellAuraInterruptFlags2::None),
i_motionMaster(new MotionMaster(this)), m_regenTimer(0), m_vehicle(nullptr),
m_vehicleKit(nullptr), m_unitTypeMask(UNIT_MASK_NONE), m_Diminishing(), m_combatManager(this),
m_threatManager(this), _aiAnimKitId(0), _movementAnimKitId(0), _meleeAnimKitId(0),
_spellHistory(new SpellHistory(this))
{
m_objectType |= TYPEMASK_UNIT;
@@ -377,18 +377,12 @@ Unit::Unit(bool isWorldObject) :
}
m_baseSpellCritChance = 5;
m_CombatTimer = 0;
for (uint8 i = 0; i < MAX_SPELL_SCHOOL; ++i)
m_threatModifier[i] = 1.0f;
for (uint8 i = 0; i < MAX_MOVE_TYPE; ++i)
m_speed_rate[i] = 1.0f;
m_charmInfo = nullptr;
_redirectThreadInfo = RedirectThreatInfo();
// remove aurastates allowing special moves
for (uint8 i = 0; i < MAX_REACTIVE; ++i)
m_reactiveTimer[i] = 0;
@@ -438,28 +432,6 @@ Unit::~Unit()
ASSERT(m_dynObj.empty());
}
// Check if unit in combat with specific unit
bool Unit::IsInCombatWith(Unit const* who) const
{
// Check target exists
if (!who)
return false;
// Search in threat list
ObjectGuid guid = who->GetGUID();
for (ThreatContainer::StorageType::const_iterator i = m_ThreatManager.getThreatList().begin(); i != m_ThreatManager.getThreatList().end(); ++i)
{
HostileReference* ref = (*i);
// Return true if the unit matches
if (ref && ref->getUnitGuid() == guid)
return true;
}
// Nothing found, false.
return false;
}
void Unit::Update(uint32 p_time)
{
// WARNING! Order of execution here is important, do not change.
@@ -476,24 +448,7 @@ void Unit::Update(uint32 p_time)
// Having this would prevent spells from being proced, so let's crash
ASSERT(!m_procDeep);
if (CanHaveThreatList() && GetThreatManager().isNeedUpdateToClient(p_time))
SendThreatListUpdate();
// update combat timer only for players and pets (only pets with PetAI)
if (IsInCombat() && (GetTypeId() == TYPEID_PLAYER || (IsPet() && IsControlledByPlayer())))
{
// Check UNIT_STATE_MELEE_ATTACKING or UNIT_STATE_CHASE (without UNIT_STATE_FOLLOW in this case) so pets can reach far away
// targets without stopping half way there and running off.
// These flags are reset after target dies or another command is given.
if (m_HostileRefManager.isEmpty())
{
// m_CombatTimer set at aura start and it will be freeze until aura removing
if (m_CombatTimer <= p_time)
ClearInCombat();
else
m_CombatTimer -= p_time;
}
}
m_combatManager.Update(p_time);
// not implemented before 3.0.2
if (uint32 base_att = getAttackTimer(BASE_ATTACK))
@@ -2060,7 +2015,7 @@ void Unit::AttackerStateUpdate(Unit* victim, WeaponAttackType attType, bool extr
if ((attType == BASE_ATTACK || attType == OFF_ATTACK) && !IsWithinLOSInMap(victim))
return;
CombatStart(victim);
AttackedTarget(victim, true);
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Attacking);
if (attType != BASE_ATTACK && attType != OFF_ATTACK)
@@ -2144,7 +2099,7 @@ void Unit::FakeAttackerStateUpdate(Unit* victim, WeaponAttackType attType /*= BA
if ((attType == BASE_ATTACK || attType == OFF_ATTACK) && !IsWithinLOSInMap(victim))
return;
CombatStart(victim);
AttackedTarget(victim, true);
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Attacking);
if (attType != BASE_ATTACK && attType != OFF_ATTACK)
@@ -5718,20 +5673,20 @@ Unit* Unit::getAttackerForHelper() const // If someone wants to
return nullptr;
if (Unit* victim = GetVictim())
if ((!IsPet() && !GetPlayerMovingMe()) || IsInCombatWith(victim) || victim->IsInCombatWith(this))
if ((!IsPet() && !GetPlayerMovingMe()) || IsInCombatWith(victim))
return victim;
if (!m_attackers.empty())
return *(m_attackers.begin());
if (Player* owner = GetCharmerOrOwnerPlayerOrPlayerItself())
{
HostileRefManager& refs = owner->getHostileRefManager();
for (Reference<Unit, ThreatManager> const& ref : refs)
if (Unit* hostile = ref.GetSource()->GetOwner())
return hostile;
}
CombatManager const& mgr = GetCombatManager();
// pick arbitrary targets; our pvp combat > owner's pvp combat > our pve combat > owner's pve combat
Unit* owner = GetCharmerOrOwner();
if (mgr.HasPvPCombat())
return mgr.GetPvPCombatRefs().begin()->second->GetOther(this);
if (owner && (owner->GetCombatManager().HasPvPCombat()))
return owner->GetCombatManager().GetPvPCombatRefs().begin()->second->GetOther(owner);
if (mgr.HasPvECombat())
return mgr.GetPvECombatRefs().begin()->second->GetOther(this);
if (owner && (owner->GetCombatManager().HasPvECombat()))
return owner->GetCombatManager().GetPvECombatRefs().begin()->second->GetOther(owner);
return nullptr;
}
@@ -5822,19 +5777,7 @@ bool Unit::Attack(Unit* victim, bool meleeAttack)
if (creature && !IsPet())
{
// should not let player enter combat by right clicking target - doesn't helps
GetThreatManager().AddThreat(victim, 0.0f);
SetInCombatWith(victim);
if (victim->GetTypeId() == TYPEID_PLAYER)
victim->SetInCombatWith(this);
if (Unit* owner = victim->GetOwner())
{
GetThreatManager().AddThreat(owner, 0.0f);
SetInCombatWith(owner);
if (owner->GetTypeId() == TYPEID_PLAYER)
owner->SetInCombatWith(this);
}
EngageWithTarget(victim); // ensure that anything we're attacking has threat
creature->SendAIReaction(AI_REACTION_HOSTILE);
creature->CallAssistance();
@@ -5915,7 +5858,7 @@ void Unit::ValidateAttackersAndOwnTarget()
AttackStop();
}
void Unit::CombatStop(bool includingCast)
void Unit::CombatStop(bool includingCast, bool mutualPvP)
{
if (includingCast && IsNonMeleeSpellCast(false))
InterruptNonMeleeSpells(false);
@@ -5924,19 +5867,22 @@ void Unit::CombatStop(bool includingCast)
RemoveAllAttackers();
if (GetTypeId() == TYPEID_PLAYER)
ToPlayer()->SendAttackSwingCancelAttack(); // melee and ranged forced attack cancel
ClearInCombat();
// just in case
if (IsPetInCombat() && GetTypeId() != TYPEID_PLAYER)
ClearInPetCombat();
if (mutualPvP)
ClearInCombat();
else
{ // vanish and brethren are weird
m_combatManager.EndAllPvECombat();
m_combatManager.SuppressPvPCombat();
}
}
void Unit::CombatStopWithPets(bool includingCast)
{
CombatStop(includingCast);
for (ControlList::const_iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr)
(*itr)->CombatStop(includingCast);
for (Unit* minion : m_Controlled)
minion->CombatStop(includingCast);
}
bool Unit::isAttackingPlayer() const
@@ -6329,6 +6275,7 @@ void Unit::SetMinion(Minion *minion, bool apply)
}
}
}
UpdatePetCombatState();
}
void Unit::GetAllMinionsByEntry(std::list<TempSummon*>& Minions, uint32 entry)
@@ -6430,6 +6377,7 @@ void Unit::SetCharm(Unit* charm, bool apply)
m_Controlled.erase(charm);
}
}
UpdatePetCombatState();
}
void Unit::DealHeal(HealInfo& healInfo)
@@ -6576,6 +6524,8 @@ void Unit::RemoveAllControlled()
TC_LOG_FATAL("entities.unit", "Unit %u is not able to release its minion %s", GetEntry(), GetMinionGUID().ToString().c_str());
if (!GetCharmGUID().IsEmpty())
TC_LOG_FATAL("entities.unit", "Unit %u is not able to release its charm %s", GetEntry(), GetCharmGUID().ToString().c_str());
if (!IsPet()) // pets don't use the flag for this
RemoveUnitFlag(UNIT_FLAG_PET_IN_COMBAT); // m_controlled is now empty, so we know none of our minions are in combat
}
bool Unit::isPossessedByPlayer() const
@@ -8143,190 +8093,119 @@ bool Unit::IsServiceProvider() const
UNIT_NPC_FLAG_SPIRITGUIDE | UNIT_NPC_FLAG_TABARDDESIGNER | UNIT_NPC_FLAG_AUCTIONEER));
}
void Unit::SetInCombatWith(Unit* enemy)
void Unit::EngageWithTarget(Unit* enemy)
{
Unit* eOwner = enemy->GetCharmerOrOwnerOrSelf();
if (eOwner->IsPvP() || eOwner->IsFFAPvP())
{
SetInCombatState(true, enemy);
if (!enemy)
return;
}
// check for duel
if (eOwner->GetTypeId() == TYPEID_PLAYER && eOwner->ToPlayer()->duel)
if (IsEngagedBy(enemy))
return;
if (CanHaveThreatList())
m_threatManager.AddThreat(enemy, 0.0f, nullptr, true, true);
else
SetInCombatWith(enemy);
if (Creature* creature = ToCreature())
if (CreatureGroup* formation = creature->GetFormation())
formation->MemberEngagingTarget(creature, enemy);
}
void Unit::AttackedTarget(Unit* target, bool canInitialAggro)
{
if (!target->IsEngaged() && !canInitialAggro)
return;
target->EngageWithTarget(this);
if (Unit* targetOwner = target->GetCharmerOrOwner())
targetOwner->EngageWithTarget(this);
Player* myPlayerOwner = GetCharmerOrOwnerPlayerOrPlayerItself();
Player* targetPlayerOwner = target->GetCharmerOrOwnerPlayerOrPlayerItself();
if (myPlayerOwner && targetPlayerOwner && !(myPlayerOwner->duel && myPlayerOwner->duel->opponent == targetPlayerOwner))
{
Unit const* myOwner = GetCharmerOrOwnerOrSelf();
if (((Player const*)eOwner)->duel->opponent == myOwner)
{
SetInCombatState(true, enemy);
return;
}
myPlayerOwner->UpdatePvP(true);
myPlayerOwner->SetContestedPvP(targetPlayerOwner);
myPlayerOwner->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::PvPActive);
}
}
void Unit::SetImmuneToAll(bool apply, bool keepCombat)
{
if (apply)
{
AddUnitFlag(UnitFlags(UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_IMMUNE_TO_NPC));
ValidateAttackersAndOwnTarget();
if (keepCombat)
m_threatManager.UpdateOnlineStates(true, true);
else
m_combatManager.EndAllCombat();
}
else
{
RemoveUnitFlag(UnitFlags(UNIT_FLAG_IMMUNE_TO_PC | UNIT_FLAG_IMMUNE_TO_NPC));
m_threatManager.UpdateOnlineStates(true, true);
}
SetInCombatState(false, enemy);
}
void Unit::SetImmuneToPC(bool apply, bool keepCombat)
{
(void)keepCombat;
if (apply)
{
AddUnitFlag(UNIT_FLAG_IMMUNE_TO_PC);
ValidateAttackersAndOwnTarget();
if (keepCombat)
m_threatManager.UpdateOnlineStates(true, true);
else
{
std::list<CombatReference*> toEnd;
for (auto const& pair : m_combatManager.GetPvECombatRefs())
if (pair.second->GetOther(this)->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE))
toEnd.push_back(pair.second);
for (auto const& pair : m_combatManager.GetPvPCombatRefs())
if (pair.second->GetOther(this)->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE))
toEnd.push_back(pair.second);
for (CombatReference* ref : toEnd)
ref->EndCombat();
}
}
else
{
RemoveUnitFlag(UNIT_FLAG_IMMUNE_TO_PC);
m_threatManager.UpdateOnlineStates(true, true);
}
}
void Unit::SetImmuneToNPC(bool apply, bool keepCombat)
{
(void)keepCombat;
if (apply)
{
AddUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC);
ValidateAttackersAndOwnTarget();
if (keepCombat)
m_threatManager.UpdateOnlineStates(true, true);
else
{
std::list<CombatReference*> toEnd;
for (auto const& pair : m_combatManager.GetPvECombatRefs())
if (!pair.second->GetOther(this)->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE))
toEnd.push_back(pair.second);
for (auto const& pair : m_combatManager.GetPvPCombatRefs())
if (!pair.second->GetOther(this)->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE))
toEnd.push_back(pair.second);
for (CombatReference* ref : toEnd)
ref->EndCombat();
}
}
else
{
RemoveUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC);
}
void Unit::CombatStart(Unit* target, bool initialAggro)
{
if (initialAggro)
{
if (!target->IsStandState())
target->SetStandState(UNIT_STAND_STATE_STAND);
if (!target->IsInCombat() && target->GetTypeId() != TYPEID_PLAYER
&& !target->ToCreature()->HasReactState(REACT_PASSIVE) && target->ToCreature()->IsAIEnabled)
target->ToCreature()->AI()->AttackStart(this);
SetInCombatWith(target);
target->SetInCombatWith(this);
}
Player* me = GetCharmerOrOwnerPlayerOrPlayerItself();
Unit* who = target->GetCharmerOrOwnerOrSelf();
if (me && who->GetTypeId() == TYPEID_PLAYER)
me->SetContestedPvP(who->ToPlayer());
if (me && who->IsPvP()
&& (who->GetTypeId() != TYPEID_PLAYER
|| !me->duel || me->duel->opponent != who))
{
me->UpdatePvP(true);
me->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::PvPActive);
m_threatManager.UpdateOnlineStates(true, true);
}
}
void Unit::SetInCombatState(bool PvP, Unit* enemy)
bool Unit::IsThreatened() const
{
// only alive units can be in combat
if (!IsAlive())
return;
if (PvP)
{
m_CombatTimer = 5000;
if (Player* me = ToPlayer())
me->EnablePvpRules(true);
}
if (IsInCombat() || HasUnitState(UNIT_STATE_EVADE))
return;
AddUnitFlag(UNIT_FLAG_IN_COMBAT);
if (Creature* creature = ToCreature())
{
// Set home position at place of engaging combat for escorted creatures
if ((IsAIEnabled && creature->AI()->IsEscorted()) ||
GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE ||
GetMotionMaster()->GetCurrentMovementGeneratorType() == POINT_MOTION_TYPE)
creature->SetHomePosition(GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation());
if (enemy)
{
if (IsAIEnabled)
creature->AI()->JustEngagedWith(enemy);
if (creature->GetFormation())
creature->GetFormation()->MemberEngagingTarget(creature, enemy);
}
if (IsPet())
{
UpdateSpeed(MOVE_RUN);
UpdateSpeed(MOVE_SWIM);
UpdateSpeed(MOVE_FLIGHT);
}
if (!(creature->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_MOUNTED_COMBAT_ALLOWED))
Dismount();
}
for (Unit::ControlList::iterator itr = m_Controlled.begin(); itr != m_Controlled.end();)
{
Unit* controlled = *itr;
++itr;
controlled->SetInCombatState(PvP, enemy);
}
for (auto itr = m_appliedAuras.begin(); itr != m_appliedAuras.end();)
{
AuraApplication* aurApp = itr->second;
++itr;
aurApp->GetBase()->CallScriptEnterLeaveCombatHandlers(aurApp, true);
}
if (Spell* spell = m_currentSpells[CURRENT_GENERIC_SPELL])
if (spell->getState() == SPELL_STATE_PREPARING
&& spell->m_spellInfo->HasAttribute(SPELL_ATTR0_CANT_USED_IN_COMBAT)
&& spell->m_spellInfo->InterruptFlags.HasFlag(SpellInterruptFlags::Combat))
InterruptNonMeleeSpells(false);
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::EnteringCombat);
ProcSkillsAndAuras(enemy, PROC_FLAG_ENTER_COMBAT, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr);
}
void Unit::ClearInCombat()
{
m_CombatTimer = 0;
RemoveUnitFlag(UNIT_FLAG_IN_COMBAT);
// Player's state will be cleared in Player::UpdateContestedPvP
if (Creature* creature = ToCreature())
{
ClearUnitState(UNIT_STATE_ATTACK_PLAYER);
if (HasDynamicFlag(UNIT_DYNFLAG_TAPPED))
SetDynamicFlags(creature->GetCreatureTemplate()->dynamicflags);
if (creature->IsPet() || creature->IsGuardian())
{
if (Unit* owner = GetOwner())
for (uint8 i = 0; i < MAX_MOVE_TYPE; ++i)
if (owner->GetSpeedRate(UnitMoveType(i)) > GetSpeedRate(UnitMoveType(i)))
SetSpeedRate(UnitMoveType(i), owner->GetSpeedRate(UnitMoveType(i)));
}
else if (!IsCharmed())
return;
}
OnCombatExit();
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::LeavingCombat);
}
void Unit::ClearInPetCombat()
{
RemoveUnitFlag(UNIT_FLAG_PET_IN_COMBAT);
if (Unit* owner = GetOwner())
owner->RemoveUnitFlag(UNIT_FLAG_PET_IN_COMBAT);
}
void Unit::OnCombatExit()
{
for (auto itr = m_appliedAuras.begin(); itr != m_appliedAuras.end();)
{
AuraApplication* aurApp = itr->second;
++itr;
aurApp->GetBase()->CallScriptEnterLeaveCombatHandlers(aurApp, false);
}
return !m_threatManager.IsThreatListEmpty();
}
bool Unit::isTargetableForAttack(bool checkFakeDeath) const
@@ -8375,7 +8254,7 @@ bool Unit::_IsValidAttackTarget(Unit const* target, SpellInfo const* bySpell, Wo
{
// ignore stealth for aoe spells. Ignore stealth if target is player and unit in combat with same player
bool const ignoreStealthCheck = (bySpell && bySpell->IsAffectingArea()) ||
(target->GetTypeId() == TYPEID_PLAYER && target->HasStealthAura() && target->IsInCombat() && IsInCombatWith(target));
(target->GetTypeId() == TYPEID_PLAYER && target->HasStealthAura() && IsInCombatWith(target));
if (!CanSeeOrDetect(target, ignoreStealthCheck))
return false;
@@ -8961,7 +8840,6 @@ void Unit::setDeathState(DeathState s)
{
CombatStop();
GetThreatManager().ClearAllThreat();
getHostileRefManager().deleteReferences();
if (IsNonMeleeSpellCast(false))
InterruptNonMeleeSpells(false);
@@ -9005,242 +8883,59 @@ void Unit::setDeathState(DeathState s)
RemoveUnitFlag(UNIT_FLAG_SKINNABLE); // clear skinnable for creature and player (at battleground)
}
/*########################################
######## ########
######## AGGRO SYSTEM ########
######## ########
########################################*/
bool Unit::CanHaveThreatList(bool skipAliveCheck) const
{
// only creatures can have threat list
if (GetTypeId() != TYPEID_UNIT)
return false;
// only alive units can have threat list
if (!skipAliveCheck && !IsAlive())
return false;
// totems can not have threat list
if (IsTotem())
return false;
// vehicles can not have threat list
//if (ToCreature()->IsVehicle())
// return false;
// summons can not have a threat list, unless they are controlled by a creature
if (HasUnitTypeMask(UNIT_MASK_MINION | UNIT_MASK_GUARDIAN | UNIT_MASK_CONTROLABLE_GUARDIAN) && GetOwnerGUID().IsPlayer())
return false;
return true;
}
//======================================================================
float Unit::ApplyTotalThreatModifier(float fThreat, SpellSchoolMask schoolMask)
void Unit::AtEnterCombat()
{
if (!HasAuraType(SPELL_AURA_MOD_THREAT) || fThreat < 0)
return fThreat;
SpellSchools school = GetFirstSchoolInMask(schoolMask);
return fThreat * m_threatModifier[school];
}
//======================================================================
void Unit::TauntApply(Unit* taunter)
{
ASSERT(GetTypeId() == TYPEID_UNIT);
if (!taunter || (taunter->GetTypeId() == TYPEID_PLAYER && taunter->ToPlayer()->IsGameMaster()))
return;
if (!CanHaveThreatList())
return;
Creature* creature = ToCreature();
if (creature->HasReactState(REACT_PASSIVE))
return;
Unit* target = GetVictim();
if (target && target == taunter)
return;
if (!IsFocusing(nullptr, true))
SetInFront(taunter);
if (creature->IsAIEnabled)
creature->AI()->AttackStart(taunter);
//m_ThreatManager.tauntApply(taunter);
}
//======================================================================
void Unit::TauntFadeOut(Unit* taunter)
{
ASSERT(GetTypeId() == TYPEID_UNIT);
if (!taunter || (taunter->GetTypeId() == TYPEID_PLAYER && taunter->ToPlayer()->IsGameMaster()))
return;
if (!CanHaveThreatList())
return;
Creature* creature = ToCreature();
if (creature->HasReactState(REACT_PASSIVE))
return;
Unit* target = GetVictim();
if (!target || target != taunter)
return;
if (m_ThreatManager.isThreatListEmpty())
for (auto itr = m_appliedAuras.begin(); itr != m_appliedAuras.end();)
{
if (creature->IsAIEnabled)
creature->AI()->EnterEvadeMode(CreatureAI::EVADE_REASON_NO_HOSTILES);
return;
AuraApplication* aurApp = itr->second;
++itr;
aurApp->GetBase()->CallScriptEnterLeaveCombatHandlers(aurApp, true);
}
target = creature->SelectVictim(); // might have more taunt auras remaining
if (Spell* spell = m_currentSpells[CURRENT_GENERIC_SPELL])
if (spell->getState() == SPELL_STATE_PREPARING
&& spell->m_spellInfo->HasAttribute(SPELL_ATTR0_CANT_USED_IN_COMBAT)
&& spell->m_spellInfo->InterruptFlags.HasFlag(SpellInterruptFlags::Combat))
InterruptNonMeleeSpells(false);
if (target && target != taunter)
{
if (!IsFocusing(nullptr, true))
SetInFront(target);
if (creature->IsAIEnabled)
creature->AI()->AttackStart(target);
}
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::EnteringCombat);
ProcSkillsAndAuras(nullptr, PROC_FLAG_ENTER_COMBAT, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr);
}
//======================================================================
Unit* Creature::SelectVictim()
void Unit::AtExitCombat()
{
// function provides main threat functionality
// next-victim-selection algorithm and evade mode are called
// threat list sorting etc.
Unit* target = nullptr;
// First checking if we have some taunt on us
AuraEffectList const& tauntAuras = GetAuraEffectsByType(SPELL_AURA_MOD_TAUNT);
if (!tauntAuras.empty())
for (auto itr = m_appliedAuras.begin(); itr != m_appliedAuras.end();)
{
Unit* caster = tauntAuras.back()->GetCaster();
AuraApplication* aurApp = itr->second;
++itr;
// The last taunt aura caster is alive an we are happy to attack him
if (caster && caster->IsAlive())
return GetVictim();
else if (tauntAuras.size() > 1)
aurApp->GetBase()->CallScriptEnterLeaveCombatHandlers(aurApp, false);
}
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::LeavingCombat);
}
void Unit::UpdatePetCombatState()
{
ASSERT(!IsPet()); // player pets do not use UNIT_FLAG_PET_IN_COMBAT for this purpose - but player pets should also never have minions of their own to call this
bool state = false;
for (Unit* minion : m_Controlled)
if (minion->IsInCombat())
{
// We do not have last taunt aura caster but we have more taunt auras,
// so find first available target
// Auras are pushed_back, last caster will be on the end
AuraEffectList::const_iterator aura = --tauntAuras.end();
do
{
--aura;
caster = (*aura)->GetCaster();
if (caster && CanSeeOrDetect(caster, true) && IsValidAttackTarget(caster) && caster->isInAccessiblePlaceFor(ToCreature()))
{
target = caster;
break;
}
} while (aura != tauntAuras.begin());
state = true;
break;
}
else
target = GetVictim();
}
if (CanHaveThreatList())
{
if (!target && !m_ThreatManager.isThreatListEmpty())
// No taunt aura or taunt aura caster is dead standard target selection
target = m_ThreatManager.getHostilTarget();
}
else if (!HasReactState(REACT_PASSIVE))
{
// We have player pet probably
target = getAttackerForHelper();
if (!target && IsSummon())
{
if (Unit* owner = ToTempSummon()->GetOwner())
{
if (owner->IsInCombat())
target = owner->getAttackerForHelper();
if (!target)
{
for (ControlList::const_iterator itr = owner->m_Controlled.begin(); itr != owner->m_Controlled.end(); ++itr)
{
if ((*itr)->IsInCombat())
{
target = (*itr)->getAttackerForHelper();
if (target)
break;
}
}
}
}
}
}
if (state)
AddUnitFlag(UNIT_FLAG_PET_IN_COMBAT);
else
return nullptr;
if (target && _IsTargetAcceptable(target) && CanCreatureAttack(target))
{
if (!IsFocusing(nullptr, true))
SetInFront(target);
return target;
}
// last case when creature must not go to evade mode:
// it in combat but attacker not make any damage and not enter to aggro radius to have record in threat list
// Note: creature does not have targeted movement generator but has attacker in this case
for (AttackerSet::const_iterator itr = m_attackers.begin(); itr != m_attackers.end(); ++itr)
{
if ((*itr) && CanCreatureAttack(*itr) && (*itr)->GetTypeId() != TYPEID_PLAYER
&& !(*itr)->ToCreature()->HasUnitTypeMask(UNIT_MASK_CONTROLABLE_GUARDIAN))
return nullptr;
}
/// @todo a vehicle may eat some mob, so mob should not evade
if (GetVehicle())
return nullptr;
// search nearby enemy before enter evade mode
if (HasReactState(REACT_AGGRESSIVE))
{
target = SelectNearestTargetInAttackDistance(m_CombatDistance ? m_CombatDistance : ATTACK_DISTANCE);
if (target && _IsTargetAcceptable(target) && CanCreatureAttack(target))
return target;
}
Unit::AuraEffectList const& iAuras = GetAuraEffectsByType(SPELL_AURA_MOD_INVISIBILITY);
if (!iAuras.empty())
{
for (Unit::AuraEffectList::const_iterator itr = iAuras.begin(); itr != iAuras.end(); ++itr)
{
if ((*itr)->GetBase()->IsPermanent())
{
AI()->EnterEvadeMode(CreatureAI::EVADE_REASON_OTHER);
break;
}
}
return nullptr;
}
// enter in evade mode in other case
AI()->EnterEvadeMode(CreatureAI::EVADE_REASON_NO_HOSTILES);
return nullptr;
RemoveUnitFlag(UNIT_FLAG_PET_IN_COMBAT);
}
//======================================================================
//======================================================================
//======================================================================
float Unit::ApplyEffectModifiers(SpellInfo const* spellProto, uint8 effect_index, float value) const
@@ -10274,7 +9969,6 @@ void Unit::CleanupBeforeRemoveFromMap(bool finalCleanup)
m_Events.KillAllEvents(false); // non-delatable (currently cast spells) will not deleted now but it will deleted at call in Map::RemoveAllObjectsInRemoveList
CombatStop();
GetThreatManager().ClearAllThreat();
getHostileRefManager().deleteReferences();
}
void Unit::CleanupsBeforeDelete(bool finalCleanup)
@@ -11701,7 +11395,6 @@ void Unit::Kill(Unit* victim, bool durabilityLoss /*= true*/, bool skipSettingDe
}
// 10% durability loss on death
// clean InHateListOf
if (Player* plrVictim = victim->ToPlayer())
{
// remember victim PvP death for corpse type and corpse reclaim delay
@@ -12107,7 +11800,6 @@ bool Unit::SetCharmedBy(Unit* charmer, CharmType type, AuraApplication const* au
CastStop();
CombatStop(); /// @todo CombatStop(true) may cause crash (interrupt spells)
GetThreatManager().ClearAllThreat();
Player* playerCharmer = charmer->ToPlayer();
@@ -12255,8 +11947,6 @@ void Unit::RemoveCharmedBy(Unit* charmer)
CastStop();
CombatStop(); /// @todo CombatStop(true) may cause crash (interrupt spells)
getHostileRefManager().deleteReferences();
GetThreatManager().ClearAllThreat();
if (_oldFactionId)
{
@@ -12336,6 +12026,8 @@ void Unit::RemoveCharmedBy(Unit* charmer)
player->SetClientControl(this, true);
}
EngageWithTarget(charmer);
// a guardian should always have charminfo
if (playerCharmer && this != charmer->GetFirstControlled())
playerCharmer->SendRemoveControlBar();
@@ -12363,11 +12055,6 @@ void Unit::RestoreFaction()
}
}
Unit* Unit::GetRedirectThreatTarget()
{
return !_redirectThreadInfo.GetTargetGUID().IsEmpty() ? ObjectAccessor::GetUnit(*this, _redirectThreadInfo.GetTargetGUID()) : nullptr;
}
bool Unit::CreateVehicleKit(uint32 id, uint32 creatureEntry, bool loading /*= false*/)
{
VehicleEntry const* vehInfo = sVehicleStore.LookupEntry(id);
@@ -12748,35 +12435,7 @@ void Unit::OnPhaseChange()
return;
if (GetTypeId() == TYPEID_UNIT || !ToPlayer()->GetSession()->PlayerLogout())
{
HostileRefManager& refManager = getHostileRefManager();
HostileReference* ref = refManager.getFirst();
while (ref)
{
if (Unit* unit = ref->GetSource()->GetOwner())
if (Creature* creature = unit->ToCreature())
refManager.setOnlineOfflineState(creature, creature->IsInPhase(this));
ref = ref->next();
}
// modify threat lists for new phasemask
if (GetTypeId() != TYPEID_PLAYER)
{
std::list<HostileReference*> threatList = GetThreatManager().getThreatList();
std::list<HostileReference*> offlineThreatList = GetThreatManager().getOfflineThreatList();
// merge expects sorted lists
threatList.sort();
offlineThreatList.sort();
threatList.merge(offlineThreatList);
for (std::list<HostileReference*>::const_iterator itr = threatList.begin(); itr != threatList.end(); ++itr)
if (Unit* unit = (*itr)->getTarget())
unit->getHostileRefManager().setOnlineOfflineState(ToCreature(), unit->IsInPhase(this));
}
}
m_threatManager.UpdateOnlineStates(true, true);
}
void Unit::UpdateObjectVisibility(bool forced)
@@ -12785,6 +12444,7 @@ void Unit::UpdateObjectVisibility(bool forced)
AddToNotify(NOTIFY_VISIBILITY_CHANGED);
else
{
m_threatManager.UpdateOnlineStates(true, true);
WorldObject::UpdateObjectVisibility(true);
// call MoveInLineOfSight for nearby creatures
Trinity::AIRelocationNotifier notifier(*this);
@@ -13222,7 +12882,9 @@ bool Unit::CanSwim() const
return true;
if (HasUnitFlag2(UnitFlags2(0x1000000)))
return false;
return HasUnitFlag(UnitFlags(UNIT_FLAG_PET_IN_COMBAT | UNIT_FLAG_RENAME | UNIT_FLAG_UNK_15));
if (IsPet() && HasUnitFlag(UNIT_FLAG_PET_IN_COMBAT))
return true;
return HasUnitFlag(UnitFlags(UNIT_FLAG_RENAME | UNIT_FLAG_UNK_15));
}
void Unit::NearTeleportTo(Position const& pos, bool casting /*= false*/)
@@ -13344,60 +13006,6 @@ void Unit::UpdateHeight(float newZ)
GetVehicleKit()->RelocatePassengers();
}
void Unit::SendThreatListUpdate()
{
if (!GetThreatManager().isThreatListEmpty())
{
WorldPackets::Combat::ThreatUpdate packet;
packet.UnitGUID = GetGUID();
ThreatContainer::StorageType const& tlist = GetThreatManager().getThreatList();
packet.ThreatList.reserve(tlist.size());
for (ThreatContainer::StorageType::const_iterator itr = tlist.begin(); itr != tlist.end(); ++itr)
{
WorldPackets::Combat::ThreatInfo info;
info.UnitGUID = (*itr)->getUnitGuid();
info.Threat = int64((*itr)->getThreat() * 100);
packet.ThreatList.push_back(info);
}
SendMessageToSet(packet.Write(), false);
}
}
void Unit::SendChangeCurrentVictimOpcode(HostileReference* pHostileReference)
{
if (!GetThreatManager().isThreatListEmpty())
{
WorldPackets::Combat::HighestThreatUpdate packet;
packet.UnitGUID = GetGUID();
packet.HighestThreatGUID = pHostileReference->getUnitGuid();
ThreatContainer::StorageType const& tlist = GetThreatManager().getThreatList();
packet.ThreatList.reserve(tlist.size());
for (ThreatContainer::StorageType::const_iterator itr = tlist.begin(); itr != tlist.end(); ++itr)
{
WorldPackets::Combat::ThreatInfo info;
info.UnitGUID = (*itr)->getUnitGuid();
info.Threat = int64((*itr)->getThreat() * 100);
packet.ThreatList.push_back(info);
}
SendMessageToSet(packet.Write(), false);
}
}
void Unit::SendClearThreatListOpcode()
{
WorldPackets::Combat::ThreatClear packet;
packet.UnitGUID = GetGUID();
SendMessageToSet(packet.Write(), false);
}
void Unit::SendRemoveFromThreatListOpcode(HostileReference* pHostileReference)
{
WorldPackets::Combat::ThreatRemove packet;
packet.UnitGUID = GetGUID();
packet.AboutGUID = pHostileReference->getUnitGuid();
SendMessageToSet(packet.Write(), false);
}
// baseRage means damage taken when attacker = false
void Unit::RewardRage(uint32 baseRage)
{
@@ -13439,10 +13047,15 @@ void Unit::StopAttackFaction(uint32 faction_id)
++itr;
}
getHostileRefManager().deleteReferencesForFaction(faction_id);
std::vector<CombatReference*> refsToEnd;
for (auto const& pair : m_combatManager.GetPvECombatRefs())
if (pair.second->GetOther(this)->GetFactionTemplateEntry()->Faction == faction_id)
refsToEnd.push_back(pair.second);
for (CombatReference* ref : refsToEnd)
ref->EndCombat();
for (ControlList::const_iterator itr = m_Controlled.begin(); itr != m_Controlled.end(); ++itr)
(*itr)->StopAttackFaction(faction_id);
for (Unit* minion : m_Controlled)
minion->StopAttackFaction(faction_id);
}
void Unit::OutDebugInfo() const
+43 -76
View File
@@ -22,7 +22,7 @@
#include "EventProcessor.h"
#include "FollowerReference.h"
#include "FollowerRefManager.h"
#include "HostileRefManager.h"
#include "CombatManager.h"
#include "OptionalFwd.h"
#include "SpellAuraDefines.h"
#include "SpellDefines.h"
@@ -581,28 +581,6 @@ struct SpellPeriodicAuraLogInfo
uint32 createProcHitMask(SpellNonMeleeDamage* damageInfo, SpellMissInfo missCondition);
struct RedirectThreatInfo
{
RedirectThreatInfo() : _threatPct(0) { }
ObjectGuid _targetGUID;
uint32 _threatPct;
ObjectGuid GetTargetGUID() const { return _targetGUID; }
uint32 GetThreatPct() const { return _threatPct; }
void Set(ObjectGuid guid, uint32 pct)
{
_targetGUID = guid;
_threatPct = pct;
}
void ModifyThreatPct(int32 amount)
{
amount += _threatPct;
_threatPct = uint32(std::max(0, amount));
}
};
enum CurrentSpellTypes : uint8
{
CURRENT_MELEE_SPELL = 0,
@@ -854,7 +832,7 @@ class TC_GAME_API Unit : public WorldObject
}
void ValidateAttackersAndOwnTarget();
void CombatStop(bool includingCast = false);
void CombatStop(bool includingCast = false, bool mutualPvP = true);
void CombatStopWithPets(bool includingCast = false);
void StopAttackFaction(uint32 faction_id);
Unit* SelectNearbyTarget(Unit* exclude = nullptr, float dist = NOMINAL_MELEE_RANGE) const;
@@ -1123,29 +1101,45 @@ class TC_GAME_API Unit : public WorldObject
bool IsInFlight() const { return HasUnitState(UNIT_STATE_IN_FLIGHT); }
bool IsEngaged() const { return IsInCombat(); }
bool IsEngagedBy(Unit const* who) const { return IsInCombatWith(who); }
void EngageWithTarget(Unit* who) { SetInCombatWith(who); who->SetInCombatWith(this); GetThreatManager().AddThreat(who, 0.0f); }
bool IsThreatened() const { return CanHaveThreatList() && !GetThreatManager().IsThreatListEmpty(); }
bool IsThreatenedBy(Unit const* who) const { return who && CanHaveThreatList() && GetThreatManager().IsThreatenedBy(who); }
/// ====================== THREAT & COMBAT ====================
bool CanHaveThreatList() const { return m_threatManager.CanHaveThreatList(); }
// For NPCs with threat list: Whether there are any enemies on our threat list
// For other units: Whether we're in combat
// This value is different from IsInCombat when a projectile spell is midair (combat on launch - threat+aggro on impact)
bool IsEngaged() const { return CanHaveThreatList() ? m_threatManager.IsEngaged() : 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
CombatManager& GetCombatManager() { return m_combatManager; }
CombatManager const& GetCombatManager() const { return m_combatManager; }
void AttackedTarget(Unit* target, bool canInitialAggro);
void SetImmuneToAll(bool apply, bool keepCombat = false) { SetImmuneToPC(apply, keepCombat); SetImmuneToNPC(apply, keepCombat); }
bool IsImmuneToAll() const { return IsImmuneToPC() && IsImmuneToNPC(); }
void SetImmuneToPC(bool apply, bool keepCombat = false);
void SetImmuneToAll(bool apply, bool keepCombat);
virtual void SetImmuneToAll(bool apply) { SetImmuneToAll(apply, false); }
bool IsImmuneToPC() const { return HasUnitFlag(UNIT_FLAG_IMMUNE_TO_PC); }
void SetImmuneToNPC(bool apply, bool keepCombat = false);
void SetImmuneToPC(bool apply, bool keepCombat);
virtual void SetImmuneToPC(bool apply) { SetImmuneToPC(apply, false); }
bool IsImmuneToNPC() const { return HasUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC); }
void SetImmuneToNPC(bool apply, bool keepCombat);
virtual void SetImmuneToNPC(bool apply) { SetImmuneToNPC(apply, false); }
bool IsInCombat() const { return HasUnitFlag(UNIT_FLAG_IN_COMBAT); }
bool IsPetInCombat() const { return HasUnitFlag(UNIT_FLAG_PET_IN_COMBAT); }
bool IsInCombatWith(Unit const* who) const;
void CombatStart(Unit* target, bool initialAggro = true);
void SetInCombatState(bool PvP, Unit* enemy = nullptr);
void SetInCombatWith(Unit* enemy);
void ClearInCombat();
void ClearInPetCombat();
uint32 GetCombatTimer() const { return m_CombatTimer; }
virtual void OnCombatExit();
bool IsInCombat() const { return HasUnitFlag(UNIT_FLAG_IN_COMBAT); }
bool IsInCombatWith(Unit const* who) const { return who && m_combatManager.IsInCombatWith(who); }
void SetInCombatWith(Unit* enemy) { if (enemy) m_combatManager.SetInCombatWith(enemy); }
void ClearInCombat() { m_combatManager.EndAllCombat(); }
void UpdatePetCombatState();
// Threat handling
bool IsThreatened() const;
bool IsThreatenedBy(Unit const* who) const { return who && m_threatManager.IsThreatenedBy(who, true); }
// Exposes the threat manager directly - be careful when interfacing with this
// As a general rule of thumb, any unit pointer MUST be null checked BEFORE passing it to threatmanager methods
// threatmanager will NOT null check your pointers for you - misuse = crash
ThreatManager& GetThreatManager() { return m_threatManager; }
ThreatManager const& GetThreatManager() const { return m_threatManager; }
void SendClearTarget();
void SendThreatListUpdate() { m_threatManager.SendThreatListToClients(); }
bool HasAuraTypeWithFamilyFlags(AuraType auraType, uint32 familyName, flag128 familyFlags) const;
bool virtual HasSpell(uint32 /*spellID*/) const { return false; }
@@ -1251,13 +1245,6 @@ class TC_GAME_API Unit : public WorldObject
void SetFacingTo(float const ori, bool force = true);
void SetFacingToObject(WorldObject const* object, bool force = true);
void SendChangeCurrentVictimOpcode(HostileReference* pHostileReference);
void SendClearThreatListOpcode();
void SendRemoveFromThreatListOpcode(HostileReference* pHostileReference);
void SendThreatListUpdate();
void SendClearTarget();
bool IsAlive() const { return (m_deathState == ALIVE); }
bool isDying() const { return (m_deathState == JUST_DIED); }
bool isDead() const { return (m_deathState == DEAD || m_deathState == CORPSE); }
@@ -1554,7 +1541,6 @@ class TC_GAME_API Unit : public WorldObject
float m_modSpellHitChance;
int32 m_baseSpellCritChance;
float m_threatModifier[MAX_SPELL_SCHOOL];
uint32 m_baseAttackSpeed[MAX_ATTACK];
float m_modAttackSpeedPct[MAX_ATTACK];
@@ -1632,17 +1618,6 @@ class TC_GAME_API Unit : public WorldObject
SpellImmuneContainer m_spellImmune[MAX_SPELL_IMMUNITY];
uint32 m_lastSanctuaryTime;
// Threat related methods
bool CanHaveThreatList(bool skipAliveCheck = false) const;
float ApplyTotalThreatModifier(float fThreat, SpellSchoolMask schoolMask = SPELL_SCHOOL_MASK_NORMAL);
void TauntApply(Unit* victim);
void TauntFadeOut(Unit* taunter);
ThreatManager& GetThreatManager() { return m_ThreatManager; }
ThreatManager const& GetThreatManager() const { return m_ThreatManager; }
void addHatedBy(HostileReference* pHostileReference) { m_HostileRefManager.insertFirst(pHostileReference); }
void removeHatedBy(HostileReference* /*pHostileReference*/) { /* nothing to do yet */ }
HostileRefManager& getHostileRefManager() { return m_HostileRefManager; }
VisibleAuraContainer const& GetVisibleAuras() const { return m_visibleAuras; }
bool HasVisibleAura(AuraApplication* aurApp) const { return m_visibleAuras.count(aurApp) > 0; }
void SetVisibleAura(AuraApplication* aurApp);
@@ -1724,8 +1699,6 @@ class TC_GAME_API Unit : public WorldObject
uint32 SpellCriticalDamageBonus(SpellInfo const* spellProto, uint32 damage, Unit* victim);
uint32 SpellCriticalHealingBonus(SpellInfo const* spellProto, uint32 damage, Unit* victim);
void SetContestedPvP(Player* attackedPlayer = nullptr);
uint32 GetCastingTimeForBonus(SpellInfo const* spellProto, DamageEffectType damagetype, uint32 CastingTime) const;
float CalculateDefaultCoefficient(SpellInfo const* spellInfo, DamageEffectType damagetype) const;
@@ -1810,13 +1783,6 @@ class TC_GAME_API Unit : public WorldObject
uint32 GetModelForForm(ShapeshiftForm form, uint32 spellId) const;
// Redirect Threat
void SetRedirectThreat(ObjectGuid guid, uint32 pct) { _redirectThreadInfo.Set(guid, pct); }
void ResetRedirectThreat() { SetRedirectThreat(ObjectGuid::Empty, 0); }
void ModifyRedirectThreat(int32 amount) { _redirectThreadInfo.ModifyThreatPct(amount); }
uint32 GetRedirectThreatPercent() const { return _redirectThreadInfo.GetThreatPct(); }
Unit* GetRedirectThreatTarget();
friend class VehicleJoinEvent;
bool IsAIEnabled, NeedChangeAI;
ObjectGuid LastCharmerGUID;
@@ -1977,8 +1943,6 @@ class TC_GAME_API Unit : public WorldObject
uint32 m_reactiveTimer[MAX_REACTIVE];
uint32 m_regenTimer;
ThreatManager m_ThreatManager;
Vehicle* m_vehicle;
Vehicle* m_vehicleKit;
@@ -1994,6 +1958,9 @@ class TC_GAME_API Unit : public WorldObject
virtual void ProcessTerrainStatusUpdate(ZLiquidStatus status, Optional<LiquidData> const& liquidData);
virtual void SetInWater(bool inWater);
virtual void AtEnterCombat();
virtual void AtExitCombat();
private:
void UpdateSplineMovement(uint32 t_diff);
@@ -2016,17 +1983,17 @@ class TC_GAME_API Unit : public WorldObject
private:
uint32 m_state; // Even derived shouldn't modify
uint32 m_CombatTimer;
TimeTrackerSmall m_movesplineTimer;
Diminishing m_Diminishing;
// Manage all Units that are threatened by us
HostileRefManager m_HostileRefManager;
friend class CombatManager;
CombatManager m_combatManager;
friend class ThreatManager;
ThreatManager m_threatManager;
FollowerRefManager m_FollowingRefManager;
RedirectThreatInfo _redirectThreadInfo;
bool m_cleanupDone; // lock made to not add stuff after cleanup before delete
bool m_duringRemoveFromWorld; // lock made to not add stuff after begining removing from world
bool _instantCast;
+1 -1
View File
@@ -274,7 +274,7 @@ void ObjectGridStoper::Visit(CreatureMapType &m)
{
iter->GetSource()->RemoveAllDynObjects();
iter->GetSource()->RemoveAllAreaTriggers();
if (iter->GetSource()->IsInCombat() || !iter->GetSource()->GetThreatManager().areThreatListsEmpty())
if (iter->GetSource()->IsInCombat())
{
iter->GetSource()->CombatStop();
iter->GetSource()->GetThreatManager().ClearAllThreat();
-3
View File
@@ -134,7 +134,6 @@ void WorldSession::HandlePetStopAttack(WorldPackets::Pet::PetStopAttack& packet)
return;
pet->AttackStop();
pet->ClearInPetCombat();
}
void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spellid, uint16 flag, ObjectGuid guid2, Position const& pos)
@@ -168,7 +167,6 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe
case COMMAND_FOLLOW: //spellid=1792 //FOLLOW
pet->AttackStop();
pet->InterruptNonMeleeSpells(false);
pet->ClearInPetCombat();
pet->GetMotionMaster()->MoveFollow(_player, PET_FOLLOW_DIST, pet->GetFollowAngle());
charmInfo->SetCommandState(COMMAND_FOLLOW);
@@ -282,7 +280,6 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe
{
case REACT_PASSIVE: //passive
pet->AttackStop();
pet->ClearInPetCombat();
/* fallthrough */
case REACT_DEFENSIVE: //recovery
case REACT_AGGRESSIVE: //activete
+5 -16
View File
@@ -875,21 +875,10 @@ void Map::Update(uint32 t_diff)
// Handle updates for creatures in combat with player and are more than 60 yards away
if (player->IsInCombat())
{
std::vector<Creature*> updateList;
HostileReference* ref = player->getHostileRefManager().getFirst();
while (ref)
{
if (Unit* unit = ref->GetSource()->GetOwner())
if (unit->ToCreature() && unit->GetMapId() == player->GetMapId() && !unit->IsWithinDistInMap(player, GetVisibilityRange(), false))
updateList.push_back(unit->ToCreature());
ref = ref->next();
}
// Process deferred update list for player
for (Creature* c : updateList)
VisitNearbyCellsOf(c, grid_object_update, world_object_update);
for (auto const& pair : player->GetCombatManager().GetPvECombatRefs())
if (Creature* unit = pair.second->GetOther(player)->ToCreature())
if (unit->GetMapId() == player->GetMapId() && !unit->IsWithinDistInMap(player, GetVisibilityRange(), false))
VisitNearbyCellsOf(unit, grid_object_update, world_object_update);
}
}
@@ -1040,7 +1029,7 @@ void Map::RemovePlayerFromMap(Player* player, bool remove)
player->UpdateZone(MAP_INVALID_ZONE, 0);
sScriptMgr->OnPlayerLeaveMap(this, player);
player->getHostileRefManager().deleteReferences(); // multithreading crashfix
player->CombatStop();
bool const inWorld = player->IsInWorld();
player->RemoveFromWorld();
@@ -117,7 +117,6 @@ void FlightPathMovementGenerator::DoFinalize(Player* player)
if (player->m_taxi.empty())
{
player->getHostileRefManager().setOnlineOfflineState(true);
// update z position to ground and orientation for landing point
// this prevent cheating with landing point at lags
// when client side flight end early in comparison server side
@@ -131,8 +130,8 @@ void FlightPathMovementGenerator::DoFinalize(Player* player)
void FlightPathMovementGenerator::DoReset(Player* player)
{
player->getHostileRefManager().setOnlineOfflineState(false);
player->AddUnitState(UNIT_STATE_IN_FLIGHT);
player->CombatStopWithPets();
player->AddUnitFlag(UnitFlags(UNIT_FLAG_REMOVE_CLIENT_CONTROL | UNIT_FLAG_TAXI_FLIGHT));
Movement::MoveSplineInit init(player);
+1 -1
View File
@@ -508,7 +508,7 @@ void WorldSession::LogoutPlayer(bool save)
///- If the player just died before logging out, make him appear as a ghost
if (_player->GetDeathTimer())
{
_player->getHostileRefManager().deleteReferences();
_player->CombatStop();
_player->BuildPlayerRepop();
_player->RepopAtGraveyard();
}
@@ -288,7 +288,7 @@ NonDefaultConstructible<pAuraEffectHandler> AuraEffectHandler[TOTAL_AURAS]=
&AuraEffect::HandleNULL, //218 SPELL_AURA_ADD_PCT_MODIFIER_BY_SPELL_LABEL
&AuraEffect::HandleNULL, //219 SPELL_AURA_ADD_FLAT_MODIFIER_BY_SPELL_LABEL
&AuraEffect::HandleNULL, //220 SPELL_AURA_MOD_ABILITY_SCHOOL_MASK
&AuraEffect::HandleNULL, //221 SPELL_AURA_MOD_DETAUNT
&AuraEffect::HandleModDetaunt, //221 SPELL_AURA_MOD_DETAUNT
&AuraEffect::HandleNoImmediateEffect, //222 SPELL_AURA_REMOVE_TRANSMOG_COST implemented in WorldSession::HandleTransmogrifyItems
&AuraEffect::HandleNoImmediateEffect, //223 SPELL_AURA_REMOVE_BARBER_SHOP_COST implemented in Player::GetBarberShopCost
&AuraEffect::HandleNULL, //224 SPELL_AURA_LEARN_TALENT
@@ -1975,6 +1975,8 @@ void AuraEffect::HandleAuraTransform(AuraApplication const* aurApp, uint8 mode,
}
}
}
target->GetThreatManager().UpdateOnlineStates(true, false);
}
void AuraEffect::HandleAuraModScale(AuraApplication const* aurApp, uint8 mode, bool apply) const
@@ -2045,13 +2047,20 @@ void AuraEffect::HandleFeignDeath(AuraApplication const* aurApp, uint8 mode, boo
}
}
}
target->CombatStop();
if (target->GetMap()->IsDungeon()) // feign death does not remove combat in dungeons
{
target->AttackStop();
if (Player* targetPlayer = target->ToPlayer())
targetPlayer->SendAttackSwingCancelAttack();
}
else
target->CombatStop(false, false);
// prevent interrupt message
if (GetCasterGUID() == target->GetGUID() && target->GetCurrentSpell(CURRENT_GENERIC_SPELL))
target->FinishSpell(CURRENT_GENERIC_SPELL, false);
target->InterruptNonMeleeSpells(true);
target->getHostileRefManager().deleteReferences();
// stop handling the effect if it was removed by linked event
if (aurApp->GetRemoveMode())
@@ -2075,6 +2084,7 @@ void AuraEffect::HandleFeignDeath(AuraApplication const* aurApp, uint8 mode, boo
if (Creature* creature = target->ToCreature())
creature->InitializeReactState();
}
target->GetThreatManager().UpdateOnlineStates(true, false);
}
void AuraEffect::HandleModUnattackable(AuraApplication const* aurApp, uint8 mode, bool apply) const
@@ -2095,7 +2105,16 @@ void AuraEffect::HandleModUnattackable(AuraApplication const* aurApp, uint8 mode
// call functions which may have additional effects after chainging state of unit
if (apply && (mode & AURA_EFFECT_HANDLE_REAL))
target->CombatStop();
{
if (target->GetMap()->IsDungeon())
{
target->AttackStop();
if (Player* targetPlayer = target->ToPlayer())
targetPlayer->SendAttackSwingCancelAttack();
}
else
target->CombatStop();
}
}
void AuraEffect::HandleAuraModDisarm(AuraApplication const* aurApp, uint8 mode, bool apply) const
@@ -2655,28 +2674,15 @@ void AuraEffect::HandleIgnoreMovementForces(AuraApplication const* aurApp, uint8
/*** THREAT ***/
/****************************/
void AuraEffect::HandleModThreat(AuraApplication const* aurApp, uint8 mode, bool apply) const
void AuraEffect::HandleModThreat(AuraApplication const* aurApp, uint8 mode, bool /*apply*/) const
{
if (!(mode & AURA_EFFECT_HANDLE_CHANGE_AMOUNT_MASK))
return;
Unit* target = aurApp->GetTarget();
for (uint8 i = 0; i < MAX_SPELL_SCHOOL; ++i)
{
if (GetMiscValue() & (1 << i))
{
if (apply)
AddPct(target->m_threatModifier[i], GetAmount());
else
{
float amount = target->GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_THREAT, 1 << i);
target->m_threatModifier[i] = amount;
}
}
}
aurApp->GetTarget()->GetThreatManager().UpdateMySpellSchoolModifiers();
}
void AuraEffect::HandleAuraModTotalThreat(AuraApplication const* aurApp, uint8 mode, bool apply) const
void AuraEffect::HandleAuraModTotalThreat(AuraApplication const* aurApp, uint8 mode, bool /*apply*/) const
{
if (!(mode & AURA_EFFECT_HANDLE_CHANGE_AMOUNT_MASK))
return;
@@ -2688,10 +2694,10 @@ void AuraEffect::HandleAuraModTotalThreat(AuraApplication const* aurApp, uint8 m
Unit* caster = GetCaster();
if (caster && caster->IsAlive())
target->getHostileRefManager().addTempThreat((float)GetAmount(), apply);
caster->GetThreatManager().UpdateMyTempModifiers();
}
void AuraEffect::HandleModTaunt(AuraApplication const* aurApp, uint8 mode, bool apply) const
void AuraEffect::HandleModTaunt(AuraApplication const* aurApp, uint8 mode, bool /*apply*/) const
{
if (!(mode & AURA_EFFECT_HANDLE_REAL))
return;
@@ -2701,17 +2707,21 @@ void AuraEffect::HandleModTaunt(AuraApplication const* aurApp, uint8 mode, bool
if (!target->IsAlive() || !target->CanHaveThreatList())
return;
Unit* caster = GetCaster();
if (!caster || !caster->IsAlive())
target->GetThreatManager().TauntUpdate();
}
void AuraEffect::HandleModDetaunt(AuraApplication const* aurApp, uint8 mode, bool /*apply*/) const
{
if (!(mode & AURA_EFFECT_HANDLE_REAL))
return;
if (apply)
target->TauntApply(caster);
else
{
// When taunt aura fades out, mob will switch to previous target if current has less than 1.1 * secondthreat
target->TauntFadeOut(caster);
}
Unit* caster = GetCaster();
Unit* target = aurApp->GetTarget();
if (!caster || !caster->IsAlive() || !target->IsAlive() || !caster->CanHaveThreatList())
return;
caster->GetThreatManager().TauntUpdate();
}
/*****************************/
@@ -2726,6 +2736,7 @@ void AuraEffect::HandleModConfuse(AuraApplication const* aurApp, uint8 mode, boo
Unit* target = aurApp->GetTarget();
target->SetControlled(apply, UNIT_STATE_CONFUSED);
target->GetThreatManager().UpdateOnlineStates(true, false);
}
void AuraEffect::HandleModFear(AuraApplication const* aurApp, uint8 mode, bool apply) const
@@ -2736,6 +2747,7 @@ void AuraEffect::HandleModFear(AuraApplication const* aurApp, uint8 mode, bool a
Unit* target = aurApp->GetTarget();
target->SetControlled(apply, UNIT_STATE_FLEEING);
target->GetThreatManager().UpdateOnlineStates(true, false);
}
void AuraEffect::HandleAuraModStun(AuraApplication const* aurApp, uint8 mode, bool apply) const
@@ -2746,6 +2758,7 @@ void AuraEffect::HandleAuraModStun(AuraApplication const* aurApp, uint8 mode, bo
Unit* target = aurApp->GetTarget();
target->SetControlled(apply, UNIT_STATE_STUNNED);
target->GetThreatManager().UpdateOnlineStates(true, false);
}
void AuraEffect::HandleAuraModRoot(AuraApplication const* aurApp, uint8 mode, bool apply) const
@@ -2756,6 +2769,7 @@ void AuraEffect::HandleAuraModRoot(AuraApplication const* aurApp, uint8 mode, bo
Unit* target = aurApp->GetTarget();
target->SetControlled(apply, UNIT_STATE_ROOT);
target->GetThreatManager().UpdateOnlineStates(true, false);
}
void AuraEffect::HandlePreventFleeing(AuraApplication const* aurApp, uint8 mode, bool apply) const
@@ -3136,6 +3150,8 @@ void AuraEffect::HandleAuraModSchoolImmunity(AuraApplication const* aurApp, uint
&& GetSpellInfo()->HasAttribute(SPELL_ATTR2_DAMAGE_REDUCED_SHIELD))
target->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::StealthOrInvis);
}
target->GetThreatManager().UpdateOnlineStates(true, false);
}
void AuraEffect::HandleAuraModDmgImmunity(AuraApplication const* aurApp, uint8 mode, bool apply) const
@@ -3145,6 +3161,8 @@ void AuraEffect::HandleAuraModDmgImmunity(AuraApplication const* aurApp, uint8 m
Unit* target = aurApp->GetTarget();
m_spellInfo->ApplyAllSpellImmunitiesTo(target, GetSpellEffectInfo(), apply);
target->GetThreatManager().UpdateOnlineStates(true, false);
}
void AuraEffect::HandleAuraModDispelImmunity(AuraApplication const* aurApp, uint8 mode, bool apply) const
@@ -4771,7 +4789,7 @@ void AuraEffect::HandleForceReaction(AuraApplication const* aurApp, uint8 mode,
player->GetReputationMgr().ApplyForceReaction(factionId, factionRank, apply);
player->GetReputationMgr().SendForceReactions();
// stop fighting if at apply forced rank friendly or at remove real rank friendly
// stop fighting at apply (if forced rank friendly) or at remove (if real rank friendly)
if ((apply && factionRank >= REP_FRIENDLY) || (!apply && player->GetReputationRank(factionId) >= REP_FRIENDLY))
player->StopAttackFaction(factionId);
}
@@ -193,6 +193,7 @@ class TC_GAME_API AuraEffect
void HandleModThreat(AuraApplication const* aurApp, uint8 mode, bool apply) const;
void HandleAuraModTotalThreat(AuraApplication const* aurApp, uint8 mode, bool apply) const;
void HandleModTaunt(AuraApplication const* aurApp, uint8 mode, bool apply) const;
void HandleModDetaunt(AuraApplication const* aurApp, uint8 mode, bool apply) const;
// control
void HandleModConfuse(AuraApplication const* aurApp, uint8 mode, bool apply) const;
void HandleModFear(AuraApplication const* aurApp, uint8 mode, bool apply) const;
+8 -3
View File
@@ -2560,7 +2560,7 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target)
// spellHitTarget can be null if spell is missed in DoSpellHitOnUnit
if (missInfo != SPELL_MISS_EVADE && spellHitTarget && !m_caster->IsFriendlyTo(unit) && (!IsPositive() || m_spellInfo->HasEffect(SPELL_EFFECT_DISPEL)))
{
m_caster->CombatStart(unit, m_spellInfo->HasInitialAggro());
m_caster->AttackedTarget(unit, m_spellInfo->HasInitialAggro());
if (!unit->IsStandState())
unit->SetStandState(UNIT_STAND_STATE_STAND);
@@ -2655,7 +2655,8 @@ SpellMissInfo Spell::DoSpellHitOnUnit(Unit* unit, uint32 effectMask)
}
if (unit->IsInCombat() && m_spellInfo->HasInitialAggro())
{
m_caster->SetInCombatState(unit->GetCombatTimer() > 0, unit);
if (m_caster->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE)) // only do explicit combat forwarding for PvP enabled units
m_caster->GetCombatManager().InheritCombatStatesFrom(unit); // for creature v creature combat, the threat forward does it for us
unit->GetThreatManager().ForwardThreatForAssistingMe(m_caster, 0.0f, nullptr, true);
}
}
@@ -7459,6 +7460,10 @@ void Spell::DoAllEffectOnLaunchTarget(TargetInfo& targetInfo, float* multiplier)
if (!unit)
return;
// This will only cause combat - the target will engage once the projectile hits (in DoAllEffectOnTarget)
if (targetInfo.missCondition != SPELL_MISS_EVADE && !m_caster->IsFriendlyTo(unit) && (!m_spellInfo->IsPositive() || m_spellInfo->HasEffect(SPELL_EFFECT_DISPEL)) && (m_spellInfo->HasInitialAggro() || unit->IsEngaged()))
m_caster->SetInCombatWith(unit);
for (SpellEffectInfo const* effect : m_spellInfo->GetEffects())
{
if (effect && (targetInfo.effectMask & (1<<effect->EffectIndex)))
@@ -8043,7 +8048,7 @@ bool WorldObjectSpellTargetCheck::operator()(WorldObject* target)
return false;
break;
case TARGET_CHECK_THREAT:
if (_referer->GetThreatManager().getThreat(unitTarget, true) <= 0.0f)
if (_referer->GetThreatManager().GetThreat(unitTarget, true) <= 0.0f)
return false;
break;
case TARGET_CHECK_TAP:
+24 -30
View File
@@ -1090,7 +1090,7 @@ void Spell::EffectPowerDrain(SpellEffIndex effIndex)
int32 gain = int32(newDamage* gainMultiplier);
m_caster->EnergizeBySpell(m_caster, m_spellInfo->Id, gain, powerType);
m_caster->EnergizeBySpell(m_caster, m_spellInfo, gain, powerType);
}
ExecuteLogEffectTakeTargetPower(effIndex, unitTarget, powerType, newDamage, gainMultiplier);
}
@@ -1522,7 +1522,7 @@ void Spell::EffectEnergize(SpellEffIndex /*effIndex*/)
break;
}
m_caster->EnergizeBySpell(unitTarget, m_spellInfo->Id, damage, power);
m_caster->EnergizeBySpell(unitTarget, m_spellInfo, damage, power);
}
void Spell::EffectEnergizePct(SpellEffIndex /*effIndex*/)
@@ -1544,7 +1544,7 @@ void Spell::EffectEnergizePct(SpellEffIndex /*effIndex*/)
return;
uint32 gain = CalculatePct(maxPower, damage);
m_caster->EnergizeBySpell(unitTarget, m_spellInfo->Id, gain, power);
m_caster->EnergizeBySpell(unitTarget, m_spellInfo, gain, power);
}
void Spell::SendLoot(ObjectGuid guid, LootType loottype)
@@ -2700,27 +2700,22 @@ void Spell::EffectTaunt(SpellEffIndex /*effIndex*/)
// this effect use before aura Taunt apply for prevent taunt already attacking target
// for spell as marked "non effective at already attacking target"
if (!unitTarget || !unitTarget->CanHaveThreatList() || unitTarget->GetVictim() == m_caster)
if (!unitTarget || !unitTarget->CanHaveThreatList())
{
SendCastResult(SPELL_FAILED_DONT_REPORT);
return;
}
if (!unitTarget->GetThreatManager().getOnlineContainer().empty())
ThreatManager& mgr = unitTarget->GetThreatManager();
if (mgr.GetCurrentVictim() == m_caster)
{
// Also use this effect to set the taunter's threat to the taunted creature's highest value
float myThreat = unitTarget->GetThreatManager().getThreat(m_caster);
float topThreat = unitTarget->GetThreatManager().getOnlineContainer().getMostHated()->getThreat();
if (topThreat > myThreat)
unitTarget->GetThreatManager().doAddThreat(m_caster, topThreat - myThreat);
//Set aggro victim to caster
if (HostileReference* forcedVictim = unitTarget->GetThreatManager().getOnlineContainer().getReferenceByTarget(m_caster))
unitTarget->GetThreatManager().setCurrentVictim(forcedVictim);
SendCastResult(SPELL_FAILED_DONT_REPORT);
return;
}
if (unitTarget->ToCreature()->IsAIEnabled && !unitTarget->ToCreature()->HasReactState(REACT_PASSIVE))
unitTarget->ToCreature()->AI()->AttackStart(m_caster);
if (!mgr.IsThreatListEmpty())
// Set threat equal to highest threat currently on target
mgr.MatchUnitThreatToHighestThreat(m_caster);
}
void Spell::EffectWeaponDmg(SpellEffIndex effIndex)
@@ -2920,13 +2915,13 @@ void Spell::EffectThreat(SpellEffIndex /*effIndex*/)
if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET)
return;
if (!unitTarget || !unitTarget->IsAlive() || !m_caster->IsAlive())
if (!unitTarget || !m_caster->IsAlive())
return;
if (!unitTarget->CanHaveThreatList())
return;
unitTarget->GetThreatManager().AddThreat(m_caster, float(damage));
unitTarget->GetThreatManager().AddThreat(m_caster, float(damage), m_spellInfo, true);
}
void Spell::EffectHealMaxHealth(SpellEffIndex /*effIndex*/)
@@ -3456,20 +3451,19 @@ void Spell::EffectSanctuary(SpellEffIndex /*effIndex*/)
if (!unitTarget)
return;
if (unitTarget->GetTypeId() == TYPEID_PLAYER)
unitTarget->ToPlayer()->SendAttackSwingCancelAttack(); // melee and ranged forced attack cancel
unitTarget->getHostileRefManager().UpdateVisibility();
Unit::AttackerSet const& attackers = unitTarget->getAttackers();
for (Unit::AttackerSet::const_iterator itr = attackers.begin(); itr != attackers.end();)
if (unitTarget->GetTypeId() == TYPEID_PLAYER && !unitTarget->GetMap()->IsDungeon())
{
if (!(*itr)->CanSeeOrDetect(unitTarget))
(*(itr++))->AttackStop();
else
++itr;
// stop all pve combat for players outside dungeons, suppress pvp combat
unitTarget->CombatStop(false, false);
}
else
{
// in dungeons (or for nonplayers), reset this unit on all enemies' threat lists
for (auto const& pair : unitTarget->GetThreatManager().GetThreatenedByMeList())
pair.second->SetThreat(0.0f);
}
// makes spells cast before this time fizzle
unitTarget->m_lastSanctuaryTime = GameTime::GetGameTimeMS();
}
@@ -5015,7 +5009,7 @@ void Spell::EffectRedirectThreat(SpellEffIndex /*effIndex*/)
return;
if (unitTarget)
m_caster->SetRedirectThreat(unitTarget->GetGUID(), uint32(damage));
m_caster->GetThreatManager().RegisterRedirectThreat(m_spellInfo->Id, unitTarget->GetGUID(), uint32(damage));
}
void Spell::EffectGameObjectDamage(SpellEffIndex /*effIndex*/)
+2 -2
View File
@@ -2106,8 +2106,8 @@ SpellCastResult SpellInfo::CheckTarget(Unit const* caster, WorldObject const* ta
// creature/player specific target checks
if (unitTarget)
{
// spells cannot be cast if player is in fake combat also
if (HasAttribute(SPELL_ATTR1_CANT_TARGET_IN_COMBAT) && (unitTarget->IsInCombat() || unitTarget->IsPetInCombat()))
// spells cannot be cast if target has a pet in combat either
if (HasAttribute(SPELL_ATTR1_CANT_TARGET_IN_COMBAT) && (unitTarget->IsInCombat() || unitTarget->HasUnitFlag(UNIT_FLAG_PET_IN_COMBAT)))
return SPELL_FAILED_TARGET_AFFECTING_COMBAT;
// only spells with SPELL_ATTR3_ONLY_TARGET_GHOSTS can target ghosts
+75 -26
View File
@@ -846,23 +846,73 @@ public:
static bool HandleDebugThreatListCommand(ChatHandler* handler, char const* /*args*/)
{
Creature* target = handler->getSelectedCreature();
if (!target || target->IsTotem() || target->IsPet())
return false;
ThreatContainer::StorageType const& threatList = target->GetThreatManager().getThreatList();
ThreatContainer::StorageType::const_iterator itr;
uint32 count = 0;
handler->PSendSysMessage("Threat list of %s (%s)", target->GetName().c_str(), target->GetGUID().ToString().c_str());
for (itr = threatList.begin(); itr != threatList.end(); ++itr)
Unit* target = handler->getSelectedUnit();
if (!target)
target = handler->GetSession()->GetPlayer();
ThreatManager& mgr = target->GetThreatManager();
if (!target->IsAlive())
{
Unit* unit = (*itr)->getTarget();
if (!unit)
continue;
++count;
handler->PSendSysMessage(" %u. %s (%s) - threat %f", count, unit->GetName().c_str(), unit->GetGUID().ToString().c_str(), (*itr)->getThreat());
handler->PSendSysMessage("%s (%s) is not alive.", target->GetName().c_str(), target->GetGUID().ToString().c_str());
return true;
}
handler->SendSysMessage("End of threat list.");
if (!target->CanHaveThreatList())
handler->PSendSysMessage("%s (%s) cannot have a threat list.", target->GetName().c_str(), target->GetGUID().ToString().c_str());
uint32 count = 0;
auto const& threatenedByMe = target->GetThreatManager().GetThreatenedByMeList();
if (threatenedByMe.empty())
handler->PSendSysMessage("%s (%s) does not threaten any units.", target->GetName().c_str(), target->GetGUID().ToString().c_str());
else
{
handler->PSendSysMessage("List of units threatened by %s (%s)", target->GetName().c_str(), target->GetGUID().ToString().c_str());
for (auto const& pair : threatenedByMe)
{
Unit* unit = pair.second->GetOwner();
handler->PSendSysMessage(" %u. %s (%s, SpawnID %u) - threat %f", ++count, unit->GetName().c_str(), unit->GetGUID().ToString().c_str(), unit->GetTypeId() == TYPEID_UNIT ? unit->ToCreature()->GetSpawnId() : 0, pair.second->GetThreat());
}
handler->SendSysMessage("End of threatened-by-me list.");
}
if (!mgr.CanHaveThreatList())
return true;
if (mgr.IsEngaged())
{
count = 0;
handler->PSendSysMessage("Threat list of %s (%s, SpawnID %u)", target->GetName().c_str(), target->GetGUID().ToString().c_str(), target->GetTypeId() == TYPEID_UNIT ? target->ToCreature()->GetSpawnId() : 0);
for (ThreatReference const* ref : mgr.GetSortedThreatList())
{
Unit* unit = ref->GetVictim();
char const* onlineStr;
switch (ref->GetOnlineState())
{
case ThreatReference::ONLINE_STATE_SUPPRESSED:
onlineStr = " [SUPPRESSED]";
break;
case ThreatReference::ONLINE_STATE_OFFLINE:
onlineStr = " [OFFLINE]";
break;
default:
onlineStr = "";
}
char const* tauntStr;
switch (ref->GetTauntState())
{
case ThreatReference::TAUNT_STATE_TAUNT:
tauntStr = " [TAUNT]";
break;
case ThreatReference::TAUNT_STATE_DETAUNT:
tauntStr = " [DETAUNT]";
break;
default:
tauntStr = "";
}
handler->PSendSysMessage(" %u. %s (%s) - threat %f%s%s", ++count, unit->GetName().c_str(), unit->GetGUID().ToString().c_str(), ref->GetThreat(), tauntStr, onlineStr);
}
handler->SendSysMessage("End of threat list.");
}
else
handler->PSendSysMessage("%s (%s, SpawnID %u) is not currently engaged.", target->GetName().c_str(), target->GetGUID().ToString().c_str(), target->GetTypeId() == TYPEID_UNIT ? target->ToCreature()->GetSpawnId() : 0);
return true;
}
@@ -871,19 +921,18 @@ public:
Unit* target = handler->getSelectedUnit();
if (!target)
target = handler->GetSession()->GetPlayer();
HostileReference* ref = target->getHostileRefManager().getFirst();
uint32 count = 0;
handler->PSendSysMessage("Hostil reference list of %s (%s)", target->GetName().c_str(), target->GetGUID().ToString().c_str());
while (ref)
handler->PSendSysMessage("Combat refs: (Combat state: %d | Manager state: %d)", target->IsInCombat(), target->GetCombatManager().HasCombat());
for (auto const& ref : target->GetCombatManager().GetPvPCombatRefs())
{
if (Unit* unit = ref->GetSource()->GetOwner())
{
++count;
handler->PSendSysMessage(" %u. %s (%s, SpawnId: %s) - threat %f", count, unit->GetName().c_str(), unit->GetGUID().ToString().c_str(), unit->GetTypeId() == TYPEID_UNIT ? std::to_string(unit->ToCreature()->GetSpawnId()).c_str() : "0", ref->getThreat());
}
ref = ref->next();
Unit* unit = ref.second->GetOther(target);
handler->PSendSysMessage("[PvP] %s (SpawnID %u)", unit->GetName().c_str(), unit->GetTypeId() == TYPEID_UNIT ? unit->ToCreature()->GetSpawnId() : 0);
}
for (auto const& ref : target->GetCombatManager().GetPvECombatRefs())
{
Unit* unit = ref.second->GetOther(target);
handler->PSendSysMessage("[PvE] %s (SpawnID %u)", unit->GetName().c_str(), unit->GetTypeId() == TYPEID_UNIT ? unit->ToCreature()->GetSpawnId() : 0);
}
handler->SendSysMessage("End of hostil reference list.");
return true;
}
-1
View File
@@ -2491,7 +2491,6 @@ public:
return false;
target->CombatStop();
target->getHostileRefManager().deleteReferences();
return true;
}
@@ -212,7 +212,7 @@ class FocusedFireTargetSelector : public std::unary_function<Unit *, bool>
bool operator() (WorldObject* target)
{
if (target == _victim && _me->GetThreatManager().getThreatList().size() > 1)
if (target == _victim && _me->GetThreatManager().GetThreatListSize() > 1)
return true;
if (target->GetTypeId() != TYPEID_PLAYER)
@@ -198,30 +198,21 @@ public:
if (!summonedUnit)
return;
ThreatContainer::StorageType const& threatlist = me->GetThreatManager().getThreatList();
ThreatContainer::StorageType::const_iterator i = threatlist.begin();
for (i = threatlist.begin(); i != threatlist.end(); ++i)
for (auto* ref : me->GetThreatManager().GetUnsortedThreatList())
{
Unit* unit = ObjectAccessor::GetUnit(*me, (*i)->getUnitGuid());
Unit* unit = ref->GetVictim();
if (unit && unit->IsAlive())
{
float threat = me->GetThreatManager().getThreat(unit);
AddThreat(unit, threat, summonedUnit);
}
AddThreat(unit, ref->GetThreat(), summonedUnit);
}
}
void TeleportPlayersToSelf()
{
float x = KaelLocations[0][0];
float y = KaelLocations[0][1];
me->UpdatePosition(x, y, LOCATION_Z, 0.0f);
ThreatContainer::StorageType threatlist = me->GetThreatManager().getThreatList();
ThreatContainer::StorageType::const_iterator i = threatlist.begin();
for (i = threatlist.begin(); i != threatlist.end(); ++i)
me->UpdatePosition(KaelLocations[0][0], KaelLocations[0][1], LOCATION_Z, 0.0f);
for (auto const& pair : me->GetCombatManager().GetPvECombatRefs())
{
Unit* unit = ObjectAccessor::GetUnit(*me, (*i)->getUnitGuid());
if (unit && (unit->GetTypeId() == TYPEID_PLAYER))
Unit* unit = pair.second->GetOther(me);
if (unit->GetTypeId() == TYPEID_PLAYER)
unit->CastSpell(unit, SPELL_TELEPORT_CENTER, true);
}
DoCast(me, SPELL_TELEPORT_CENTER, true);
@@ -229,14 +220,11 @@ public:
void CastGravityLapseKnockUp()
{
ThreatContainer::StorageType threatlist = me->GetThreatManager().getThreatList();
ThreatContainer::StorageType::const_iterator i = threatlist.begin();
for (i = threatlist.begin(); i != threatlist.end(); ++i)
for (auto const& pair : me->GetCombatManager().GetPvECombatRefs())
{
Unit* unit = ObjectAccessor::GetUnit(*me, (*i)->getUnitGuid());
if (unit && (unit->GetTypeId() == TYPEID_PLAYER))
Unit* unit = pair.second->GetOther(me);
if (unit->GetTypeId() == TYPEID_PLAYER)
{
// Knockback into the air
CastSpellExtraArgs args;
args.TriggerFlags = TRIGGERED_FULL_MASK;
args.OriginalCaster = me->GetGUID();
@@ -247,12 +235,10 @@ public:
void CastGravityLapseFly() // Use Fly Packet hack for now as players can't cast "fly" spells unless in map 530. Has to be done a while after they get knocked into the air...
{
ThreatContainer::StorageType threatlist = me->GetThreatManager().getThreatList();
ThreatContainer::StorageType::const_iterator i = threatlist.begin();
for (i = threatlist.begin(); i != threatlist.end(); ++i)
for (auto const& pair : me->GetCombatManager().GetPvECombatRefs())
{
Unit* unit = ObjectAccessor::GetUnit(*me, (*i)->getUnitGuid());
if (unit && (unit->GetTypeId() == TYPEID_PLAYER))
Unit* unit = pair.second->GetOther(me);
if (unit->GetTypeId() == TYPEID_PLAYER)
{
// Also needs an exception in spell system.
CastSpellExtraArgs args;
@@ -266,12 +252,10 @@ public:
void RemoveGravityLapse()
{
ThreatContainer::StorageType threatlist = me->GetThreatManager().getThreatList();
ThreatContainer::StorageType::const_iterator i = threatlist.begin();
for (i = threatlist.begin(); i != threatlist.end(); ++i)
for (auto const& pair : me->GetCombatManager().GetPvECombatRefs())
{
Unit* unit = ObjectAccessor::GetUnit(*me, (*i)->getUnitGuid());
if (unit && (unit->GetTypeId() == TYPEID_PLAYER))
Unit* unit = pair.second->GetOther(me);
if (unit->GetTypeId() == TYPEID_PLAYER)
{
unit->RemoveAurasDueToSpell(SPELL_GRAVITY_LAPSE_FLY);
unit->RemoveAurasDueToSpell(SPELL_GRAVITY_LAPSE_DOT);
@@ -684,8 +668,8 @@ public:
{
if (Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 100, true))
{
AddThreat(target, 1.0f);
me->TauntApply(target);
ResetThreatList();
AddThreat(target, 1000000.0f);
AttackStart(target);
}
@@ -850,17 +850,12 @@ public:
if (Blink_Timer <= diff)
{
bool InMeleeRange = false;
ThreatContainer::StorageType const& t_list = me->GetThreatManager().getThreatList();
for (ThreatContainer::StorageType::const_iterator itr = t_list.begin(); itr!= t_list.end(); ++itr)
for (auto const& pair : me->GetCombatManager().GetPvECombatRefs())
{
if (Unit* target = ObjectAccessor::GetUnit(*me, (*itr)->getUnitGuid()))
if (pair.second->GetOther(me)->IsWithinMeleeRange(me))
{
//if in melee range
if (target->IsWithinDistInMap(me, 5))
{
InMeleeRange = true;
break;
}
InMeleeRange = true;
break;
}
}
@@ -944,17 +939,12 @@ public:
if (Intercept_Stun_Timer <= diff)
{
bool InMeleeRange = false;
ThreatContainer::StorageType const& t_list = me->GetThreatManager().getThreatList();
for (ThreatContainer::StorageType::const_iterator itr = t_list.begin(); itr!= t_list.end(); ++itr)
for (auto const& pair : me->GetCombatManager().GetPvECombatRefs())
{
if (Unit* target = ObjectAccessor::GetUnit(*me, (*itr)->getUnitGuid()))
if (pair.second->GetOther(me)->IsWithinMeleeRange(me))
{
//if in melee range
if (target->IsWithinDistInMap(me, ATTACK_DISTANCE))
{
InMeleeRange = true;
break;
}
InMeleeRange = true;
break;
}
}
@@ -115,7 +115,6 @@ struct boss_randolph_moloch : public BossAI
me->RemoveAllAuras();
DoCastSelf(SPELL_VANISH);
me->SetReactState(REACT_PASSIVE);
me->SetInCombatState(true); // Prevents the boss from resetting
events.ScheduleEvent(EVENT_JUST_VANISHED, 2s);
break;
case EVENT_JUST_VANISHED:
@@ -555,7 +555,7 @@ class DevastatingSlamTargetSelector : public std::unary_function<Unit *, bool>
bool operator() (WorldObject* target)
{
if (target == _victim && _me->GetThreatManager().getThreatList().size() > 1)
if (target == _victim && _me->GetThreatManager().GetThreatListSize() > 1)
return true;
if (target->GetTypeId() != TYPEID_PLAYER)
@@ -684,7 +684,7 @@ class spell_mandokir_ohgan_orders_trigger : public SpellScriptLoader
caster->GetMotionMaster()->Clear();
caster->GetThreatManager().ClearAllThreat();
caster->GetThreatManager().AddThreat(target, 50000000.0f);
caster->TauntApply(target);
// TODO: Fixate mechanic
}
}
@@ -635,7 +635,7 @@ class spell_alysrazor_aggro_closest : public SpellScriptLoader
void HandleEffect(SpellEffIndex effIndex)
{
PreventHitDefaultEffect(effIndex);
float curThreat = GetCaster()->GetThreatManager().getThreat(GetHitUnit(), true);
float curThreat = GetCaster()->GetThreatManager().GetThreat(GetHitUnit(), true);
GetCaster()->GetThreatManager().AddThreat(GetHitUnit(), -curThreat + 50000.0f / std::min(1.0f, GetCaster()->GetDistance(GetHitUnit())));
}
@@ -233,7 +233,7 @@ struct npc_firelands_magmakin : public ScriptedAI
return;
AddThreat(target, 50000000.0f);
me->TauntApply(target);
// TODO: Fixate mechanic
}
void UpdateAI(uint32 /*diff*/) override
@@ -267,13 +267,13 @@ class npc_rock_borer : public CreatureScript
{
me->SetDisableGravity(true);
me->SetReactState(REACT_PASSIVE);
events.ScheduleEvent(EVENT_EMERGED, 1200);
events.ScheduleEvent(EVENT_ROCK_BORE, urand(15000, 20000)); // Need sniffs for this timer
}
void IsSummonedBy(Unit* summoner) override
void IsSummonedBy(Unit* /*summoner*/) override
{
me->SetInCombatState(false, summoner);
events.ScheduleEvent(EVENT_EMERGED, 1200);
events.ScheduleEvent(EVENT_ROCK_BORE, urand(15000, 20000)); // Need sniffs for this timer
DoZoneInCombat();
DoCast(me, SPELL_ROCK_BORER_EMERGE);
}
@@ -41,6 +41,7 @@ enum Spells
SPELL_FLAME_SPHERE_DEATH_EFFECT = 55947,
SPELL_EMBRACE_OF_THE_VAMPYR = 55959,
SPELL_VANISH = 55964,
SPELL_SHADOWSTEP = 55966,
NPC_FLAME_SPHERE_1 = 30106,
NPC_FLAME_SPHERE_2 = 31686,
@@ -77,9 +78,8 @@ enum Events
EVENT_CONJURE_FLAME_SPHERES = 1,
EVENT_BLOODTHIRST,
EVENT_VANISH,
EVENT_JUST_VANISHED,
EVENT_VANISHED,
EVENT_FEEDING,
EVENT_START_FEEDING,
EVENT_DONE_FEEDING,
// Flame Sphere
EVENT_START_MOVE,
@@ -97,6 +97,7 @@ class boss_prince_taldaram : public CreatureScript
{
me->SetDisableGravity(true);
_embraceTakenDamage = 0;
_initialCheckTimer = 3000;
}
void Reset() override
@@ -140,8 +141,30 @@ class boss_prince_taldaram : public CreatureScript
void UpdateAI(uint32 diff) override
{
if (!UpdateVictim())
return;
if (_initialCheckTimer)
{
if (_initialCheckTimer <= diff)
{
CheckSpheres();
_initialCheckTimer = 0;
}
else
_initialCheckTimer -= diff;
}
if (me->HasAura(SPELL_VANISH))
{
if (me->GetThreatManager().IsThreatListEmpty(true))
{
EnterEvadeMode(EVADE_REASON_NO_HOSTILES);
return;
}
}
else
{
if (!UpdateVictim())
return;
}
events.Update(diff);
@@ -167,47 +190,29 @@ class boss_prince_taldaram : public CreatureScript
break;
case EVENT_VANISH:
{
Map::PlayerList const& players = me->GetMap()->GetPlayers();
uint32 targets = 0;
for (Map::PlayerList::const_iterator i = players.begin(); i != players.end(); ++i)
if (me->GetThreatManager().GetThreatListSize() > 1)
{
Player* player = i->GetSource();
if (player && player->IsAlive())
++targets;
}
if (targets > 2)
{
Talk(SAY_VANISH);
DoCast(me, SPELL_VANISH);
me->SetInCombatState(true); // Prevents the boss from resetting
events.DelayEvents(500);
events.ScheduleEvent(EVENT_JUST_VANISHED, 500);
if (Unit* embraceTarget = SelectTarget(SELECT_TARGET_RANDOM, 0, 100.0f, true))
_embraceTargetGUID = embraceTarget->GetGUID();
Talk(SAY_VANISH);
DoCast(me, SPELL_VANISH);
events.DelayEvents(500);
events.ScheduleEvent(EVENT_START_FEEDING, 2000);
}
events.ScheduleEvent(EVENT_VANISH, urand(25000, 35000));
break;
}
case EVENT_JUST_VANISHED:
case EVENT_START_FEEDING:
me->RemoveAurasDueToSpell(SPELL_VANISH);
if (Unit* embraceTarget = GetEmbraceTarget())
{
me->GetMotionMaster()->Clear();
me->SetSpeedRate(MOVE_WALK, 2.0f);
me->GetMotionMaster()->MoveChase(embraceTarget);
}
events.ScheduleEvent(EVENT_VANISHED, 1300);
break;
case EVENT_VANISHED:
if (Unit* embraceTarget = GetEmbraceTarget())
DoCast(embraceTarget, SPELL_SHADOWSTEP);
DoCast(embraceTarget, SPELL_EMBRACE_OF_THE_VAMPYR);
Talk(SAY_FEED);
me->GetMotionMaster()->Clear();
me->SetSpeedRate(MOVE_WALK, 1.0f);
me->GetMotionMaster()->MoveChase(me->GetVictim());
events.ScheduleEvent(EVENT_FEEDING, 20000);
Talk(SAY_FEED);
events.ScheduleEvent(EVENT_DONE_FEEDING, 20000);
}
break;
case EVENT_FEEDING:
case EVENT_DONE_FEEDING:
_embraceTargetGUID.Clear();
break;
default:
@@ -289,6 +294,7 @@ class boss_prince_taldaram : public CreatureScript
ObjectGuid _flameSphereTargetGUID;
ObjectGuid _embraceTargetGUID;
uint32 _embraceTakenDamage;
uint32 _initialCheckTimer;
};
CreatureAI* GetAI(Creature* creature) const override
@@ -163,13 +163,9 @@ public:
bool IsInCombatWithPlayer() const
{
std::list<HostileReference*> const& refs = me->GetThreatManager().getThreatList();
for (HostileReference const* hostileRef : refs)
{
if (Unit const* target = hostileRef->getTarget())
if (target->IsControlledByPlayer())
return true;
}
for (auto const& pair : me->GetCombatManager().GetPvECombatRefs())
if (pair.second->GetOther(me)->IsControlledByPlayer())
return true;
return false;
}
@@ -820,7 +820,7 @@ class npc_anubarak_spike : public CreatureScript
DoCast(who, SPELL_MARK);
me->SetSpeedRate(MOVE_RUN, 0.5f);
// make sure the Spine will really follow the one he should
ResetThreatList();
me->GetThreatManager().ResetAllThreat();
me->SetInCombatWithZone();
AddThreat(who, 1000000.0f);
me->GetMotionMaster()->Clear(true);
@@ -672,14 +672,10 @@ struct boss_faction_championsAI : public BossAI
Unit* SelectEnemyCaster(bool /*casting*/)
{
std::list<HostileReference*> const& tList = me->GetThreatManager().getThreatList();
std::list<HostileReference*>::const_iterator iter;
for (iter = tList.begin(); iter!=tList.end(); ++iter)
{
Unit* target = ObjectAccessor::GetUnit(*me, (*iter)->getUnitGuid());
if (target && target->GetPowerType() == POWER_MANA)
return target;
}
for (auto const& pair : me->GetCombatManager().GetPvECombatRefs())
if (Player* player = pair.second->GetOther(me)->ToPlayer())
if (player->GetPowerType() == POWER_MANA)
return player;
return nullptr;
}
@@ -1342,7 +1342,7 @@ class npc_the_lich_king_escape_hor : public CreatureScript
AttackStart(victim);
return me->GetVictim() != nullptr;
}
else if (me->GetThreatManager().GetThreatListSize() < 2 && me->HasAura(SPELL_REMORSELESS_WINTER))
else if (me->GetCombatManager().GetPvECombatRefs().size() < 2 && me->HasAura(SPELL_REMORSELESS_WINTER))
{
EnterEvadeMode(EVADE_REASON_OTHER);
return false;
@@ -148,15 +148,12 @@ class boss_drakkari_colossus : public CreatureScript
if (me->GetReactState() == REACT_AGGRESSIVE)
return;
me->SetReactState(REACT_AGGRESSIVE);
me->SetImmuneToPC(false);
me->SetReactState(REACT_AGGRESSIVE);
me->RemoveAura(SPELL_FREEZE_ANIM);
me->SetInCombatWithZone();
if (me->GetVictim())
me->GetMotionMaster()->MoveChase(me->GetVictim(), 0, 0);
break;
}
}
@@ -559,25 +559,15 @@ class npc_green_dragon_combat_trigger : public CreatureScript
if (!me->IsInCombat())
return;
// @TODO check out of bounds on all encounter creatures, evade if matched
std::list<HostileReference*> const& threatList = me->GetThreatManager().getThreatList();
if (threatList.empty())
{
EnterEvadeMode();
return;
}
// check evade every second tick
_evadeCheck ^= true;
if (!_evadeCheck)
return;
// check if there is any player on threatlist, if not - evade
for (std::list<HostileReference*>::const_iterator itr = threatList.begin(); itr != threatList.end(); ++itr)
if (Unit* target = (*itr)->getTarget())
if (target->GetTypeId() == TYPEID_PLAYER)
return; // found any player, return
// check if there is any player engaged, if not - evade
for (auto const& pair : me->GetCombatManager().GetPvECombatRefs())
if (pair.second->GetOther(me)->GetTypeId() == TYPEID_PLAYER)
return;
EnterEvadeMode();
}
@@ -489,12 +489,11 @@ class boss_hodir : public CreatureScript
if (gettingColdInHereTimer <= diff && gettingColdInHere)
{
std::list<HostileReference*> ThreatList = me->GetThreatManager().getThreatList();
for (std::list<HostileReference*>::const_iterator itr = ThreatList.begin(); itr != ThreatList.end(); ++itr)
if (Unit* target = ObjectAccessor::GetUnit(*me, (*itr)->getUnitGuid()))
for (auto const& pair : me->GetCombatManager().GetPvECombatRefs())
if (Player* target = pair.second->GetOther(me)->ToPlayer())
if (Aura* BitingColdAura = target->GetAura(SPELL_BITING_COLD_TRIGGERED))
if ((target->GetTypeId() == TYPEID_PLAYER) && (BitingColdAura->GetStackAmount() > 2))
SetData(DATA_GETTING_COLD_IN_HERE, 0);
if (BitingColdAura->GetStackAmount() > 2)
SetData(DATA_GETTING_COLD_IN_HERE, 0);
gettingColdInHereTimer = 1000;
}
else
@@ -198,7 +198,7 @@ class boss_ingvar_the_plunderer : public CreatureScript
void UpdateAI(uint32 diff) override
{
if (!events.IsInPhase(PHASE_EVENT) && !UpdateVictim())
if (!UpdateVictim())
return;
events.Update(diff);
@@ -231,9 +231,7 @@ class boss_ingvar_the_plunderer : public CreatureScript
ScheduleSecondPhase();
me->RemoveUnitFlag(UnitFlags(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE));
me->SetImmuneToPC(false);
if (Unit* target = me->GetThreatManager().SelectVictim())
AttackStart(target);
else
if (!me->IsThreatened())
{
EnterEvadeMode(EVADE_REASON_NO_HOSTILES);
return;
@@ -184,7 +184,7 @@ class spell_toravon_random_aggro : public SpellScript
if (!caster->IsAIEnabled)
return;
caster->GetThreatManager().resetAllAggro();
caster->GetThreatManager().ResetAllThreat();
if (Unit* target = caster->AI()->SelectTarget(SELECT_TARGET_RANDOM, 1))
caster->GetThreatManager().AddThreat(target, 1000000);
+3 -8
View File
@@ -38,6 +38,7 @@ enum HunterSpells
SPELL_HUNTER_EXHILARATION_R2 = 231546,
SPELL_HUNTER_LONE_WOLF = 155228,
SPELL_HUNTER_MASTERS_CALL_TRIGGERED = 62305,
SPELL_HUNTER_MISDIRECTION = 34477,
SPELL_HUNTER_MISDIRECTION_PROC = 35079,
SPELL_HUNTER_MULTI_SHOT_FOCUS = 213363,
SPELL_HUNTER_PET_LAST_STAND_TRIGGERED = 53479,
@@ -297,12 +298,7 @@ class spell_hun_misdirection : public SpellScriptLoader
return;
if (!GetTarget()->HasAura(SPELL_HUNTER_MISDIRECTION_PROC))
GetTarget()->ResetRedirectThreat();
}
bool CheckProc(ProcEventInfo& /*eventInfo*/)
{
return GetTarget()->GetRedirectThreatTarget() != nullptr;
GetTarget()->GetThreatManager().UnregisterRedirectThreat(SPELL_HUNTER_MISDIRECTION);
}
void HandleProc(AuraEffect* aurEff, ProcEventInfo& /*eventInfo*/)
@@ -314,7 +310,6 @@ class spell_hun_misdirection : public SpellScriptLoader
void Register() override
{
AfterEffectRemove += AuraEffectRemoveFn(spell_hun_misdirection_AuraScript::OnRemove, EFFECT_1, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
DoCheckProc += AuraCheckProcFn(spell_hun_misdirection_AuraScript::CheckProc);
OnEffectProc += AuraEffectProcFn(spell_hun_misdirection_AuraScript::HandleProc, EFFECT_1, SPELL_AURA_DUMMY);
}
};
@@ -337,7 +332,7 @@ class spell_hun_misdirection_proc : public SpellScriptLoader
void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
{
GetTarget()->ResetRedirectThreat();
GetTarget()->GetThreatManager().UnregisterRedirectThreat(SPELL_HUNTER_MISDIRECTION);
}
void Register() override
+63 -66
View File
@@ -51,6 +51,7 @@ enum RogueSpells
SPELL_ROGUE_STEALTH_STEALTH_AURA = 158185,
SPELL_ROGUE_STEALTH_SHAPESHIFT_AURA = 158188,
SPELL_ROGUE_VANISH_AURA = 11327,
SPELL_ROGUE_TRICKS_OF_THE_TRADE = 57934,
SPELL_ROGUE_TRICKS_OF_THE_TRADE_PROC = 59628,
SPELL_ROGUE_HONOR_AMONG_THIEVES_ENERGIZE = 51699,
SPELL_ROGUE_T5_2P_SET_BONUS = 37169
@@ -507,82 +508,78 @@ class spell_rog_vanish_aura : public SpellScriptLoader
};
// 57934 - Tricks of the Trade
class spell_rog_tricks_of_the_trade : public SpellScriptLoader
class spell_rog_tricks_of_the_trade_aura : public AuraScript
{
public:
spell_rog_tricks_of_the_trade() : SpellScriptLoader("spell_rog_tricks_of_the_trade") { }
PrepareAuraScript(spell_rog_tricks_of_the_trade_aura);
class spell_rog_tricks_of_the_trade_AuraScript : public AuraScript
{
PrepareAuraScript(spell_rog_tricks_of_the_trade_AuraScript);
bool Validate(SpellInfo const* /*spellInfo*/) override
{
return ValidateSpellInfo({ SPELL_ROGUE_TRICKS_OF_THE_TRADE_PROC });
}
bool Validate(SpellInfo const* /*spellInfo*/) override
void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
{
if (GetTargetApplication()->GetRemoveMode() != AURA_REMOVE_BY_DEFAULT || !GetTarget()->HasAura(SPELL_ROGUE_TRICKS_OF_THE_TRADE_PROC))
GetTarget()->GetThreatManager().UnregisterRedirectThreat(SPELL_ROGUE_TRICKS_OF_THE_TRADE);
}
void HandleProc(AuraEffect* aurEff, ProcEventInfo& /*eventInfo*/)
{
PreventDefaultAction();
Unit* rogue = GetTarget();
if (ObjectAccessor::GetUnit(*rogue, _redirectTarget))
rogue->CastSpell(rogue, SPELL_ROGUE_TRICKS_OF_THE_TRADE_PROC, aurEff);
Remove(AURA_REMOVE_BY_DEFAULT);
}
void Register() override
{
AfterEffectRemove += AuraEffectRemoveFn(spell_rog_tricks_of_the_trade_aura::OnRemove, EFFECT_1, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
OnEffectProc += AuraEffectProcFn(spell_rog_tricks_of_the_trade_aura::HandleProc, EFFECT_1, SPELL_AURA_DUMMY);
}
ObjectGuid _redirectTarget;
public:
void SetRedirectTarget(ObjectGuid guid) { _redirectTarget = guid; }
};
class spell_rog_tricks_of_the_trade : public SpellScript
{
PrepareSpellScript(spell_rog_tricks_of_the_trade);
void DoAfterHit()
{
if (Aura* aura = GetHitAura())
if (auto* script = aura->GetScript<spell_rog_tricks_of_the_trade_aura>("spell_rog_tricks_of_the_trade"))
{
return ValidateSpellInfo({ SPELL_ROGUE_TRICKS_OF_THE_TRADE_PROC });
if (Unit* explTarget = GetExplTargetUnit())
script->SetRedirectTarget(explTarget->GetGUID());
else
script->SetRedirectTarget(ObjectGuid::Empty);
}
}
void OnRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
{
if (GetTargetApplication()->GetRemoveMode() != AURA_REMOVE_BY_DEFAULT)
GetTarget()->ResetRedirectThreat();
}
bool CheckProc(ProcEventInfo& /*eventInfo*/)
{
_redirectTarget = GetTarget()->GetRedirectThreatTarget();
return _redirectTarget != nullptr;
}
void HandleProc(AuraEffect* /*aurEff*/, ProcEventInfo& /*eventInfo*/)
{
PreventDefaultAction();
Unit* target = GetTarget();
target->CastSpell(target, SPELL_ROGUE_TRICKS_OF_THE_TRADE_PROC, true);
Remove(AURA_REMOVE_BY_DEFAULT); // maybe handle by proc charges
}
void Register() override
{
AfterEffectRemove += AuraEffectRemoveFn(spell_rog_tricks_of_the_trade_AuraScript::OnRemove, EFFECT_1, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
DoCheckProc += AuraCheckProcFn(spell_rog_tricks_of_the_trade_AuraScript::CheckProc);
OnEffectProc += AuraEffectProcFn(spell_rog_tricks_of_the_trade_AuraScript::HandleProc, EFFECT_1, SPELL_AURA_DUMMY);
}
Unit* _redirectTarget = nullptr;
};
AuraScript* GetAuraScript() const override
{
return new spell_rog_tricks_of_the_trade_AuraScript();
}
void Register() override
{
AfterHit += SpellHitFn(spell_rog_tricks_of_the_trade::DoAfterHit);
}
};
// 59628 - Tricks of the Trade (Proc)
class spell_rog_tricks_of_the_trade_proc : public SpellScriptLoader
class spell_rog_tricks_of_the_trade_proc : public AuraScript
{
public:
spell_rog_tricks_of_the_trade_proc() : SpellScriptLoader("spell_rog_tricks_of_the_trade_proc") { }
PrepareAuraScript(spell_rog_tricks_of_the_trade_proc);
class spell_rog_tricks_of_the_trade_proc_AuraScript : public AuraScript
{
PrepareAuraScript(spell_rog_tricks_of_the_trade_proc_AuraScript);
void HandleRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
{
GetTarget()->GetThreatManager().UnregisterRedirectThreat(SPELL_ROGUE_TRICKS_OF_THE_TRADE);
}
void HandleRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
{
GetTarget()->ResetRedirectThreat();
}
void Register() override
{
AfterEffectRemove += AuraEffectRemoveFn(spell_rog_tricks_of_the_trade_proc_AuraScript::HandleRemove, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
}
};
AuraScript* GetAuraScript() const override
{
return new spell_rog_tricks_of_the_trade_proc_AuraScript();
}
void Register() override
{
AfterEffectRemove += AuraEffectRemoveFn(spell_rog_tricks_of_the_trade_proc::HandleRemove, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
}
};
// 198031 - Honor Among Thieves
@@ -704,8 +701,8 @@ void AddSC_rogue_spell_scripts()
new spell_rog_stealth();
new spell_rog_vanish();
new spell_rog_vanish_aura();
new spell_rog_tricks_of_the_trade();
new spell_rog_tricks_of_the_trade_proc();
RegisterSpellAndAuraScriptPair(spell_rog_tricks_of_the_trade, spell_rog_tricks_of_the_trade_aura);
RegisterAuraScript(spell_rog_tricks_of_the_trade_proc);
new spell_rog_honor_among_thieves();
new spell_rog_eviscerate();
new spell_rog_envenom();
+4 -4
View File
@@ -51,7 +51,7 @@ enum WarlockSpells
SPELL_WARLOCK_RAIN_OF_FIRE_DAMAGE = 42223,
SPELL_WARLOCK_SEED_OF_CORRUPTION_DAMAGE = 27285,
SPELL_WARLOCK_SEED_OF_CORRUPTION_GENERIC = 32865,
SPELL_WARLOCK_SOULSHATTER = 32835,
SPELL_WARLOCK_SOULSHATTER_EFFECT = 32835,
SPELL_WARLOCK_SOUL_SWAP_CD_MARKER = 94229,
SPELL_WARLOCK_SOUL_SWAP_OVERRIDE = 86211,
SPELL_WARLOCK_SOUL_SWAP_MOD_COST = 92794,
@@ -815,15 +815,15 @@ class spell_warl_soulshatter : public SpellScriptLoader
bool Validate(SpellInfo const* /*spellInfo*/) override
{
return ValidateSpellInfo({ SPELL_WARLOCK_SOULSHATTER });
return ValidateSpellInfo({ SPELL_WARLOCK_SOULSHATTER_EFFECT });
}
void HandleDummy(SpellEffIndex /*effIndex*/)
{
Unit* caster = GetCaster();
if (Unit* target = GetHitUnit())
if (target->CanHaveThreatList() && target->GetThreatManager().IsThreatenedBy(caster, true))
caster->CastSpell(target, SPELL_WARLOCK_SOULSHATTER, true);
if (target->GetThreatManager().IsThreatenedBy(caster, true))
caster->CastSpell(target, SPELL_WARLOCK_SOULSHATTER_EFFECT, true);
}
void Register() override
+12 -5
View File
@@ -1806,21 +1806,28 @@ public:
{
case EVENT_TD_CHECK_COMBAT:
{
time_t now = GameTime::GetGameTime();
for (std::unordered_map<ObjectGuid, time_t>::iterator itr = _damageTimes.begin(); itr != _damageTimes.end();)
time_t const now = GameTime::GetGameTime();
auto const& pveRefs = me->GetCombatManager().GetPvECombatRefs();
for (auto itr = _damageTimes.begin(); itr != _damageTimes.end();)
{
// If unit has not dealt damage to training dummy for 5 seconds, remove him from combat
if (itr->second < now - 5)
{
if (Unit* unit = ObjectAccessor::GetUnit(*me, itr->first))
unit->getHostileRefManager().deleteReference(me);
auto it = pveRefs.find(itr->first);
if (it != pveRefs.end())
it->second->EndCombat();
itr = _damageTimes.erase(itr);
}
else
++itr;
}
_events.ScheduleEvent(EVENT_TD_CHECK_COMBAT, 1000);
for (auto const& pair : pveRefs)
if (_damageTimes.find(pair.first) == _damageTimes.end())
_damageTimes[pair.first] = now;
_events.Repeat(1s);
break;
}
case EVENT_TD_DESPAWN: