diff --git a/src/strategy/raids/karazhan/RaidKarazhanActionContext.h b/src/strategy/raids/karazhan/RaidKarazhanActionContext.h index 2c1f7168..34f276a9 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanActionContext.h +++ b/src/strategy/raids/karazhan/RaidKarazhanActionContext.h @@ -1,5 +1,5 @@ -#ifndef _PLAYERBOT_RAIDKARAZHANACTIONS_CONTEXT_H -#define _PLAYERBOT_RAIDKARAZHANACTIONS_CONTEXT_H +#ifndef _PLAYERBOT_RAIDKARAZHANACTIONCONTEXT_H +#define _PLAYERBOT_RAIDKARAZHANACTIONCONTEXT_H #include "RaidKarazhanActions.h" #include "NamedObjectContext.h" @@ -9,77 +9,254 @@ class RaidKarazhanActionContext : public NamedObjectContext public: RaidKarazhanActionContext() { - creators["karazhan attumen the huntsman stack behind"] = &RaidKarazhanActionContext::karazhan_attumen_the_huntsman_stack_behind; + // Trash + creators["mana warp stun creature before warp breach"] = + &RaidKarazhanActionContext::mana_warp_stun_creature_before_warp_breach; - creators["karazhan moroes mark target"] = &RaidKarazhanActionContext::karazhan_moroes_mark_target; + // Attumen the Huntsman + creators["attumen the huntsman mark target"] = + &RaidKarazhanActionContext::attumen_the_huntsman_mark_target; - creators["karazhan maiden of virtue position boss"] = &RaidKarazhanActionContext::karazhan_maiden_of_virtue_position_boss; - creators["karazhan maiden of virtue position ranged"] = &RaidKarazhanActionContext::karazhan_maiden_of_virtue_position_ranged; + creators["attumen the huntsman split bosses"] = + &RaidKarazhanActionContext::attumen_the_huntsman_split_bosses; - creators["karazhan big bad wolf position boss"] = &RaidKarazhanActionContext::karazhan_big_bad_wolf_position_boss; - creators["karazhan big bad wolf run away"] = &RaidKarazhanActionContext::karazhan_big_bad_wolf_run_away; + creators["attumen the huntsman stack behind"] = + &RaidKarazhanActionContext::attumen_the_huntsman_stack_behind; - creators["karazhan romulo and julianne mark target"] = &RaidKarazhanActionContext::karazhan_romulo_and_julianne_mark_target; + creators["attumen the huntsman manage dps timer"] = + &RaidKarazhanActionContext::attumen_the_huntsman_manage_dps_timer; - creators["karazhan wizard of oz mark target"] = &RaidKarazhanActionContext::karazhan_wizard_of_oz_mark_target; - creators["karazhan wizard of oz scorch strawman"] = &RaidKarazhanActionContext::karazhan_wizard_of_oz_scorch_strawman; + // Moroes + creators["moroes main tank attack boss"] = + &RaidKarazhanActionContext::moroes_main_tank_attack_boss; - creators["karazhan the curator mark target"] = &RaidKarazhanActionContext::karazhan_the_curator_mark_target; - creators["karazhan the curator position boss"] = &RaidKarazhanActionContext::karazhan_the_curator_position_boss; - creators["karazhan the curator spread ranged"] = &RaidKarazhanActionContext::karazhan_the_curator_spread_ranged; + creators["moroes mark target"] = + &RaidKarazhanActionContext::moroes_mark_target; - creators["karazhan terestian illhoof mark target"] = &RaidKarazhanActionContext::karazhan_terestian_illhoof_mark_target; + // Maiden of Virtue + creators["maiden of virtue move boss to healer"] = + &RaidKarazhanActionContext::maiden_of_virtue_move_boss_to_healer; - creators["karazhan shade of aran arcane explosion run away"] = &RaidKarazhanActionContext::karazhan_shade_of_aran_arcane_explosion_run_away; - creators["karazhan shade of aran flame wreath stop movement"] = &RaidKarazhanActionContext::karazhan_shade_of_aran_flame_wreath_stop_movement; - creators["karazhan shade of aran mark conjured elemental"] = &RaidKarazhanActionContext::karazhan_shade_of_aran_mark_conjured_elemental; - creators["karazhan shade of aran spread ranged"] = &RaidKarazhanActionContext::karazhan_shade_of_aran_spread_ranged; + creators["maiden of virtue position ranged"] = + &RaidKarazhanActionContext::maiden_of_virtue_position_ranged; - creators["karazhan netherspite block red beam"] = &RaidKarazhanActionContext::karazhan_netherspite_block_red_beam; - creators["karazhan netherspite block blue beam"] = &RaidKarazhanActionContext::karazhan_netherspite_block_blue_beam; - creators["karazhan netherspite block green beam"] = &RaidKarazhanActionContext::karazhan_netherspite_block_green_beam; - creators["karazhan netherspite avoid beam and void zone"] = &RaidKarazhanActionContext::karazhan_netherspite_avoid_beam_and_void_zone; - creators["karazhan netherspite banish phase avoid void zone"] = &RaidKarazhanActionContext::karazhan_netherspite_banish_phase_avoid_void_zone; + // The Big Bad Wolf + creators["big bad wolf position boss"] = + &RaidKarazhanActionContext::big_bad_wolf_position_boss; - creators["karazhan prince malchezaar non tank avoid hazard"] = &RaidKarazhanActionContext::karazhan_prince_malchezaar_non_tank_avoid_hazard; - creators["karazhan prince malchezaar tank avoid hazard"] = &RaidKarazhanActionContext::karazhan_prince_malchezaar_tank_avoid_hazard; + creators["big bad wolf run away from boss"] = + &RaidKarazhanActionContext::big_bad_wolf_run_away_from_boss; + + // Romulo and Julianne + creators["romulo and julianne mark target"] = + &RaidKarazhanActionContext::romulo_and_julianne_mark_target; + + // The Wizard of Oz + creators["wizard of oz mark target"] = + &RaidKarazhanActionContext::wizard_of_oz_mark_target; + creators["wizard of oz scorch strawman"] = + &RaidKarazhanActionContext::wizard_of_oz_scorch_strawman; + + // The Curator + creators["the curator mark astral flare"] = + &RaidKarazhanActionContext::the_curator_mark_astral_flare; + + creators["the curator position boss"] = + &RaidKarazhanActionContext::the_curator_position_boss; + + creators["the curator spread ranged"] = + &RaidKarazhanActionContext::the_curator_spread_ranged; + + // Terestian Illhoof + creators["terestian illhoof mark target"] = + &RaidKarazhanActionContext::terestian_illhoof_mark_target; + + // Shade of Aran + creators["shade of aran run away from arcane explosion"] = + &RaidKarazhanActionContext::shade_of_aran_run_away_from_arcane_explosion; + + creators["shade of aran stop moving during flame wreath"] = + &RaidKarazhanActionContext::shade_of_aran_stop_moving_during_flame_wreath; + + creators["shade of aran mark conjured elemental"] = + &RaidKarazhanActionContext::shade_of_aran_mark_conjured_elemental; + + creators["shade of aran ranged maintain distance"] = + &RaidKarazhanActionContext::shade_of_aran_ranged_maintain_distance; + + // Netherspite + creators["netherspite block red beam"] = + &RaidKarazhanActionContext::netherspite_block_red_beam; + + creators["netherspite block blue beam"] = + &RaidKarazhanActionContext::netherspite_block_blue_beam; + + creators["netherspite block green beam"] = + &RaidKarazhanActionContext::netherspite_block_green_beam; + + creators["netherspite avoid beam and void zone"] = + &RaidKarazhanActionContext::netherspite_avoid_beam_and_void_zone; + + creators["netherspite banish phase avoid void zone"] = + &RaidKarazhanActionContext::netherspite_banish_phase_avoid_void_zone; + + creators["netherspite manage timers and trackers"] = + &RaidKarazhanActionContext::netherspite_manage_timers_and_trackers; + + // Prince Malchezaar + creators["prince malchezaar enfeebled avoid hazard"] = + &RaidKarazhanActionContext::prince_malchezaar_enfeebled_avoid_hazard; + + creators["prince malchezaar non tank avoid infernal"] = + &RaidKarazhanActionContext::prince_malchezaar_non_tank_avoid_infernal; + + creators["prince malchezaar main tank movement"] = + &RaidKarazhanActionContext::prince_malchezaar_main_tank_movement; + + // Nightbane + creators["nightbane ground phase position boss"] = + &RaidKarazhanActionContext::nightbane_ground_phase_position_boss; + + creators["nightbane ground phase rotate ranged positions"] = + &RaidKarazhanActionContext::nightbane_ground_phase_rotate_ranged_positions; + + creators["nightbane cast fear ward on main tank"] = + &RaidKarazhanActionContext::nightbane_cast_fear_ward_on_main_tank; + + creators["nightbane control pet aggression"] = + &RaidKarazhanActionContext::nightbane_control_pet_aggression; + + creators["nightbane flight phase movement"] = + &RaidKarazhanActionContext::nightbane_flight_phase_movement; + + creators["nightbane manage timers and trackers"] = + &RaidKarazhanActionContext::nightbane_manage_timers_and_trackers; } private: - static Action* karazhan_attumen_the_huntsman_stack_behind(PlayerbotAI* botAI) { return new KarazhanAttumenTheHuntsmanStackBehindAction(botAI); } + // Trash + static Action* mana_warp_stun_creature_before_warp_breach( + PlayerbotAI* botAI) { return new ManaWarpStunCreatureBeforeWarpBreachAction(botAI); } - static Action* karazhan_moroes_mark_target(PlayerbotAI* botAI) { return new KarazhanMoroesMarkTargetAction(botAI); } + // Attumen the Huntsman + static Action* attumen_the_huntsman_mark_target( + PlayerbotAI* botAI) { return new AttumenTheHuntsmanMarkTargetAction(botAI); } - static Action* karazhan_maiden_of_virtue_position_boss(PlayerbotAI* botAI) { return new KarazhanMaidenOfVirtuePositionBossAction(botAI); } - static Action* karazhan_maiden_of_virtue_position_ranged(PlayerbotAI* botAI) { return new KarazhanMaidenOfVirtuePositionRangedAction(botAI); } + static Action* attumen_the_huntsman_split_bosses( + PlayerbotAI* botAI) { return new AttumenTheHuntsmanSplitBossesAction(botAI); } - static Action* karazhan_big_bad_wolf_position_boss(PlayerbotAI* botAI) { return new KarazhanBigBadWolfPositionBossAction(botAI); } - static Action* karazhan_big_bad_wolf_run_away(PlayerbotAI* botAI) { return new KarazhanBigBadWolfRunAwayAction(botAI); } + static Action* attumen_the_huntsman_stack_behind( + PlayerbotAI* botAI) { return new AttumenTheHuntsmanStackBehindAction(botAI); } - static Action* karazhan_romulo_and_julianne_mark_target(PlayerbotAI* botAI) { return new KarazhanRomuloAndJulianneMarkTargetAction(botAI); } + static Action* attumen_the_huntsman_manage_dps_timer( + PlayerbotAI* botAI) { return new AttumenTheHuntsmanManageDpsTimerAction(botAI); } - static Action* karazhan_wizard_of_oz_mark_target(PlayerbotAI* botAI) { return new KarazhanWizardOfOzMarkTargetAction(botAI); } - static Action* karazhan_wizard_of_oz_scorch_strawman(PlayerbotAI* botAI) { return new KarazhanWizardOfOzScorchStrawmanAction(botAI); } + // Moroes + static Action* moroes_main_tank_attack_boss( + PlayerbotAI* botAI) { return new MoroesMainTankAttackBossAction(botAI); } - static Action* karazhan_the_curator_mark_target(PlayerbotAI* botAI) { return new KarazhanTheCuratorMarkTargetAction(botAI); } - static Action* karazhan_the_curator_position_boss(PlayerbotAI* botAI) { return new KarazhanTheCuratorPositionBossAction(botAI); } - static Action* karazhan_the_curator_spread_ranged(PlayerbotAI* botAI) { return new KarazhanTheCuratorSpreadRangedAction(botAI); } + static Action* moroes_mark_target( + PlayerbotAI* botAI) { return new MoroesMarkTargetAction(botAI); } - static Action* karazhan_terestian_illhoof_mark_target(PlayerbotAI* botAI) { return new KarazhanTerestianIllhoofMarkTargetAction(botAI); } + // Maiden of Virtue + static Action* maiden_of_virtue_move_boss_to_healer( + PlayerbotAI* botAI) { return new MaidenOfVirtueMoveBossToHealerAction(botAI); } - static Action* karazhan_shade_of_aran_arcane_explosion_run_away(PlayerbotAI* botAI) { return new KarazhanShadeOfAranArcaneExplosionRunAwayAction(botAI); } - static Action* karazhan_shade_of_aran_flame_wreath_stop_movement(PlayerbotAI* botAI) { return new KarazhanShadeOfAranFlameWreathStopMovementAction(botAI); } - static Action* karazhan_shade_of_aran_mark_conjured_elemental(PlayerbotAI* botAI) { return new KarazhanShadeOfAranMarkConjuredElementalAction(botAI); } - static Action* karazhan_shade_of_aran_spread_ranged(PlayerbotAI* botAI) { return new KarazhanShadeOfAranSpreadRangedAction(botAI); } + static Action* maiden_of_virtue_position_ranged( + PlayerbotAI* botAI) { return new MaidenOfVirtuePositionRangedAction(botAI); } - static Action* karazhan_netherspite_block_red_beam(PlayerbotAI* botAI) { return new KarazhanNetherspiteBlockRedBeamAction(botAI); } - static Action* karazhan_netherspite_block_blue_beam(PlayerbotAI* botAI) { return new KarazhanNetherspiteBlockBlueBeamAction(botAI); } - static Action* karazhan_netherspite_block_green_beam(PlayerbotAI* botAI) { return new KarazhanNetherspiteBlockGreenBeamAction(botAI); } - static Action* karazhan_netherspite_avoid_beam_and_void_zone(PlayerbotAI* botAI) { return new KarazhanNetherspiteAvoidBeamAndVoidZoneAction(botAI); } - static Action* karazhan_netherspite_banish_phase_avoid_void_zone(PlayerbotAI* botAI) { return new KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction(botAI); } + // The Big Bad Wolf + static Action* big_bad_wolf_position_boss( + PlayerbotAI* botAI) { return new BigBadWolfPositionBossAction(botAI); } - static Action* karazhan_prince_malchezaar_non_tank_avoid_hazard(PlayerbotAI* botAI) { return new KarazhanPrinceMalchezaarNonTankAvoidHazardAction(botAI); } - static Action* karazhan_prince_malchezaar_tank_avoid_hazard(PlayerbotAI* botAI) { return new KarazhanPrinceMalchezaarTankAvoidHazardAction(botAI); } + static Action* big_bad_wolf_run_away_from_boss( + PlayerbotAI* botAI) { return new BigBadWolfRunAwayFromBossAction(botAI); } + + // Romulo and Julianne + static Action* romulo_and_julianne_mark_target( + PlayerbotAI* botAI) { return new RomuloAndJulianneMarkTargetAction(botAI); } + + // The Wizard of Oz + static Action* wizard_of_oz_mark_target( + PlayerbotAI* botAI) { return new WizardOfOzMarkTargetAction(botAI); } + + static Action* wizard_of_oz_scorch_strawman( + PlayerbotAI* botAI) { return new WizardOfOzScorchStrawmanAction(botAI); } + + // The Curator + static Action* the_curator_mark_astral_flare( + PlayerbotAI* botAI) { return new TheCuratorMarkAstralFlareAction(botAI); } + + static Action* the_curator_position_boss( + PlayerbotAI* botAI) { return new TheCuratorPositionBossAction(botAI); } + + static Action* the_curator_spread_ranged( + PlayerbotAI* botAI) { return new TheCuratorSpreadRangedAction(botAI); } + + // Terestian Illhoof + static Action* terestian_illhoof_mark_target( + PlayerbotAI* botAI) { return new TerestianIllhoofMarkTargetAction(botAI); } + + // Shade of Aran + static Action* shade_of_aran_run_away_from_arcane_explosion( + PlayerbotAI* botAI) { return new ShadeOfAranRunAwayFromArcaneExplosionAction(botAI); } + + static Action* shade_of_aran_stop_moving_during_flame_wreath( + PlayerbotAI* botAI) { return new ShadeOfAranStopMovingDuringFlameWreathAction(botAI); } + + static Action* shade_of_aran_mark_conjured_elemental( + PlayerbotAI* botAI) { return new ShadeOfAranMarkConjuredElementalAction(botAI); } + + static Action* shade_of_aran_ranged_maintain_distance( + PlayerbotAI* botAI) { return new ShadeOfAranRangedMaintainDistanceAction(botAI); } + + // Netherspite + static Action* netherspite_block_red_beam( + PlayerbotAI* botAI) { return new NetherspiteBlockRedBeamAction(botAI); } + + static Action* netherspite_block_blue_beam( + PlayerbotAI* botAI) { return new NetherspiteBlockBlueBeamAction(botAI); } + + static Action* netherspite_block_green_beam( + PlayerbotAI* botAI) { return new NetherspiteBlockGreenBeamAction(botAI); } + + static Action* netherspite_avoid_beam_and_void_zone( + PlayerbotAI* botAI) { return new NetherspiteAvoidBeamAndVoidZoneAction(botAI); } + + static Action* netherspite_banish_phase_avoid_void_zone( + PlayerbotAI* botAI) { return new NetherspiteBanishPhaseAvoidVoidZoneAction(botAI); } + + static Action* netherspite_manage_timers_and_trackers( + PlayerbotAI* botAI) { return new NetherspiteManageTimersAndTrackersAction(botAI); } + + // Prince Malchezaar + static Action* prince_malchezaar_enfeebled_avoid_hazard( + PlayerbotAI* botAI) { return new PrinceMalchezaarEnfeebledAvoidHazardAction(botAI); } + + static Action* prince_malchezaar_non_tank_avoid_infernal( + PlayerbotAI* botAI) { return new PrinceMalchezaarNonTankAvoidInfernalAction(botAI); } + + static Action* prince_malchezaar_main_tank_movement( + PlayerbotAI* botAI) { return new PrinceMalchezaarMainTankMovementAction(botAI); } + + // Nightbane + static Action* nightbane_ground_phase_position_boss( + PlayerbotAI* botAI) { return new NightbaneGroundPhasePositionBossAction(botAI); } + + static Action* nightbane_ground_phase_rotate_ranged_positions( + PlayerbotAI* botAI) { return new NightbaneGroundPhaseRotateRangedPositionsAction(botAI); } + + static Action* nightbane_cast_fear_ward_on_main_tank( + PlayerbotAI* botAI) { return new NightbaneCastFearWardOnMainTankAction(botAI); } + + static Action* nightbane_control_pet_aggression( + PlayerbotAI* botAI) { return new NightbaneControlPetAggressionAction(botAI); } + + static Action* nightbane_flight_phase_movement( + PlayerbotAI* botAI) { return new NightbaneFlightPhaseMovementAction(botAI); } + + static Action* nightbane_manage_timers_and_trackers( + PlayerbotAI* botAI) { return new NightbaneManageTimersAndTrackersAction(botAI); } }; #endif diff --git a/src/strategy/raids/karazhan/RaidKarazhanActions.cpp b/src/strategy/raids/karazhan/RaidKarazhanActions.cpp index e45d63f7..e452cf45 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanActions.cpp +++ b/src/strategy/raids/karazhan/RaidKarazhanActions.cpp @@ -1,81 +1,215 @@ #include "RaidKarazhanActions.h" #include "RaidKarazhanHelpers.h" -#include "AiObjectContext.h" -#include "PlayerbotAI.h" -#include "PlayerbotMgr.h" -#include "PlayerbotTextMgr.h" #include "Playerbots.h" -#include "Position.h" +#include "PlayerbotTextMgr.h" -namespace +using namespace KarazhanHelpers; + +// Trash + +// Mana Warps blow up when they die for massive raid damage +// But they cannot cast the ability if they are stunned +bool ManaWarpStunCreatureBeforeWarpBreachAction::Execute(Event event) { - // Big Bad Wolf - static int currentIndex = 0; - // Netherspite - static std::map beamMoveTimes; - static std::map lastBeamMoveSideways; -} + Unit* manaWarp = GetFirstAliveUnitByEntry(botAI, NPC_MANA_WARP); + if (!manaWarp) + return false; -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; - - if (bot->GetExactDist2d(rx, ry) > 1.0f) + static const std::array spells = { - return MoveTo(bot->GetMapId(), rx, ry, z, false, false, false, false, MovementPriority::MOVEMENT_COMBAT); + "bash", + "concussion blow", + "hammer of justice", + "kidney shot", + "maim", + "revenge stun", + "shadowfury", + "shockwave" + }; + + for (const char* spell : spells) + { + if (botAI->CanCastSpell(spell, manaWarp)) + return botAI->CastSpell(spell, manaWarp); } return false; } -bool KarazhanAttumenTheHuntsmanStackBehindAction::isUseful() -{ - RaidKarazhanHelpers karazhanHelper(botAI); - Unit* boss = karazhanHelper.GetFirstAliveUnitByEntry(NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED); +// Attumen the Huntsman - return boss && !(botAI->IsTank(bot) && botAI->HasAggro(boss) && boss->GetVictim() == bot); +// Prioritize Midnight until Attumen is mounted +bool AttumenTheHuntsmanMarkTargetAction::Execute(Event event) +{ + Unit* attumenMounted = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED); + if (attumenMounted) + { + if (IsMapIDTimerManager(botAI, bot)) + MarkTargetWithStar(bot, attumenMounted); + + SetRtiTarget(botAI, "star", attumenMounted); + + if (bot->GetTarget() != attumenMounted->GetGUID()) + { + bot->SetTarget(attumenMounted->GetGUID()); + return Attack(attumenMounted); + } + } + else if (Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight")) + { + if (IsMapIDTimerManager(botAI, bot)) + MarkTargetWithStar(bot, midnight); + + if (!botAI->IsAssistTankOfIndex(bot, 0)) + { + SetRtiTarget(botAI, "star", midnight); + + if (bot->GetTarget() != midnight->GetGUID()) + { + bot->SetTarget(midnight->GetGUID()); + return Attack(midnight); + } + } + } + + return false; } -bool KarazhanMoroesMarkTargetAction::Execute(Event event) +// Off tank should move Attumen out of the way so he doesn't cleave bots +bool AttumenTheHuntsmanSplitBossesAction::Execute(Event event) { - RaidKarazhanHelpers karazhanHelper(botAI); + Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight"); + if (!midnight) + return false; + Unit* attumen = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN); + if (!attumen) + return false; + + MarkTargetWithSquare(bot, attumen); + SetRtiTarget(botAI, "square", attumen); + + if (bot->GetVictim() != attumen) + return Attack(attumen); + + if (attumen->GetVictim() == bot && midnight->GetVictim() != bot) + { + const float safeDistance = 6.0f; + Unit* nearestPlayer = GetNearestPlayerInRadius(bot, safeDistance); + if (nearestPlayer && attumen->GetExactDist2d(nearestPlayer) < safeDistance) + return MoveFromGroup(safeDistance + 2.0f); + } + + return false; +} + +// Stack behind mounted Attumen (inside minimum range of Berserker Charge) +bool AttumenTheHuntsmanStackBehindAction::Execute(Event event) +{ + Unit* attumenMounted = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED); + if (!attumenMounted) + return false; + + const float distanceBehind = botAI->IsRanged(bot) ? 6.0f : 2.0f; + float orientation = attumenMounted->GetOrientation() + M_PI; + float rearX = attumenMounted->GetPositionX() + std::cos(orientation) * distanceBehind; + float rearY = attumenMounted->GetPositionY() + std::sin(orientation) * distanceBehind; + + if (bot->GetExactDist2d(rearX, rearY) > 1.0f) + { + return MoveTo(KARAZHAN_MAP_ID, rearX, rearY, attumenMounted->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_FORCED, true, false); + } + + return false; +} + +// Reset timer for bots to pause DPS when Attumen mounts Midnight +bool AttumenTheHuntsmanManageDpsTimerAction::Execute(Event event) +{ + Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight"); + if (!midnight) + return false; + + if (midnight && midnight->GetHealth() == midnight->GetMaxHealth()) + attumenDpsWaitTimer.erase(KARAZHAN_MAP_ID); + + // Midnight is still present as a separate (invisible) unit after Attumen mounts + // So this block can be reached + Unit* attumenMounted = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED); + if (!attumenMounted) + return false; + + const time_t now = std::time(nullptr); + + if (attumenMounted) + attumenDpsWaitTimer.try_emplace(KARAZHAN_MAP_ID, now); + + return false; +} + +// Moroes + +bool MoroesMainTankAttackBossAction::Execute(Event event) +{ + Unit* moroes = AI_VALUE2(Unit*, "find target", "moroes"); + if (!moroes) + return false; + + MarkTargetWithCircle(bot, moroes); + SetRtiTarget(botAI, "circle", moroes); + + if (bot->GetVictim() != moroes) + return Attack(moroes); + + return false; +} + +// Mark targets with skull in the recommended kill order +bool MoroesMarkTargetAction::Execute(Event event) +{ 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}); + Unit* target = GetFirstAliveUnit({dorothea, catriona, keira, rafe, robin, crispin}); - karazhanHelper.MarkTargetWithSkull(target); + if (target) + { + if (IsMapIDTimerManager(botAI, bot)) + MarkTargetWithSkull(bot, target); + + SetRtiTarget(botAI, "skull", target); + } return false; } -bool KarazhanMaidenOfVirtuePositionBossAction::Execute(Event event) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "maiden of virtue"); - Unit* healer = nullptr; +// Maiden of Virtue +// Tank the boss in the center of the room +// Move to healers after Repentenace to break the stun +bool MaidenOfVirtueMoveBossToHealerAction::Execute(Event event) +{ + Unit* maiden = AI_VALUE2(Unit*, "find target", "maiden of virtue"); + if (!maiden) + return false; + + if (bot->GetVictim() != maiden) + return Attack(maiden); + + Unit* healer = nullptr; if (Group* group = bot->GetGroup()) { - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { - Player* member = itr->GetSource(); - if (!member || !member->IsAlive() || !botAI->IsHeal(member) || !member->HasAura(SPELL_REPENTANCE)) - { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || !botAI->IsHeal(member) || + !member->HasAura(SPELL_REPENTANCE)) continue; - } + healer = member; break; } @@ -84,580 +218,534 @@ bool KarazhanMaidenOfVirtuePositionBossAction::Execute(Event event) 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(); + float targetX = healer->GetPositionX() + std::cos(angle) * 6.0f; + float targetY = healer->GetPositionY() + std::sin(angle) * 6.0f; { - bot->AttackStop(); - bot->InterruptNonMeleeSpells(false); - - return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, true, - MovementPriority::MOVEMENT_COMBAT); + return MoveTo(KARAZHAN_MAP_ID, targetX, targetY, healer->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, false); } } - const float maxDistance = 3.0f; - const float distanceToBossPosition = boss->GetExactDist2d(KARAZHAN_MAIDEN_OF_VIRTUE_BOSS_POSITION); - - if (distanceToBossPosition > maxDistance) + const Position& position = MAIDEN_OF_VIRTUE_BOSS_POSITION; + const float maxDistance = 2.0f; + float distanceToPosition = maiden->GetExactDist2d(position); + if (distanceToPosition > 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; + float dX = position.GetPositionX() - maiden->GetPositionX(); + float dY = position.GetPositionY() - maiden->GetPositionY(); + float mX = position.GetPositionX() + (dX / distanceToPosition) * maxDistance; + float mY = position.GetPositionY() + (dY / distanceToPosition) * maxDistance; { - bot->AttackStop(); - bot->InterruptNonMeleeSpells(false); - - return MoveTo(bot->GetMapId(), mX, mY, - bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); + return MoveTo(KARAZHAN_MAP_ID, mX, mY, position.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, false); } } return false; } -bool KarazhanMaidenOfVirtuePositionBossAction::isUseful() +// Spread out ranged DPS between the pillars +bool MaidenOfVirtuePositionRangedAction::Execute(Event event) { - Unit* boss = AI_VALUE2(Unit*, "find target", "maiden of virtue"); + const uint8 maxIndex = 7; + uint8 index = 0; - 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 (auto const& memberGuid : members) + if (Group* group = bot->GetGroup()) { - Unit* member = botAI->GetUnit(memberGuid); - - if (!member || !botAI->IsRanged(member->ToPlayer())) + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { - continue; - } + Player* member = ref->GetSource(); + if (!member || !botAI->IsRanged(member)) + continue; - if (member == bot) - break; + if (member == bot) + break; - if (index >= maxIndex) - { - index = 0; - continue; + if (index >= maxIndex) + { + index = 0; + continue; + } + index++; } - index++; } - float distance = bot->GetExactDist2d(KARAZHAN_MAIDEN_OF_VIRTUE_RANGED_POSITION[index]); - const float maxDistance = 2.0f; - if (distance > maxDistance) + const Position& position = MAIDEN_OF_VIRTUE_RANGED_POSITION[index]; + if (bot->GetExactDist2d(position) > 2.0f) { bot->AttackStop(); - bot->InterruptNonMeleeSpells(false); - - 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); + bot->InterruptNonMeleeSpells(true); + return MoveTo(KARAZHAN_MAP_ID, position.GetPositionX(), position.GetPositionY(), position.GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); } return false; } -bool KarazhanMaidenOfVirtuePositionRangedAction::isUseful() +// The Big Bad Wolf + +// Tank the boss at the front left corner of the stage +bool BigBadWolfPositionBossAction::Execute(Event event) { - 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]; - } - - bot->AttackStop(); - bot->InterruptNonMeleeSpells(false); - - 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) - { + Unit* wolf = AI_VALUE2(Unit*, "find target", "the big bad wolf"); + if (!wolf) return false; - } - RaidKarazhanHelpers karazhanHelper(botAI); - karazhanHelper.MarkTargetWithSkull(target); + if (bot->GetVictim() != wolf) + return Attack(wolf); + + if (wolf->GetVictim() == bot) + { + const Position& position = BIG_BAD_WOLF_BOSS_POSITION; + const float maxStep = 2.0f; + float dist = wolf->GetExactDist2d(position); + + if (dist > 0.0f && dist > maxStep) + { + float dX = position.GetPositionX() - wolf->GetPositionX(); + float dY = position.GetPositionY() - wolf->GetPositionY(); + float moveDist = std::min(maxStep, dist); + float moveX = wolf->GetPositionX() + (dX / dist) * moveDist; + float moveY = wolf->GetPositionY() + (dY / dist) * moveDist; + + return MoveTo(KARAZHAN_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + } return false; } -bool KarazhanWizardOfOzMarkTargetAction::Execute(Event event) +// Run away, little girl, run away +bool BigBadWolfRunAwayFromBossAction::Execute(Event event) +{ + const ObjectGuid botGuid = bot->GetGUID(); + uint8 index = bigBadWolfRunIndex.count(botGuid) ? bigBadWolfRunIndex[botGuid] : 0; + + while (bot->GetExactDist2d(BIG_BAD_WOLF_RUN_POSITION[index].GetPositionX(), + BIG_BAD_WOLF_RUN_POSITION[index].GetPositionY()) < 1.0f) + { + index = (index + 1) % 4; + } + + bigBadWolfRunIndex[botGuid] = index; + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + + const Position& position = BIG_BAD_WOLF_RUN_POSITION[index]; + return MoveTo(KARAZHAN_MAP_ID, position.GetPositionX(), position.GetPositionY(), position.GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_FORCED, true, false); +} + +// Romulo and Julianne + +// Keep the couple within 10% HP of each other +bool RomuloAndJulianneMarkTargetAction::Execute(Event event) +{ + Unit* romulo = AI_VALUE2(Unit*, "find target", "romulo"); + if (!romulo) + return false; + + Unit* julianne = AI_VALUE2(Unit*, "find target", "julianne"); + if (!julianne) + return false; + + Unit* target = nullptr; + const float maxPctDifference = 10.0f; + + 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; + else + target = (romulo->GetHealthPct() >= julianne->GetHealthPct()) ? romulo : julianne; + + if (target) + MarkTargetWithSkull(bot, target); + + return false; +} + +// The Wizard of Oz + +// Mark targets with skull in the recommended kill order +bool WizardOfOzMarkTargetAction::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}); + Unit* target = GetFirstAliveUnit({dorothee, tito, roar, strawman, tinhead, crone}); - karazhanHelper.MarkTargetWithSkull(target); + if (target) + MarkTargetWithSkull(bot, target); return false; } -bool KarazhanWizardOfOzScorchStrawmanAction::Execute(Event event) +// Mages spam Scorch on Strawman to disorient him +bool WizardOfOzScorchStrawmanAction::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); - } + if (strawman && botAI->CanCastSpell("scorch", strawman)) + return 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; - } +// The Curator - RaidKarazhanHelpers karazhanHelper(botAI); - karazhanHelper.MarkTargetWithSkull(target); +// Prioritize destroying Astral Flares +bool TheCuratorMarkAstralFlareAction::Execute(Event event) +{ + Unit* flare = AI_VALUE2(Unit*, "find target", "astral flare"); + if (!flare) + return false; + + if (IsMapIDTimerManager(botAI, bot)) + MarkTargetWithSkull(bot, flare); + + SetRtiTarget(botAI, "skull", flare); return false; } -bool KarazhanTheCuratorPositionBossAction::Execute(Event event) +// Tank the boss in the center of the hallway near the Guardian's Library +// Main tank and off tank will attack the boss; others will focus on Astral Flares +bool TheCuratorPositionBossAction::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); + Unit* curator = AI_VALUE2(Unit*, "find target", "the curator"); + if (!curator) + return false; - if (distanceToBossPosition > maxDistance) + MarkTargetWithCircle(bot, curator); + SetRtiTarget(botAI, "circle", curator); + + if (bot->GetVictim() != curator) + return Attack(curator); + + if (curator->GetVictim() == bot) { - 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; + const Position& position = THE_CURATOR_BOSS_POSITION; + const float maxDistance = 3.0f; + float distanceToBossPosition = curator->GetExactDist2d(position); + + if (distanceToBossPosition > maxDistance) { - return MoveTo(bot->GetMapId(), mX, mY, - bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, - false); + float dX = position.GetPositionX() - curator->GetPositionX(); + float dY = position.GetPositionY() - curator->GetPositionY(); + float mX = position.GetPositionX() + (dX / distanceToBossPosition) * maxDistance; + float mY = position.GetPositionY() + (dY / distanceToBossPosition) * maxDistance; + + return MoveTo(KARAZHAN_MAP_ID, mX, mY, position.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, false); } } return false; } -bool KarazhanTheCuratorPositionBossAction::isUseful() +// Spread out ranged DPS to avoid Arcing Sear damage +bool TheCuratorSpreadRangedAction::Execute(Event event) { - 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); + Unit* nearestPlayer = GetNearestPlayerInRadius(bot, minDistance); if (nearestPlayer) { bot->AttackStop(); - bot->InterruptNonMeleeSpells(false); - + bot->InterruptNonMeleeSpells(true); return FleePosition(nearestPlayer->GetPosition(), minDistance); } return false; } -bool KarazhanTheCuratorSpreadRangedAction::isUseful() +// Terestian Illhoof + +// Prioritize (1) Demon Chains, (2) Kil'rek, (3) Illhoof +bool TerestianIllhoofMarkTargetAction::Execute(Event event) { - Unit* boss = AI_VALUE2(Unit*, "find target", "the curator"); + Unit* demonChains = AI_VALUE2(Unit*, "find target", "demon chains"); + Unit* kilrek = AI_VALUE2(Unit*, "find target", "kil'rek"); + Unit* illhoof = AI_VALUE2(Unit*, "find target", "terestian illhoof"); + Unit* target = GetFirstAliveUnit({demonChains, kilrek, illhoof}); - 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); + if (target) + MarkTargetWithSkull(bot, 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); +// Shade of Aran +// Run to the edge of the room to avoid Arcane Explosion +bool ShadeOfAranRunAwayFromArcaneExplosionAction::Execute(Event event) +{ + Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran"); + if (!aran) + return false; + + const float safeDistance = 20.0f; + float distance = bot->GetDistance2d(aran); if (distance < safeDistance) { bot->AttackStop(); - bot->InterruptNonMeleeSpells(false); - - return MoveAway(boss, safeDistance - distance); + bot->InterruptNonMeleeSpells(true); + return MoveAway(aran, safeDistance - distance); } return false; } -bool KarazhanShadeOfAranArcaneExplosionRunAwayAction::isUseful() +// I will not move when Flame Wreath is cast or the raid blows up +bool ShadeOfAranStopMovingDuringFlameWreathAction::Execute(Event event) { - Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran"); + AI_VALUE(LastMovement&, "last movement").Set(nullptr); - 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()) + if (bot->isMoving()) { - AI_VALUE(LastMovement&, "last movement").Set(nullptr); bot->GetMotionMaster()->Clear(); - if (bot->isMoving()) - { - bot->StopMoving(); - } + bot->StopMoving(); return true; } return false; } -bool KarazhanShadeOfAranMarkConjuredElementalAction::Execute(Event event) +// Mark Conjured Elementals with skull so DPS can burn them down +bool ShadeOfAranMarkConjuredElementalAction::Execute(Event event) { - RaidKarazhanHelpers karazhanHelper(botAI); - Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran"); - Unit* target = karazhanHelper.GetFirstAliveUnitByEntry(NPC_CONJURED_ELEMENTAL); + Unit* elemental = GetFirstAliveUnitByEntry(botAI, NPC_CONJURED_ELEMENTAL); - if (!boss || !boss->IsAlive() || - !target || !target->IsAlive() || target->HasAura(SPELL_WARLOCK_BANISH)) - { - return false; - } - - karazhanHelper.MarkTargetWithSkull(target); + if (elemental) + MarkTargetWithSkull(bot, elemental); return false; } -bool KarazhanShadeOfAranSpreadRangedAction::Execute(Event event) +// Don't get closer than 11 yards to Aran to avoid counterspell +// Don't get farther than 15 yards from Aran to avoid getting stuck in alcoves +bool ShadeOfAranRangedMaintainDistanceAction::Execute(Event event) { - Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran"); + Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran"); + if (!aran) + return false; - const float maxBossDistance = 12.0f; - float bossDistance = bot->GetExactDist2d(boss); - if (bossDistance > maxBossDistance) + Group* group = bot->GetGroup(); + if (!group) + return false; + + const float minDist = 11.0f; + const float maxDist = 15.0f; + const float ringIncrement = M_PI / 8; + const float distIncrement = 0.5f; + + float bestX = 0, bestY = 0, bestMoveDist = std::numeric_limits::max(); + bool found = false; + + for (float dist = minDist; dist <= maxDist; dist += distIncrement) { - 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; + for (float angle = 0; angle < 2 * M_PI; angle += ringIncrement) { - return MoveTo(bot->GetMapId(), tX, tY, bot->GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_COMBAT); + float x = aran->GetPositionX() + std::cos(angle) * dist; + float y = aran->GetPositionY() + std::sin(angle) * dist; + + bool tooClose = false; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || member == bot || !member->IsAlive()) + continue; + if (member->GetExactDist2d(x, y) < 3.0f) + { + tooClose = true; + break; + } + } + if (tooClose) + continue; + + float moveDist = bot->GetExactDist2d(x, y); + if (moveDist < bestMoveDist) + { + bestMoveDist = moveDist; + bestX = x; + bestY = y; + found = true; + } } } - const float minDistance = 5.0f; - RaidKarazhanHelpers karazhanHelper(botAI); - Unit* nearestPlayer = karazhanHelper.GetNearestPlayerInRadius(minDistance); - if (nearestPlayer) + if (found && bestMoveDist > 0.5f) { - return FleePosition(nearestPlayer->GetPosition(), minDistance); + return MoveTo(KARAZHAN_MAP_ID, bestX, bestY, bot->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, false); } 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)); -} +// Netherspite // 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) +// Tank bots will ignore void zones--their positioning is too important to risk losing beam control +bool NetherspiteBlockRedBeamAction::Execute(Event event) { - Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); + Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite"); + if (!netherspite) + return false; + Unit* redPortal = bot->FindNearestCreature(NPC_RED_PORTAL, 150.0f); + if (!redPortal) + return false; - RaidKarazhanHelpers karazhanHelper(botAI); - static std::map wasBlockingRedBeam; - ObjectGuid botGuid = bot->GetGUID(); - auto [redBlocker, greenBlocker, blueBlocker] = karazhanHelper.GetCurrentBeamBlockers(); + const ObjectGuid botGuid = bot->GetGUID(); + auto [redBlocker, greenBlocker, blueBlocker] = GetCurrentBeamBlockers(botAI, bot); bool isBlockingNow = (bot == redBlocker); - bool wasBlocking = wasBlockingRedBeam[botGuid]; - Position beamPos = karazhanHelper.GetPositionOnBeam(boss, redPortal, 18.0f); + auto it = _wasBlockingRedBeam.find(botGuid); + bool wasBlocking = (it != _wasBlockingRedBeam.end()) ? it->second : false; + + Position beamPos = GetPositionOnBeam(netherspite, redPortal, 18.0f); if (isBlockingNow) { if (!wasBlocking) { - std::map ph; - ph["%player"] = bot->GetName(); + std::map placeholders{{"%player", bot->GetName()}}; std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( - "netherspite_beam_blocking_red", "%player is moving to block the red beam!", ph); + "netherspite_beam_blocking_red", "%player is moving to block the red beam!", placeholders); bot->Yell(text, LANG_UNIVERSAL); } - wasBlockingRedBeam[botGuid] = true; + _wasBlockingRedBeam[botGuid] = true; - uint32 intervalSecs = 5; - - if (beamMoveTimes[botGuid] == 0) - { - beamMoveTimes[botGuid] = time(nullptr); - lastBeamMoveSideways[botGuid] = false; - } - if (time(nullptr) - beamMoveTimes[botGuid] >= intervalSecs) + const uint8 intervalSecs = 5; + if (std::time(nullptr) - redBeamMoveTimer[botGuid] >= intervalSecs) { lastBeamMoveSideways[botGuid] = !lastBeamMoveSideways[botGuid]; - beamMoveTimes[botGuid] = time(nullptr); + redBeamMoveTimer[botGuid] = std::time(nullptr); } if (!lastBeamMoveSideways[botGuid]) - { - return MoveTo(bot->GetMapId(), beamPos.GetPositionX(), beamPos.GetPositionY(), beamPos.GetPositionZ(), - false, false, false, true, MovementPriority::MOVEMENT_FORCED); - } + return MoveTo(KARAZHAN_MAP_ID, beamPos.GetPositionX(), beamPos.GetPositionY(), beamPos.GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_FORCED, true, false); 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); + float length = netherspite->GetExactDist2d(redPortal); if (length == 0.0f) - { return false; - } - dx /= length; - dy /= length; + float dx = (redPortal->GetPositionX() - netherspite->GetPositionX()) / length; + float dy = (redPortal->GetPositionY() - netherspite->GetPositionY()) / 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 MoveTo(KARAZHAN_MAP_ID, sideX, sideY, sideZ, false, false, false, false, + MovementPriority::MOVEMENT_FORCED, true, false); } } - wasBlockingRedBeam[botGuid] = false; + _wasBlockingRedBeam[botGuid] = false; return false; } -bool KarazhanNetherspiteBlockRedBeamAction::isUseful() +Position NetherspiteBlockRedBeamAction::GetPositionOnBeam(Unit* netherspite, Unit* portal, float distanceFromBoss) { - Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); - Unit* redPortal = bot->FindNearestCreature(NPC_RED_PORTAL, 150.0f); + float bx = netherspite->GetPositionX(); + float by = netherspite->GetPositionY(); + float bz = netherspite->GetPositionZ(); + float px = portal->GetPositionX(); + float py = portal->GetPositionY(); - ObjectGuid botGuid = bot->GetGUID(); - static std::map lastBossBanishState; - bool bossIsBanished = boss && boss->HasAura(SPELL_NETHERSPITE_BANISHED); + float dx = px - bx; + float dy = py - by; + float length = netherspite->GetExactDist2d(px, py); + if (length == 0.0f) + return Position(bx, by, bz); - if (lastBossBanishState[botGuid] != bossIsBanished) - { - if (!bossIsBanished) - { - beamMoveTimes[botGuid] = 0; - lastBeamMoveSideways[botGuid] = false; - } - lastBossBanishState[botGuid] = bossIsBanished; - } + dx /= length; + dy /= length; + float targetX = bx + dx * distanceFromBoss; + float targetY = by + dy * distanceFromBoss; + float targetZ = bz; - return boss && redPortal && !bossIsBanished; + return Position(targetX, targetY, targetZ); } -// Two non-Rogue/Warrior DPS bots will block the blue beam for each phase (swap at 26 debuff stacks) +// 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) +bool NetherspiteBlockBlueBeamAction::Execute(Event event) { - Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); - Unit* bluePortal = bot->FindNearestCreature(NPC_BLUE_PORTAL, 150.0f); + Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite"); + if (!netherspite) + return false; - RaidKarazhanHelpers karazhanHelper(botAI); - static std::map wasBlockingBlueBeam; - ObjectGuid botGuid = bot->GetGUID(); - auto [redBlocker, greenBlocker, blueBlocker] = karazhanHelper.GetCurrentBeamBlockers(); + Unit* bluePortal = bot->FindNearestCreature(NPC_BLUE_PORTAL, 150.0f); + if (!bluePortal) + return false; + + const ObjectGuid botGuid = bot->GetGUID(); + auto [redBlocker, greenBlocker, blueBlocker] = GetCurrentBeamBlockers(botAI, bot); bool isBlockingNow = (bot == blueBlocker); - bool wasBlocking = wasBlockingBlueBeam[botGuid]; + + auto it = _wasBlockingBlueBeam.find(botGuid); + bool wasBlocking = (it != _wasBlockingBlueBeam.end()) ? it->second : false; if (wasBlocking && !isBlockingNow) { - std::map ph; - ph["%player"] = bot->GetName(); + std::map placeholders{{"%player", bot->GetName()}}; std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( - "netherspite_beam_leaving_blue", "%player is leaving the blue beam--next blocker up!", ph); + "netherspite_beam_leaving_blue", "%player is leaving the blue beam--next blocker up!", placeholders); bot->Yell(text, LANG_UNIVERSAL); - wasBlockingBlueBeam[botGuid] = false; - - return false; + _wasBlockingBlueBeam[botGuid] = false; } if (isBlockingNow) { if (!wasBlocking) { - std::map ph; - ph["%player"] = bot->GetName(); + std::map placeholders{{"%player", bot->GetName()}}; std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( - "netherspite_beam_blocking_blue", "%player is moving to block the blue beam!", ph); + "netherspite_beam_blocking_blue", "%player is moving to block the blue beam!", placeholders); bot->Yell(text, LANG_UNIVERSAL); } - wasBlockingBlueBeam[botGuid] = true; + _wasBlockingBlueBeam[botGuid] = true; - std::vector voidZones = karazhanHelper.GetAllVoidZones(); - float bx = boss->GetPositionX(); - float by = boss->GetPositionY(); - float bz = boss->GetPositionZ(); + float idealDistance = botAI->IsRanged(bot) ? 25.0f : 18.0f; + std::vector voidZones = GetAllVoidZones(botAI, bot); + + float bx = netherspite->GetPositionX(); + float by = netherspite->GetPositionY(); + float bz = netherspite->GetPositionZ(); float px = bluePortal->GetPositionX(); float py = bluePortal->GetPositionY(); + float dx = px - bx; float dy = py - by; - float length = sqrt(dx*dx + dy*dy); + float length = netherspite->GetExactDist2d(bluePortal); 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 <= 30.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) - { + if (!IsSafePosition(candidateX, candidateY, candidateZ, voidZones, 4.0f)) continue; - } - float distToIdeal = fabs(dist - 18.0f); + + float distToIdeal = fabs(dist - idealDistance); if (!found || distToIdeal < bestDist) { bestDist = distToIdeal; @@ -665,108 +753,90 @@ bool KarazhanNetherspiteBlockBlueBeamAction::Execute(Event event) found = true; } } + if (found) { bot->AttackStop(); - bot->InterruptNonMeleeSpells(false); - - return MoveTo(bot->GetMapId(), bestPos.GetPositionX(), bestPos.GetPositionY(), bestPos.GetPositionZ(), - false, false, false, true, MovementPriority::MOVEMENT_FORCED); + bot->InterruptNonMeleeSpells(true); + return MoveTo(KARAZHAN_MAP_ID, bestPos.GetPositionX(), bestPos.GetPositionY(), bestPos.GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_FORCED, true, false); } return false; } - wasBlockingBlueBeam[botGuid] = 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 26 debuff stacks) +// 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) +bool NetherspiteBlockGreenBeamAction::Execute(Event event) { - Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); - Unit* greenPortal = bot->FindNearestCreature(NPC_GREEN_PORTAL, 150.0f); + Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite"); + if (!netherspite) + return false; - RaidKarazhanHelpers karazhanHelper(botAI); - static std::map wasBlockingGreenBeam; - ObjectGuid botGuid = bot->GetGUID(); - auto [redBlocker, greenBlocker, blueBlocker] = karazhanHelper.GetCurrentBeamBlockers(); + Unit* greenPortal = bot->FindNearestCreature(NPC_GREEN_PORTAL, 150.0f); + if (!greenPortal) + return false; + + const ObjectGuid botGuid = bot->GetGUID(); + auto [redBlocker, greenBlocker, blueBlocker] = GetCurrentBeamBlockers(botAI, bot); bool isBlockingNow = (bot == greenBlocker); - bool wasBlocking = wasBlockingGreenBeam[botGuid]; + + auto it = _wasBlockingGreenBeam.find(botGuid); + bool wasBlocking = (it != _wasBlockingGreenBeam.end()) ? it->second : false; if (wasBlocking && !isBlockingNow) { - std::map ph; - ph["%player"] = bot->GetName(); + std::map placeholders{{"%player", bot->GetName()}}; std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( - "netherspite_beam_leaving_green", "%player is leaving the green beam--next blocker up!", ph); + "netherspite_beam_leaving_green", "%player is leaving the green beam--next blocker up!", placeholders); bot->Yell(text, LANG_UNIVERSAL); - wasBlockingGreenBeam[botGuid] = false; - - return false; + _wasBlockingGreenBeam[botGuid] = false; } if (isBlockingNow) { if (!wasBlocking) { - std::map ph; - ph["%player"] = bot->GetName(); + std::map placeholders{{"%player", bot->GetName()}}; std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( - "netherspite_beam_blocking_green", "%player is moving to block the green beam!", ph); + "netherspite_beam_blocking_green", "%player is moving to block the green beam!", placeholders); bot->Yell(text, LANG_UNIVERSAL); } - wasBlockingGreenBeam[botGuid] = true; + _wasBlockingGreenBeam[botGuid] = true; - std::vector voidZones = karazhanHelper.GetAllVoidZones(); - float bx = boss->GetPositionX(); - float by = boss->GetPositionY(); - float bz = boss->GetPositionZ(); + std::vector voidZones = GetAllVoidZones(botAI, bot); + + float bx = netherspite->GetPositionX(); + float by = netherspite->GetPositionY(); + float bz = netherspite->GetPositionZ(); float px = greenPortal->GetPositionX(); float py = greenPortal->GetPositionY(); + float dx = px - bx; float dy = py - by; - float length = sqrt(dx*dx + dy*dy); + float length = netherspite->GetExactDist2d(greenPortal); 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 <= 30.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) - { + if (!IsSafePosition(candidateX, candidateY, candidateZ, voidZones, 4.0f)) continue; - } + float distToIdeal = fabs(dist - 18.0f); if (!found || distToIdeal < bestDist) { @@ -775,380 +845,362 @@ bool KarazhanNetherspiteBlockGreenBeamAction::Execute(Event event) found = true; } } + if (found) { bot->AttackStop(); - bot->InterruptNonMeleeSpells(false); - - return MoveTo(bot->GetMapId(), bestPos.GetPositionX(), bestPos.GetPositionY(), bestPos.GetPositionZ(), - false, false, false, true, MovementPriority::MOVEMENT_FORCED); + bot->InterruptNonMeleeSpells(true); + return MoveTo(KARAZHAN_MAP_ID, bestPos.GetPositionX(), bestPos.GetPositionY(), bestPos.GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_FORCED, true, false); } return false; } - wasBlockingGreenBeam[botGuid] = 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) +bool NetherspiteAvoidBeamAndVoidZoneAction::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; }; + Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite"); + if (!netherspite) + return false; + + auto [redBlocker, greenBlocker, blueBlocker] = GetCurrentBeamBlockers(botAI, bot); + std::vector voidZones = GetAllVoidZones(botAI, bot); + + bool nearVoidZone = !IsSafePosition(bot->GetPositionX(), bot->GetPositionY(), + bot->GetPositionZ(), voidZones, 4.0f); + 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); + float length = netherspite->GetExactDist2d(redPortal); 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); + float length = netherspite->GetExactDist2d(bluePortal); 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); + float length = netherspite->GetExactDist2d(greenPortal); beams.push_back({greenPortal, 0.0f, length}); } - bool nearBeam = false; - for (auto const& 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 = 2.0f, maxSearchDist = 30.0f, stepAngle = M_PI/18.0f, stepDist = 0.5f; - float bossZ = boss->GetPositionZ(); + bool nearBeam = !IsAwayFromBeams(bot->GetPositionX(), bot->GetPositionY(), beams, netherspite); + + if (!nearVoidZone && !nearBeam) + return false; + + const float minMoveDist = 2.0f; + const float minMoveDistSq = minMoveDist * minMoveDist; + const float maxSearchDist = 30.0f, stepAngle = M_PI/18.0f, stepDist = 0.5f; + float netherspiteZ = netherspite->GetPositionZ(); Position bestCandidate; - float bestDist = 0.0f; + float bestDistSq = std::numeric_limits::max(); bool found = false; + + const float botX = bot->GetPositionX(); + const float botY = bot->GetPositionY(); + for (float angle = 0; angle < 2 * M_PI; angle += stepAngle) { for (float dist = 2.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; })) - { + float cx = botX + std::cos(angle) * dist; + float cy = botY + std::sin(angle) * dist; + float cz = netherspiteZ; + + if (!IsSafePosition(cx, cy, cz, voidZones, 4.0f) || + !IsAwayFromBeams(cx, cy, beams, netherspite)) continue; - } - bool tooCloseToBeam = false; - for (auto const& 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) - { + + float dx = cx - botX; + float dy = cy - botY; + float moveDistSq = dx*dx + dy*dy; + if (moveDistSq < minMoveDistSq) continue; - } - float moveDist = sqrt(pow(cx - bot->GetPositionX(), 2) + pow(cy - bot->GetPositionY(), 2)); - if (moveDist < minMoveDist) - { - continue; - } - if (!found || moveDist < bestDist) + + if (!found || moveDistSq < bestDistSq) { bestCandidate = Position(cx, cy, cz); - bestDist = moveDist; + bestDistSq = moveDistSq; found = true; } } } - if (found && karazhanHelper.IsSafePosition(bestCandidate.GetPositionX(), - bestCandidate.GetPositionY(), bestCandidate.GetPositionZ(), - voidZones, 4.0f)) + + if (found) { bot->AttackStop(); - bot->InterruptNonMeleeSpells(false); - - return MoveTo(bot->GetMapId(), bestCandidate.GetPositionX(), bestCandidate.GetPositionY(), - bestCandidate.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT); + bot->InterruptNonMeleeSpells(true); + return MoveTo(KARAZHAN_MAP_ID, bestCandidate.GetPositionX(), bestCandidate.GetPositionY(), + bestCandidate.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, false); } return false; } -bool KarazhanNetherspiteAvoidBeamAndVoidZoneAction::isUseful() +bool NetherspiteAvoidBeamAndVoidZoneAction::IsAwayFromBeams( + float x, float y, const std::vector& beams, Unit* netherspite) { - Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); - if (!boss || boss->HasAura(SPELL_NETHERSPITE_BANISHED)) + for (auto const& beam : beams) { - return false; - } + float bx = netherspite->GetPositionX(), by = netherspite->GetPositionY(); + float px = beam.portal->GetPositionX(), py = beam.portal->GetPositionY(); + float dx = px - bx, dy = py - by; + float length = netherspite->GetExactDist2d(beam.portal); - RaidKarazhanHelpers karazhanHelper(botAI); - auto [redBlocker, greenBlocker, blueBlocker] = karazhanHelper.GetCurrentBeamBlockers(); - if (bot == redBlocker || bot == blueBlocker || bot == greenBlocker) - { - return false; + if (length == 0.0f) + continue; + + dx /= length; dy /= length; + float botdx = x - bx, botdy = y - by; + float distanceAlongBeam = (botdx * dx + botdy * dy); + float beamX = bx + dx * distanceAlongBeam, beamY = by + dy * distanceAlongBeam; + float distToBeam = sqrt((x - beamX) * (x - beamX) + (y - beamY) * (y - beamY)); + + if (distToBeam < 5.0f && distanceAlongBeam > beam.minDist && distanceAlongBeam < beam.maxDist) + return false; } return true; } -bool KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction::Execute(Event event) +bool NetherspiteBanishPhaseAvoidVoidZoneAction::Execute(Event event) { - RaidKarazhanHelpers karazhanHelper(botAI); - std::vector voidZones = karazhanHelper.GetAllVoidZones(); + std::vector voidZones = GetAllVoidZones(botAI, bot); 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() +bool NetherspiteManageTimersAndTrackersAction::Execute(Event event) { - Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); - if (!boss || !boss->HasAura(SPELL_NETHERSPITE_BANISHED)) - { + Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite"); + if (!netherspite) return false; - } - RaidKarazhanHelpers karazhanHelper(botAI); - std::vector voidZones = karazhanHelper.GetAllVoidZones(); - for (Unit* vz : voidZones) + const ObjectGuid botGuid = bot->GetGUID(); + const time_t now = std::time(nullptr); + + // DpsWaitTimer is for pausing DPS during phase transitions + // redBeamMoveTimer and lastBeamMoveSideways are for tank dancing in/out of the red beam + if (netherspite->GetHealth() == netherspite->GetMaxHealth() && + !netherspite->HasAura(SPELL_GREEN_BEAM_HEAL)) { - if (bot->GetExactDist2d(vz) < 4.0f) + if (IsMapIDTimerManager(botAI, bot)) + netherspiteDpsWaitTimer.insert_or_assign(KARAZHAN_MAP_ID, now); + + if (botAI->IsTank(bot) && !bot->HasAura(SPELL_RED_BEAM_DEBUFF)) { - return true; + redBeamMoveTimer.erase(botGuid); + lastBeamMoveSideways.erase(botGuid); + } + } + else if (netherspite->HasAura(SPELL_NETHERSPITE_BANISHED)) + { + if (IsMapIDTimerManager(botAI, bot)) + netherspiteDpsWaitTimer.erase(KARAZHAN_MAP_ID); + + if (botAI->IsTank(bot)) + { + redBeamMoveTimer.erase(botGuid); + lastBeamMoveSideways.erase(botGuid); + } + } + else if (!netherspite->HasAura(SPELL_NETHERSPITE_BANISHED)) + { + if (IsMapIDTimerManager(botAI, bot)) + netherspiteDpsWaitTimer.try_emplace(KARAZHAN_MAP_ID, now); + + if (botAI->IsTank(bot) && bot->HasAura(SPELL_RED_BEAM_DEBUFF)) + { + redBeamMoveTimer.try_emplace(botGuid, now); + lastBeamMoveSideways.try_emplace(botGuid, false); } } return false; } -bool KarazhanPrinceMalchezaarNonTankAvoidHazardAction::Execute(Event event) +// Move away from the boss to avoid Shadow Nova when Enfeebled +// Do not cross within Infernal Hellfire radius while doing so +bool PrinceMalchezaarEnfeebledAvoidHazardAction::Execute(Event event) { - Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar"); - RaidKarazhanHelpers karazhanHelper(botAI); - std::vector infernals = karazhanHelper.GetSpawnedInfernals(); + Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar"); + if (!malchezaar) + return false; + + std::vector infernals = GetSpawnedInfernals(botAI); const float minSafeBossDistance = 34.0f; + const float minSafeBossDistanceSq = minSafeBossDistance * minSafeBossDistance; const float maxSafeBossDistance = 60.0f; const float safeInfernalDistance = 23.0f; - const float stepSize = 0.5f; - const int numAngles = 64; + const float safeInfernalDistanceSq = safeInfernalDistance * safeInfernalDistance; + const float distIncrement = 0.5f; + const uint8 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 malchezaarX = malchezaar->GetPositionX(); + float malchezaarY = malchezaar->GetPositionY(); + float malchezaarZ = malchezaar->GetPositionZ(); + float bestMoveDistSq = 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) { - for (int i = 0; i < numAngles; ++i) + float angle = (2 * M_PI * i) / numAngles; + float dx = std::cos(angle); + float dy = std::sin(angle); + + for (float dist = minSafeBossDistance; dist <= maxSafeBossDistance; dist += distIncrement) { - 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 = malchezaarX + dx * dist; + float y = malchezaarY + dy * dist; + float destZ = malchezaarZ; + float destX = x, destY = y; + if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bx, by, bz, destX, destY, destZ, true)) + continue; + + float ddx = destX - malchezaarX; + float ddy = destY - malchezaarY; + float distFromBossSq = ddx*ddx + ddy*ddy; + if (distFromBossSq < minSafeBossDistanceSq) + continue; + + bool pathSafe = IsStraightPathSafe(Position(bx, by, bz), Position(destX, destY, destZ), + infernals, safeInfernalDistance, distIncrement); + float mdx = destX - bx; + float mdy = destY - by; + float moveDistSq = mdx*mdx + mdy*mdy; + + if (pathSafe && moveDistSq < bestMoveDistSq) { - float x = bossX + dx * dist; - float y = bossY + dy * dist; - float destZ = bossZ; - float destX = x, destY = y, destZ2 = destZ; - if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bx, by, bz, destX, destY, destZ2, true)) - { - continue; - } - float distFromBoss = sqrt(pow(destX - bossX, 2) + pow(destY - bossY, 2)); - if (distFromBoss < minSafeBossDistance) - { - continue; - } - bool pathSafe = karazhanHelper.IsStraightPathSafe(Position(bx, by, bz), Position(destX, destY, destZ2), - infernals, safeInfernalDistance, stepSize); - float moveDist = sqrt(pow(destX - bx, 2) + pow(destY - by, 2)); - if (pathSafe && moveDist < bestMoveDist) - { - bestMoveDist = moveDist; - bestDestX = destX; - bestDestY = destY; - bestDestZ = destZ2; - found = true; - } + bestMoveDistSq = moveDistSq; + bestDestX = destX; + bestDestY = destY; + bestDestZ = destZ; + found = true; } } + } + + if (found) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + return MoveTo(KARAZHAN_MAP_ID, bestDestX, bestDestY, bestDestZ, false, false, false, false, + MovementPriority::MOVEMENT_FORCED, true, false); + } + + return false; +} + +// Move away from infernals while staying within range of the boss +// Prioritize finding a safe path to the new location, but will fallback to just finding a safe location if needed +bool PrinceMalchezaarNonTankAvoidInfernalAction::Execute(Event event) +{ + Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar"); + if (!malchezaar) + return false; + + std::vector infernals = GetSpawnedInfernals(botAI); + + const float safeInfernalDistance = 23.0f; + const float safeInfernalDistanceSq = safeInfernalDistance * safeInfernalDistance; + const float maxSafeBossDistance = 35.0f; + + float bx = bot->GetPositionX(); + float by = bot->GetPositionY(); + float bz = bot->GetPositionZ(); + float malchezaarX = malchezaar->GetPositionX(); + float malchezaarY = malchezaar->GetPositionY(); + float malchezaarZ = malchezaar->GetPositionZ(); + + bool nearInfernal = false; + for (Unit* infernal : infernals) + { + float dx = bx - infernal->GetPositionX(); + float dy = by - infernal->GetPositionY(); + float infernalDistSq = dx*dx + dy*dy; + if (infernalDistSq < safeInfernalDistanceSq) + { + nearInfernal = true; + break; + } + } + + float bestDestX = bx, bestDestY = by, bestDestZ = bz; + bool found = false; + + if (nearInfernal) + { + const float distIncrement = 0.5f; + const uint8 numAngles = 64; + + // 1. Try to find a safe position with a safe path + found = TryFindSafePositionWithSafePath(bot, bx, by, bz, malchezaarX, malchezaarY, malchezaarZ, + infernals, safeInfernalDistance, distIncrement, numAngles, maxSafeBossDistance, + true, bestDestX, bestDestY, bestDestZ); + + // 2. Fallback: try to find a safe position (ignore path safety) + if (!found) + { + found = TryFindSafePositionWithSafePath(bot, bx, by, bz, malchezaarX, malchezaarY, malchezaarZ, + infernals, safeInfernalDistance, distIncrement, numAngles, maxSafeBossDistance, + false, bestDestX, bestDestY, bestDestZ); + } + 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; - 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 <= maxSafeBossDistance; dist += stepSize) - { - float x = bossX + dx * dist; - float y = bossY + dy * dist; - float destZ = bossZ; - float destX = x, destY = y, destZ2 = destZ; - if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bossX, bossY, bossZ, destX, destY, destZ2, true)) - { - continue; - } - bool destSafe = true; - for (Unit* infernal : infernals) - { - float infernalDist = sqrt(pow(destX - infernal->GetPositionX(), 2) + pow(destY - infernal->GetPositionY(), 2)); - if (infernalDist < safeInfernalDistance) - { - destSafe = false; - break; - } - } - if (!destSafe) - continue; - float moveDist = sqrt(pow(destX - bx, 2) + pow(destY - by, 2)); - if (moveDist < bestMoveDist) - { - bestMoveDist = moveDist; - bestDestX = destX; - bestDestY = destY; - bestDestZ = destZ2; - found = true; - } - } - } - if (found) - { - bot->AttackStop(); - bot->InterruptNonMeleeSpells(false); - - return MoveTo(bot->GetMapId(), bestDestX, bestDestY, bestDestZ, false, false, false, true, - MovementPriority::MOVEMENT_COMBAT); - } + bot->InterruptNonMeleeSpells(true); + return MoveTo(KARAZHAN_MAP_ID, bestDestX, bestDestY, bestDestZ, false, false, false, false, + MovementPriority::MOVEMENT_FORCED, true, false); } } return false; } -bool KarazhanPrinceMalchezaarNonTankAvoidHazardAction::isUseful() +// This is similar to the non-tank avoid infernal action, but the movement is based on the bot's location +// And the safe distance from infernals is larger to give melee more room to maneuver +bool PrinceMalchezaarMainTankMovementAction::Execute(Event event) { - Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar"); + Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar"); + if (!malchezaar) + return false; - return boss && !(botAI->IsTank(bot) && botAI->HasAggro(boss) && boss->GetVictim() == bot); -} + if (bot->GetVictim() != malchezaar) + return Attack(malchezaar); -bool KarazhanPrinceMalchezaarTankAvoidHazardAction::Execute(Event event) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar"); - RaidKarazhanHelpers karazhanHelper(botAI); - std::vector infernals = karazhanHelper.GetSpawnedInfernals(); + std::vector infernals = GetSpawnedInfernals(botAI); + + const float safeInfernalDistance = 30.0f; + const float safeInfernalDistanceSq = safeInfernalDistance * safeInfernalDistance; + const float maxSampleDist = 75.0f; - const float safeInfernalDistance = 28.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(); @@ -1156,116 +1208,283 @@ bool KarazhanPrinceMalchezaarTankAvoidHazardAction::Execute(Event event) bool nearInfernal = false; for (Unit* infernal : infernals) { - float infernalDist = sqrt(pow(bx - infernal->GetPositionX(), 2) + pow(by - infernal->GetPositionY(), 2)); - if (infernalDist < safeInfernalDistance) + float dx = bx - infernal->GetPositionX(); + float dy = by - infernal->GetPositionY(); + float infernalDistSq = dx*dx + dy*dy; + if (infernalDistSq < safeInfernalDistanceSq) { nearInfernal = true; break; } } - float bestMoveDist = std::numeric_limits::max(); float bestDestX = bx, bestDestY = by, bestDestZ = bz; bool found = false; 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; + const float distIncrement = 0.5f; + const uint8 numAngles = 64; - float destX = x, destY = y, destZ = z; - if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bx, by, bz, destX, destY, destZ, true)) - continue; + // 1. Try to find a safe position with a safe path + found = TryFindSafePositionWithSafePath( bot, bx, by, bz, bx, by, bz, + infernals, safeInfernalDistance, distIncrement, numAngles, maxSampleDist, + true, bestDestX, bestDestY, bestDestZ); - bool destSafe = true; - for (Unit* infernal : infernals) - { - float infernalDist = sqrt(pow(destX - infernal->GetPositionX(), 2) + pow(destY - infernal->GetPositionY(), 2)); - if (infernalDist < safeInfernalDistance) - { - destSafe = false; - break; - } - } - if (!destSafe) - continue; - - bool pathSafe = karazhanHelper.IsStraightPathSafe(Position(bx, by, bz), Position(destX, destY, destZ), - infernals, safeInfernalDistance, stepSize); - float moveDist = sqrt(pow(destX - bx, 2) + pow(destY - by, 2)); - if (pathSafe && moveDist < bestMoveDist) - { - bestMoveDist = moveDist; - bestDestX = destX; - bestDestY = destY; - bestDestZ = destZ; - found = true; - } - } - } + // 2. Fallback: try to find a safe position (ignore path safety) if (!found) { - 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; - - float destX = x, destY = y, destZ = z; - if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bx, by, bz, destX, destY, destZ, true)) - continue; - - bool destSafe = true; - for (Unit* infernal : infernals) - { - float infernalDist = sqrt(pow(destX - infernal->GetPositionX(), 2) + pow(destY - infernal->GetPositionY(), 2)); - if (infernalDist < safeInfernalDistance) - { - destSafe = false; - break; - } - } - float moveDist = sqrt(pow(destX - bx, 2) + pow(destY - by, 2)); - if (destSafe && moveDist < bestMoveDist) - { - bestMoveDist = moveDist; - bestDestX = destX; - bestDestY = destY; - bestDestZ = destZ; - found = true; - } - } - } + found = TryFindSafePositionWithSafePath( bot, bx, by, bz, bx, by, bz, + infernals, safeInfernalDistance, distIncrement, numAngles, maxSampleDist, + false, bestDestX, bestDestY, bestDestZ); } + if (found) { bot->AttackStop(); - bot->InterruptNonMeleeSpells(false); - - return MoveTo(bot->GetMapId(), bestDestX, bestDestY, bestDestZ, false, false, false, true, - MovementPriority::MOVEMENT_COMBAT); + return MoveTo(KARAZHAN_MAP_ID, bestDestX, bestDestY, bestDestZ, false, false, false, false, + MovementPriority::MOVEMENT_FORCED, true, false); } } return false; } -bool KarazhanPrinceMalchezaarTankAvoidHazardAction::isUseful() +// The tank position is near the Southeastern area of the Master's Terrace +// The tank moves Nightbane into position in two steps to try to get Nightbane to face sideways to the raid +bool NightbaneGroundPhasePositionBossAction::Execute(Event event) { - Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar"); + Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane"); + if (!nightbane) + return false; - return boss && botAI->IsTank(bot) && botAI->HasAggro(boss) && boss->GetVictim() == bot; + MarkTargetWithSkull(bot, nightbane); + + if (bot->GetVictim() != nightbane) + return Attack(nightbane); + + const ObjectGuid botGuid = bot->GetGUID(); + uint8 step = nightbaneTankStep.count(botGuid) ? nightbaneTankStep[botGuid] : 0; + + if (nightbane->GetVictim() == bot) + { + const Position tankPositions[2] = + { + NIGHTBANE_TRANSITION_BOSS_POSITION, + NIGHTBANE_FINAL_BOSS_POSITION + }; + const Position& position = tankPositions[step]; + const float maxDistance = 0.5f; + float distanceToTarget = bot->GetExactDist2d(position); + + if ((distanceToTarget > maxDistance) && bot->IsWithinMeleeRange(nightbane)) + return MoveTo(KARAZHAN_MAP_ID, position.GetPositionX(), position.GetPositionY(), position.GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_FORCED, true, true); + + if (step == 0 && distanceToTarget <= maxDistance) + nightbaneTankStep[botGuid] = 1; + + if (step == 1 && distanceToTarget <= maxDistance) + { + float orientation = atan2(nightbane->GetPositionY() - bot->GetPositionY(), + nightbane->GetPositionX() - bot->GetPositionX()); + bot->SetFacingTo(orientation); + } + } + + return false; +} + +// Ranged bots rotate between 3 positions to avoid standing in Charred Earth, which lasts for +// 30s and has a minimum cooldown of 18s (so there can be 2 active at once) +// Ranged positions are near the Northeastern door to the tower +bool NightbaneGroundPhaseRotateRangedPositionsAction::Execute(Event event) +{ + const ObjectGuid botGuid = bot->GetGUID(); + uint8 index = nightbaneRangedStep.count(botGuid) ? nightbaneRangedStep[botGuid] : 0; + + const Position rangedPositions[3] = + { + NIGHTBANE_RANGED_POSITION1, + NIGHTBANE_RANGED_POSITION2, + NIGHTBANE_RANGED_POSITION3 + }; + const Position& position = rangedPositions[index]; + const float maxDistance = 2.0f; + float distanceToTarget = bot->GetExactDist2d(position); + + if (distanceToTarget <= maxDistance && + bot->HasAura(SPELL_CHARRED_EARTH) && !bot->HasAura(SPELL_BELLOWING_ROAR)) + { + index = (index + 1) % 3; + nightbaneRangedStep[botGuid] = index; + const Position& newPosition = rangedPositions[index]; + float newDistanceToTarget = bot->GetExactDist2d(newPosition); + if (newDistanceToTarget > maxDistance) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + return MoveTo(KARAZHAN_MAP_ID, newPosition.GetPositionX(), newPosition.GetPositionY(), newPosition.GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_FORCED, true, false); + } + return false; + } + + if (distanceToTarget > maxDistance) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + return MoveTo(KARAZHAN_MAP_ID, position.GetPositionX(), position.GetPositionY(), position.GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_FORCED, true, false); + } + + return false; +} + +// For countering Bellowing Roars during the ground phase +bool NightbaneCastFearWardOnMainTankAction::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 && botAI->IsMainTank(member)) + { + mainTank = member; + break; + } + } + } + + if (mainTank && botAI->CanCastSpell("fear ward", mainTank)) + return botAI->CastSpell("fear ward", mainTank); + + return false; +} + +// Put pets on passive during the flight phase so they don't try to chase Nightbane off the map +bool NightbaneControlPetAggressionAction::Execute(Event event) +{ + Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane"); + if (!nightbane) + return false; + + Pet* pet = bot->GetPet(); + if (!pet) + return false; + + if (nightbane->GetPositionZ() <= NIGHTBANE_FLIGHT_Z && pet->GetReactState() == REACT_PASSIVE) + pet->SetReactState(REACT_DEFENSIVE); + + if (nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z && pet->GetReactState() != REACT_PASSIVE) + { + pet->AttackStop(); + pet->SetReactState(REACT_PASSIVE); + } + + return false; +} + +// 1. Stack at the "Flight Stack Position" near Nightbane so he doesn't use Fireball Barrage +// 2. Once Rain of Bones hits, the whole party moves to a new stack position +// This action lasts for the first 35 seconds of the flight phase, after which Nightbane gets +// ready to land, and the player will need to lead the bots over near the ground phase position +bool NightbaneFlightPhaseMovementAction::Execute(Event event) +{ + Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane"); + if (!nightbane || nightbane->GetPositionZ() <= NIGHTBANE_FLIGHT_Z) + return false; + + MarkTargetWithMoon(bot, nightbane); + + Unit* botTarget = botAI->GetUnit(bot->GetTarget()); + if (botTarget && botTarget == nightbane) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + } + + const ObjectGuid botGuid = bot->GetGUID(); + bool hasRainOfBones = bot->HasAura(SPELL_RAIN_OF_BONES); + + if (hasRainOfBones) + nightbaneRainOfBonesHit[botGuid] = true; + + float destX, destY, destZ; + if (nightbaneRainOfBonesHit[botGuid]) + { + destX = NIGHTBANE_RAIN_OF_BONES_POSITION.GetPositionX(); + destY = NIGHTBANE_RAIN_OF_BONES_POSITION.GetPositionY(); + destZ = NIGHTBANE_RAIN_OF_BONES_POSITION.GetPositionZ(); + } + else + { + destX = NIGHTBANE_FLIGHT_STACK_POSITION.GetPositionX(); + destY = NIGHTBANE_FLIGHT_STACK_POSITION.GetPositionY(); + destZ = NIGHTBANE_FLIGHT_STACK_POSITION.GetPositionZ(); + } + + if (bot->GetExactDist2d(destX, destY) > 2.0f) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + return MoveTo(KARAZHAN_MAP_ID, destX, destY, destZ, false, false, false, false, + MovementPriority::MOVEMENT_FORCED, true, false); + } + + return false; +} + +bool NightbaneManageTimersAndTrackersAction::Execute(Event event) +{ + Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane"); + if (!nightbane) + return false; + + const ObjectGuid botGuid = bot->GetGUID(); + const time_t now = std::time(nullptr); + + // Erase DPS wait timer and tank and ranged position tracking on encounter reset + if (nightbane->GetHealth() == nightbane->GetMaxHealth()) + { + if (botAI->IsMainTank(bot)) + nightbaneTankStep.erase(botGuid); + + if (botAI->IsRanged(bot)) + nightbaneRangedStep.erase(botGuid); + + if (IsMapIDTimerManager(botAI, bot)) + nightbaneDpsWaitTimer.erase(KARAZHAN_MAP_ID); + } + // Erase flight phase timer and Rain of Bones tracker on ground phase and start DPS wait timer + else if (nightbane->GetPositionZ() <= NIGHTBANE_FLIGHT_Z) + { + nightbaneRainOfBonesHit.erase(botGuid); + + if (IsMapIDTimerManager(botAI, bot)) + { + nightbaneFlightPhaseStartTimer.erase(KARAZHAN_MAP_ID); + nightbaneDpsWaitTimer.try_emplace(KARAZHAN_MAP_ID, now); + } + } + // Erase DPS wait timer and tank and ranged position tracking and start flight phase timer + // at beginning of flight phase + else if (nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z) + { + if (botAI->IsMainTank(bot)) + nightbaneTankStep.erase(botGuid); + + if (botAI->IsRanged(bot)) + nightbaneRangedStep.erase(botGuid); + + if (IsMapIDTimerManager(botAI, bot)) + { + nightbaneDpsWaitTimer.erase(KARAZHAN_MAP_ID); + nightbaneFlightPhaseStartTimer.try_emplace(KARAZHAN_MAP_ID, now); + } + } + + return false; } diff --git a/src/strategy/raids/karazhan/RaidKarazhanActions.h b/src/strategy/raids/karazhan/RaidKarazhanActions.h index 4ab24ed9..2d50944c 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanActions.h +++ b/src/strategy/raids/karazhan/RaidKarazhanActions.h @@ -2,217 +2,322 @@ #define _PLAYERBOT_RAIDKARAZHANACTIONS_H #include "Action.h" +#include "AttackAction.h" #include "MovementActions.h" -class KarazhanAttumenTheHuntsmanStackBehindAction : public MovementAction +class ManaWarpStunCreatureBeforeWarpBreachAction : public AttackAction { public: - KarazhanAttumenTheHuntsmanStackBehindAction(PlayerbotAI* botAI, std::string const name = "karazhan attumen the huntsman stack behind") : MovementAction(botAI, name) {} - - bool Execute(Event event) override; - bool isUseful() override; -}; - -class KarazhanMoroesMarkTargetAction : public Action -{ -public: - KarazhanMoroesMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan moroes mark target") : Action(botAI, name) {} + ManaWarpStunCreatureBeforeWarpBreachAction( + PlayerbotAI* botAI, std::string const name = "mana warp stun creature before warp breach") : AttackAction(botAI, name) {} bool Execute(Event event) override; }; -class KarazhanMaidenOfVirtuePositionBossAction : public MovementAction +class AttumenTheHuntsmanMarkTargetAction : public AttackAction { public: - KarazhanMaidenOfVirtuePositionBossAction(PlayerbotAI* botAI, std::string const name = "karazhan maiden of virtue position boss") : MovementAction(botAI, name) {} - + AttumenTheHuntsmanMarkTargetAction( + PlayerbotAI* botAI, std::string const name = "attumen the huntsman mark target") : AttackAction(botAI, name) {} bool Execute(Event event) override; - bool isUseful() override; }; -class KarazhanMaidenOfVirtuePositionRangedAction : public MovementAction +class AttumenTheHuntsmanSplitBossesAction : public AttackAction { public: - KarazhanMaidenOfVirtuePositionRangedAction(PlayerbotAI* botAI, std::string const name = "karazhan maiden of virtue position ranged") : MovementAction(botAI, name) {} - + AttumenTheHuntsmanSplitBossesAction( + PlayerbotAI* botAI, std::string const name = "attumen the huntsman split bosses") : AttackAction(botAI, name) {} bool Execute(Event event) override; - bool isUseful() override; }; -class KarazhanBigBadWolfPositionBossAction : public MovementAction +class AttumenTheHuntsmanStackBehindAction : public MovementAction { public: - KarazhanBigBadWolfPositionBossAction(PlayerbotAI* botAI, std::string const name = "karazhan big bad wolf position boss") : MovementAction(botAI, name) {} - + AttumenTheHuntsmanStackBehindAction( + PlayerbotAI* botAI, std::string const name = "attumen the huntsman stack behind") : MovementAction(botAI, name) {} bool Execute(Event event) override; - bool isUseful() override; }; -class KarazhanBigBadWolfRunAwayAction : public MovementAction +class AttumenTheHuntsmanManageDpsTimerAction : public Action { public: - KarazhanBigBadWolfRunAwayAction(PlayerbotAI* botAI, std::string const name = "karazhan big bad wolf run away") : MovementAction(botAI, name) {} - + AttumenTheHuntsmanManageDpsTimerAction( + PlayerbotAI* botAI, std::string const name = "attumen the huntsman manage dps timer") : Action(botAI, name) {} + bool Execute(Event event) override; +}; + +class MoroesMainTankAttackBossAction : public AttackAction +{ +public: + MoroesMainTankAttackBossAction( + PlayerbotAI* botAI, std::string const name = "moroes main tank attack boss") : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class MoroesMarkTargetAction : public Action +{ +public: + MoroesMarkTargetAction( + PlayerbotAI* botAI, std::string const name = "moroes mark target") : Action(botAI, name) {} + bool Execute(Event event) override; +}; + +class MaidenOfVirtueMoveBossToHealerAction : public AttackAction +{ +public: + MaidenOfVirtueMoveBossToHealerAction( + PlayerbotAI* botAI, std::string const name = "maiden of virtue move boss to healer") : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class MaidenOfVirtuePositionRangedAction : public MovementAction +{ +public: + MaidenOfVirtuePositionRangedAction( + PlayerbotAI* botAI, std::string const name = "maiden of virtue position ranged") : MovementAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class BigBadWolfPositionBossAction : public AttackAction +{ +public: + BigBadWolfPositionBossAction( + PlayerbotAI* botAI, std::string const name = "big bad wolf position boss") : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class BigBadWolfRunAwayFromBossAction : public MovementAction +{ +public: + BigBadWolfRunAwayFromBossAction( + PlayerbotAI* botAI, std::string const name = "big bad wolf run away from boss") : MovementAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class RomuloAndJulianneMarkTargetAction : public Action +{ +public: + RomuloAndJulianneMarkTargetAction( + PlayerbotAI* botAI, std::string const name = "romulo and julianne mark target") : Action(botAI, name) {} + bool Execute(Event event) override; +}; + +class WizardOfOzMarkTargetAction : public Action +{ +public: + WizardOfOzMarkTargetAction( + PlayerbotAI* botAI, std::string const name = "wizard of oz mark target") : Action(botAI, name) {} + bool Execute(Event event) override; +}; + +class WizardOfOzScorchStrawmanAction : public Action +{ +public: + WizardOfOzScorchStrawmanAction( + PlayerbotAI* botAI, std::string const name = "wizard of oz scorch strawman") : Action(botAI, name) {} + bool Execute(Event event) override; +}; + +class TheCuratorMarkAstralFlareAction : public Action +{ +public: + TheCuratorMarkAstralFlareAction( + PlayerbotAI* botAI, std::string const name = "the curator mark astral flare") : Action(botAI, name) {} + bool Execute(Event event) override; +}; + +class TheCuratorPositionBossAction : public AttackAction +{ +public: + TheCuratorPositionBossAction( + PlayerbotAI* botAI, std::string const name = "the curator position boss") : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class TheCuratorSpreadRangedAction : public MovementAction +{ +public: + TheCuratorSpreadRangedAction( + PlayerbotAI* botAI, std::string const name = "the curator spread ranged") : MovementAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class TerestianIllhoofMarkTargetAction : public Action +{ +public: + TerestianIllhoofMarkTargetAction( + PlayerbotAI* botAI, std::string const name = "terestian illhoof mark target") : Action(botAI, name) {} + bool Execute(Event event) override; +}; + +class ShadeOfAranRunAwayFromArcaneExplosionAction : public MovementAction +{ +public: + ShadeOfAranRunAwayFromArcaneExplosionAction( + PlayerbotAI* botAI, std::string const name = "shade of aran run away from arcane explosion") : MovementAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class ShadeOfAranStopMovingDuringFlameWreathAction : public MovementAction +{ +public: + ShadeOfAranStopMovingDuringFlameWreathAction( + PlayerbotAI* botAI, std::string const name = "shade of aran stop moving during flame wreath") : MovementAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class ShadeOfAranMarkConjuredElementalAction : public Action +{ +public: + ShadeOfAranMarkConjuredElementalAction( + PlayerbotAI* botAI, std::string const name = "shade of aran mark conjured elemental") : Action(botAI, name) {} + bool Execute(Event event) override; +}; + +class ShadeOfAranRangedMaintainDistanceAction : public MovementAction +{ +public: + ShadeOfAranRangedMaintainDistanceAction( + PlayerbotAI* botAI, std::string const name = "shade of aran ranged maintain distance") : MovementAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class NetherspiteBlockRedBeamAction : public MovementAction +{ +public: + NetherspiteBlockRedBeamAction( + PlayerbotAI* botAI, std::string const name = "netherspite block red beam") : MovementAction(botAI, name) {} bool Execute(Event event) override; - bool isUseful() override; private: - size_t currentIndex = 0; + Position GetPositionOnBeam(Unit* netherspite, Unit* portal, float distanceFromBoss); + std::unordered_map _wasBlockingRedBeam; }; -class KarazhanRomuloAndJulianneMarkTargetAction : public Action +class NetherspiteBlockBlueBeamAction : public MovementAction { public: - KarazhanRomuloAndJulianneMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan romulo and julianne mark target") : Action(botAI, name) {} + NetherspiteBlockBlueBeamAction( + PlayerbotAI* botAI, std::string const name = "netherspite block blue beam") : MovementAction(botAI, name) {} + bool Execute(Event event) override; +private: + std::unordered_map _wasBlockingBlueBeam; +}; + +class NetherspiteBlockGreenBeamAction : public MovementAction +{ +public: + NetherspiteBlockGreenBeamAction( + PlayerbotAI* botAI, std::string const name = "netherspite block green beam") : MovementAction(botAI, name) {} + bool Execute(Event event) override; + +private: + std::unordered_map _wasBlockingGreenBeam; +}; + +class NetherspiteAvoidBeamAndVoidZoneAction : public MovementAction +{ +public: + NetherspiteAvoidBeamAndVoidZoneAction( + PlayerbotAI* botAI, std::string const name = "netherspite avoid beam and void zone") : MovementAction(botAI, name) {} + bool Execute(Event event) override; + +private: + struct BeamAvoid + { + Unit* portal; + float minDist, maxDist; + }; + bool IsAwayFromBeams(float x, float y, const std::vector& beams, Unit* netherspite); +}; + +class NetherspiteBanishPhaseAvoidVoidZoneAction : public MovementAction +{ +public: + NetherspiteBanishPhaseAvoidVoidZoneAction( + PlayerbotAI* botAI, std::string const name = "netherspite banish phase avoid void zone") : MovementAction(botAI, name) {} bool Execute(Event event) override; }; -class KarazhanWizardOfOzMarkTargetAction : public Action +class NetherspiteManageTimersAndTrackersAction : public Action { public: - KarazhanWizardOfOzMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan wizard of oz mark target") : Action(botAI, name) {} - + NetherspiteManageTimersAndTrackersAction( + PlayerbotAI* botAI, std::string const name = "netherspite manage timers and trackers") : Action(botAI, name) {} bool Execute(Event event) override; }; -class KarazhanWizardOfOzScorchStrawmanAction : public Action +class PrinceMalchezaarEnfeebledAvoidHazardAction : public MovementAction { public: - KarazhanWizardOfOzScorchStrawmanAction(PlayerbotAI* botAI, std::string const name = "karazhan wizard of oz scorch strawman") : Action(botAI, name) {} - + PrinceMalchezaarEnfeebledAvoidHazardAction( + PlayerbotAI* botAI, std::string const name = "prince malchezaar enfeebled avoid hazard") : MovementAction(botAI, name) {} bool Execute(Event event) override; }; -class KarazhanTheCuratorMarkTargetAction : public Action +class PrinceMalchezaarNonTankAvoidInfernalAction : public MovementAction { public: - KarazhanTheCuratorMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan the curator mark target") : Action(botAI, name) {} - + PrinceMalchezaarNonTankAvoidInfernalAction( + PlayerbotAI* botAI, std::string const name = "prince malchezaar non tank avoid infernal") : MovementAction(botAI, name) {} bool Execute(Event event) override; }; -class KarazhanTheCuratorPositionBossAction : public MovementAction +class PrinceMalchezaarMainTankMovementAction : public AttackAction { public: - KarazhanTheCuratorPositionBossAction(PlayerbotAI* botAI, std::string const name = "karazhan the curator position boss") : MovementAction(botAI, name) {} - - bool Execute(Event event) override; - bool isUseful() override; -}; - -class KarazhanTheCuratorSpreadRangedAction : public MovementAction -{ -public: - KarazhanTheCuratorSpreadRangedAction(PlayerbotAI* botAI, std::string const name = "karazhan the curator spread ranged") : MovementAction(botAI, name) {} - - bool Execute(Event event) override; - bool isUseful() override; -}; - -class KarazhanTerestianIllhoofMarkTargetAction : public Action -{ -public: - KarazhanTerestianIllhoofMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan terestian illhoof mark target") : Action(botAI, name) {} - + PrinceMalchezaarMainTankMovementAction( + PlayerbotAI* botAI, std::string const name = "prince malchezaar main tank movement") : AttackAction(botAI, name) {} bool Execute(Event event) override; }; -class KarazhanShadeOfAranArcaneExplosionRunAwayAction : public MovementAction +class NightbaneGroundPhasePositionBossAction : public AttackAction { public: - KarazhanShadeOfAranArcaneExplosionRunAwayAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran arcane explosion run away") : MovementAction(botAI, name) {} - - bool Execute(Event event) override; - bool isUseful() override; -}; - -class KarazhanShadeOfAranFlameWreathStopMovementAction : public MovementAction -{ -public: - KarazhanShadeOfAranFlameWreathStopMovementAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran flame wreath stop bot") : MovementAction(botAI, name) {} - + NightbaneGroundPhasePositionBossAction( + PlayerbotAI* botAI, std::string const name = "nightbane ground phase position boss") : AttackAction(botAI, name) {} bool Execute(Event event) override; }; -class KarazhanShadeOfAranMarkConjuredElementalAction : public Action +class NightbaneGroundPhaseRotateRangedPositionsAction : public MovementAction { public: - KarazhanShadeOfAranMarkConjuredElementalAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran mark conjured elemental") : Action(botAI, name) {} - + NightbaneGroundPhaseRotateRangedPositionsAction( + PlayerbotAI* botAI, std::string const name = "nightbane ground phase rotate ranged positions") : MovementAction(botAI, name) {} bool Execute(Event event) override; }; -class KarazhanShadeOfAranSpreadRangedAction : public MovementAction +class NightbaneCastFearWardOnMainTankAction : public Action { public: - KarazhanShadeOfAranSpreadRangedAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran spread ranged") : MovementAction(botAI, name) {} - + NightbaneCastFearWardOnMainTankAction( + PlayerbotAI* botAI, std::string const name = "nightbane cast fear ward on main tank") : Action(botAI, name) {} bool Execute(Event event) override; - bool isUseful() override; }; -class KarazhanNetherspiteBlockRedBeamAction : public MovementAction +class NightbaneControlPetAggressionAction : public Action { public: - KarazhanNetherspiteBlockRedBeamAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite block red beam") : MovementAction(botAI, name) {} - + NightbaneControlPetAggressionAction( + PlayerbotAI* botAI, std::string const name = "nightbane control pet aggression") : Action(botAI, name) {} bool Execute(Event event) override; - bool isUseful() override; }; -class KarazhanNetherspiteBlockBlueBeamAction : public MovementAction +class NightbaneFlightPhaseMovementAction : public MovementAction { public: - KarazhanNetherspiteBlockBlueBeamAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite block blue beam") : MovementAction(botAI, name) {} - + NightbaneFlightPhaseMovementAction( + PlayerbotAI* botAI, std::string const name = "nightbane flight phase movement") : MovementAction(botAI, name) {} bool Execute(Event event) override; - bool isUseful() override; }; -class KarazhanNetherspiteBlockGreenBeamAction : public MovementAction +class NightbaneManageTimersAndTrackersAction : public Action { public: - KarazhanNetherspiteBlockGreenBeamAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite block green beam") : MovementAction(botAI, name) {} - + NightbaneManageTimersAndTrackersAction( + PlayerbotAI* botAI, std::string const name = "nightbane manage timers and trackers") : Action(botAI, name) {} bool Execute(Event event) override; - bool isUseful() override; -}; - -class KarazhanNetherspiteAvoidBeamAndVoidZoneAction : public MovementAction -{ -public: - KarazhanNetherspiteAvoidBeamAndVoidZoneAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite avoid beam and void zone") : MovementAction(botAI, name) {} - - bool Execute(Event event) override; - bool isUseful() override; -}; - -class KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction : public MovementAction -{ -public: - KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite banish phase avoid void zone") : MovementAction(botAI, name) {} - - bool Execute(Event event) override; - bool isUseful() override; -}; - -class KarazhanPrinceMalchezaarNonTankAvoidHazardAction : public MovementAction -{ -public: - KarazhanPrinceMalchezaarNonTankAvoidHazardAction(PlayerbotAI* botAI, std::string const name = "karazhan prince malchezaar non-tank avoid hazard") : MovementAction(botAI, name) {} - - bool Execute(Event event) override; - bool isUseful() override; -}; - -class KarazhanPrinceMalchezaarTankAvoidHazardAction : public MovementAction -{ -public: - KarazhanPrinceMalchezaarTankAvoidHazardAction(PlayerbotAI* botAI, std::string const name = "karazhan prince malchezaar tank avoid hazard") : MovementAction(botAI, name) {} - - bool Execute(Event event) override; - bool isUseful() override; }; #endif diff --git a/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp b/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp index 29634174..9ca9f7b3 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp +++ b/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp @@ -1,316 +1,356 @@ -#include -#include - #include "RaidKarazhanHelpers.h" #include "RaidKarazhanActions.h" -#include "AiObjectContext.h" -#include "PlayerbotMgr.h" -#include "Position.h" -#include "Spell.h" +#include "Playerbots.h" +#include "RtiTargetValue.h" -const Position KARAZHAN_MAIDEN_OF_VIRTUE_BOSS_POSITION = Position(-10945.881f, -2103.782f, 92.712f); -const Position KARAZHAN_MAIDEN_OF_VIRTUE_RANGED_POSITION[8] = +namespace KarazhanHelpers { - { -10931.178f, -2116.580f, 92.179f }, - { -10925.828f, -2102.425f, 92.180f }, - { -10933.089f, -2088.5017f, 92.180f }, - { -10947.59f, -2082.8147f, 92.180f }, - { -10960.912f, -2090.4368f, 92.179f }, - { -10966.017f, -2105.288f, 92.175f }, - { -10959.242f, -2119.6172f, 92.180f }, - { -10944.495f, -2123.857f, 92.180f }, -}; + // Attumen the Huntsman + std::unordered_map attumenDpsWaitTimer; + // Big Bad Wolf + std::unordered_map bigBadWolfRunIndex; + // Netherspite + std::unordered_map netherspiteDpsWaitTimer; + std::unordered_map redBeamMoveTimer; + std::unordered_map lastBeamMoveSideways; + // Nightbane + std::unordered_map nightbaneDpsWaitTimer; + std::unordered_map nightbaneTankStep; + std::unordered_map nightbaneRangedStep; + std::unordered_map nightbaneFlightPhaseStartTimer; + std::unordered_map nightbaneRainOfBonesHit; -const Position KARAZHAN_BIG_BAD_WOLF_BOSS_POSITION = Position(-10913.391f, -1773.508f, 90.477f); -const Position KARAZHAN_BIG_BAD_WOLF_RUN_POSITION[4] = -{ - { -10875.456f, -1779.036f, 90.477f }, - { -10872.281f, -1751.638f, 90.477f }, - { -10910.492f, -1747.401f, 90.477f }, - { -10913.391f, -1773.508f, 90.477f }, -}; - -const Position KARAZHAN_THE_CURATOR_BOSS_POSITION = Position(-11139.463f, -1884.645f, 165.765f); - -void RaidKarazhanHelpers::MarkTargetWithSkull(Unit* target) -{ - if (!target) + const Position MAIDEN_OF_VIRTUE_BOSS_POSITION = { -10945.881f, -2103.782f, 92.712f }; + const Position MAIDEN_OF_VIRTUE_RANGED_POSITION[8] = { - return; - } + { -10931.178f, -2116.580f, 92.179f }, + { -10925.828f, -2102.425f, 92.180f }, + { -10933.089f, -2088.502f, 92.180f }, + { -10947.590f, -2082.815f, 92.180f }, + { -10960.912f, -2090.437f, 92.179f }, + { -10966.017f, -2105.288f, 92.175f }, + { -10959.242f, -2119.617f, 92.180f }, + { -10944.495f, -2123.857f, 92.180f }, + }; - if (Group* group = bot->GetGroup()) + const Position BIG_BAD_WOLF_BOSS_POSITION = { -10913.391f, -1773.508f, 90.477f }; + const Position BIG_BAD_WOLF_RUN_POSITION[4] = { - constexpr uint8_t skullIconId = 7; - ObjectGuid skullGuid = group->GetTargetIcon(skullIconId); + { -10875.456f, -1779.036f, 90.477f }, + { -10872.281f, -1751.638f, 90.477f }, + { -10910.492f, -1747.401f, 90.477f }, + { -10913.391f, -1773.508f, 90.477f }, + }; - if (skullGuid != target->GetGUID()) + const Position THE_CURATOR_BOSS_POSITION = { -11139.463f, -1884.645f, 165.765f }; + + const Position NIGHTBANE_TRANSITION_BOSS_POSITION = { -11160.646f, -1932.773f, 91.473f }; // near some ribs + const Position NIGHTBANE_FINAL_BOSS_POSITION = { -11173.530f, -1940.707f, 91.473f }; + const Position NIGHTBANE_RANGED_POSITION1 = { -11145.949f, -1970.927f, 91.473f }; + const Position NIGHTBANE_RANGED_POSITION2 = { -11143.594f, -1954.981f, 91.473f }; + const Position NIGHTBANE_RANGED_POSITION3 = { -11159.778f, -1961.031f, 91.473f }; + const Position NIGHTBANE_FLIGHT_STACK_POSITION = { -11159.555f, -1893.526f, 91.473f }; // Broken Barrel + const Position NIGHTBANE_RAIN_OF_BONES_POSITION = { -11165.233f, -1911.123f, 91.473f }; + + void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId) + { + if (!target) + return; + + if (Group* group = bot->GetGroup()) { - group->SetTargetIcon(skullIconId, bot->GetGUID(), target->GetGUID()); - } - } -} - -Unit* RaidKarazhanHelpers::GetFirstAliveUnit(const std::vector& units) -{ - for (Unit* unit : units) - { - if (unit && unit->IsAlive()) - { - return unit; + ObjectGuid currentGuid = group->GetTargetIcon(iconId); + if (currentGuid != target->GetGUID()) + group->SetTargetIcon(iconId, bot->GetGUID(), target->GetGUID()); } } - return nullptr; -} - -Unit* RaidKarazhanHelpers::GetFirstAliveUnitByEntry(uint32 entry) -{ - const GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); - - for (auto const& npcGuid : npcs) + void MarkTargetWithSkull(Player* bot, Unit* target) { - Unit* unit = botAI->GetUnit(npcGuid); + MarkTargetWithIcon(bot, target, RtiTargetValue::skullIndex); + } - if (unit && unit->IsAlive() && unit->GetEntry() == entry) + void MarkTargetWithSquare(Player* bot, Unit* target) + { + MarkTargetWithIcon(bot, target, RtiTargetValue::squareIndex); + } + + void MarkTargetWithStar(Player* bot, Unit* target) + { + MarkTargetWithIcon(bot, target, RtiTargetValue::starIndex); + } + + void MarkTargetWithCircle(Player* bot, Unit* target) + { + MarkTargetWithIcon(bot, target, RtiTargetValue::circleIndex); + } + + void MarkTargetWithMoon(Player* bot, Unit* target) + { + MarkTargetWithIcon(bot, target, RtiTargetValue::moonIndex); + } + + void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target) + { + if (!target) + return; + + std::string currentRti = botAI->GetAiObjectContext()->GetValue("rti")->Get(); + Unit* currentTarget = botAI->GetAiObjectContext()->GetValue("rti target")->Get(); + + if (currentRti != rtiName || currentTarget != target) { - return unit; + botAI->GetAiObjectContext()->GetValue("rti")->Set(rtiName); + botAI->GetAiObjectContext()->GetValue("rti target")->Set(target); } } - return nullptr; -} - -Unit* RaidKarazhanHelpers::GetNearestPlayerInRadius(float radius) -{ - if (Group* group = bot->GetGroup()) + // Only one bot is needed to set/reset mapwide timers + bool IsMapIDTimerManager(PlayerbotAI* botAI, Player* bot) { - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + if (Group* group = bot->GetGroup()) { - Player* member = itr->GetSource(); - - if (!member || !member->IsAlive() || member == bot) + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { - continue; - } - - if (bot->GetExactDist2d(member) < radius) - { - return member; + Player* member = ref->GetSource(); + if (member && member->IsAlive() && botAI->IsDps(member) && GET_PLAYERBOT_AI(member)) + return member == bot; } } + + return false; } - return nullptr; -} - -bool RaidKarazhanHelpers::IsFlameWreathActive() -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran"); - Spell* currentSpell = boss ? boss->GetCurrentSpell(CURRENT_GENERIC_SPELL) : nullptr; - if (currentSpell && currentSpell->m_spellInfo && currentSpell->m_spellInfo->Id == SPELL_FLAME_WREATH) + Unit* GetFirstAliveUnit(const std::vector& units) { - return true; - } - - if (Group* group = bot->GetGroup()) - { - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + for (Unit* unit : units) { - Player* member = itr->GetSource(); - if (!member || !member->IsAlive()) + if (unit && unit->IsAlive()) + return unit; + } + + return nullptr; + } + + Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry) + { + const GuidVector npcs = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); + for (auto const& npcGuid : npcs) + { + Unit* unit = botAI->GetUnit(npcGuid); + if (unit && unit->IsAlive() && unit->GetEntry() == entry) + return unit; + } + + return nullptr; + } + + Unit* GetNearestPlayerInRadius(Player* bot, float radius) + { + Unit* nearestPlayer = nullptr; + float nearestDistance = radius; + + if (Group* group = bot->GetGroup()) + { + for (GroupReference* ref = group->GetFirstMember(); ref != nullptr; ref = ref->next()) { - continue; - } - if (member->HasAura(SPELL_AURA_FLAME_WREATH)) - { - return true; + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || member == bot) + continue; + + float distance = bot->GetExactDist2d(member); + if (distance < nearestDistance) + { + nearestDistance = distance; + nearestPlayer = member; + } } } + + return nearestPlayer; } - return false; -} - -// Red beam blockers: tank bots, no Nether Exhaustion Red -std::vector RaidKarazhanHelpers::GetRedBlockers() -{ - std::vector redBlockers; - if (Group* group = bot->GetGroup()) + bool IsFlameWreathActive(PlayerbotAI* botAI, Player* bot) { - 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; - } - redBlockers.push_back(member); - } - } + Unit* aran = botAI->GetAiObjectContext()->GetValue("find target", "shade of aran")->Get(); + Spell* currentSpell = aran ? aran->GetCurrentSpell(CURRENT_GENERIC_SPELL) : nullptr; - return redBlockers; -} + if (currentSpell && currentSpell->m_spellInfo && + currentSpell->m_spellInfo->Id == SPELL_FLAME_WREATH_CAST) + return true; -// Blue beam blockers: non-Rogue/Warrior DPS bots, no Nether Exhaustion Blue and ≤25 stacks of Blue Beam debuff -std::vector RaidKarazhanHelpers::GetBlueBlockers() -{ - std::vector blueBlockers; - if (Group* group = bot->GetGroup()) - { - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + if (Group* group = bot->GetGroup()) { - Player* member = itr->GetSource(); - if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member)) + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { - continue; - } - bool isDps = botAI->IsDps(member); - bool isWarrior = member->getClass() == CLASS_WARRIOR; - bool isRogue = member->getClass() == CLASS_ROGUE; - bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_BLUE); - Aura* blueBuff = member->GetAura(SPELL_BLUE_BEAM_DEBUFF); - bool overStack = blueBuff && blueBuff->GetStackAmount() >= 26; - if (isDps && !isWarrior && !isRogue && !hasExhaustion && !overStack) - { - blueBlockers.push_back(member); + Player* member = ref->GetSource(); + if (!member || !member->IsAlive()) + continue; + + if (member->HasAura(SPELL_FLAME_WREATH_AURA)) + return true; } } + + return false; } - return blueBlockers; -} - -// Green beam blockers: -// (1) Rogue and non-tank Warrior bots, no Nether Exhaustion Green -// (2) Healer bots, no Nether Exhaustion Green and ≤25 stacks of Green Beam debuff -std::vector RaidKarazhanHelpers::GetGreenBlockers() -{ - std::vector greenBlockers; - if (Group* group = bot->GetGroup()) + // Red beam blockers: tank bots, no Nether Exhaustion Red + std::vector GetRedBlockers(PlayerbotAI* botAI, Player* bot) { - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + std::vector redBlockers; + if (Group* group = bot->GetGroup()) { - Player* member = itr->GetSource(); - if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member)) + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { - continue; - } - bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_GREEN); - Aura* greenBuff = member->GetAura(SPELL_GREEN_BEAM_DEBUFF); - bool overStack = greenBuff && greenBuff->GetStackAmount() >= 26; - bool isRogue = member->getClass() == CLASS_ROGUE; - bool isDpsWarrior = member->getClass() == CLASS_WARRIOR && botAI->IsDps(member); - bool eligibleRogueWarrior = (isRogue || isDpsWarrior) && !hasExhaustion; - bool isHealer = botAI->IsHeal(member); - bool eligibleHealer = isHealer && !hasExhaustion && !overStack; - if (eligibleRogueWarrior || eligibleHealer) - { - greenBlockers.push_back(member); + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || !botAI->IsTank(member) || !GET_PLAYERBOT_AI(member) || + member->HasAura(SPELL_NETHER_EXHAUSTION_RED)) + continue; + + redBlockers.push_back(member); } } + + return redBlockers; } - return greenBlockers; -} - -Position RaidKarazhanHelpers::GetPositionOnBeam(Unit* boss, Unit* portal, float distanceFromBoss) -{ - float bx = boss->GetPositionX(); - float by = boss->GetPositionY(); - float bz = boss->GetPositionZ(); - float px = portal->GetPositionX(); - float py = portal->GetPositionY(); - - float dx = px - bx; - float dy = py - by; - float length = sqrt(dx*dx + dy*dy); - if (length == 0.0f) + // Blue beam blockers: non-Rogue/Warrior DPS bots, no Nether Exhaustion Blue and <24 stacks of Blue Beam debuff + std::vector GetBlueBlockers(PlayerbotAI* botAI, Player* bot) { - return Position(bx, by, bz); + std::vector blueBlockers; + if (Group* group = bot->GetGroup()) + { + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member)) + continue; + + bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_BLUE); + Aura* blueBuff = member->GetAura(SPELL_BLUE_BEAM_DEBUFF); + bool overStack = blueBuff && blueBuff->GetStackAmount() >= 24; + + bool isDps = botAI->IsDps(member); + bool isWarrior = member->getClass() == CLASS_WARRIOR; + bool isRogue = member->getClass() == CLASS_ROGUE; + + if (isDps && !isWarrior && !isRogue && !hasExhaustion && !overStack) + blueBlockers.push_back(member); + } + } + + return blueBlockers; } - dx /= length; - dy /= length; - float targetX = bx + dx * distanceFromBoss; - float targetY = by + dy * distanceFromBoss; - float targetZ = bz; - - return Position(targetX, targetY, targetZ); -} - -std::tuple RaidKarazhanHelpers::GetCurrentBeamBlockers() -{ - static ObjectGuid currentRedBlocker; - static ObjectGuid currentGreenBlocker; - static ObjectGuid currentBlueBlocker; - - Player* redBlocker = nullptr; - Player* greenBlocker = nullptr; - Player* blueBlocker = nullptr; - - std::vector redBlockers = GetRedBlockers(); - if (!redBlockers.empty()) + // Green beam blockers: + // (1) Prioritize Rogues and non-tank Warrior bots, no Nether Exhaustion Green + // (2) Then assign Healer bots, no Nether Exhaustion Green and <24 stacks of Green Beam debuff + std::vector GetGreenBlockers(PlayerbotAI* botAI, Player* bot) { - auto it = std::find_if(redBlockers.begin(), redBlockers.end(), [](Player* p) + std::vector greenBlockers; + if (Group* group = bot->GetGroup()) { - return p && p->GetGUID() == currentRedBlocker; - }); - if (it != redBlockers.end()) + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member)) + continue; + + bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_GREEN); + bool isRogue = member->getClass() == CLASS_ROGUE; + bool isDpsWarrior = member->getClass() == CLASS_WARRIOR && botAI->IsDps(member); + bool eligibleRogueWarrior = (isRogue || isDpsWarrior) && !hasExhaustion; + + if (eligibleRogueWarrior) + greenBlockers.push_back(member); + } + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member)) + continue; + + bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_GREEN); + Aura* greenBuff = member->GetAura(SPELL_GREEN_BEAM_DEBUFF); + bool overStack = greenBuff && greenBuff->GetStackAmount() >= 24; + bool isHealer = botAI->IsHeal(member); + bool eligibleHealer = isHealer && !hasExhaustion && !overStack; + + if (eligibleHealer) + greenBlockers.push_back(member); + } + } + + return greenBlockers; + } + + std::tuple GetCurrentBeamBlockers(PlayerbotAI* botAI, Player* bot) + { + static ObjectGuid currentRedBlocker; + static ObjectGuid currentGreenBlocker; + static ObjectGuid currentBlueBlocker; + + Player* redBlocker = nullptr; + Player* greenBlocker = nullptr; + Player* blueBlocker = nullptr; + + std::vector redBlockers = GetRedBlockers(botAI, bot); + if (!redBlockers.empty()) { - redBlocker = *it; + auto it = std::find_if(redBlockers.begin(), redBlockers.end(), [](Player* player) + { + return player && player->GetGUID() == currentRedBlocker; + }); + + if (it != redBlockers.end()) + redBlocker = *it; + else + redBlocker = redBlockers.front(); + + currentRedBlocker = redBlocker ? redBlocker->GetGUID() : ObjectGuid::Empty; } else { - redBlocker = redBlockers.front(); + currentRedBlocker = ObjectGuid::Empty; + redBlocker = nullptr; } - currentRedBlocker = redBlocker ? redBlocker->GetGUID() : ObjectGuid::Empty; - } - else - { - currentRedBlocker = ObjectGuid::Empty; - redBlocker = nullptr; - } - std::vector greenBlockers = GetGreenBlockers(); - if (!greenBlockers.empty()) - { - auto it = std::find_if(greenBlockers.begin(), greenBlockers.end(), [](Player* p) + std::vector greenBlockers = GetGreenBlockers(botAI, bot); + if (!greenBlockers.empty()) { - return p && p->GetGUID() == currentGreenBlocker; - }); - if (it != greenBlockers.end()) - { - greenBlocker = *it; + auto it = std::find_if(greenBlockers.begin(), greenBlockers.end(), [](Player* player) + { + return player && player->GetGUID() == currentGreenBlocker; + }); + + if (it != greenBlockers.end()) + greenBlocker = *it; + else + greenBlocker = greenBlockers.front(); + + currentGreenBlocker = greenBlocker ? greenBlocker->GetGUID() : ObjectGuid::Empty; } else { - greenBlocker = greenBlockers.front(); + currentGreenBlocker = ObjectGuid::Empty; + greenBlocker = nullptr; } - currentGreenBlocker = greenBlocker ? greenBlocker->GetGUID() : ObjectGuid::Empty; - } - else - { - currentGreenBlocker = ObjectGuid::Empty; - greenBlocker = nullptr; - } - std::vector blueBlockers = GetBlueBlockers(); + std::vector blueBlockers = GetBlueBlockers(botAI, bot); if (!blueBlockers.empty()) { - auto it = std::find_if(blueBlockers.begin(), blueBlockers.end(), [](Player* p) + auto it = std::find_if(blueBlockers.begin(), blueBlockers.end(), [](Player* player) { - return p && p->GetGUID() == currentBlueBlocker; + return player && player->GetGUID() == currentBlueBlocker; }); + if (it != blueBlockers.end()) - { blueBlocker = *it; - } else - { blueBlocker = blueBlockers.front(); - } + currentBlueBlocker = blueBlocker ? blueBlocker->GetGUID() : ObjectGuid::Empty; } else @@ -319,91 +359,132 @@ std::tuple RaidKarazhanHelpers::GetCurrentBeamBlocker blueBlocker = nullptr; } - return std::make_tuple(redBlocker, greenBlocker, blueBlocker); -} - -std::vector RaidKarazhanHelpers::GetAllVoidZones() -{ - std::vector voidZones; - const float radius = 30.0f; - const GuidVector npcs = botAI->GetAiObjectContext()->GetValue("nearest npcs")->Get(); - for (auto const& npcGuid : npcs) - { - Unit* unit = botAI->GetUnit(npcGuid); - if (!unit || unit->GetEntry() != NPC_VOID_ZONE) - { - continue; - } - float dist = bot->GetExactDist2d(unit); - if (dist < radius) - { - voidZones.push_back(unit); - } + return std::make_tuple(redBlocker, greenBlocker, blueBlocker); } - return voidZones; -} - -bool RaidKarazhanHelpers::IsSafePosition(float x, float y, float z, - const std::vector& hazards, float hazardRadius) -{ - for (Unit* hazard : hazards) + std::vector GetAllVoidZones(PlayerbotAI* botAI, Player* bot) { - float dist = std::sqrt(std::pow(x - hazard->GetPositionX(), 2) + std::pow(y - hazard->GetPositionY(), 2)); - if (dist < hazardRadius) + std::vector voidZones; + const float radius = 30.0f; + const GuidVector npcs = botAI->GetAiObjectContext()->GetValue("nearest npcs")->Get(); + for (auto const& npcGuid : npcs) { - return false; + Unit* unit = botAI->GetUnit(npcGuid); + if (!unit || unit->GetEntry() != NPC_VOID_ZONE) + continue; + + float dist = bot->GetExactDist2d(unit); + if (dist < radius) + voidZones.push_back(unit); } + + return voidZones; } - return true; -} - -std::vector RaidKarazhanHelpers::GetSpawnedInfernals() const -{ - std::vector infernals; - const GuidVector npcs = botAI->GetAiObjectContext()->GetValue("nearest npcs")->Get(); - for (auto const& npcGuid : npcs) + bool IsSafePosition(float x, float y, float z, const std::vector& hazards, float hazardRadius) { - Unit* unit = botAI->GetUnit(npcGuid); - if (unit && unit->GetEntry() == NPC_NETHERSPITE_INFERNAL) + for (Unit* hazard : hazards) { - infernals.push_back(unit); + float dist = hazard->GetExactDist2d(x, y); + if (dist < hazardRadius) + return false; } - } - return infernals; -} - -bool RaidKarazhanHelpers::IsStraightPathSafe(const Position& start, const Position& target, const std::vector& hazards, float hazardRadius, float stepSize) -{ - float sx = start.GetPositionX(); - float sy = start.GetPositionY(); - float sz = start.GetPositionZ(); - float tx = target.GetPositionX(); - float ty = target.GetPositionY(); - float tz = target.GetPositionZ(); - float totalDist = std::sqrt(std::pow(tx - sx, 2) + std::pow(ty - sy, 2)); - if (totalDist == 0.0f) - { return true; } - for (float checkDist = 0.0f; checkDist <= totalDist; checkDist += stepSize) + std::vector GetSpawnedInfernals(PlayerbotAI* botAI) { - float t = checkDist / totalDist; - float checkX = sx + (tx - sx) * t; - float checkY = sy + (ty - sy) * t; - float checkZ = sz + (tz - sz) * t; - for (Unit* hazard : hazards) + std::vector infernals; + const GuidVector npcs = botAI->GetAiObjectContext()->GetValue("nearest npcs")->Get(); + for (auto const& npcGuid : npcs) { - float hazardDist = std::sqrt(std::pow(checkX - hazard->GetPositionX(), 2) + std::pow(checkY - hazard->GetPositionY(), 2)); - if (hazardDist < hazardRadius) - { - return false; - } + Unit* unit = botAI->GetUnit(npcGuid); + if (unit && unit->GetEntry() == NPC_NETHERSPITE_INFERNAL) + infernals.push_back(unit); } + + return infernals; } - return true; + bool IsStraightPathSafe(const Position& start, const Position& target, const std::vector& hazards, + float hazardRadius, float stepSize) + { + float sx = start.GetPositionX(); + float sy = start.GetPositionY(); + float sz = start.GetPositionZ(); + float tx = target.GetPositionX(); + float ty = target.GetPositionY(); + float tz = target.GetPositionZ(); + + const float totalDist = start.GetExactDist2d(target.GetPositionX(), target.GetPositionY()); + if (totalDist == 0.0f) + return true; + + for (float checkDist = 0.0f; checkDist <= totalDist; checkDist += stepSize) + { + float t = checkDist / totalDist; + float checkX = sx + (tx - sx) * t; + float checkY = sy + (ty - sy) * t; + float checkZ = sz + (tz - sz) * t; + for (Unit* hazard : hazards) + { + const float hx = checkX - hazard->GetPositionX(); + const float hy = checkY - hazard->GetPositionY(); + if ((hx*hx + hy*hy) < hazardRadius * hazardRadius) + return false; + } + } + + return true; + } + + bool TryFindSafePositionWithSafePath( + Player* bot, float originX, float originY, float originZ, float centerX, float centerY, float centerZ, + const std::vector& hazards, float safeDistance, float stepSize, uint8 numAngles, + float maxSampleDist, bool requireSafePath, float& bestDestX, float& bestDestY, float& bestDestZ) + { + float bestMoveDist = std::numeric_limits::max(); + bool found = false; + + for (int i = 0; i < numAngles; ++i) + { + float angle = (2.0f * M_PI * i) / numAngles; + float dx = cos(angle); + float dy = sin(angle); + + for (float dist = stepSize; dist <= maxSampleDist; dist += stepSize) + { + float x = centerX + dx * dist; + float y = centerY + dy * dist; + float z = centerZ; + float destX = x, destY = y, destZ = z; + if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, centerX, centerY, centerZ, + destX, destY, destZ, true)) + continue; + + if (!IsSafePosition(destX, destY, destZ, hazards, safeDistance)) + continue; + + if (requireSafePath) + { + if (!IsStraightPathSafe(Position(originX, originY, originZ), Position(destX, destY, destZ), + hazards, safeDistance, stepSize)) + continue; + } + + const float moveDist = Position(originX, originY, originZ).GetExactDist2d(destX, destY); + if (moveDist < bestMoveDist) + { + bestMoveDist = moveDist; + bestDestX = destX; + bestDestY = destY; + bestDestZ = destZ; + found = true; + } + } + } + + return found; + } } diff --git a/src/strategy/raids/karazhan/RaidKarazhanHelpers.h b/src/strategy/raids/karazhan/RaidKarazhanHelpers.h index d26d8501..fb08333a 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanHelpers.h +++ b/src/strategy/raids/karazhan/RaidKarazhanHelpers.h @@ -1,85 +1,136 @@ #ifndef _PLAYERBOT_RAIDKARAZHANHELPERS_H_ #define _PLAYERBOT_RAIDKARAZHANHELPERS_H_ +#include +#include + #include "AiObject.h" -#include "Playerbots.h" #include "Position.h" +#include "Unit.h" -enum KarazhanSpells +namespace KarazhanHelpers { - // Maiden of Virtue - SPELL_REPENTANCE = 29511, + enum KarazhanSpells + { + // Maiden of Virtue + SPELL_REPENTANCE = 29511, - // Opera Event - SPELL_LITTLE_RED_RIDING_HOOD = 30756, + // Opera Event + SPELL_LITTLE_RED_RIDING_HOOD = 30756, - // Shade of Aran - SPELL_FLAME_WREATH = 30004, - SPELL_AURA_FLAME_WREATH = 29946, - SPELL_ARCANE_EXPLOSION = 29973, - SPELL_WARLOCK_BANISH = 18647, // Rank 2 + // The Curator + SPELL_CURATOR_EVOCATION = 30254, - // Netherspite - SPELL_GREEN_BEAM_DEBUFF = 30422, - SPELL_BLUE_BEAM_DEBUFF = 30423, - SPELL_NETHER_EXHAUSTION_RED = 38637, - SPELL_NETHER_EXHAUSTION_GREEN = 38638, - SPELL_NETHER_EXHAUSTION_BLUE = 38639, - SPELL_NETHERSPITE_BANISHED = 39833, + // Shade of Aran + SPELL_FLAME_WREATH_CAST = 30004, + SPELL_FLAME_WREATH_AURA = 29946, + SPELL_ARCANE_EXPLOSION = 29973, - // Prince Malchezaar - SPELL_ENFEEBLE = 30843, -}; + // Netherspite + SPELL_RED_BEAM_DEBUFF = 30421, // "Nether Portal - Perseverance" (player aura) + SPELL_GREEN_BEAM_DEBUFF = 30422, // "Nether Portal - Serenity" (player aura) + SPELL_BLUE_BEAM_DEBUFF = 30423, // "Nether Portal - Dominance" (player aura) + SPELL_GREEN_BEAM_HEAL = 30467, // "Nether Portal - Serenity" (Netherspite aura) + SPELL_NETHER_EXHAUSTION_RED = 38637, + SPELL_NETHER_EXHAUSTION_GREEN = 38638, + SPELL_NETHER_EXHAUSTION_BLUE = 38639, + SPELL_NETHERSPITE_BANISHED = 39833, // "Vortex Shade Black" + + // Prince Malchezaar + SPELL_ENFEEBLE = 30843, + + // Nightbane + SPELL_CHARRED_EARTH = 30129, + SPELL_BELLOWING_ROAR = 36922, + SPELL_RAIN_OF_BONES = 37091, + + // Warlock + SPELL_WARLOCK_BANISH = 18647, + + // Priest + SPELL_FEAR_WARD = 6346, + }; + + enum KarazhanNPCs + { + // Trash + NPC_SPECTRAL_RETAINER = 16410, + NPC_MANA_WARP = 16530, + + // Attumen the Huntsman + NPC_ATTUMEN_THE_HUNTSMAN = 15550, + NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED = 16152, + + // Shade of Aran + NPC_CONJURED_ELEMENTAL = 17167, + + // Netherspite + NPC_VOID_ZONE = 16697, + NPC_GREEN_PORTAL = 17367, // "Nether Portal - Serenity " + NPC_BLUE_PORTAL = 17368, // "Nether Portal - Dominance " + NPC_RED_PORTAL = 17369, // "Nether Portal - Perseverance " + + // Prince Malchezaar + NPC_NETHERSPITE_INFERNAL = 17646, + }; + + const uint32 KARAZHAN_MAP_ID = 532; + const float NIGHTBANE_FLIGHT_Z = 95.0f; -enum KarazhanNpcs -{ // Attumen the Huntsman - NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED = 16152, - - // Terestian Illhoof - NPC_KILREK = 17229, - NPC_DEMON_CHAINS = 17248, - - // Shade of Aran - NPC_CONJURED_ELEMENTAL = 17167, - + extern std::unordered_map attumenDpsWaitTimer; + // Big Bad Wolf + extern std::unordered_map bigBadWolfRunIndex; // Netherspite - NPC_VOID_ZONE = 16697, - NPC_RED_PORTAL = 17369, - NPC_BLUE_PORTAL = 17368, - NPC_GREEN_PORTAL = 17367, + extern std::unordered_map netherspiteDpsWaitTimer; + extern std::unordered_map redBeamMoveTimer; + extern std::unordered_map lastBeamMoveSideways; + // Nightbane + extern std::unordered_map nightbaneDpsWaitTimer; + extern std::unordered_map nightbaneTankStep; + extern std::unordered_map nightbaneRangedStep; + extern std::unordered_map nightbaneFlightPhaseStartTimer; + extern std::unordered_map nightbaneRainOfBonesHit; - // Prince Malchezaar - NPC_NETHERSPITE_INFERNAL = 17646, -}; + extern const Position MAIDEN_OF_VIRTUE_BOSS_POSITION; + extern const Position MAIDEN_OF_VIRTUE_RANGED_POSITION[8]; + extern const Position BIG_BAD_WOLF_BOSS_POSITION; + extern const Position BIG_BAD_WOLF_RUN_POSITION[4]; + extern const Position THE_CURATOR_BOSS_POSITION; + extern const Position NIGHTBANE_TRANSITION_BOSS_POSITION; + extern const Position NIGHTBANE_FINAL_BOSS_POSITION; + extern const Position NIGHTBANE_RANGED_POSITION1; + extern const Position NIGHTBANE_RANGED_POSITION2; + extern const Position NIGHTBANE_RANGED_POSITION3; + extern const Position NIGHTBANE_FLIGHT_STACK_POSITION; + extern const Position NIGHTBANE_RAIN_OF_BONES_POSITION; -extern const Position KARAZHAN_MAIDEN_OF_VIRTUE_BOSS_POSITION; -extern const Position KARAZHAN_MAIDEN_OF_VIRTUE_RANGED_POSITION[8]; -extern const Position KARAZHAN_BIG_BAD_WOLF_BOSS_POSITION; -extern const Position KARAZHAN_BIG_BAD_WOLF_RUN_POSITION[4]; -extern const Position KARAZHAN_THE_CURATOR_BOSS_POSITION; - -class RaidKarazhanHelpers : public AiObject -{ -public: - explicit RaidKarazhanHelpers(PlayerbotAI* botAI) : AiObject(botAI) {} - - void MarkTargetWithSkull(Unit* /*target*/); - Unit* GetFirstAliveUnit(const std::vector& /*units*/); - Unit* GetFirstAliveUnitByEntry(uint32 /*entry*/); - Unit* GetNearestPlayerInRadius(float /*radius*/ = 5.0f); - bool IsFlameWreathActive(); - Position GetPositionOnBeam(Unit* boss, Unit* portal, float distanceFromBoss); - std::vector GetRedBlockers(); - std::vector GetBlueBlockers(); - std::vector GetGreenBlockers(); - std::tuple GetCurrentBeamBlockers(); - std::vector GetAllVoidZones(); - bool IsSafePosition (float x, float y, float z, - const std::vector& hazards, float hazardRadius); - std::vector GetSpawnedInfernals() const; - bool IsStraightPathSafe(const Position& start, const Position& target, - const std::vector& hazards, float hazardRadius, float stepSize); -}; + void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId); + void MarkTargetWithSkull(Player* bot, Unit* target); + void MarkTargetWithSquare(Player* bot, Unit* target); + void MarkTargetWithStar(Player* bot, Unit* target); + void MarkTargetWithCircle(Player* bot, Unit* target); + void MarkTargetWithMoon(Player* bot, Unit* target); + void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target); + bool IsMapIDTimerManager(PlayerbotAI* botAI, Player* bot); + Unit* GetFirstAliveUnit(const std::vector& units); + Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry); + Unit* GetNearestPlayerInRadius(Player* bot, float radius); + bool IsFlameWreathActive(PlayerbotAI* botAI, Player* bot); + std::vector GetRedBlockers(PlayerbotAI* botAI, Player* bot); + std::vector GetBlueBlockers(PlayerbotAI* botAI, Player* bot); + std::vector GetGreenBlockers(PlayerbotAI* botAI, Player* bot); + std::tuple GetCurrentBeamBlockers(PlayerbotAI* botAI, Player* bot); + std::vector GetAllVoidZones(PlayerbotAI *botAI, Player* bot); + bool IsSafePosition (float x, float y, float z, const std::vector& hazards, float hazardRadius); + std::vector GetSpawnedInfernals(PlayerbotAI* botAI); + bool IsStraightPathSafe( + const Position& start, const Position& target, + const std::vector& hazards, float hazardRadius, float stepSize); + bool TryFindSafePositionWithSafePath( + Player* bot, float originX, float originY, float originZ, float centerX, float centerY, float centerZ, + const std::vector& hazards, float safeDistance, float stepSize, uint8 numAngles, + float maxSampleDist, bool requireSafePath, float& bestDestX, float& bestDestY, float& bestDestZ); +} #endif diff --git a/src/strategy/raids/karazhan/RaidKarazhanMultipliers.cpp b/src/strategy/raids/karazhan/RaidKarazhanMultipliers.cpp index f68cbe8b..cd64ea98 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanMultipliers.cpp +++ b/src/strategy/raids/karazhan/RaidKarazhanMultipliers.cpp @@ -1,265 +1,359 @@ #include "RaidKarazhanMultipliers.h" #include "RaidKarazhanActions.h" #include "RaidKarazhanHelpers.h" -#include "AiObjectContext.h" #include "AttackAction.h" -#include "DruidBearActions.h" -#include "DruidCatActions.h" +#include "ChooseTargetActions.h" +#include "DruidActions.h" +#include "FollowActions.h" +#include "GenericActions.h" +#include "HunterActions.h" +#include "MageActions.h" +#include "Playerbots.h" +#include "PriestActions.h" +#include "ReachTargetActions.h" #include "RogueActions.h" -#include "WarriorActions.h" +#include "ShamanActions.h" -static bool IsChargeAction(Action* action) -{ - return dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action); -} +using namespace KarazhanHelpers; -float KarazhanAttumenTheHuntsmanMultiplier::GetValue(Action* action) +// Keep tanks from jumping back and forth between Attumen and Midnight +float AttumenTheHuntsmanDisableTankAssistMultiplier::GetValue(Action* action) { - RaidKarazhanHelpers karazhanHelper(botAI); - Unit* boss = karazhanHelper.GetFirstAliveUnitByEntry(NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED); - if (boss && !(botAI->IsTank(bot) && botAI->HasAggro(boss) && boss->GetVictim() == bot) && - (dynamic_cast(action) && - !dynamic_cast(action))) - { + Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight"); + if (!midnight) + return 1.0f; + + Unit* attumen = AI_VALUE2(Unit*, "find target", "attumen the huntsman"); + if (!attumen) + return 1.0f; + + if (bot->GetVictim() != nullptr && dynamic_cast(action)) return 0.0f; + + return 1.0f; +} + +// Try to get rid of jittering when bots are stacked behind Attumen +float AttumenTheHuntsmanStayStackedMultiplier::GetValue(Action* action) +{ + Unit* attumenMounted = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED); + if (!attumenMounted) + return 1.0f; + + if (!botAI->IsMainTank(bot) && attumenMounted->GetVictim() != bot) + { + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; } return 1.0f; } -float KarazhanBigBadWolfMultiplier::GetValue(Action* action) +// Give the main tank 8 seconds to grab aggro when Attumen mounts Midnight +// In reality it's shorter because it takes Attumen a few seconds to aggro after mounting +float AttumenTheHuntsmanWaitForDpsMultiplier::GetValue(Action* action) { - Unit* boss = AI_VALUE2(Unit*, "find target", "the big bad wolf"); - if (!boss) - { + Unit* attumenMounted = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED); + if (!attumenMounted) return 1.0f; - } - if (bot->HasAura(SPELL_LITTLE_RED_RIDING_HOOD)) + const time_t now = std::time(nullptr); + const uint8 dpsWaitSeconds = 8; + + auto it = attumenDpsWaitTimer.find(KARAZHAN_MAP_ID); + if (it == attumenDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds) { - if ((dynamic_cast(action) && !dynamic_cast(action)) || - (dynamic_cast(action))) + if (!botAI->IsMainTank(bot)) { - return 0.0f; - } - } - - return 1.0f; -} - -float KarazhanShadeOfAranMultiplier::GetValue(Action* action) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran"); - if (!boss) - { - return 1.0f; - } - - if (boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION)) - { - if (IsChargeAction(action)) - { - return 0.0f; - } - - if (dynamic_cast(action)) - { - const float safeDistance = 20.0f; - if (bot->GetDistance2d(boss) >= safeDistance) - { + if (dynamic_cast(action) || (dynamic_cast(action) && + !dynamic_cast(action))) return 0.0f; - } } } - bool flameWreathActive = boss->HasAura(SPELL_FLAME_WREATH); - if (!flameWreathActive && bot->GetGroup()) + return 1.0f; +} + +// The assist tank should stay on the boss to be 2nd on aggro and tank Hateful Bolts +float TheCuratorDisableTankAssistMultiplier::GetValue(Action* action) +{ + Unit* curator = AI_VALUE2(Unit*, "find target", "the curator"); + if (!curator) + return 1.0f; + + if (bot->GetVictim() != nullptr && dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + +// Save Bloodlust/Heroism for Evocation (100% increased damage) +float TheCuratorDelayBloodlustAndHeroismMultiplier::GetValue(Action* action) +{ + Unit* curator = AI_VALUE2(Unit*, "find target", "the curator"); + if (!curator) + return 1.0f; + + if (!curator->HasAura(SPELL_CURATOR_EVOCATION)) { - for (GroupReference* itr = bot->GetGroup()->GetFirstMember(); itr != nullptr; itr = itr->next()) + if (dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + +// Don't charge back in when running from Arcane Explosion +float ShadeOfAranArcaneExplosionDisableChargeMultiplier::GetValue(Action* action) +{ + Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran"); + if (!aran) + return 1.0f; + + if (aran->HasUnitState(UNIT_STATE_CASTING) && + aran->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION)) + { + if (dynamic_cast(action)) + return 0.0f; + + if (bot->GetDistance2d(aran) >= 20.0f) { - Player* member = itr->GetSource(); - if (member && member->HasAura(SPELL_AURA_FLAME_WREATH)) - { - flameWreathActive = true; - break; - } + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; } } - if (flameWreathActive) + + return 1.0f; +} + +// I will not move when Flame Wreath is cast or the raid blows up +float ShadeOfAranFlameWreathDisableMovementMultiplier::GetValue(Action* action) +{ + Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran"); + if (!aran) + return 1.0f; + + if (IsFlameWreathActive(botAI, bot)) { - if (dynamic_cast(action) || IsChargeAction(action)) + 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)) + return 0.0f; + } + + return 1.0f; +} + +// Try to rid of the jittering when blocking beams +float NetherspiteKeepBlockingBeamMultiplier::GetValue(Action* action) +{ + Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite"); + if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED)) + return 1.0f; + + auto [redBlocker, greenBlocker, blueBlocker] = GetCurrentBeamBlockers(botAI, bot); + + if (bot == redBlocker) + { + if (dynamic_cast(action)) + return 0.0f; + } + + if (bot == blueBlocker) + { + if (dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + } + + if (bot == greenBlocker) + { + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + +// Give tanks 5 seconds to get aggro during phase transitions +float NetherspiteWaitForDpsMultiplier::GetValue(Action* action) +{ + Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite"); + if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED)) + return 1.0f; + + const time_t now = std::time(nullptr); + const uint8 dpsWaitSeconds = 5; + + auto it = netherspiteDpsWaitTimer.find(KARAZHAN_MAP_ID); + if (it == netherspiteDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds) + { + if (!botAI->IsTank(bot)) { + if (dynamic_cast(action) || (dynamic_cast(action) && + !dynamic_cast(action))) return 0.0f; } } - return 1.0f; + return 1.0f; } -float KarazhanNetherspiteBlueAndGreenBeamMultiplier::GetValue(Action* action) +// Disable standard "avoid aoe" strategy, which may interfere with scripted avoidance +float PrinceMalchezaarDisableAvoidAoeMultiplier::GetValue(Action* action) { - Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); - if (!boss || !boss->IsAlive()) - { + Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar"); + if (!malchezaar) return 1.0f; - } - - if (dynamic_cast(action) || dynamic_cast(action)) - { - return 0.0f; - } - - RaidKarazhanHelpers karazhanHelper(botAI); - auto [redBlocker /*unused*/, greenBlocker, blueBlocker] = karazhanHelper.GetCurrentBeamBlockers(); - bool isBlocker = (bot == greenBlocker || bot == blueBlocker); - if (isBlocker) - { - Unit* bluePortal = bot->FindNearestCreature(NPC_BLUE_PORTAL, 150.0f); - Unit* greenPortal = bot->FindNearestCreature(NPC_GREEN_PORTAL, 150.0f); - bool inBeam = false; - for (Unit* portal : {bluePortal, greenPortal}) - { - if (!portal) - { - continue; - } - float bx = boss->GetPositionX(), by = boss->GetPositionY(); - float px = portal->GetPositionX(), py = 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 < 0.3f && t > 0.0f && t < length) - { - inBeam = true; - break; - } - } - if (inBeam) - { - std::vector voidZones = karazhanHelper.GetAllVoidZones(); - bool inVoidZone = false; - for (Unit* vz : voidZones) - { - if (bot->GetExactDist2d(vz) < 4.0f) - { - inVoidZone = true; - break; - } - } - if (!inVoidZone) - { - if (dynamic_cast(action) || IsChargeAction(action)) - { - return 0.0f; - } - } - } - } - - return 1.0f; -} - -float KarazhanNetherspiteRedBeamMultiplier::GetValue(Action* action) -{ - Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); - if (!boss || !boss->IsAlive()) - { - return 1.0f; - } if (dynamic_cast(action)) - { return 0.0f; + + return 1.0f; +} + +// Don't run back into Shadow Nova when Enfeebled +float PrinceMalchezaarEnfeebleKeepDistanceMultiplier::GetValue(Action* action) +{ + Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar"); + if (!malchezaar) + return 1.0f; + + if (bot->HasAura(SPELL_ENFEEBLE)) + { + if (dynamic_cast(action) && + !dynamic_cast(action)) + return 0.0f; } - RaidKarazhanHelpers karazhanHelper(botAI); - auto [redBlocker, greenBlocker /*unused*/, blueBlocker /*unused*/] = karazhanHelper.GetCurrentBeamBlockers(); - static std::map beamMoveTimes; - static std::map lastBeamMoveSideways; - ObjectGuid botGuid = bot->GetGUID(); - Unit* redPortal = bot->FindNearestCreature(NPC_RED_PORTAL, 150.0f); - if (bot == redBlocker && boss && redPortal) - { - Position blockingPos = karazhanHelper.GetPositionOnBeam(boss, redPortal, 18.0f); - 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) - { - dx /= length; - dy /= length; - float perpDx = -dy; - float perpDy = dx; - Position sidewaysPos(blockingPos.GetPositionX() + perpDx * 3.0f, - blockingPos.GetPositionY() + perpDy * 3.0f, - blockingPos.GetPositionZ()); + return 1.0f; +} - 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); - } - Position targetPos = lastBeamMoveSideways[botGuid] ? sidewaysPos : blockingPos; - float distToTarget = bot->GetExactDist2d(targetPos.GetPositionX(), targetPos.GetPositionY()); - const float positionTolerance = 1.5f; - if (distToTarget < positionTolerance) - { - if (dynamic_cast(action) || IsChargeAction(action)) - { - return 0.0f; - } - } +// Wait until Phase 3 to use Bloodlust/Heroism +float PrinceMalchezaarDelayBloodlustAndHeroismMultiplier::GetValue(Action* action) +{ + Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar"); + if (!malchezaar) + return 1.0f; + + if (malchezaar->GetHealthPct() > 30.0f) + { + if (dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + +// Pets tend to run out of bounds and cause skeletons to spawn off the map +// Pets also tend to pull adds from inside of the tower through the floor +// This multiplier DOES NOT impact Hunter and Warlock pets +// Hunter and Warlock pets are addressed in ControlPetAggressionAction +float NightbaneDisablePetsMultiplier::GetValue(Action* action) +{ + Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane"); + if (!nightbane) + 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; + + if (nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z) + { + if (dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + +// Give the main tank 8 seconds to get aggro during phase transitions +float NightbaneWaitForDpsMultiplier::GetValue(Action* action) +{ + Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane"); + if (!nightbane || nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z) + return 1.0f; + + const time_t now = std::time(nullptr); + const uint8 dpsWaitSeconds = 8; + + auto it = nightbaneDpsWaitTimer.find(KARAZHAN_MAP_ID); + if (it == nightbaneDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds) + { + if (!botAI->IsMainTank(bot)) + { + if (dynamic_cast(action) || (dynamic_cast(action) && + !dynamic_cast(action))) + return 0.0f; } } return 1.0f; } -float KarazhanPrinceMalchezaarMultiplier::GetValue(Action* action) +// The "avoid aoe" strategy must be disabled for the main tank +// Otherwise, the main tank will spin Nightbane to avoid Charred Earth and wipe the raid +// It is also disabled for all bots during the flight phase +float NightbaneDisableAvoidAoeMultiplier::GetValue(Action* action) { - Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar"); - if (!boss || !boss->IsAlive()) - { + Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane"); + if (!nightbane) return 1.0f; - } - if (dynamic_cast(action)) + if (nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z || botAI->IsMainTank(bot)) { - return 0.0f; - } - - if (botAI->IsMelee(bot) && bot->HasAura(SPELL_ENFEEBLE) && - !dynamic_cast(action)) - { - return 0.0f; - } - - if (botAI->IsRanged(bot) && bot->HasAura(SPELL_ENFEEBLE) && - (dynamic_cast(action) && - !dynamic_cast(action))) - { - return 0.0f; + if (dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + +// Disable some movement actions that conflict with the strategies +float NightbaneDisableMovementMultiplier::GetValue(Action* action) +{ + Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane"); + if (!nightbane) + return 1.0f; + + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + + // Disable CombatFormationMoveAction for all bots except: + // (1) main tank and (2) only during the ground phase, other melee + if (botAI->IsRanged(bot) || + (botAI->IsMelee(bot) && !botAI->IsMainTank(bot) && + nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z)) + { + if (dynamic_cast(action)) + return 0.0f; } return 1.0f; diff --git a/src/strategy/raids/karazhan/RaidKarazhanMultipliers.h b/src/strategy/raids/karazhan/RaidKarazhanMultipliers.h index c8a4ba37..8ad5a1d4 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanMultipliers.h +++ b/src/strategy/raids/karazhan/RaidKarazhanMultipliers.h @@ -3,45 +3,131 @@ #include "Multiplier.h" -class KarazhanAttumenTheHuntsmanMultiplier : public Multiplier +class AttumenTheHuntsmanDisableTankAssistMultiplier : public Multiplier { public: - KarazhanAttumenTheHuntsmanMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan attumen the huntsman multiplier") {} + AttumenTheHuntsmanDisableTankAssistMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "attumen the huntsman disable tank assist multiplier") {} virtual float GetValue(Action* action); }; -class KarazhanBigBadWolfMultiplier : public Multiplier +class AttumenTheHuntsmanStayStackedMultiplier : public Multiplier { public: - KarazhanBigBadWolfMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan big bad wolf multiplier") {} + AttumenTheHuntsmanStayStackedMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "attumen the huntsman stay stacked multiplier") {} virtual float GetValue(Action* action); }; -class KarazhanShadeOfAranMultiplier : public Multiplier +class AttumenTheHuntsmanWaitForDpsMultiplier : public Multiplier { public: - KarazhanShadeOfAranMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan shade of aran multiplier") {} + AttumenTheHuntsmanWaitForDpsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "attumen the huntsman wait for dps multiplier") {} virtual float GetValue(Action* action); }; -class KarazhanNetherspiteBlueAndGreenBeamMultiplier : public Multiplier +class TheCuratorDisableTankAssistMultiplier : public Multiplier { public: - KarazhanNetherspiteBlueAndGreenBeamMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan netherspite blue and green beam multiplier") {} + TheCuratorDisableTankAssistMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "the curator disable tank assist multiplier") {} virtual float GetValue(Action* action); }; -class KarazhanNetherspiteRedBeamMultiplier : public Multiplier +class TheCuratorDelayBloodlustAndHeroismMultiplier : public Multiplier { public: - KarazhanNetherspiteRedBeamMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan netherspite red beam multiplier") {} + TheCuratorDelayBloodlustAndHeroismMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "the curator delay bloodlust and heroism multiplier") {} virtual float GetValue(Action* action); }; -class KarazhanPrinceMalchezaarMultiplier : public Multiplier +class ShadeOfAranArcaneExplosionDisableChargeMultiplier : public Multiplier { public: - KarazhanPrinceMalchezaarMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan prince malchezaar multiplier") {} + ShadeOfAranArcaneExplosionDisableChargeMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "shade of aran arcane explosion disable charge multiplier") {} + virtual float GetValue(Action* action); +}; + +class ShadeOfAranFlameWreathDisableMovementMultiplier : public Multiplier +{ +public: + ShadeOfAranFlameWreathDisableMovementMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "shade of aran flame wreath disable movement multiplier") {} + virtual float GetValue(Action* action); +}; + +class NetherspiteKeepBlockingBeamMultiplier : public Multiplier +{ +public: + NetherspiteKeepBlockingBeamMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "netherspite keep blocking beam multiplier") {} + virtual float GetValue(Action* action); +}; + +class NetherspiteWaitForDpsMultiplier : public Multiplier +{ +public: + NetherspiteWaitForDpsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "netherspite wait for dps multiplier") {} + virtual float GetValue(Action* action); +}; + +class PrinceMalchezaarDisableAvoidAoeMultiplier : public Multiplier +{ +public: + PrinceMalchezaarDisableAvoidAoeMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "prince malchezaar disable avoid aoe multiplier") {} + virtual float GetValue(Action* action); +}; + +class PrinceMalchezaarEnfeebleKeepDistanceMultiplier : public Multiplier +{ +public: + PrinceMalchezaarEnfeebleKeepDistanceMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "prince malchezaar enfeeble keep distance multiplier") {} + virtual float GetValue(Action* action); +}; + +class PrinceMalchezaarDelayBloodlustAndHeroismMultiplier : public Multiplier +{ +public: + PrinceMalchezaarDelayBloodlustAndHeroismMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "prince malchezaar delay bloodlust and heroism multiplier") {} + virtual float GetValue(Action* action); +}; + +class NightbaneDisablePetsMultiplier : public Multiplier +{ +public: + NightbaneDisablePetsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "nightbane disable pets multiplier") {} + virtual float GetValue(Action* action); +}; + +class NightbaneWaitForDpsMultiplier : public Multiplier +{ +public: + NightbaneWaitForDpsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "nightbane wait for dps multiplier") {} + virtual float GetValue(Action* action); +}; + +class NightbaneDisableAvoidAoeMultiplier : public Multiplier +{ +public: + NightbaneDisableAvoidAoeMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "nightbane disable avoid aoe multiplier") {} + virtual float GetValue(Action* action); +}; + +class NightbaneDisableMovementMultiplier : public Multiplier +{ +public: + NightbaneDisableMovementMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "nightbane disable movement multiplier") {} virtual float GetValue(Action* action); }; diff --git a/src/strategy/raids/karazhan/RaidKarazhanStrategy.cpp b/src/strategy/raids/karazhan/RaidKarazhanStrategy.cpp index f93c923c..041651af 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanStrategy.cpp +++ b/src/strategy/raids/karazhan/RaidKarazhanStrategy.cpp @@ -3,79 +3,160 @@ void RaidKarazhanStrategy::InitTriggers(std::vector& triggers) { - triggers.push_back(new TriggerNode( - "karazhan attumen the huntsman", NextAction::array(0, - new NextAction("karazhan attumen the huntsman stack behind", ACTION_RAID + 1), - nullptr))); + // Trash + triggers.push_back(new TriggerNode("mana warp is about to explode", + NextAction::array(0, new NextAction("mana warp stun creature before warp breach", ACTION_EMERGENCY + 6), nullptr) + )); - triggers.push_back(new TriggerNode( - "karazhan moroes", NextAction::array(0, - new NextAction("karazhan moroes mark target", ACTION_RAID + 1), - nullptr))); + // Attumen the Huntsman + triggers.push_back(new TriggerNode("attumen the huntsman need target priority", + NextAction::array(0, new NextAction("attumen the huntsman mark target", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("attumen the huntsman attumen spawned", + NextAction::array(0, new NextAction("attumen the huntsman split bosses", ACTION_RAID + 2), nullptr) + )); + triggers.push_back(new TriggerNode("attumen the huntsman attumen is mounted", + NextAction::array(0, new NextAction("attumen the huntsman stack behind", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("attumen the huntsman boss wipes aggro when mounting", + NextAction::array(0, new NextAction("attumen the huntsman manage dps timer", ACTION_RAID + 2), nullptr) + )); - triggers.push_back(new TriggerNode( - "karazhan maiden of virtue", NextAction::array(0, - new NextAction("karazhan maiden of virtue position ranged", ACTION_RAID + 1), - new NextAction("karazhan maiden of virtue position boss", ACTION_RAID + 1), - nullptr))); + // Moroes + triggers.push_back(new TriggerNode("moroes boss engaged by main tank", + NextAction::array(0, new NextAction("moroes main tank attack boss", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("moroes need target priority", + NextAction::array(0, new NextAction("moroes mark target", ACTION_RAID + 1), nullptr) + )); - triggers.push_back(new TriggerNode( - "karazhan big bad wolf", NextAction::array(0, - new NextAction("karazhan big bad wolf run away", ACTION_EMERGENCY + 6), - new NextAction("karazhan big bad wolf position boss", ACTION_RAID + 1), - nullptr))); + // Maiden of Virtue + triggers.push_back(new TriggerNode("maiden of virtue healers are stunned by repentance", + NextAction::array(0, new NextAction("maiden of virtue move boss to healer", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("maiden of virtue holy wrath deals chain damage", + NextAction::array(0, new NextAction("maiden of virtue position ranged", ACTION_RAID + 1), nullptr) + )); - triggers.push_back(new TriggerNode( - "karazhan romulo and julianne", NextAction::array(0, - new NextAction("karazhan romulo and julianne mark target", ACTION_RAID + 1), - nullptr))); + // The Big Bad Wolf + triggers.push_back(new TriggerNode("big bad wolf boss is chasing little red riding hood", + NextAction::array(0, new NextAction("big bad wolf run away from boss", ACTION_EMERGENCY + 6), nullptr) + )); + triggers.push_back(new TriggerNode("big bad wolf boss engaged by tank", + NextAction::array(0, new NextAction("big bad wolf position boss", ACTION_RAID + 1), nullptr) + )); - triggers.push_back(new TriggerNode( - "karazhan wizard of oz", NextAction::array(0, - new NextAction("karazhan wizard of oz scorch strawman", ACTION_RAID + 2), - new NextAction("karazhan wizard of oz mark target", ACTION_RAID + 1), - nullptr))); + // Romulo and Julianne + triggers.push_back(new TriggerNode("romulo and julianne both bosses revived", + NextAction::array(0, new NextAction("romulo and julianne mark target", ACTION_RAID + 1), nullptr) + )); - triggers.push_back(new TriggerNode( - "karazhan the curator", NextAction::array(0, - new NextAction("karazhan the curator spread ranged", ACTION_RAID + 2), - new NextAction("karazhan the curator position boss", ACTION_RAID + 2), - new NextAction("karazhan the curator mark target", ACTION_RAID + 1), - nullptr))); + // The Wizard of Oz + triggers.push_back(new TriggerNode("wizard of oz need target priority", + NextAction::array(0, new NextAction("wizard of oz mark target", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("wizard of oz strawman is vulnerable to fire", + NextAction::array(0, new NextAction("wizard of oz scorch strawman", ACTION_RAID + 2), nullptr) + )); - triggers.push_back(new TriggerNode( - "karazhan terestian illhoof", NextAction::array(0, - new NextAction("karazhan terestian illhoof mark target", ACTION_RAID + 1), - nullptr))); + // The Curator + triggers.push_back(new TriggerNode("the curator astral flare spawned", + NextAction::array(0, new NextAction("the curator mark astral flare", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("the curator boss engaged by tanks", + NextAction::array(0, new NextAction("the curator position boss", ACTION_RAID + 2), nullptr) + )); + triggers.push_back(new TriggerNode("the curator astral flares cast arcing sear", + NextAction::array(0, new NextAction("the curator spread ranged", ACTION_RAID + 2), nullptr) + )); - triggers.push_back(new TriggerNode( - "karazhan shade of aran", NextAction::array(0, - new NextAction("karazhan shade of aran flame wreath stop movement", ACTION_EMERGENCY + 7), - new NextAction("karazhan shade of aran arcane explosion run away", ACTION_EMERGENCY + 6), - new NextAction("karazhan shade of aran spread ranged", ACTION_RAID + 2), - new NextAction("karazhan shade of aran mark conjured elemental", ACTION_RAID + 1), - nullptr))); + // Terestian Illhoof + triggers.push_back(new TriggerNode("terestian illhoof need target priority", + NextAction::array(0, new NextAction("terestian illhoof mark target", ACTION_RAID + 1), nullptr) + )); - triggers.push_back(new TriggerNode( - "karazhan netherspite", NextAction::array(0, - new NextAction("karazhan netherspite block red beam", ACTION_EMERGENCY + 8), - new NextAction("karazhan netherspite block blue beam", ACTION_EMERGENCY + 8), - new NextAction("karazhan netherspite block green beam", ACTION_EMERGENCY + 8), - new NextAction("karazhan netherspite avoid beam and void zone", ACTION_EMERGENCY + 7), - new NextAction("karazhan netherspite banish phase avoid void zone", ACTION_RAID + 1), - nullptr))); + // Shade of Aran + triggers.push_back(new TriggerNode("shade of aran arcane explosion is casting", + NextAction::array(0, new NextAction("shade of aran run away from arcane explosion", ACTION_EMERGENCY + 6), nullptr) + )); + triggers.push_back(new TriggerNode("shade of aran flame wreath is active", + NextAction::array(0, new NextAction("shade of aran stop moving during flame wreath", ACTION_EMERGENCY + 7), nullptr) + )); + triggers.push_back(new TriggerNode("shade of aran conjured elementals summoned", + NextAction::array(0, new NextAction("shade of aran mark conjured elemental", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("shade of aran boss uses counterspell and blizzard", + NextAction::array(0, new NextAction("shade of aran ranged maintain distance", ACTION_RAID + 2), nullptr) + )); - triggers.push_back(new TriggerNode( - "karazhan prince malchezaar", NextAction::array(0, - new NextAction("karazhan prince malchezaar non tank avoid hazard", ACTION_EMERGENCY + 6), - new NextAction("karazhan prince malchezaar tank avoid hazard", ACTION_EMERGENCY + 6), - nullptr))); + // Netherspite + triggers.push_back(new TriggerNode("netherspite red beam is active", + NextAction::array(0, new NextAction("netherspite block red beam", ACTION_EMERGENCY + 8), nullptr) + )); + triggers.push_back(new TriggerNode("netherspite blue beam is active", + NextAction::array(0, new NextAction("netherspite block blue beam", ACTION_EMERGENCY + 8), nullptr) + )); + triggers.push_back(new TriggerNode("netherspite green beam is active", + NextAction::array(0, new NextAction("netherspite block green beam", ACTION_EMERGENCY + 8), nullptr) + )); + triggers.push_back(new TriggerNode("netherspite bot is not beam blocker", + NextAction::array(0, new NextAction("netherspite avoid beam and void zone", ACTION_EMERGENCY + 7), nullptr) + )); + triggers.push_back(new TriggerNode("netherspite boss is banished", + NextAction::array(0, new NextAction("netherspite banish phase avoid void zone", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("netherspite need to manage timers and trackers", + NextAction::array(0, new NextAction("netherspite manage timers and trackers", ACTION_EMERGENCY + 10), nullptr) + )); + + // Prince Malchezaar + triggers.push_back(new TriggerNode("prince malchezaar bot is enfeebled", + NextAction::array(0, new NextAction("prince malchezaar enfeebled avoid hazard", ACTION_EMERGENCY + 6), nullptr) + )); + triggers.push_back(new TriggerNode("prince malchezaar infernals are spawned", + NextAction::array(0, new NextAction("prince malchezaar non tank avoid infernal", ACTION_EMERGENCY + 1), nullptr) + )); + triggers.push_back(new TriggerNode("prince malchezaar boss engaged by main tank", + NextAction::array(0, new NextAction("prince malchezaar main tank movement", ACTION_EMERGENCY + 6), nullptr) + )); + + // Nightbane + triggers.push_back(new TriggerNode("nightbane boss engaged by main tank", + NextAction::array(0, new NextAction("nightbane ground phase position boss", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("nightbane ranged bots are in charred earth", + NextAction::array(0, new NextAction("nightbane ground phase rotate ranged positions", ACTION_EMERGENCY + 1), nullptr) + )); + triggers.push_back(new TriggerNode("nightbane main tank is susceptible to fear", + NextAction::array(0, new NextAction("nightbane cast fear ward on main tank", ACTION_RAID + 2), nullptr) + )); + triggers.push_back(new TriggerNode("nightbane pets ignore collision to chase flying boss", + NextAction::array(0, new NextAction("nightbane control pet aggression", ACTION_RAID + 2), nullptr) + )); + triggers.push_back(new TriggerNode("nightbane boss is flying", + NextAction::array(0, new NextAction("nightbane flight phase movement", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("nightbane need to manage timers and trackers", + NextAction::array(0, new NextAction("nightbane manage timers and trackers", ACTION_EMERGENCY + 10), nullptr) + )); } void RaidKarazhanStrategy::InitMultipliers(std::vector& multipliers) { - multipliers.push_back(new KarazhanShadeOfAranMultiplier(botAI)); - multipliers.push_back(new KarazhanNetherspiteBlueAndGreenBeamMultiplier(botAI)); - multipliers.push_back(new KarazhanNetherspiteRedBeamMultiplier(botAI)); - multipliers.push_back(new KarazhanPrinceMalchezaarMultiplier(botAI)); + multipliers.push_back(new AttumenTheHuntsmanDisableTankAssistMultiplier(botAI)); + multipliers.push_back(new AttumenTheHuntsmanStayStackedMultiplier(botAI)); + multipliers.push_back(new AttumenTheHuntsmanWaitForDpsMultiplier(botAI)); + multipliers.push_back(new TheCuratorDisableTankAssistMultiplier(botAI)); + multipliers.push_back(new TheCuratorDelayBloodlustAndHeroismMultiplier(botAI)); + multipliers.push_back(new ShadeOfAranArcaneExplosionDisableChargeMultiplier(botAI)); + multipliers.push_back(new ShadeOfAranFlameWreathDisableMovementMultiplier(botAI)); + multipliers.push_back(new NetherspiteKeepBlockingBeamMultiplier(botAI)); + multipliers.push_back(new NetherspiteWaitForDpsMultiplier(botAI)); + multipliers.push_back(new PrinceMalchezaarDisableAvoidAoeMultiplier(botAI)); + multipliers.push_back(new PrinceMalchezaarEnfeebleKeepDistanceMultiplier(botAI)); + multipliers.push_back(new PrinceMalchezaarDelayBloodlustAndHeroismMultiplier(botAI)); + multipliers.push_back(new NightbaneDisablePetsMultiplier(botAI)); + multipliers.push_back(new NightbaneWaitForDpsMultiplier(botAI)); + multipliers.push_back(new NightbaneDisableAvoidAoeMultiplier(botAI)); + multipliers.push_back(new NightbaneDisableMovementMultiplier(botAI)); } diff --git a/src/strategy/raids/karazhan/RaidKarazhanStrategy.h b/src/strategy/raids/karazhan/RaidKarazhanStrategy.h index c03e4285..7d6b16de 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanStrategy.h +++ b/src/strategy/raids/karazhan/RaidKarazhanStrategy.h @@ -7,7 +7,7 @@ class RaidKarazhanStrategy : public Strategy { public: - RaidKarazhanStrategy(PlayerbotAI* ai) : Strategy(ai) {} + RaidKarazhanStrategy(PlayerbotAI* botAI) : Strategy(botAI) {} std::string const getName() override { return "karazhan"; } diff --git a/src/strategy/raids/karazhan/RaidKarazhanTriggerContext.h b/src/strategy/raids/karazhan/RaidKarazhanTriggerContext.h index 0b6a29e8..fa2b7d37 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanTriggerContext.h +++ b/src/strategy/raids/karazhan/RaidKarazhanTriggerContext.h @@ -9,31 +9,255 @@ class RaidKarazhanTriggerContext : public NamedObjectContext public: RaidKarazhanTriggerContext() { - creators["karazhan attumen the huntsman"] = &RaidKarazhanTriggerContext::karazhan_attumen_the_huntsman; - creators["karazhan moroes"] = &RaidKarazhanTriggerContext::karazhan_moroes; - creators["karazhan maiden of virtue"] = &RaidKarazhanTriggerContext::karazhan_maiden_of_virtue; - creators["karazhan big bad wolf"] = &RaidKarazhanTriggerContext::karazhan_big_bad_wolf; - creators["karazhan romulo and julianne"] = &RaidKarazhanTriggerContext::karazhan_romulo_and_julianne; - creators["karazhan wizard of oz"] = &RaidKarazhanTriggerContext::karazhan_wizard_of_oz; - creators["karazhan the curator"] = &RaidKarazhanTriggerContext::karazhan_the_curator; - creators["karazhan terestian illhoof"] = &RaidKarazhanTriggerContext::karazhan_terestian_illhoof; - creators["karazhan shade of aran"] = &RaidKarazhanTriggerContext::karazhan_shade_of_aran; - creators["karazhan netherspite"] = &RaidKarazhanTriggerContext::karazhan_netherspite; - creators["karazhan prince malchezaar"] = &RaidKarazhanTriggerContext::karazhan_prince_malchezaar; + // Trash + creators["mana warp is about to explode"] = + &RaidKarazhanTriggerContext::mana_warp_is_about_to_explode; + + // Attumen the Huntsman + creators["attumen the huntsman need target priority"] = + &RaidKarazhanTriggerContext::attumen_the_huntsman_need_target_priority; + + creators["attumen the huntsman attumen spawned"] = + &RaidKarazhanTriggerContext::attumen_the_huntsman_attumen_spawned; + + creators["attumen the huntsman attumen is mounted"] = + &RaidKarazhanTriggerContext::attumen_the_huntsman_attumen_is_mounted; + + creators["attumen the huntsman boss wipes aggro when mounting"] = + &RaidKarazhanTriggerContext::attumen_the_huntsman_boss_wipes_aggro_when_mounting; + + // Moroes + creators["moroes boss engaged by main tank"] = + &RaidKarazhanTriggerContext::moroes_boss_engaged_by_main_tank; + + creators["moroes need target priority"] = + &RaidKarazhanTriggerContext::moroes_need_target_priority; + + // Maiden of Virtue + creators["maiden of virtue healers are stunned by repentance"] = + &RaidKarazhanTriggerContext::maiden_of_virtue_healers_are_stunned_by_repentance; + + creators["maiden of virtue holy wrath deals chain damage"] = + &RaidKarazhanTriggerContext::maiden_of_virtue_holy_wrath_deals_chain_damage; + + // The Big Bad Wolf + creators["big bad wolf boss engaged by tank"] = + &RaidKarazhanTriggerContext::big_bad_wolf_boss_engaged_by_tank; + + creators["big bad wolf boss is chasing little red riding hood"] = + &RaidKarazhanTriggerContext::big_bad_wolf_boss_is_chasing_little_red_riding_hood; + + // Romulo and Julianne + creators["romulo and julianne both bosses revived"] = + &RaidKarazhanTriggerContext::romulo_and_julianne_both_bosses_revived; + + // The Wizard of Oz + creators["wizard of oz need target priority"] = + &RaidKarazhanTriggerContext::wizard_of_oz_need_target_priority; + + creators["wizard of oz strawman is vulnerable to fire"] = + &RaidKarazhanTriggerContext::wizard_of_oz_strawman_is_vulnerable_to_fire; + + // The Curator + creators["the curator astral flare spawned"] = + &RaidKarazhanTriggerContext::the_curator_astral_flare_spawned; + + creators["the curator boss engaged by tanks"] = + &RaidKarazhanTriggerContext::the_curator_boss_engaged_by_tanks; + + creators["the curator astral flares cast arcing sear"] = + &RaidKarazhanTriggerContext::the_curator_astral_flares_cast_arcing_sear; + + // Terestian Illhoof + creators["terestian illhoof need target priority"] = + &RaidKarazhanTriggerContext::terestian_illhoof_need_target_priority; + + // Shade of Aran + creators["shade of aran arcane explosion is casting"] = + &RaidKarazhanTriggerContext::shade_of_aran_arcane_explosion_is_casting; + + creators["shade of aran flame wreath is active"] = + &RaidKarazhanTriggerContext::shade_of_aran_flame_wreath_is_active; + + creators["shade of aran conjured elementals summoned"] = + &RaidKarazhanTriggerContext::shade_of_aran_conjured_elementals_summoned; + + creators["shade of aran boss uses counterspell and blizzard"] = + &RaidKarazhanTriggerContext::shade_of_aran_boss_uses_counterspell_and_blizzard; + + // Netherspite + creators["netherspite red beam is active"] = + &RaidKarazhanTriggerContext::netherspite_red_beam_is_active; + + creators["netherspite blue beam is active"] = + &RaidKarazhanTriggerContext::netherspite_blue_beam_is_active; + + creators["netherspite green beam is active"] = + &RaidKarazhanTriggerContext::netherspite_green_beam_is_active; + + creators["netherspite bot is not beam blocker"] = + &RaidKarazhanTriggerContext::netherspite_bot_is_not_beam_blocker; + + creators["netherspite boss is banished"] = + &RaidKarazhanTriggerContext::netherspite_boss_is_banished; + + creators["netherspite need to manage timers and trackers"] = + &RaidKarazhanTriggerContext::netherspite_need_to_manage_timers_and_trackers; + + // Prince Malchezaar + creators["prince malchezaar bot is enfeebled"] = + &RaidKarazhanTriggerContext::prince_malchezaar_bot_is_enfeebled; + + creators["prince malchezaar infernals are spawned"] = + &RaidKarazhanTriggerContext::prince_malchezaar_infernals_are_spawned; + + creators["prince malchezaar boss engaged by main tank"] = + &RaidKarazhanTriggerContext::prince_malchezaar_boss_engaged_by_main_tank; + + // Nightbane + creators["nightbane boss engaged by main tank"] = + &RaidKarazhanTriggerContext::nightbane_boss_engaged_by_main_tank; + + creators["nightbane ranged bots are in charred earth"] = + &RaidKarazhanTriggerContext::nightbane_ranged_bots_are_in_charred_earth; + + creators["nightbane main tank is susceptible to fear"] = + &RaidKarazhanTriggerContext::nightbane_main_tank_is_susceptible_to_fear; + + creators["nightbane pets ignore collision to chase flying boss"] = + &RaidKarazhanTriggerContext::nightbane_pets_ignore_collision_to_chase_flying_boss; + + creators["nightbane boss is flying"] = + &RaidKarazhanTriggerContext::nightbane_boss_is_flying; + + creators["nightbane need to manage timers and trackers"] = + &RaidKarazhanTriggerContext::nightbane_need_to_manage_timers_and_trackers; } private: - static Trigger* karazhan_attumen_the_huntsman(PlayerbotAI* botAI) { return new KarazhanAttumenTheHuntsmanTrigger(botAI); } - static Trigger* karazhan_moroes(PlayerbotAI* botAI) { return new KarazhanMoroesTrigger(botAI); } - static Trigger* karazhan_maiden_of_virtue(PlayerbotAI* botAI) { return new KarazhanMaidenOfVirtueTrigger(botAI); } - static Trigger* karazhan_big_bad_wolf(PlayerbotAI* botAI) { return new KarazhanBigBadWolfTrigger(botAI); } - static Trigger* karazhan_romulo_and_julianne(PlayerbotAI* botAI) { return new KarazhanRomuloAndJulianneTrigger(botAI); } - static Trigger* karazhan_wizard_of_oz(PlayerbotAI* botAI) { return new KarazhanWizardOfOzTrigger(botAI); } - static Trigger* karazhan_the_curator(PlayerbotAI* botAI) { return new KarazhanTheCuratorTrigger(botAI); } - static Trigger* karazhan_terestian_illhoof(PlayerbotAI* botAI) { return new KarazhanTerestianIllhoofTrigger(botAI); } - static Trigger* karazhan_shade_of_aran(PlayerbotAI* botAI) { return new KarazhanShadeOfAranTrigger(botAI); } - static Trigger* karazhan_netherspite(PlayerbotAI* botAI) { return new KarazhanNetherspiteTrigger(botAI); } - static Trigger* karazhan_prince_malchezaar(PlayerbotAI* botAI) { return new KarazhanPrinceMalchezaarTrigger(botAI); } + // Trash + static Trigger* mana_warp_is_about_to_explode( + PlayerbotAI* botAI) { return new ManaWarpIsAboutToExplodeTrigger(botAI); } + + // Attumen the Huntsman + static Trigger* attumen_the_huntsman_need_target_priority( + PlayerbotAI* botAI) { return new AttumenTheHuntsmanNeedTargetPriorityTrigger(botAI); } + + static Trigger* attumen_the_huntsman_attumen_spawned( + PlayerbotAI* botAI) { return new AttumenTheHuntsmanAttumenSpawnedTrigger(botAI); } + + static Trigger* attumen_the_huntsman_attumen_is_mounted( + PlayerbotAI* botAI) { return new AttumenTheHuntsmanAttumenIsMountedTrigger(botAI); } + + static Trigger* attumen_the_huntsman_boss_wipes_aggro_when_mounting( + PlayerbotAI* botAI) { return new AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger(botAI); } + + // Moroes + static Trigger* moroes_boss_engaged_by_main_tank( + PlayerbotAI* botAI) { return new MoroesBossEngagedByMainTankTrigger(botAI); } + + static Trigger* moroes_need_target_priority( + PlayerbotAI* botAI) { return new MoroesNeedTargetPriorityTrigger(botAI); } + + // Maiden of Virtue + static Trigger* maiden_of_virtue_healers_are_stunned_by_repentance( + PlayerbotAI* botAI) { return new MaidenOfVirtueHealersAreStunnedByRepentanceTrigger(botAI); } + + static Trigger* maiden_of_virtue_holy_wrath_deals_chain_damage( + PlayerbotAI* botAI) { return new MaidenOfVirtueHolyWrathDealsChainDamageTrigger(botAI); } + + // The Big Bad Wolf + static Trigger* big_bad_wolf_boss_engaged_by_tank( + PlayerbotAI* botAI) { return new BigBadWolfBossEngagedByTankTrigger(botAI); } + + static Trigger* big_bad_wolf_boss_is_chasing_little_red_riding_hood( + PlayerbotAI* botAI) { return new BigBadWolfBossIsChasingLittleRedRidingHoodTrigger(botAI); } + + // Romulo and Julianne + static Trigger* romulo_and_julianne_both_bosses_revived( + PlayerbotAI* botAI) { return new RomuloAndJulianneBothBossesRevivedTrigger(botAI); } + + // The Wizard of Oz + static Trigger* wizard_of_oz_need_target_priority( + PlayerbotAI* botAI) { return new WizardOfOzNeedTargetPriorityTrigger(botAI); } + + static Trigger* wizard_of_oz_strawman_is_vulnerable_to_fire( + PlayerbotAI* botAI) { return new WizardOfOzStrawmanIsVulnerableToFireTrigger(botAI); } + + // The Curator + static Trigger* the_curator_astral_flare_spawned( + PlayerbotAI* botAI) { return new TheCuratorAstralFlareSpawnedTrigger(botAI); } + + static Trigger* the_curator_boss_engaged_by_tanks( + PlayerbotAI* botAI) { return new TheCuratorBossEngagedByTanksTrigger(botAI); } + + static Trigger* the_curator_astral_flares_cast_arcing_sear( + PlayerbotAI* botAI) { return new TheCuratorBossAstralFlaresCastArcingSearTrigger(botAI); } + + // Terestian Illhoof + static Trigger* terestian_illhoof_need_target_priority( + PlayerbotAI* botAI) { return new TerestianIllhoofNeedTargetPriorityTrigger(botAI); } + + // Shade of Aran + static Trigger* shade_of_aran_arcane_explosion_is_casting( + PlayerbotAI* botAI) { return new ShadeOfAranArcaneExplosionIsCastingTrigger(botAI); } + + static Trigger* shade_of_aran_flame_wreath_is_active( + PlayerbotAI* botAI) { return new ShadeOfAranFlameWreathIsActiveTrigger(botAI); } + + static Trigger* shade_of_aran_conjured_elementals_summoned( + PlayerbotAI* botAI) { return new ShadeOfAranConjuredElementalsSummonedTrigger(botAI); } + + static Trigger* shade_of_aran_boss_uses_counterspell_and_blizzard( + PlayerbotAI* botAI) { return new ShadeOfAranBossUsesCounterspellAndBlizzardTrigger(botAI); } + + // Netherspite + static Trigger* netherspite_red_beam_is_active( + PlayerbotAI* botAI) { return new NetherspiteRedBeamIsActiveTrigger(botAI); } + + static Trigger* netherspite_blue_beam_is_active( + PlayerbotAI* botAI) { return new NetherspiteBlueBeamIsActiveTrigger(botAI); } + + static Trigger* netherspite_green_beam_is_active( + PlayerbotAI* botAI) { return new NetherspiteGreenBeamIsActiveTrigger(botAI); } + + static Trigger* netherspite_bot_is_not_beam_blocker( + PlayerbotAI* botAI) { return new NetherspiteBotIsNotBeamBlockerTrigger(botAI); } + + static Trigger* netherspite_boss_is_banished( + PlayerbotAI* botAI) { return new NetherspiteBossIsBanishedTrigger(botAI); } + + static Trigger* netherspite_need_to_manage_timers_and_trackers( + PlayerbotAI* botAI) { return new NetherspiteNeedToManageTimersAndTrackersTrigger(botAI); } + + // Prince Malchezaar + static Trigger* prince_malchezaar_bot_is_enfeebled( + PlayerbotAI* botAI) { return new PrinceMalchezaarBotIsEnfeebledTrigger(botAI); } + + static Trigger* prince_malchezaar_infernals_are_spawned( + PlayerbotAI* botAI) { return new PrinceMalchezaarInfernalsAreSpawnedTrigger(botAI); } + + static Trigger* prince_malchezaar_boss_engaged_by_main_tank( + PlayerbotAI* botAI) { return new PrinceMalchezaarBossEngagedByMainTankTrigger(botAI); } + + // Nightbane + static Trigger* nightbane_boss_engaged_by_main_tank( + PlayerbotAI* botAI) { return new NightbaneBossEngagedByMainTankTrigger(botAI); } + + static Trigger* nightbane_ranged_bots_are_in_charred_earth( + PlayerbotAI* botAI) { return new NightbaneRangedBotsAreInCharredEarthTrigger(botAI); } + + static Trigger* nightbane_main_tank_is_susceptible_to_fear( + PlayerbotAI* botAI) { return new NightbaneMainTankIsSusceptibleToFearTrigger(botAI); } + + static Trigger* nightbane_pets_ignore_collision_to_chase_flying_boss( + PlayerbotAI* botAI) { return new NightbanePetsIgnoreCollisionToChaseFlyingBossTrigger(botAI); } + + static Trigger* nightbane_boss_is_flying( + PlayerbotAI* botAI) { return new NightbaneBossIsFlyingTrigger(botAI); } + + static Trigger* nightbane_need_to_manage_timers_and_trackers( + PlayerbotAI* botAI) { return new NightbaneNeedToManageTimersAndTrackersTrigger(botAI); } }; -#endif +#endif \ No newline at end of file diff --git a/src/strategy/raids/karazhan/RaidKarazhanTriggers.cpp b/src/strategy/raids/karazhan/RaidKarazhanTriggers.cpp index f70ae70f..e39006f7 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanTriggers.cpp +++ b/src/strategy/raids/karazhan/RaidKarazhanTriggers.cpp @@ -3,17 +3,64 @@ #include "RaidKarazhanActions.h" #include "Playerbots.h" -bool KarazhanAttumenTheHuntsmanTrigger::IsActive() -{ - RaidKarazhanHelpers helpers(botAI); - Unit* boss = helpers.GetFirstAliveUnitByEntry(NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED); +using namespace KarazhanHelpers; - return boss && boss->IsAlive(); +bool ManaWarpIsAboutToExplodeTrigger::IsActive() +{ + Unit* manaWarp = AI_VALUE2(Unit*, "find target", "mana warp"); + return manaWarp && manaWarp->GetHealthPct() < 15; } -bool KarazhanMoroesTrigger::IsActive() +bool AttumenTheHuntsmanNeedTargetPriorityTrigger::IsActive() { + if (botAI->IsHeal(bot)) + return false; + + Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight"); + return midnight != nullptr; +} + +bool AttumenTheHuntsmanAttumenSpawnedTrigger::IsActive() +{ + if (!botAI->IsAssistTankOfIndex(bot, 0)) + return false; + + Unit* attumen = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN); + return attumen != nullptr; +} + +bool AttumenTheHuntsmanAttumenIsMountedTrigger::IsActive() +{ + if (botAI->IsMainTank(bot)) + return false; + + Unit* attumenMounted = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED); + return attumenMounted && attumenMounted->GetVictim() != bot; +} + +bool AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger::IsActive() +{ + if (!IsMapIDTimerManager(botAI, bot)) + return false; + + Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight"); + return midnight != nullptr; +} + +bool MoroesBossEngagedByMainTankTrigger::IsActive() +{ + if (!botAI->IsMainTank(bot)) + return false; + Unit* moroes = AI_VALUE2(Unit*, "find target", "moroes"); + return moroes != nullptr; +} + +bool MoroesNeedTargetPriorityTrigger::IsActive() +{ + if (!botAI->IsDps(bot)) + return false; + 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"); @@ -21,39 +68,67 @@ bool KarazhanMoroesTrigger::IsActive() Unit* robin = AI_VALUE2(Unit*, "find target", "lord robin daris"); Unit* crispin = AI_VALUE2(Unit*, "find target", "lord crispin ference"); - return ((moroes && moroes->IsAlive()) || - (dorothea && dorothea->IsAlive()) || - (catriona && catriona->IsAlive()) || - (keira && keira->IsAlive()) || - (rafe && rafe->IsAlive()) || - (robin && robin->IsAlive()) || - (crispin && crispin->IsAlive())); + Unit* target = GetFirstAliveUnit({ dorothea, catriona, keira, rafe, robin, crispin }); + return target != nullptr; } -bool KarazhanMaidenOfVirtueTrigger::IsActive() +bool MaidenOfVirtueHealersAreStunnedByRepentanceTrigger::IsActive() { - Unit* boss = AI_VALUE2(Unit*, "find target", "maiden of virtue"); + if (!botAI->IsTank(bot)) + return false; - return boss && boss->IsAlive(); + Unit* maiden = AI_VALUE2(Unit*, "find target", "maiden of virtue"); + return maiden && maiden->GetVictim() == bot; } -bool KarazhanBigBadWolfTrigger::IsActive() +bool MaidenOfVirtueHolyWrathDealsChainDamageTrigger::IsActive() { - Unit* boss = AI_VALUE2(Unit*, "find target", "the big bad wolf"); + if (!botAI->IsRanged(bot)) + return false; - return boss && boss->IsAlive(); + Unit* maiden = AI_VALUE2(Unit*, "find target", "maiden of virtue"); + return maiden != nullptr; } -bool KarazhanRomuloAndJulianneTrigger::IsActive() +bool BigBadWolfBossEngagedByTankTrigger::IsActive() { - Unit* julianne = AI_VALUE2(Unit*, "find target", "julianne"); + if (!botAI->IsTank(bot) || bot->HasAura(SPELL_LITTLE_RED_RIDING_HOOD)) + return false; + + Unit* wolf = AI_VALUE2(Unit*, "find target", "the big bad wolf"); + return wolf != nullptr; +} + +bool BigBadWolfBossIsChasingLittleRedRidingHoodTrigger::IsActive() +{ + if (!bot->HasAura(SPELL_LITTLE_RED_RIDING_HOOD)) + return false; + + Unit* wolf = AI_VALUE2(Unit*, "find target", "the big bad wolf"); + return wolf != nullptr; +} + +bool RomuloAndJulianneBothBossesRevivedTrigger::IsActive() +{ + if (!IsMapIDTimerManager(botAI, bot)) + return false; + Unit* romulo = AI_VALUE2(Unit*, "find target", "romulo"); + if (!romulo) + return false; - return julianne && julianne->IsAlive() && romulo && romulo->IsAlive(); + Unit* julianne = AI_VALUE2(Unit*, "find target", "julianne"); + if (!julianne) + return false; + + return true; } -bool KarazhanWizardOfOzTrigger::IsActive() +bool WizardOfOzNeedTargetPriorityTrigger::IsActive() { + if (!IsMapIDTimerManager(botAI, bot)) + return false; + Unit* dorothee = AI_VALUE2(Unit*, "find target", "dorothee"); Unit* tito = AI_VALUE2(Unit*, "find target", "tito"); Unit* roar = AI_VALUE2(Unit*, "find target", "roar"); @@ -61,45 +136,249 @@ bool KarazhanWizardOfOzTrigger::IsActive() Unit* tinhead = AI_VALUE2(Unit*, "find target", "tinhead"); Unit* crone = AI_VALUE2(Unit*, "find target", "the crone"); - return ((dorothee && dorothee->IsAlive()) || - (tito && tito->IsAlive()) || - (roar && roar->IsAlive()) || - (strawman && strawman->IsAlive()) || - (tinhead && tinhead->IsAlive()) || - (crone && crone->IsAlive())); + Unit* target = GetFirstAliveUnit({ dorothee, tito, roar, strawman, tinhead, crone }); + return target != nullptr; } -bool KarazhanTheCuratorTrigger::IsActive() +bool WizardOfOzStrawmanIsVulnerableToFireTrigger::IsActive() { - Unit* boss = AI_VALUE2(Unit*, "find target", "the curator"); + if (bot->getClass() != CLASS_MAGE) + return false; - return boss && boss->IsAlive(); + Unit* strawman = AI_VALUE2(Unit*, "find target", "strawman"); + return strawman && strawman->IsAlive(); } -bool KarazhanTerestianIllhoofTrigger::IsActive() +bool TheCuratorAstralFlareSpawnedTrigger::IsActive() { - Unit* boss = AI_VALUE2(Unit*, "find target", "terestian illhoof"); + if (!botAI->IsDps(bot)) + return false; - return boss && boss->IsAlive(); + Unit* flare = AI_VALUE2(Unit*, "find target", "astral flare"); + return flare != nullptr; } -bool KarazhanShadeOfAranTrigger::IsActive() +bool TheCuratorBossEngagedByTanksTrigger::IsActive() { - Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran"); + if (!botAI->IsMainTank(bot) && !botAI->IsAssistTankOfIndex(bot, 0)) + return false; - return boss && boss->IsAlive(); + Unit* curator = AI_VALUE2(Unit*, "find target", "the curator"); + return curator != nullptr; } -bool KarazhanNetherspiteTrigger::IsActive() +bool TheCuratorBossAstralFlaresCastArcingSearTrigger::IsActive() { - Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); + if (!botAI->IsRanged(bot)) + return false; - return boss && boss->IsAlive(); + Unit* curator = AI_VALUE2(Unit*, "find target", "the curator"); + return curator != nullptr; } -bool KarazhanPrinceMalchezaarTrigger::IsActive() +bool TerestianIllhoofNeedTargetPriorityTrigger::IsActive() { - Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar"); + if (!IsMapIDTimerManager(botAI, bot)) + return false; - return boss && boss->IsAlive(); + Unit* illhoof = AI_VALUE2(Unit*, "find target", "terestian illhoof"); + return illhoof != nullptr; +} + +bool ShadeOfAranArcaneExplosionIsCastingTrigger::IsActive() +{ + Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran"); + return aran && aran->HasUnitState(UNIT_STATE_CASTING) && + aran->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION) && + !IsFlameWreathActive(botAI, bot); +} + +bool ShadeOfAranFlameWreathIsActiveTrigger::IsActive() +{ + Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran"); + return aran && IsFlameWreathActive(botAI, bot); +} + +// Exclusion of Banish is so the player may Banish elementals if they wish +bool ShadeOfAranConjuredElementalsSummonedTrigger::IsActive() +{ + if (!IsMapIDTimerManager(botAI, bot)) + return false; + + Unit* elemental = AI_VALUE2(Unit*, "find target", "conjured elemental"); + return elemental && elemental->IsAlive() && + !elemental->HasAura(SPELL_WARLOCK_BANISH); +} + +bool ShadeOfAranBossUsesCounterspellAndBlizzardTrigger::IsActive() +{ + if (!botAI->IsRanged(bot)) + return false; + + Unit* aran = AI_VALUE2(Unit*, "find target", "shade of aran"); + return aran && !(aran->HasUnitState(UNIT_STATE_CASTING) && + aran->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION)) && + !IsFlameWreathActive(botAI, bot); +} + +bool NetherspiteRedBeamIsActiveTrigger::IsActive() +{ + Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite"); + if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED)) + return false; + + Unit* redPortal = bot->FindNearestCreature(NPC_RED_PORTAL, 150.0f); + return redPortal != nullptr; +} + +bool NetherspiteBlueBeamIsActiveTrigger::IsActive() +{ + Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite"); + if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED)) + return false; + + Unit* bluePortal = bot->FindNearestCreature(NPC_BLUE_PORTAL, 150.0f); + return bluePortal != nullptr; +} + +bool NetherspiteGreenBeamIsActiveTrigger::IsActive() +{ + Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite"); + if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED)) + return false; + + Unit* greenPortal = bot->FindNearestCreature(NPC_GREEN_PORTAL, 150.0f); + return greenPortal != nullptr; +} + +bool NetherspiteBotIsNotBeamBlockerTrigger::IsActive() +{ + Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite"); + if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED)) + return false; + + auto [redBlocker, greenBlocker, blueBlocker] = GetCurrentBeamBlockers(botAI, bot); + return bot != redBlocker && bot != blueBlocker && bot != greenBlocker; +} + +bool NetherspiteBossIsBanishedTrigger::IsActive() +{ + Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite"); + if (!netherspite || !netherspite->HasAura(SPELL_NETHERSPITE_BANISHED)) + return false; + + std::vector voidZones = GetAllVoidZones(botAI, bot); + for (Unit* vz : voidZones) + { + if (bot->GetExactDist2d(vz) < 4.0f) + return true; + } + + return false; +} + +bool NetherspiteNeedToManageTimersAndTrackersTrigger::IsActive() +{ + if (!botAI->IsTank(bot) && !IsMapIDTimerManager(botAI, bot)) + return false; + + Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite"); + return netherspite != nullptr; +} + +bool PrinceMalchezaarBotIsEnfeebledTrigger::IsActive() +{ + return bot->HasAura(SPELL_ENFEEBLE); +} + +bool PrinceMalchezaarInfernalsAreSpawnedTrigger::IsActive() +{ + if (botAI->IsMainTank(bot) || bot->HasAura(SPELL_ENFEEBLE)) + return false; + + Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar"); + return malchezaar != nullptr; +} + +bool PrinceMalchezaarBossEngagedByMainTankTrigger::IsActive() +{ + if (!botAI->IsMainTank(bot)) + return false; + + Unit* malchezaar = AI_VALUE2(Unit*, "find target", "prince malchezaar"); + return malchezaar != nullptr; +} + +bool NightbaneBossEngagedByMainTankTrigger::IsActive() +{ + if (!botAI->IsMainTank(bot)) + return false; + + Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane"); + return nightbane && nightbane->GetPositionZ() <= NIGHTBANE_FLIGHT_Z; +} + +bool NightbaneRangedBotsAreInCharredEarthTrigger::IsActive() +{ + if (!botAI->IsRanged(bot)) + return false; + + Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane"); + return nightbane && nightbane->GetPositionZ() <= NIGHTBANE_FLIGHT_Z; +} + +bool NightbaneMainTankIsSusceptibleToFearTrigger::IsActive() +{ + if (bot->getClass() != CLASS_PRIEST) + return false; + + Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane"); + if (!nightbane) + 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 && botAI->IsMainTank(member)) + { + mainTank = member; + break; + } + } + } + + return mainTank && !mainTank->HasAura(SPELL_FEAR_WARD) && + botAI->CanCastSpell("fear ward", mainTank); +} + +bool NightbanePetsIgnoreCollisionToChaseFlyingBossTrigger::IsActive() +{ + Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane"); + if (!nightbane) + return false; + + Pet* pet = bot->GetPet(); + return pet && pet->IsAlive(); +} + +bool NightbaneBossIsFlyingTrigger::IsActive() +{ + Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane"); + if (!nightbane || nightbane->GetPositionZ() <= NIGHTBANE_FLIGHT_Z) + return false; + + const time_t now = std::time(nullptr); + const uint8 flightPhaseDurationSeconds = 35; + + return nightbaneFlightPhaseStartTimer.find(KARAZHAN_MAP_ID) != nightbaneFlightPhaseStartTimer.end() && + (now - nightbaneFlightPhaseStartTimer[KARAZHAN_MAP_ID] < flightPhaseDurationSeconds); +} + +bool NightbaneNeedToManageTimersAndTrackersTrigger::IsActive() +{ + Unit* nightbane = AI_VALUE2(Unit*, "find target", "nightbane"); + return nightbane != nullptr; } diff --git a/src/strategy/raids/karazhan/RaidKarazhanTriggers.h b/src/strategy/raids/karazhan/RaidKarazhanTriggers.h index 69f44152..e3465ef1 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanTriggers.h +++ b/src/strategy/raids/karazhan/RaidKarazhanTriggers.h @@ -3,80 +3,298 @@ #include "Trigger.h" -class KarazhanAttumenTheHuntsmanTrigger : public Trigger +class ManaWarpIsAboutToExplodeTrigger : public Trigger { public: - KarazhanAttumenTheHuntsmanTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan attumen the huntsman") {} + ManaWarpIsAboutToExplodeTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "mana warp is about to explode") {} bool IsActive() override; }; -class KarazhanMoroesTrigger : public Trigger +class AttumenTheHuntsmanNeedTargetPriorityTrigger : public Trigger { public: - KarazhanMoroesTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan moroes") {} + AttumenTheHuntsmanNeedTargetPriorityTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "attumen the huntsman need target priority") {} bool IsActive() override; }; -class KarazhanMaidenOfVirtueTrigger : public Trigger +class AttumenTheHuntsmanAttumenSpawnedTrigger : public Trigger { public: - KarazhanMaidenOfVirtueTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan maiden of virtue") {} + AttumenTheHuntsmanAttumenSpawnedTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "attumen the huntsman attumen spawned") {} bool IsActive() override; }; -class KarazhanBigBadWolfTrigger : public Trigger +class AttumenTheHuntsmanAttumenIsMountedTrigger : public Trigger { public: - KarazhanBigBadWolfTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan big bad wolf") {} + AttumenTheHuntsmanAttumenIsMountedTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "attumen the huntsman attumen is mounted") {} bool IsActive() override; }; -class KarazhanRomuloAndJulianneTrigger : public Trigger +class AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger : public Trigger { public: - KarazhanRomuloAndJulianneTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan romulo and julianne") {} + AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "attumen the huntsman boss wipes aggro when mounting") {} bool IsActive() override; }; -class KarazhanWizardOfOzTrigger : public Trigger +class MoroesBossEngagedByMainTankTrigger : public Trigger { public: - KarazhanWizardOfOzTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan wizard of oz") {} + MoroesBossEngagedByMainTankTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "moroes boss engaged by main tank") {} bool IsActive() override; }; -class KarazhanTheCuratorTrigger : public Trigger +class MoroesNeedTargetPriorityTrigger : public Trigger { public: - KarazhanTheCuratorTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan the curator") {} + MoroesNeedTargetPriorityTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "moroes need target priority") {} bool IsActive() override; }; -class KarazhanTerestianIllhoofTrigger : public Trigger +class MaidenOfVirtueHealersAreStunnedByRepentanceTrigger : public Trigger { public: - KarazhanTerestianIllhoofTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan terestian illhoof") {} + MaidenOfVirtueHealersAreStunnedByRepentanceTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "maiden of virtue healers are stunned by repentance") {} bool IsActive() override; }; -class KarazhanShadeOfAranTrigger : public Trigger +class MaidenOfVirtueHolyWrathDealsChainDamageTrigger : public Trigger { public: - KarazhanShadeOfAranTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan shade of aran") {} + MaidenOfVirtueHolyWrathDealsChainDamageTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "maiden of virtue holy wrath deals chain damage") {} bool IsActive() override; }; -class KarazhanNetherspiteTrigger : public Trigger +class BigBadWolfBossEngagedByTankTrigger : public Trigger { public: - KarazhanNetherspiteTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan netherspite") {} + BigBadWolfBossEngagedByTankTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "big bad wolf boss engaged by tank") {} bool IsActive() override; }; -class KarazhanPrinceMalchezaarTrigger : public Trigger +class BigBadWolfBossIsChasingLittleRedRidingHoodTrigger : public Trigger { public: - KarazhanPrinceMalchezaarTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan prince malchezaar") {} + BigBadWolfBossIsChasingLittleRedRidingHoodTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "big bad wolf boss is chasing little red riding hood") {} + bool IsActive() override; +}; + +class RomuloAndJulianneBothBossesRevivedTrigger : public Trigger +{ +public: + RomuloAndJulianneBothBossesRevivedTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "romulo and julianne both bosses revived") {} + bool IsActive() override; +}; + +class WizardOfOzNeedTargetPriorityTrigger : public Trigger +{ +public: + WizardOfOzNeedTargetPriorityTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "wizard of oz need target priority") {} + bool IsActive() override; +}; + +class WizardOfOzStrawmanIsVulnerableToFireTrigger : public Trigger +{ +public: + WizardOfOzStrawmanIsVulnerableToFireTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "wizard of oz strawman is vulnerable to fire") {} + bool IsActive() override; +}; +class TheCuratorAstralFlareSpawnedTrigger : public Trigger +{ +public: + TheCuratorAstralFlareSpawnedTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "the curator astral flare spawned") {} + bool IsActive() override; +}; + +class TheCuratorBossEngagedByTanksTrigger : public Trigger +{ +public: + TheCuratorBossEngagedByTanksTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "the curator boss engaged by tanks") {} + bool IsActive() override; +}; + +class TheCuratorBossAstralFlaresCastArcingSearTrigger : public Trigger +{ +public: + TheCuratorBossAstralFlaresCastArcingSearTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "the curator astral flares cast arcing sear") {} + bool IsActive() override; +}; + +class TerestianIllhoofNeedTargetPriorityTrigger : public Trigger +{ +public: + TerestianIllhoofNeedTargetPriorityTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "terestian illhoof need target priority") {} + bool IsActive() override; +}; + +class ShadeOfAranArcaneExplosionIsCastingTrigger : public Trigger +{ +public: + ShadeOfAranArcaneExplosionIsCastingTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "shade of aran arcane explosion is casting") {} + bool IsActive() override; +}; + +class ShadeOfAranFlameWreathIsActiveTrigger : public Trigger +{ +public: + ShadeOfAranFlameWreathIsActiveTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "shade of aran flame wreath is active") {} + bool IsActive() override; +}; + +class ShadeOfAranConjuredElementalsSummonedTrigger : public Trigger +{ +public: + ShadeOfAranConjuredElementalsSummonedTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "shade of aran conjured elementals summoned") {} + bool IsActive() override; +}; + +class ShadeOfAranBossUsesCounterspellAndBlizzardTrigger : public Trigger +{ +public: + ShadeOfAranBossUsesCounterspellAndBlizzardTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "shade of aran boss uses counterspell and blizzard") {} + bool IsActive() override; +}; + +class NetherspiteRedBeamIsActiveTrigger : public Trigger +{ +public: + NetherspiteRedBeamIsActiveTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "netherspite red beam is active") {} + bool IsActive() override; +}; + +class NetherspiteBlueBeamIsActiveTrigger : public Trigger +{ +public: + NetherspiteBlueBeamIsActiveTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "netherspite blue beam is active") {} + bool IsActive() override; +}; + +class NetherspiteGreenBeamIsActiveTrigger : public Trigger +{ +public: + NetherspiteGreenBeamIsActiveTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "netherspite green beam is active") {} + bool IsActive() override; +}; + +class NetherspiteBotIsNotBeamBlockerTrigger : public Trigger +{ +public: + NetherspiteBotIsNotBeamBlockerTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "netherspite bot is not beam blocker") {} + bool IsActive() override; +}; + +class NetherspiteBossIsBanishedTrigger : public Trigger +{ +public: + NetherspiteBossIsBanishedTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "netherspite boss is banished") {} + bool IsActive() override; +}; + +class NetherspiteNeedToManageTimersAndTrackersTrigger : public Trigger +{ +public: + NetherspiteNeedToManageTimersAndTrackersTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "netherspite need to manage timers and trackers") {} + bool IsActive() override; +}; + +class PrinceMalchezaarBotIsEnfeebledTrigger : public Trigger +{ +public: + PrinceMalchezaarBotIsEnfeebledTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "prince malchezaar bot is enfeebled") {} + bool IsActive() override; +}; + +class PrinceMalchezaarInfernalsAreSpawnedTrigger : public Trigger +{ +public: + PrinceMalchezaarInfernalsAreSpawnedTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "prince malchezaar infernals are spawned") {} + bool IsActive() override; +}; + +class PrinceMalchezaarBossEngagedByMainTankTrigger : public Trigger +{ +public: + PrinceMalchezaarBossEngagedByMainTankTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "prince malchezaar boss engaged by main tank") {} + bool IsActive() override; +}; + +class NightbaneBossEngagedByMainTankTrigger : public Trigger +{ +public: + NightbaneBossEngagedByMainTankTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "nightbane boss engaged by main tank") {} + bool IsActive() override; +}; + +class NightbaneRangedBotsAreInCharredEarthTrigger : public Trigger +{ +public: + NightbaneRangedBotsAreInCharredEarthTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "nightbane ranged bots are in charred earth") {} + bool IsActive() override; +}; + +class NightbaneMainTankIsSusceptibleToFearTrigger : public Trigger +{ +public: + NightbaneMainTankIsSusceptibleToFearTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "nightbane main tank is susceptible to fear") {} + bool IsActive() override; +}; + +class NightbanePetsIgnoreCollisionToChaseFlyingBossTrigger : public Trigger +{ +public: + NightbanePetsIgnoreCollisionToChaseFlyingBossTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "nightbane pets ignore collision to chase flying boss") {} + bool IsActive() override; +}; + +class NightbaneBossIsFlyingTrigger : public Trigger +{ +public: + NightbaneBossIsFlyingTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "nightbane boss is flying") {} + bool IsActive() override; +}; + +class NightbaneNeedToManageTimersAndTrackersTrigger : public Trigger +{ +public: + NightbaneNeedToManageTimersAndTrackersTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "nightbane need to manage timers and trackers") {} bool IsActive() override; };