#include "RaidKarazhanActions.h" #include "RaidKarazhanHelpers.h" #include "AiObjectContext.h" #include "PlayerbotAI.h" #include "PlayerbotMgr.h" #include "PlayerbotTextMgr.h" #include "Playerbots.h" #include "Position.h" namespace { // Big Bad Wolf static int currentIndex = 0; // Netherspite static std::map beamMoveTimes; static std::map lastBeamMoveSideways; } bool KarazhanAttumenTheHuntsmanStackBehindAction::Execute(Event event) { RaidKarazhanHelpers karazhanHelper(botAI); Unit* boss = karazhanHelper.GetFirstAliveUnitByEntry(NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED); float distance = 5.0f; float orientation = boss->GetOrientation() + M_PI; float x = boss->GetPositionX(); float y = boss->GetPositionY(); float z = boss->GetPositionZ(); float rx = x + cos(orientation) * distance; float ry = y + sin(orientation) * distance; return MoveTo(bot->GetMapId(), rx, ry, z, false, false, false, false, MovementPriority::MOVEMENT_COMBAT); } bool KarazhanAttumenTheHuntsmanStackBehindAction::isUseful() { RaidKarazhanHelpers karazhanHelper(botAI); Unit* boss = karazhanHelper.GetFirstAliveUnitByEntry(NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED); return boss && !(botAI->IsTank(bot) && botAI->HasAggro(boss) && boss->GetVictim() == bot); } bool KarazhanMoroesMarkTargetAction::Execute(Event event) { RaidKarazhanHelpers karazhanHelper(botAI); Unit* dorothea = AI_VALUE2(Unit*, "find target", "baroness dorothea millstipe"); Unit* catriona = AI_VALUE2(Unit*, "find target", "lady catriona von'indi"); Unit* keira = AI_VALUE2(Unit*, "find target", "lady keira berrybuck"); Unit* rafe = AI_VALUE2(Unit*, "find target", "baron rafe dreuger"); Unit* robin = AI_VALUE2(Unit*, "find target", "lord robin daris"); Unit* crispin = AI_VALUE2(Unit*, "find target", "lord crispin ference"); Unit* target = karazhanHelper.GetFirstAliveUnit({dorothea, catriona, keira, rafe, robin, crispin}); karazhanHelper.MarkTargetWithSkull(target); return false; } bool KarazhanMaidenOfVirtuePositionBossAction::Execute(Event event) { Unit* boss = AI_VALUE2(Unit*, "find target", "maiden of virtue"); Unit* healer = nullptr; if (Group* group = bot->GetGroup()) { for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) { Player* member = itr->GetSource(); if (!member || !member->IsAlive() || !botAI->IsHeal(member) || !member->HasAura(SPELL_REPENTANCE)) { continue; } healer = member; break; } } if (healer) { float angle = healer->GetOrientation(); float targetX = healer->GetPositionX() + cos(angle) * 6.0f; float targetY = healer->GetPositionY() + sin(angle) * 6.0f; float targetZ = healer->GetPositionZ(); { return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, true, MovementPriority::MOVEMENT_COMBAT); } } const float maxDistance = 3.0f; const float distanceToBossPosition = boss->GetExactDist2d(KARAZHAN_MAIDEN_OF_VIRTUE_BOSS_POSITION); if (distanceToBossPosition > maxDistance) { float dX = KARAZHAN_MAIDEN_OF_VIRTUE_BOSS_POSITION.GetPositionX() - boss->GetPositionX(); float dY = KARAZHAN_MAIDEN_OF_VIRTUE_BOSS_POSITION.GetPositionY() - boss->GetPositionY(); float mX = KARAZHAN_MAIDEN_OF_VIRTUE_BOSS_POSITION.GetPositionX() + (dX / distanceToBossPosition) * maxDistance; float mY = KARAZHAN_MAIDEN_OF_VIRTUE_BOSS_POSITION.GetPositionY() + (dY / distanceToBossPosition) * maxDistance; { return MoveTo(bot->GetMapId(), mX, mY, bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); } } return false; } bool KarazhanMaidenOfVirtuePositionBossAction::isUseful() { Unit* boss = AI_VALUE2(Unit*, "find target", "maiden of virtue"); return boss && botAI->IsTank(bot) && botAI->HasAggro(boss) && boss->GetVictim() == bot; } bool KarazhanMaidenOfVirtuePositionRangedAction::Execute(Event event) { int maxIndex = 7; int index = 0; const GuidVector members = AI_VALUE(GuidVector, "group members"); for (const auto& memberGuid : members) { Unit* member = botAI->GetUnit(memberGuid); if (!member || !botAI->IsRanged(member->ToPlayer())) { continue; } if (member == bot) break; if (index >= maxIndex) { index = 0; continue; } index++; } float distance = bot->GetExactDist2d(KARAZHAN_MAIDEN_OF_VIRTUE_RANGED_POSITION[index]); const float maxDistance = 2.0f; if (distance > maxDistance) { return MoveTo(bot->GetMapId(), KARAZHAN_MAIDEN_OF_VIRTUE_RANGED_POSITION[index].GetPositionX(), KARAZHAN_MAIDEN_OF_VIRTUE_RANGED_POSITION[index].GetPositionY(), bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); } return false; } bool KarazhanMaidenOfVirtuePositionRangedAction::isUseful() { Unit* boss = AI_VALUE2(Unit*, "find target", "maiden of virtue"); return boss && botAI->IsRanged(bot); } bool KarazhanBigBadWolfPositionBossAction::Execute(Event event) { Unit* boss = AI_VALUE2(Unit*, "find target", "the big bad wolf"); const float maxDistance = 3.0f; const float distanceToBossPosition = boss->GetExactDist2d(KARAZHAN_BIG_BAD_WOLF_BOSS_POSITION); if (distanceToBossPosition > maxDistance) { float dX = KARAZHAN_BIG_BAD_WOLF_BOSS_POSITION.GetPositionX() - boss->GetPositionX(); float dY = KARAZHAN_BIG_BAD_WOLF_BOSS_POSITION.GetPositionY() - boss->GetPositionY(); float mX = KARAZHAN_BIG_BAD_WOLF_BOSS_POSITION.GetPositionX() + (dX / distanceToBossPosition) * maxDistance; float mY = KARAZHAN_BIG_BAD_WOLF_BOSS_POSITION.GetPositionY() + (dY / distanceToBossPosition) * maxDistance; float moveDistance = bot->GetExactDist2d(mX, mY); if (moveDistance < 0.5f) { return false; } return MoveTo(bot->GetMapId(), mX, mY, bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); } return false; } bool KarazhanBigBadWolfPositionBossAction::isUseful() { Unit* boss = AI_VALUE2(Unit*, "find target", "the big bad wolf"); return boss && botAI->IsTank(bot) && botAI->HasAggro(boss) && boss->GetVictim() == bot && !bot->HasAura(SPELL_LITTLE_RED_RIDING_HOOD); } bool KarazhanBigBadWolfRunAwayAction::Execute(Event event) { constexpr float threshold = 1.0f; Position target = KARAZHAN_BIG_BAD_WOLF_RUN_POSITION[currentIndex]; while (bot->GetExactDist2d(target.GetPositionX(), target.GetPositionY()) < threshold) { currentIndex = (currentIndex + 1) % 4; target = KARAZHAN_BIG_BAD_WOLF_RUN_POSITION[currentIndex]; } return MoveTo(bot->GetMapId(), target.GetPositionX(), target.GetPositionY(), target.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_FORCED); } bool KarazhanBigBadWolfRunAwayAction::isUseful() { Unit* boss = AI_VALUE2(Unit*, "find target", "the big bad wolf"); return boss && bot->HasAura(SPELL_LITTLE_RED_RIDING_HOOD); } bool KarazhanRomuloAndJulianneMarkTargetAction::Execute(Event event) { Unit* target = nullptr; Unit* romulo = AI_VALUE2(Unit*, "find target", "romulo"); Unit* julianne = AI_VALUE2(Unit*, "find target", "julianne"); const int maxPctDifference = 10; if (julianne->GetHealthPct() + maxPctDifference < romulo->GetHealthPct() || julianne->GetHealthPct() < 1.0f) { target = romulo; } else if (romulo->GetHealthPct() + maxPctDifference < julianne->GetHealthPct() || romulo->GetHealthPct() < 1.0f) { target = julianne; } if (!target) { return false; } RaidKarazhanHelpers karazhanHelper(botAI); karazhanHelper.MarkTargetWithSkull(target); return false; } bool KarazhanWizardOfOzMarkTargetAction::Execute(Event event) { RaidKarazhanHelpers karazhanHelper(botAI); Unit* dorothee = AI_VALUE2(Unit*, "find target", "dorothee"); Unit* tito = AI_VALUE2(Unit*, "find target", "tito"); Unit* roar = AI_VALUE2(Unit*, "find target", "roar"); Unit* strawman = AI_VALUE2(Unit*, "find target", "strawman"); Unit* tinhead = AI_VALUE2(Unit*, "find target", "tinhead"); Unit* crone = AI_VALUE2(Unit*, "find target", "the crone"); Unit* target = karazhanHelper.GetFirstAliveUnit({dorothee, tito, roar, strawman, tinhead, crone}); karazhanHelper.MarkTargetWithSkull(target); return false; } bool KarazhanWizardOfOzScorchStrawmanAction::Execute(Event event) { Unit* strawman = AI_VALUE2(Unit*, "find target", "strawman"); if (!strawman || !strawman->IsAlive() || bot->getClass() != CLASS_MAGE) { return false; } if (botAI->CanCastSpell("scorch", strawman)) { botAI->CastSpell("scorch", strawman); } return false; } bool KarazhanTheCuratorMarkTargetAction::Execute(Event event) { Unit* target = AI_VALUE2(Unit*, "find target", "astral flare"); if (!target || !target->IsAlive()) { return false; } RaidKarazhanHelpers karazhanHelper(botAI); karazhanHelper.MarkTargetWithSkull(target); return false; } bool KarazhanTheCuratorPositionBossAction::Execute(Event event) { Unit* boss = AI_VALUE2(Unit*, "find target", "the curator"); const float maxDistance = 3.0f; const float distanceToBossPosition = boss->GetExactDist2d(KARAZHAN_THE_CURATOR_BOSS_POSITION); if (distanceToBossPosition > maxDistance) { float dX = KARAZHAN_THE_CURATOR_BOSS_POSITION.GetPositionX() - boss->GetPositionX(); float dY = KARAZHAN_THE_CURATOR_BOSS_POSITION.GetPositionY() - boss->GetPositionY(); float mX = KARAZHAN_THE_CURATOR_BOSS_POSITION.GetPositionX() + (dX / distanceToBossPosition) * maxDistance; float mY = KARAZHAN_THE_CURATOR_BOSS_POSITION.GetPositionY() + (dY / distanceToBossPosition) * maxDistance; { return MoveTo(bot->GetMapId(), mX, mY, bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); } } return false; } bool KarazhanTheCuratorPositionBossAction::isUseful() { Unit* boss = AI_VALUE2(Unit*, "find target", "the curator"); return boss && botAI->IsTank(bot) && botAI->HasAggro(boss) && boss->GetVictim() == bot; } bool KarazhanTheCuratorSpreadRangedAction::Execute(Event event) { RaidKarazhanHelpers karazhanHelper(botAI); const float minDistance = 5.0f; Unit* nearestPlayer = karazhanHelper.GetNearestPlayerInRadius(minDistance); if (nearestPlayer) { return FleePosition(nearestPlayer->GetPosition(), minDistance); } return false; } bool KarazhanTheCuratorSpreadRangedAction::isUseful() { Unit* boss = AI_VALUE2(Unit*, "find target", "the curator"); return boss && botAI->IsRanged(bot); } bool KarazhanTerestianIllhoofMarkTargetAction::Execute(Event event) { Unit* boss = AI_VALUE2(Unit*, "find target", "terestian illhoof"); if (!boss) { return false; } RaidKarazhanHelpers karazhanHelper(botAI); Unit* target = karazhanHelper.GetFirstAliveUnitByEntry(NPC_DEMON_CHAINS); if (!target || !target->IsAlive()) { target = karazhanHelper.GetFirstAliveUnitByEntry(NPC_KILREK); if (!target || !target->IsAlive()) { target = boss; } } karazhanHelper.MarkTargetWithSkull(target); return false; } bool KarazhanShadeOfAranArcaneExplosionRunAwayAction::Execute(Event event) { Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran"); const float safeDistance = 20.0f; const float distance = bot->GetDistance2d(boss); bot->AttackStop(); bot->InterruptNonMeleeSpells(false); if (distance < safeDistance) { return MoveAway(boss, safeDistance - distance); } return false; } bool KarazhanShadeOfAranArcaneExplosionRunAwayAction::isUseful() { Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran"); return boss && boss->IsAlive() && boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION); } bool KarazhanShadeOfAranFlameWreathStopMovementAction::Execute(Event event) { RaidKarazhanHelpers karazhanHelper(botAI); if (karazhanHelper.IsFlameWreathActive()) { AI_VALUE(LastMovement&, "last movement").Set(nullptr); bot->GetMotionMaster()->Clear(); if (bot->isMoving()) { bot->StopMoving(); } return true; } return false; } bool KarazhanShadeOfAranMarkConjuredElementalAction::Execute(Event event) { RaidKarazhanHelpers karazhanHelper(botAI); Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran"); Unit* target = karazhanHelper.GetFirstAliveUnitByEntry(NPC_CONJURED_ELEMENTAL); if (!boss || !boss->IsAlive() || !target || !target->IsAlive() || target->HasAura(SPELL_WARLOCK_BANISH)) { return false; } karazhanHelper.MarkTargetWithSkull(target); return false; } bool KarazhanShadeOfAranSpreadRangedAction::Execute(Event event) { Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran"); const float maxBossDistance = 12.0f; float bossDistance = bot->GetExactDist2d(boss); if (bossDistance > maxBossDistance) { float dX = bot->GetPositionX() - boss->GetPositionX(); float dY = bot->GetPositionY() - boss->GetPositionY(); float length = std::sqrt(dX * dX + dY * dY); dX /= length; dY /= length; float tX = boss->GetPositionX() + dX * maxBossDistance; float tY = boss->GetPositionY() + dY * maxBossDistance; { return MoveTo(bot->GetMapId(), tX, tY, bot->GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT); } } const float minDistance = 5.0f; RaidKarazhanHelpers karazhanHelper(botAI); Unit* nearestPlayer = karazhanHelper.GetNearestPlayerInRadius(minDistance); if (nearestPlayer) { return FleePosition(nearestPlayer->GetPosition(), minDistance); } return false; } bool KarazhanShadeOfAranSpreadRangedAction::isUseful() { Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran"); RaidKarazhanHelpers karazhanHelper(botAI); return boss && boss->IsAlive() && botAI->IsRanged(bot) && !karazhanHelper.IsFlameWreathActive() && !(boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION)); } // One tank bot per phase will dance in and out of the red beam (5 seconds in, 5 seconds out) // Tank bots will ignore void zones--their positioning is too important bool KarazhanNetherspiteBlockRedBeamAction::Execute(Event event) { Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); Unit* redPortal = bot->FindNearestCreature(NPC_RED_PORTAL, 150.0f); Group* group = bot->GetGroup(); if (!group) { return false; } Player* eligibleTank = nullptr; for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) { Player* member = itr->GetSource(); if (!member || !member->IsAlive() || !botAI->IsTank(member) || !GET_PLAYERBOT_AI(member) || member->HasAura(SPELL_NETHER_EXHAUSTION_RED)) { continue; } eligibleTank = member; break; } RaidKarazhanHelpers karazhanHelper(botAI); Position beamPos = karazhanHelper.GetPositionOnBeam(boss, redPortal, 18.0f); if (bot == eligibleTank) { std::map ph; ph["%player"] = bot->GetName(); std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( "netherspite_beam_blocking_red", "%player is moving to block the red beam!", ph); bot->Yell(text, LANG_UNIVERSAL); ObjectGuid botGuid = bot->GetGUID(); uint32 intervalSecs = 5; if (beamMoveTimes[botGuid] == 0) { beamMoveTimes[botGuid] = time(nullptr); lastBeamMoveSideways[botGuid] = false; } if (time(nullptr) - beamMoveTimes[botGuid] >= intervalSecs) { lastBeamMoveSideways[botGuid] = !lastBeamMoveSideways[botGuid]; beamMoveTimes[botGuid] = time(nullptr); } if (!lastBeamMoveSideways[botGuid]) { return MoveTo(bot->GetMapId(), beamPos.GetPositionX(), beamPos.GetPositionY(), beamPos.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_FORCED); } else { float bx = boss->GetPositionX(); float by = boss->GetPositionY(); float px = redPortal->GetPositionX(); float py = redPortal->GetPositionY(); float dx = px - bx; float dy = py - by; float length = sqrt(dx*dx + dy*dy); if (length == 0.0f) { return false; } dx /= length; dy /= length; float perpDx = -dy; float perpDy = dx; float sideX = beamPos.GetPositionX() + perpDx * 3.0f; float sideY = beamPos.GetPositionY() + perpDy * 3.0f; float sideZ = beamPos.GetPositionZ(); return MoveTo(bot->GetMapId(), sideX, sideY, sideZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED); } } return false; } bool KarazhanNetherspiteBlockRedBeamAction::isUseful() { Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); Unit* redPortal = bot->FindNearestCreature(NPC_RED_PORTAL, 150.0f); ObjectGuid botGuid = bot->GetGUID(); static std::map lastBossBanishState; bool bossIsBanished = boss && boss->HasAura(SPELL_NETHERSPITE_BANISHED); if (!boss || !redPortal || bossIsBanished) { return false; } if (lastBossBanishState[botGuid] != bossIsBanished) { if (!bossIsBanished) { beamMoveTimes[botGuid] = 0; lastBeamMoveSideways[botGuid] = false; } lastBossBanishState[botGuid] = bossIsBanished; } return true; } // Two non-Rogue/Warrior DPS bots will block the blue beam for each phase (swap at 25 debuff stacks) // When avoiding void zones, blocking bots will move along the beam to continue blocking bool KarazhanNetherspiteBlockBlueBeamAction::Execute(Event event) { Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); Unit* bluePortal = bot->FindNearestCreature(NPC_BLUE_PORTAL, 150.0f); RaidKarazhanHelpers karazhanHelper(botAI); std::vector blueBlockers = karazhanHelper.GetBlueBlockers(); static std::map wasBlockingBlueBeam; ObjectGuid botGuid = bot->GetGUID(); Player* assignedBlueBlocker = blueBlockers.empty() ? nullptr : blueBlockers.front(); bool isBlockingNow = (bot == assignedBlueBlocker); bool wasBlocking = wasBlockingBlueBeam[botGuid]; if (wasBlocking && !isBlockingNow) { std::map ph; ph["%player"] = bot->GetName(); std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( "netherspite_beam_leaving_blue", "%player is leaving the blue beam--next blocker up!", ph); bot->Yell(text, LANG_UNIVERSAL); wasBlockingBlueBeam[botGuid] = false; return false; } if (isBlockingNow) { if (!wasBlocking) { std::map ph; ph["%player"] = bot->GetName(); std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( "netherspite_beam_blocking_blue", "%player is moving to block the blue beam!", ph); bot->Yell(text, LANG_UNIVERSAL); } wasBlockingBlueBeam[botGuid] = true; std::vector voidZones = karazhanHelper.GetAllVoidZones(); float bx = boss->GetPositionX(); float by = boss->GetPositionY(); float bz = boss->GetPositionZ(); float px = bluePortal->GetPositionX(); float py = bluePortal->GetPositionY(); float dx = px - bx; float dy = py - by; float length = sqrt(dx*dx + dy*dy); if (length == 0.0f) { return false; } dx /= length; dy /= length; float bestDist = 150.0f; Position bestPos; bool found = false; for (float dist = 18.0f; dist <= 25.0f; dist += 0.5f) { float candidateX = bx + dx * dist; float candidateY = by + dy * dist; float candidateZ = bz; bool outsideAllVoidZones = true; for (Unit* voidZone : voidZones) { float voidZoneDist = sqrt(pow(candidateX - voidZone->GetPositionX(), 2) + pow(candidateY - voidZone->GetPositionY(), 2)); if (voidZoneDist < 4.0f) { outsideAllVoidZones = false; break; } } if (!outsideAllVoidZones) { continue; } float distToIdeal = fabs(dist - 18.0f); if (!found || distToIdeal < bestDist) { bestDist = distToIdeal; bestPos = Position(candidateX, candidateY, candidateZ); found = true; } } if (found) { return MoveTo(bot->GetMapId(), bestPos.GetPositionX(), bestPos.GetPositionY(), bestPos.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_FORCED); } return false; } wasBlockingBlueBeam[botGuid] = false; return false; } bool KarazhanNetherspiteBlockBlueBeamAction::isUseful() { Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); Unit* bluePortal = bot->FindNearestCreature(NPC_BLUE_PORTAL, 150.0f); return boss && bluePortal && !boss->HasAura(SPELL_NETHERSPITE_BANISHED); } // Two healer bots will block the green beam for each phase (swap at 25 debuff stacks) // OR one rogue or DPS warrior bot will block the green beam for an entire phase (if they begin the phase as the blocker) // When avoiding void zones, blocking bots will move along the beam to continue blocking bool KarazhanNetherspiteBlockGreenBeamAction::Execute(Event event) { Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); Unit* greenPortal = bot->FindNearestCreature(NPC_GREEN_PORTAL, 150.0f); RaidKarazhanHelpers karazhanHelper(botAI); std::vector greenBlockers = karazhanHelper.GetGreenBlockers(); static std::map wasBlockingGreenBeam; ObjectGuid botGuid = bot->GetGUID(); Player* assignedGreenBlocker = greenBlockers.empty() ? nullptr : greenBlockers.front(); bool isBlockingNow = (bot == assignedGreenBlocker); bool wasBlocking = wasBlockingGreenBeam[botGuid]; if (wasBlocking && !isBlockingNow) { std::map ph; ph["%player"] = bot->GetName(); std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( "netherspite_beam_leaving_green", "%player is leaving the green beam--next blocker up!", ph); bot->Yell(text, LANG_UNIVERSAL); wasBlockingGreenBeam[botGuid] = false; return false; } if (isBlockingNow) { if (!wasBlocking) { std::map ph; ph["%player"] = bot->GetName(); std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( "netherspite_beam_blocking_green", "%player is moving to block the green beam!", ph); bot->Yell(text, LANG_UNIVERSAL); } wasBlockingGreenBeam[botGuid] = true; std::vector voidZones = karazhanHelper.GetAllVoidZones(); float bx = boss->GetPositionX(); float by = boss->GetPositionY(); float bz = boss->GetPositionZ(); float px = greenPortal->GetPositionX(); float py = greenPortal->GetPositionY(); float dx = px - bx; float dy = py - by; float length = sqrt(dx*dx + dy*dy); if (length == 0.0f) { return false; } dx /= length; dy /= length; float bestDist = 150.0f; Position bestPos; bool found = false; for (float dist = 18.0f; dist <= 25.0f; dist += 0.5f) { float candidateX = bx + dx * dist; float candidateY = by + dy * dist; float candidateZ = bz; bool outsideAllVoidZones = true; for (Unit* voidZone : voidZones) { float voidZoneDist = sqrt(pow(candidateX - voidZone->GetPositionX(), 2) + pow(candidateY - voidZone->GetPositionY(), 2)); if (voidZoneDist < 4.0f) { outsideAllVoidZones = false; break; } } if (!outsideAllVoidZones) { continue; } float distToIdeal = fabs(dist - 18.0f); if (!found || distToIdeal < bestDist) { bestDist = distToIdeal; bestPos = Position(candidateX, candidateY, candidateZ); found = true; } } if (found) { return MoveTo(bot->GetMapId(), bestPos.GetPositionX(), bestPos.GetPositionY(), bestPos.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_FORCED); } return false; } wasBlockingGreenBeam[botGuid] = false; return false; } bool KarazhanNetherspiteBlockGreenBeamAction::isUseful() { Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); Unit* greenPortal = bot->FindNearestCreature(NPC_GREEN_PORTAL, 150.0f); return boss && greenPortal && !boss->HasAura(SPELL_NETHERSPITE_BANISHED); } // All bots not currently blocking a beam will avoid beams and void zones bool KarazhanNetherspiteAvoidBeamAndVoidZoneAction::Execute(Event event) { Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); RaidKarazhanHelpers karazhanHelper(botAI); auto [redBlocker, greenBlocker, blueBlocker] = karazhanHelper.GetCurrentBeamBlockers(); std::vector voidZones = karazhanHelper.GetAllVoidZones(); bool nearVoidZone = false; for (Unit* vz : voidZones) { if (bot->GetExactDist2d(vz) < 4.0f) { nearVoidZone = true; break; } } struct BeamAvoid { Unit* portal; float minDist, maxDist; }; std::vector beams; Unit* redPortal = bot->FindNearestCreature(NPC_RED_PORTAL, 150.0f); Unit* bluePortal = bot->FindNearestCreature(NPC_BLUE_PORTAL, 150.0f); Unit* greenPortal = bot->FindNearestCreature(NPC_GREEN_PORTAL, 150.0f); if (redPortal) { float bx = boss->GetPositionX(), by = boss->GetPositionY(); float px = redPortal->GetPositionX(), py = redPortal->GetPositionY(); float dx = px - bx, dy = py - by; float length = sqrt(dx*dx + dy*dy); beams.push_back({redPortal, 0.0f, length}); } if (bluePortal) { float bx = boss->GetPositionX(), by = boss->GetPositionY(); float px = bluePortal->GetPositionX(), py = bluePortal->GetPositionY(); float dx = px - bx, dy = py - by; float length = sqrt(dx*dx + dy*dy); beams.push_back({bluePortal, 0.0f, length}); } if (greenPortal) { float bx = boss->GetPositionX(), by = boss->GetPositionY(); float px = greenPortal->GetPositionX(), py = greenPortal->GetPositionY(); float dx = px - bx, dy = py - by; float length = sqrt(dx*dx + dy*dy); beams.push_back({greenPortal, 0.0f, length}); } bool nearBeam = false; for (const auto& beam : beams) { float bx = boss->GetPositionX(), by = boss->GetPositionY(); float px = beam.portal->GetPositionX(), py = beam.portal->GetPositionY(); float dx = px - bx, dy = py - by; float length = sqrt(dx*dx + dy*dy); if (length == 0.0f) { continue; } dx /= length; dy /= length; float botdx = bot->GetPositionX() - bx, botdy = bot->GetPositionY() - by; float t = (botdx * dx + botdy * dy); float beamX = bx + dx * t, beamY = by + dy * t; float distToBeam = sqrt(pow(bot->GetPositionX() - beamX, 2) + pow(bot->GetPositionY() - beamY, 2)); if (distToBeam < 5.0f && t > beam.minDist && t < beam.maxDist) { nearBeam = true; break; } } if (!nearVoidZone && !nearBeam) { return false; } const float minMoveDist = 3.0f, maxSearchDist = 20.0f, stepAngle = M_PI/18.0f, stepDist = 0.5f; float bossZ = boss->GetPositionZ(); Position bestCandidate; float bestDist = 0.0f; bool found = false; for (float angle = 0; angle < 2 * M_PI; angle += stepAngle) { for (float dist = 5.0f; dist <= maxSearchDist; dist += stepDist) { float cx = bot->GetPositionX() + cos(angle) * dist; float cy = bot->GetPositionY() + sin(angle) * dist; float cz = bossZ; if (std::any_of(voidZones.begin(), voidZones.end(), [&](Unit* vz){ return Position(cx, cy, cz).GetExactDist2d(vz) < 4.0f; })) { continue; } bool tooCloseToBeam = false; for (const auto& beam : beams) { float bx = boss->GetPositionX(), by = boss->GetPositionY(); float px = beam.portal->GetPositionX(), py = beam.portal->GetPositionY(); float dx = px - bx, dy = py - by; float length = sqrt(dx*dx + dy*dy); if (length == 0.0f) { continue; } dx /= length; dy /= length; float botdx = cx - bx, botdy = cy - by; float t = (botdx * dx + botdy * dy); float beamX = bx + dx * t, beamY = by + dy * t; float distToBeam = sqrt(pow(cx - beamX, 2) + pow(cy - beamY, 2)); if (distToBeam < 5.0f && t > beam.minDist && t < beam.maxDist) { tooCloseToBeam = true; break; } } if (tooCloseToBeam) { continue; } float moveDist = sqrt(pow(cx - bot->GetPositionX(), 2) + pow(cy - bot->GetPositionY(), 2)); if (moveDist < minMoveDist) { continue; } if (!found || moveDist < bestDist) { bestCandidate = Position(cx, cy, cz); bestDist = moveDist; found = true; } } } if (found && karazhanHelper.IsSafePosition(bestCandidate.GetPositionX(), bestCandidate.GetPositionY(), bestCandidate.GetPositionZ(), voidZones, 4.0f)) { return MoveTo(bot->GetMapId(), bestCandidate.GetPositionX(), bestCandidate.GetPositionY(), bestCandidate.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT); } return false; } bool KarazhanNetherspiteAvoidBeamAndVoidZoneAction::isUseful() { Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); if (!boss || boss->HasAura(SPELL_NETHERSPITE_BANISHED)) { return false; } RaidKarazhanHelpers karazhanHelper(botAI); auto [redBlocker, greenBlocker, blueBlocker] = karazhanHelper.GetCurrentBeamBlockers(); if (bot == redBlocker || bot == blueBlocker || bot == greenBlocker) { return false; } return true; } bool KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction::Execute(Event event) { RaidKarazhanHelpers karazhanHelper(botAI); std::vector voidZones = karazhanHelper.GetAllVoidZones(); for (Unit* vz : voidZones) { if (vz->GetEntry() == NPC_VOID_ZONE && bot->GetExactDist2d(vz) < 4.0f) { return FleePosition(vz->GetPosition(), 4.0f); } } return false; } bool KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction::isUseful() { Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); if (!boss || !boss->HasAura(SPELL_NETHERSPITE_BANISHED)) { return false; } RaidKarazhanHelpers karazhanHelper(botAI); std::vector voidZones = karazhanHelper.GetAllVoidZones(); for (Unit* vz : voidZones) { if (bot->GetExactDist2d(vz) < 4.0f) { return true; } } return false; } bool KarazhanPrinceMalchezaarNonTankAvoidHazardAction::Execute(Event event) { Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar"); RaidKarazhanHelpers karazhanHelper(botAI); std::vector infernals = karazhanHelper.GetSpawnedInfernals(); const float minSafeBossDistance = 35.0f; const float maxSafeBossDistance = 40.0f; const float safeInfernalDistance = 22.0f; const float stepSize = 0.5f; const int numAngles = 64; float bx = bot->GetPositionX(); float by = bot->GetPositionY(); float bz = bot->GetPositionZ(); float bossX = boss->GetPositionX(); float bossY = boss->GetPositionY(); float bossZ = boss->GetPositionZ(); float bestMoveDist = std::numeric_limits::max(); float bestDestX = 0.0f, bestDestY = 0.0f, bestDestZ = bz; bool found = false; if (bot->HasAura(SPELL_ENFEEBLE)) { for (int i = 0; i < numAngles; ++i) { float angle = (2 * M_PI * i) / numAngles; float dx = cos(angle); float dy = sin(angle); for (float dist = minSafeBossDistance; dist <= maxSafeBossDistance; dist += stepSize) { float x = bossX + dx * dist; float y = bossY + dy * dist; float destZ = bossZ; if (!bot->IsWithinLOS(x, y, destZ)) { continue; } bool pathSafe = karazhanHelper.IsStraightPathSafe(Position(bx, by, bz), Position(x, y, destZ), infernals, safeInfernalDistance, stepSize); float moveDist = sqrt(pow(x - bx, 2) + pow(y - by, 2)); if (pathSafe && moveDist < bestMoveDist) { bestMoveDist = moveDist; bestDestX = x; bestDestY = y; bestDestZ = destZ; found = true; } } } if (found) { bot->AttackStop(); bot->InterruptNonMeleeSpells(false); return MoveTo(bot->GetMapId(), bestDestX, bestDestY, bestDestZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED); } return false; } if (!bot->HasAura(SPELL_ENFEEBLE)) { bool nearInfernal = false; for (Unit* infernal : infernals) { float infernalDist = sqrt(pow(bx - infernal->GetPositionX(), 2) + pow(by - infernal->GetPositionY(), 2)); if (infernalDist < safeInfernalDistance) { nearInfernal = true; break; } } if (nearInfernal) { float bestMoveDist = std::numeric_limits::max(); float bestDestX = bx, bestDestY = by, bestDestZ = bz; bool found = false; bool usedArc = false; for (int i = 0; i < numAngles; ++i) { float angle = (2 * M_PI * i) / numAngles; float dx = cos(angle); float dy = sin(angle); for (float dist = stepSize; dist <= 35.0f; dist += stepSize) { float x = bossX + dx * dist; float y = bossY + dy * dist; float destZ = bossZ; if (!bot->IsWithinLOS(x, y, destZ)) { continue; } bool pathSafe = karazhanHelper.IsStraightPathSafe(Position(bx, by, bz), Position(x, y, destZ), infernals, safeInfernalDistance, stepSize); float moveDist = sqrt(pow(x - bx, 2) + pow(y - by, 2)); if (pathSafe && moveDist < bestMoveDist) { bestMoveDist = moveDist; bestDestX = x; bestDestY = y; bestDestZ = destZ; found = true; usedArc = false; } if (!pathSafe) { Position arcPoint = karazhanHelper.CalculateArcPoint(Position(bx, by, bz), Position(x, y, destZ), Position(bossX, bossY, bossZ)); if (!bot->IsWithinLOS(arcPoint.GetPositionX(), arcPoint.GetPositionY(), arcPoint.GetPositionZ())) { continue; } bool arcSafe = true; for (Unit* infernal : infernals) { float infernalDist = sqrt(pow(arcPoint.GetPositionX() - infernal->GetPositionX(), 2) + pow(arcPoint.GetPositionY() - infernal->GetPositionY(), 2)); if (infernalDist < safeInfernalDistance) { arcSafe = false; break; } } float arcMoveDist = sqrt(pow(arcPoint.GetPositionX() - bx, 2) + pow(arcPoint.GetPositionY() - by, 2)); if (arcSafe && arcMoveDist < bestMoveDist) { bestMoveDist = arcMoveDist; bestDestX = arcPoint.GetPositionX(); bestDestY = arcPoint.GetPositionY(); bestDestZ = arcPoint.GetPositionZ(); found = true; usedArc = true; } } } } if (found) { bot->AttackStop(); bot->InterruptNonMeleeSpells(false); return MoveTo(bot->GetMapId(), bestDestX, bestDestY, bestDestZ, false, false, false, true, MovementPriority::MOVEMENT_COMBAT); } } } return false; } bool KarazhanPrinceMalchezaarNonTankAvoidHazardAction::isUseful() { Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar"); return boss && !(botAI->IsTank(bot) && botAI->HasAggro(boss) && boss->GetVictim() == bot); } bool KarazhanPrinceMalchezaarTankAvoidHazardAction::Execute(Event event) { Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar"); RaidKarazhanHelpers karazhanHelper(botAI); std::vector infernals = karazhanHelper.GetSpawnedInfernals(); const float safeInfernalDistance = 30.0f; const float stepSize = 0.5f; const int numAngles = 64; const float maxSampleDist = 60.0f; float bx = bot->GetPositionX(); float by = bot->GetPositionY(); float bz = bot->GetPositionZ(); float bestMoveDist = std::numeric_limits::max(); float bestDestX = bx, bestDestY = by, bestDestZ = bz; bool found = false; bool usedArc = false; bool nearInfernal = false; for (Unit* infernal : infernals) { float infernalDist = sqrt(pow(bx - infernal->GetPositionX(), 2) + pow(by - infernal->GetPositionY(), 2)); if (infernalDist < safeInfernalDistance) { nearInfernal = true; break; } } if (nearInfernal) { for (int i = 0; i < numAngles; ++i) { float angle = (2 * M_PI * i) / numAngles; float dx = cos(angle); float dy = sin(angle); for (float dist = stepSize; dist <= maxSampleDist; dist += stepSize) { float x = bx + dx * dist; float y = by + dy * dist; float z = bz; if (!bot->IsWithinLOS(x, y, z)) { continue; } bool safe = karazhanHelper.IsStraightPathSafe(Position(bx, by, bz), Position(x, y, z), infernals, safeInfernalDistance, stepSize); float moveDist = sqrt(pow(x - bx, 2) + pow(y - by, 2)); if (safe && moveDist < bestMoveDist) { bestMoveDist = moveDist; bestDestX = x; bestDestY = y; bestDestZ = z; found = true; usedArc = false; } if (!safe) { Position arcPoint = karazhanHelper.CalculateArcPoint(Position(bx, by, bz), Position(x, y, z), Position(bx, by, bz)); float arcX = arcPoint.GetPositionX(); float arcY = arcPoint.GetPositionY(); float arcZ = arcPoint.GetPositionZ(); float arcDestX = arcX, arcDestY = arcY, arcDestZ = arcZ; if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bx, by, bz, arcDestX, arcDestY, arcDestZ)) { continue; } bool arcSafe = true; for (Unit* infernal : infernals) { float infernalDist = sqrt(pow(arcPoint.GetPositionX() - infernal->GetPositionX(), 2) + pow(arcPoint.GetPositionY() - infernal->GetPositionY(), 2)); if (infernalDist < safeInfernalDistance) { arcSafe = false; break; } } float arcMoveDist = sqrt(pow(arcPoint.GetPositionX() - bx, 2) + pow(arcPoint.GetPositionY() - by, 2)); if (arcSafe && arcMoveDist < bestMoveDist) { bestMoveDist = arcMoveDist; bestDestX = arcPoint.GetPositionX(); bestDestY = arcPoint.GetPositionY(); bestDestZ = arcPoint.GetPositionZ(); found = true; usedArc = true; } } } } if (found) { bot->AttackStop(); bot->InterruptNonMeleeSpells(false); return MoveTo(bot->GetMapId(), bestDestX, bestDestY, bestDestZ, false, false, false, true, MovementPriority::MOVEMENT_COMBAT); } } return false; } bool KarazhanPrinceMalchezaarTankAvoidHazardAction::isUseful() { Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar"); return boss && botAI->IsTank(bot) && botAI->HasAggro(boss) && boss->GetVictim() == bot; }