Fix Karazhan timers (#1926)

I'm not 100% sure but think that the current implementation of timers
for Attumen, Netherspite, and Nightbane with the instance's map id as
the key would result in the timers being shared across separate raids
running the instance at the same time. I've refactored to use the
instance id as the key for all shared timers so to the extent this was
an issue, it shouldn't be anymore.

I also made some minor tweaks to the tank positioning for BBW and
Curator as I noticed there was some weirdness in the logic that I
neglected to address with the refactor.
This commit is contained in:
Crow
2025-12-21 14:40:19 -06:00
committed by GitHub
parent 66f5f597bb
commit 467b63b840
5 changed files with 54 additions and 47 deletions

View File

@@ -44,7 +44,7 @@ bool AttumenTheHuntsmanMarkTargetAction::Execute(Event event)
Unit* attumenMounted = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED); Unit* attumenMounted = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED);
if (attumenMounted) if (attumenMounted)
{ {
if (IsMapIDTimerManager(botAI, bot)) if (IsInstanceTimerManager(botAI, bot))
MarkTargetWithStar(bot, attumenMounted); MarkTargetWithStar(bot, attumenMounted);
SetRtiTarget(botAI, "star", attumenMounted); SetRtiTarget(botAI, "star", attumenMounted);
@@ -57,7 +57,7 @@ bool AttumenTheHuntsmanMarkTargetAction::Execute(Event event)
} }
else if (Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight")) else if (Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight"))
{ {
if (IsMapIDTimerManager(botAI, bot)) if (IsInstanceTimerManager(botAI, bot))
MarkTargetWithStar(bot, midnight); MarkTargetWithStar(bot, midnight);
if (!botAI->IsAssistTankOfIndex(bot, 0)) if (!botAI->IsAssistTankOfIndex(bot, 0))
@@ -131,8 +131,10 @@ bool AttumenTheHuntsmanManageDpsTimerAction::Execute(Event event)
if (!midnight) if (!midnight)
return false; return false;
const uint32 instanceId = midnight->GetMap()->GetInstanceId();
if (midnight && midnight->GetHealth() == midnight->GetMaxHealth()) if (midnight && midnight->GetHealth() == midnight->GetMaxHealth())
attumenDpsWaitTimer.erase(KARAZHAN_MAP_ID); attumenDpsWaitTimer.erase(instanceId);
// Midnight is still present as a separate (invisible) unit after Attumen mounts // Midnight is still present as a separate (invisible) unit after Attumen mounts
// So this block can be reached // So this block can be reached
@@ -143,7 +145,7 @@ bool AttumenTheHuntsmanManageDpsTimerAction::Execute(Event event)
const time_t now = std::time(nullptr); const time_t now = std::time(nullptr);
if (attumenMounted) if (attumenMounted)
attumenDpsWaitTimer.try_emplace(KARAZHAN_MAP_ID, now); attumenDpsWaitTimer.try_emplace(instanceId, now);
return false; return false;
} }
@@ -178,7 +180,7 @@ bool MoroesMarkTargetAction::Execute(Event event)
if (target) if (target)
{ {
if (IsMapIDTimerManager(botAI, bot)) if (IsInstanceTimerManager(botAI, bot))
MarkTargetWithSkull(bot, target); MarkTargetWithSkull(bot, target);
SetRtiTarget(botAI, "skull", target); SetRtiTarget(botAI, "skull", target);
@@ -297,19 +299,18 @@ bool BigBadWolfPositionBossAction::Execute(Event event)
if (wolf->GetVictim() == bot) if (wolf->GetVictim() == bot)
{ {
const Position& position = BIG_BAD_WOLF_BOSS_POSITION; const Position& position = BIG_BAD_WOLF_BOSS_POSITION;
const float maxStep = 2.0f; float distanceToPosition = wolf->GetExactDist2d(position);
float dist = wolf->GetExactDist2d(position);
if (dist > 0.0f && dist > maxStep) if (distanceToPosition > 2.0f)
{ {
float dX = position.GetPositionX() - wolf->GetPositionX(); float dX = position.GetPositionX() - wolf->GetPositionX();
float dY = position.GetPositionY() - wolf->GetPositionY(); float dY = position.GetPositionY() - wolf->GetPositionY();
float moveDist = std::min(maxStep, dist); float moveDist = std::min(5.0f, distanceToPosition);
float moveX = wolf->GetPositionX() + (dX / dist) * moveDist; float moveX = wolf->GetPositionX() + (dX / distanceToPosition) * moveDist;
float moveY = wolf->GetPositionY() + (dY / dist) * moveDist; float moveY = wolf->GetPositionY() + (dY / distanceToPosition) * moveDist;
return MoveTo(KARAZHAN_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false, return MoveTo(KARAZHAN_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false); MovementPriority::MOVEMENT_COMBAT, true, true);
} }
} }
@@ -404,7 +405,7 @@ bool TheCuratorMarkAstralFlareAction::Execute(Event event)
if (!flare) if (!flare)
return false; return false;
if (IsMapIDTimerManager(botAI, bot)) if (IsInstanceTimerManager(botAI, bot))
MarkTargetWithSkull(bot, flare); MarkTargetWithSkull(bot, flare);
SetRtiTarget(botAI, "skull", flare); SetRtiTarget(botAI, "skull", flare);
@@ -429,17 +430,17 @@ bool TheCuratorPositionBossAction::Execute(Event event)
if (curator->GetVictim() == bot) if (curator->GetVictim() == bot)
{ {
const Position& position = THE_CURATOR_BOSS_POSITION; const Position& position = THE_CURATOR_BOSS_POSITION;
const float maxDistance = 3.0f; float distanceToPosition = curator->GetExactDist2d(position);
float distanceToBossPosition = curator->GetExactDist2d(position);
if (distanceToBossPosition > maxDistance) if (distanceToPosition > 2.0f)
{ {
float dX = position.GetPositionX() - curator->GetPositionX(); float dX = position.GetPositionX() - curator->GetPositionX();
float dY = position.GetPositionY() - curator->GetPositionY(); float dY = position.GetPositionY() - curator->GetPositionY();
float mX = position.GetPositionX() + (dX / distanceToBossPosition) * maxDistance; float moveDist = std::min(10.0f, distanceToPosition);
float mY = position.GetPositionY() + (dY / distanceToBossPosition) * maxDistance; float moveX = position.GetPositionX() + (dX / distanceToPosition) * moveDist;
float moveY = position.GetPositionY() + (dY / distanceToPosition) * moveDist;
return MoveTo(KARAZHAN_MAP_ID, mX, mY, position.GetPositionZ(), false, false, false, false, return MoveTo(KARAZHAN_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false); MovementPriority::MOVEMENT_COMBAT, true, false);
} }
} }
@@ -997,6 +998,7 @@ bool NetherspiteManageTimersAndTrackersAction::Execute(Event event)
if (!netherspite) if (!netherspite)
return false; return false;
const uint32 instanceId = netherspite->GetMap()->GetInstanceId();
const ObjectGuid botGuid = bot->GetGUID(); const ObjectGuid botGuid = bot->GetGUID();
const time_t now = std::time(nullptr); const time_t now = std::time(nullptr);
@@ -1005,8 +1007,8 @@ bool NetherspiteManageTimersAndTrackersAction::Execute(Event event)
if (netherspite->GetHealth() == netherspite->GetMaxHealth() && if (netherspite->GetHealth() == netherspite->GetMaxHealth() &&
!netherspite->HasAura(SPELL_GREEN_BEAM_HEAL)) !netherspite->HasAura(SPELL_GREEN_BEAM_HEAL))
{ {
if (IsMapIDTimerManager(botAI, bot)) if (IsInstanceTimerManager(botAI, bot))
netherspiteDpsWaitTimer.insert_or_assign(KARAZHAN_MAP_ID, now); netherspiteDpsWaitTimer.insert_or_assign(instanceId, now);
if (botAI->IsTank(bot) && !bot->HasAura(SPELL_RED_BEAM_DEBUFF)) if (botAI->IsTank(bot) && !bot->HasAura(SPELL_RED_BEAM_DEBUFF))
{ {
@@ -1016,8 +1018,8 @@ bool NetherspiteManageTimersAndTrackersAction::Execute(Event event)
} }
else if (netherspite->HasAura(SPELL_NETHERSPITE_BANISHED)) else if (netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
{ {
if (IsMapIDTimerManager(botAI, bot)) if (IsInstanceTimerManager(botAI, bot))
netherspiteDpsWaitTimer.erase(KARAZHAN_MAP_ID); netherspiteDpsWaitTimer.erase(instanceId);
if (botAI->IsTank(bot)) if (botAI->IsTank(bot))
{ {
@@ -1027,8 +1029,8 @@ bool NetherspiteManageTimersAndTrackersAction::Execute(Event event)
} }
else if (!netherspite->HasAura(SPELL_NETHERSPITE_BANISHED)) else if (!netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
{ {
if (IsMapIDTimerManager(botAI, bot)) if (IsInstanceTimerManager(botAI, bot))
netherspiteDpsWaitTimer.try_emplace(KARAZHAN_MAP_ID, now); netherspiteDpsWaitTimer.try_emplace(instanceId, now);
if (botAI->IsTank(bot) && bot->HasAura(SPELL_RED_BEAM_DEBUFF)) if (botAI->IsTank(bot) && bot->HasAura(SPELL_RED_BEAM_DEBUFF))
{ {
@@ -1443,6 +1445,7 @@ bool NightbaneManageTimersAndTrackersAction::Execute(Event event)
if (!nightbane) if (!nightbane)
return false; return false;
const uint32 instanceId = nightbane->GetMap()->GetInstanceId();
const ObjectGuid botGuid = bot->GetGUID(); const ObjectGuid botGuid = bot->GetGUID();
const time_t now = std::time(nullptr); const time_t now = std::time(nullptr);
@@ -1455,18 +1458,18 @@ bool NightbaneManageTimersAndTrackersAction::Execute(Event event)
if (botAI->IsRanged(bot)) if (botAI->IsRanged(bot))
nightbaneRangedStep.erase(botGuid); nightbaneRangedStep.erase(botGuid);
if (IsMapIDTimerManager(botAI, bot)) if (IsInstanceTimerManager(botAI, bot))
nightbaneDpsWaitTimer.erase(KARAZHAN_MAP_ID); nightbaneDpsWaitTimer.erase(instanceId);
} }
// Erase flight phase timer and Rain of Bones tracker on ground phase and start DPS wait timer // Erase flight phase timer and Rain of Bones tracker on ground phase and start DPS wait timer
else if (nightbane->GetPositionZ() <= NIGHTBANE_FLIGHT_Z) else if (nightbane->GetPositionZ() <= NIGHTBANE_FLIGHT_Z)
{ {
nightbaneRainOfBonesHit.erase(botGuid); nightbaneRainOfBonesHit.erase(botGuid);
if (IsMapIDTimerManager(botAI, bot)) if (IsInstanceTimerManager(botAI, bot))
{ {
nightbaneFlightPhaseStartTimer.erase(KARAZHAN_MAP_ID); nightbaneFlightPhaseStartTimer.erase(instanceId);
nightbaneDpsWaitTimer.try_emplace(KARAZHAN_MAP_ID, now); nightbaneDpsWaitTimer.try_emplace(instanceId, now);
} }
} }
// Erase DPS wait timer and tank and ranged position tracking and start flight phase timer // Erase DPS wait timer and tank and ranged position tracking and start flight phase timer
@@ -1479,10 +1482,10 @@ bool NightbaneManageTimersAndTrackersAction::Execute(Event event)
if (botAI->IsRanged(bot)) if (botAI->IsRanged(bot))
nightbaneRangedStep.erase(botGuid); nightbaneRangedStep.erase(botGuid);
if (IsMapIDTimerManager(botAI, bot)) if (IsInstanceTimerManager(botAI, bot))
{ {
nightbaneDpsWaitTimer.erase(KARAZHAN_MAP_ID); nightbaneDpsWaitTimer.erase(instanceId);
nightbaneFlightPhaseStartTimer.try_emplace(KARAZHAN_MAP_ID, now); nightbaneFlightPhaseStartTimer.try_emplace(instanceId, now);
} }
} }

View File

@@ -105,8 +105,8 @@ namespace KarazhanHelpers
} }
} }
// Only one bot is needed to set/reset mapwide timers // Only one bot is needed to set/reset instance-wide timers
bool IsMapIDTimerManager(PlayerbotAI* botAI, Player* bot) bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot)
{ {
if (Group* group = bot->GetGroup()) if (Group* group = bot->GetGroup())
{ {

View File

@@ -112,7 +112,7 @@ namespace KarazhanHelpers
void MarkTargetWithCircle(Player* bot, Unit* target); void MarkTargetWithCircle(Player* bot, Unit* target);
void MarkTargetWithMoon(Player* bot, Unit* target); void MarkTargetWithMoon(Player* bot, Unit* target);
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target); void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target);
bool IsMapIDTimerManager(PlayerbotAI* botAI, Player* bot); bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot);
Unit* GetFirstAliveUnit(const std::vector<Unit*>& units); Unit* GetFirstAliveUnit(const std::vector<Unit*>& units);
Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry); Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry);
Unit* GetNearestPlayerInRadius(Player* bot, float radius); Unit* GetNearestPlayerInRadius(Player* bot, float radius);

View File

@@ -61,10 +61,11 @@ float AttumenTheHuntsmanWaitForDpsMultiplier::GetValue(Action* action)
if (!attumenMounted) if (!attumenMounted)
return 1.0f; return 1.0f;
const uint32 instanceId = attumenMounted->GetMap()->GetInstanceId();
const time_t now = std::time(nullptr); const time_t now = std::time(nullptr);
const uint8 dpsWaitSeconds = 8; const uint8 dpsWaitSeconds = 8;
auto it = attumenDpsWaitTimer.find(KARAZHAN_MAP_ID); auto it = attumenDpsWaitTimer.find(instanceId);
if (it == attumenDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds) if (it == attumenDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds)
{ {
if (!botAI->IsMainTank(bot)) if (!botAI->IsMainTank(bot))
@@ -201,10 +202,11 @@ float NetherspiteWaitForDpsMultiplier::GetValue(Action* action)
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED)) if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
return 1.0f; return 1.0f;
const uint32 instanceId = netherspite->GetMap()->GetInstanceId();
const time_t now = std::time(nullptr); const time_t now = std::time(nullptr);
const uint8 dpsWaitSeconds = 5; const uint8 dpsWaitSeconds = 5;
auto it = netherspiteDpsWaitTimer.find(KARAZHAN_MAP_ID); auto it = netherspiteDpsWaitTimer.find(instanceId);
if (it == netherspiteDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds) if (it == netherspiteDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds)
{ {
if (!botAI->IsTank(bot)) if (!botAI->IsTank(bot))
@@ -299,10 +301,11 @@ float NightbaneWaitForDpsMultiplier::GetValue(Action* action)
if (!nightbane || nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z) if (!nightbane || nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z)
return 1.0f; return 1.0f;
const uint32 instanceId = nightbane->GetMap()->GetInstanceId();
const time_t now = std::time(nullptr); const time_t now = std::time(nullptr);
const uint8 dpsWaitSeconds = 8; const uint8 dpsWaitSeconds = 8;
auto it = nightbaneDpsWaitTimer.find(KARAZHAN_MAP_ID); auto it = nightbaneDpsWaitTimer.find(instanceId);
if (it == nightbaneDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds) if (it == nightbaneDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds)
{ {
if (!botAI->IsMainTank(bot)) if (!botAI->IsMainTank(bot))

View File

@@ -40,7 +40,7 @@ bool AttumenTheHuntsmanAttumenIsMountedTrigger::IsActive()
bool AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger::IsActive() bool AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger::IsActive()
{ {
if (!IsMapIDTimerManager(botAI, bot)) if (!IsInstanceTimerManager(botAI, bot))
return false; return false;
Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight"); Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight");
@@ -110,7 +110,7 @@ bool BigBadWolfBossIsChasingLittleRedRidingHoodTrigger::IsActive()
bool RomuloAndJulianneBothBossesRevivedTrigger::IsActive() bool RomuloAndJulianneBothBossesRevivedTrigger::IsActive()
{ {
if (!IsMapIDTimerManager(botAI, bot)) if (!IsInstanceTimerManager(botAI, bot))
return false; return false;
Unit* romulo = AI_VALUE2(Unit*, "find target", "romulo"); Unit* romulo = AI_VALUE2(Unit*, "find target", "romulo");
@@ -126,7 +126,7 @@ bool RomuloAndJulianneBothBossesRevivedTrigger::IsActive()
bool WizardOfOzNeedTargetPriorityTrigger::IsActive() bool WizardOfOzNeedTargetPriorityTrigger::IsActive()
{ {
if (!IsMapIDTimerManager(botAI, bot)) if (!IsInstanceTimerManager(botAI, bot))
return false; return false;
Unit* dorothee = AI_VALUE2(Unit*, "find target", "dorothee"); Unit* dorothee = AI_VALUE2(Unit*, "find target", "dorothee");
@@ -178,7 +178,7 @@ bool TheCuratorBossAstralFlaresCastArcingSearTrigger::IsActive()
bool TerestianIllhoofNeedTargetPriorityTrigger::IsActive() bool TerestianIllhoofNeedTargetPriorityTrigger::IsActive()
{ {
if (!IsMapIDTimerManager(botAI, bot)) if (!IsInstanceTimerManager(botAI, bot))
return false; return false;
Unit* illhoof = AI_VALUE2(Unit*, "find target", "terestian illhoof"); Unit* illhoof = AI_VALUE2(Unit*, "find target", "terestian illhoof");
@@ -202,7 +202,7 @@ bool ShadeOfAranFlameWreathIsActiveTrigger::IsActive()
// Exclusion of Banish is so the player may Banish elementals if they wish // Exclusion of Banish is so the player may Banish elementals if they wish
bool ShadeOfAranConjuredElementalsSummonedTrigger::IsActive() bool ShadeOfAranConjuredElementalsSummonedTrigger::IsActive()
{ {
if (!IsMapIDTimerManager(botAI, bot)) if (!IsInstanceTimerManager(botAI, bot))
return false; return false;
Unit* elemental = AI_VALUE2(Unit*, "find target", "conjured elemental"); Unit* elemental = AI_VALUE2(Unit*, "find target", "conjured elemental");
@@ -279,7 +279,7 @@ bool NetherspiteBossIsBanishedTrigger::IsActive()
bool NetherspiteNeedToManageTimersAndTrackersTrigger::IsActive() bool NetherspiteNeedToManageTimersAndTrackersTrigger::IsActive()
{ {
if (!botAI->IsTank(bot) && !IsMapIDTimerManager(botAI, bot)) if (!botAI->IsTank(bot) && !IsInstanceTimerManager(botAI, bot))
return false; return false;
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite"); Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
@@ -370,11 +370,12 @@ bool NightbaneBossIsFlyingTrigger::IsActive()
if (!nightbane || nightbane->GetPositionZ() <= NIGHTBANE_FLIGHT_Z) if (!nightbane || nightbane->GetPositionZ() <= NIGHTBANE_FLIGHT_Z)
return false; return false;
const uint32 instanceId = nightbane->GetMap()->GetInstanceId();
const time_t now = std::time(nullptr); const time_t now = std::time(nullptr);
const uint8 flightPhaseDurationSeconds = 35; const uint8 flightPhaseDurationSeconds = 35;
return nightbaneFlightPhaseStartTimer.find(KARAZHAN_MAP_ID) != nightbaneFlightPhaseStartTimer.end() && return nightbaneFlightPhaseStartTimer.find(instanceId) != nightbaneFlightPhaseStartTimer.end() &&
(now - nightbaneFlightPhaseStartTimer[KARAZHAN_MAP_ID] < flightPhaseDurationSeconds); (now - nightbaneFlightPhaseStartTimer[instanceId] < flightPhaseDurationSeconds);
} }
bool NightbaneNeedToManageTimersAndTrackersTrigger::IsActive() bool NightbaneNeedToManageTimersAndTrackersTrigger::IsActive()