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:
@@ -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());
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#define TRINITY_UNITAI_H
|
||||
|
||||
#include "Containers.h"
|
||||
#include "Errors.h"
|
||||
#include "EventMap.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include "SpellDefines.h"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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*/)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
+4
-8
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user