From 2069445a97cc6b1d57ad24750c4fb2c01d32d780 Mon Sep 17 00:00:00 2001 From: Gultask <100873791+Gultask@users.noreply.github.com> Date: Thu, 22 Jan 2026 00:52:18 -0300 Subject: [PATCH] fix(Core/Threat): Creatures should target the closest unit when threat is tied between players (#24227) --- src/server/game/Combat/ThreatMgr.cpp | 69 +++++++++++++++++++++++++++- src/server/game/Combat/ThreatMgr.h | 4 ++ 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/server/game/Combat/ThreatMgr.cpp b/src/server/game/Combat/ThreatMgr.cpp index a421b3ac9..59da5a32a 100644 --- a/src/server/game/Combat/ThreatMgr.cpp +++ b/src/server/game/Combat/ThreatMgr.cpp @@ -328,7 +328,7 @@ HostileReference* ThreatContainer::SelectNextVictim(Creature* attacker, HostileR Unit* cvUnit = currentVictim->getTarget(); if (!attacker->CanCreatureAttack(cvUnit)) // pussywizard: if currentVictim is not valid => don't compare the threat with it, just take the highest threat valid target currentVictim = nullptr; - else if (cvUnit->IsImmunedToDamageOrSchool(attacker->GetMeleeDamageSchoolMask()) || cvUnit->HasNegativeAuraWithInterruptFlag(AURA_INTERRUPT_FLAG_TAKE_DAMAGE) || cvUnit->HasUnitState(UNIT_STATE_CONFUSED)) // pussywizard: no 10%/30% if currentVictim is immune to damage or has auras breakable by damage + else if (!IsPreferredTarget(attacker, cvUnit)) // pussywizard: no 10%/30% if currentVictim is immune to damage or has auras breakable by damage currentVictim = nullptr; } @@ -345,7 +345,7 @@ HostileReference* ThreatContainer::SelectNextVictim(Creature* attacker, HostileR // pussywizard: don't go to threat comparison if this ref is immune to damage or has aura breakable on damage (second choice target) // pussywizard: if this is the last entry on the threat list, then all targets are second choice, set bool to true and loop threat list again, ignoring this section - if (!noPriorityTargetFound && (target->IsImmunedToDamageOrSchool(attacker->GetMeleeDamageSchoolMask()) || target->HasNegativeAuraWithInterruptFlag(AURA_INTERRUPT_FLAG_TAKE_DAMAGE) || target->HasUnitState(UNIT_STATE_CONFUSED) || target->HasAuraTypeWithCaster(SPELL_AURA_IGNORED, attacker->GetGUID()))) + if (!noPriorityTargetFound && !IsPreferredTarget(attacker, target)) { if (iter != lastRef) { @@ -394,6 +394,8 @@ HostileReference* ThreatContainer::SelectNextVictim(Creature* attacker, HostileR } else // pussywizard: no currentVictim, first passing all checks is chosen (highest threat, list is sorted) { + // tie-breaker if multiple targets have the same threat: select closest target + currentRef = SelectNextVictimTieBreaker(attacker, iter, noPriorityTargetFound); found = true; break; } @@ -406,6 +408,69 @@ HostileReference* ThreatContainer::SelectNextVictim(Creature* attacker, HostileR return currentRef; } +// Helper for Tie-breakers +HostileReference* ThreatContainer::SelectNextVictimTieBreaker(Creature* attacker, ThreatContainer::StorageType::const_iterator currentIter, bool noPriorityTargetFound) const +{ + HostileReference* bestRef = *currentIter; + float bestThreat = bestRef->GetThreat(); + float shortestDistSq = attacker->GetExactDistSq(bestRef->getTarget()); + + auto tieIter = std::next(currentIter); + + while (tieIter != iThreatList.end()) + { + HostileReference* nextRef = *tieIter; + + if (!nextRef) + { + ++tieIter; + continue; + } + + // Threatlist is sorted, so we can stop as soon as we find a lower threat + if (bestThreat - nextRef->GetThreat() > 0.01f) + break; + + Unit* target = nextRef->getTarget(); + + if (!target) + { + ++tieIter; + continue; + } + + if (attacker->CanCreatureAttack(target) && (noPriorityTargetFound || IsPreferredTarget(attacker, target))) + { + float distSq = attacker->GetExactDistSq(target); + if (distSq < shortestDistSq) + { + bestRef = nextRef; + shortestDistSq = distSq; + } + } + ++tieIter; + } + return bestRef; +} + +// Helper for checking if a target is preferred (not immune, not confused, etc) +bool ThreatContainer::IsPreferredTarget(Creature* attacker, Unit* target) const +{ + if (target->IsImmunedToDamageOrSchool(attacker->GetMeleeDamageSchoolMask())) + return false; + + if (target->HasNegativeAuraWithInterruptFlag(AURA_INTERRUPT_FLAG_TAKE_DAMAGE)) + return false; + + if (target->HasUnitState(UNIT_STATE_CONFUSED)) + return false; + + if (target->HasAuraTypeWithCaster(SPELL_AURA_IGNORED, attacker->GetGUID())) + return false; + + return true; +} + //============================================================ //=================== ThreatMgr ========================== //============================================================ diff --git a/src/server/game/Combat/ThreatMgr.h b/src/server/game/Combat/ThreatMgr.h index 3f31c9ac8..105c405a9 100644 --- a/src/server/game/Combat/ThreatMgr.h +++ b/src/server/game/Combat/ThreatMgr.h @@ -156,6 +156,10 @@ public: HostileReference* SelectNextVictim(Creature* attacker, HostileReference* currentVictim) const; + HostileReference* SelectNextVictimTieBreaker(Creature* attacker, ThreatContainer::StorageType::const_iterator currentIter, bool noPriorityTargetFound) const; + + bool IsPreferredTarget(Creature* attacker, Unit* target) const; + void setDirty(bool isDirty) { iDirty = isDirty; } [[nodiscard]] bool isDirty() const { return iDirty; }