mirror of
https://github.com/liyunfan1223/azerothcore-wotlk.git
synced 2026-08-08 15:58:04 +00:00
fix(Core/Combat): Immediately reselect victim after taunt aura update (#25254)
Co-authored-by: blinkysc <blinkysc@users.noreply.github.com>
This commit is contained in:
@@ -540,6 +540,11 @@ void ThreatManager::TauntUpdate()
|
|||||||
|
|
||||||
// taunt aura update also re-evaluates all suppressed states (retail behavior)
|
// taunt aura update also re-evaluates all suppressed states (retail behavior)
|
||||||
EvaluateSuppressed(true);
|
EvaluateSuppressed(true);
|
||||||
|
|
||||||
|
// immediately reselect victim so taunt takes effect without waiting
|
||||||
|
// for the next THREAT_UPDATE_INTERVAL (1 s) timer tick
|
||||||
|
UpdateVictim();
|
||||||
|
_updateTimer = THREAT_UPDATE_INTERVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThreatManager::SetTauntStateForTesting(
|
void ThreatManager::SetTauntStateForTesting(
|
||||||
|
|||||||
@@ -182,6 +182,8 @@ public:
|
|||||||
// Test-only: directly set taunt state on a threat ref
|
// Test-only: directly set taunt state on a threat ref
|
||||||
// Uses uint32 to avoid incomplete-type issue with ThreatReference
|
// Uses uint32 to avoid incomplete-type issue with ThreatReference
|
||||||
void SetTauntStateForTesting(Unit* target, uint32 state);
|
void SetTauntStateForTesting(Unit* target, uint32 state);
|
||||||
|
// Immediately reselect victim (bypasses 1-second Update timer)
|
||||||
|
void UpdateVictimForTesting() { UpdateVictim(); }
|
||||||
|
|
||||||
///== REDIRECT SYSTEM ==
|
///== REDIRECT SYSTEM ==
|
||||||
void RegisterRedirectThreat(uint32 spellId, ObjectGuid const& victim, uint32 pct);
|
void RegisterRedirectThreat(uint32 spellId, ObjectGuid const& victim, uint32 pct);
|
||||||
|
|||||||
@@ -2313,4 +2313,247 @@ TEST_F(ThreatManagerIntegrationTest,
|
|||||||
delete creatureC;
|
delete creatureC;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// TauntUpdate immediate victim reselection tests
|
||||||
|
// Verify that TauntUpdate() calls UpdateVictim() immediately (no 1-second
|
||||||
|
// delay), and that rapid/repeated calls do not cause recursion or crashes.
|
||||||
|
//
|
||||||
|
// Note: TauntUpdate() reads real auras via GetAuraEffectsByType(), which are
|
||||||
|
// not present in unit tests. Tests that need taunt state to persist use
|
||||||
|
// SetTauntStateForTesting() + UpdateVictim() to exercise the same code path
|
||||||
|
// (ReselectVictim → ProcessAIUpdates) that TauntUpdate now invokes.
|
||||||
|
// TauntUpdate()-specific tests verify no-crash / no-recursion behaviour.
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
TEST_F(ThreatManagerIntegrationTest,
|
||||||
|
UpdateVictim_ImmediateReselectAfterTauntState)
|
||||||
|
{
|
||||||
|
TestCreature* creatureC = new TestCreature();
|
||||||
|
creatureC->SetupForCombatTest(_map, 3, 12347);
|
||||||
|
creatureC->SetFaction(90002);
|
||||||
|
|
||||||
|
// B has low threat, C has high threat
|
||||||
|
_creatureA->TestGetThreatMgr().AddThreat(_creatureB, 100.0f);
|
||||||
|
_creatureA->TestGetThreatMgr().AddThreat(creatureC, 500.0f);
|
||||||
|
_creatureA->TestGetThreatMgr().Update(
|
||||||
|
ThreatManager::THREAT_UPDATE_INTERVAL);
|
||||||
|
EXPECT_EQ(
|
||||||
|
_creatureA->TestGetThreatMgr().GetCurrentVictim(),
|
||||||
|
creatureC);
|
||||||
|
|
||||||
|
// Set taunt on B, then call UpdateVictim directly (the call that
|
||||||
|
// TauntUpdate now makes internally) — WITHOUT waiting for the
|
||||||
|
// 1-second Update() timer
|
||||||
|
_creatureA->TestGetThreatMgr().SetTauntStateForTesting(
|
||||||
|
_creatureB, ThreatReference::TAUNT_STATE_TAUNT);
|
||||||
|
_creatureA->TestGetThreatMgr().UpdateVictimForTesting();
|
||||||
|
|
||||||
|
// Victim should switch immediately to B (taunted)
|
||||||
|
EXPECT_EQ(
|
||||||
|
_creatureA->TestGetThreatMgr().GetCurrentVictim(),
|
||||||
|
_creatureB);
|
||||||
|
|
||||||
|
creatureC->CleanupCombatState();
|
||||||
|
delete creatureC;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ThreatManagerIntegrationTest,
|
||||||
|
UpdateVictim_Idempotent_MultipleCalls)
|
||||||
|
{
|
||||||
|
TestCreature* creatureC = new TestCreature();
|
||||||
|
creatureC->SetupForCombatTest(_map, 3, 12347);
|
||||||
|
creatureC->SetFaction(90002);
|
||||||
|
|
||||||
|
_creatureA->TestGetThreatMgr().AddThreat(_creatureB, 100.0f);
|
||||||
|
_creatureA->TestGetThreatMgr().AddThreat(creatureC, 500.0f);
|
||||||
|
_creatureA->TestGetThreatMgr().Update(
|
||||||
|
ThreatManager::THREAT_UPDATE_INTERVAL);
|
||||||
|
|
||||||
|
_creatureA->TestGetThreatMgr().SetTauntStateForTesting(
|
||||||
|
_creatureB, ThreatReference::TAUNT_STATE_TAUNT);
|
||||||
|
|
||||||
|
// Call UpdateVictim many times in a row — must be idempotent
|
||||||
|
for (int i = 0; i < 50; ++i)
|
||||||
|
_creatureA->TestGetThreatMgr().UpdateVictimForTesting();
|
||||||
|
|
||||||
|
EXPECT_EQ(
|
||||||
|
_creatureA->TestGetThreatMgr().GetCurrentVictim(),
|
||||||
|
_creatureB);
|
||||||
|
|
||||||
|
creatureC->CleanupCombatState();
|
||||||
|
delete creatureC;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ThreatManagerIntegrationTest,
|
||||||
|
UpdateVictim_RapidTauntToggle_NoCrash)
|
||||||
|
{
|
||||||
|
TestCreature* creatureC = new TestCreature();
|
||||||
|
creatureC->SetupForCombatTest(_map, 3, 12347);
|
||||||
|
creatureC->SetFaction(90002);
|
||||||
|
|
||||||
|
_creatureA->TestGetThreatMgr().AddThreat(_creatureB, 100.0f);
|
||||||
|
_creatureA->TestGetThreatMgr().AddThreat(creatureC, 500.0f);
|
||||||
|
_creatureA->TestGetThreatMgr().Update(
|
||||||
|
ThreatManager::THREAT_UPDATE_INTERVAL);
|
||||||
|
|
||||||
|
// Rapidly toggle taunt state with immediate UpdateVictim each time
|
||||||
|
for (int i = 0; i < 50; ++i)
|
||||||
|
{
|
||||||
|
_creatureA->TestGetThreatMgr().SetTauntStateForTesting(
|
||||||
|
_creatureB, ThreatReference::TAUNT_STATE_TAUNT);
|
||||||
|
_creatureA->TestGetThreatMgr().UpdateVictimForTesting();
|
||||||
|
EXPECT_EQ(
|
||||||
|
_creatureA->TestGetThreatMgr().GetCurrentVictim(),
|
||||||
|
_creatureB);
|
||||||
|
|
||||||
|
_creatureA->TestGetThreatMgr().SetTauntStateForTesting(
|
||||||
|
_creatureB, ThreatReference::TAUNT_STATE_NONE);
|
||||||
|
_creatureA->TestGetThreatMgr().UpdateVictimForTesting();
|
||||||
|
EXPECT_EQ(
|
||||||
|
_creatureA->TestGetThreatMgr().GetCurrentVictim(),
|
||||||
|
creatureC);
|
||||||
|
}
|
||||||
|
|
||||||
|
creatureC->CleanupCombatState();
|
||||||
|
delete creatureC;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ThreatManagerIntegrationTest,
|
||||||
|
UpdateVictim_WithConcurrentThreatChanges_NoCrash)
|
||||||
|
{
|
||||||
|
TestCreature* creatureC = new TestCreature();
|
||||||
|
creatureC->SetupForCombatTest(_map, 3, 12347);
|
||||||
|
creatureC->SetFaction(90002);
|
||||||
|
|
||||||
|
_creatureA->TestGetThreatMgr().AddThreat(_creatureB, 100.0f);
|
||||||
|
_creatureA->TestGetThreatMgr().AddThreat(creatureC, 500.0f);
|
||||||
|
_creatureA->TestGetThreatMgr().Update(
|
||||||
|
ThreatManager::THREAT_UPDATE_INTERVAL);
|
||||||
|
|
||||||
|
// Simulate taunt landing while threat is also being modified
|
||||||
|
// (e.g. damage events arriving in the same server tick)
|
||||||
|
_creatureA->TestGetThreatMgr().SetTauntStateForTesting(
|
||||||
|
_creatureB, ThreatReference::TAUNT_STATE_TAUNT);
|
||||||
|
_creatureA->TestGetThreatMgr().AddThreat(creatureC, 200.0f);
|
||||||
|
_creatureA->TestGetThreatMgr().UpdateVictimForTesting();
|
||||||
|
|
||||||
|
// B should be victim because taunt overrides threat
|
||||||
|
EXPECT_EQ(
|
||||||
|
_creatureA->TestGetThreatMgr().GetCurrentVictim(),
|
||||||
|
_creatureB);
|
||||||
|
|
||||||
|
creatureC->CleanupCombatState();
|
||||||
|
delete creatureC;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ThreatManagerIntegrationTest,
|
||||||
|
UpdateVictim_MultipleTaunters_HighestStateWins)
|
||||||
|
{
|
||||||
|
TestCreature* creatureC = new TestCreature();
|
||||||
|
creatureC->SetupForCombatTest(_map, 3, 12347);
|
||||||
|
creatureC->SetFaction(90002);
|
||||||
|
|
||||||
|
TestCreature* creatureD = new TestCreature();
|
||||||
|
creatureD->SetupForCombatTest(_map, 4, 12348);
|
||||||
|
creatureD->SetFaction(90002);
|
||||||
|
|
||||||
|
_creatureA->TestGetThreatMgr().AddThreat(_creatureB, 100.0f);
|
||||||
|
_creatureA->TestGetThreatMgr().AddThreat(creatureC, 200.0f);
|
||||||
|
_creatureA->TestGetThreatMgr().AddThreat(creatureD, 300.0f);
|
||||||
|
_creatureA->TestGetThreatMgr().Update(
|
||||||
|
ThreatManager::THREAT_UPDATE_INTERVAL);
|
||||||
|
EXPECT_EQ(
|
||||||
|
_creatureA->TestGetThreatMgr().GetCurrentVictim(),
|
||||||
|
creatureD);
|
||||||
|
|
||||||
|
// Two taunters: B (state=TAUNT) and C (state=TAUNT+1, i.e. "last taunt")
|
||||||
|
_creatureA->TestGetThreatMgr().SetTauntStateForTesting(
|
||||||
|
_creatureB, ThreatReference::TAUNT_STATE_TAUNT);
|
||||||
|
_creatureA->TestGetThreatMgr().SetTauntStateForTesting(
|
||||||
|
creatureC, static_cast<ThreatReference::TauntState>(
|
||||||
|
ThreatReference::TAUNT_STATE_TAUNT + 1));
|
||||||
|
_creatureA->TestGetThreatMgr().UpdateVictimForTesting();
|
||||||
|
|
||||||
|
// C has the higher taunt state — should be victim immediately
|
||||||
|
EXPECT_EQ(
|
||||||
|
_creatureA->TestGetThreatMgr().GetCurrentVictim(),
|
||||||
|
creatureC);
|
||||||
|
|
||||||
|
creatureC->CleanupCombatState();
|
||||||
|
creatureD->CleanupCombatState();
|
||||||
|
delete creatureC;
|
||||||
|
delete creatureD;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ThreatManagerIntegrationTest,
|
||||||
|
UpdateVictim_FixateStillTakesPrecedence)
|
||||||
|
{
|
||||||
|
TestCreature* creatureC = new TestCreature();
|
||||||
|
creatureC->SetupForCombatTest(_map, 3, 12347);
|
||||||
|
creatureC->SetFaction(90002);
|
||||||
|
|
||||||
|
_creatureA->TestGetThreatMgr().AddThreat(_creatureB, 100.0f);
|
||||||
|
_creatureA->TestGetThreatMgr().AddThreat(creatureC, 200.0f);
|
||||||
|
|
||||||
|
// Fixate on C
|
||||||
|
_creatureA->TestGetThreatMgr().FixateTarget(creatureC);
|
||||||
|
_creatureA->TestGetThreatMgr().Update(
|
||||||
|
ThreatManager::THREAT_UPDATE_INTERVAL);
|
||||||
|
EXPECT_EQ(
|
||||||
|
_creatureA->TestGetThreatMgr().GetCurrentVictim(),
|
||||||
|
creatureC);
|
||||||
|
|
||||||
|
// Taunt from B + immediate UpdateVictim — fixate should still win
|
||||||
|
_creatureA->TestGetThreatMgr().SetTauntStateForTesting(
|
||||||
|
_creatureB, ThreatReference::TAUNT_STATE_TAUNT);
|
||||||
|
_creatureA->TestGetThreatMgr().UpdateVictimForTesting();
|
||||||
|
|
||||||
|
EXPECT_EQ(
|
||||||
|
_creatureA->TestGetThreatMgr().GetCurrentVictim(),
|
||||||
|
creatureC);
|
||||||
|
EXPECT_EQ(
|
||||||
|
_creatureA->TestGetThreatMgr().GetFixateTarget(),
|
||||||
|
creatureC);
|
||||||
|
|
||||||
|
creatureC->CleanupCombatState();
|
||||||
|
delete creatureC;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ThreatManagerIntegrationTest,
|
||||||
|
TauntUpdate_EmptyThreatList_NoCrash)
|
||||||
|
{
|
||||||
|
// TauntUpdate on a creature with no threats should not crash
|
||||||
|
// even though UpdateVictim is now called internally
|
||||||
|
_creatureA->TestGetThreatMgr().TauntUpdate();
|
||||||
|
EXPECT_EQ(
|
||||||
|
_creatureA->TestGetThreatMgr().GetCurrentVictim(),
|
||||||
|
nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ThreatManagerIntegrationTest,
|
||||||
|
TauntUpdate_RapidCalls_NoCrash)
|
||||||
|
{
|
||||||
|
TestCreature* creatureC = new TestCreature();
|
||||||
|
creatureC->SetupForCombatTest(_map, 3, 12347);
|
||||||
|
creatureC->SetFaction(90002);
|
||||||
|
|
||||||
|
_creatureA->TestGetThreatMgr().AddThreat(_creatureB, 100.0f);
|
||||||
|
_creatureA->TestGetThreatMgr().AddThreat(creatureC, 500.0f);
|
||||||
|
_creatureA->TestGetThreatMgr().Update(
|
||||||
|
ThreatManager::THREAT_UPDATE_INTERVAL);
|
||||||
|
|
||||||
|
// Rapidly call TauntUpdate (no real auras — clears taunt state each
|
||||||
|
// time and calls UpdateVictim). Must not crash or recurse.
|
||||||
|
for (int i = 0; i < 50; ++i)
|
||||||
|
_creatureA->TestGetThreatMgr().TauntUpdate();
|
||||||
|
|
||||||
|
// C remains victim (highest threat, no taunt active)
|
||||||
|
EXPECT_EQ(
|
||||||
|
_creatureA->TestGetThreatMgr().GetCurrentVictim(),
|
||||||
|
creatureC);
|
||||||
|
|
||||||
|
creatureC->CleanupCombatState();
|
||||||
|
delete creatureC;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
Reference in New Issue
Block a user