diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index be48bb3c..beaa09dc 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -439,7 +439,7 @@ AiPlayerbot.AutoAvoidAoe = 1 AiPlayerbot.MaxAoeAvoidRadius = 15.0 # A whitelist of aoe spell IDs that should not be avoided -AiPlayerbot.AoeAvoidSpellWhitelist = 50759,57491,13810 +AiPlayerbot.AoeAvoidSpellWhitelist = 50759,57491,13810,29946 # Enable healer bot save mana strategy # Default: 1 (enabled) diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index d0a776b3..1d5de49b 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -1524,6 +1524,9 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster) case 509: strategyName = "aq20"; break; + case 532: + strategyName = "karazhan"; + break; case 533: strategyName = "naxx"; break; diff --git a/src/factory/PlayerbotFactory.cpp b/src/factory/PlayerbotFactory.cpp index 585f2633..d789bd21 100644 --- a/src/factory/PlayerbotFactory.cpp +++ b/src/factory/PlayerbotFactory.cpp @@ -2346,9 +2346,6 @@ void PlayerbotFactory::UpdateTradeSkills() void PlayerbotFactory::InitSkills() { - //uint32 maxValue = level * 5; //not used, line marked for removal. - bot->UpdateSkillsForLevel(); - bot->SetSkill(SKILL_RIDING, 0, 0, 0); if (bot->GetLevel() >= sPlayerbotAIConfig->useGroundMountAtMinLevel) bot->learnSpell(33388); diff --git a/src/strategy/AiObjectContext.cpp b/src/strategy/AiObjectContext.cpp index dd025c2e..2e516c54 100644 --- a/src/strategy/AiObjectContext.cpp +++ b/src/strategy/AiObjectContext.cpp @@ -27,6 +27,27 @@ #include "WarriorAiObjectContext.h" #include "WorldPacketActionContext.h" #include "WorldPacketTriggerContext.h" +#include "raids/RaidStrategyContext.h" +#include "raids/blackwinglair/RaidBwlActionContext.h" +#include "raids/blackwinglair/RaidBwlTriggerContext.h" +#include "raids/naxxramas/RaidNaxxActionContext.h" +#include "raids/naxxramas/RaidNaxxTriggerContext.h" +#include "raids/icecrown/RaidIccActionContext.h" +#include "raids/icecrown/RaidIccTriggerContext.h" +#include "raids/obsidiansanctum/RaidOsActionContext.h" +#include "raids/obsidiansanctum/RaidOsTriggerContext.h" +#include "raids/eyeofeternity/RaidEoEActionContext.h" +#include "raids/vaultofarchavon/RaidVoATriggerContext.h" +#include "raids/onyxia/RaidOnyxiaActionContext.h" +#include "raids/onyxia/RaidOnyxiaTriggerContext.h" +#include "raids/vaultofarchavon/RaidVoAActionContext.h" +#include "raids/eyeofeternity/RaidEoETriggerContext.h" +#include "raids/moltencore/RaidMcActionContext.h" +#include "raids/moltencore/RaidMcTriggerContext.h" +#include "raids/aq20/RaidAq20ActionContext.h" +#include "raids/aq20/RaidAq20TriggerContext.h" +#include "raids/karazhan/RaidKarazhanActionContext.h" +#include "raids/karazhan/RaidKarazhanTriggerContext.h" #include "dungeons/DungeonStrategyContext.h" #include "dungeons/wotlk/WotlkDungeonActionContext.h" #include "dungeons/wotlk/WotlkDungeonTriggerContext.h" @@ -115,6 +136,7 @@ void AiObjectContext::BuildSharedActionContexts(SharedNamedObjectContextList { @@ -31,6 +32,7 @@ public: creators["uld"] = &RaidStrategyContext::uld; creators["icc"] = &RaidStrategyContext::icc; creators["onyxia"] = &RaidStrategyContext::onyxia; + creators["karazhan"] = &RaidStrategyContext::karazhan; } private: @@ -44,6 +46,7 @@ private: static Strategy* uld(PlayerbotAI* botAI) { return new RaidUlduarStrategy(botAI); } static Strategy* icc(PlayerbotAI* botAI) { return new RaidIccStrategy(botAI); } static Strategy* onyxia(PlayerbotAI* botAI) { return new RaidOnyxiaStrategy(botAI); } + static Strategy* karazhan(PlayerbotAI* botAI) { return new RaidKarazhanStrategy(botAI); } }; #endif diff --git a/src/strategy/raids/karazhan/RaidKarazhanActionContext.h b/src/strategy/raids/karazhan/RaidKarazhanActionContext.h new file mode 100644 index 00000000..f58fe4b5 --- /dev/null +++ b/src/strategy/raids/karazhan/RaidKarazhanActionContext.h @@ -0,0 +1,85 @@ +#ifndef _PLAYERBOT_RAIDKARAZHANACTIONS_CONTEXT_H +#define _PLAYERBOT_RAIDKARAZHANACTIONS_CONTEXT_H + +#include "Action.h" +#include "NamedObjectContext.h" +#include "RaidKarazhanActions.h" + + +class RaidKarazhanActionContext : public NamedObjectContext +{ +public: + RaidKarazhanActionContext() + { + creators["karazhan attumen the huntsman stack behind"] = &RaidKarazhanActionContext::karazhan_attumen_the_huntsman_stack_behind; + + creators["karazhan moroes mark target"] = &RaidKarazhanActionContext::karazhan_moroes_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["karazhan big bad wolf run away"] = &RaidKarazhanActionContext::karazhan_big_bad_wolf_run_away; + + creators["karazhan romulo and julianne mark target"] = &RaidKarazhanActionContext::karazhan_romulo_and_julianne_mark_target; + + 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; + + 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["karazhan terestian illhoof mark target"] = &RaidKarazhanActionContext::karazhan_terestian_illhoof_mark_target; + + 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 bot"] = &RaidKarazhanActionContext::karazhan_shade_of_aran_flame_wreath_stop_bot; + 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["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; + + creators["karazhan prince malchezaar avoid infernal"] = &RaidKarazhanActionContext::karazhan_prince_malchezaar_avoid_infernal; + creators["karazhan prince malchezaar run away from shadow nova"] = &RaidKarazhanActionContext::karazhan_prince_malchezaar_run_away_from_shadow_nova; + } + +private: + static Action* karazhan_attumen_the_huntsman_stack_behind(PlayerbotAI* botAI) { return new KarazhanAttumenTheHuntsmanStackBehindAction(botAI); } + + static Action* karazhan_moroes_mark_target(PlayerbotAI* botAI) { return new KarazhanMoroesMarkTargetAction(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* karazhan_big_bad_wolf_run_away(PlayerbotAI* botAI) { return new KarazhanBigBadWolfRunAwayAction(botAI); } + + static Action* karazhan_romulo_and_julianne_mark_target(PlayerbotAI* botAI) { return new KarazhanRomuloAndJulianneMarkTargetAction(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); } + + 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* karazhan_terestian_illhoof_mark_target(PlayerbotAI* botAI) { return new KarazhanTerestianIllhoofMarkTargetAction(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_bot(PlayerbotAI* botAI) { return new KarazhanShadeOfAranFlameWreathStopBotAction(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* 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); } + + static Action* karazhan_prince_malchezaar_avoid_infernal(PlayerbotAI* botAI) { return new KarazhanPrinceMalchezaarAvoidInfernalAction(botAI); } + static Action* karazhan_prince_malchezaar_run_away_from_shadow_nova(PlayerbotAI* botAI) { return new KarazhanPrinceMalchezaarRunAwayFromShadowNovaAction(botAI); } +}; + +#endif diff --git a/src/strategy/raids/karazhan/RaidKarazhanActions.cpp b/src/strategy/raids/karazhan/RaidKarazhanActions.cpp new file mode 100644 index 00000000..41ecc0bf --- /dev/null +++ b/src/strategy/raids/karazhan/RaidKarazhanActions.cpp @@ -0,0 +1,1026 @@ +#include "Playerbots.h" +#include "RaidKarazhanActions.h" +#include "RaidKarazhanHelpers.h" +#include "Timer.h" +#include "WarlockActions.h" +#include "AiObjectContext.h" +#include "Pet.h" +#include "GenericActions.h" +#include "PlayerbotMgr.h" +#include "PlayerbotAI.h" +#include "MovementActions.h" + +namespace +{ + static std::map beamMoveTimes; + static std::map lastBeamMoveSideways; +} + +bool KarazhanAttumenTheHuntsmanStackBehindAction::Execute(Event event) +{ + RaidKarazhanHelpers karazhanHelper(botAI); + Unit* boss = karazhanHelper.GetFirstAliveUnitByEntry(NPC_ATTUMEN_THE_HUNTSMAN); + + float distance = 5.0f; + float orientation = boss->GetOrientation() + M_PI; + float x = boss->GetPositionX(); + float y = boss->GetPositionY(); + float z = boss->GetPositionZ(); + float rx = x + cos(orientation) * distance; + float ry = y + sin(orientation) * distance; + + return MoveTo(bot->GetMapId(), rx, ry, z, false, false, false, false, MovementPriority::MOVEMENT_COMBAT); +} + +bool KarazhanAttumenTheHuntsmanStackBehindAction::isUseful() +{ + RaidKarazhanHelpers karazhanHelper(botAI); + Unit* boss = karazhanHelper.GetFirstAliveUnitByEntry(NPC_ATTUMEN_THE_HUNTSMAN); + + if (boss && botAI->IsTank(bot) && botAI->HasAggro(boss) && boss->GetVictim() == bot) + return false; + + return boss != nullptr; +} + +bool KarazhanMoroesMarkTargetAction::Execute(Event event) +{ + RaidKarazhanHelpers karazhanHelper(botAI); + + Unit* dorothea = AI_VALUE2(Unit*, "find target", "baroness dorothea millstipe"); + Unit* catriona = AI_VALUE2(Unit*, "find target", "lady catriona von'indi"); + Unit* keira = AI_VALUE2(Unit*, "find target", "lady keira berrybuck"); + Unit* rafe = AI_VALUE2(Unit*, "find target", "baron rafe dreuger"); + Unit* robin = AI_VALUE2(Unit*, "find target", "lord robin daris"); + Unit* crispin = AI_VALUE2(Unit*, "find target", "lord crispin ference"); + Unit* target = karazhanHelper.GetFirstAliveUnit({dorothea, catriona, keira, rafe, robin, crispin}); + + karazhanHelper.MarkTargetWithSkull(target); + + return false; +} + +bool KarazhanMaidenOfVirtuePositionBossAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "maiden of virtue"); + Unit* healer = nullptr; + + if (Group* group = bot->GetGroup()) + { + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive() || !botAI->IsHeal(member) || !member->HasAura(SPELL_REPENTANCE)) + continue; + healer = member; + break; + } + } + + if (healer) + { + float angle = healer->GetOrientation(); + float targetX = healer->GetPositionX() + cos(angle) * 6.0f; + float targetY = healer->GetPositionY() + sin(angle) * 6.0f; + float targetZ = healer->GetPositionZ(); + + return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, true, + MovementPriority::MOVEMENT_COMBAT); + } + + const float maxDistance = 3.0f; + const float distanceToBossPosition = boss->GetExactDist2d(KARAZHAN_MAIDEN_OF_VIRTUE_BOSS_POSITION); + + if (distanceToBossPosition > maxDistance) + { + float dX = KARAZHAN_MAIDEN_OF_VIRTUE_BOSS_POSITION.GetPositionX() - boss->GetPositionX(); + float dY = KARAZHAN_MAIDEN_OF_VIRTUE_BOSS_POSITION.GetPositionY() - boss->GetPositionY(); + float mX = KARAZHAN_MAIDEN_OF_VIRTUE_BOSS_POSITION.GetPositionX() + (dX / distanceToBossPosition) * maxDistance; + float mY = KARAZHAN_MAIDEN_OF_VIRTUE_BOSS_POSITION.GetPositionY() + (dY / distanceToBossPosition) * maxDistance; + + return MoveTo(bot->GetMapId(), mX, mY, + bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); + } + return false; +} + +bool KarazhanMaidenOfVirtuePositionBossAction::isUseful() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "maiden of virtue"); + + return boss && botAI->IsTank(bot) && botAI->HasAggro(boss) && boss->GetVictim() == bot; +} + +bool KarazhanMaidenOfVirtuePositionRangedAction::Execute(Event event) +{ + int maxIndex = 7; + int index = 0; + + const GuidVector members = AI_VALUE(GuidVector, "group members"); + + for (const auto& memberGuid : members) + { + Unit* member = botAI->GetUnit(memberGuid); + + if (!member || !botAI->IsRanged(member->ToPlayer())) + continue; + + if (member == bot) + break; + + if (index >= maxIndex) + { + index = 0; + continue; + } + index++; + } + + float distance = bot->GetExactDist2d(KARAZHAN_MAIDEN_OF_VIRTUE_RANGED_POSITION[index]); + const float maxDistance = 2.0f; + + if (distance > maxDistance) + return MoveTo(bot->GetMapId(), KARAZHAN_MAIDEN_OF_VIRTUE_RANGED_POSITION[index].GetPositionX(), + KARAZHAN_MAIDEN_OF_VIRTUE_RANGED_POSITION[index].GetPositionY(), bot->GetPositionZ(), false, + false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); + + return false; +} + +bool KarazhanMaidenOfVirtuePositionRangedAction::isUseful() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "maiden of virtue"); + + return boss && botAI->IsRanged(bot); +} + +bool KarazhanBigBadWolfRunAwayAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "the big bad wolf"); + + bot->AttackStop(); + bot->InterruptNonMeleeSpells(false); + + constexpr float threshold = 1.0f; + Position target = KARAZHAN_BIG_BAD_WOLF_RUN_POSITION[currentIndex]; + + if (bot->GetExactDist2d(target.GetPositionX(), target.GetPositionY()) < threshold) + { + currentIndex = (currentIndex + 1) % 4; + target = KARAZHAN_BIG_BAD_WOLF_RUN_POSITION[currentIndex]; + } + + return MoveTo(bot->GetMapId(), target.GetPositionX(), target.GetPositionY(), target.GetPositionZ(), false, false, + false, true, MovementPriority::MOVEMENT_FORCED); +} + +bool KarazhanBigBadWolfRunAwayAction::isUseful() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "the big bad wolf"); + + return boss && bot->HasAura(SPELL_LITTLE_RED_RIDING_HOOD); +} + +bool KarazhanRomuloAndJulianneMarkTargetAction::Execute(Event event) +{ + Unit* target = nullptr; + Unit* romulo = AI_VALUE2(Unit*, "find target", "romulo"); + Unit* julianne = AI_VALUE2(Unit*, "find target", "julianne"); + + const int maxPctDifference = 10; + if (julianne->GetHealthPct() + maxPctDifference < romulo->GetHealthPct() || julianne->GetHealthPct() < 1.0f) + target = romulo; + else if (romulo->GetHealthPct() + maxPctDifference < julianne->GetHealthPct() || romulo->GetHealthPct() < 1.0f) + target = julianne; + if (!target) + return false; + + RaidKarazhanHelpers karazhanHelper(botAI); + karazhanHelper.MarkTargetWithSkull(target); + + return false; +} + +bool KarazhanWizardOfOzMarkTargetAction::Execute(Event event) +{ + RaidKarazhanHelpers karazhanHelper(botAI); + Unit* dorothee = AI_VALUE2(Unit*, "find target", "dorothee"); + Unit* tito = AI_VALUE2(Unit*, "find target", "tito"); + Unit* roar = AI_VALUE2(Unit*, "find target", "roar"); + Unit* strawman = AI_VALUE2(Unit*, "find target", "strawman"); + Unit* tinhead = AI_VALUE2(Unit*, "find target", "tinhead"); + Unit* crone = AI_VALUE2(Unit*, "find target", "the crone"); + Unit* target = karazhanHelper.GetFirstAliveUnit({dorothee, tito, roar, strawman, tinhead, crone}); + + karazhanHelper.MarkTargetWithSkull(target); + + return false; +} + +bool KarazhanWizardOfOzScorchStrawmanAction::Execute(Event event) +{ + Unit* strawman = AI_VALUE2(Unit*, "find target", "strawman"); + if (!strawman || !strawman->IsAlive()) + return false; + + Group* group = bot->GetGroup(); + if (!group) + return false; + + const std::vector scorchSpellIds = {42859, 42858, 10207}; + + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive()) + continue; + + if (member->getClass() != CLASS_MAGE) + continue; + + PlayerbotAI* mageAI = sPlayerbotsMgr->GetPlayerbotAI(member); + if (!mageAI) + continue; + + uint32 knownScorchId = 0; + for (uint32 spellId : scorchSpellIds) + { + if (member->HasSpell(spellId)) + { + knownScorchId = spellId; + break; + } + } + if (!knownScorchId) + continue; + + mageAI->CastSpell(knownScorchId, strawman); + } + return false; +} + +bool KarazhanTheCuratorMarkTargetAction::Execute(Event event) +{ + Unit* target = AI_VALUE2(Unit*, "find target", "astral flare"); + if (!target || !target->IsAlive()) + return false; + + RaidKarazhanHelpers karazhanHelper(botAI); + karazhanHelper.MarkTargetWithSkull(target); + + return false; +} + +bool KarazhanTheCuratorPositionBossAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "the curator"); + + const float maxDistance = 3.0f; + const float distanceToBossPosition = boss->GetExactDist2d(KARAZHAN_THE_CURATOR_BOSS_POSITION); + + if (distanceToBossPosition > maxDistance) + { + float dX = KARAZHAN_THE_CURATOR_BOSS_POSITION.GetPositionX() - boss->GetPositionX(); + float dY = KARAZHAN_THE_CURATOR_BOSS_POSITION.GetPositionY() - boss->GetPositionY(); + + float mX = KARAZHAN_THE_CURATOR_BOSS_POSITION.GetPositionX() + (dX / distanceToBossPosition) * maxDistance; + float mY = KARAZHAN_THE_CURATOR_BOSS_POSITION.GetPositionY() + (dY / distanceToBossPosition) * maxDistance; + + return MoveTo(bot->GetMapId(), mX, mY, + bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, + false); + } + return false; +} + +bool KarazhanTheCuratorPositionBossAction::isUseful() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "the curator"); + + return boss && botAI->IsTank(bot) && botAI->HasAggro(boss) && boss->GetVictim() == bot; +} + +bool KarazhanTheCuratorSpreadRangedAction::Execute(Event event) +{ + RaidKarazhanHelpers karazhanHelper(botAI); + const float minDistance = 5.0f; + Unit* nearestPlayer = karazhanHelper.GetNearestPlayerInRadius(minDistance); + + if (nearestPlayer) + return FleePosition(nearestPlayer->GetPosition(), minDistance); + + return false; +} + +bool KarazhanTheCuratorSpreadRangedAction::isUseful() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "the curator"); + + return boss && botAI->IsRanged(bot); +} + +bool KarazhanTerestianIllhoofMarkTargetAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "terestian illhoof"); + if (!boss) + return false; + + RaidKarazhanHelpers karazhanHelper(botAI); + Unit* target = karazhanHelper.GetFirstAliveUnitByEntry(NPC_DEMON_CHAINS); + + if (!target || !target->IsAlive()) + { + target = karazhanHelper.GetFirstAliveUnitByEntry(NPC_KILREK); + if (!target || !target->IsAlive()) + { + target = boss; + } + } + karazhanHelper.MarkTargetWithSkull(target); + + return false; +} + +bool KarazhanShadeOfAranArcaneExplosionRunAwayAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran"); + static std::map arcaneExplosionEndTimes; + ObjectGuid botGuid = bot->GetGUID(); + const float safeDistance = 20.0f; + const float distance = bot->GetDistance2d(boss); + + bot->AttackStop(); + bot->InterruptNonMeleeSpells(false); + + if (distance < safeDistance) + { + return MoveAway(boss, safeDistance - distance); + } + + if (!botAI->HasStrategy("stay", BOT_STATE_COMBAT)) + botAI->ChangeStrategy("+stay", BOT_STATE_COMBAT); + + return false; +} + +bool KarazhanShadeOfAranArcaneExplosionRunAwayAction::isUseful() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran"); + static std::map arcaneExplosionEndTimes; + ObjectGuid botGuid = bot->GetGUID(); + if (!boss || !boss->IsAlive()) + return false; + + if (boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION)) + { + arcaneExplosionEndTimes[botGuid] = time(nullptr) + 1; + return true; + } + + if (arcaneExplosionEndTimes.count(botGuid) && arcaneExplosionEndTimes[botGuid] > time(nullptr)) + { + return true; + } + + if (arcaneExplosionEndTimes.count(botGuid)) + arcaneExplosionEndTimes.erase(botGuid); + + if (botAI->HasStrategy("stay", BOT_STATE_COMBAT)) + botAI->ChangeStrategy("-stay", BOT_STATE_COMBAT); + + return false; +} + +bool KarazhanShadeOfAranFlameWreathStopBotAction::Execute(Event event) +{ + RaidKarazhanHelpers karazhanHelper(botAI); + static std::map flameWreathPositions; + ObjectGuid botGuid = bot->GetGUID(); + if (karazhanHelper.IsFlameWreathActive()) + { + if (flameWreathPositions.find(botGuid) == flameWreathPositions.end()) + { + flameWreathPositions[botGuid] = Position(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ()); + } + + AI_VALUE(LastMovement&, "last movement").Set(nullptr); + bot->GetMotionMaster()->Clear(); + if (bot->isMoving()) + bot->StopMoving(); + + Position& pos = flameWreathPositions[botGuid]; + return MoveTo(bot->GetMapId(), pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_FORCED); + } + else + { + flameWreathPositions.erase(botGuid); + } + return false; +} + +bool KarazhanShadeOfAranMarkConjuredElementalAction::Execute(Event event) +{ + RaidKarazhanHelpers karazhanHelper(botAI); + Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran"); + if (!boss || !boss->IsAlive() || karazhanHelper.IsFlameWreathActive()) + return false; + + Unit* target = karazhanHelper.GetFirstAliveUnitByEntry(NPC_CONJURED_ELEMENTAL); + if (!target || target->HasAura(SPELL_WARLOCK_BANISH) || !target->IsAlive()) + { + return false; + } + karazhanHelper.MarkTargetWithSkull(target); + + return false; +} + +bool KarazhanShadeOfAranSpreadRangedAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran"); + + const float maxBossDistance = 12.0f; + float bossDistance = bot->GetExactDist2d(boss); + if (bossDistance > maxBossDistance) + { + float dX = bot->GetPositionX() - boss->GetPositionX(); + float dY = bot->GetPositionY() - boss->GetPositionY(); + float length = std::sqrt(dX * dX + dY * dY); + + dX /= length; + dY /= length; + + float tX = boss->GetPositionX() + dX * maxBossDistance; + float tY = boss->GetPositionY() + dY * maxBossDistance; + + return MoveTo(bot->GetMapId(), tX, tY, bot->GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT); + } + + const float minDistance = 5.0f; + RaidKarazhanHelpers karazhanHelper(botAI); + Unit* nearestPlayer = karazhanHelper.GetNearestPlayerInRadius(minDistance); + if (nearestPlayer) + return FleePosition(nearestPlayer->GetPosition(), minDistance); + + return false; +} + +bool KarazhanShadeOfAranSpreadRangedAction::isUseful() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran"); + if (!boss || !boss->IsAlive()) + return false; + + RaidKarazhanHelpers karazhanHelper(botAI); + + return botAI->IsRanged(bot) && !karazhanHelper.IsFlameWreathActive() && !(boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION)); +} + +// One tank per phase will dance in and out of the red beam (5 seconds in, 5 seconds out) +// Tanks will ignore void zones--their positioning is too important +bool KarazhanNetherspiteBlockRedBeamAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); + Unit* redPortal = bot->FindNearestCreature(NPC_RED_PORTAL, 150.0f); + + Group* group = bot->GetGroup(); + if (!group) + return false; + + Player* eligibleTank = nullptr; + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive()) + continue; + PlayerbotAI* memberAI = sPlayerbotsMgr->GetPlayerbotAI(member); + if (!memberAI || !memberAI->IsTank(member)) + continue; + if (member->HasAura(SPELL_NETHER_EXHAUSTION_RED)) + continue; + eligibleTank = member; + break; + } + + RaidKarazhanHelpers karazhanHelper(botAI); + Position beamPos = karazhanHelper.GetPositionOnBeam(boss, redPortal, 18.0f); + + if (bot == eligibleTank) + { + bot->Yell("I'm moving to block the red beam!", LANG_UNIVERSAL); + ObjectGuid botGuid = bot->GetGUID(); + uint32 intervalSecs = 5; + + if (beamMoveTimes[botGuid] == 0) + { + beamMoveTimes[botGuid] = time(nullptr); + lastBeamMoveSideways[botGuid] = false; + } + if (time(nullptr) - beamMoveTimes[botGuid] >= intervalSecs) + { + lastBeamMoveSideways[botGuid] = !lastBeamMoveSideways[botGuid]; + beamMoveTimes[botGuid] = time(nullptr); + } + if (!lastBeamMoveSideways[botGuid]) + { + return MoveTo(bot->GetMapId(), beamPos.GetPositionX(), beamPos.GetPositionY(), beamPos.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_FORCED); + } + else + { + float bx = boss->GetPositionX(); + float by = boss->GetPositionY(); + float px = redPortal->GetPositionX(); + float py = redPortal->GetPositionY(); + float dx = px - bx; + float dy = py - by; + float length = sqrt(dx*dx + dy*dy); + if (length == 0.0f) + return false; + + dx /= length; + dy /= length; + float perpDx = -dy; + float perpDy = dx; + float sideX = beamPos.GetPositionX() + perpDx * 3.0f; + float sideY = beamPos.GetPositionY() + perpDy * 3.0f; + float sideZ = beamPos.GetPositionZ(); + + return MoveTo(bot->GetMapId(), sideX, sideY, sideZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED); + } + } + return false; +} + +bool KarazhanNetherspiteBlockRedBeamAction::isUseful() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); + Unit* redPortal = bot->FindNearestCreature(NPC_RED_PORTAL, 150.0f); + + ObjectGuid botGuid = bot->GetGUID(); + static std::map lastBossBanishState; + bool bossIsBanished = boss && boss->HasAura(SPELL_NETHERSPITE_BANISHED); + + if (!boss || !redPortal) + return false; + + if (lastBossBanishState[botGuid] != bossIsBanished) + { + if (!bossIsBanished) + { + beamMoveTimes[botGuid] = 0; + lastBeamMoveSideways[botGuid] = false; + } + lastBossBanishState[botGuid] = bossIsBanished; + } + + if (bossIsBanished) + return false; + + return true; +} + +// Two non-Rogue/Warrior DPS will block the blue beam for each phase (swap at 25 debuff stacks) +// When avoiding void zones, blocking bots will move along the beam to continue blocking +bool KarazhanNetherspiteBlockBlueBeamAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); + Unit* bluePortal = bot->FindNearestCreature(NPC_BLUE_PORTAL, 150.0f); + + RaidKarazhanHelpers karazhanHelper(botAI); + std::vector blueBlockers = karazhanHelper.GetBlueBlockers(); + static std::map wasBlockingBlueBeam; + ObjectGuid botGuid = bot->GetGUID(); + Player* assignedBlueBlocker = blueBlockers.empty() ? nullptr : blueBlockers.front(); + bool isBlockingNow = (bot == assignedBlueBlocker); + bool wasBlocking = wasBlockingBlueBeam[botGuid]; + + if (wasBlocking && !isBlockingNow) + { + bot->Yell("I'm leaving the blue beam--next blocker up!", LANG_UNIVERSAL); + wasBlockingBlueBeam[botGuid] = false; + } + else if (isBlockingNow) + { + wasBlockingBlueBeam[botGuid] = true; + } + if (isBlockingNow) + { + if (!wasBlocking) + bot->Yell("I'm moving to block the blue beam!", LANG_UNIVERSAL); + wasBlockingBlueBeam[botGuid] = true; + std::vector voidZones = karazhanHelper.GetAllVoidZones(); + float bx = boss->GetPositionX(); + float by = boss->GetPositionY(); + float bz = boss->GetPositionZ(); + float px = bluePortal->GetPositionX(); + float py = bluePortal->GetPositionY(); + float dx = px - bx; + float dy = py - by; + float length = sqrt(dx*dx + dy*dy); + if (length == 0.0f) + return false; + + dx /= length; + dy /= length; + float bestDist = 150.0f; + Position bestPos; + bool found = false; + for (float dist = 18.0f; dist <= 25.0f; dist += 0.5f) + { + float candidateX = bx + dx * dist; + float candidateY = by + dy * dist; + float candidateZ = bz; + bool outsideAllVoidZones = true; + for (Unit* voidZone : voidZones) + { + float voidZoneDist = sqrt(pow(candidateX - voidZone->GetPositionX(), 2) + pow(candidateY - voidZone->GetPositionY(), 2)); + if (voidZoneDist < 4.0f) + { + outsideAllVoidZones = false; + break; + } + } + if (!outsideAllVoidZones) + continue; + + float distToIdeal = fabs(dist - 18.0f); + if (!found || distToIdeal < bestDist) + { + bestDist = distToIdeal; + bestPos = Position(candidateX, candidateY, candidateZ); + found = true; + } + } + if (found) + { + return MoveTo(bot->GetMapId(), bestPos.GetPositionX(), bestPos.GetPositionY(), bestPos.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_FORCED); + } + return false; + } + else if (wasBlocking) + { + 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 healers will block the green beam for each phase (swap at 25 debuff stacks +// OR one rogue or DPS warrior will block the green beam for any entire phase +// When avoiding void zones, blocking bots will move along the beam to continue blocking +bool KarazhanNetherspiteBlockGreenBeamAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); + Unit* greenPortal = bot->FindNearestCreature(NPC_GREEN_PORTAL, 150.0f); + + RaidKarazhanHelpers karazhanHelper(botAI); + std::vector greenBlockers = karazhanHelper.GetGreenBlockers(); + static std::map wasBlockingGreenBeam; + ObjectGuid botGuid = bot->GetGUID(); + Player* assignedGreenBlocker = greenBlockers.empty() ? nullptr : greenBlockers.front(); + bool isBlockingNow = (bot == assignedGreenBlocker); + bool wasBlocking = wasBlockingGreenBeam[botGuid]; + + if (wasBlocking && !isBlockingNow) + { + bot->Yell("I'm leaving the green beam--next blocker up!", LANG_UNIVERSAL); + wasBlockingGreenBeam[botGuid] = false; + } + else if (isBlockingNow) + { + if (!wasBlocking) + bot->Yell("I'm moving to block the green beam!", LANG_UNIVERSAL); + wasBlockingGreenBeam[botGuid] = true; + std::vector voidZones = karazhanHelper.GetAllVoidZones(); + float bx = boss->GetPositionX(); + float by = boss->GetPositionY(); + float bz = boss->GetPositionZ(); + float px = greenPortal->GetPositionX(); + float py = greenPortal->GetPositionY(); + float dx = px - bx; + float dy = py - by; + float length = sqrt(dx*dx + dy*dy); + if (length == 0.0f) + return false; + + dx /= length; + dy /= length; + float bestDist = 150.0f; + Position bestPos; + bool found = false; + for (float dist = 18.0f; dist <= 25.0f; dist += 0.5f) + { + float candidateX = bx + dx * dist; + float candidateY = by + dy * dist; + float candidateZ = bz; + bool outsideAllVoidZones = true; + for (Unit* voidZone : voidZones) + { + float voidZoneDist = sqrt(pow(candidateX - voidZone->GetPositionX(), 2) + pow(candidateY - voidZone->GetPositionY(), 2)); + if (voidZoneDist < 4.0f) + { + outsideAllVoidZones = false; + break; + } + } + if (!outsideAllVoidZones) + continue; + + float distToIdeal = fabs(dist - 18.0f); + if (!found || distToIdeal < bestDist) + { + bestDist = distToIdeal; + bestPos = Position(candidateX, candidateY, candidateZ); + found = true; + } + } + if (found) + { + return MoveTo(bot->GetMapId(), bestPos.GetPositionX(), bestPos.GetPositionY(), bestPos.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_FORCED); + } + return false; + } + else if (wasBlocking) + { + wasBlockingGreenBeam[botGuid] = false; + } + return false; +} + +bool KarazhanNetherspiteBlockGreenBeamAction::isUseful() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); + Unit* greenPortal = bot->FindNearestCreature(NPC_GREEN_PORTAL, 150.0f); + + return boss && greenPortal && !boss->HasAura(SPELL_NETHERSPITE_BANISHED); +} + +// All bots not currently blocking a beam will avoid beams and void zones +bool KarazhanNetherspiteAvoidBeamAndVoidZoneAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); + RaidKarazhanHelpers karazhanHelper(botAI); + auto [redBlocker, greenBlocker, blueBlocker] = karazhanHelper.GetCurrentBeamBlockers(); + std::vector voidZones = karazhanHelper.GetAllVoidZones(); + + bool nearVoidZone = false; + for (Unit* vz : voidZones) + { + if (bot->GetExactDist2d(vz) < 4.0f) + { + nearVoidZone = true; + break; + } + } + struct BeamAvoid { Unit* portal; float minDist, maxDist; }; + std::vector beams; + Unit* redPortal = bot->FindNearestCreature(NPC_RED_PORTAL, 150.0f); + Unit* bluePortal = bot->FindNearestCreature(NPC_BLUE_PORTAL, 150.0f); + Unit* greenPortal = bot->FindNearestCreature(NPC_GREEN_PORTAL, 150.0f); + + if (redPortal) { + float bx = boss->GetPositionX(), by = boss->GetPositionY(); + float px = redPortal->GetPositionX(), py = redPortal->GetPositionY(); + float dx = px - bx, dy = py - by; + float length = sqrt(dx*dx + dy*dy); + beams.push_back({redPortal, 0.0f, length}); + } + if (bluePortal) { + float bx = boss->GetPositionX(), by = boss->GetPositionY(); + float px = bluePortal->GetPositionX(), py = bluePortal->GetPositionY(); + float dx = px - bx, dy = py - by; + float length = sqrt(dx*dx + dy*dy); + beams.push_back({bluePortal, 0.0f, length}); + } + if (greenPortal) { + float bx = boss->GetPositionX(), by = boss->GetPositionY(); + float px = greenPortal->GetPositionX(), py = greenPortal->GetPositionY(); + float dx = px - bx, dy = py - by; + float length = sqrt(dx*dx + dy*dy); + beams.push_back({greenPortal, 0.0f, length}); + } + + bool nearBeam = false; + for (const auto& beam : beams) + { + float bx = boss->GetPositionX(), by = boss->GetPositionY(); + float px = beam.portal->GetPositionX(), py = beam.portal->GetPositionY(); + float dx = px - bx, dy = py - by; + float length = sqrt(dx*dx + dy*dy); + if (length == 0.0f) continue; + + dx /= length; dy /= length; + float botdx = bot->GetPositionX() - bx, botdy = bot->GetPositionY() - by; + float t = (botdx * dx + botdy * dy); + float beamX = bx + dx * t, beamY = by + dy * t; + float distToBeam = sqrt(pow(bot->GetPositionX() - beamX, 2) + pow(bot->GetPositionY() - beamY, 2)); + if (distToBeam < 5.0f && t > beam.minDist && t < beam.maxDist) + { + nearBeam = true; + break; + } + } + + if (!nearVoidZone && !nearBeam) + return false; + + const float minMoveDist = 3.0f, maxSearchDist = 20.0f, stepAngle = M_PI/18.0f, stepDist = 0.5f; + float bossZ = boss->GetPositionZ(); + Position bestCandidate; + float bestDist = 0.0f; + bool found = false; + for (float angle = 0; angle < 2 * M_PI; angle += stepAngle) + { + for (float dist = 5.0f; dist <= maxSearchDist; dist += stepDist) + { + float cx = bot->GetPositionX() + cos(angle) * dist; + float cy = bot->GetPositionY() + sin(angle) * dist; + float cz = bossZ; + if (std::any_of(voidZones.begin(), voidZones.end(), [&](Unit* vz){ return Position(cx, cy, cz).GetExactDist2d(vz) < 4.0f; })) + continue; + + bool tooCloseToBeam = false; + for (const auto& beam : beams) + { + float bx = boss->GetPositionX(), by = boss->GetPositionY(); + float px = beam.portal->GetPositionX(), py = beam.portal->GetPositionY(); + float dx = px - bx, dy = py - by; + float length = sqrt(dx*dx + dy*dy); + if (length == 0.0f) continue; + dx /= length; dy /= length; + float botdx = cx - bx, botdy = cy - by; + float t = (botdx * dx + botdy * dy); + float beamX = bx + dx * t, beamY = by + dy * t; + float distToBeam = sqrt(pow(cx - beamX, 2) + pow(cy - beamY, 2)); + if (distToBeam < 5.0f && t > beam.minDist && t < beam.maxDist) + { + tooCloseToBeam = true; + break; + } + } + if (tooCloseToBeam) continue; + + float moveDist = sqrt(pow(cx - bot->GetPositionX(), 2) + pow(cy - bot->GetPositionY(), 2)); + if (moveDist < minMoveDist) continue; + + if (!found || moveDist < bestDist) + { + bestCandidate = Position(cx, cy, cz); + bestDist = moveDist; + found = true; + } + } + } + if (found && karazhanHelper.IsSafePosition(bestCandidate.GetPositionX(), bestCandidate.GetPositionY(), bestCandidate.GetPositionZ(), + voidZones, 4.0f)) + { + return MoveTo(bot->GetMapId(), bestCandidate.GetPositionX(), bestCandidate.GetPositionY(), bestCandidate.GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_COMBAT); + } + return false; +} + +bool KarazhanNetherspiteAvoidBeamAndVoidZoneAction::isUseful() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); + if (!boss || boss->HasAura(SPELL_NETHERSPITE_BANISHED)) + return false; + + RaidKarazhanHelpers karazhanHelper(botAI); + auto [redBlocker, greenBlocker, blueBlocker] = karazhanHelper.GetCurrentBeamBlockers(); + if (bot == redBlocker || bot == blueBlocker || bot == greenBlocker) + return false; + + return true; +} + +bool KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction::Execute(Event event) +{ + RaidKarazhanHelpers karazhanHelper(botAI); + std::vector voidZones = karazhanHelper.GetAllVoidZones(); + + for (Unit* vz : voidZones) + { + if (vz->GetEntry() == NPC_VOID_ZONE && bot->GetExactDist2d(vz) < 4.0f) + { + return FleePosition(vz->GetPosition(), 4.0f); + } + } + return false; +} + +bool KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction::isUseful() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); + if (!boss || !boss->HasAura(SPELL_NETHERSPITE_BANISHED)) + return false; + + RaidKarazhanHelpers karazhanHelper(botAI); + std::vector voidZones = karazhanHelper.GetAllVoidZones(); + for (Unit* vz : voidZones) + { + if (bot->GetExactDist2d(vz) < 4.0f) + { + return true; + } + } + return false; +} + +bool KarazhanPrinceMalchezaarAvoidInfernalAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar"); + if (!boss) + return false; + + RaidKarazhanHelpers karazhanHelper(botAI); + std::vector infernals = karazhanHelper.GetSpawnedInfernals(); + + const float safeInfernalDistance = 20.0f; + const float safeInfernalTankingDistance = 25.0f; + float safeDistance = botAI->IsTank(bot) && botAI->HasAggro(boss) && boss->GetVictim() == + bot ? safeInfernalTankingDistance : safeInfernalDistance; + + for (Unit* infernal : infernals) + { + float distance = bot->GetDistance2d(infernal); + if (distance < safeDistance) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(false); + return MoveAway(infernal, safeDistance - distance); + } + } + return false; +} + +// For Enfeebled bots to avoid getting one-shot by Shadow Nova +bool KarazhanPrinceMalchezaarRunAwayFromShadowNovaAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar"); + RaidKarazhanHelpers karazhanHelper(botAI); + std::vector infernals = karazhanHelper.GetSpawnedInfernals(); + + const float safeBossDistance = 30.0f; + const float safeInfernalDistance = 20.0f; + float currentBossDistance = bot->GetDistance2d(boss); + if (currentBossDistance < safeBossDistance) + { + const float stepSize = 2.0f; + const int numAngles = 16; + for (int i = 0; i < numAngles; ++i) + { + float angle = (2 * M_PI * i) / numAngles; + float dx = cos(angle); + float dy = sin(angle); + + bool pathIsSafe = true; + for (float dist = stepSize; dist <= safeBossDistance; dist += stepSize) + { + float x = bot->GetPositionX() + dx * dist; + float y = bot->GetPositionY() + dy * dist; + for (Unit* infernal : infernals) + { + float infernalDist = sqrt(pow(x - infernal->GetPositionX(), 2) + pow(y - infernal->GetPositionY(), 2)); + if (infernalDist < safeInfernalDistance) + { + pathIsSafe = false; + break; + } + } + if (!pathIsSafe) + break; + } + if (pathIsSafe) + { + float destX = bot->GetPositionX() + dx * (safeBossDistance - currentBossDistance); + float destY = bot->GetPositionY() + dy * (safeBossDistance - currentBossDistance); + float destZ = bot->GetPositionZ(); + bot->AttackStop(); + bot->InterruptNonMeleeSpells(false); + if (karazhanHelper.IsSafePosition(destX, destY, destZ, infernals, 20.0f)) + return MoveTo(bot->GetMapId(), destX, destY, destZ, false, false, false, true, MovementPriority::MOVEMENT_COMBAT); + } + } + } + return false; +} + +bool KarazhanPrinceMalchezaarRunAwayFromShadowNovaAction::isUseful() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar"); + RaidKarazhanHelpers karazhanHelper(botAI); + std::vector infernals = karazhanHelper.GetSpawnedInfernals(); + if (infernals.empty()) + return false; + + return boss && bot->HasAura(SPELL_ENFEEBLE); +} diff --git a/src/strategy/raids/karazhan/RaidKarazhanActions.h b/src/strategy/raids/karazhan/RaidKarazhanActions.h new file mode 100644 index 00000000..37d69b14 --- /dev/null +++ b/src/strategy/raids/karazhan/RaidKarazhanActions.h @@ -0,0 +1,208 @@ +#ifndef _PLAYERBOT_RAIDKARAZHANACTIONS_H +#define _PLAYERBOT_RAIDKARAZHANACTIONS_H + +#include "AttackAction.h" +#include "RaidKarazhanHelpers.h" + +class KarazhanAttumenTheHuntsmanStackBehindAction : public AttackAction +{ +public: + KarazhanAttumenTheHuntsmanStackBehindAction(PlayerbotAI* botAI, std::string const name = "karazhan attumen the huntsman stack behind") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; + bool isUseful() override; +}; + +class KarazhanMoroesMarkTargetAction : public AttackAction +{ +public: + KarazhanMoroesMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan moroes mark target") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; +}; + +class KarazhanMaidenOfVirtuePositionBossAction : public AttackAction +{ +public: + KarazhanMaidenOfVirtuePositionBossAction(PlayerbotAI* botAI, std::string const name = "karazhan maiden of virtue position boss") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; + bool isUseful() override; +}; + +class KarazhanMaidenOfVirtuePositionRangedAction : public AttackAction +{ +public: + KarazhanMaidenOfVirtuePositionRangedAction(PlayerbotAI* botAI, std::string const name = "karazhan maiden of virtue position ranged") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; + bool isUseful() override; +}; + +class KarazhanBigBadWolfRunAwayAction : public AttackAction +{ +public: + KarazhanBigBadWolfRunAwayAction(PlayerbotAI* botAI, std::string const name = "karazhan big bad wolf run away") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; + bool isUseful() override; + +private: + size_t currentIndex = 0; +}; + +class KarazhanRomuloAndJulianneMarkTargetAction : public AttackAction +{ +public: + KarazhanRomuloAndJulianneMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan romulo and julianne mark target") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; +}; + +class KarazhanWizardOfOzMarkTargetAction : public AttackAction +{ +public: + KarazhanWizardOfOzMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan wizard of oz mark target") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; +}; + +class KarazhanWizardOfOzScorchStrawmanAction : public AttackAction +{ +public: + KarazhanWizardOfOzScorchStrawmanAction(PlayerbotAI* botAI, std::string const name = "karazhan wizard of oz scorch strawman") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; +}; + +class KarazhanTheCuratorMarkTargetAction : public AttackAction +{ +public: + KarazhanTheCuratorMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan the curator mark target") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; +}; + +class KarazhanTheCuratorPositionBossAction : public AttackAction +{ +public: + KarazhanTheCuratorPositionBossAction(PlayerbotAI* botAI, std::string const name = "karazhan the curator position boss") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; + bool isUseful() override; +}; + +class KarazhanTheCuratorSpreadRangedAction : public AttackAction +{ +public: + KarazhanTheCuratorSpreadRangedAction(PlayerbotAI* botAI, std::string const name = "karazhan the curator spread ranged") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; + bool isUseful() override; +}; + +class KarazhanTerestianIllhoofMarkTargetAction : public AttackAction +{ +public: + KarazhanTerestianIllhoofMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan terestian illhoof mark target") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; +}; + +class KarazhanShadeOfAranArcaneExplosionRunAwayAction : public AttackAction +{ +public: + KarazhanShadeOfAranArcaneExplosionRunAwayAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran arcane explosion run away") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; + bool isUseful() override; +}; + +class KarazhanShadeOfAranFlameWreathStopBotAction : public AttackAction +{ +public: + KarazhanShadeOfAranFlameWreathStopBotAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran flame wreath stop bot") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; +}; + +class KarazhanShadeOfAranMarkConjuredElementalAction : public AttackAction +{ +public: + KarazhanShadeOfAranMarkConjuredElementalAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran mark conjured elemental") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; +}; + +class KarazhanShadeOfAranSpreadRangedAction : public AttackAction +{ +public: + KarazhanShadeOfAranSpreadRangedAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran spread ranged") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; + bool isUseful() override; +}; + +class KarazhanNetherspiteBlockRedBeamAction : public AttackAction +{ +public: + KarazhanNetherspiteBlockRedBeamAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite block red beam") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; + bool isUseful() override; +}; + +class KarazhanNetherspiteBlockBlueBeamAction : public AttackAction +{ +public: + KarazhanNetherspiteBlockBlueBeamAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite block blue beam") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; + bool isUseful() override; +}; + +class KarazhanNetherspiteBlockGreenBeamAction : public AttackAction +{ +public: + KarazhanNetherspiteBlockGreenBeamAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite block green beam") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; + bool isUseful() override; +}; + +class KarazhanNetherspiteAvoidBeamAndVoidZoneAction : public AttackAction +{ +public: + KarazhanNetherspiteAvoidBeamAndVoidZoneAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite avoid beam and void zone") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; + bool isUseful() override; +}; + +class KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction : public AttackAction +{ +public: + KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite banish phase avoid void zone") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; + bool isUseful() override; +}; + +class KarazhanPrinceMalchezaarAvoidInfernalAction : public AttackAction +{ +public: + KarazhanPrinceMalchezaarAvoidInfernalAction(PlayerbotAI* botAI, std::string const name = "karazhan prince malchezaar avoid infernal") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; +}; + +class KarazhanPrinceMalchezaarRunAwayFromShadowNovaAction : public AttackAction +{ +public: + KarazhanPrinceMalchezaarRunAwayFromShadowNovaAction(PlayerbotAI* botAI, std::string const name = "karazhan prince malchezaar run away from shadow nova") : AttackAction(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 new file mode 100644 index 00000000..3a1360cc --- /dev/null +++ b/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp @@ -0,0 +1,262 @@ +#include "RaidKarazhanActions.h" +#include "RaidKarazhanHelpers.h" +#include "PlayerbotMgr.h" +#include "AiObjectContext.h" +#include "Position.h" +#include "Spell.h" + +#include +#include + +void RaidKarazhanHelpers::MarkTargetWithSkull(Unit* target) +{ + if (!target) + return; + + if (Group* group = bot->GetGroup()) + { + constexpr uint8_t skullIconId = 7; + ObjectGuid skullGuid = group->GetTargetIcon(skullIconId); + + if (skullGuid != target->GetGUID()) + group->SetTargetIcon(skullIconId, bot->GetGUID(), target->GetGUID()); + } +} + +Unit* RaidKarazhanHelpers::GetFirstAliveUnit(const std::vector& units) +{ + for (Unit* unit : units) + if (unit && unit->IsAlive()) + return unit; + + return nullptr; +} + +Unit* RaidKarazhanHelpers::GetFirstAliveUnitByEntry(uint32 entry) +{ + const GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + + for (const auto& npcGuid : npcs) + { + Unit* unit = botAI->GetUnit(npcGuid); + + if (unit && unit->IsAlive() && unit->GetEntry() == entry) + return unit; + } + return nullptr; +} + +Unit* RaidKarazhanHelpers::GetNearestPlayerInRadius(float radius) +{ + if (Group* group = bot->GetGroup()) + { + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + + if (!member || !member->IsAlive() || member == bot) + continue; + + if (bot->GetExactDist2d(member) < radius) + return member; + } + } + return nullptr; +} + +bool RaidKarazhanHelpers::IsFlameWreathActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran"); + if (!boss) + return false; + + Spell* currentSpell = boss->GetCurrentSpell(CURRENT_GENERIC_SPELL); + if (currentSpell && currentSpell->m_spellInfo && currentSpell->m_spellInfo->Id == SPELL_FLAME_WREATH_CAST) + { + bot->Yell("I will not move when Flame Wreath is cast or the raid blows up.", LANG_UNIVERSAL); + return true; + } + + if (Group* group = bot->GetGroup()) + { + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive()) + continue; + if (member->HasAura(SPELL_FLAME_WREATH_AURA)) + return true; + } + } + return false; +} + +// Blue beam blockers: non-Rogue/Warrior DPS, 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()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member)) + 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() >= 25; + + if (isDps && !isWarrior && !isRogue && !hasExhaustion && !overStack) + { + blueBlockers.push_back(member); + } + } + } + return blueBlockers; +} + +// Green beam blockers: +// (1) Rogues and non-tank Warriors, no Nether Exhaustion Green +// (2) Healers, no Nether Exhaustion Green and <25 stacks of Green Beam debuff +std::vector RaidKarazhanHelpers::GetGreenBlockers() +{ + std::vector greenBlockers; + if (Group* group = bot->GetGroup()) + { + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->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() >= 25; + 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); + } + } + } + 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) + return Position(bx, by, bz); + + 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() +{ + Player* redBlocker = nullptr; + Player* greenBlocker = nullptr; + Player* blueBlocker = nullptr; + std::vector redBlockers; + + if (Group* group = bot->GetGroup()) + { + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member || !member->IsAlive()) + continue; + PlayerbotAI* memberAI = sPlayerbotsMgr->GetPlayerbotAI(member); + if (!memberAI || !memberAI->IsTank(member)) + continue; + if (member->HasAura(SPELL_NETHER_EXHAUSTION_RED)) + continue; + redBlockers.push_back(member); + } + } + + if (!redBlockers.empty()) + redBlocker = redBlockers.front(); + + std::vector greenBlockers = GetGreenBlockers(); + if (!greenBlockers.empty()) + greenBlocker = greenBlockers.front(); + + std::vector blueBlockers = GetBlueBlockers(); + if (!blueBlockers.empty()) + blueBlocker = blueBlockers.front(); + + return std::make_tuple(redBlocker, greenBlocker, blueBlocker); +} + +std::vector RaidKarazhanHelpers::GetAllVoidZones() +{ + std::vector voidZones; + const float radius = 15.0f; + const GuidVector npcs = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); + for (const auto& 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 voidZones; +} + +bool RaidKarazhanHelpers::IsSafePosition(float x, float y, float z, + const std::vector& hazards, float hazardRadius) +{ + for (Unit* hazard : hazards) + { + float dist = std::sqrt(std::pow(x - hazard->GetPositionX(), 2) + std::pow(y - hazard->GetPositionY(), 2)); + + if (dist < hazardRadius) + return false; + } + return true; +} + +std::vector RaidKarazhanHelpers::GetSpawnedInfernals() const +{ + std::vector infernals; + const GuidVector npcs = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); + + for (const auto& npcGuid : npcs) + { + Unit* unit = botAI->GetUnit(npcGuid); + + if (unit && unit->GetEntry() == NPC_NETHERSPITE_INFERNAL) + infernals.push_back(unit); + } + return infernals; +} diff --git a/src/strategy/raids/karazhan/RaidKarazhanHelpers.h b/src/strategy/raids/karazhan/RaidKarazhanHelpers.h new file mode 100644 index 00000000..bb218f45 --- /dev/null +++ b/src/strategy/raids/karazhan/RaidKarazhanHelpers.h @@ -0,0 +1,105 @@ +#ifndef _PLAYERBOT_RAIDKARAZHANHELPERS_H_ +#define _PLAYERBOT_RAIDKARAZHANHELPERS_H_ + +#include "AiObject.h" +#include "Playerbots.h" +#include "MovementActions.h" + +enum KarazhanSpells +{ + // Maiden of Virtue + SPELL_REPENTANCE = 29511, + + // Opera Event + SPELL_LITTLE_RED_RIDING_HOOD = 30756, + SPELL_FEAR = 6215, // Rank 3 + + // Shade of Aran + SPELL_FLAME_WREATH_CAST = 30004, + SPELL_FLAME_WREATH_AURA = 29946, + SPELL_ARCANE_EXPLOSION = 29973, + SPELL_WARLOCK_BANISH = 18647, // Rank 2 + + // 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, + + // Prince Malchezaar + SPELL_ENFEEBLE = 30843, + + // Nightbane + SPELL_CHARRED_EARTH = 30129 +}; + +enum KarazhanNpcs +{ + // Attumen the Huntsman + NPC_ATTUMEN_THE_HUNTSMAN = 16152, // Mounted ID + + // Terestian Illhoof + NPC_KILREK = 17229, + NPC_DEMON_CHAINS = 17248, + + // Shade of Aran + NPC_CONJURED_ELEMENTAL = 17167, + + // Netherspite + NPC_VOID_ZONE = 16697, + NPC_RED_PORTAL = 17369, + NPC_BLUE_PORTAL = 17368, + NPC_GREEN_PORTAL = 17367, + + // Prince Malchezaar + NPC_NETHERSPITE_INFERNAL = 17646, + + // Nightbane + NPC_RESTLESS_SKELETON = 17261, +}; + +const Position KARAZHAN_MAIDEN_OF_VIRTUE_BOSS_POSITION = Position(-10945.881f, -2103.7817f, 92.71163f); +const Position KARAZHAN_MAIDEN_OF_VIRTUE_RANGED_POSITION[8] = +{ + { -10931.178f, -2116.58f, 92.1787f }, + { -10925.828f, -2102.425f, 92.18016f }, + { -10933.089f, -2088.5017f, 92.18028f }, + { -10947.59f, -2082.8147f, 92.18024f }, + { -10960.912f, -2090.4368f, 92.17964f }, + { -10966.017f, -2105.288f, 92.17582f }, + { -10959.242f, -2119.6172f, 92.18062f }, + { -10944.495f, -2123.857f, 92.18021f }, +}; + +const Position KARAZHAN_BIG_BAD_WOLF_RUN_POSITION[4] = { + { -10913.391f, -1773.5083f, 90.47706f }, + { -10875.456f, -1779.0358f, 90.47706f }, + { -10872.281f, -1751.6376f, 90.47716f }, + { -10910.492f, -1747.401f, 90.477165f }, +}; + +const Position KARAZHAN_THE_CURATOR_BOSS_POSITION = Position(-11139.463f, -1884.6451f, 165.76564f); + +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 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; +}; + +#endif diff --git a/src/strategy/raids/karazhan/RaidKarazhanStrategy.cpp b/src/strategy/raids/karazhan/RaidKarazhanStrategy.cpp new file mode 100644 index 00000000..79544687 --- /dev/null +++ b/src/strategy/raids/karazhan/RaidKarazhanStrategy.cpp @@ -0,0 +1,76 @@ +#include "RaidKarazhanStrategy.h" + +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))); + + triggers.push_back(new TriggerNode( + "karazhan moroes", NextAction::array(0, + new NextAction("karazhan moroes mark target", ACTION_RAID + 1), + 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))); + + triggers.push_back(new TriggerNode( + "karazhan big bad wolf", NextAction::array(0, + new NextAction("karazhan big bad wolf run away", ACTION_EMERGENCY + 6), + 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))); + + 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))); + + 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))); + + triggers.push_back(new TriggerNode( + "karazhan terestian illhoof", NextAction::array(0, + new NextAction("karazhan terestian illhoof mark target", ACTION_RAID + 1), + nullptr))); + + triggers.push_back(new TriggerNode( + "karazhan shade of aran", NextAction::array(0, + new NextAction("karazhan shade of aran flame wreath stop bot", 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))); + + 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))); + + triggers.push_back(new TriggerNode( + "karazhan prince malchezaar", NextAction::array(0, + new NextAction("karazhan prince malchezaar run away from shadow nova", ACTION_EMERGENCY + 6), + new NextAction("karazhan prince malchezaar avoid infernal", ACTION_RAID + 1), + nullptr))); +} + +void RaidKarazhanStrategy::InitMultipliers(std::vector& /*multipliers*/) +{ + // No multipliers for this strategy +} diff --git a/src/strategy/raids/karazhan/RaidKarazhanStrategy.h b/src/strategy/raids/karazhan/RaidKarazhanStrategy.h new file mode 100644 index 00000000..070259b4 --- /dev/null +++ b/src/strategy/raids/karazhan/RaidKarazhanStrategy.h @@ -0,0 +1,17 @@ +#ifndef _PLAYERBOT_RAIDKARAZHANSTRATEGY_H_ +#define _PLAYERBOT_RAIDKARAZHANSTRATEGY_H_ + +#include "Strategy.h" + +class RaidKarazhanStrategy : public Strategy +{ +public: + RaidKarazhanStrategy(PlayerbotAI* ai) : Strategy(ai) {} + + std::string const getName() override { return "karazhan"; } + + void InitTriggers(std::vector& /*triggers*/) override; + void InitMultipliers(std::vector& /*multipliers*/) override; +}; + +#endif diff --git a/src/strategy/raids/karazhan/RaidKarazhanTriggerContext.h b/src/strategy/raids/karazhan/RaidKarazhanTriggerContext.h new file mode 100644 index 00000000..4451212c --- /dev/null +++ b/src/strategy/raids/karazhan/RaidKarazhanTriggerContext.h @@ -0,0 +1,39 @@ +#ifndef _PLAYERBOT_RAIDKARAZHANTRIGGERCONTEXT_H +#define _PLAYERBOT_RAIDKARAZHANTRIGGERCONTEXT_H + +#include "AiObjectContext.h" +#include "RaidKarazhanTriggers.h" + +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; + } + +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); } +}; + +#endif diff --git a/src/strategy/raids/karazhan/RaidKarazhanTriggers.cpp b/src/strategy/raids/karazhan/RaidKarazhanTriggers.cpp new file mode 100644 index 00000000..206ae531 --- /dev/null +++ b/src/strategy/raids/karazhan/RaidKarazhanTriggers.cpp @@ -0,0 +1,134 @@ +#include "Playerbots.h" +#include "RaidKarazhanTriggers.h" +#include "RaidKarazhanHelpers.h" +#include "RaidKarazhanActions.h" + +bool KarazhanAttumenTheHuntsmanTrigger::IsActive() +{ + RaidKarazhanHelpers helpers(botAI); + Unit* boss = helpers.GetFirstAliveUnitByEntry(NPC_ATTUMEN_THE_HUNTSMAN); + return boss != nullptr; +} + +bool KarazhanMoroesTrigger::IsActive() +{ + Unit* moroes = AI_VALUE2(Unit*, "find target", "moroes"); + 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"); + + if ((!moroes || !moroes->IsAlive()) && + (!dorothea || !dorothea->IsAlive()) && + (!catriona || !catriona->IsAlive()) && + (!keira || !keira->IsAlive()) && + (!rafe || !rafe->IsAlive()) && + (!robin || !robin->IsAlive()) && + (!crispin || !crispin->IsAlive())) + return false; + + return true; +} + +bool KarazhanMaidenOfVirtueTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "maiden of virtue"); + + if (!boss || !boss->IsAlive()) + return false; + + return true; +} + +bool KarazhanBigBadWolfTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "the big bad wolf"); + + if (!boss || !boss->IsAlive()) + return false; + + return true; +} + +bool KarazhanRomuloAndJulianneTrigger::IsActive() +{ + Unit* julianne = AI_VALUE2(Unit*, "find target", "julianne"); + Unit* romulo = AI_VALUE2(Unit*, "find target", "romulo"); + + if (!julianne || !julianne->IsAlive() || !romulo || !romulo->IsAlive()) + return false; + + return true; +} + +bool KarazhanWizardOfOzTrigger::IsActive() +{ + 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"); + + if (( !dorothee || !dorothee->IsAlive() ) && + ( !tito || !tito->IsAlive() ) && + ( !roar || !roar->IsAlive() ) && + ( !strawman || !strawman->IsAlive() ) && + ( !tinhead || !tinhead->IsAlive() ) && + ( !crone || !crone->IsAlive() )) + return false; + + return true; +} + +bool KarazhanTheCuratorTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "the curator"); + + if (!boss || !boss->IsAlive()) + return false; + + return true; +} + +bool KarazhanTerestianIllhoofTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "terestian illhoof"); + + if (!boss || !boss->IsAlive()) + return false; + + return true; +} + +bool KarazhanShadeOfAranTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran"); + + if (!boss) + return false; + + return true; +} + +bool KarazhanNetherspiteTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); + + if (!boss || !boss->IsAlive()) + return false; + + return true; +} + +bool KarazhanPrinceMalchezaarTrigger::IsActive() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar"); + + if (!boss || !boss->IsAlive()) + return false; + + return true; +} diff --git a/src/strategy/raids/karazhan/RaidKarazhanTriggers.h b/src/strategy/raids/karazhan/RaidKarazhanTriggers.h new file mode 100644 index 00000000..69f44152 --- /dev/null +++ b/src/strategy/raids/karazhan/RaidKarazhanTriggers.h @@ -0,0 +1,83 @@ +#ifndef _PLAYERBOT_RAIDKARAZHANTRIGGERS_H +#define _PLAYERBOT_RAIDKARAZHANTRIGGERS_H + +#include "Trigger.h" + +class KarazhanAttumenTheHuntsmanTrigger : public Trigger +{ +public: + KarazhanAttumenTheHuntsmanTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan attumen the huntsman") {} + bool IsActive() override; +}; + +class KarazhanMoroesTrigger : public Trigger +{ +public: + KarazhanMoroesTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan moroes") {} + bool IsActive() override; +}; + +class KarazhanMaidenOfVirtueTrigger : public Trigger +{ +public: + KarazhanMaidenOfVirtueTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan maiden of virtue") {} + bool IsActive() override; +}; + +class KarazhanBigBadWolfTrigger : public Trigger +{ +public: + KarazhanBigBadWolfTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan big bad wolf") {} + bool IsActive() override; +}; + +class KarazhanRomuloAndJulianneTrigger : public Trigger +{ +public: + KarazhanRomuloAndJulianneTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan romulo and julianne") {} + bool IsActive() override; +}; + +class KarazhanWizardOfOzTrigger : public Trigger +{ +public: + KarazhanWizardOfOzTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan wizard of oz") {} + bool IsActive() override; +}; + +class KarazhanTheCuratorTrigger : public Trigger +{ +public: + KarazhanTheCuratorTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan the curator") {} + bool IsActive() override; +}; + +class KarazhanTerestianIllhoofTrigger : public Trigger +{ +public: + KarazhanTerestianIllhoofTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan terestian illhoof") {} + bool IsActive() override; +}; + +class KarazhanShadeOfAranTrigger : public Trigger +{ +public: + KarazhanShadeOfAranTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan shade of aran") {} + bool IsActive() override; +}; + +class KarazhanNetherspiteTrigger : public Trigger +{ +public: + KarazhanNetherspiteTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan netherspite") {} + bool IsActive() override; +}; + +class KarazhanPrinceMalchezaarTrigger : public Trigger +{ +public: + KarazhanPrinceMalchezaarTrigger(PlayerbotAI* botAI) : Trigger(botAI, "karazhan prince malchezaar") {} + bool IsActive() override; +}; + +#endif