diff --git a/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp b/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp index 7aa3eda3..0b31a1c1 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp @@ -1,3 +1,8 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + #include "RaidSSCActions.h" #include "RaidSSCHelpers.h" #include "AiFactory.h" @@ -19,42 +24,40 @@ bool SerpentShrineCavernEraseTimersAndTrackersAction::Execute(Event /*event*/) const ObjectGuid guid = bot->GetGUID(); bool erased = false; - if (!AI_VALUE2(Unit*, "find target", "hydross the unstable")) + + if (!AI_VALUE2(Unit*, "find target", "hydross the unstable") && + (hydrossChangeToNaturePhaseTimer.erase(instanceId) > 0 || + hydrossChangeToFrostPhaseTimer.erase(instanceId) > 0 || + hydrossNatureDpsWaitTimer.erase(instanceId) > 0 || + hydrossFrostDpsWaitTimer.erase(instanceId) > 0)) { - if (hydrossChangeToNaturePhaseTimer.erase(instanceId) > 0) - erased = true; - if (hydrossChangeToFrostPhaseTimer.erase(instanceId) > 0) - erased = true; - if (hydrossNatureDpsWaitTimer.erase(instanceId) > 0) - erased = true; - if (hydrossFrostDpsWaitTimer.erase(instanceId) > 0) - erased = true; + erased = true; } - if (!AI_VALUE2(Unit*, "find target", "the lurker below")) + + if (!AI_VALUE2(Unit*, "find target", "the lurker below") && + (lurkerRangedPositions.erase(guid) > 0 || + lurkerSpoutTimer.erase(instanceId) > 0)) { - if (lurkerRangedPositions.erase(guid) > 0) - erased = true; - if (lurkerSpoutTimer.erase(instanceId) > 0) - erased = true; + erased = true; } - if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) + + if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress") && + karathressDpsWaitTimer.erase(instanceId) > 0) { - if (karathressDpsWaitTimer.erase(instanceId) > 0) - erased = true; + erased = true; } - if (!AI_VALUE2(Unit*, "find target", "morogrim tidewalker")) + + if (!AI_VALUE2(Unit*, "find target", "morogrim tidewalker") && + (tidewalkerTankStep.erase(guid) > 0 || + tidewalkerRangedStep.erase(guid) > 0)) { - if (tidewalkerTankStep.erase(guid) > 0) - erased = true; - if (tidewalkerRangedStep.erase(guid) > 0) - erased = true; + erased = true; } - if (!AI_VALUE2(Unit*, "find target", "lady vashj")) + + if (!AI_VALUE2(Unit*, "find target", "lady vashj") && + hasReachedVashjRangedPosition.erase(guid) > 0) { - if (vashjRangedPositions.erase(guid) > 0) - erased = true; - if (hasReachedVashjRangedPosition.erase(guid) > 0) - erased = true; + erased = true; } return erased; @@ -74,21 +77,18 @@ bool UnderbogColossusEscapeToxicPoolAction::Execute(Event /*event*/) return false; float radius = dynObj->GetRadius(); - if (radius <= 0.0f) + const SpellInfo* sInfo = sSpellMgr->GetSpellInfo(dynObj->GetSpellId()); + if (radius <= 0.0f && sInfo) { - const SpellInfo* sInfo = sSpellMgr->GetSpellInfo(dynObj->GetSpellId()); - if (sInfo) + for (int e = 0; e < MAX_SPELL_EFFECTS; ++e) { - for (int e = 0; e < MAX_SPELL_EFFECTS; ++e) + auto const& eff = sInfo->Effects[e]; + if (eff.Effect == SPELL_EFFECT_SCHOOL_DAMAGE || + (eff.Effect == SPELL_EFFECT_APPLY_AURA && + eff.ApplyAuraName == SPELL_AURA_PERIODIC_DAMAGE)) { - auto const& eff = sInfo->Effects[e]; - if (eff.Effect == SPELL_EFFECT_SCHOOL_DAMAGE || - (eff.Effect == SPELL_EFFECT_APPLY_AURA && - eff.ApplyAuraName == SPELL_AURA_PERIODIC_DAMAGE)) - { - radius = eff.CalcRadius(); - break; - } + radius = eff.CalcRadius(); + break; } } } @@ -207,15 +207,13 @@ bool HydrossTheUnstablePositionFrostTankAction::Execute(Event /*event*/) } } - if (hydross->HasAura(SPELL_CORRUPTION)) + const Position& position = HYDROSS_FROST_TANK_POSITION; + if (hydross->HasAura(SPELL_CORRUPTION) && + bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 2.0f) { - const Position& position = HYDROSS_FROST_TANK_POSITION; - if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 2.0f) - { - return MoveTo(SSC_MAP_ID, position.GetPositionX(), position.GetPositionY(), - position.GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_COMBAT, true, false); - } + return MoveTo(SSC_MAP_ID, position.GetPositionX(), position.GetPositionY(), + position.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT, true, false); } return false; @@ -289,15 +287,13 @@ bool HydrossTheUnstablePositionNatureTankAction::Execute(Event /*event*/) } } - if (!hydross->HasAura(SPELL_CORRUPTION)) + const Position& position = HYDROSS_NATURE_TANK_POSITION; + if (!hydross->HasAura(SPELL_CORRUPTION) && + bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 2.0f) { - const Position& position = HYDROSS_NATURE_TANK_POSITION; - if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 2.0f) - { - return MoveTo(SSC_MAP_ID, position.GetPositionX(), position.GetPositionY(), - position.GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_COMBAT, true, false); - } + return MoveTo(SSC_MAP_ID, position.GetPositionX(), position.GetPositionY(), + position.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT, true, false); } return false; @@ -305,8 +301,7 @@ bool HydrossTheUnstablePositionNatureTankAction::Execute(Event /*event*/) bool HydrossTheUnstablePrioritizeElementalAddsAction::Execute(Event /*event*/) { - Unit* waterElemental = GetFirstAliveUnitByEntry(botAI, NPC_PURE_SPAWN_OF_HYDROSS); - if (waterElemental) + if (Unit* waterElemental = GetFirstAliveUnitByEntry(botAI, NPC_PURE_SPAWN_OF_HYDROSS)) { if (IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr)) MarkTargetWithSkull(bot, waterElemental); @@ -336,20 +331,10 @@ bool HydrossTheUnstableFrostPhaseSpreadOutAction::Execute(Event /*event*/) if (!AI_VALUE2(Unit*, "find target", "hydross the unstable")) return false; - if (Group* group = bot->GetGroup()) - { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (!member || member == bot || !member->IsAlive()) - continue; - - constexpr float safeDistance = 6.0f; - constexpr uint32 minInterval = 1000; - if (bot->GetExactDist2d(member) < safeDistance) - return FleePosition(member->GetPosition(), safeDistance, minInterval); - } - } + constexpr float safeDistance = 6.0f; + constexpr uint32 minInterval = 1000; + if (Unit* nearestPlayer = GetNearestPlayerInRadius(bot, safeDistance)) + return FleePosition(nearestPlayer->GetPosition(), safeDistance, minInterval); return false; } @@ -360,33 +345,17 @@ bool HydrossTheUnstableMisdirectBossToTankAction::Execute(Event /*event*/) if (!hydross) return false; - if (Group* group = bot->GetGroup()) - { - if (TryMisdirectToFrostTank(hydross, group)) - return true; - - if (TryMisdirectToNatureTank(hydross, group)) - return true; - } - - return false; + return TryMisdirectToFrostTank(hydross) || TryMisdirectToNatureTank(hydross); } bool HydrossTheUnstableMisdirectBossToTankAction::TryMisdirectToFrostTank( - Unit* hydross, Group* group) + Unit* hydross) { - Player* frostTank = nullptr; - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsMainTank(member)) - { - frostTank = member; - break; - } - } + Player* frostTank = GetGroupMainTank(botAI, bot); + if (!frostTank) + return false; - if (HasNoMarkOfHydross(bot) && !hydross->HasAura(SPELL_CORRUPTION) && frostTank) + if (HasNoMarkOfHydross(bot) && !hydross->HasAura(SPELL_CORRUPTION)) { if (botAI->CanCastSpell("misdirection", frostTank)) return botAI->CastSpell("misdirection", frostTank); @@ -399,20 +368,13 @@ bool HydrossTheUnstableMisdirectBossToTankAction::TryMisdirectToFrostTank( } bool HydrossTheUnstableMisdirectBossToTankAction::TryMisdirectToNatureTank( - Unit* hydross, Group* group) + Unit* hydross) { - Player* natureTank = nullptr; - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && botAI->IsAssistTankOfIndex(member, 0, true)) - { - natureTank = member; - break; - } - } + Player* natureTank = GetGroupAssistTank(botAI, bot, 0); + if (!natureTank) + return false; - if (HasNoMarkOfCorruption(bot) && hydross->HasAura(SPELL_CORRUPTION) && natureTank) + if (HasNoMarkOfCorruption(bot) && hydross->HasAura(SPELL_CORRUPTION)) { if (botAI->CanCastSpell("misdirection", natureTank)) return botAI->CastSpell("misdirection", natureTank); @@ -480,33 +442,28 @@ bool HydrossTheUnstableManageTimersAction::Execute(Event /*event*/) const time_t now = std::time(nullptr); bool changed = false; + if (!hydross->HasAura(SPELL_CORRUPTION)) { - if (hydrossFrostDpsWaitTimer.try_emplace(instanceId, now).second) + if (hydrossFrostDpsWaitTimer.try_emplace(instanceId, now).second || + hydrossNatureDpsWaitTimer.erase(instanceId) > 0 || + hydrossChangeToFrostPhaseTimer.erase(instanceId) > 0) changed = true; - if (hydrossNatureDpsWaitTimer.erase(instanceId) > 0) + + if (HasMarkOfHydrossAt100Percent(bot) && + hydrossChangeToNaturePhaseTimer.try_emplace(instanceId, now).second) changed = true; - if (hydrossChangeToFrostPhaseTimer.erase(instanceId) > 0) - changed = true; - if (HasMarkOfHydrossAt100Percent(bot)) - { - if (hydrossChangeToNaturePhaseTimer.try_emplace(instanceId, now).second) - changed = true; - } } else { - if (hydrossNatureDpsWaitTimer.try_emplace(instanceId, now).second) + if (hydrossNatureDpsWaitTimer.try_emplace(instanceId, now).second || + hydrossFrostDpsWaitTimer.erase(instanceId) > 0 || + hydrossChangeToNaturePhaseTimer.erase(instanceId) > 0) changed = true; - if (hydrossFrostDpsWaitTimer.erase(instanceId) > 0) + + if (HasMarkOfCorruptionAt100Percent(bot) && + hydrossChangeToFrostPhaseTimer.try_emplace(instanceId, now).second) changed = true; - if (hydrossChangeToNaturePhaseTimer.erase(instanceId) > 0) - changed = true; - if (HasMarkOfCorruptionAt100Percent(bot)) - { - if (hydrossChangeToFrostPhaseTimer.try_emplace(instanceId, now).second) - changed = true; - } } return changed; @@ -521,17 +478,19 @@ bool TheLurkerBelowRunAroundBehindBossAction::Execute(Event /*event*/) if (!lurker) return false; - float radius = frand(20.0f, 21.0f); + float radius = frand(19.0f, 20.0f); float botAngle = std::atan2( bot->GetPositionY() - lurker->GetPositionY(), bot->GetPositionX() - lurker->GetPositionX()); float relativeAngle = Position::NormalizeOrientation(botAngle - lurker->GetOrientation()); constexpr float safeArc = M_PI / 2.0f; - if (std::fabs(Position::NormalizeOrientation(relativeAngle - M_PI)) > safeArc / 2.0f) + if (!botAI->IsMainTank(bot) && + std::fabs(Position::NormalizeOrientation(relativeAngle - M_PI)) > safeArc / 2.0f) { float tangentAngle = botAngle + (relativeAngle > M_PI ? -0.1f : 0.1f); float moveX = lurker->GetPositionX() + radius * std::cos(tangentAngle); float moveY = lurker->GetPositionY() + radius * std::sin(tangentAngle); + botAI->Reset(); return MoveTo(SSC_MAP_ID, moveX, moveY, lurker->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_FORCED, true, false); @@ -541,6 +500,7 @@ bool TheLurkerBelowRunAroundBehindBossAction::Execute(Event /*event*/) float behindAngle = lurker->GetOrientation() + M_PI + frand(-0.5f, 0.5f) * safeArc; float targetX = lurker->GetPositionX() + radius * std::cos(behindAngle); float targetY = lurker->GetPositionY() + radius * std::sin(behindAngle); + if (bot->GetExactDist2d(targetX, targetY) > 2.0f) { botAI->Reset(); @@ -611,7 +571,7 @@ bool TheLurkerBelowSpreadRangedInArcAction::Execute(Event /*event*/) float angle = (count == 1) ? arcCenter : (arcStart + arcSpan * static_cast(botIndex) / static_cast(count - 1)); - float radius = 28.0f; + constexpr float radius = 27.0f; float targetX = lurker->GetPositionX() + radius * std::sin(angle); float targetY = lurker->GetPositionY() + radius * std::cos(angle); @@ -638,38 +598,24 @@ bool TheLurkerBelowSpreadRangedInArcAction::Execute(Event /*event*/) // the first 3 will each pick up 1 Guardian bool TheLurkerBelowTanksPickUpAddsAction::Execute(Event /*event*/) { - Player* mainTank = nullptr; - Player* firstAssistTank = nullptr; - Player* secondAssistTank = nullptr; - - if (Group* group = bot->GetGroup()) - { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (!member || !member->IsAlive()) - continue; - - if (!mainTank && botAI->IsMainTank(member)) - mainTank = member; - else if (!firstAssistTank && botAI->IsAssistTankOfIndex(member, 0, true)) - firstAssistTank = member; - else if (!secondAssistTank && botAI->IsAssistTankOfIndex(member, 1, true)) - secondAssistTank = member; - } - } - + Player* mainTank = GetGroupMainTank(botAI, bot); + Player* firstAssistTank = GetGroupAssistTank(botAI, bot, 0); + Player* secondAssistTank = GetGroupAssistTank(botAI, bot, 1); if (!mainTank || !firstAssistTank || !secondAssistTank) return false; std::vector guardians; - auto const& npcs = - botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); - for (auto guid : npcs) + auto const& attackers = + botAI->GetAiObjectContext()->GetValue("possible targets no los")->Get(); + + for (auto guid : attackers) { Unit* unit = botAI->GetUnit(guid); - if (unit && unit->IsAlive() && unit->GetEntry() == NPC_COILFANG_GUARDIAN) + if (unit && unit->IsAlive() && + unit->GetEntry() == NPC_COILFANG_GUARDIAN) + { guardians.push_back(unit); + } } if (guardians.size() < 3) @@ -692,7 +638,8 @@ bool TheLurkerBelowTanksPickUpAddsAction::Execute(Event /*event*/) { MarkTargetWithIcon(bot, guardian, rtiIndices[i]); SetRtiTarget(botAI, rtiNames[i], guardian); - if (bot->GetTarget() != guardian->GetGUID()) + + if (bot->GetVictim() != guardian) return Attack(guardian); } } @@ -709,18 +656,24 @@ bool TheLurkerBelowManageSpoutTimerAction::Execute(Event /*event*/) const uint32 instanceId = lurker->GetMap()->GetInstanceId(); const time_t now = std::time(nullptr); + bool changed = false; + auto it = lurkerSpoutTimer.find(instanceId); if (it != lurkerSpoutTimer.end() && it->second <= now) { lurkerSpoutTimer.erase(it); + changed = true; it = lurkerSpoutTimer.end(); } const time_t spoutCastTime = 20; if (IsLurkerCastingSpout(lurker) && it == lurkerSpoutTimer.end()) + { lurkerSpoutTimer.try_emplace(instanceId, now + spoutCastTime); + changed = true; + } - return false; + return changed; } // Leotheras the Blind @@ -737,15 +690,16 @@ bool LeotherasTheBlindTargetSpellbindersAction::Execute(Event /*event*/) // Use tank strategy for Demon Form and DPS strategy for Human Form bool LeotherasTheBlindDemonFormTankAttackBossAction::Execute(Event /*event*/) { + auto const& attackers = + botAI->GetAiObjectContext()->GetValue("possible targets no los")->Get(); + Unit* innerDemon = nullptr; - auto const& npcs = - botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); - for (auto const& guid : npcs) + for (auto guid : attackers) { Unit* unit = botAI->GetUnit(guid); Creature* creature = unit ? unit->ToCreature() : nullptr; - if (creature && creature->GetEntry() == NPC_INNER_DEMON - && creature->GetSummonerGUID() == bot->GetGUID()) + if (creature && creature->GetEntry() == NPC_INNER_DEMON && + creature->GetSummonerGUID() == bot->GetGUID()) { innerDemon = creature; break; @@ -755,7 +709,7 @@ bool LeotherasTheBlindDemonFormTankAttackBossAction::Execute(Event /*event*/) if (innerDemon) return false; - if (Unit* leotherasDemon = GetActiveLeotherasDemon(botAI)) + if (Unit* leotherasDemon = GetActiveLeotherasDemon(bot)) { MarkTargetWithSquare(bot, leotherasDemon); SetRtiTarget(botAI, "square", leotherasDemon); @@ -781,7 +735,7 @@ bool LeotherasTheBlindMeleeTanksDontAttackDemonFormAction::Execute(Event /*event bool LeotherasTheBlindPositionRangedAction::Execute(Event /*event*/) { constexpr float safeDistFromBoss = 15.0f; - Unit* leotherasHuman = GetLeotherasHuman(botAI); + Unit* leotherasHuman = GetLeotherasHuman(bot); if (leotherasHuman && bot->GetExactDist2d(leotherasHuman) < safeDistFromBoss && leotherasHuman->GetVictim() != bot) { @@ -789,11 +743,10 @@ bool LeotherasTheBlindPositionRangedAction::Execute(Event /*event*/) return FleePosition(leotherasHuman->GetPosition(), safeDistFromBoss, minInterval); } - Group* group = bot->GetGroup(); - if (!group) + if (!GetActiveLeotherasDemon(bot)) return false; - if (GetActiveLeotherasDemon(botAI)) + if (Group* group = bot->GetGroup()) { for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { @@ -804,9 +757,9 @@ bool LeotherasTheBlindPositionRangedAction::Execute(Event /*event*/) constexpr uint32 minInterval = 0; if (GetLeotherasDemonFormTank(bot) == member) { - constexpr float safeDistFromTank = 10.0f; - if (bot->GetExactDist2d(member) < safeDistFromTank) - return FleePosition(member->GetPosition(), safeDistFromTank, minInterval); + constexpr float safeDistFromMember = 10.0f; + if (bot->GetExactDist2d(member) < safeDistFromMember) + return FleePosition(member->GetPosition(), safeDistFromMember, minInterval); } else { @@ -822,7 +775,7 @@ bool LeotherasTheBlindPositionRangedAction::Execute(Event /*event*/) bool LeotherasTheBlindRunAwayFromWhirlwindAction::Execute(Event /*event*/) { - if (Unit* leotherasHuman = GetLeotherasHuman(botAI)) + if (Unit* leotherasHuman = GetLeotherasHuman(bot)) { float currentDistance = bot->GetExactDist2d(leotherasHuman); constexpr float safeDistance = 25.0f; @@ -843,7 +796,7 @@ bool LeotherasTheBlindMeleeDpsRunAwayFromBossAction::Execute(Event /*event*/) if (botAI->CanCastSpell("cloak of shadows", bot)) return botAI->CastSpell("cloak of shadows", bot); - Unit* leotheras = GetPhase2LeotherasDemon(botAI); + Unit* leotheras = GetPhase2LeotherasDemon(bot); if (!leotheras) return false; @@ -853,12 +806,8 @@ bool LeotherasTheBlindMeleeDpsRunAwayFromBossAction::Execute(Event /*event*/) float currentDistance = bot->GetExactDist2d(demonVictim); constexpr float safeDistance = 10.0f; - if (currentDistance < safeDistance) - { - botAI->Reset(); - if (demonVictim != bot) - return MoveAway(demonVictim, safeDistance - currentDistance); - } + if (currentDistance < safeDistance && demonVictim != bot) + return MoveAway(demonVictim, safeDistance - currentDistance); return false; } @@ -866,15 +815,16 @@ bool LeotherasTheBlindMeleeDpsRunAwayFromBossAction::Execute(Event /*event*/) // Hardcoded actions for healers and bear tanks to kill Inner Demons bool LeotherasTheBlindDestroyInnerDemonAction::Execute(Event /*event*/) { + auto const& attackers = + botAI->GetAiObjectContext()->GetValue("possible targets no los")->Get(); + Unit* innerDemon = nullptr; - auto const& npcs = - botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); - for (auto const& guid : npcs) + for (auto guid : attackers) { Unit* unit = botAI->GetUnit(guid); Creature* creature = unit ? unit->ToCreature() : nullptr; - if (creature && creature->GetEntry() == NPC_INNER_DEMON - && creature->GetSummonerGUID() == bot->GetGUID()) + if (creature && creature->GetEntry() == NPC_INNER_DEMON && + creature->GetSummonerGUID() == bot->GetGUID()) { innerDemon = creature; break; @@ -910,37 +860,34 @@ bool LeotherasTheBlindDestroyInnerDemonAction::HandleFeralTankStrategy(Unit* inn bot->RemoveAura(SPELL_BEAR_FORM); bool casted = false; - if (!bot->HasAura(SPELL_CAT_FORM) && botAI->CanCastSpell("cat form", bot)) - { - if (botAI->CastSpell("cat form", bot)) - casted = true; - } - if (botAI->CanCastSpell("berserk", bot)) - { - if (botAI->CastSpell("berserk", bot)) - casted = true; - } - if (bot->GetPower(POWER_ENERGY) < 30 && botAI->CanCastSpell("tiger's fury", bot)) - { - if (botAI->CastSpell("tiger's fury", bot)) - casted = true; - } - if (bot->GetComboPoints() >= 4 && botAI->CanCastSpell("ferocious bite", innerDemon)) - { - if (botAI->CastSpell("ferocious bite", innerDemon)) - casted = true; - } + + if (!bot->HasAura(SPELL_CAT_FORM) && + botAI->CanCastSpell("cat form", bot) && + botAI->CastSpell("cat form", bot)) + casted = true; + + if (botAI->CanCastSpell("berserk", bot) && + botAI->CastSpell("berserk", bot)) + casted = true; + + if (bot->GetPower(POWER_ENERGY) < 30 && + botAI->CanCastSpell("tiger's fury", bot) && + botAI->CastSpell("tiger's fury", bot)) + casted = true; + + if (bot->GetComboPoints() >= 4 && + botAI->CanCastSpell("ferocious bite", innerDemon) && + botAI->CastSpell("ferocious bite", innerDemon)) + casted = true; + if (bot->GetComboPoints() == 0 && innerDemon->GetHealthPct() > 25.0f && - botAI->CanCastSpell("rake", innerDemon)) - { - if (botAI->CastSpell("rake", innerDemon)) - casted = true; - } - if (botAI->CanCastSpell("mangle (cat)", innerDemon)) - { - if (botAI->CastSpell("mangle (cat)", innerDemon)) - casted = true; - } + botAI->CanCastSpell("rake", innerDemon) && + botAI->CastSpell("rake", innerDemon)) + casted = true; + + if (botAI->CanCastSpell("mangle (cat)", innerDemon) && + botAI->CastSpell("mangle (cat)", innerDemon)) + casted = true; return casted; } @@ -953,52 +900,44 @@ bool LeotherasTheBlindDestroyInnerDemonAction::HandleHealerStrategy(Unit* innerD bot->RemoveAura(SPELL_TREE_OF_LIFE); bool casted = false; - if (botAI->CanCastSpell("barkskin", bot)) - { - if (botAI->CastSpell("barkskin", bot)) - casted = true; - } - if (botAI->CanCastSpell("wrath", innerDemon)) - { - if (botAI->CastSpell("wrath", innerDemon)) - casted = true; - } + + if (botAI->CanCastSpell("barkskin", bot) && + botAI->CastSpell("barkskin", bot)) + casted = true; + + if (botAI->CanCastSpell("wrath", innerDemon) && + botAI->CastSpell("wrath", innerDemon)) + casted = true; return casted; } else if (bot->getClass() == CLASS_PALADIN) { bool casted = false; - if (botAI->CanCastSpell("avenging wrath", bot)) - { - if (botAI->CastSpell("avenging wrath", bot)) - casted = true; - } - if (botAI->CanCastSpell("consecration", bot)) - { - if (botAI->CastSpell("consecration", bot)) - casted = true; - } - if (botAI->CanCastSpell("exorcism", innerDemon)) - { - if (botAI->CastSpell("exorcism", innerDemon)) - casted = true; - } - if (botAI->CanCastSpell("hammer of wrath", innerDemon)) - { - if (botAI->CastSpell("hammer of wrath", innerDemon)) - casted = true; - } - if (botAI->CanCastSpell("holy shock", innerDemon)) - { - if (botAI->CastSpell("holy shock", innerDemon)) - casted = true; - } - if (botAI->CanCastSpell("judgment of light", innerDemon)) - { - if (botAI->CastSpell("judgment of light", innerDemon)) - casted = true; - } + + if (botAI->CanCastSpell("avenging wrath", bot) && + botAI->CastSpell("avenging wrath", bot)) + casted = true; + + if (botAI->CanCastSpell("consecration", bot) && + botAI->CastSpell("consecration", bot)) + casted = true; + + if (botAI->CanCastSpell("exorcism", innerDemon) && + botAI->CastSpell("exorcism", innerDemon)) + casted = true; + + if (botAI->CanCastSpell("hammer of wrath", innerDemon) && + botAI->CastSpell("hammer of wrath", innerDemon)) + casted = true; + + if (botAI->CanCastSpell("holy shock", innerDemon) && + botAI->CastSpell("holy shock", innerDemon)) + casted = true; + + if (botAI->CanCastSpell("judgement of light", innerDemon) && + botAI->CastSpell("judgement of light", innerDemon)) + casted = true; return casted; } @@ -1010,21 +949,18 @@ bool LeotherasTheBlindDestroyInnerDemonAction::HandleHealerStrategy(Unit* innerD else if (bot->getClass() == CLASS_SHAMAN) { bool casted = false; - if (botAI->CanCastSpell("earth shock", innerDemon)) - { - if (botAI->CastSpell("earth shock", innerDemon)) - casted = true; - } - if (botAI->CanCastSpell("chain lightning", innerDemon)) - { - if (botAI->CastSpell("chain lightning", innerDemon)) - casted = true; - } - if (botAI->CanCastSpell("lightning bolt", innerDemon)) - { - if (botAI->CastSpell("lightning bolt", innerDemon)) - casted = true; - } + + if (botAI->CanCastSpell("earth shock", innerDemon) && + botAI->CastSpell("earth shock", innerDemon)) + casted = true; + + if (botAI->CanCastSpell("chain lightning", innerDemon) && + botAI->CastSpell("chain lightning", innerDemon)) + casted = true; + + if (botAI->CanCastSpell("lightning bolt", innerDemon) && + botAI->CastSpell("lightning bolt", innerDemon)) + casted = true; return casted; } @@ -1035,7 +971,7 @@ bool LeotherasTheBlindDestroyInnerDemonAction::HandleHealerStrategy(Unit* innerD // Everybody except the Warlock tank should focus on Leotheras in Phase 3 bool LeotherasTheBlindFinalPhaseAssignDpsPriorityAction::Execute(Event /*event*/) { - Unit* leotherasHuman = GetLeotherasHuman(botAI); + Unit* leotherasHuman = GetLeotherasHuman(bot); if (!leotherasHuman) return false; @@ -1045,23 +981,27 @@ bool LeotherasTheBlindFinalPhaseAssignDpsPriorityAction::Execute(Event /*event*/ if (bot->GetTarget() != leotherasHuman->GetGUID()) return Attack(leotherasHuman); - Unit* leotherasDemon = GetPhase3LeotherasDemon(botAI); - if (leotherasDemon) - { - if (leotherasHuman->GetVictim() == bot) - { - Unit* demonTarget = leotherasDemon->GetVictim(); - if (demonTarget && leotherasHuman->GetExactDist2d(demonTarget) < 20.0f) - { - float angle = atan2(bot->GetPositionY() - demonTarget->GetPositionY(), - bot->GetPositionX() - demonTarget->GetPositionX()); - float targetX = bot->GetPositionX() + 25.0f * std::cos(angle); - float targetY = bot->GetPositionY() + 25.0f * std::sin(angle); + Unit* leotherasDemon = GetPhase3LeotherasDemon(bot); + if (!leotherasDemon) + return false; - return MoveTo(SSC_MAP_ID, targetX, targetY, bot->GetPositionZ(), false, false, - false, false, MovementPriority::MOVEMENT_FORCED, true, false); - } - } + if (leotherasHuman->GetVictim() != bot) + return false; + + Unit* demonTarget = leotherasDemon->GetVictim(); + if (!demonTarget) + return false; + + constexpr float safeDistanceFromDemon = 20.0f; + if (leotherasHuman->GetExactDist2d(demonTarget) < safeDistanceFromDemon) + { + float angle = atan2(bot->GetPositionY() - demonTarget->GetPositionY(), + bot->GetPositionX() - demonTarget->GetPositionX()); + float targetX = bot->GetPositionX() + safeDistanceFromDemon * std::cos(angle); + float targetY = bot->GetPositionY() + safeDistanceFromDemon * std::sin(angle); + + return MoveTo(SSC_MAP_ID, targetX, targetY, bot->GetPositionZ(), false, false, + false, false, MovementPriority::MOVEMENT_FORCED, true, false); } return false; @@ -1070,28 +1010,13 @@ bool LeotherasTheBlindFinalPhaseAssignDpsPriorityAction::Execute(Event /*event*/ // Misdirect to Warlock tank or to main tank if there is no Warlock tank bool LeotherasTheBlindMisdirectBossToDemonFormTankAction::Execute(Event /*event*/) { - Unit* leotherasDemon = GetActiveLeotherasDemon(botAI); + Unit* leotherasDemon = GetActiveLeotherasDemon(bot); if (!leotherasDemon) return false; - Player* demonFormTank = GetLeotherasDemonFormTank(bot); - Player* targetTank = demonFormTank; - + Player* targetTank = GetLeotherasDemonFormTank(bot); if (!targetTank) - { - if (Group* group = bot->GetGroup()) - { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsMainTank(member)) - { - targetTank = member; - break; - } - } - } - } + targetTank = GetGroupMainTank(botAI, bot); if (!targetTank) return false; @@ -1117,43 +1042,37 @@ bool LeotherasTheBlindManageDpsWaitTimersAction::Execute(Event /*event*/) bool changed = false; // Encounter start/reset: clear all timers - if (leotheras->HasAura(SPELL_LEOTHERAS_BANISHED)) + if (leotheras->HasAura(SPELL_LEOTHERAS_BANISHED) && + (leotherasHumanFormDpsWaitTimer.erase(instanceId) > 0 || + leotherasDemonFormDpsWaitTimer.erase(instanceId) > 0 || + leotherasFinalPhaseDpsWaitTimer.erase(instanceId) > 0)) { - if (leotherasHumanFormDpsWaitTimer.erase(instanceId) > 0) - changed = true; - if (leotherasDemonFormDpsWaitTimer.erase(instanceId) > 0) - changed = true; - if (leotherasFinalPhaseDpsWaitTimer.erase(instanceId) > 0) - changed = true; + changed = true; } // Human Phase - Unit* leotherasHuman = GetLeotherasHuman(botAI); - Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI); - if (leotherasHuman && !leotherasPhase3Demon) + Unit* leotherasHuman = GetLeotherasHuman(bot); + Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(bot); + if (leotherasHuman && !leotherasPhase3Demon && + (leotherasHumanFormDpsWaitTimer.try_emplace(instanceId, now).second || + leotherasDemonFormDpsWaitTimer.erase(instanceId) > 0)) { - if (leotherasHumanFormDpsWaitTimer.try_emplace(instanceId, now).second) - changed = true; - if (leotherasDemonFormDpsWaitTimer.erase(instanceId) > 0) - changed = true; + changed = true; } // Demon Phase - else if (Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI)) + else if (GetPhase2LeotherasDemon(bot) && + (leotherasDemonFormDpsWaitTimer.try_emplace(instanceId, now).second || + leotherasHumanFormDpsWaitTimer.erase(instanceId) > 0)) { - if (leotherasDemonFormDpsWaitTimer.try_emplace(instanceId, now).second) - changed = true; - if (leotherasHumanFormDpsWaitTimer.erase(instanceId) > 0) - changed = true; + changed = true; } // Final Phase (<15% HP) - else if (leotherasHuman && leotherasPhase3Demon) + else if (leotherasHuman && leotherasPhase3Demon && + (leotherasFinalPhaseDpsWaitTimer.try_emplace(instanceId, now).second || + leotherasHumanFormDpsWaitTimer.erase(instanceId) > 0 || + leotherasDemonFormDpsWaitTimer.erase(instanceId) > 0)) { - if (leotherasFinalPhaseDpsWaitTimer.try_emplace(instanceId, now).second) - changed = true; - if (leotherasHumanFormDpsWaitTimer.erase(instanceId) > 0) - changed = true; - if (leotherasDemonFormDpsWaitTimer.erase(instanceId) > 0) - changed = true; + changed = true; } return changed; @@ -1367,41 +1286,17 @@ bool FathomLordKarathressMisdirectBossesToTanksAction::Execute(Event /*event*/) if (hunterIndex == 0) { bossTarget = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsAssistTankOfIndex(member, 0, false)) - { - tankTarget = member; - break; - } - } + tankTarget = GetGroupAssistTank(botAI, bot, 0); } else if (hunterIndex == 1) { bossTarget = AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess"); - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsAssistTankOfIndex(member, 2, false)) - { - tankTarget = member; - break; - } - } + tankTarget = GetGroupAssistTank(botAI, bot, 2); } else if (hunterIndex == 2) { bossTarget = AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis"); - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsAssistTankOfIndex(member, 1, false)) - { - tankTarget = member; - break; - } - } + tankTarget = GetGroupAssistTank(botAI, bot, 1); } if (!bossTarget || !tankTarget) @@ -1529,11 +1424,9 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event /*event*/) bool FathomLordKarathressManageDpsTimerAction::Execute(Event /*event*/) { Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); - if (!karathress) - return false; - - karathressDpsWaitTimer.try_emplace( - karathress->GetMap()->GetInstanceId(), std::time(nullptr)); + if (karathress && karathressDpsWaitTimer.try_emplace( + karathress->GetMap()->GetInstanceId(), std::time(nullptr)).second) + return true; return false; } @@ -1546,20 +1439,7 @@ bool MorogrimTidewalkerMisdirectBossToMainTankAction::Execute(Event /*event*/) if (!tidewalker) return false; - Player* mainTank = nullptr; - if (Group* group = bot->GetGroup()) - { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsMainTank(member)) - { - mainTank = member; - break; - } - } - } - + Player* mainTank = GetGroupMainTank(botAI, bot); if (!mainTank) return false; @@ -1757,8 +1637,7 @@ bool LadyVashjMainTankPositionBossAction::Execute(Event /*event*/) // Phase 3: No fixed position, but move Vashj away from Enchanted Elementals else if (IsLadyVashjInPhase3(botAI)) { - Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental"); - if (enchanted) + if (Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental")) { float currentDistance = bot->GetExactDist2d(enchanted); constexpr float safeDistance = 10.0f; @@ -1775,70 +1654,52 @@ bool LadyVashjMainTankPositionBossAction::Execute(Event /*event*/) bool LadyVashjPhase1SpreadRangedInArcAction::Execute(Event /*event*/) { std::vector spreadMembers; - if (Group* group = bot->GetGroup()) + Group* group = bot->GetGroup(); + if (!group) + return false; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && GET_PLAYERBOT_AI(member)) - { - if (botAI->IsRanged(member)) - spreadMembers.push_back(member); - } - } + Player* member = ref->GetSource(); + if (member && GET_PLAYERBOT_AI(member) && botAI->IsRanged(member)) + spreadMembers.push_back(member); } const ObjectGuid guid = bot->GetGUID(); - - auto itPos = vashjRangedPositions.find(guid); auto itReached = hasReachedVashjRangedPosition.find(guid); - if (itPos == vashjRangedPositions.end()) - { - auto it = std::find(spreadMembers.begin(), spreadMembers.end(), bot); - size_t botIndex = (it != spreadMembers.end()) ? - std::distance(spreadMembers.begin(), it) : 0; - size_t count = spreadMembers.size(); - if (count == 0) - return false; - const Position& center = VASHJ_PLATFORM_CENTER_POSITION; - constexpr float minRadius = 20.0f; - constexpr float maxRadius = 30.0f; - - constexpr float arcCenter = M_PI / 2.0f; // North - constexpr float arcSpan = M_PI; // 180° - constexpr float arcStart = arcCenter - arcSpan / 2.0f; - - float angle; - if (count == 1) - angle = arcCenter; - else - angle = arcStart + (static_cast(botIndex) / (count - 1)) * arcSpan; - - float radius = frand(minRadius, maxRadius); - float targetX = center.GetPositionX() + radius * std::cos(angle); - float targetY = center.GetPositionY() + radius * std::sin(angle); - - auto res = vashjRangedPositions.try_emplace(guid, Position(targetX, targetY, center.GetPositionZ())); - itPos = res.first; - hasReachedVashjRangedPosition.try_emplace(guid, false); - itReached = hasReachedVashjRangedPosition.find(guid); - } - - if (itPos == vashjRangedPositions.end()) + auto it = std::find(spreadMembers.begin(), spreadMembers.end(), bot); + size_t botIndex = (it != spreadMembers.end()) ? + std::distance(spreadMembers.begin(), it) : 0; + size_t count = spreadMembers.size(); + if (count == 0) return false; - Position position = itPos->second; + constexpr float arcCenter = M_PI / 2.0f; // North + constexpr float arcSpan = M_PI; // 180° + constexpr float arcStart = arcCenter - arcSpan / 2.0f; + + float angle; + if (count == 1) + angle = arcCenter; + else + angle = arcStart + (static_cast(botIndex) / (count - 1)) * arcSpan; + + const Position& center = VASHJ_PLATFORM_CENTER_POSITION; + float radius = 25.0f; + float targetX = center.GetPositionX() + radius * std::cos(angle); + float targetY = center.GetPositionY() + radius * std::sin(angle); + float targetZ = center.GetPositionZ(); + if (itReached == hasReachedVashjRangedPosition.end() || !(itReached->second)) { - if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 2.0f) + if (bot->GetExactDist2d(targetX, targetY) > 2.0f) { - return MoveTo(SSC_MAP_ID, position.GetPositionX(), position.GetPositionY(), - position.GetPositionZ(), false, false, false, false, + hasReachedVashjRangedPosition.try_emplace(guid, false); + return MoveTo(SSC_MAP_ID, targetX, targetY, targetZ, false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); } - if (itReached != hasReachedVashjRangedPosition.end()) - itReached->second = true; + hasReachedVashjRangedPosition[guid] = true; } return false; @@ -1847,37 +1708,19 @@ bool LadyVashjPhase1SpreadRangedInArcAction::Execute(Event /*event*/) // For absorbing Shock Burst bool LadyVashjSetGroundingTotemInMainTankGroupAction::Execute(Event /*event*/) { - Player* mainTank = nullptr; - if (Group* group = bot->GetGroup()) - { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsMainTank(member)) - { - mainTank = member; - break; - } - } - } - + Player* mainTank = GetGroupMainTank(botAI, bot); if (!mainTank) return false; - if (bot->GetExactDist2d(mainTank) > 25.0f) - { - return MoveInside(SSC_MAP_ID, mainTank->GetPositionX(), mainTank->GetPositionY(), - mainTank->GetPositionZ(), 20.0f, MovementPriority::MOVEMENT_COMBAT); - } + if (mainTank->HasAura(SPELL_GROUNDING_TOTEM_EFFECT)) + return false; - if (!botAI->HasStrategy("grounding", BotState::BOT_STATE_COMBAT)) - botAI->ChangeStrategy("+grounding", BotState::BOT_STATE_COMBAT); + constexpr float distFromTank = 25.0f; + if (bot->GetDistance(mainTank) > distFromTank) + return MoveTo(mainTank, distFromTank, MovementPriority::MOVEMENT_COMBAT); - if (!bot->HasAura(SPELL_GROUNDING_TOTEM_EFFECT) && - botAI->CanCastSpell("grounding totem", bot)) - return botAI->CastSpell("grounding totem", bot); - - return false; + return botAI->CanCastSpell("grounding totem", bot) && + botAI->CastSpell("grounding totem", bot); } bool LadyVashjMisdirectBossToMainTankAction::Execute(Event /*event*/) @@ -1886,20 +1729,7 @@ bool LadyVashjMisdirectBossToMainTankAction::Execute(Event /*event*/) if (!vashj) return false; - Player* mainTank = nullptr; - if (Group* group = bot->GetGroup()) - { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsMainTank(member)) - { - mainTank = member; - break; - } - } - } - + Player* mainTank = GetGroupMainTank(botAI, bot); if (!mainTank) return false; @@ -1919,19 +1749,8 @@ bool LadyVashjStaticChargeMoveAwayFromGroupAction::Execute(Event /*event*/) return false; // If the main tank has Static Charge, other group members should move away - Player* mainTank = nullptr; - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsMainTank(member) && - member->HasAura(SPELL_STATIC_CHARGE)) - { - mainTank = member; - break; - } - } - - if (mainTank && bot != mainTank) + Player* mainTank = GetGroupMainTank(botAI, bot); + if (mainTank && bot != mainTank && mainTank->HasAura(SPELL_STATIC_CHARGE)) { float currentDistance = bot->GetExactDist2d(mainTank); constexpr float safeDistance = 11.0f; @@ -1979,7 +1798,7 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event /*event*/) } auto const& attackers = - botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); + botAI->GetAiObjectContext()->GetValue("possible targets no los")->Get(); Unit* target = nullptr; Unit* enchanted = nullptr; Unit* elite = nullptr; @@ -2019,7 +1838,7 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event /*event*/) break; case NPC_TOXIC_SPOREBAT: - if (!sporebat || bot->GetExactDist2d(unit) < bot->GetExactDist2d(sporebat)) + if (!sporebat || bot->GetDistance(unit) < bot->GetDistance(sporebat)) sporebat = unit; break; @@ -2067,10 +1886,10 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event /*event*/) SetRtiTarget(botAI, "diamond", vashj); targets = { vashj }; } - else if (botAI->IsAssistTankOfIndex(bot, 0, true)) + else if (botAI->HasCheat(BotCheatMask::raid) && + botAI->IsAssistTankOfIndex(bot, 0, true)) { - if (botAI->HasCheat(BotCheatMask::raid)) - targets = { strider, elite, enchanted, vashj }; + targets = { strider, elite, enchanted, vashj }; } else targets = { elite, strider, enchanted, vashj }; @@ -2114,22 +1933,8 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event /*event*/) return Attack(target); // If bots have wandered too far from the center, move them back - if (bot->GetExactDist2d(center.GetPositionX(), center.GetPositionY()) > 55.0f) - { - Player* designatedLooter = GetDesignatedCoreLooter(bot->GetGroup(), botAI); - Player* firstCorePasser = GetFirstTaintedCorePasser(bot->GetGroup(), botAI); - // A bot will not move back to the middle if (1) there is a Tainted Elemental, and - // (2) the bot is either the designated looter or the first core passer - if (Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental")) - { - if ((designatedLooter && designatedLooter == bot) || - (firstCorePasser && firstCorePasser == bot)) - return false; - } - - return MoveInside(SSC_MAP_ID, center.GetPositionX(), center.GetPositionY(), - center.GetPositionZ(), 40.0f, MovementPriority::MOVEMENT_COMBAT); - } + if (bot->GetExactDist2d(vashj) > maxPursueRange) + return MoveTo(vashj, maxPursueRange - 10.0f, MovementPriority::MOVEMENT_FORCED); return false; } @@ -2144,24 +1949,11 @@ bool LadyVashjMisdirectStriderToFirstAssistTankAction::Execute(Event /*event*/) if (bot->getClass() != CLASS_HUNTER) return false; - Unit* strider = GetFirstAliveUnitByEntry(botAI, NPC_COILFANG_STRIDER); + Unit* strider = AI_VALUE2(Unit*, "find target", "coilfang strider"); if (!strider) return false; - Player* firstAssistTank = nullptr; - if (Group* group = bot->GetGroup()) - { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && botAI->IsAssistTankOfIndex(member, 0, true)) - { - firstAssistTank = member; - break; - } - } - } - + Player* firstAssistTank = GetGroupAssistTank(botAI, bot, 0); if (!firstAssistTank || strider->GetVictim() == firstAssistTank) return false; @@ -2180,7 +1972,7 @@ bool LadyVashjTankAttackAndMoveAwayStriderAction::Execute(Event /*event*/) if (!vashj) return false; - Unit* strider = GetFirstAliveUnitByEntry(botAI, NPC_COILFANG_STRIDER); + Unit* strider = AI_VALUE2(Unit*, "find target", "coilfang strider"); if (!strider) return false; @@ -2192,53 +1984,41 @@ bool LadyVashjTankAttackAndMoveAwayStriderAction::Execute(Event /*event*/) if (!bot->HasAura(SPELL_FEAR_WARD)) bot->AddAura(SPELL_FEAR_WARD, bot); - if (botAI->IsAssistTankOfIndex(bot, 0, true) && - bot->GetTarget() != strider->GetGUID()) + if (botAI->IsAssistTankOfIndex(bot, 0, true) && bot->GetTarget() != strider->GetGUID()) return Attack(strider); - if (strider->GetVictim() == bot) - { - float currentDistance = bot->GetExactDist2d(vashj); - constexpr float safeDistance = 28.0f; - - if (currentDistance < safeDistance) - return MoveAway(vashj, safeDistance - currentDistance); - } + float currentDistance = bot->GetExactDist2d(vashj); + constexpr float safeDistance = 28.0f; + if (strider->GetVictim() == bot && currentDistance < safeDistance) + return MoveAway(vashj, safeDistance - currentDistance); return false; } // Don't move away if raid cheats are enabled, or in any case if the bot is a tank - if (!botAI->HasCheat(BotCheatMask::raid) || !botAI->IsTank(bot)) + if (!botAI->HasCheat(BotCheatMask::raid)) { float currentDistance = bot->GetExactDist2d(strider); constexpr float safeDistance = 20.0f; - if (currentDistance < safeDistance) + if (!botAI->IsTank(bot) && currentDistance < safeDistance) return MoveAway(strider, safeDistance - currentDistance); - } - - // Try to root/slow the Strider if it is not tankable (poor man's kiting strategy) - if (!botAI->HasCheat(BotCheatMask::raid)) - { - if (!strider->HasAura(SPELL_HEAVY_NETHERWEAVE_NET)) - { - Item* net = bot->GetItemByEntry(ITEM_HEAVY_NETHERWEAVE_NET); - if (net && botAI->HasItemInInventory(ITEM_HEAVY_NETHERWEAVE_NET) && - botAI->CanCastSpell("heavy netherweave net", strider)) - return botAI->CastSpell("heavy netherweave net", strider); - } + // Try to root/slow the Strider if it is not tankable (poor man's kiting strategy) if (!botAI->HasAura("frost shock", strider) && bot->getClass() == CLASS_SHAMAN && botAI->CanCastSpell("frost shock", strider)) + { return botAI->CastSpell("frost shock", strider); - - if (!strider->HasAura(SPELL_CURSE_OF_EXHAUSTION) && bot->getClass() == CLASS_WARLOCK && - botAI->CanCastSpell("curse of exhaustion", strider)) + } + else if (!strider->HasAura(SPELL_CURSE_OF_EXHAUSTION) && bot->getClass() == CLASS_WARLOCK && + botAI->CanCastSpell("curse of exhaustion", strider)) + { return botAI->CastSpell("curse of exhaustion", strider); - - if (!strider->HasAura(SPELL_SLOW) && bot->getClass() == CLASS_MAGE && - botAI->CanCastSpell("slow", strider)) + } + else if (!strider->HasAura(SPELL_SLOW) && bot->getClass() == CLASS_MAGE && + botAI->CanCastSpell("slow", strider)) + { return botAI->CastSpell("slow", strider); + } } return false; @@ -2252,7 +2032,7 @@ bool LadyVashjTeleportToTaintedElementalAction::Execute(Event /*event*/) if (!tainted) return false; - if (bot->GetExactDist2d(tainted) >= 10.0f) + if (bot->GetExactDist2d(tainted) > 10.0f) { bot->AttackStop(); bot->InterruptNonMeleeSpells(true); @@ -2276,87 +2056,7 @@ bool LadyVashjTeleportToTaintedElementalAction::Execute(Event /*event*/) return false; } -bool LadyVashjLootTaintedCoreAction::Execute(Event) -{ - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - if (!vashj) - return false; - - auto const& corpses = context->GetValue("nearest corpses")->Get(); - const float maxLootRange = sPlayerbotAIConfig.lootDistance; - - for (auto const& guid : corpses) - { - LootObject loot(bot, guid); - if (!loot.IsLootPossible(bot)) - continue; - - WorldObject* object = loot.GetWorldObject(bot); - if (!object) - continue; - - Creature* creature = object->ToCreature(); - if (!creature || creature->GetEntry() != NPC_TAINTED_ELEMENTAL || creature->IsAlive()) - continue; - - context->GetValue("loot target")->Set(loot); - - float dist = bot->GetDistance(object); - if (dist > maxLootRange) - return MoveTo(object, 2.0f, MovementPriority::MOVEMENT_FORCED); - - OpenLootAction open(botAI); - if (!open.Execute(Event())) - return false; - - if (Group* group = bot->GetGroup()) - { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->HasItemCount(ITEM_TAINTED_CORE, 1, false)) - return true; - } - } - - const ObjectGuid botGuid = bot->GetGUID(); - const ObjectGuid corpseGuid = guid; - constexpr uint8 coreIndex = 0; - - botAI->AddTimedEvent([botGuid, corpseGuid, coreIndex, vashj]() - { - Player* receiver = botGuid.IsEmpty() ? nullptr : ObjectAccessor::FindPlayer(botGuid); - if (!receiver) - return; - - if (Group* group = receiver->GetGroup()) - { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->HasItemCount(ITEM_TAINTED_CORE, 1, false)) - return; - } - } - - receiver->SetLootGUID(corpseGuid); - - WorldPacket* packet = new WorldPacket(CMSG_AUTOSTORE_LOOT_ITEM, 1); - *packet << coreIndex; - receiver->GetSession()->QueuePacket(packet); - - const uint32 instanceId = vashj->GetMap()->GetInstanceId(); - const time_t now = std::time(nullptr); - lastCoreInInventoryTime.insert_or_assign(instanceId, now); - }, 600); - - return true; - } - - return false; -} - -bool LadyVashjPassTheTaintedCoreAction::Execute(Event /*event*/) +bool LadyVashjLootTaintedCoreAction::Execute(Event /*event*/) { Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); if (!vashj) @@ -2366,36 +2066,78 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event /*event*/) if (!group) return false; - Player* designatedLooter = GetDesignatedCoreLooter(group, botAI); - if (!designatedLooter) + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->HasItemCount(ITEM_TAINTED_CORE, 1, false)) + return false; + } + + constexpr float searchRadius = 150.0f; + Creature* elemental = bot->FindNearestCreature(NPC_TAINTED_ELEMENTAL, searchRadius, false); + + if (!elemental || elemental->IsAlive()) return false; - Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI); - Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI); - Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI); - Player* fourthCorePasser = GetFourthTaintedCorePasser(group, botAI); + LootObject loot(bot, elemental->GetGUID()); + if (!loot.IsLootPossible(bot)) + return false; + + context->GetValue("loot target")->Set(loot); + + const float maxLootRange = sPlayerbotAIConfig.lootDistance; + constexpr float distFromObject = 2.0f; + + if (bot->GetDistance(elemental) > maxLootRange) + return MoveTo(elemental, distFromObject, MovementPriority::MOVEMENT_FORCED); + + OpenLootAction open(botAI); + if (!open.Execute(Event())) + return false; + + bot->SetLootGUID(elemental->GetGUID()); + constexpr uint8 coreIndex = 0; + WorldPacket* packet = new WorldPacket(CMSG_AUTOSTORE_LOOT_ITEM, 1); + *packet << coreIndex; + bot->GetSession()->QueuePacket(packet); + + const time_t now = std::time(nullptr); + lastCoreInInventoryTime.insert_or_assign(bot->GetGUID(), now); + + return true; +} + +bool LadyVashjPassTheTaintedCoreAction::Execute(Event /*event*/) +{ + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + if (!vashj) + return false; + + Player* designatedLooter = GetDesignatedCoreLooter(botAI, bot); + Player* firstCorePasser = GetFirstTaintedCorePasser(botAI, bot); + Player* secondCorePasser = GetSecondTaintedCorePasser(botAI, bot); + Player* thirdCorePasser = GetThirdTaintedCorePasser(botAI, bot); + Player* fourthCorePasser = GetFourthTaintedCorePasser(botAI, bot); + const uint32 instanceId = vashj->GetMap()->GetInstanceId(); Unit* closestTrigger = nullptr; - if (Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental")) + if (Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental"); + (closestTrigger = GetNearestActiveShieldGeneratorTriggerByEntry(tainted))) { - closestTrigger = GetNearestActiveShieldGeneratorTriggerByEntry(tainted); - if (closestTrigger) - nearestTriggerGuid.insert_or_assign(instanceId, closestTrigger->GetGUID()); + nearestTriggerGuid.try_emplace(instanceId, closestTrigger->GetGUID()); } auto itSnap = nearestTriggerGuid.find(instanceId); if (itSnap != nearestTriggerGuid.end() && !itSnap->second.IsEmpty()) { - Unit* snapUnit = botAI->GetUnit(itSnap->second); - if (snapUnit) + if (Unit* snapUnit = botAI->GetUnit(itSnap->second)) closestTrigger = snapUnit; else nearestTriggerGuid.erase(instanceId); } - if (!firstCorePasser || !secondCorePasser || !thirdCorePasser || - !fourthCorePasser || !closestTrigger) + if (!closestTrigger) return false; // Not gated behind CheatMask because the auto application of Fear Ward is necessary @@ -2408,63 +2150,57 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event /*event*/) if (!item || !botAI->HasItemInInventory(ITEM_TAINTED_CORE)) { // Passer order: HealAssistantOfIndex 0, 1, 2, then RangedDpsAssistantOfIndex 0 - if (bot == firstCorePasser) + if (bot == firstCorePasser && + LineUpFirstCorePasser(designatedLooter, closestTrigger)) { - if (LineUpFirstCorePasser(designatedLooter, closestTrigger)) - return true; + return true; } - else if (bot == secondCorePasser) + else if (bot == secondCorePasser && + LineUpSecondCorePasser(firstCorePasser, closestTrigger)) { - if (LineUpSecondCorePasser(firstCorePasser, closestTrigger)) - return true; + return true; } - else if (bot == thirdCorePasser) + else if (bot == thirdCorePasser && LineUpThirdCorePasser( + designatedLooter, firstCorePasser, secondCorePasser, closestTrigger)) { - if (LineUpThirdCorePasser(designatedLooter, firstCorePasser, - secondCorePasser, closestTrigger)) - return true; + return true; } - else if (bot == fourthCorePasser) + else if (bot == fourthCorePasser && LineUpFourthCorePasser( + firstCorePasser, secondCorePasser, thirdCorePasser, closestTrigger)) { - if (LineUpFourthCorePasser(firstCorePasser, secondCorePasser, - thirdCorePasser, closestTrigger)) - return true; + return true; } } else if (item && botAI->HasItemInInventory(ITEM_TAINTED_CORE)) { // Designated core looter logic // Applicable only if cheat mode is on and thus looter is a bot - if (bot == designatedLooter) - { - if (IsFirstCorePasserInIntendedPosition( - designatedLooter, firstCorePasser, closestTrigger)) - { - const time_t now = std::time(nullptr); - auto it = lastImbueAttempt.find(instanceId); - if (it == lastImbueAttempt.end() || (now - it->second) >= 2) - { - lastImbueAttempt.insert_or_assign(instanceId, now); - lastCoreInInventoryTime.insert_or_assign(instanceId, now); - botAI->ImbueItem(item, firstCorePasser); - intendedLineup.erase(bot->GetGUID()); - ScheduleTransferCoreAfterImbue(botAI, bot, firstCorePasser); - return true; - } - } - } - // First core passer: receive core from looter at the top of the stairs, - // pass to second core passer - else if (bot == firstCorePasser) + if (bot == designatedLooter && + IsFirstCorePasserInPosition(designatedLooter, firstCorePasser, closestTrigger)) + { + const time_t now = std::time(nullptr); + auto it = lastImbueAttempt.find(instanceId); + if (it == lastImbueAttempt.end() || (now - it->second) >= 2) + { + lastImbueAttempt.insert_or_assign(instanceId, now); + botAI->ImbueItem(item, firstCorePasser); + lastCoreInInventoryTime.insert_or_assign(bot->GetGUID(), now); + ScheduleTransferCoreAfterImbue(botAI, bot, firstCorePasser); + return true; + } + } + // First core passer: receive core from looter at the top of the stairs, + // pass to second core passer + else if (bot == firstCorePasser && + IsSecondCorePasserInPosition(firstCorePasser, secondCorePasser, closestTrigger)) { const time_t now = std::time(nullptr); auto it = lastImbueAttempt.find(instanceId); if (it == lastImbueAttempt.end() || (now - it->second) >= 2) { lastImbueAttempt.insert_or_assign(instanceId, now); - lastCoreInInventoryTime.insert_or_assign(instanceId, now); botAI->ImbueItem(item, secondCorePasser); - intendedLineup.erase(bot->GetGUID()); + lastCoreInInventoryTime.insert_or_assign(bot->GetGUID(), now); ScheduleTransferCoreAfterImbue(botAI, bot, secondCorePasser); return true; } @@ -2472,55 +2208,41 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event /*event*/) // Second core passer: if closest usable generator is within passing distance // of the first passer, move to the generator; otherwise, move as close as // possible to the generator while staying in passing range - else if (bot == secondCorePasser) + else if (bot == secondCorePasser && !UseCoreOnNearestGenerator(instanceId) && + IsThirdCorePasserInPosition(secondCorePasser, thirdCorePasser, closestTrigger)) { - if (!UseCoreOnNearestGenerator(instanceId)) + const time_t now = std::time(nullptr); + auto it = lastImbueAttempt.find(instanceId); + if (it == lastImbueAttempt.end() || (now - it->second) >= 2) { - if (IsThirdCorePasserInIntendedPosition( - secondCorePasser, thirdCorePasser, closestTrigger)) - { - const time_t now = std::time(nullptr); - auto it = lastImbueAttempt.find(instanceId); - if (it == lastImbueAttempt.end() || (now - it->second) >= 2) - { - lastImbueAttempt.insert_or_assign(instanceId, now); - lastCoreInInventoryTime.insert_or_assign(instanceId, now); - botAI->ImbueItem(item, thirdCorePasser); - intendedLineup.erase(bot->GetGUID()); - ScheduleTransferCoreAfterImbue(botAI, bot, thirdCorePasser); - return true; - } - } + lastImbueAttempt.insert_or_assign(instanceId, now); + botAI->ImbueItem(item, thirdCorePasser); + lastCoreInInventoryTime.insert_or_assign(bot->GetGUID(), now); + ScheduleTransferCoreAfterImbue(botAI, bot, thirdCorePasser); + return true; } } // Third core passer: if closest usable generator is within passing distance // of the second passer, move to the generator; otherwise, move as close as // possible to the generator while staying in passing range - else if (bot == thirdCorePasser) + else if (bot == thirdCorePasser && !UseCoreOnNearestGenerator(instanceId) && + IsFourthCorePasserInPosition(thirdCorePasser, fourthCorePasser, closestTrigger)) { - if (!UseCoreOnNearestGenerator(instanceId)) + const time_t now = std::time(nullptr); + auto it = lastImbueAttempt.find(instanceId); + if (it == lastImbueAttempt.end() || (now - it->second) >= 2) { - if (IsFourthCorePasserInIntendedPosition( - thirdCorePasser, fourthCorePasser, closestTrigger)) - { - const time_t now = std::time(nullptr); - auto it = lastImbueAttempt.find(instanceId); - if (it == lastImbueAttempt.end() || (now - it->second) >= 2) - { - lastImbueAttempt.insert_or_assign(instanceId, now); - lastCoreInInventoryTime.insert_or_assign(instanceId, now); - botAI->ImbueItem(item, fourthCorePasser); - intendedLineup.erase(bot->GetGUID()); - ScheduleTransferCoreAfterImbue(botAI, bot, fourthCorePasser); - return true; - } - } + lastImbueAttempt.insert_or_assign(instanceId, now); + botAI->ImbueItem(item, fourthCorePasser); + lastCoreInInventoryTime.insert_or_assign(bot->GetGUID(), now); + ScheduleTransferCoreAfterImbue(botAI, bot, fourthCorePasser); + return true; } } // Fourth core passer: the fourth passer is rarely needed and no more than // four ever should be, so it should use the Core on the nearest generator - else if (bot == fourthCorePasser) - UseCoreOnNearestGenerator(instanceId); + else if (bot == fourthCorePasser && UseCoreOnNearestGenerator(instanceId)) + return true; } return false; @@ -2533,15 +2255,25 @@ bool LadyVashjPassTheTaintedCoreAction::LineUpFirstCorePasser( const float centerY = VASHJ_PLATFORM_CENTER_POSITION.GetPositionY(); constexpr float radius = 57.5f; - float mx = designatedLooter->GetPositionX(); - float my = designatedLooter->GetPositionY(); - float angle = atan2(my - centerY, mx - centerX); + auto it = intendedLineup.find(bot->GetGUID()); + if (it == intendedLineup.end()) + { + float mx = designatedLooter->GetPositionX(); + float my = designatedLooter->GetPositionY(); + float angle = atan2(my - centerY, mx - centerX); - float targetX = centerX + radius * std::cos(angle); - float targetY = centerY + radius * std::sin(angle); - constexpr float targetZ = 41.097f; + float targetX = centerX + radius * std::cos(angle); + float targetY = centerY + radius * std::sin(angle); + constexpr float targetZ = VASHJ_PLATFORM_CENTER_Z; - intendedLineup.insert_or_assign(bot->GetGUID(), Position(targetX, targetY, targetZ)); + intendedLineup.try_emplace(bot->GetGUID(), Position(targetX, targetY, targetZ)); + it = intendedLineup.find(bot->GetGUID()); + } + + const Position& pos = it->second; + float targetX = pos.GetPositionX(); + float targetY = pos.GetPositionY(); + float targetZ = pos.GetPositionZ(); bot->AttackStop(); bot->InterruptNonMeleeSpells(true); @@ -2552,143 +2284,183 @@ bool LadyVashjPassTheTaintedCoreAction::LineUpFirstCorePasser( bool LadyVashjPassTheTaintedCoreAction::LineUpSecondCorePasser( Player* firstCorePasser, Unit* closestTrigger) { - float fx = firstCorePasser->GetPositionX(); - float fy = firstCorePasser->GetPositionY(); - - float dx = closestTrigger->GetPositionX() - fx; - float dy = closestTrigger->GetPositionY() - fy; - float distToTrigger = firstCorePasser->GetExactDist2d(closestTrigger); - - if (distToTrigger == 0.0f) + auto itFirst = intendedLineup.find(firstCorePasser->GetGUID()); + if (itFirst == intendedLineup.end()) return false; - dx /= distToTrigger; dy /= distToTrigger; + const Position& firstLineup = itFirst->second; - // Target is on a line between firstCorePasser and closestTrigger - float targetX, targetY, targetZ; - // If firstCorePasser is within thresholdDist of closestTrigger, - // go to nearTriggerDist short of closestTrigger - constexpr float thresholdDist = 40.0f; - constexpr float nearTriggerDist = 1.5f; - // If firstCorePasser is not thresholdDist yards from closestTrigger, - // go to farDistance from firstCorePasser - constexpr float farDistance = 38.0f; + auto itSecond = intendedLineup.find(bot->GetGUID()); + if (itSecond == intendedLineup.end()) + { + float fx = itFirst->second.GetPositionX(); + float fy = itFirst->second.GetPositionY(); - if (distToTrigger <= thresholdDist) - { - float moveDist = std::max(distToTrigger - nearTriggerDist, 0.0f); - targetX = fx + dx * moveDist; - targetY = fy + dy * moveDist; - } - else - { - targetX = fx + dx * farDistance; - targetY = fy + dy * farDistance; + float dx = closestTrigger->GetPositionX() - fx; + float dy = closestTrigger->GetPositionY() - fy; + float distToTrigger = std::sqrt(dx * dx + dy * dy); + + if (distToTrigger == 0.0f) + return false; + + dx /= distToTrigger; dy /= distToTrigger; + + float targetX, targetY; + constexpr float targetZ = VASHJ_PLATFORM_CENTER_Z; + constexpr float thresholdDist = 40.0f; + constexpr float nearTriggerDist = 1.5f; + constexpr float farDistance = 38.0f; + + if (distToTrigger <= thresholdDist) + { + float moveDist = std::max(distToTrigger - nearTriggerDist, 0.0f); + targetX = fx + dx * moveDist; + targetY = fy + dy * moveDist; + } + else + { + targetX = fx + dx * farDistance; + targetY = fy + dy * farDistance; + } + + intendedLineup.try_emplace(bot->GetGUID(), Position(targetX, targetY, targetZ)); + itSecond = intendedLineup.find(bot->GetGUID()); } - intendedLineup.insert_or_assign(bot->GetGUID(), Position(targetX, targetY, VASHJ_PLATFORM_Z)); + const Position& pos = itSecond->second; + float targetX = pos.GetPositionX(); + float targetY = pos.GetPositionY(); + float targetZ = pos.GetPositionZ(); bot->AttackStop(); bot->InterruptNonMeleeSpells(true); - return MoveTo(SSC_MAP_ID, targetX, targetY, VASHJ_PLATFORM_Z, false, false, false, true, + return MoveTo(SSC_MAP_ID, targetX, targetY, targetZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); } bool LadyVashjPassTheTaintedCoreAction::LineUpThirdCorePasser( - Player* designatedLooter, Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger) + Player* designatedLooter, Player* firstCorePasser, + Player* secondCorePasser, Unit* closestTrigger) { - // Wait to move until it is clear that a third passer is needed - bool needThird = - (IsFirstCorePasserInIntendedPosition(designatedLooter, firstCorePasser, closestTrigger) && + bool needThirdPasser = + (IsFirstCorePasserInPosition(designatedLooter, firstCorePasser, closestTrigger) && firstCorePasser->GetExactDist2d(closestTrigger) > 42.0f) || - (IsSecondCorePasserInIntendedPosition(firstCorePasser, secondCorePasser, closestTrigger) && + (IsSecondCorePasserInPosition(firstCorePasser, secondCorePasser, closestTrigger) && secondCorePasser->GetExactDist2d(closestTrigger) > 4.0f); - if (!needThird) + if (!needThirdPasser) return false; - float sx = secondCorePasser->GetPositionX(); - float sy = secondCorePasser->GetPositionY(); - - float dx = closestTrigger->GetPositionX() - sx; - float dy = closestTrigger->GetPositionY() - sy; - float distToTrigger = secondCorePasser->GetExactDist2d(closestTrigger); - - if (distToTrigger == 0.0f) + auto itSecond = intendedLineup.find(secondCorePasser->GetGUID()); + if (itSecond == intendedLineup.end()) return false; - dx /= distToTrigger; dy /= distToTrigger; - - float targetX, targetY, targetZ; - constexpr float thresholdDist = 40.0f; - constexpr float nearTriggerDist = 1.5f; - constexpr float farDistance = 38.0f; - - if (distToTrigger <= thresholdDist) + auto itThird = intendedLineup.find(bot->GetGUID()); + if (itThird == intendedLineup.end()) { - float moveDist = std::max(distToTrigger - nearTriggerDist, 0.0f); - targetX = sx + dx * moveDist; - targetY = sy + dy * moveDist; - } - else - { - targetX = sx + dx * farDistance; - targetY = sy + dy * farDistance; + float sx = itSecond->second.GetPositionX(); + float sy = itSecond->second.GetPositionY(); + + float dx = closestTrigger->GetPositionX() - sx; + float dy = closestTrigger->GetPositionY() - sy; + float distToTrigger = std::sqrt(dx * dx + dy * dy); + + if (distToTrigger == 0.0f) + return false; + + dx /= distToTrigger; dy /= distToTrigger; + + float targetX, targetY; + constexpr float targetZ = VASHJ_PLATFORM_CENTER_Z; + constexpr float thresholdDist = 40.0f; + constexpr float nearTriggerDist = 1.5f; + constexpr float farDistance = 38.0f; + + if (distToTrigger <= thresholdDist) + { + float moveDist = std::max(distToTrigger - nearTriggerDist, 0.0f); + targetX = sx + dx * moveDist; + targetY = sy + dy * moveDist; + } + else + { + targetX = sx + dx * farDistance; + targetY = sy + dy * farDistance; + } + + intendedLineup.try_emplace(bot->GetGUID(), Position(targetX, targetY, targetZ)); + itThird = intendedLineup.find(bot->GetGUID()); } - intendedLineup.insert_or_assign(bot->GetGUID(), Position(targetX, targetY, VASHJ_PLATFORM_Z)); + const Position& pos = itThird->second; + float targetX = pos.GetPositionX(); + float targetY = pos.GetPositionY(); + float targetZ = pos.GetPositionZ(); bot->AttackStop(); bot->InterruptNonMeleeSpells(true); - return MoveTo(SSC_MAP_ID, targetX, targetY, VASHJ_PLATFORM_Z, false, false, false, true, + return MoveTo(SSC_MAP_ID, targetX, targetY, targetZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); - - return false; } bool LadyVashjPassTheTaintedCoreAction::LineUpFourthCorePasser( - Player* firstCorePasser, Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger) + Player* firstCorePasser, Player* secondCorePasser, + Player* thirdCorePasser, Unit* closestTrigger) { - // Wait to move until it is clear that a fourth passer is needed - bool needFourth = - (IsSecondCorePasserInIntendedPosition(firstCorePasser, secondCorePasser, closestTrigger) && + bool needFourthPasser = + (IsSecondCorePasserInPosition(firstCorePasser, secondCorePasser, closestTrigger) && secondCorePasser->GetExactDist2d(closestTrigger) > 42.0f) || - (IsThirdCorePasserInIntendedPosition(secondCorePasser, thirdCorePasser, closestTrigger) && + (IsThirdCorePasserInPosition(secondCorePasser, thirdCorePasser, closestTrigger) && thirdCorePasser->GetExactDist2d(closestTrigger) > 4.0f); - if (!needFourth) + if (!needFourthPasser) return false; - float sx = thirdCorePasser->GetPositionX(); - float sy = thirdCorePasser->GetPositionY(); - - float tx = closestTrigger->GetPositionX(); - float ty = closestTrigger->GetPositionY(); - - float dx = tx - sx; - float dy = ty - sy; - float distToTrigger = thirdCorePasser->GetExactDist2d(closestTrigger); - - if (distToTrigger == 0.0f) + auto itThird = intendedLineup.find(thirdCorePasser->GetGUID()); + if (itThird == intendedLineup.end()) return false; - dx /= distToTrigger; dy /= distToTrigger; + auto itFourth = intendedLineup.find(bot->GetGUID()); + if (itFourth == intendedLineup.end()) + { + float sx = itThird->second.GetPositionX(); + float sy = itThird->second.GetPositionY(); - constexpr float nearTriggerDist = 1.5f; - float targetX = tx - dx * nearTriggerDist; - float targetY = ty - dy * nearTriggerDist; + float tx = closestTrigger->GetPositionX(); + float ty = closestTrigger->GetPositionY(); - intendedLineup.insert_or_assign(bot->GetGUID(), Position(targetX, targetY, VASHJ_PLATFORM_Z)); + float dx = tx - sx; + float dy = ty - sy; + float distToTrigger = std::sqrt(dx * dx + dy * dy); + + if (distToTrigger == 0.0f) + return false; + + dx /= distToTrigger; dy /= distToTrigger; + + constexpr float nearTriggerDist = 1.5f; + float targetX = tx - dx * nearTriggerDist; + float targetY = ty - dy * nearTriggerDist; + constexpr float targetZ = VASHJ_PLATFORM_CENTER_Z; + + intendedLineup.try_emplace(bot->GetGUID(), Position(targetX, targetY, targetZ)); + itFourth = intendedLineup.find(bot->GetGUID()); + } + + const Position& pos = itFourth->second; + float targetX = pos.GetPositionX(); + float targetY = pos.GetPositionY(); + float targetZ = pos.GetPositionZ(); bot->AttackStop(); bot->InterruptNonMeleeSpells(true); - return MoveTo(SSC_MAP_ID, targetX, targetY, VASHJ_PLATFORM_Z, false, false, false, true, + return MoveTo(SSC_MAP_ID, targetX, targetY, targetZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); } // The next four functions check if the respective passer is <= 2 yards of their intended // position and are used to determine when the prior bot in the chain can pass the core -bool LadyVashjPassTheTaintedCoreAction::IsFirstCorePasserInIntendedPosition( +bool LadyVashjPassTheTaintedCoreAction::IsFirstCorePasserInPosition( Player* designatedLooter, Player* firstCorePasser, Unit* closestTrigger) { auto itSnap = intendedLineup.find(firstCorePasser->GetGUID()); @@ -2702,7 +2474,7 @@ bool LadyVashjPassTheTaintedCoreAction::IsFirstCorePasserInIntendedPosition( return false; } -bool LadyVashjPassTheTaintedCoreAction::IsSecondCorePasserInIntendedPosition( +bool LadyVashjPassTheTaintedCoreAction::IsSecondCorePasserInPosition( Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger) { auto itSnap = intendedLineup.find(secondCorePasser->GetGUID()); @@ -2716,7 +2488,7 @@ bool LadyVashjPassTheTaintedCoreAction::IsSecondCorePasserInIntendedPosition( return false; } -bool LadyVashjPassTheTaintedCoreAction::IsThirdCorePasserInIntendedPosition( +bool LadyVashjPassTheTaintedCoreAction::IsThirdCorePasserInPosition( Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger) { auto itSnap = intendedLineup.find(thirdCorePasser->GetGUID()); @@ -2730,7 +2502,7 @@ bool LadyVashjPassTheTaintedCoreAction::IsThirdCorePasserInIntendedPosition( return false; } -bool LadyVashjPassTheTaintedCoreAction::IsFourthCorePasserInIntendedPosition( +bool LadyVashjPassTheTaintedCoreAction::IsFourthCorePasserInPosition( Player* thirdCorePasser, Player* fourthCorePasser, Unit* closestTrigger) { auto itSnap = intendedLineup.find(fourthCorePasser->GetGUID()); @@ -2799,17 +2571,11 @@ bool LadyVashjPassTheTaintedCoreAction::UseCoreOnNearestGenerator(const uint32 i return false; GameObject* generator = botAI->GetGameObject(nearestGen->guid); - if (!generator) - return false; - - if (bot->GetExactDist2d(generator) > 4.5f) + if (!generator || bot->GetExactDist2d(generator) > 4.5f) return false; Item* core = bot->GetItemByEntry(ITEM_TAINTED_CORE); - if (!core) - return false; - - if (bot->CanUseItem(core) != EQUIP_ERR_OK) + if (!core || bot->CanUseItem(core) != EQUIP_ERR_OK) return false; if (bot->IsNonMeleeSpellCast(false)) @@ -2845,9 +2611,18 @@ bool LadyVashjPassTheTaintedCoreAction::UseCoreOnNearestGenerator(const uint32 i packet << generator->GetGUID().WriteAsPacked(); bot->GetSession()->HandleUseItemOpcode(packet); - nearestTriggerGuid.erase(instanceId); + lastImbueAttempt.erase(instanceId); - lastCoreInInventoryTime.erase(instanceId); + auto coreHandlers = GetCoreHandlers(botAI, bot); + for (Player* handler : coreHandlers) + { + if (handler) + { + intendedLineup.erase(handler->GetGUID()); + lastCoreInInventoryTime.erase(handler->GetGUID()); + } + } + return true; } @@ -2864,35 +2639,12 @@ bool LadyVashjDestroyTaintedCoreAction::Execute(Event /*event*/) return false; } -// This needs to be separate from the general map erasing logic because -// Bots may end up out of combat during the Vashj encounter -bool LadyVashjEraseCorePassingTrackersAction::Execute(Event /*event*/) -{ - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - if (!vashj) - return false; - - const uint32 instanceId = vashj->GetMap()->GetInstanceId(); - - bool erased = false; - if (nearestTriggerGuid.erase(instanceId) > 0) - erased = true; - if (lastImbueAttempt.erase(instanceId) > 0) - erased = true; - if (lastCoreInInventoryTime.erase(instanceId) > 0) - erased = true; - if (intendedLineup.erase(bot->GetGUID()) > 0) - erased = true; - - return erased; -} - // The standard "avoid aoe" strategy does work for Toxic Spores, but this method // provides more buffer distance and limits the area in which bots can move // so that they do not go down the stairs bool LadyVashjAvoidToxicSporesAction::Execute(Event /*event*/) { - auto const& spores = GetAllSporeDropTriggers(botAI, bot); + auto const& spores = GetAllSporeDropTriggers(bot); if (spores.empty()) return false; @@ -2910,16 +2662,21 @@ bool LadyVashjAvoidToxicSporesAction::Execute(Event /*event*/) if (!inDanger) return false; + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + if (!vashj) + return false; + const Position& vashjCenter = VASHJ_PLATFORM_CENTER_POSITION; constexpr float maxRadius = 60.0f; Position safestPos = FindSafestNearbyPosition(spores, vashjCenter, maxRadius, hazardRadius); + bool backwards = vashj->GetVictim() == bot; + MovementPriority priority = backwards ? + MovementPriority::MOVEMENT_FORCED : MovementPriority::MOVEMENT_COMBAT; - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - bool backwards = (vashj && vashj->GetVictim() == bot); return MoveTo(SSC_MAP_ID, safestPos.GetPositionX(), safestPos.GetPositionY(), safestPos.GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_COMBAT, true, backwards); + priority, true, backwards); } Position LadyVashjAvoidToxicSporesAction::FindSafestNearbyPosition( @@ -3015,19 +2772,18 @@ bool LadyVashjAvoidToxicSporesAction::IsPathSafeFromSpores(const Position& start // When Toxic Sporebats spit poison, they summon "Spore Drop Trigger" NPCs // that create the toxic pools -std::vector LadyVashjAvoidToxicSporesAction::GetAllSporeDropTriggers( - PlayerbotAI* botAI, Player* bot) +std::vector LadyVashjAvoidToxicSporesAction::GetAllSporeDropTriggers(Player* bot) { std::vector sporeDropTriggers; - auto const& npcs = - botAI->GetAiObjectContext()->GetValue("nearest npcs")->Get(); - for (auto const& npcGuid : npcs) + std::list creatureList; + constexpr float searchRadius = 50.0f; + + bot->GetCreatureListWithEntryInGrid(creatureList, NPC_SPORE_DROP_TRIGGER, searchRadius); + + for (Creature* creature : creatureList) { - constexpr float maxSearchRadius = 40.0f; - Unit* unit = botAI->GetUnit(npcGuid); - if (unit && unit->GetEntry() == NPC_SPORE_DROP_TRIGGER && - bot->GetExactDist2d(unit) < maxSearchRadius) - sporeDropTriggers.push_back(unit); + if (creature && creature->IsAlive()) + sporeDropTriggers.push_back(creature); } return sporeDropTriggers; @@ -3040,7 +2796,7 @@ bool LadyVashjUseFreeActionAbilitiesAction::Execute(Event /*event*/) return false; auto const& spores = - LadyVashjAvoidToxicSporesAction::GetAllSporeDropTriggers(botAI, bot); + LadyVashjAvoidToxicSporesAction::GetAllSporeDropTriggers(bot); constexpr float toxicSporeRadius = 6.0f; // If Rogues are Entangled and either have Static Charge or @@ -3109,19 +2865,13 @@ bool LadyVashjUseFreeActionAbilitiesAction::Execute(Event /*event*/) { // Priority 1: Entangled in Toxic Spores (prefer main tank) Player* toxicTarget = mainTankToxic ? mainTankToxic : anyToxic; - if (toxicTarget) - { - if (botAI->CanCastSpell("hand of freedom", toxicTarget)) - return botAI->CastSpell("hand of freedom", toxicTarget); - } + if (toxicTarget && botAI->CanCastSpell("hand of freedom", toxicTarget)) + return botAI->CastSpell("hand of freedom", toxicTarget); // Priority 2: Entangled with Static Charge (prefer main tank) Player* staticTarget = mainTankStatic ? mainTankStatic : anyStatic; - if (staticTarget) - { - if (botAI->CanCastSpell("hand of freedom", staticTarget)) - return botAI->CastSpell("hand of freedom", staticTarget); - } + if (staticTarget && botAI->CanCastSpell("hand of freedom", staticTarget)) + return botAI->CastSpell("hand of freedom", staticTarget); } return false; diff --git a/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.h b/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.h index cbd23740..08ad4c48 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.h +++ b/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.h @@ -1,3 +1,8 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + #ifndef _PLAYERBOT_RAIDSSCACTIONS_H #define _PLAYERBOT_RAIDSSCACTIONS_H @@ -75,8 +80,8 @@ public: bool Execute(Event event) override; private: - bool TryMisdirectToFrostTank(Unit* hydross, Group* group); - bool TryMisdirectToNatureTank(Unit* hydross, Group* group); + bool TryMisdirectToFrostTank(Unit* hydross); + bool TryMisdirectToNatureTank(Unit* hydross); }; class HydrossTheUnstableStopDpsUponPhaseChangeAction : public Action @@ -413,10 +418,10 @@ private: bool LineUpSecondCorePasser(Player* firstCorePasser, Unit* closestTrigger); bool LineUpThirdCorePasser(Player* designatedLooter, Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger); bool LineUpFourthCorePasser(Player* firstCorePasser, Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger); - bool IsFirstCorePasserInIntendedPosition(Player* designatedLooter, Player* firstCorePasser, Unit* closestTrigger); - bool IsSecondCorePasserInIntendedPosition(Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger); - bool IsThirdCorePasserInIntendedPosition(Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger); - bool IsFourthCorePasserInIntendedPosition(Player* thirdCorePasser, Player* fourthCorePasser, Unit* closestTrigger); + bool IsFirstCorePasserInPosition(Player* designatedLooter, Player* firstCorePasser, Unit* closestTrigger); + bool IsSecondCorePasserInPosition(Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger); + bool IsThirdCorePasserInPosition(Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger); + bool IsFourthCorePasserInPosition(Player* thirdCorePasser, Player* fourthCorePasser, Unit* closestTrigger); void ScheduleTransferCoreAfterImbue(PlayerbotAI* botAI, Player* giver, Player* receiver); bool UseCoreOnNearestGenerator(const uint32 instanceId); }; @@ -428,19 +433,12 @@ public: bool Execute(Event event) override; }; -class LadyVashjEraseCorePassingTrackersAction : public Action -{ -public: - LadyVashjEraseCorePassingTrackersAction(PlayerbotAI* botAI, std::string const name = "lady vashj erase core passing trackers") : Action(botAI, name) {} - bool Execute(Event event) override; -}; - class LadyVashjAvoidToxicSporesAction : public MovementAction { public: LadyVashjAvoidToxicSporesAction(PlayerbotAI* botAI, std::string const name = "lady vashj avoid toxic spores") : MovementAction(botAI, name) {} bool Execute(Event event) override; - static std::vector GetAllSporeDropTriggers(PlayerbotAI* botAI, Player* bot); + static std::vector GetAllSporeDropTriggers(Player* bot); private: Position FindSafestNearbyPosition(const std::vector& spores, const Position& position, float maxRadius, float hazardRadius); diff --git a/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp b/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp index c99cafa3..c841c7dc 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp @@ -1,3 +1,8 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + #include "RaidSSCMultipliers.h" #include "RaidSSCActions.h" #include "RaidSSCHelpers.h" @@ -28,12 +33,10 @@ using namespace SerpentShrineCavernHelpers; float UnderbogColossusEscapeToxicPoolMultiplier::GetValue(Action* action) { - if (bot->HasAura(SPELL_TOXIC_POOL)) - { - if (dynamic_cast(action) && - !dynamic_cast(action)) - return 0.0f; - } + if (bot->HasAura(SPELL_TOXIC_POOL) && + dynamic_cast(action) && + !dynamic_cast(action)) + return 0.0f; return 1.0f; } @@ -53,16 +56,16 @@ float HydrossTheUnstableDisableTankActionsMultiplier::GetValue(Action* action) dynamic_cast(action)) return 0.0f; + if ((botAI->IsMainTank(bot) && !hydross->HasAura(SPELL_CORRUPTION)) || + (botAI->IsAssistTankOfIndex(bot, 0, true) && hydross->HasAura(SPELL_CORRUPTION))) + return 1.0f; + if (dynamic_cast(action) || dynamic_cast(action) || (dynamic_cast(action) && !dynamic_cast(action) && !dynamic_cast(action))) - { - if ((botAI->IsMainTank(bot) && hydross->HasAura(SPELL_CORRUPTION)) || - (botAI->IsAssistTankOfIndex(bot, 0, true) && !hydross->HasAura(SPELL_CORRUPTION))) - return 0.0f; - } + return 0.0f; return 1.0f; } @@ -97,13 +100,13 @@ float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action) bool aboutToChange = (itPhase != hydrossChangeToFrostPhaseTimer.end() && (now - itPhase->second) > phaseChangeWaitSeconds); - if (justChanged || aboutToChange) - { - if (dynamic_cast(action) || - (dynamic_cast(action) && - !dynamic_cast(action))) - return 0.0f; - } + if (!justChanged && !aboutToChange) + return 1.0f; + + if (dynamic_cast(action) || + (dynamic_cast(action) && + !dynamic_cast(action))) + return 0.0f; } if (hydross->HasAura(SPELL_CORRUPTION) && !botAI->IsAssistTankOfIndex(bot, 0, true)) @@ -116,13 +119,13 @@ float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action) bool aboutToChange = (itPhase != hydrossChangeToNaturePhaseTimer.end() && (now - itPhase->second) > phaseChangeWaitSeconds); - if (justChanged || aboutToChange) - { - if (dynamic_cast(action) || - (dynamic_cast(action) && - !dynamic_cast(action))) - return 0.0f; - } + if (!justChanged && !aboutToChange) + return 1.0f; + + if (dynamic_cast(action) || + (dynamic_cast(action) && + !dynamic_cast(action))) + return 0.0f; } return 1.0f; @@ -133,11 +136,9 @@ float HydrossTheUnstableControlMisdirectionMultiplier::GetValue(Action* action) if (bot->getClass() != CLASS_HUNTER) return 1.0f; - if (AI_VALUE2(Unit*, "find target", "hydross the unstable")) - { - if (dynamic_cast(action)) - return 0.0f; - } + if (AI_VALUE2(Unit*, "find target", "hydross the unstable") && + dynamic_cast(action)) + return 0.0f; return 1.0f; } @@ -175,14 +176,14 @@ float TheLurkerBelowMaintainRangedSpreadMultiplier::GetValue(Action* action) if (!botAI->IsRanged(bot)) return 1.0f; - if (AI_VALUE2(Unit*, "find target", "the lurker below")) - { - if (dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action)) - return 0.0f; - } + if (!AI_VALUE2(Unit*, "find target", "the lurker below")) + return 1.0f; + + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; return 1.0f; } @@ -215,11 +216,11 @@ float TheLurkerBelowDisableTankAssistMultiplier::GetValue(Action* action) ++tankCount; } - if (tankCount >= 3) - { - if (dynamic_cast(action)) - return 0.0f; - } + if (tankCount < 3) + return 1.0f; + + if (dynamic_cast(action)) + return 0.0f; return 1.0f; } @@ -234,22 +235,18 @@ float LeotherasTheBlindAvoidWhirlwindMultiplier::GetValue(Action* action) if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) return 1.0f; - Unit* leotherasHuman = GetLeotherasHuman(botAI); - if (!leotherasHuman) + Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); + if (!leotheras || (!leotheras->HasAura(SPELL_WHIRLWIND) && + !leotheras->HasAura(SPELL_WHIRLWIND_CHANNEL))) return 1.0f; - if (!leotherasHuman->HasAura(SPELL_LEOTHERAS_BANISHED) && - (leotherasHuman->HasAura(SPELL_WHIRLWIND) || - leotherasHuman->HasAura(SPELL_WHIRLWIND_CHANNEL))) - { - if (dynamic_cast(action)) - return 0.0f; + if (dynamic_cast(action)) + return 0.0f; - if (dynamic_cast(action) && - !dynamic_cast(action) && - !dynamic_cast(action)) - return 0.0f; - } + if (dynamic_cast(action) && + !dynamic_cast(action) && + !dynamic_cast(action)) + return 0.0f; return 1.0f; } @@ -262,10 +259,10 @@ float LeotherasTheBlindDisableTankActionsMultiplier::GetValue(Action* action) if (!AI_VALUE2(Unit*, "find target", "leotheras the blind")) return 1.0f; - if (GetPhase2LeotherasDemon(botAI) && dynamic_cast(action)) + if (GetPhase2LeotherasDemon(bot) && dynamic_cast(action)) return 0.0f; - if (!GetPhase3LeotherasDemon(botAI) && dynamic_cast(action)) + if (!GetPhase3LeotherasDemon(bot) && dynamic_cast(action)) return 0.0f; return 1.0f; @@ -273,21 +270,21 @@ float LeotherasTheBlindDisableTankActionsMultiplier::GetValue(Action* action) float LeotherasTheBlindFocusOnInnerDemonMultiplier::GetValue(Action* action) { - if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) - { - if (dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action)) - return 0.0f; - } + if (!bot->HasAura(SPELL_INSIDIOUS_WHISPER)) + return 1.0f; + + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; return 1.0f; } @@ -297,19 +294,19 @@ float LeotherasTheBlindMeleeDpsAvoidChaosBlastMultiplier::GetValue(Action* actio if (botAI->IsRanged(bot) || botAI->IsTank(bot)) return 1.0f; - if (!GetPhase2LeotherasDemon(botAI)) + if (!GetPhase2LeotherasDemon(bot)) return 1.0f; Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST); - if (chaosBlast && chaosBlast->GetStackAmount() >= 5) - { - if (dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action)) - return 0.0f; - } + if (!chaosBlast || chaosBlast->GetStackAmount() < 5) + return 1.0f; + + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; return 1.0f; } @@ -330,8 +327,8 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) const time_t now = std::time(nullptr); constexpr uint8 dpsWaitSecondsPhase1 = 5; - Unit* leotherasHuman = GetLeotherasHuman(botAI); - Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI); + Unit* leotherasHuman = GetLeotherasHuman(bot); + Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(bot); if (leotherasHuman && !leotherasHuman->HasAura(SPELL_LEOTHERAS_BANISHED) && !leotherasPhase3Demon) { @@ -345,12 +342,12 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) if (dynamic_cast(action) || (dynamic_cast(action) && !dynamic_cast(action))) - return 0.0f; + return 0.0f; } } constexpr uint8 dpsWaitSecondsPhase2 = 12; - Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI); + Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(bot); Player* demonFormTank = GetLeotherasDemonFormTank(bot); if (leotherasPhase2Demon) { @@ -367,7 +364,7 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) if (dynamic_cast(action) || (dynamic_cast(action) && !dynamic_cast(action))) - return 0.0f; + return 0.0f; } } @@ -384,7 +381,7 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) if (dynamic_cast(action) || (dynamic_cast(action) && !dynamic_cast(action))) - return 0.0f; + return 0.0f; } } @@ -398,12 +395,12 @@ float LeotherasTheBlindDelayBloodlustAndHeroismMultiplier::GetValue(Action* acti return 1.0f; Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); - if (leotheras && leotheras->HasAura(SPELL_LEOTHERAS_BANISHED)) - { - if (dynamic_cast(action) || - dynamic_cast(action)) - return 0.0f; - } + if (!leotheras || !leotheras->HasAura(SPELL_LEOTHERAS_BANISHED)) + return 1.0f; + + if (dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; return 1.0f; } @@ -447,14 +444,12 @@ float FathomLordKarathressDisableAoeMultiplier::GetValue(Action* action) if (!botAI->IsDps(bot)) return 1.0f; - if (AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) - { - if (auto castSpellAction = dynamic_cast(action)) - { - if (castSpellAction->getThreatType() == Action::ActionThreatType::Aoe) - return 0.0f; - } - } + if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) + return 1.0f; + + auto castSpellAction = dynamic_cast(action); + if (castSpellAction && castSpellAction->getThreatType() == Action::ActionThreatType::Aoe) + return 0.0f; return 1.0f; } @@ -464,11 +459,11 @@ float FathomLordKarathressControlMisdirectionMultiplier::GetValue(Action* action if (bot->getClass() != CLASS_HUNTER) return 1.0f; - if (AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) - { - if (dynamic_cast(action)) - return 0.0f; - } + if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) + return 1.0f; + + if (dynamic_cast(action)) + return 0.0f; return 1.0f; } @@ -494,7 +489,7 @@ float FathomLordKarathressWaitForDpsMultiplier::GetValue(Action* action) if (dynamic_cast(action) || (dynamic_cast(action) && !dynamic_cast(action))) - return 0.0f; + return 0.0f; } return 1.0f; @@ -505,12 +500,12 @@ float FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier::GetValue if (!botAI->IsAssistHealOfIndex(bot, 0, true)) return 1.0f; - if (AI_VALUE2(Unit*, "find target", "fathom-guard caribdis")) - { - if (dynamic_cast(action) || - dynamic_cast(action)) - return 0.0f; - } + if (!AI_VALUE2(Unit*, "find target", "fathom-guard caribdis")) + return 1.0f; + + if (dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; return 1.0f; } @@ -526,12 +521,12 @@ float MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier::GetValue(Action* act if (!AI_VALUE2(Unit*, "find target", "morogrim tidewalker")) return 1.0f; - if (!AI_VALUE2(Unit*, "find target", "tidewalker lurker")) - { - if (dynamic_cast(action) || - dynamic_cast(action)) - return 0.0f; - } + if (AI_VALUE2(Unit*, "find target", "tidewalker lurker")) + return 1.0f; + + if (dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; return 1.0f; } @@ -541,11 +536,11 @@ float MorogrimTidewalkerDisableTankActionsMultiplier::GetValue(Action* action) if (!botAI->IsMainTank(bot)) return 1.0f; - if (AI_VALUE2(Unit*, "find target", "morogrim tidewalker")) - { - if (dynamic_cast(action)) - return 0.0f; - } + if (!AI_VALUE2(Unit*, "find target", "morogrim tidewalker")) + return 1.0f; + + if (dynamic_cast(action)) + return 0.0f; return 1.0f; } @@ -556,17 +551,14 @@ float MorogrimTidewalkerMaintainPhase2StackingMultiplier::GetValue(Action* actio return 1.0f; Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); - if (!tidewalker) + if (!tidewalker || tidewalker->GetHealthPct() > 25.0f) return 1.0f; - if (tidewalker->GetHealthPct() < 25.0f) - { - if (dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action)) - return 0.0f; - } + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; return 1.0f; } @@ -580,41 +572,59 @@ float LadyVashjDelayCooldownsMultiplier::GetValue(Action* action) if (!AI_VALUE2(Unit*, "find target", "lady vashj")) return 1.0f; - if (bot->getClass() == CLASS_SHAMAN) - { - if (IsLadyVashjInPhase3(botAI)) - return 1.0f; + if (bot->getClass() == CLASS_SHAMAN && + !IsLadyVashjInPhase3(botAI) && + (dynamic_cast(action) || + dynamic_cast(action))) + return 0.0f; - if (dynamic_cast(action) || - dynamic_cast(action)) - return 0.0f; - } + if (!botAI->IsDps(bot) || !IsLadyVashjInPhase1(botAI)) + return 1.0f; - if (botAI->IsDps(bot) && IsLadyVashjInPhase1(botAI)) - { - if (dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action)) - return 0.0f; - } + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + +float LadyVashjMainTankGroupShamanUseGroundingTotemMultiplier::GetValue(Action* action) +{ + if (bot->getClass() != CLASS_SHAMAN) + return 1.0f; + + if (!AI_VALUE2(Unit*, "find target", "lady vashj")) + return 1.0f; + + if (!IsMainTankInSameSubgroup(botAI, bot)) + return 1.0f; + + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; return 1.0f; } @@ -624,15 +634,15 @@ float LadyVashjMaintainPhase1RangedSpreadMultiplier::GetValue(Action* action) if (!botAI->IsRanged(bot)) return 1.0f; - if (AI_VALUE2(Unit*, "find target", "lady vashj") && - IsLadyVashjInPhase1(botAI)) - { - if (dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action)) - return 0.0f; - } + if (!AI_VALUE2(Unit*, "find target", "lady vashj") || + !IsLadyVashjInPhase1(botAI)) + return 1.0f; + + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; return 1.0f; } @@ -658,19 +668,18 @@ float LadyVashjStaticChargeStayAwayFromGroupMultiplier::GetValue(Action* action) // Bots should not loot the core with normal looting logic float LadyVashjDoNotLootTheTaintedCoreMultiplier::GetValue(Action* action) { - if (AI_VALUE2(Unit*, "find target", "lady vashj")) - { - if (dynamic_cast(action)) - return 0.0f; - } + if (!AI_VALUE2(Unit*, "find target", "lady vashj")) + return 1.0f; + + if (dynamic_cast(action)) + return 0.0f; return 1.0f; } float LadyVashjCorePassersPrioritizePositioningMultiplier::GetValue(Action* action) { - if (!AI_VALUE2(Unit*, "find target", "lady vashj") || - !IsLadyVashjInPhase2(botAI)) + if (!AI_VALUE2(Unit*, "find target", "lady vashj") || !IsLadyVashjInPhase2(botAI)) return 1.0f; if (dynamic_cast(action) || @@ -678,65 +687,43 @@ float LadyVashjCorePassersPrioritizePositioningMultiplier::GetValue(Action* acti dynamic_cast(action)) return 1.0f; - Group* group = bot->GetGroup(); - if (!group) - return 1.0f; + auto coreHandlers = GetCoreHandlers(botAI, bot); - Player* designatedLooter = GetDesignatedCoreLooter(group, botAI); - Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI); - Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI); - Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI); - Player* fourthCorePasser = GetFourthTaintedCorePasser(group, botAI); + bool isCoreHandler = false; + int myIndex = -1; + for (int i = 0; i < static_cast(coreHandlers.size()); ++i) + { + if (coreHandlers[i] && coreHandlers[i] == bot) + { + isCoreHandler = true; + myIndex = i; + } + } + if (!isCoreHandler) + return 1.0f; auto hasCore = [](Player* player) { return player && player->HasItemCount(ITEM_TAINTED_CORE, 1, false); }; - if (hasCore(bot)) - { - if (!dynamic_cast(action)) - return 0.0f; - } + // If the bot actually has the core, only allow core handling + if (hasCore(bot) && !dynamic_cast(action)) + return 0.0f; - if (bot == designatedLooter) - { - if (!hasCore(bot)) - return 1.0f; - } - else if (bot == firstCorePasser) - { - if (hasCore(secondCorePasser) || hasCore(thirdCorePasser) || - hasCore(fourthCorePasser)) - return 1.0f; - } - else if (bot == secondCorePasser) - { - if (hasCore(thirdCorePasser) || hasCore(fourthCorePasser)) - return 1.0f; - } - else if (bot == thirdCorePasser) - { - if (hasCore(fourthCorePasser)) - return 1.0f; - } - else if (bot != fourthCorePasser) - return 1.0f; + // First and second passers block movement when the looter teleports to the elemental + Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental"); + if (tainted && coreHandlers[0]->GetExactDist2d(tainted) < 5.0f && + (bot == coreHandlers[1] || bot == coreHandlers[2]) && + (dynamic_cast(action) && + !dynamic_cast(action))) + return 0.0f; - if (AI_VALUE2(Unit*, "find target", "tainted elemental") && - (bot == firstCorePasser || bot == secondCorePasser)) - { - if (dynamic_cast(action) && - !dynamic_cast(action)) - return 0.0f; - } - - if (AnyRecentCoreInInventory(group, botAI)) - { - if (dynamic_cast(action) && - !dynamic_cast(action)) - return 0.0f; - } + // If any prior handler (including self) recently had the core, block other movement + if (AnyRecentCoreInInventory(botAI, bot) && + dynamic_cast(action) && + !dynamic_cast(action)) + return 0.0f; return 1.0f; } @@ -745,7 +732,8 @@ float LadyVashjCorePassersPrioritizePositioningMultiplier::GetValue(Action* acti // So the standard target selection system must be disabled float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *action) { - if (!AI_VALUE2(Unit*, "find target", "lady vashj")) + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + if (!vashj) return 1.0f; if (dynamic_cast(action)) @@ -755,24 +743,26 @@ float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *ac { if (dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action)) return 0.0f; + if (bot->GetExactDist2d(vashj) < 60.0f && + dynamic_cast(action)) + return 0.0f; + if (!botAI->IsHeal(bot) && dynamic_cast(action)) return 0.0f; Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental"); - if (enchanted && bot->GetVictim() == enchanted) - { - if (dynamic_cast(action)) - return 0.0f; - } + if (enchanted && bot->GetVictim() == enchanted && + dynamic_cast(action)) + return 0.0f; } if (IsLadyVashjInPhase3(botAI)) { - if (dynamic_cast(action)) + if (dynamic_cast(action) || + dynamic_cast(action)) return 0.0f; Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental"); @@ -780,16 +770,13 @@ float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *ac Unit* elite = AI_VALUE2(Unit*, "find target", "coilfang elite"); if (enchanted || strider || elite) { - if (dynamic_cast(action) || - dynamic_cast(action) || + if (dynamic_cast(action) || dynamic_cast(action)) return 0.0f; - if (enchanted && bot->GetVictim() == enchanted) - { - if (dynamic_cast(action)) - return 0.0f; - } + if (enchanted && bot->GetVictim() == enchanted && + dynamic_cast(action)) + return 0.0f; } else if (dynamic_cast(action)) return 0.0f; diff --git a/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.h b/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.h index 6630dc20..19ba7352 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.h +++ b/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.h @@ -1,3 +1,8 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + #ifndef _PLAYERBOT_RAIDSSCMULTIPLIERS_H #define _PLAYERBOT_RAIDSSCMULTIPLIERS_H @@ -193,6 +198,14 @@ public: virtual float GetValue(Action* action); }; +class LadyVashjMainTankGroupShamanUseGroundingTotemMultiplier : public Multiplier +{ +public: + LadyVashjMainTankGroupShamanUseGroundingTotemMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj main tank group shaman use grounding totem") {} + virtual float GetValue(Action* action); +}; + class LadyVashjMaintainPhase1RangedSpreadMultiplier : public Multiplier { public: diff --git a/src/Ai/Raid/SerpentshrineCavern/RaidSSCActionContext.h b/src/Ai/Raid/SerpentshrineCavern/RaidSSCActionContext.h index e6dce169..1e2c44b4 100644 --- a/src/Ai/Raid/SerpentshrineCavern/RaidSSCActionContext.h +++ b/src/Ai/Raid/SerpentshrineCavern/RaidSSCActionContext.h @@ -1,3 +1,8 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + #ifndef _PLAYERBOT_RAIDSSCACTIONCONTEXT_H #define _PLAYERBOT_RAIDSSCACTIONCONTEXT_H @@ -161,9 +166,6 @@ public: creators["lady vashj destroy tainted core"] = &RaidSSCActionContext::lady_vashj_destroy_tainted_core; - creators["lady vashj erase core passing trackers"] = - &RaidSSCActionContext::lady_vashj_erase_core_passing_trackers; - creators["lady vashj avoid toxic spores"] = &RaidSSCActionContext::lady_vashj_avoid_toxic_spores; @@ -324,9 +326,6 @@ private: static Action* lady_vashj_destroy_tainted_core( PlayerbotAI* botAI) { return new LadyVashjDestroyTaintedCoreAction(botAI); } - static Action* lady_vashj_erase_core_passing_trackers( - PlayerbotAI* botAI) { return new LadyVashjEraseCorePassingTrackersAction(botAI); } - static Action* lady_vashj_avoid_toxic_spores( PlayerbotAI* botAI) { return new LadyVashjAvoidToxicSporesAction(botAI); } diff --git a/src/Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h b/src/Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h index 13135bf3..737fd3a3 100644 --- a/src/Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h +++ b/src/Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h @@ -1,3 +1,8 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + #ifndef _PLAYERBOT_RAIDSSCTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDSSCTRIGGERCONTEXT_H @@ -155,9 +160,6 @@ public: creators["lady vashj tainted core is unusable"] = &RaidSSCTriggerContext::lady_vashj_tainted_core_is_unusable; - creators["lady vashj need to reset core passing trackers"] = - &RaidSSCTriggerContext::lady_vashj_need_to_reset_core_passing_trackers; - creators["lady vashj toxic sporebats are spewing poison clouds"] = &RaidSSCTriggerContext::lady_vashj_toxic_sporebats_are_spewing_poison_clouds; @@ -312,9 +314,6 @@ private: static Trigger* lady_vashj_tainted_core_is_unusable( PlayerbotAI* botAI) { return new LadyVashjTaintedCoreIsUnusableTrigger(botAI); } - static Trigger* lady_vashj_need_to_reset_core_passing_trackers( - PlayerbotAI* botAI) { return new LadyVashjNeedToResetCorePassingTrackersTrigger(botAI); } - static Trigger* lady_vashj_toxic_sporebats_are_spewing_poison_clouds( PlayerbotAI* botAI) { return new LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger(botAI); } diff --git a/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.cpp b/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.cpp index 139667dc..624049c8 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.cpp @@ -1,3 +1,8 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + #include "RaidSSCStrategy.h" #include "RaidSSCMultipliers.h" @@ -144,9 +149,6 @@ void RaidSSCStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("lady vashj tainted core is unusable", { NextAction("lady vashj destroy tainted core", ACTION_EMERGENCY + 1) })); - triggers.push_back(new TriggerNode("lady vashj need to reset core passing trackers", { - NextAction("lady vashj erase core passing trackers", ACTION_EMERGENCY + 10) })); - triggers.push_back(new TriggerNode("lady vashj adds spawn in phase 2 and phase 3", { NextAction("lady vashj assign phase 2 and phase 3 dps priority", ACTION_RAID + 1) })); @@ -198,6 +200,7 @@ void RaidSSCStrategy::InitMultipliers(std::vector& multipliers) // Lady Vashj multipliers.push_back(new LadyVashjDelayCooldownsMultiplier(botAI)); + multipliers.push_back(new LadyVashjMainTankGroupShamanUseGroundingTotemMultiplier(botAI)); multipliers.push_back(new LadyVashjMaintainPhase1RangedSpreadMultiplier(botAI)); multipliers.push_back(new LadyVashjStaticChargeStayAwayFromGroupMultiplier(botAI)); multipliers.push_back(new LadyVashjDoNotLootTheTaintedCoreMultiplier(botAI)); diff --git a/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.h b/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.h index 3c2c05f5..a994600b 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.h +++ b/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.h @@ -1,3 +1,8 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + #ifndef _PLAYERBOT_RAIDSSCSTRATEGY_H_ #define _PLAYERBOT_RAIDSSCSTRATEGY_H_ diff --git a/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp b/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp index e77e6364..c3e75322 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp @@ -1,3 +1,8 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + #include "RaidSSCTriggers.h" #include "RaidSSCHelpers.h" #include "RaidSSCActions.h" @@ -26,35 +31,37 @@ bool UnderbogColossusSpawnedToxicPoolAfterDeathTrigger::IsActive() bool GreyheartTidecallerWaterElementalTotemSpawnedTrigger::IsActive() { return botAI->IsDps(bot) && - GetFirstAliveUnitByEntry(botAI, NPC_WATER_ELEMENTAL_TOTEM); + AI_VALUE2(Unit*, "find target", "greyheart tidecaller"); } // Hydross the Unstable bool HydrossTheUnstableBotIsFrostTankTrigger::IsActive() { - return AI_VALUE2(Unit*, "find target", "hydross the unstable") && - botAI->IsMainTank(bot); + return botAI->IsMainTank(bot) && + AI_VALUE2(Unit*, "find target", "hydross the unstable"); } bool HydrossTheUnstableBotIsNatureTankTrigger::IsActive() { - return AI_VALUE2(Unit*, "find target", "hydross the unstable") && - botAI->IsAssistTankOfIndex(bot, 0, true); + return botAI->IsAssistTankOfIndex(bot, 0, true) && + AI_VALUE2(Unit*, "find target", "hydross the unstable"); } bool HydrossTheUnstableElementalsSpawnedTrigger::IsActive() { + if (botAI->IsHeal(bot)) + return false; + Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); - if (hydross && hydross->GetHealthPct() < 10.0f) + if (!hydross || hydross->GetHealthPct() < 10.0f) return false; - if (!AI_VALUE2(Unit*, "find target", "pure spawn of hydross") && - !AI_VALUE2(Unit*, "find target", "tainted spawn of hydross")) + if (botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0, true)) return false; - return !botAI->IsHeal(bot) && !botAI->IsMainTank(bot) && - !botAI->IsAssistTankOfIndex(bot, 0, true); + return AI_VALUE2(Unit*, "find target", "pure spawn of hydross") || + AI_VALUE2(Unit*, "find target", "tainted spawn of hydross"); } bool HydrossTheUnstableDangerFromWaterTombsTrigger::IsActive() @@ -71,19 +78,19 @@ bool HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger::IsActive() bool HydrossTheUnstableAggroResetsUponPhaseChangeTrigger::IsActive() { - if (!AI_VALUE2(Unit*, "find target", "hydross the unstable")) + if (bot->getClass() == CLASS_HUNTER || + botAI->IsHeal(bot) || + botAI->IsMainTank(bot) || + botAI->IsAssistTankOfIndex(bot, 0, true)) return false; - return bot->getClass() != CLASS_HUNTER && - !botAI->IsHeal(bot) && - !botAI->IsMainTank(bot) && - !botAI->IsAssistTankOfIndex(bot, 0, true); + return AI_VALUE2(Unit*, "find target", "hydross the unstable"); } bool HydrossTheUnstableNeedToManageTimersTrigger::IsActive() { - return AI_VALUE2(Unit*, "find target", "hydross the unstable") && - IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr); + return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) && + AI_VALUE2(Unit*, "find target", "hydross the unstable"); } // The Lurker Below @@ -102,11 +109,11 @@ bool TheLurkerBelowSpoutIsActiveTrigger::IsActive() bool TheLurkerBelowBossIsActiveForMainTankTrigger::IsActive() { - Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); - if (!lurker) + if (!botAI->IsMainTank(bot)) return false; - if (!botAI->IsMainTank(bot)) + Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); + if (!lurker) return false; const time_t now = std::time(nullptr); @@ -135,35 +142,16 @@ bool TheLurkerBelowBossCastsGeyserTrigger::IsActive() // Trigger will be active only if there are at least 3 tanks in the raid bool TheLurkerBelowBossIsSubmergedTrigger::IsActive() { + if (!botAI->IsTank(bot)) + return false; + Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); if (!lurker || lurker->getStandState() != UNIT_STAND_STATE_SUBMERGED) return false; - Player* mainTank = nullptr; - Player* firstAssistTank = nullptr; - Player* secondAssistTank = nullptr; - - Group* group = bot->GetGroup(); - if (!group) - return false; - - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (!member || !member->IsAlive()) - continue; - - PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); - if (!memberAI) - continue; - - if (!mainTank && memberAI->IsMainTank(member)) - mainTank = member; - else if (!firstAssistTank && memberAI->IsAssistTankOfIndex(member, 0, true)) - firstAssistTank = member; - else if (!secondAssistTank && memberAI->IsAssistTankOfIndex(member, 1, true)) - secondAssistTank = member; - } + Player* mainTank = GetGroupMainTank(botAI, bot); + Player* firstAssistTank = GetGroupAssistTank(botAI, bot, 0); + Player* secondAssistTank = GetGroupAssistTank(botAI, bot, 1); if (!mainTank || !firstAssistTank || !secondAssistTank) return false; @@ -173,51 +161,55 @@ bool TheLurkerBelowBossIsSubmergedTrigger::IsActive() bool TheLurkerBelowNeedToPrepareTimerForSpoutTrigger::IsActive() { - return AI_VALUE2(Unit*, "find target", "the lurker below") && - IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr); + return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) && + AI_VALUE2(Unit*, "find target", "the lurker below"); } // Leotheras the Blind bool LeotherasTheBlindBossIsInactiveTrigger::IsActive() { - return AI_VALUE2(Unit*, "find target", "greyheart spellbinder"); + return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) && + AI_VALUE2(Unit*, "find target", "greyheart spellbinder"); } bool LeotherasTheBlindBossTransformedIntoDemonFormTrigger::IsActive() { + if (bot->getClass() != CLASS_WARLOCK) + return false; + if (!AI_VALUE2(Unit*, "find target", "leotheras the blind")) return false; if (GetLeotherasDemonFormTank(bot) != bot) return false; - return GetActiveLeotherasDemon(botAI); + return GetActiveLeotherasDemon(bot); } bool LeotherasTheBlindOnlyWarlockShouldTankDemonFormTrigger::IsActive() { - if (botAI->IsRanged(bot) || !botAI->IsTank(bot)) + if (!botAI->IsTank(bot)) + return false; + + if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) return false; if (!AI_VALUE2(Unit*, "find target", "leotheras the blind")) return false; - if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) - return false; - if (!GetLeotherasDemonFormTank(bot)) return false; - return GetPhase2LeotherasDemon(botAI); + return GetPhase2LeotherasDemon(bot); } bool LeotherasTheBlindBossEngagedByRangedTrigger::IsActive() { - if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) + if (!botAI->IsRanged(bot)) return false; - if (!botAI->IsRanged(bot)) + if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) return false; Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); @@ -231,14 +223,14 @@ bool LeotherasTheBlindBossEngagedByRangedTrigger::IsActive() bool LeotherasTheBlindBossChannelingWhirlwindTrigger::IsActive() { - if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) - return false; - - if (botAI->IsTank(bot) && botAI->IsMelee(bot)) + if (botAI->IsTank(bot)) return false; Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); - if (!leotheras || leotheras->HasAura(SPELL_LEOTHERAS_BANISHED)) + if (!leotheras) + return false; + + if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) return false; return leotheras->HasAura(SPELL_WHIRLWIND) || @@ -247,10 +239,13 @@ bool LeotherasTheBlindBossChannelingWhirlwindTrigger::IsActive() bool LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger::IsActive() { - if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) + if (botAI->IsRanged(bot)) return false; - if (botAI->IsRanged(bot)) + if (!AI_VALUE2(Unit*, "find target", "leotheras the blind")) + return false; + + if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) return false; Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST); @@ -260,7 +255,7 @@ bool LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger::IsActive() if (!GetLeotherasDemonFormTank(bot) && botAI->IsMainTank(bot)) return false; - return GetPhase2LeotherasDemon(botAI); + return GetPhase2LeotherasDemon(bot); } bool LeotherasTheBlindInnerDemonHasAwakenedTrigger::IsActive() @@ -271,89 +266,68 @@ bool LeotherasTheBlindInnerDemonHasAwakenedTrigger::IsActive() bool LeotherasTheBlindEnteredFinalPhaseTrigger::IsActive() { - if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) - return false; - if (botAI->IsHeal(bot)) return false; - if (GetLeotherasDemonFormTank(bot) == bot) + if (!AI_VALUE2(Unit*, "find target", "leotheras the blind")) return false; - return GetPhase3LeotherasDemon(botAI) && - GetLeotherasHuman(botAI); + if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) + return false; + + if (bot->getClass() == CLASS_WARLOCK && GetLeotherasDemonFormTank(bot) == bot) + return false; + + return GetPhase3LeotherasDemon(bot); } bool LeotherasTheBlindDemonFormTankNeedsAggro::IsActive() { - if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) - return false; - if (bot->getClass() != CLASS_HUNTER) return false; - return AI_VALUE2(Unit*, "find target", "leotheras the blind"); + if (!AI_VALUE2(Unit*, "find target", "leotheras the blind")) + return false; + + return !bot->HasAura(SPELL_INSIDIOUS_WHISPER); } bool LeotherasTheBlindBossWipesAggroUponPhaseChangeTrigger::IsActive() { - return AI_VALUE2(Unit*, "find target", "leotheras the blind") && - IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr); + return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) && + AI_VALUE2(Unit*, "find target", "leotheras the blind"); } // Fathom-Lord Karathress bool FathomLordKarathressBossEngagedByMainTankTrigger::IsActive() { - return AI_VALUE2(Unit*, "find target", "fathom-lord karathress") && - botAI->IsMainTank(bot); + return botAI->IsMainTank(bot) && + AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); } bool FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger::IsActive() { - return AI_VALUE2(Unit*, "find target", "fathom-guard caribdis") && - botAI->IsAssistTankOfIndex(bot, 0, false); + return botAI->IsAssistTankOfIndex(bot, 0, false) && + AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); } bool FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger::IsActive() { - return AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis") && - botAI->IsAssistTankOfIndex(bot, 1, false); + return botAI->IsAssistTankOfIndex(bot, 1, false) && + AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis"); } bool FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger::IsActive() { - return AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess") && - botAI->IsAssistTankOfIndex(bot, 2, false); + return botAI->IsAssistTankOfIndex(bot, 2, false) && + AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess"); } bool FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger::IsActive() { - Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); - if (!caribdis) - return false; - - if (!botAI->IsAssistHealOfIndex(bot, 0, true)) - return false; - - Player* firstAssistTank = nullptr; - if (Group* group = bot->GetGroup()) - { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (!member || !member->IsAlive()) - continue; - - if (botAI->IsAssistTankOfIndex(member, 0, false)) - { - firstAssistTank = member; - break; - } - } - } - - return firstAssistTank; + return botAI->IsAssistHealOfIndex(bot, 0, true) && + AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); } bool FathomLordKarathressPullingBossesTrigger::IsActive() @@ -367,10 +341,10 @@ bool FathomLordKarathressPullingBossesTrigger::IsActive() bool FathomLordKarathressDeterminingKillOrderTrigger::IsActive() { - if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) + if (botAI->IsHeal(bot)) return false; - if (botAI->IsHeal(bot)) + if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) return false; if (botAI->IsDps(bot)) @@ -387,8 +361,8 @@ bool FathomLordKarathressDeterminingKillOrderTrigger::IsActive() bool FathomLordKarathressTanksNeedToEstablishAggroTrigger::IsActive() { - return AI_VALUE2(Unit*, "find target", "fathom-lord karathress") && - IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr); + return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) && + AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); } // Morogrim Tidewalker @@ -404,8 +378,8 @@ bool MorogrimTidewalkerPullingBossTrigger::IsActive() bool MorogrimTidewalkerBossEngagedByMainTankTrigger::IsActive() { - return AI_VALUE2(Unit*, "find target", "morogrim tidewalker") && - botAI->IsMainTank(bot); + return botAI->IsMainTank(bot) && + AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); } bool MorogrimTidewalkerWaterGlobulesAreIncomingTrigger::IsActive() @@ -421,8 +395,11 @@ bool MorogrimTidewalkerWaterGlobulesAreIncomingTrigger::IsActive() bool LadyVashjBossEngagedByMainTankTrigger::IsActive() { + if (!botAI->IsMainTank(bot)) + return false; + return AI_VALUE2(Unit*, "find target", "lady vashj") && - !IsLadyVashjInPhase2(botAI) && botAI->IsMainTank(bot); + !IsLadyVashjInPhase2(botAI); } bool LadyVashjBossEngagedByRangedInPhase1Trigger::IsActive() @@ -439,10 +416,7 @@ bool LadyVashjCastsShockBlastOnHighestAggroTrigger::IsActive() IsLadyVashjInPhase2(botAI)) return false; - if (!IsMainTankInSameSubgroup(bot)) - return false; - - return true; + return IsMainTankInSameSubgroup(botAI, bot); } bool LadyVashjBotHasStaticChargeTrigger::IsActive() @@ -450,14 +424,15 @@ bool LadyVashjBotHasStaticChargeTrigger::IsActive() if (!AI_VALUE2(Unit*, "find target", "lady vashj")) return false; - if (Group* group = bot->GetGroup()) + Group* group = bot->GetGroup(); + if (!group) + return false; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->HasAura(SPELL_STATIC_CHARGE)) - return true; - } + Player* member = ref->GetSource(); + if (member && member->HasAura(SPELL_STATIC_CHARGE)) + return true; } return false; @@ -500,9 +475,10 @@ bool LadyVashjTaintedElementalCheatTrigger::IsActive() return false; bool taintedPresent = false; - Unit* taintedUnit = AI_VALUE2(Unit*, "find target", "tainted elemental"); - if (taintedUnit) + if (AI_VALUE2(Unit*, "find target", "tainted elemental")) + { taintedPresent = true; + } else { GuidVector corpses = AI_VALUE(GuidVector, "nearest corpses"); @@ -513,13 +489,11 @@ bool LadyVashjTaintedElementalCheatTrigger::IsActive() if (!object) continue; - if (Creature* creature = object->ToCreature()) + if (Creature* creature = object->ToCreature(); + creature->GetEntry() == NPC_TAINTED_ELEMENTAL && !creature->IsAlive()) { - if (creature->GetEntry() == NPC_TAINTED_ELEMENTAL && !creature->IsAlive()) - { - taintedPresent = true; - break; - } + taintedPresent = true; + break; } } } @@ -527,12 +501,8 @@ bool LadyVashjTaintedElementalCheatTrigger::IsActive() if (!taintedPresent) return false; - Group* group = bot->GetGroup(); - if (!group) - return false; - - return (GetDesignatedCoreLooter(group, botAI) == bot && - !bot->HasItemCount(ITEM_TAINTED_CORE, 1, false)); + return GetDesignatedCoreLooter(botAI, bot) == bot && + !bot->HasItemCount(ITEM_TAINTED_CORE, 1, false); } bool LadyVashjTaintedCoreWasLootedTrigger::IsActive() @@ -540,54 +510,24 @@ bool LadyVashjTaintedCoreWasLootedTrigger::IsActive() if (!AI_VALUE2(Unit*, "find target", "lady vashj") || !IsLadyVashjInPhase2(botAI)) return false; - Group* group = bot->GetGroup(); - if (!group) + auto coreHandlers = GetCoreHandlers(botAI, bot); + + bool isCoreHandler = false; + for (Player* handler : coreHandlers) + if (handler == bot) + isCoreHandler = true; + + if (!isCoreHandler) return false; - Player* designatedLooter = GetDesignatedCoreLooter(group, botAI); - Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI); - Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI); - Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI); - Player* fourthCorePasser = GetFourthTaintedCorePasser(group, botAI); - - auto hasCore = [](Player* player) -> bool - { - return player && player->HasItemCount(ITEM_TAINTED_CORE, 1, false); - }; - - if (bot == designatedLooter) - { - if (!hasCore(bot)) - return false; - } - else if (bot == firstCorePasser) - { - if (hasCore(secondCorePasser) || hasCore(thirdCorePasser) || - hasCore(fourthCorePasser)) - return false; - } - else if (bot == secondCorePasser) - { - if (hasCore(thirdCorePasser) || hasCore(fourthCorePasser)) - return false; - } - else if (bot == thirdCorePasser) - { - if (hasCore(fourthCorePasser)) - return false; - } - else if (bot != fourthCorePasser) - return false; - - if (AnyRecentCoreInInventory(group, botAI)) - return true; - // First and second passers move to positions as soon as the elemental appears - if (AI_VALUE2(Unit*, "find target", "tainted elemental") && - (bot == firstCorePasser || bot == secondCorePasser)) + Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental"); + if (tainted && coreHandlers[0]->GetExactDist2d(tainted) < 5.0f && + (bot == coreHandlers[1] || bot == coreHandlers[2])) return true; - return false; + // Main logic: run if core is in play for this bot or a prior handler + return AnyRecentCoreInInventory(botAI, bot); } bool LadyVashjTaintedCoreIsUnusableTrigger::IsActive() @@ -599,18 +539,7 @@ bool LadyVashjTaintedCoreIsUnusableTrigger::IsActive() if (!IsLadyVashjInPhase2(botAI)) return bot->HasItemCount(ITEM_TAINTED_CORE, 1, false); - Group* group = bot->GetGroup(); - if (!group) - return false; - - Player* coreHandlers[] = - { - GetDesignatedCoreLooter(group, botAI), - GetFirstTaintedCorePasser(group, botAI), - GetSecondTaintedCorePasser(group, botAI), - GetThirdTaintedCorePasser(group, botAI), - GetFourthTaintedCorePasser(group, botAI) - }; + auto coreHandlers = GetCoreHandlers(botAI, bot); if (bot->HasItemCount(ITEM_TAINTED_CORE, 1, false)) { @@ -625,24 +554,6 @@ bool LadyVashjTaintedCoreIsUnusableTrigger::IsActive() return false; } -bool LadyVashjNeedToResetCorePassingTrackersTrigger::IsActive() -{ - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - if (!vashj || IsLadyVashjInPhase2(botAI)) - return false; - - Group* group = bot->GetGroup(); - if (!group) - return false; - - return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr) || - GetDesignatedCoreLooter(group, botAI) == bot || - GetFirstTaintedCorePasser(group, botAI) == bot || - GetSecondTaintedCorePasser(group, botAI) == bot || - GetThirdTaintedCorePasser(group, botAI) == bot || - GetFourthTaintedCorePasser(group, botAI) == bot; -} - bool LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger::IsActive() { return IsLadyVashjInPhase3(botAI); @@ -653,17 +564,18 @@ bool LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger::IsActive() if (!AI_VALUE2(Unit*, "find target", "lady vashj")) return false; - if (Group* group = bot->GetGroup()) - { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (!member || !member->HasAura(SPELL_ENTANGLE)) - continue; + Group* group = bot->GetGroup(); + if (!group) + return false; - if (botAI->IsMelee(member)) - return true; - } + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->HasAura(SPELL_ENTANGLE)) + continue; + + if (botAI->IsMelee(member)) + return true; } return false; diff --git a/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.h b/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.h index e106b58f..4bf9e741 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.h +++ b/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.h @@ -1,3 +1,8 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + #ifndef _PLAYERBOT_RAIDSSCTRIGGERS_H #define _PLAYERBOT_RAIDSSCTRIGGERS_H @@ -387,14 +392,6 @@ public: bool IsActive() override; }; -class LadyVashjNeedToResetCorePassingTrackersTrigger : public Trigger -{ -public: - LadyVashjNeedToResetCorePassingTrackersTrigger( - PlayerbotAI* botAI) : Trigger(botAI, "lady vashj need to reset core passing trackers") {} - bool IsActive() override; -}; - class LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger : public Trigger { public: diff --git a/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.cpp b/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.cpp index 7bda085b..4ebeb6f8 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.cpp @@ -1,3 +1,8 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + #include "RaidSSCHelpers.h" #include "AiFactory.h" #include "Creature.h" @@ -79,61 +84,54 @@ namespace SerpentShrineCavernHelpers std::unordered_map leotherasDemonFormDpsWaitTimer; std::unordered_map leotherasFinalPhaseDpsWaitTimer; - Unit* GetLeotherasHuman(PlayerbotAI* botAI) + Unit* GetLeotherasHuman(Player* bot) { - auto const& npcs = - botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); - for (auto const& guid : npcs) - { - Unit* unit = botAI->GetUnit(guid); - if (unit && unit->GetEntry() == NPC_LEOTHERAS_THE_BLIND && - unit->IsInCombat() && !unit->HasAura(SPELL_METAMORPHOSIS)) - return unit; - } + constexpr float searchRadius = 100.0f; + Creature* leotheras = + bot->FindNearestCreature(NPC_LEOTHERAS_THE_BLIND, searchRadius, true); + + if (leotheras && leotheras->IsInCombat() && + !leotheras->HasAura(SPELL_METAMORPHOSIS)) + return leotheras; + return nullptr; } - Unit* GetPhase2LeotherasDemon(PlayerbotAI* botAI) + Unit* GetPhase2LeotherasDemon(Player* bot) { - auto const& npcs = - botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); - for (auto const& guid : npcs) - { - Unit* unit = botAI->GetUnit(guid); - if (unit && unit->GetEntry() == NPC_LEOTHERAS_THE_BLIND && - unit->HasAura(SPELL_METAMORPHOSIS)) - return unit; - } + constexpr float searchRadius = 100.0f; + Creature* leotheras = + bot->FindNearestCreature(NPC_LEOTHERAS_THE_BLIND, searchRadius, true); + + if (leotheras && leotheras->HasAura(SPELL_METAMORPHOSIS)) + return leotheras; + return nullptr; } - Unit* GetPhase3LeotherasDemon(PlayerbotAI* botAI) + Unit* GetPhase3LeotherasDemon(Player* bot) { - auto const& npcs = - botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); - for (auto const& guid : npcs) - { - Unit* unit = botAI->GetUnit(guid); - if (unit && unit->GetEntry() == NPC_SHADOW_OF_LEOTHERAS) - return unit; - } - return nullptr; + constexpr float searchRadius = 100.0f; + return bot->FindNearestCreature(NPC_SHADOW_OF_LEOTHERAS, searchRadius, true); } - Unit* GetActiveLeotherasDemon(PlayerbotAI* botAI) + Unit* GetActiveLeotherasDemon(Player* bot) { - Unit* phase2 = GetPhase2LeotherasDemon(botAI); - Unit* phase3 = GetPhase3LeotherasDemon(botAI); + Unit* phase2 = GetPhase2LeotherasDemon(bot); + Unit* phase3 = GetPhase3LeotherasDemon(bot); return phase2 ? phase2 : phase3; } + // (1) First priority is an assistant Warlock (real player or bot) + // (2) If no assistant Warlock, then look for any Warlock bot Player* GetLeotherasDemonFormTank(Player* bot) { Group* group = bot->GetGroup(); if (!group) return nullptr; - // (1) First loop: Return the first assistant Warlock (real player or bot) + Player* fallbackWarlock = nullptr; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); @@ -142,21 +140,12 @@ namespace SerpentShrineCavernHelpers if (group->IsAssistant(member->GetGUID())) return member; + + if (!fallbackWarlock && GET_PLAYERBOT_AI(member)) + fallbackWarlock = member; } - // (2) Fall back to first found bot Warlock - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) || - member->getClass() != CLASS_WARLOCK) - continue; - - return member; - } - - // (3) Return nullptr if none found - return nullptr; + return fallbackWarlock; } // Fathom-Lord Karathress @@ -182,16 +171,15 @@ namespace SerpentShrineCavernHelpers // Lady Vashj - const Position VASHJ_PLATFORM_CENTER_POSITION = { 29.634f, -923.541f, 42.985f }; + const Position VASHJ_PLATFORM_CENTER_POSITION = { 29.634f, -923.541f, 42.902f }; - std::unordered_map vashjRangedPositions; std::unordered_map hasReachedVashjRangedPosition; std::unordered_map nearestTriggerGuid; std::unordered_map intendedLineup; std::unordered_map lastImbueAttempt; - std::unordered_map lastCoreInInventoryTime; + std::unordered_map lastCoreInInventoryTime; - bool IsMainTankInSameSubgroup(Player* bot) + bool IsMainTankInSameSubgroup(PlayerbotAI* botAI, Player* bot) { Group* group = bot->GetGroup(); if (!group || !group->isRaidGroup()) @@ -210,11 +198,8 @@ namespace SerpentShrineCavernHelpers if (group->GetMemberGroup(member->GetGUID()) != botSubGroup) continue; - if (PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member)) - { - if (memberAI->IsMainTank(member)) - return true; - } + if (botAI->IsMainTank(member)) + return true; } return false; @@ -277,38 +262,9 @@ namespace SerpentShrineCavernHelpers return false; } - bool AnyRecentCoreInInventory(Group* group, PlayerbotAI* botAI, uint32 graceSeconds) - { - Unit* vashj = - botAI->GetAiObjectContext()->GetValue("find target", "lady vashj")->Get(); - if (!vashj) - return false; - - if (group) - { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->HasItemCount(ITEM_TAINTED_CORE, 1, false)) - return true; - } - } - - const uint32 instanceId = vashj->GetMap()->GetInstanceId(); - const time_t now = std::time(nullptr); - - auto it = lastCoreInInventoryTime.find(instanceId); - if (it != lastCoreInInventoryTime.end()) - { - if ((now - it->second) <= static_cast(graceSeconds)) - return true; - } - - return false; - } - - Player* GetDesignatedCoreLooter(Group* group, PlayerbotAI* botAI) + Player* GetDesignatedCoreLooter(PlayerbotAI* botAI, Player* bot) { + Group* group = bot->GetGroup(); if (!group) return nullptr; @@ -317,10 +273,15 @@ namespace SerpentShrineCavernHelpers if (!leaderGuid.IsEmpty()) leader = ObjectAccessor::FindPlayer(leaderGuid); + // If cheats are disabled, the group leader will be the designated looter if (!botAI->HasCheat(BotCheatMask::raid)) return leader; - Player* fallback = leader; + // Priority: (1) assistant melee DPS, (2) other melee DPS, (3) any ranged DPS + Player* meleeDpsAssistant = nullptr; + Player* meleeDps = nullptr; + Player* rangedDps = nullptr; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); @@ -331,22 +292,36 @@ namespace SerpentShrineCavernHelpers if (!memberAI) continue; - if (memberAI->IsMelee(member) && memberAI->IsDps(member)) - return member; + if (!meleeDpsAssistant && memberAI->IsMelee(member) && + memberAI->IsDps(member) && group->IsAssistant(member->GetGUID())) + { + meleeDpsAssistant = member; + break; + } - if (!fallback && memberAI->IsRangedDps(member)) - fallback = member; + if (!meleeDps && memberAI->IsMelee(member) && memberAI->IsDps(member)) + meleeDps = member; + + if (!rangedDps && memberAI->IsRangedDps(member)) + rangedDps = member; } - return fallback ? fallback : leader; + if (meleeDpsAssistant) + return meleeDpsAssistant; + if (meleeDps) + return meleeDps; + if (rangedDps) + return rangedDps; + return leader; } - Player* GetFirstTaintedCorePasser(Group* group, PlayerbotAI* botAI) + Player* GetFirstTaintedCorePasser(PlayerbotAI* botAI, Player* bot) { + Group* group = bot->GetGroup(); if (!group) return nullptr; - Player* designatedLooter = GetDesignatedCoreLooter(group, botAI); + Player* designatedLooter = GetDesignatedCoreLooter(botAI, bot); for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { @@ -355,32 +330,29 @@ namespace SerpentShrineCavernHelpers continue; PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); - if (!memberAI) - continue; - - if (memberAI->IsAssistHealOfIndex(member, 0, true)) + if (memberAI && memberAI->IsAssistHealOfIndex(member, 0, true)) return member; } for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) || - botAI->IsTank(member) || member == designatedLooter) - continue; - return member; + if (member && member->IsAlive() && GET_PLAYERBOT_AI(member) && + !botAI->IsTank(member) && member != designatedLooter) + return member; } return nullptr; } - Player* GetSecondTaintedCorePasser(Group* group, PlayerbotAI* botAI) + Player* GetSecondTaintedCorePasser(PlayerbotAI* botAI, Player* bot) { + Group* group = bot->GetGroup(); if (!group) return nullptr; - Player* designatedLooter = GetDesignatedCoreLooter(group, botAI); - Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI); + Player* designatedLooter = GetDesignatedCoreLooter(botAI, bot); + Player* firstCorePasser = GetFirstTaintedCorePasser(botAI, bot); for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { @@ -390,34 +362,31 @@ namespace SerpentShrineCavernHelpers continue; PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); - if (!memberAI) - continue; - - if (memberAI->IsAssistHealOfIndex(member, 1, true)) + if (memberAI && memberAI->IsAssistHealOfIndex(member, 1, true)) return member; } for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) || - botAI->IsTank(member) || member == designatedLooter || - member == firstCorePasser) - continue; - return member; + if (member && member->IsAlive() && GET_PLAYERBOT_AI(member) && + !botAI->IsTank(member) && member != designatedLooter && + member != firstCorePasser) + return member; } return nullptr; } - Player* GetThirdTaintedCorePasser(Group* group, PlayerbotAI* botAI) + Player* GetThirdTaintedCorePasser(PlayerbotAI* botAI, Player* bot) { + Group* group = bot->GetGroup(); if (!group) return nullptr; - Player* designatedLooter = GetDesignatedCoreLooter(group, botAI); - Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI); - Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI); + Player* designatedLooter = GetDesignatedCoreLooter(botAI, bot); + Player* firstCorePasser = GetFirstTaintedCorePasser(botAI, bot); + Player* secondCorePasser = GetSecondTaintedCorePasser(botAI, bot); for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { @@ -427,35 +396,32 @@ namespace SerpentShrineCavernHelpers continue; PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); - if (!memberAI) - continue; - - if (memberAI->IsAssistHealOfIndex(member, 2, true)) + if (memberAI && memberAI->IsAssistHealOfIndex(member, 2, true)) return member; } for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) || - botAI->IsTank(member) || member == designatedLooter || - member == firstCorePasser || member == secondCorePasser) - continue; - return member; + if (member && member->IsAlive() && GET_PLAYERBOT_AI(member) && + !botAI->IsTank(member) && member != designatedLooter && + member != firstCorePasser && member != secondCorePasser) + return member; } return nullptr; } - Player* GetFourthTaintedCorePasser(Group* group, PlayerbotAI* botAI) + Player* GetFourthTaintedCorePasser(PlayerbotAI* botAI, Player* bot) { + Group* group = bot->GetGroup(); if (!group) return nullptr; - Player* designatedLooter = GetDesignatedCoreLooter(group, botAI); - Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI); - Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI); - Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI); + Player* designatedLooter = GetDesignatedCoreLooter(botAI, bot); + Player* firstCorePasser = GetFirstTaintedCorePasser(botAI, bot); + Player* secondCorePasser = GetSecondTaintedCorePasser(botAI, bot); + Player* thirdCorePasser = GetThirdTaintedCorePasser(botAI, bot); for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { @@ -466,27 +432,75 @@ namespace SerpentShrineCavernHelpers continue; PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); - if (!memberAI) - continue; - - if (memberAI->IsAssistRangedDpsOfIndex(member, 0, true)) + if (memberAI && memberAI->IsAssistRangedDpsOfIndex(member, 0, true)) return member; } for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) || - botAI->IsTank(member) || member == designatedLooter || - member == firstCorePasser || member == secondCorePasser || - member == thirdCorePasser) - continue; - return member; + if (member && member->IsAlive() && GET_PLAYERBOT_AI(member) && + !botAI->IsTank(member) && member != designatedLooter && + member != firstCorePasser && member != secondCorePasser && + member != thirdCorePasser) + return member; } return nullptr; } + std::array GetCoreHandlers(PlayerbotAI* botAI, Player* bot) + { + return + { + GetDesignatedCoreLooter(botAI, bot), + GetFirstTaintedCorePasser(botAI, bot), + GetSecondTaintedCorePasser(botAI, bot), + GetThirdTaintedCorePasser(botAI, bot), + GetFourthTaintedCorePasser(botAI, bot) + }; + } + + // Checks if any bot from earlier in the passing sequence has the Tainted Core or + // had it within the prior 3 seconds so the chain is not broken when the Core is in transit + bool AnyRecentCoreInInventory(PlayerbotAI* botAI, Player* bot) + { + Unit* vashj = + botAI->GetAiObjectContext()->GetValue("find target", "lady vashj")->Get(); + if (!vashj) + return false; + + auto coreHandlers = GetCoreHandlers(botAI, bot); + + int8 myIndex = -1; + for (int8 i = 0; i < 5; ++i) + if (coreHandlers[i] && coreHandlers[i] == bot) + myIndex = i; + + if (myIndex == -1) + return false; + + const time_t now = std::time(nullptr); + constexpr uint8 lookbackSeconds = 3; + + for (int8 i = 0; i <= myIndex; ++i) + { + Player* handler = coreHandlers[i]; + if (!handler) + continue; + + if (handler->HasItemCount(ITEM_TAINTED_CORE, 1, false)) + return true; + + auto it = lastCoreInInventoryTime.find(handler->GetGUID()); + if (it != lastCoreInInventoryTime.end() && + (now - it->second) <= static_cast(lookbackSeconds)) + return true; + } + + return false; + } + const std::vector SHIELD_GENERATOR_DB_GUIDS = { 47482, // NW @@ -510,10 +524,7 @@ namespace SerpentShrineCavernHelpers continue; GameObject* go = bounds.first->second; - if (!go) - continue; - - if (go->GetGoState() != GO_STATE_READY) + if (!go || go->GetGoState() != GO_STATE_READY) continue; GeneratorInfo info; @@ -529,7 +540,7 @@ namespace SerpentShrineCavernHelpers // Returns the nearest active Shield Generator to the bot // Active generators are powered by NPC_WORLD_INVISIBLE_TRIGGER creatures, - // which depawn after use + // which despawn after use Unit* GetNearestActiveShieldGeneratorTriggerByEntry(Unit* reference) { if (!reference) diff --git a/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.h b/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.h index a725e28f..72ee52c8 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.h +++ b/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.h @@ -1,3 +1,8 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU AGPL v3 license, you may redistribute it + * and/or modify it under version 3 of the License, or (at your option), any later version. + */ + #ifndef _PLAYERBOT_RAIDSSCHELPERS_H_ #define _PLAYERBOT_RAIDSSCHELPERS_H_ @@ -64,9 +69,6 @@ namespace SerpentShrineCavernHelpers // Warlock SPELL_CURSE_OF_EXHAUSTION = 18223, - - // Item - SPELL_HEAVY_NETHERWEAVE_NET = 31368, }; enum SerpentShrineCavernNPCs @@ -105,9 +107,6 @@ namespace SerpentShrineCavernHelpers { // Lady Vashj ITEM_TAINTED_CORE = 31088, - - // Tailoring - ITEM_HEAVY_NETHERWEAVE_NET = 24269, }; constexpr uint32 SSC_MAP_ID = 548; @@ -134,10 +133,10 @@ namespace SerpentShrineCavernHelpers extern std::unordered_map leotherasHumanFormDpsWaitTimer; extern std::unordered_map leotherasDemonFormDpsWaitTimer; extern std::unordered_map leotherasFinalPhaseDpsWaitTimer; - Unit* GetLeotherasHuman(PlayerbotAI* botAI); - Unit* GetPhase2LeotherasDemon(PlayerbotAI* botAI); - Unit* GetPhase3LeotherasDemon(PlayerbotAI* botAI); - Unit* GetActiveLeotherasDemon(PlayerbotAI* botAI); + Unit* GetLeotherasHuman(Player* bot); + Unit* GetPhase2LeotherasDemon(Player* bot); + Unit* GetPhase3LeotherasDemon(Player* bot); + Unit* GetActiveLeotherasDemon(Player* bot); Player* GetLeotherasDemonFormTank(Player* bot); // Fathom-Lord Karathress @@ -158,25 +157,26 @@ namespace SerpentShrineCavernHelpers extern std::unordered_map tidewalkerRangedStep; // Lady Vashj - constexpr float VASHJ_PLATFORM_Z = 42.985f; + constexpr float VASHJ_PLATFORM_CENTER_Z = 42.902f; + constexpr float VASHJ_PLATFORM_EDGE_Z = 41.097f; extern const Position VASHJ_PLATFORM_CENTER_POSITION; - extern std::unordered_map vashjRangedPositions; extern std::unordered_map hasReachedVashjRangedPosition; extern std::unordered_map nearestTriggerGuid; extern std::unordered_map intendedLineup; extern std::unordered_map lastImbueAttempt; - extern std::unordered_map lastCoreInInventoryTime; - bool IsMainTankInSameSubgroup(Player* bot); + extern std::unordered_map lastCoreInInventoryTime; + bool IsMainTankInSameSubgroup(PlayerbotAI* botAI, Player* bot); bool IsLadyVashjInPhase1(PlayerbotAI* botAI); bool IsLadyVashjInPhase2(PlayerbotAI* botAI); bool IsLadyVashjInPhase3(PlayerbotAI* botAI); bool IsValidLadyVashjCombatNpc(Unit* unit, PlayerbotAI* botAI); - bool AnyRecentCoreInInventory(Group* group, PlayerbotAI* botAI, uint32 graceSeconds = 3); - Player* GetDesignatedCoreLooter(Group* group, PlayerbotAI* botAI); - Player* GetFirstTaintedCorePasser(Group* group, PlayerbotAI* botAI); - Player* GetSecondTaintedCorePasser(Group* group, PlayerbotAI* botAI); - Player* GetThirdTaintedCorePasser(Group* group, PlayerbotAI* botAI); - Player* GetFourthTaintedCorePasser(Group* group, PlayerbotAI* botAI); + Player* GetDesignatedCoreLooter(PlayerbotAI* botAI, Player* bot); + Player* GetFirstTaintedCorePasser(PlayerbotAI* botAI, Player* bot); + Player* GetSecondTaintedCorePasser(PlayerbotAI* botAI, Player* bot); + Player* GetThirdTaintedCorePasser(PlayerbotAI* botAI, Player* bot); + Player* GetFourthTaintedCorePasser(PlayerbotAI* botAI, Player* bot); + std::array GetCoreHandlers(PlayerbotAI* botAI, Player* bot); + bool AnyRecentCoreInInventory(PlayerbotAI* botAI, Player* bot); struct GeneratorInfo { ObjectGuid guid; float x, y, z; }; extern const std::vector SHIELD_GENERATOR_DB_GUIDS; std::vector GetAllGeneratorInfosByDbGuids(