From 4a4de647cb9648573f3608ac59db2a39fc387e65 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Sun, 14 Jun 2026 17:51:09 -0500 Subject: [PATCH] fix(Core/Combat): let asymmetric-faction aggressors engage friendly-back targets (#26194) Co-authored-by: blinkysc --- src/server/game/Combat/CombatManager.cpp | 3 +- .../server/game/Combat/CombatManagerTest.cpp | 43 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/server/game/Combat/CombatManager.cpp b/src/server/game/Combat/CombatManager.cpp index 60bcd77be4..6883067a8b 100644 --- a/src/server/game/Combat/CombatManager.cpp +++ b/src/server/game/Combat/CombatManager.cpp @@ -59,7 +59,8 @@ // ... both units must be allowed to enter combat if (a->IsCombatDisallowed() || b->IsCombatDisallowed()) return false; - if (a->IsFriendlyTo(b) || b->IsFriendlyTo(a)) + // ...not friendly, unless one side is hostile (asymmetric aggressor wins) + if ((a->IsFriendlyTo(b) || b->IsFriendlyTo(a)) && !a->IsHostileTo(b) && !b->IsHostileTo(a)) return false; Player const* playerA = a->GetCharmerOrOwnerPlayerOrPlayerItself(); Player const* playerB = b->GetCharmerOrOwnerPlayerOrPlayerItself(); diff --git a/src/test/server/game/Combat/CombatManagerTest.cpp b/src/test/server/game/Combat/CombatManagerTest.cpp index f060a83d6a..5900ee5e8b 100644 --- a/src/test/server/game/Combat/CombatManagerTest.cpp +++ b/src/test/server/game/Combat/CombatManagerTest.cpp @@ -1103,6 +1103,49 @@ TEST_F(CombatManagerIntegrationTest, CanBeginCombat_ValidUnits_Succeeds) EXPECT_TRUE(CombatManager::CanBeginCombat(_creatureA, _creatureB)); } +// Asymmetric factions: A is hostile to the target, but the target's faction still considers A +// friendly (e.g. Dragonblight Mage Hunters vs Moonrest Highborne). The aggressor's hostility must +// win so combat can begin. +TEST_F(CombatManagerIntegrationTest, CanBeginCombat_AsymmetricHostileVsFriendly_Succeeds) +{ + auto* factionC = new FactionTemplateEntry{}; + factionC->ID = 90003; + factionC->faction = 90003; + factionC->factionFlags = 0; + factionC->ourMask = 2; // A (hostileMask=2) is hostile to this group... + factionC->friendlyMask = 1; // ...but this faction considers A's group (ourMask=1) friendly + factionC->hostileMask = 0; + for (auto& e : factionC->enemyFaction) e = 0; + for (auto& f : factionC->friendFaction) f = 0; + sFactionTemplateStore.SetEntry(90003, factionC); + + _creatureB->SetFaction(90003); + ASSERT_TRUE(_creatureA->IsHostileTo(_creatureB)); + ASSERT_TRUE(_creatureB->IsFriendlyTo(_creatureA)); + EXPECT_TRUE(CombatManager::CanBeginCombat(_creatureA, _creatureB)); +} + +// One side friendly and neither side hostile: combat must still be blocked so neutral/friendly +// bystanders are not dragged into combat. +TEST_F(CombatManagerIntegrationTest, CanBeginCombat_FriendlyWithoutHostility_Fails) +{ + auto* factionD = new FactionTemplateEntry{}; + factionD->ID = 90004; + factionD->faction = 90004; + factionD->factionFlags = 0; + factionD->ourMask = 4; // A (hostileMask=2) is NOT hostile to this group... + factionD->friendlyMask = 1; // ...and this faction considers A's group friendly + factionD->hostileMask = 0; + for (auto& e : factionD->enemyFaction) e = 0; + for (auto& f : factionD->friendFaction) f = 0; + sFactionTemplateStore.SetEntry(90004, factionD); + + _creatureB->SetFaction(90004); + ASSERT_FALSE(_creatureA->IsHostileTo(_creatureB)); + ASSERT_FALSE(_creatureB->IsHostileTo(_creatureA)); + EXPECT_FALSE(CombatManager::CanBeginCombat(_creatureA, _creatureB)); +} + // ============================================================================ // GAP COVERAGE: CombatManager::IsInCombatWith (ObjectGuid variant) // ============================================================================