Compare commits

...

2 Commits

Author SHA1 Message Date
Crow
467b63b840 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.
2025-12-21 21:40:19 +01:00
HennyWilly
66f5f597bb Re-enable no-xp-feature with better checks (#1935)
The feature `Disable bot XP gain when master has XP turned off` (#1910)
was unintentionally disabled in #1929.
This PR re-enables it with better checks in order to prevent crashes
when playing together with the individual progression module.
2025-12-21 21:28:39 +01:00
6 changed files with 67 additions and 52 deletions

View File

@@ -239,24 +239,32 @@ public:
void OnPlayerGiveXP(Player* player, uint32& amount, Unit* /*victim*/, uint8 /*xpSource*/) override
{
if (sPlayerbotAIConfig->randomBotXPRate == 1.0f || !player || !player->IsInWorld())
if (!player || !player->IsInWorld())
return;
if (WorldSession* session = player->GetSession(); !session || !session->IsBot())
return;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(player);
if (!botAI || !sRandomPlayerbotMgr->IsRandomBot(player))
if (!botAI)
return;
// No XP gain if master is a real player with XP gain disabled
if (const Player* master = botAI->GetMaster())
{
if (WorldSession* masterSession = master->GetSession();
masterSession && !masterSession->IsBot() && master->HasPlayerFlag(PLAYER_FLAGS_NO_XP_GAIN))
masterSession && !masterSession->IsBot() &&
master->HasPlayerFlag(PLAYER_FLAGS_NO_XP_GAIN))
{
amount = 0; // disable XP multiplier
return;
}
}
// From here on we only care about random bots
if (sPlayerbotAIConfig->randomBotXPRate == 1.0f || !sRandomPlayerbotMgr->IsRandomBot(player))
return;
// No XP multiplier if bot is in a group with at least one real player
if (Group* group = player->GetGroup())
{
@@ -264,14 +272,14 @@ public:
{
if (Player* member = gref->GetSource())
{
if (!member->GetSession()->IsBot())
if (WorldSession* session = member->GetSession();session && !session->IsBot())
return;
}
}
}
// Otherwise apply XP multiplier
amount = static_cast<uint32>(std::round(amount * sPlayerbotAIConfig->randomBotXPRate));
amount = static_cast<uint32>(std::round(static_cast<float>(amount) * sPlayerbotAIConfig->randomBotXPRate));
}
};

View File

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

View File

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

View File

@@ -112,7 +112,7 @@ namespace KarazhanHelpers
void MarkTargetWithCircle(Player* bot, Unit* target);
void MarkTargetWithMoon(Player* bot, 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* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry);
Unit* GetNearestPlayerInRadius(Player* bot, float radius);

View File

@@ -61,10 +61,11 @@ float AttumenTheHuntsmanWaitForDpsMultiplier::GetValue(Action* action)
if (!attumenMounted)
return 1.0f;
const uint32 instanceId = attumenMounted->GetMap()->GetInstanceId();
const time_t now = std::time(nullptr);
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 (!botAI->IsMainTank(bot))
@@ -201,10 +202,11 @@ float NetherspiteWaitForDpsMultiplier::GetValue(Action* action)
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
return 1.0f;
const uint32 instanceId = netherspite->GetMap()->GetInstanceId();
const time_t now = std::time(nullptr);
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 (!botAI->IsTank(bot))
@@ -299,10 +301,11 @@ float NightbaneWaitForDpsMultiplier::GetValue(Action* action)
if (!nightbane || nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z)
return 1.0f;
const uint32 instanceId = nightbane->GetMap()->GetInstanceId();
const time_t now = std::time(nullptr);
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 (!botAI->IsMainTank(bot))

View File

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