diff options
| author | Treeston <treeston.mmoc@gmail.com> | 2017-07-01 20:18:02 +0200 |
|---|---|---|
| committer | Shauren <shauren.trinity@gmail.com> | 2017-07-01 20:18:02 +0200 |
| commit | e2a1ccd118d129b96e09ff1a15ed0adb1d4a3897 (patch) | |
| tree | bbe6600c4066078bb7c64a117df457dce0d00b26 /src/server/game | |
| parent | 5879eb2198fdb976b9fff136757bf8187adb6cf0 (diff) | |
[3.3.5] Combat/Threat rewrite - prep & refactor (#19966)
* Combat/Threat rewrite (PR #19930) prep work. Mostly refactors, and a compatibility layer on ThreatManager/HostileReference that allows scripts to be changed already.
Diffstat (limited to 'src/server/game')
28 files changed, 460 insertions, 391 deletions
diff --git a/src/server/game/AI/CoreAI/GuardAI.cpp b/src/server/game/AI/CoreAI/GuardAI.cpp index f216059e492..7f65af41d63 100644 --- a/src/server/game/AI/CoreAI/GuardAI.cpp +++ b/src/server/game/AI/CoreAI/GuardAI.cpp @@ -45,14 +45,9 @@ void GuardAI::UpdateAI(uint32 /*diff*/) bool GuardAI::CanSeeAlways(WorldObject const* obj) { - if (!obj->isType(TYPEMASK_UNIT)) - return false; - - ThreatContainer::StorageType threatList = me->getThreatManager().getThreatList(); - for (ThreatContainer::StorageType::const_iterator itr = threatList.begin(); itr != threatList.end(); ++itr) - if ((*itr)->getUnitGuid() == obj->GetGUID()) + if (Unit const* unit = obj->ToUnit()) + if (unit->IsControlledByPlayer() && me->IsEngagedBy(unit)) return true; - return false; } @@ -62,14 +57,14 @@ void GuardAI::EnterEvadeMode(EvadeReason /*why*/) { me->GetMotionMaster()->MoveIdle(); me->CombatStop(true); - me->DeleteThreatList(); + me->GetThreatManager().ClearAllThreat(); return; } TC_LOG_DEBUG("entities.unit", "Guard entry: %u enters evade mode.", me->GetEntry()); me->RemoveAllAuras(); - me->DeleteThreatList(); + me->GetThreatManager().ClearAllThreat(); me->CombatStop(true); // Remove ChaseMovementGenerator from MotionMaster stack list, and add HomeMovementGenerator instead diff --git a/src/server/game/AI/CoreAI/UnitAI.cpp b/src/server/game/AI/CoreAI/UnitAI.cpp index 9f13f7901bc..50a94caa1fa 100644 --- a/src/server/game/AI/CoreAI/UnitAI.cpp +++ b/src/server/game/AI/CoreAI/UnitAI.cpp @@ -103,14 +103,14 @@ bool UnitAI::DoSpellAttackIfReady(uint32 spell) return false; } -Unit* UnitAI::SelectTarget(SelectAggroTarget targetType, uint32 position, float dist, bool playerOnly, int32 aura) +Unit* UnitAI::SelectTarget(SelectAggroTarget targetType, uint32 position, float dist, bool playerOnly, bool withTank, int32 aura) { - return SelectTarget(targetType, position, DefaultTargetSelector(me, dist, playerOnly, aura)); + return SelectTarget(targetType, position, DefaultTargetSelector(me, dist, playerOnly, withTank, aura)); } -void UnitAI::SelectTargetList(std::list<Unit*>& targetList, uint32 num, SelectAggroTarget targetType, float dist, bool playerOnly, int32 aura) +void UnitAI::SelectTargetList(std::list<Unit*>& targetList, uint32 num, SelectAggroTarget targetType, uint32 offset, float dist, bool playerOnly, bool withTank, int32 aura) { - SelectTargetList(targetList, DefaultTargetSelector(me, dist, playerOnly, aura), num, targetType); + SelectTargetList(targetList, num, targetType, offset, DefaultTargetSelector(me, dist, playerOnly, withTank, aura)); } float UnitAI::DoGetSpellMaxRange(uint32 spellId, bool positive) @@ -154,7 +154,7 @@ void UnitAI::DoCast(uint32 spellId) bool playerOnly = spellInfo->HasAttribute(SPELL_ATTR3_ONLY_TARGET_PLAYERS); float range = spellInfo->GetMaxRange(false); - DefaultTargetSelector targetSelector(me, range, playerOnly, -(int32)spellId); + DefaultTargetSelector targetSelector(me, range, playerOnly, true, -(int32)spellId); if (!(spellInfo->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_VICTIM) && targetSelector(me->GetVictim())) target = me->GetVictim(); @@ -251,7 +251,12 @@ void UnitAI::FillAISpellInfo() ThreatManager& UnitAI::GetThreatManager() { - return me->getThreatManager(); + return me->GetThreatManager(); +} + +void UnitAI::SortByDistance(std::list<Unit*> list, bool ascending) +{ + list.sort(Trinity::ObjectDistanceOrderPred(me, ascending)); } bool DefaultTargetSelector::operator()(Unit const* target) const @@ -262,6 +267,9 @@ bool DefaultTargetSelector::operator()(Unit const* target) const if (!target) return false; + if (target == except) + return false; + if (m_playerOnly && (target->GetTypeId() != TYPEID_PLAYER)) return false; @@ -363,8 +371,8 @@ bool NonTankTargetSelector::operator()(Unit const* target) const if (_playerOnly && target->GetTypeId() != TYPEID_PLAYER) return false; - if (HostileReference* currentVictim = _source->getThreatManager().getCurrentVictim()) - return target->GetGUID() != currentVictim->getUnitGuid(); + if (Unit* currentVictim = _source->GetThreatManager().GetCurrentVictim()) + return target != currentVictim; return target != _source->GetVictim(); } @@ -405,8 +413,3 @@ bool FarthestTargetSelector::operator()(Unit const* target) const return true; } - -void SortByDistanceTo(Unit* reference, std::list<Unit*>& targets) -{ - targets.sort(Trinity::ObjectDistanceOrderPred(reference)); -} diff --git a/src/server/game/AI/CoreAI/UnitAI.h b/src/server/game/AI/CoreAI/UnitAI.h index d164b9fe6d5..27542ebd3d1 100644 --- a/src/server/game/AI/CoreAI/UnitAI.h +++ b/src/server/game/AI/CoreAI/UnitAI.h @@ -46,11 +46,11 @@ enum SpellEffIndex : uint8; //Selection method used by SelectTarget enum SelectAggroTarget { - SELECT_TARGET_RANDOM = 0, //Just selects a random target - SELECT_TARGET_TOPAGGRO, //Selects targes from top aggro to bottom - SELECT_TARGET_BOTTOMAGGRO, //Selects targets from bottom aggro to top - SELECT_TARGET_NEAREST, - SELECT_TARGET_FARTHEST + SELECT_TARGET_RANDOM = 0, // just pick a random target + SELECT_TARGET_MAXTHREAT, // prefer targets higher in the threat list + SELECT_TARGET_MINTHREAT, // prefer targets lower in the threat list + SELECT_TARGET_MAXDISTANCE, // prefer targets further from us + SELECT_TARGET_MINDISTANCE // prefer targets closer to us }; // default predicate function to select target based on distance, player and/or aura criteria @@ -59,13 +59,15 @@ struct TC_GAME_API DefaultTargetSelector Unit const* me; float m_dist; bool m_playerOnly; + Unit const* except; int32 m_aura; // unit: the reference unit // dist: if 0: ignored, if > 0: maximum distance to the reference unit, if < 0: minimum distance to the reference unit // playerOnly: self explaining + // withMainTank: allow current tank to be selected // aura: if 0: ignored, if > 0: the target shall have the aura, if < 0, the target shall NOT have the aura - DefaultTargetSelector(Unit const* unit, float dist, bool playerOnly, int32 aura) : me(unit), m_dist(dist), m_playerOnly(playerOnly), m_aura(aura) { } + DefaultTargetSelector(Unit const* unit, float dist, bool playerOnly, bool withMainTank, int32 aura) : me(unit), m_dist(dist), m_playerOnly(playerOnly), except(withMainTank ? me->GetThreatManager().GetCurrentVictim() : nullptr), m_aura(aura) { } bool operator()(Unit const* target) const; }; @@ -124,8 +126,6 @@ struct TC_GAME_API FarthestTargetSelector bool _inLos; }; -TC_GAME_API void SortByDistanceTo(Unit* reference, std::list<Unit*>& targets); - class TC_GAME_API UnitAI { protected: @@ -152,94 +152,174 @@ class TC_GAME_API UnitAI virtual void SetGUID(ObjectGuid /*guid*/, int32 /*id*/ = 0) { } virtual ObjectGuid GetGUID(int32 /*id*/ = 0) const { return ObjectGuid::Empty; } - Unit* SelectTarget(SelectAggroTarget targetType, uint32 position = 0, float dist = 0.0f, bool playerOnly = false, int32 aura = 0); - // Select the targets satisfying the predicate. - // predicate shall extend std::unary_function<Unit*, bool> + // Select the best target (in <targetType> order) from the threat list that fulfill the following: + // - Not among the first <offset> entries in <targetType> order (or MAXTHREAT order, if <targetType> is RANDOM). + // - Within at most <dist> yards (if dist > 0.0f) + // - At least -<dist> yards away (if dist < 0.0f) + // - Is a player (if playerOnly = true) + // - Not the current tank (if withTank = false) + // - Has aura with ID <aura> (if aura > 0) + // - Does not have aura with ID -<aura> (if aura < 0) + Unit* SelectTarget(SelectAggroTarget targetType, uint32 offset = 0, float dist = 0.0f, bool playerOnly = false, bool withTank = true, int32 aura = 0); + // Select the best target (in <targetType> order) satisfying <predicate> from the threat list. + // If <offset> is nonzero, the first <offset> entries in <targetType> order (or MAXTHREAT order, if <targetType> is RANDOM) are skipped. template<class PREDICATE> - Unit* SelectTarget(SelectAggroTarget targetType, uint32 position, PREDICATE const& predicate) + Unit* SelectTarget(SelectAggroTarget targetType, uint32 offset, PREDICATE const& predicate) { - ThreatContainer::StorageType const& threatlist = GetThreatManager().getThreatList(); - if (position >= threatlist.size()) + ThreatManager& mgr = GetThreatManager(); + // shortcut: if we ignore the first <offset> elements, and there are at most <offset> elements, then we ignore ALL elements + if (mgr.GetThreatListSize() <= offset) return nullptr; std::list<Unit*> targetList; - Unit* currentVictim = nullptr; - if (auto currentVictimReference = GetThreatManager().getCurrentVictim()) + if (targetType == SELECT_TARGET_MAXDISTANCE || targetType == SELECT_TARGET_MINDISTANCE) { - currentVictim = currentVictimReference->getTarget(); + for (ThreatReference* ref : mgr.GetUnsortedThreatList()) + { + if (ref->IsOffline()) + continue; - // Current victim always goes first - if (currentVictim && predicate(currentVictim)) + targetList.push_back(ref->GetVictim()); + } + } + else + { + Unit* currentVictim = mgr.GetCurrentVictim(); + if (currentVictim) targetList.push_back(currentVictim); + + for (ThreatReference* ref : mgr.GetSortedThreatList()) + { + if (ref->IsOffline()) + continue; + + Unit* thisTarget = ref->GetVictim(); + if (thisTarget != currentVictim) + targetList.push_back(thisTarget); + } } - for (HostileReference* hostileRef : threatlist) + // filter by predicate + targetList.remove_if([&predicate](Unit* target) { return !predicate(target); }); + + // shortcut: the list certainly isn't gonna get any larger after this point + if (targetList.size() <= offset) + return nullptr; + + // right now, list is unsorted for DISTANCE types - re-sort by MAXDISTANCE + if (targetType == SELECT_TARGET_MAXDISTANCE || targetType == SELECT_TARGET_MINDISTANCE) + SortByDistance(targetList, targetType == SELECT_TARGET_MINDISTANCE); + + // then reverse the sorting for MIN sortings + if (targetType == SELECT_TARGET_MINTHREAT) + targetList.reverse(); + + // now pop the first <offset> elements + while (offset) { - if (currentVictim != nullptr && hostileRef->getTarget() != currentVictim && predicate(hostileRef->getTarget())) - targetList.push_back(hostileRef->getTarget()); - else if (currentVictim == nullptr && predicate(hostileRef->getTarget())) - targetList.push_back(hostileRef->getTarget()); + targetList.pop_front(); + --offset; } - if (position >= targetList.size()) + // maybe nothing fulfills the predicate + if (targetList.empty()) return nullptr; - if (targetType == SELECT_TARGET_NEAREST || targetType == SELECT_TARGET_FARTHEST) - SortByDistanceTo(me, targetList); - switch (targetType) { - case SELECT_TARGET_NEAREST: - case SELECT_TARGET_TOPAGGRO: - { - auto itr = targetList.begin(); - std::advance(itr, position); - return *itr; - } - case SELECT_TARGET_FARTHEST: - case SELECT_TARGET_BOTTOMAGGRO: - { - auto ritr = targetList.rbegin(); - std::advance(ritr, position); - return *ritr; - } + case SELECT_TARGET_MAXTHREAT: + case SELECT_TARGET_MINTHREAT: + case SELECT_TARGET_MAXDISTANCE: + case SELECT_TARGET_MINDISTANCE: + return targetList.front(); case SELECT_TARGET_RANDOM: return Trinity::Containers::SelectRandomContainerElement(targetList); default: - break; + return nullptr; } - - return nullptr; } - void SelectTargetList(std::list<Unit*>& targetList, uint32 num, SelectAggroTarget targetType, float dist = 0.0f, bool playerOnly = false, int32 aura = 0); - - // Select the targets satifying the predicate. - // predicate shall extend std::unary_function<Unit*, bool> + // Select the best (up to) <num> targets (in <targetType> order) from the threat list that fulfill the following: + // - Not among the first <offset> entries in <targetType> order (or MAXTHREAT order, if <targetType> is RANDOM). + // - Within at most <dist> yards (if dist > 0.0f) + // - At least -<dist> yards away (if dist < 0.0f) + // - Is a player (if playerOnly = true) + // - Not the current tank (if withTank = false) + // - Has aura with ID <aura> (if aura > 0) + // - Does not have aura with ID -<aura> (if aura < 0) + // The resulting targets are stored in <targetList> (which is cleared first). + void SelectTargetList(std::list<Unit*>& targetList, uint32 num, SelectAggroTarget targetType, uint32 offset = 0, float dist = 0.0f, bool playerOnly = false, bool withTank = true, int32 aura = 0); + + // Select the best (up to) <num> targets (in <targetType> order) satisfying <predicate> from the threat list and stores them in <targetList> (which is cleared first). + // If <offset> is nonzero, the first <offset> entries in <targetType> order (or MAXTHREAT order, if <targetType> is RANDOM) are skipped. template <class PREDICATE> - void SelectTargetList(std::list<Unit*>& targetList, PREDICATE const& predicate, uint32 maxTargets, SelectAggroTarget targetType) + void SelectTargetList(std::list<Unit*>& targetList, uint32 num, SelectAggroTarget targetType, uint32 offset, PREDICATE const& predicate) { - ThreatContainer::StorageType const& threatlist = GetThreatManager().getThreatList(); - if (threatlist.empty()) + targetList.clear(); + ThreatManager& mgr = GetThreatManager(); + // shortcut: we're gonna ignore the first <offset> elements, and there's at most <offset> elements, so we ignore them all - nothing to do here + if (mgr.GetThreatListSize() <= offset) return; - for (HostileReference* hostileRef : threatlist) - if (predicate(hostileRef->getTarget())) - targetList.push_back(hostileRef->getTarget()); + if (targetType == SELECT_TARGET_MAXDISTANCE || targetType == SELECT_TARGET_MINDISTANCE) + { + for (ThreatReference* ref : mgr.GetUnsortedThreatList()) + { + if (ref->IsOffline()) + continue; + + targetList.push_back(ref->GetVictim()); + } + } + else + { + Unit* currentVictim = mgr.GetCurrentVictim(); + if (currentVictim) + targetList.push_back(currentVictim); + + for (ThreatReference* ref : mgr.GetSortedThreatList()) + { + if (ref->IsOffline()) + continue; + + Unit* thisTarget = ref->GetVictim(); + if (thisTarget != currentVictim) + targetList.push_back(thisTarget); + } + } + + // filter by predicate + targetList.remove_if([&predicate](Unit* target) { return !predicate(target); }); - if (targetList.size() < maxTargets) + // shortcut: the list isn't gonna get any larger + if (targetList.size() <= offset) + { + targetList.clear(); return; + } - if (targetType == SELECT_TARGET_NEAREST || targetType == SELECT_TARGET_FARTHEST) - SortByDistanceTo(me, targetList); + // right now, list is unsorted for DISTANCE types - re-sort by MAXDISTANCE + if (targetType == SELECT_TARGET_MAXDISTANCE || targetType == SELECT_TARGET_MINDISTANCE) + SortByDistance(targetList, targetType == SELECT_TARGET_MINDISTANCE); - if (targetType == SELECT_TARGET_FARTHEST || targetType == SELECT_TARGET_BOTTOMAGGRO) + // now the list is MAX sorted, reverse for MIN types + if (targetType == SELECT_TARGET_MINTHREAT) targetList.reverse(); + // ignore the first <offset> elements + while (offset) + { + targetList.pop_front(); + --offset; + } + + if (targetList.size() <= num) + return; + if (targetType == SELECT_TARGET_RANDOM) - Trinity::Containers::RandomResize(targetList, maxTargets); + Trinity::Containers::RandomResize(targetList, num); else - targetList.resize(maxTargets); + targetList.resize(num); } // Called at any Damage to any victim (before damage apply) @@ -303,6 +383,7 @@ class TC_GAME_API UnitAI UnitAI& operator=(UnitAI const& right) = delete; ThreatManager& GetThreatManager(); + void SortByDistance(std::list<Unit*> list, bool ascending = true); }; #endif diff --git a/src/server/game/AI/CreatureAI.cpp b/src/server/game/AI/CreatureAI.cpp index 7ecee7cc14c..cd073d3fe39 100644 --- a/src/server/game/AI/CreatureAI.cpp +++ b/src/server/game/AI/CreatureAI.cpp @@ -82,8 +82,8 @@ void CreatureAI::DoZoneInCombat(Creature* creature /*= nullptr*/, float maxRange if (Unit* summoner = creature->ToTempSummon()->GetSummoner()) { Unit* target = summoner->getAttackerForHelper(); - if (!target && summoner->CanHaveThreatList() && !summoner->getThreatManager().isThreatListEmpty()) - target = summoner->getThreatManager().getHostilTarget(); + if (!target && summoner->CanHaveThreatList() && !summoner->GetThreatManager().IsThreatListEmpty()) + target = summoner->GetThreatManager().GetAnyTarget(); if (target && (creature->IsFriendlyTo(summoner) || creature->IsHostileTo(target))) creature->AI()->AttackStart(target); } @@ -114,16 +114,8 @@ void CreatureAI::DoZoneInCombat(Creature* creature /*= nullptr*/, float maxRange { creature->SetInCombatWith(player); player->SetInCombatWith(creature); - creature->AddThreat(player, 0.0f); + creature->GetThreatManager().AddThreat(player, 0.0f, nullptr, true, true); } - - /* Causes certain things to never leave the threat list (Priest Lightwell, etc): - for (Unit::ControlList::const_iterator itr = player->m_Controlled.begin(); itr != player->m_Controlled.end(); ++itr) - { - creature->SetInCombatWith(*itr); - (*itr)->SetInCombatWith(creature); - creature->AddThreat(*itr, 0.0f); - }*/ } } } @@ -141,11 +133,11 @@ void CreatureAI::MoveInLineOfSight_Safe(Unit* who) void CreatureAI::MoveInLineOfSight(Unit* who) { - if (me->GetVictim()) + if (me->IsEngaged()) return; if (me->HasReactState(REACT_AGGRESSIVE) && me->CanStartAttack(who, false)) - AttackStart(who); + me->EngageWithTarget(who); } void CreatureAI::_OnOwnerCombatInteraction(Unit* target) @@ -154,12 +146,7 @@ void CreatureAI::_OnOwnerCombatInteraction(Unit* target) return; if (!me->HasReactState(REACT_PASSIVE) && me->CanStartAttack(target, true)) - { - if (me->IsInCombat()) - me->AddThreat(target, 0.0f); - else - AttackStart(target); - } + me->EngageWithTarget(target); } // Distract creature, if player gets too close while stealthed/prowling @@ -169,8 +156,8 @@ void CreatureAI::TriggerAlert(Unit const* who) const if (!who || who->GetTypeId() != TYPEID_PLAYER) return; - // If this unit isn't an NPC, is already distracted, is in combat, is confused, stunned or fleeing, do nothing - if (me->GetTypeId() != TYPEID_UNIT || me->IsInCombat() || me->HasUnitState(UNIT_STATE_CONFUSED | UNIT_STATE_STUNNED | UNIT_STATE_FLEEING | UNIT_STATE_DISTRACTED)) + // If this unit isn't an NPC, is already distracted, is fighting, is confused, stunned or fleeing, do nothing + if (me->GetTypeId() != TYPEID_UNIT || me->IsEngaged() || me->HasUnitState(UNIT_STATE_CONFUSED | UNIT_STATE_STUNNED | UNIT_STATE_FLEEING | UNIT_STATE_DISTRACTED)) return; // Only alert for hostiles! @@ -227,7 +214,7 @@ void CreatureAI::SetGazeOn(Unit* target) bool CreatureAI::UpdateVictimWithGaze() { - if (!me->IsInCombat()) + if (!me->IsEngaged()) return false; if (me->HasReactState(REACT_PASSIVE)) @@ -247,7 +234,7 @@ bool CreatureAI::UpdateVictimWithGaze() bool CreatureAI::UpdateVictim() { - if (!me->IsInCombat()) + if (!me->IsEngaged()) return false; if (!me->HasReactState(REACT_PASSIVE)) @@ -258,7 +245,7 @@ bool CreatureAI::UpdateVictim() return me->GetVictim() != nullptr; } - else if (me->getThreatManager().isThreatListEmpty()) + else if (me->GetThreatManager().IsThreatListEmpty()) { EnterEvadeMode(EVADE_REASON_NO_HOSTILES); return false; @@ -275,7 +262,7 @@ bool CreatureAI::_EnterEvadeMode(EvadeReason /*why*/) me->RemoveAurasOnEvade(); // sometimes bosses stuck in combat? - me->DeleteThreatList(); + me->GetThreatManager().ClearAllThreat(); me->CombatStop(true); me->LoadCreaturesAddon(); me->SetLootRecipient(nullptr); diff --git a/src/server/game/AI/CreatureAI.h b/src/server/game/AI/CreatureAI.h index a787082bf9d..bd7a6efab22 100644 --- a/src/server/game/AI/CreatureAI.h +++ b/src/server/game/AI/CreatureAI.h @@ -110,7 +110,7 @@ class TC_GAME_API CreatureAI : public UnitAI // Called for reaction at stopping attack at no attackers or targets virtual void EnterEvadeMode(EvadeReason why = EVADE_REASON_OTHER); - // Called for reaction at enter to combat if not in combat yet (enemy can be nullptr) + // Called for reaction when initially engaged virtual void EnterCombat(Unit* /*victim*/) { } // Called when the creature is killed diff --git a/src/server/game/AI/PlayerAI/PlayerAI.cpp b/src/server/game/AI/PlayerAI/PlayerAI.cpp index 34b98cd3fe7..92f1c6f06dc 100644 --- a/src/server/game/AI/PlayerAI/PlayerAI.cpp +++ b/src/server/game/AI/PlayerAI/PlayerAI.cpp @@ -1313,7 +1313,7 @@ void SimpleCharmedPlayerAI::UpdateAI(const uint32 diff) } } - if (charmer->IsInCombat()) + if (charmer->IsEngaged()) { Unit* target = me->GetVictim(); if (!target || !charmer->IsValidAttackTarget(target) || target->HasBreakableByDamageCrowdControlAura()) diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp index d32ac53cb9f..e9e252bcf35 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp +++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp @@ -200,6 +200,49 @@ void ScriptedAI::DoPlaySoundToSet(WorldObject* source, uint32 soundId) source->PlayDirectSound(soundId); } +void ScriptedAI::AddThreat(Unit* victim, float amount, Unit* who) +{ + if (!victim) + return; + if (!who) + who = me; + who->GetThreatManager().AddThreat(victim, amount, nullptr, true, true); +} + +void ScriptedAI::ModifyThreatByPercent(Unit* victim, int32 pct, Unit* who) +{ + if (!victim) + return; + if (!who) + who = me; + who->GetThreatManager().ModifyThreatByPercent(victim, pct); +} + +void ScriptedAI::ResetThreat(Unit* victim, Unit* who) +{ + if (!victim) + return; + if (!who) + who = me; + who->GetThreatManager().ResetThreat(victim); +} + +void ScriptedAI::ResetThreatList(Unit* who) +{ + if (!who) + who = me; + who->GetThreatManager().ResetAllThreat(); +} + +float ScriptedAI::GetThreat(Unit const* victim, Unit const* who) +{ + if (!victim) + return 0.0f; + if (!who) + who = me; + return who->GetThreatManager().GetThreat(victim); +} + Creature* ScriptedAI::DoSpawnCreature(uint32 entry, float offsetX, float offsetY, float offsetZ, float angle, uint32 type, uint32 despawntime) { return me->SummonCreature(entry, me->GetPositionX() + offsetX, me->GetPositionY() + offsetY, me->GetPositionZ() + offsetZ, angle, TempSummonType(type), despawntime); @@ -292,38 +335,6 @@ SpellInfo const* ScriptedAI::SelectSpell(Unit* target, uint32 school, uint32 mec return apSpell[urand(0, spellCount - 1)]; } -void ScriptedAI::DoResetThreat() -{ - if (!me->CanHaveThreatList() || me->getThreatManager().isThreatListEmpty()) - { - TC_LOG_ERROR("scripts", "DoResetThreat called for creature that either cannot have threat list or has empty threat list (me entry = %d)", me->GetEntry()); - return; - } - - ThreatContainer::StorageType threatlist = me->getThreatManager().getThreatList(); - - for (ThreatContainer::StorageType::const_iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr) - { - Unit* unit = ObjectAccessor::GetUnit(*me, (*itr)->getUnitGuid()); - if (unit && DoGetThreat(unit)) - DoModifyThreatPercent(unit, -100); - } -} - -float ScriptedAI::DoGetThreat(Unit* unit) -{ - if (!unit) - return 0.0f; - return me->getThreatManager().getThreat(unit); -} - -void ScriptedAI::DoModifyThreatPercent(Unit* unit, int32 pct) -{ - if (!unit) - return; - me->getThreatManager().modifyThreatPercent(unit, pct); -} - void ScriptedAI::DoTeleportTo(float x, float y, float z, uint32 time) { me->Relocate(x, y, z); @@ -509,7 +520,7 @@ void BossAI::TeleportCheaters() float x, y, z; me->GetPosition(x, y, z); - ThreatContainer::StorageType threatList = me->getThreatManager().getThreatList(); + 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)) @@ -519,7 +530,7 @@ void BossAI::TeleportCheaters() void BossAI::JustSummoned(Creature* summon) { summons.Summon(summon); - if (me->IsInCombat()) + if (me->IsEngaged()) DoZoneInCombat(summon); } diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.h b/src/server/game/AI/ScriptedAI/ScriptedCreature.h index 3b24203a146..6fc85b6a81a 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedCreature.h +++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.h @@ -210,11 +210,16 @@ struct TC_GAME_API ScriptedAI : public CreatureAI //Plays a sound to all nearby players void DoPlaySoundToSet(WorldObject* source, uint32 soundId); - //Drops all threat to 0%. Does not remove players from the threat list - void DoResetThreat(); - - float DoGetThreat(Unit* unit); - void DoModifyThreatPercent(Unit* unit, int32 pct); + // Add specified amount of threat directly to victim (ignores redirection effects) - also puts victim in combat and engages them if necessary + void AddThreat(Unit* victim, float amount, Unit* who = nullptr); + // Adds/removes the specified percentage from the specified victim's threat (to who, or me if not specified) + void ModifyThreatByPercent(Unit* victim, int32 pct, Unit* who = nullptr); + // Resets the victim's threat level to who (or me if not specified) to zero + void ResetThreat(Unit* victim, Unit* who = nullptr); + // Resets the specified unit's threat list (me if not specified) - does not delete entries, just sets their threat to zero + void ResetThreatList(Unit* who = nullptr); + // Returns the threat level of victim towards who (or me if not specified) + float GetThreat(Unit const* victim, Unit const* who = nullptr); void DoTeleportTo(float x, float y, float z, uint32 time = 0); void DoTeleportTo(float const pos[4]); diff --git a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp index 6f5b7f1aa71..30a59109e44 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp +++ b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp @@ -103,8 +103,7 @@ bool npc_escortAI::AssistPlayerInCombatAgainst(Unit* who) } else { - who->SetInCombatWith(me); - me->AddThreat(who, 0.0f); + me->EngageWithTarget(who); return true; } } @@ -126,24 +125,7 @@ void npc_escortAI::MoveInLineOfSight(Unit* who) { float fAttackRadius = me->GetAttackDistance(who); if (me->IsWithinDistInMap(who, fAttackRadius) && me->IsWithinLOSInMap(who)) - { - if (!me->GetVictim()) - { - // Clear distracted state on combat - if (me->HasUnitState(UNIT_STATE_DISTRACTED)) - { - me->ClearUnitState(UNIT_STATE_DISTRACTED); - me->GetMotionMaster()->Clear(); - } - - AttackStart(who); - } - else if (me->GetMap()->IsDungeon()) - { - who->SetInCombatWith(me); - me->AddThreat(who, 0.0f); - } - } + me->EngageWithTarget(who); } } } @@ -193,7 +175,7 @@ void npc_escortAI::ReturnToLastPoint() void npc_escortAI::EnterEvadeMode(EvadeReason /*why*/) { me->RemoveAllAuras(); - me->DeleteThreatList(); + me->GetThreatManager().ClearAllThreat(); me->CombatStop(true); me->SetLootRecipient(nullptr); diff --git a/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp b/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp index d18876fc439..b0b332afecd 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp +++ b/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp @@ -52,9 +52,7 @@ void FollowerAI::AttackStart(Unit* who) if (me->Attack(who, true)) { - me->AddThreat(who, 0.0f); - me->SetInCombatWith(who); - who->SetInCombatWith(me); + me->EngageWithTarget(who); // in case it doesn't have threat+combat yet if (me->HasUnitState(UNIT_STATE_FOLLOW)) me->ClearUnitState(UNIT_STATE_FOLLOW); @@ -87,18 +85,8 @@ bool FollowerAI::AssistPlayerInCombatAgainst(Unit* who) //too far away and no free sight? if (me->IsWithinDistInMap(who, MAX_PLAYER_DISTANCE) && me->IsWithinLOSInMap(who)) { - //already fighting someone? - if (!me->GetVictim()) - { - AttackStart(who); - return true; - } - else - { - who->SetInCombatWith(me); - me->AddThreat(who, 0.0f); - return true; - } + me->EngageWithTarget(who); + return true; } return false; @@ -131,10 +119,7 @@ void FollowerAI::MoveInLineOfSight(Unit* who) AttackStart(who); } else if (me->GetMap()->IsDungeon()) - { - who->SetInCombatWith(me); - me->AddThreat(who, 0.0f); - } + me->EngageWithTarget(who); } } } @@ -176,7 +161,7 @@ void FollowerAI::JustRespawned() void FollowerAI::EnterEvadeMode(EvadeReason /*why*/) { me->RemoveAllAuras(); - me->DeleteThreatList(); + me->GetThreatManager().ClearAllThreat(); me->CombatStop(true); me->SetLootRecipient(nullptr); diff --git a/src/server/game/AI/SmartScripts/SmartAI.cpp b/src/server/game/AI/SmartScripts/SmartAI.cpp index 682f92f1ee6..5aa2550a704 100644 --- a/src/server/game/AI/SmartScripts/SmartAI.cpp +++ b/src/server/game/AI/SmartScripts/SmartAI.cpp @@ -559,18 +559,8 @@ bool SmartAI::AssistPlayerInCombatAgainst(Unit* who) //too far away and no free sight? if (me->IsWithinDistInMap(who, SMART_MAX_AID_DIST) && me->IsWithinLOSInMap(who)) { - //already fighting someone? - if (!me->GetVictim()) - { - AttackStart(who); - return true; - } - else - { - who->SetInCombatWith(me); - me->AddThreat(who, 0.0f); - return true; - } + me->EngageWithTarget(who); + return true; } return false; diff --git a/src/server/game/AI/SmartScripts/SmartScript.cpp b/src/server/game/AI/SmartScripts/SmartScript.cpp index fe86978b0db..3072ef31425 100644 --- a/src/server/game/AI/SmartScripts/SmartScript.cpp +++ b/src/server/game/AI/SmartScripts/SmartScript.cpp @@ -459,15 +459,11 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u if (!me) break; - ThreatContainer::StorageType threatList = me->getThreatManager().getThreatList(); - for (auto i = threatList.begin(); i != threatList.end(); ++i) + for (auto* ref : me->GetThreatManager().GetUnsortedThreatList()) { - if (Unit* target = ObjectAccessor::GetUnit(*me, (*i)->getUnitGuid())) - { - me->getThreatManager().modifyThreatPercent(target, e.action.threatPCT.threatINC ? (int32)e.action.threatPCT.threatINC : -(int32)e.action.threatPCT.threatDEC); - TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_THREAT_ALL_PCT: Creature guidLow %u modify threat for unit %u, value %i", - me->GetGUID().GetCounter(), target->GetGUID().GetCounter(), e.action.threatPCT.threatINC ? (int32)e.action.threatPCT.threatINC : -(int32)e.action.threatPCT.threatDEC); - } + ref->ModifyThreatByPercent(std::max<int32>(-100,int32(e.action.threatPCT.threatINC) - int32(e.action.threatPCT.threatDEC))); + TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_THREAT_ALL_PCT: Creature guidLow %u modify threat for unit %u, value %i", + me->GetGUID().GetCounter(), ref->GetVictim()->GetGUID().GetCounter(), int32(e.action.threatPCT.threatINC)-int32(e.action.threatPCT.threatDEC)); } break; } @@ -480,9 +476,9 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u { if (IsUnit(target)) { - me->getThreatManager().modifyThreatPercent(target->ToUnit(), e.action.threatPCT.threatINC ? (int32)e.action.threatPCT.threatINC : -(int32)e.action.threatPCT.threatDEC); + me->GetThreatManager().ModifyThreatByPercent(target->ToUnit(), std::max<int32>(-100, int32(e.action.threatPCT.threatINC) - int32(e.action.threatPCT.threatDEC))); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_THREAT_SINGLE_PCT: Creature guidLow %u modify threat for unit %u, value %i", - me->GetGUID().GetCounter(), target->GetGUID().GetCounter(), e.action.threatPCT.threatINC ? (int32)e.action.threatPCT.threatINC : -(int32)e.action.threatPCT.threatDEC); + me->GetGUID().GetCounter(), target->GetGUID().GetCounter(), int32(e.action.threatPCT.threatINC) - int32(e.action.threatPCT.threatDEC)); } } break; @@ -2091,7 +2087,7 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u { for (WorldObject* const target : targets) if (IsUnit(target)) - me->AddThreat(target->ToUnit(), float(e.action.threatPCT.threatINC) - float(e.action.threatPCT.threatDEC)); + me->GetThreatManager().AddThreat(target->ToUnit(), float(e.action.threatPCT.threatINC) - float(e.action.threatPCT.threatDEC), nullptr, true, true); break; } case SMART_ACTION_LOAD_EQUIPMENT: @@ -2292,10 +2288,10 @@ void SmartScript::GetTargets(ObjectVector& targets, SmartScriptHolder const& e, { if (e.target.hostilRandom.powerType) { - if (Unit* u = me->AI()->SelectTarget(SELECT_TARGET_TOPAGGRO, 1, PowerUsersSelector(me, Powers(e.target.hostilRandom.powerType - 1), float(e.target.hostilRandom.maxDist), e.target.hostilRandom.playerOnly != 0))) + if (Unit* u = me->AI()->SelectTarget(SELECT_TARGET_MAXTHREAT, 1, PowerUsersSelector(me, Powers(e.target.hostilRandom.powerType - 1), float(e.target.hostilRandom.maxDist), e.target.hostilRandom.playerOnly != 0))) targets.push_back(u); } - else if (Unit* u = me->AI()->SelectTarget(SELECT_TARGET_TOPAGGRO, 1, float(e.target.hostilRandom.maxDist), e.target.hostilRandom.playerOnly != 0)) + else if (Unit* u = me->AI()->SelectTarget(SELECT_TARGET_MAXTHREAT, 1, float(e.target.hostilRandom.maxDist), e.target.hostilRandom.playerOnly != 0)) targets.push_back(u); } break; @@ -2304,10 +2300,10 @@ void SmartScript::GetTargets(ObjectVector& targets, SmartScriptHolder const& e, { if (e.target.hostilRandom.powerType) { - if (Unit* u = me->AI()->SelectTarget(SELECT_TARGET_BOTTOMAGGRO, 0, PowerUsersSelector(me, Powers(e.target.hostilRandom.powerType - 1), float(e.target.hostilRandom.maxDist), e.target.hostilRandom.playerOnly != 0))) + if (Unit* u = me->AI()->SelectTarget(SELECT_TARGET_MINTHREAT, 0, PowerUsersSelector(me, Powers(e.target.hostilRandom.powerType - 1), float(e.target.hostilRandom.maxDist), e.target.hostilRandom.playerOnly != 0))) targets.push_back(u); } - else if (Unit* u = me->AI()->SelectTarget(SELECT_TARGET_BOTTOMAGGRO, 0, float(e.target.hostilRandom.maxDist), e.target.hostilRandom.playerOnly != 0)) + else if (Unit* u = me->AI()->SelectTarget(SELECT_TARGET_MINTHREAT, 0, float(e.target.hostilRandom.maxDist), e.target.hostilRandom.playerOnly != 0)) targets.push_back(u); } break; @@ -2338,7 +2334,7 @@ void SmartScript::GetTargets(ObjectVector& targets, SmartScriptHolder const& e, case SMART_TARGET_FARTHEST: if (me) { - if (Unit* u = me->AI()->SelectTarget(SELECT_TARGET_FARTHEST, 0, FarthestTargetSelector(me, float(e.target.farthest.maxDist), e.target.farthest.playerOnly != 0, e.target.farthest.isInLos != 0))) + if (Unit* u = me->AI()->SelectTarget(SELECT_TARGET_MAXDISTANCE, 0, FarthestTargetSelector(me, float(e.target.farthest.maxDist), e.target.farthest.playerOnly != 0, e.target.farthest.isInLos != 0))) targets.push_back(u); } break; @@ -2555,14 +2551,10 @@ void SmartScript::GetTargets(ObjectVector& targets, SmartScriptHolder const& e, } case SMART_TARGET_THREAT_LIST: { - if (me) - { - ThreatContainer::StorageType const& threatList = me->getThreatManager().getThreatList(); - for (HostileReference const* ref : threatList) - if (Unit* temp = ObjectAccessor::GetUnit(*me, ref->getUnitGuid())) - if (e.target.hostilRandom.maxDist == 0 || me->IsWithinCombatRange(temp, float(e.target.hostilRandom.maxDist))) - targets.push_back(temp); - } + if (me && me->CanHaveThreatList()) + for (auto* ref : me->GetThreatManager().GetUnsortedThreatList()) + if (!e.target.hostilRandom.maxDist || me->IsWithinCombatRange(ref->GetVictim(), float(e.target.hostilRandom.maxDist))) + targets.push_back(ref->GetVictim()); break; } case SMART_TARGET_CLOSEST_ENEMY: @@ -2646,18 +2638,18 @@ void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, ui ProcessTimedAction(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax); break; case SMART_EVENT_UPDATE_OOC: - if (me && me->IsInCombat()) + if (me && me->IsEngaged()) return; ProcessTimedAction(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax); break; case SMART_EVENT_UPDATE_IC: - if (!me || !me->IsInCombat()) + if (!me || !me->IsEngaged()) return; ProcessTimedAction(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax); break; case SMART_EVENT_HEALT_PCT: { - if (!me || !me->IsInCombat() || !me->GetMaxHealth()) + if (!me || !me->IsEngaged() || !me->GetMaxHealth()) return; uint32 perc = (uint32)me->GetHealthPct(); if (perc > e.event.minMaxRepeat.max || perc < e.event.minMaxRepeat.min) @@ -2667,7 +2659,7 @@ void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, ui } case SMART_EVENT_TARGET_HEALTH_PCT: { - if (!me || !me->IsInCombat() || !me->GetVictim() || !me->EnsureVictim()->GetMaxHealth()) + if (!me || !me->IsEngaged() || !me->GetVictim() || !me->EnsureVictim()->GetMaxHealth()) return; uint32 perc = (uint32)me->EnsureVictim()->GetHealthPct(); if (perc > e.event.minMaxRepeat.max || perc < e.event.minMaxRepeat.min) @@ -2677,7 +2669,7 @@ void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, ui } case SMART_EVENT_MANA_PCT: { - if (!me || !me->IsInCombat() || !me->GetMaxPower(POWER_MANA)) + if (!me || !me->IsEngaged() || !me->GetMaxPower(POWER_MANA)) return; uint32 perc = uint32(100.0f * me->GetPower(POWER_MANA) / me->GetMaxPower(POWER_MANA)); if (perc > e.event.minMaxRepeat.max || perc < e.event.minMaxRepeat.min) @@ -2687,7 +2679,7 @@ void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, ui } case SMART_EVENT_TARGET_MANA_PCT: { - if (!me || !me->IsInCombat() || !me->GetVictim() || !me->EnsureVictim()->GetMaxPower(POWER_MANA)) + if (!me || !me->IsEngaged() || !me->GetVictim() || !me->EnsureVictim()->GetMaxPower(POWER_MANA)) return; uint32 perc = uint32(100.0f * me->EnsureVictim()->GetPower(POWER_MANA) / me->EnsureVictim()->GetMaxPower(POWER_MANA)); if (perc > e.event.minMaxRepeat.max || perc < e.event.minMaxRepeat.min) @@ -2697,7 +2689,7 @@ void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, ui } case SMART_EVENT_RANGE: { - if (!me || !me->IsInCombat() || !me->GetVictim()) + if (!me || !me->IsEngaged() || !me->GetVictim()) return; if (me->IsInRange(me->GetVictim(), (float)e.event.minMaxRepeat.min, (float)e.event.minMaxRepeat.max)) @@ -2708,7 +2700,7 @@ void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, ui } case SMART_EVENT_VICTIM_CASTING: { - if (!me || !me->IsInCombat()) + if (!me || !me->IsEngaged()) return; Unit* victim = me->GetVictim(); @@ -2726,7 +2718,7 @@ void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, ui } case SMART_EVENT_FRIENDLY_HEALTH: { - if (!me || !me->IsInCombat()) + if (!me || !me->IsEngaged()) return; Unit* target = DoSelectLowestHpFriendly((float)e.event.friendlyHealth.radius, e.event.friendlyHealth.hpDeficit); @@ -2742,7 +2734,7 @@ void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, ui } case SMART_EVENT_FRIENDLY_IS_CC: { - if (!me || !me->IsInCombat()) + if (!me || !me->IsEngaged()) return; std::vector<Creature*> creatures; @@ -2865,7 +2857,7 @@ void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, ui } case SMART_EVENT_OOC_LOS: { - if (!me || me->IsInCombat()) + if (!me || me->IsEngaged()) return; //can trigger if closer than fMaxAllowedRange float range = (float)e.event.los.maxDist; @@ -2885,7 +2877,7 @@ void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, ui } case SMART_EVENT_IC_LOS: { - if (!me || !me->IsInCombat()) + if (!me || !me->IsEngaged()) return; //can trigger if closer than fMaxAllowedRange float range = (float)e.event.los.maxDist; @@ -3075,7 +3067,7 @@ void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, ui } case SMART_EVENT_FRIENDLY_HEALTH_PCT: { - if (!me || !me->IsInCombat()) + if (!me || !me->IsEngaged()) return; ObjectVector targets; @@ -3220,10 +3212,10 @@ void SmartScript::UpdateTimer(SmartScriptHolder& e, uint32 const diff) if (e.event.event_phase_mask && !IsInPhase(e.event.event_phase_mask)) return; - if (e.GetEventType() == SMART_EVENT_UPDATE_IC && (!me || !me->IsInCombat())) + if (e.GetEventType() == SMART_EVENT_UPDATE_IC && (!me || !me->IsEngaged())) return; - if (e.GetEventType() == SMART_EVENT_UPDATE_OOC && (me && me->IsInCombat())) //can be used with me=nullptr (go script) + if (e.GetEventType() == SMART_EVENT_UPDATE_OOC && (me && me->IsEngaged())) //can be used with me=nullptr (go script) return; if (e.timer < diff) @@ -3531,7 +3523,7 @@ void SmartScript::OnMoveInLineOfSight(Unit* who) if (!me) return; - ProcessEventsFor(me->IsInCombat() ? SMART_EVENT_IC_LOS : SMART_EVENT_OOC_LOS, who); + ProcessEventsFor(me->IsEngaged() ? SMART_EVENT_IC_LOS : SMART_EVENT_OOC_LOS, who); } // SmartScript end diff --git a/src/server/game/Combat/ThreatManager.cpp b/src/server/game/Combat/ThreatManager.cpp index 005e8fad2a8..5a1207b82ca 100644 --- a/src/server/game/Combat/ThreatManager.cpp +++ b/src/server/game/Combat/ThreatManager.cpp @@ -130,6 +130,9 @@ void HostileReference::fireStatusChanged(ThreatRefStatusChangeEvent& threatRefSt GetSource()->processThreatEvent(&threatRefStatusChangeEvent); } +// -- compatibility layer for combat rewrite (PR #19930) +Unit* HostileReference::GetOwner() const { return GetSource()->GetOwner(); } + //============================================================ void HostileReference::addThreat(float modThreat) @@ -263,7 +266,7 @@ void ThreatContainer::clearReferences() //============================================================ // Return the HostileReference of NULL, if not found -HostileReference* ThreatContainer::getReferenceByTarget(Unit* victim) const +HostileReference* ThreatContainer::getReferenceByTarget(Unit const* victim) const { if (!victim) return nullptr; @@ -292,7 +295,7 @@ HostileReference* ThreatContainer::addThreat(Unit* victim, float threat) //============================================================ -void ThreatContainer::modifyThreatPercent(Unit* victim, int32 percent) +void ThreatContainer::ModifyThreatByPercent(Unit* victim, int32 percent) { if (HostileReference* ref = getReferenceByTarget(victim)) ref->addThreatPercent(percent); @@ -391,6 +394,30 @@ HostileReference* ThreatContainer::selectNextVictim(Creature* attacker, HostileR ThreatManager::ThreatManager(Unit* owner) : iCurrentVictim(nullptr), iOwner(owner), iUpdateTimer(THREAT_UPDATE_INTERVAL) { } +// -- compatibility layer for combat rewrite (PR #19930) +void ThreatManager::ForwardThreatForAssistingMe(Unit* victim, float amount, SpellInfo const* spell, bool ignoreModifiers, bool ignoreRedirection) +{ + (void)ignoreModifiers; (void)ignoreRedirection; + GetOwner()->getHostileRefManager().threatAssist(victim, amount, spell); +} + +void ThreatManager::AddThreat(Unit* victim, float amount, SpellInfo const* spell, bool ignoreModifiers, bool ignoreRedirection) +{ + (void)ignoreModifiers; (void)ignoreRedirection; + if (!iOwner->CanHaveThreatList() || iOwner->HasUnitState(UNIT_STATE_EVADE)) + return; + iOwner->SetInCombatWith(victim); + victim->SetInCombatWith(iOwner); + addThreat(victim, amount, spell ? spell->GetSchoolMask() : victim->GetMeleeDamageSchoolMask(), spell); +} + +void ThreatManager::ClearAllThreat() +{ + if (iOwner->CanHaveThreatList(true) && !isThreatListEmpty()) + iOwner->SendClearThreatListOpcode(); + clearReferences(); +} + //============================================================ void ThreatManager::clearReferences() @@ -453,9 +480,9 @@ void ThreatManager::_addThreat(Unit* victim, float threat) //============================================================ -void ThreatManager::modifyThreatPercent(Unit* victim, int32 percent) +void ThreatManager::ModifyThreatByPercent(Unit* victim, int32 percent) { - iThreatContainer.modifyThreatPercent(victim, percent); + iThreatContainer.ModifyThreatByPercent(victim, percent); } //============================================================ diff --git a/src/server/game/Combat/ThreatManager.h b/src/server/game/Combat/ThreatManager.h index c500663214e..fcc0f82a220 100644 --- a/src/server/game/Combat/ThreatManager.h +++ b/src/server/game/Combat/ThreatManager.h @@ -24,6 +24,7 @@ #include "LinkedReference/Reference.h" #include "UnitEvents.h" #include "ObjectGuid.h" +#include "Containers.h" #include <list> @@ -51,6 +52,19 @@ 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); @@ -157,7 +171,7 @@ class TC_GAME_API ThreatContainer HostileReference* addThreat(Unit* victim, float threat); - void modifyThreatPercent(Unit* victim, int32 percent); + void ModifyThreatByPercent(Unit* victim, int32 percent); HostileReference* selectNextVictim(Creature* attacker, HostileReference* currentVictim) const; @@ -175,7 +189,7 @@ class TC_GAME_API ThreatContainer return iThreatList.empty() ? nullptr : iThreatList.front(); } - HostileReference* getReferenceByTarget(Unit* victim) const; + HostileReference* getReferenceByTarget(Unit const* victim) const; StorageType const & getThreatList() const { return iThreatList; } @@ -201,9 +215,31 @@ class TC_GAME_API ThreatContainer //================================================= +typedef HostileReference ThreatReference; class TC_GAME_API ThreatManager { public: + // -- compatibility layer for combat rewrite (PR #19930) + Trinity::Containers::IteratorPair<std::list<ThreatReference*>::const_iterator> GetSortedThreatList() const { auto& list = iThreatContainer.getThreatList(); return { list.cbegin(), list.cend() }; } + Trinity::Containers::IteratorPair<std::list<ThreatReference*>::const_iterator> GetUnsortedThreatList() const { return GetSortedThreatList(); } + 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(); } + void ClearAllThreat(); + void AddThreat(Unit* victim, float amount, SpellInfo const* spell = nullptr, bool ignoreModifiers = false, bool ignoreRedirection = false); + 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; } + + public: + friend class HostileReference; explicit ThreatManager(Unit* owner); @@ -216,7 +252,7 @@ class TC_GAME_API ThreatManager void doAddThreat(Unit* victim, float threat); - void modifyThreatPercent(Unit* victim, int32 percent); + void ModifyThreatByPercent(Unit* victim, int32 percent); float getThreat(Unit* victim, bool alsoSearchOfflineList = false); diff --git a/src/server/game/Combat/UnitEvents.h b/src/server/game/Combat/UnitEvents.h index 35b7e7ecb5f..5e7a00f049d 100644 --- a/src/server/game/Combat/UnitEvents.h +++ b/src/server/game/Combat/UnitEvents.h @@ -116,7 +116,7 @@ class TC_GAME_API ThreatRefStatusChangeEvent : public UnitBaseEvent void setThreatManager(ThreatManager* pThreatManager) { iThreatManager = pThreatManager; } - ThreatManager* getThreatManager() const { return iThreatManager; } + ThreatManager* GetThreatManager() const { return iThreatManager; } }; #endif diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index ef17eaa07bd..3b274c3b67f 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -215,9 +215,7 @@ bool AssistDelayEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/) if (assistant && assistant->CanAssistTo(&m_owner, victim)) { assistant->SetNoCallAssistance(true); - assistant->CombatStart(victim); - if (assistant->IsAIEnabled) - assistant->AI()->AttackStart(victim); + assistant->EngageWithTarget(victim); } } } @@ -682,7 +680,7 @@ void Creature::Update(uint32 diff) } // periodic check to see if the creature has passed an evade boundary - if (IsAIEnabled && !IsInEvadeMode() && IsInCombat()) + if (IsAIEnabled && !IsInEvadeMode() && IsEngaged()) { if (diff >= m_boundaryCheckTime) { @@ -693,7 +691,7 @@ void Creature::Update(uint32 diff) } // if periodic combat pulse is enabled and we are both in combat and in a dungeon, do this now - if (m_combatPulseDelay > 0 && IsInCombat() && GetMap()->IsDungeon()) + if (m_combatPulseDelay > 0 && IsEngaged() && GetMap()->IsDungeon()) { if (diff > m_combatPulseTime) m_combatPulseTime = 0; @@ -712,12 +710,7 @@ void Creature::Update(uint32 diff) continue; if (player->IsAlive() && IsHostileTo(player)) - { - if (CanHaveThreatList()) - AddThreat(player, 0.0f); - SetInCombatWith(player); - player->SetInCombatWith(this); - } + EngageWithTarget(player); } } @@ -1662,7 +1655,7 @@ bool Creature::CanStartAttack(Unit const* who, bool force) const if (!_IsTargetAcceptable(who)) return false; - if (who->IsInCombat() && IsWithinDist(who, ATTACK_DISTANCE)) + if (who->IsEngaged() && IsWithinDist(who, ATTACK_DISTANCE)) if (Unit* victim = who->getAttackerForHelper()) if (IsWithinDistInMap(victim, sWorld->getFloatConfig(CONFIG_CREATURE_FAMILY_ASSISTANCE_RADIUS))) force = true; @@ -1803,8 +1796,6 @@ void Creature::setDeathState(DeathState s) SetUInt32Value(UNIT_FIELD_FLAGS, unit_flags); SetUInt32Value(UNIT_DYNAMIC_FLAGS, dynamicflags); - RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IN_COMBAT); - SetMeleeDamageSchool(SpellSchools(cinfo->dmgschool)); if (creatureData && GetPhaseMask() != creatureData->phaseMask) @@ -2193,7 +2184,7 @@ bool Creature::CanAssistTo(Unit const* u, Unit const* enemy, bool checkfaction / return false; // skip fighting creature - if (IsInCombat()) + if (IsEngaged()) return false; // only free creature @@ -2243,7 +2234,7 @@ bool Creature::_IsTargetAcceptable(Unit const* target) const Unit const* targetVictim = target->getAttackerForHelper(); // if I'm already fighting target, or I'm hostile towards the target, the target is acceptable - if (IsInCombatWith(target) || IsHostileTo(target)) + if (IsEngagedBy(target) || IsHostileTo(target)) return true; // if the target's victim is friendly, and the target is neutral, the target is acceptable @@ -2441,11 +2432,7 @@ void Creature::SetInCombatWithZone() continue; if (player->IsAlive()) - { - SetInCombatWith(player); - player->SetInCombatWith(this); - AddThreat(player, 0.0f); - } + EngageWithTarget(player); } } } diff --git a/src/server/game/Entities/Creature/CreatureGroups.cpp b/src/server/game/Entities/Creature/CreatureGroups.cpp index f534db8759c..b7de0c874fa 100644 --- a/src/server/game/Entities/Creature/CreatureGroups.cpp +++ b/src/server/game/Entities/Creature/CreatureGroups.cpp @@ -173,7 +173,7 @@ void CreatureGroup::RemoveMember(Creature* member) member->SetFormation(nullptr); } -void CreatureGroup::MemberAttackStart(Creature* member, Unit* target) +void CreatureGroup::MemberEngagingTarget(Creature* member, Unit* target) { uint8 groupAI = sFormationMgr->CreatureGroupMap[member->GetSpawnId()]->groupAI; if (!groupAI) @@ -181,11 +181,7 @@ void CreatureGroup::MemberAttackStart(Creature* member, Unit* target) for (CreatureGroupMemberType::iterator itr = m_members.begin(); itr != m_members.end(); ++itr) { - if (m_leader) // avoid crash if leader was killed and reset. - TC_LOG_DEBUG("entities.unit", "GROUP ATTACK: group instance id %u calls member instid %u", m_leader->GetInstanceId(), member->GetInstanceId()); - Creature* other = itr->first; - // Skip self if (other == member) continue; @@ -193,11 +189,8 @@ void CreatureGroup::MemberAttackStart(Creature* member, Unit* target) if (!other->IsAlive()) continue; - if (other->GetVictim()) - continue; - if (((other != m_leader && groupAI & FLAG_AGGRO_ON_AGGRO) || (other == m_leader && groupAI & FLAG_TO_AGGRO_ON_AGGRO)) && other->IsValidAttackTarget(target)) - other->AI()->AttackStart(target); + other->EngageWithTarget(target); } } diff --git a/src/server/game/Entities/Creature/CreatureGroups.h b/src/server/game/Entities/Creature/CreatureGroups.h index f0ce2677dc8..3f8e466fca5 100644 --- a/src/server/game/Entities/Creature/CreatureGroups.h +++ b/src/server/game/Entities/Creature/CreatureGroups.h @@ -89,7 +89,7 @@ class TC_GAME_API CreatureGroup void FormationReset(bool dismiss); void LeaderMoveTo(Position const& destination, uint32 id = 0, uint32 moveType = 0, bool orientation = false); - void MemberAttackStart(Creature* member, Unit* target); + void MemberEngagingTarget(Creature* member, Unit* target); }; #define sFormationMgr FormationMgr::instance() diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index ba48a64d719..508a89a86c8 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -1000,7 +1000,7 @@ void MovementInfo::OutDebug() WorldObject::WorldObject(bool isWorldObject) : WorldLocation(), LastUsedScriptID(0), m_name(""), m_isActive(false), m_isWorldObject(isWorldObject), m_zoneScript(nullptr), m_transport(nullptr), m_zoneId(0), m_areaId(0), m_staticFloorZ(VMAP_INVALID_HEIGHT), m_currMap(nullptr), m_InstanceId(0), -m_phaseMask(PHASEMASK_NORMAL), m_notifyflags(0), m_executed_notifies(0) +m_phaseMask(PHASEMASK_NORMAL), m_notifyflags(0) { m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GHOST, GHOST_VISIBILITY_ALIVE | GHOST_VISIBILITY_GHOST); m_serverSideVisibilityDetect.SetValue(SERVERSIDE_VISIBILITY_GHOST, GHOST_VISIBILITY_ALIVE); diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h index f5ff5100b78..ca1b547f68b 100644 --- a/src/server/game/Entities/Object/Object.h +++ b/src/server/game/Entities/Object/Object.h @@ -410,9 +410,7 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation void AddToNotify(uint16 f) { m_notifyflags |= f;} bool isNeedNotify(uint16 f) const { return (m_notifyflags & f) != 0; } uint16 GetNotifyFlags() const { return m_notifyflags; } - bool NotifyExecuted(uint16 f) const { return (m_executed_notifies & f) != 0; } - void SetNotified(uint16 f) { m_executed_notifies |= f;} - void ResetAllNotifies() { m_notifyflags = 0; m_executed_notifies = 0; } + void ResetAllNotifies() { m_notifyflags = 0; } bool isActiveObject() const { return m_isActive; } void setActive(bool isActiveObject); @@ -476,7 +474,6 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation uint32 m_phaseMask; // in area phase state uint16 m_notifyflags; - uint16 m_executed_notifies; virtual bool _IsWithinDist(WorldObject const* obj, float dist2compare, bool is3D, bool incOwnRadius = true, bool incTargetRadius = true) const; bool CanNeverSee(WorldObject const* obj) const; diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index b4d93f01d09..33ce50c1053 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -20285,14 +20285,38 @@ void Player::UpdateAfkReport(time_t currTime) } } +void Player::SetContestedPvP(Player* attackedPlayer) +{ + if (attackedPlayer && (attackedPlayer == this || (duel && duel->opponent == attackedPlayer))) + return; + + SetContestedPvPTimer(30000); + if (!HasUnitState(UNIT_STATE_ATTACK_PLAYER)) + { + AddUnitState(UNIT_STATE_ATTACK_PLAYER); + SetFlag(PLAYER_FLAGS, PLAYER_FLAGS_CONTESTED_PVP); + // call MoveInLineOfSight for nearby contested guards + Trinity::AIRelocationNotifier notifier(*this); + Cell::VisitWorldObjects(this, notifier, GetVisibilityRange()); + } + for (Unit* unit : m_Controlled) + { + if (!unit->HasUnitState(UNIT_STATE_ATTACK_PLAYER)) + { + unit->AddUnitState(UNIT_STATE_ATTACK_PLAYER); + Trinity::AIRelocationNotifier notifier(*unit); + Cell::VisitWorldObjects(this, notifier, GetVisibilityRange()); + } + } +} + void Player::UpdateContestedPvP(uint32 diff) { - if (!m_contestedPvPTimer||IsInCombat()) + if (!m_contestedPvPTimer || IsInCombat()) return; + if (m_contestedPvPTimer <= diff) - { ResetContestedPvP(); - } else m_contestedPvPTimer -= diff; } diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 210909db73f..4a2a82000b4 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1502,6 +1502,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> void UpdateAfkReport(time_t currTime); void UpdatePvPFlag(time_t currTime); + void SetContestedPvP(Player* attackedPlayer = nullptr); void UpdateContestedPvP(uint32 currTime); void SetContestedPvPTimer(uint32 newTime) {m_contestedPvPTimer = newTime;} void ResetContestedPvP(); diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index bb43c34182a..daa9d5bdefa 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -445,7 +445,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)) + if (CanHaveThreatList() && GetThreatManager().isNeedUpdateToClient(p_time)) SendThreatListUpdate(); // update combat timer only for players and pets (only pets with PetAI) @@ -880,7 +880,7 @@ uint32 Unit::DealDamage(Unit* victim, uint32 damage, CleanDamage const* cleanDam if (damagetype != DOT && damage > 0 && !victim->GetOwnerGUID().IsPlayer() && (!spellProto || !spellProto->HasAura(SPELL_AURA_DAMAGE_SHIELD))) victim->ToCreature()->SetLastDamagedTime(GameTime::GetGameTime() + MAX_AGGRO_RESET_TIME); - victim->AddThreat(this, float(damage), damageSchoolMask, spellProto); + victim->GetThreatManager().AddThreat(this, float(damage), spellProto); } else // victim is a player { @@ -5763,6 +5763,9 @@ void Unit::_removeAttacker(Unit* pAttacker) Unit* Unit::getAttackerForHelper() const // If someone wants to help, who to give them { + if (!IsEngaged()) + return nullptr; + if (Unit* victim = GetVictim()) if ((!IsPet() && !GetPlayerMovingMe()) || IsInCombatWith(victim) || victim->IsInCombatWith(this)) return victim; @@ -5866,14 +5869,14 @@ bool Unit::Attack(Unit* victim, bool meleeAttack) if (creature && !IsPet()) { // should not let player enter combat by right clicking target - doesn't helps - AddThreat(victim, 0.0f); + GetThreatManager().AddThreat(victim, 0.0f); SetInCombatWith(victim); if (victim->GetTypeId() == TYPEID_PLAYER) victim->SetInCombatWith(this); if (Unit* owner = victim->GetOwner()) { - AddThreat(owner, 0.0f); + GetThreatManager().AddThreat(owner, 0.0f); SetInCombatWith(owner); if (owner->GetTypeId() == TYPEID_PLAYER) owner->SetInCombatWith(this); @@ -6768,12 +6771,16 @@ void Unit::SendEnergizeSpellLog(Unit* victim, uint32 spellId, int32 damage, Powe void Unit::EnergizeBySpell(Unit* victim, uint32 spellId, int32 damage, Powers powerType) { - SendEnergizeSpellLog(victim, spellId, damage, powerType); + if (SpellInfo const* info = sSpellMgr->GetSpellInfo(spellId)) + EnergizeBySpell(victim, info, damage, powerType); +} + +void Unit::EnergizeBySpell(Unit* victim, SpellInfo const* spellInfo, int32 damage, Powers powerType) +{ + SendEnergizeSpellLog(victim, spellInfo->Id, damage, powerType); // needs to be called after sending spell log victim->ModifyPower(powerType, damage); - - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); - victim->getHostileRefManager().threatAssist(this, float(damage) * 0.5f, spellInfo); + victim->GetThreatManager().ForwardThreatForAssistingMe(this, float(damage)/2, spellInfo, true); } uint32 Unit::SpellDamageBonusDone(Unit* victim, SpellInfo const* spellProto, uint32 pdamage, DamageEffectType damagetype, uint32 stack) const @@ -8690,11 +8697,11 @@ void Unit::CombatStart(Unit* target, bool initialAggro) SetInCombatWith(target); target->SetInCombatWith(this); } - Unit* who = target->GetCharmerOrOwnerOrSelf(); - if (who->GetTypeId() == TYPEID_PLAYER) - SetContestedPvP(who->ToPlayer()); 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)) @@ -8732,7 +8739,7 @@ void Unit::SetInCombatState(bool PvP, Unit* enemy) creature->AI()->EnterCombat(enemy); if (creature->GetFormation()) - creature->GetFormation()->MemberAttackStart(creature, enemy); + creature->GetFormation()->MemberEngagingTarget(creature, enemy); } if (IsPet()) @@ -9419,7 +9426,7 @@ void Unit::setDeathState(DeathState s) if (s != ALIVE && s != JUST_RESPAWNED) { CombatStop(); - DeleteThreatList(); + GetThreatManager().ClearAllThreat(); getHostileRefManager().deleteReferences(); ClearComboPointHolders(); // any combo points pointed to unit lost at it death @@ -9489,7 +9496,7 @@ bool Unit::CanHaveThreatList(bool skipAliveCheck) const // 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) && ((Pet*)this)->GetOwnerGUID().IsPlayer()) + if (HasUnitTypeMask(UNIT_MASK_MINION | UNIT_MASK_GUARDIAN | UNIT_MASK_CONTROLABLE_GUARDIAN) && GetOwnerGUID().IsPlayer()) return false; return true; @@ -9509,24 +9516,6 @@ float Unit::ApplyTotalThreatModifier(float fThreat, SpellSchoolMask schoolMask) //====================================================================== -void Unit::AddThreat(Unit* victim, float fThreat, SpellSchoolMask schoolMask, SpellInfo const* threatSpell) -{ - // Only mobs can manage threat lists - if (CanHaveThreatList() && !HasUnitState(UNIT_STATE_EVADE)) - m_ThreatManager.addThreat(victim, fThreat, schoolMask, threatSpell); -} - -//====================================================================== - -void Unit::DeleteThreatList() -{ - if (CanHaveThreatList(true) && !m_ThreatManager.isThreatListEmpty()) - SendClearThreatListOpcode(); - m_ThreatManager.clearReferences(); -} - -//====================================================================== - void Unit::TauntApply(Unit* taunter) { ASSERT(GetTypeId() == TYPEID_UNIT); @@ -10678,7 +10667,7 @@ void Unit::CleanupBeforeRemoveFromMap(bool finalCleanup) CombatStop(); ClearComboPoints(); ClearComboPointHolders(); - DeleteThreatList(); + GetThreatManager().ClearAllThreat(); getHostileRefManager().deleteReferences(); } @@ -11338,8 +11327,7 @@ Player* Unit::GetSpellModOwner() const if (HasUnitTypeMask(UNIT_MASK_PET | UNIT_MASK_TOTEM | UNIT_MASK_GUARDIAN)) { if (Unit* owner = GetOwner()) - if (Player* player = owner->ToPlayer()) - return player; + return owner->ToPlayer(); } return nullptr; } @@ -11904,29 +11892,6 @@ bool Unit::IsUnderLastManaUseEffect() const return getMSTimeDiff(m_lastManaUse, GameTime::GetGameTimeMS()) < 5000; } -void Unit::SetContestedPvP(Player* attackedPlayer) -{ - Player* player = GetCharmerOrOwnerPlayerOrPlayerItself(); - - if (!player || (attackedPlayer && (attackedPlayer == player || (player->duel && player->duel->opponent == attackedPlayer)))) - return; - - player->SetContestedPvPTimer(30000); - if (!player->HasUnitState(UNIT_STATE_ATTACK_PLAYER)) - { - player->AddUnitState(UNIT_STATE_ATTACK_PLAYER); - player->SetFlag(PLAYER_FLAGS, PLAYER_FLAGS_CONTESTED_PVP); - // call MoveInLineOfSight for nearby contested guards - UpdateObjectVisibility(); - } - if (!HasUnitState(UNIT_STATE_ATTACK_PLAYER)) - { - AddUnitState(UNIT_STATE_ATTACK_PLAYER); - // call MoveInLineOfSight for nearby contested guards - UpdateObjectVisibility(); - } -} - Pet* Unit::CreateTamedPetFrom(Creature* creatureTarget, uint32 spell_id) { if (GetTypeId() != TYPEID_PLAYER) @@ -12184,7 +12149,7 @@ void Unit::Kill(Unit* victim, bool durabilityLoss) if (!creature->IsPet()) { - creature->DeleteThreatList(); + creature->GetThreatManager().ClearAllThreat(); // must be after setDeathState which resets dynamic flags if (!creature->loot.isLooted()) @@ -12576,7 +12541,7 @@ bool Unit::SetCharmedBy(Unit* charmer, CharmType type, AuraApplication const* au CastStop(); CombatStop(); /// @todo CombatStop(true) may cause crash (interrupt spells) - DeleteThreatList(); + GetThreatManager().ClearAllThreat(); Player* playerCharmer = charmer->ToPlayer(); @@ -12719,7 +12684,7 @@ void Unit::RemoveCharmedBy(Unit* charmer) CastStop(); CombatStop(); /// @todo CombatStop(true) may cause crash (interrupt spells) getHostileRefManager().deleteReferences(); - DeleteThreatList(); + GetThreatManager().ClearAllThreat(); if (_oldFactionId) { @@ -13177,8 +13142,8 @@ void Unit::SetPhaseMask(uint32 newPhaseMask, bool update) // modify threat lists for new phasemask if (GetTypeId() != TYPEID_PLAYER) { - std::list<HostileReference*> threatList = getThreatManager().getThreatList(); - std::list<HostileReference*> offlineThreatList = getThreatManager().getOfflineThreatList(); + std::list<HostileReference*> threatList = GetThreatManager().getThreatList(); + std::list<HostileReference*> offlineThreatList = GetThreatManager().getOfflineThreatList(); // merge expects sorted lists threatList.sort(); @@ -14027,15 +13992,15 @@ void Unit::UpdateHeight(float newZ) void Unit::SendThreatListUpdate() { - if (!getThreatManager().isThreatListEmpty()) + if (!GetThreatManager().isThreatListEmpty()) { - uint32 count = getThreatManager().getThreatList().size(); + uint32 count = GetThreatManager().getThreatList().size(); //TC_LOG_DEBUG("entities.unit", "WORLD: Send SMSG_THREAT_UPDATE Message"); WorldPacket data(SMSG_THREAT_UPDATE, 8 + count * 8); data << GetPackGUID(); data << uint32(count); - ThreatContainer::StorageType const& tlist = getThreatManager().getThreatList(); + ThreatContainer::StorageType const& tlist = GetThreatManager().getThreatList(); for (ThreatContainer::StorageType::const_iterator itr = tlist.begin(); itr != tlist.end(); ++itr) { data << (*itr)->getUnitGuid().WriteAsPacked(); @@ -14047,16 +14012,16 @@ void Unit::SendThreatListUpdate() void Unit::SendChangeCurrentVictimOpcode(HostileReference* pHostileReference) { - if (!getThreatManager().isThreatListEmpty()) + if (!GetThreatManager().isThreatListEmpty()) { - uint32 count = getThreatManager().getThreatList().size(); + uint32 count = GetThreatManager().getThreatList().size(); TC_LOG_DEBUG("entities.unit", "WORLD: Send SMSG_HIGHEST_THREAT_UPDATE Message"); WorldPacket data(SMSG_HIGHEST_THREAT_UPDATE, 8 + 8 + count * 8); data << GetPackGUID(); data << pHostileReference->getUnitGuid().WriteAsPacked(); data << uint32(count); - ThreatContainer::StorageType const& tlist = getThreatManager().getThreatList(); + ThreatContainer::StorageType const& tlist = GetThreatManager().getThreatList(); for (ThreatContainer::StorageType::const_iterator itr = tlist.begin(); itr != tlist.end(); ++itr) { data << (*itr)->getUnitGuid().WriteAsPacked(); diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index 73ca8add151..1a32512a01b 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -1000,6 +1000,7 @@ class TC_GAME_API Unit : public WorldObject bool IsWithinCombatRange(Unit const* obj, float dist2compare) const; bool IsWithinMeleeRange(Unit const* obj) const; float GetMeleeRange(Unit const* target) const; + virtual SpellSchoolMask GetMeleeDamageSchoolMask() const; void GetRandomContactPoint(Unit const* target, float& x, float& y, float& z, float distance2dMin, float distance2dMax) const; uint32 m_extraAttacks; bool m_canDualWield; @@ -1222,6 +1223,12 @@ 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); } + bool IsInCombat() const { return HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IN_COMBAT); } bool IsPetInCombat() const { return HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT); } bool IsInCombatWith(Unit const* who) const; @@ -1259,8 +1266,9 @@ class TC_GAME_API Unit : public WorldObject void SendHealSpellLog(HealInfo& healInfo, bool critical = false); int32 HealBySpell(HealInfo& healInfo, bool critical = false); - void SendEnergizeSpellLog(Unit* victim, uint32 spellID, int32 damage, Powers powerType); - void EnergizeBySpell(Unit* victim, uint32 SpellID, int32 Damage, Powers powertype); + void SendEnergizeSpellLog(Unit* victim, uint32 spellId, int32 damage, Powers powerType); + void EnergizeBySpell(Unit* victim, uint32 spellId, int32 damage, Powers powerType); + void EnergizeBySpell(Unit* victim, SpellInfo const* spellInfo, int32 damage, Powers powerType); void CastSpell(SpellCastTargets const& targets, SpellInfo const* spellInfo, CustomSpellValues const* value, TriggerCastFlags triggerFlags = TRIGGERED_NONE, Item* castItem = nullptr, AuraEffect const* triggeredByAura = nullptr, ObjectGuid originalCaster = ObjectGuid::Empty); void CastSpell(Unit* victim, uint32 spellId, bool triggered, Item* castItem = nullptr, AuraEffect const* triggeredByAura = nullptr, ObjectGuid originalCaster = ObjectGuid::Empty); @@ -1661,12 +1669,11 @@ class TC_GAME_API Unit : public WorldObject // Threat related methods bool CanHaveThreatList(bool skipAliveCheck = false) const; - void AddThreat(Unit* victim, float fThreat, SpellSchoolMask schoolMask = SPELL_SCHOOL_MASK_NORMAL, SpellInfo const* threatSpell = nullptr); float ApplyTotalThreatModifier(float fThreat, SpellSchoolMask schoolMask = SPELL_SCHOOL_MASK_NORMAL); - void DeleteThreatList(); void TauntApply(Unit* victim); void TauntFadeOut(Unit* taunter); - ThreatManager& getThreatManager() { return m_ThreatManager; } + 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; } @@ -1974,8 +1981,6 @@ class TC_GAME_API Unit : public WorldObject CharmInfo* m_charmInfo; SharedVisionList m_sharedVision; - virtual SpellSchoolMask GetMeleeDamageSchoolMask() const; - MotionMaster* i_motionMaster; uint32 m_reactiveTimer[MAX_REACTIVE]; diff --git a/src/server/game/Grids/ObjectGridLoader.cpp b/src/server/game/Grids/ObjectGridLoader.cpp index 848df6f60dc..653c9d51d11 100644 --- a/src/server/game/Grids/ObjectGridLoader.cpp +++ b/src/server/game/Grids/ObjectGridLoader.cpp @@ -217,10 +217,10 @@ void ObjectGridStoper::Visit(CreatureMapType &m) for (CreatureMapType::iterator iter = m.begin(); iter != m.end(); ++iter) { iter->GetSource()->RemoveAllDynObjects(); - if (iter->GetSource()->IsInCombat() || !iter->GetSource()->getThreatManager().areThreatListsEmpty()) + if (iter->GetSource()->IsInCombat() || !iter->GetSource()->GetThreatManager().areThreatListsEmpty()) { iter->GetSource()->CombatStop(); - iter->GetSource()->DeleteThreatList(); + iter->GetSource()->GetThreatManager().ClearAllThreat(); iter->GetSource()->AI()->EnterEvadeMode(); } } diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp index eedbd4f90b9..86713a4efd6 100644 --- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp +++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp @@ -39,6 +39,7 @@ #include "Spell.h" #include "SpellHistory.h" #include "SpellMgr.h" +#include "ThreatManager.h" #include "Unit.h" #include "Util.h" #include "Vehicle.h" @@ -4371,7 +4372,7 @@ void AuraEffect::HandleAuraDummy(AuraApplication const* aurApp, uint8 mode, bool case 1515: // Tame beast // FIX_ME: this is 2.0.12 threat effect replaced in 2.1.x by dummy aura, must be checked for correctness if (caster && target->CanHaveThreatList()) - target->AddThreat(caster, 10.0f); + target->GetThreatManager().AddThreat(caster, 10.0f); break; case 13139: // net-o-matic // root to self part of (root_target->charge->root_self sequence @@ -5775,7 +5776,7 @@ void AuraEffect::HandlePeriodicHealthLeechAuraTick(Unit* target, Unit* caster) c HealInfo healInfo(caster, caster, heal, GetSpellInfo(), GetSpellInfo()->GetSchoolMask()); caster->HealBySpell(healInfo); - caster->getHostileRefManager().threatAssist(caster, healInfo.GetEffectiveHeal() * 0.5f, GetSpellInfo()); + caster->GetThreatManager().ForwardThreatForAssistingMe(caster, healInfo.GetEffectiveHeal()*0.5f, GetSpellInfo()); caster->ProcSkillsAndAuras(caster, PROC_FLAG_DONE_PERIODIC, PROC_FLAG_TAKEN_PERIODIC, PROC_SPELL_TYPE_HEAL, PROC_SPELL_PHASE_NONE, hitMask, nullptr, nullptr, &healInfo); } } @@ -5901,7 +5902,7 @@ void AuraEffect::HandlePeriodicHealAurasTick(Unit* target, Unit* caster) const SpellPeriodicAuraLogInfo pInfo(this, heal, heal - healInfo.GetEffectiveHeal(), healInfo.GetAbsorb(), 0, 0.0f, crit); target->SendPeriodicAuraLog(&pInfo); - target->getHostileRefManager().threatAssist(caster, float(healInfo.GetEffectiveHeal()) * 0.5f, GetSpellInfo()); + target->GetThreatManager().ForwardThreatForAssistingMe(caster, float(healInfo.GetEffectiveHeal())*0.5f, GetSpellInfo()); bool haveCastItem = !GetBase()->GetCastItemGUID().IsEmpty(); @@ -5984,7 +5985,8 @@ void AuraEffect::HandlePeriodicManaLeechAuraTick(Unit* target, Unit* caster) con if (gainAmount) { gainedAmount = caster->ModifyPower(powerType, gainAmount); - target->AddThreat(caster, float(gainedAmount) * 0.5f, GetSpellInfo()->GetSchoolMask(), GetSpellInfo()); + // energize is not modified by threat modifiers + target->GetThreatManager().AddThreat(caster, float(gainedAmount) * 0.5f, GetSpellInfo(), true); } // Drain Mana @@ -6035,7 +6037,7 @@ void AuraEffect::HandleObsModPowerAuraTick(Unit* target, Unit* caster) const int32 gain = target->ModifyPower(powerType, amount); if (caster) - target->getHostileRefManager().threatAssist(caster, float(gain) * 0.5f, GetSpellInfo()); + target->GetThreatManager().ForwardThreatForAssistingMe(caster, float(gain)*0.5f, GetSpellInfo(), true); } void AuraEffect::HandlePeriodicEnergizeAuraTick(Unit* target, Unit* caster) const @@ -6070,7 +6072,7 @@ void AuraEffect::HandlePeriodicEnergizeAuraTick(Unit* target, Unit* caster) cons int32 gain = target->ModifyPower(powerType, amount); if (caster) - target->getHostileRefManager().threatAssist(caster, float(gain) * 0.5f, GetSpellInfo()); + target->GetThreatManager().ForwardThreatForAssistingMe(caster, float(gain)*0.5f, GetSpellInfo(), true); } void AuraEffect::HandlePeriodicPowerBurnAuraTick(Unit* target, Unit* caster) const diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 8309bf00fd3..d9990dfb033 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -2385,7 +2385,7 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target) HealInfo healInfo(caster, unitTarget, addhealth, m_spellInfo, m_spellInfo->GetSchoolMask()); caster->HealBySpell(healInfo, crit); - unitTarget->getHostileRefManager().threatAssist(caster, float(healInfo.GetEffectiveHeal()) * 0.5f, m_spellInfo); + unitTarget->GetThreatManager().ForwardThreatForAssistingMe(caster, float(healInfo.GetEffectiveHeal())*0.5f, m_spellInfo); m_healing = healInfo.GetEffectiveHeal(); // Do triggers for unit @@ -2454,8 +2454,7 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target) if (missInfo == SPELL_MISS_RESIST && m_spellInfo->HasAttribute(SPELL_ATTR0_CU_PICKPOCKET) && unitTarget->GetTypeId() == TYPEID_UNIT) { m_caster->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TALK); - if (unitTarget->ToCreature()->IsAIEnabled) - unitTarget->ToCreature()->AI()->AttackStart(m_caster); + unitTarget->ToCreature()->EngageWithTarget(m_caster); } } @@ -2558,14 +2557,16 @@ SpellMissInfo Spell::DoSpellHitOnUnit(Unit* unit, uint32 effectMask, bool scaleA // assisting case, healing and resurrection if (unit->HasUnitState(UNIT_STATE_ATTACK_PLAYER)) { - m_caster->SetContestedPvP(); - if (m_caster->GetTypeId() == TYPEID_PLAYER) - m_caster->ToPlayer()->UpdatePvP(true); + if (Player* playerOwner = m_caster->GetCharmerOrOwnerPlayerOrPlayerItself()) + { + playerOwner->SetContestedPvP(); + playerOwner->UpdatePvP(true); + } } if (unit->IsInCombat() && m_spellInfo->HasInitialAggro()) { m_caster->SetInCombatState(unit->GetCombatTimer() > 0, unit); - unit->getHostileRefManager().threatAssist(m_caster, 0.0f); + unit->GetThreatManager().ForwardThreatForAssistingMe(m_caster, 0.0f, nullptr, true); } } } @@ -4786,14 +4787,14 @@ void Spell::HandleThreatSpells() // positive spells distribute threat among all units that are in combat with target, like healing if (m_spellInfo->IsPositive()) - target->getHostileRefManager().threatAssist(m_caster, threatToAdd, m_spellInfo); + target->GetThreatManager().ForwardThreatForAssistingMe(m_caster, threatToAdd, m_spellInfo); // for negative spells threat gets distributed among affected targets else { if (!target->CanHaveThreatList()) continue; - target->AddThreat(m_caster, threatToAdd, m_spellInfo->GetSchoolMask(), m_spellInfo); + target->GetThreatManager().AddThreat(m_caster, threatToAdd, m_spellInfo, true); } } TC_LOG_DEBUG("spells", "Spell %u, added an additional %f threat for %s %u target(s)", m_spellInfo->Id, threat, m_spellInfo->IsPositive() ? "assisting" : "harming", uint32(m_UniqueTargetInfo.size())); diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index 2201e39059b..72a39a6073b 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -2476,7 +2476,7 @@ void Spell::EffectDistract(SpellEffIndex /*effIndex*/) return; // Check for possible target - if (!unitTarget || unitTarget->IsInCombat()) + if (!unitTarget || unitTarget->IsEngaged()) return; // target must be OK to do this @@ -3074,17 +3074,17 @@ void Spell::EffectTaunt(SpellEffIndex /*effIndex*/) if (m_spellInfo->Id == 62124 && (!unitTarget->IsPet() || !unitTarget->GetOwnerGUID().IsPlayer())) m_caster->CastSpell(unitTarget, 67485, true); - if (!unitTarget->getThreatManager().getOnlineContainer().empty()) + if (!unitTarget->GetThreatManager().getOnlineContainer().empty()) { // 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(); + float myThreat = unitTarget->GetThreatManager().getThreat(m_caster); + float topThreat = unitTarget->GetThreatManager().getOnlineContainer().getMostHated()->getThreat(); if (topThreat > myThreat) - unitTarget->getThreatManager().doAddThreat(m_caster, 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); + if (HostileReference* forcedVictim = unitTarget->GetThreatManager().getOnlineContainer().getReferenceByTarget(m_caster)) + unitTarget->GetThreatManager().setCurrentVictim(forcedVictim); } if (unitTarget->ToCreature()->IsAIEnabled && !unitTarget->ToCreature()->HasReactState(REACT_PASSIVE)) @@ -3385,7 +3385,7 @@ void Spell::EffectThreat(SpellEffIndex /*effIndex*/) if (!unitTarget->CanHaveThreatList()) return; - unitTarget->AddThreat(m_caster, float(damage)); + unitTarget->GetThreatManager().AddThreat(m_caster, float(damage)); } void Spell::EffectHealMaxHealth(SpellEffIndex /*effIndex*/) @@ -4990,7 +4990,7 @@ void Spell::EffectModifyThreatPercent(SpellEffIndex /*effIndex*/) if (!unitTarget) return; - unitTarget->getThreatManager().modifyThreatPercent(m_caster, damage); + unitTarget->GetThreatManager().ModifyThreatByPercent(m_caster, damage); } void Spell::EffectTransmitted(SpellEffIndex effIndex) |
