From 19399c6f570a011397f9d7bcd8e7ea2dcd53a680 Mon Sep 17 00:00:00 2001 From: Revision Date: Sun, 20 Jul 2025 23:13:14 +0200 Subject: [PATCH 1/9] Implement Karazhan strategy --- conf/playerbots.conf.dist | 2 +- src/PlayerbotAI.cpp | 3 + src/factory/PlayerbotFactory.cpp | 3 - src/strategy/AiObjectContext.cpp | 23 + src/strategy/raids/RaidStrategyContext.h | 3 + .../karazhan/RaidKarazhanActionContext.h | 85 ++ .../raids/karazhan/RaidKarazhanActions.cpp | 1026 +++++++++++++++++ .../raids/karazhan/RaidKarazhanActions.h | 208 ++++ .../raids/karazhan/RaidKarazhanHelpers.cpp | 262 +++++ .../raids/karazhan/RaidKarazhanHelpers.h | 105 ++ .../raids/karazhan/RaidKarazhanStrategy.cpp | 76 ++ .../raids/karazhan/RaidKarazhanStrategy.h | 17 + .../karazhan/RaidKarazhanTriggerContext.h | 39 + .../raids/karazhan/RaidKarazhanTriggers.cpp | 134 +++ .../raids/karazhan/RaidKarazhanTriggers.h | 83 ++ 15 files changed, 2065 insertions(+), 4 deletions(-) create mode 100644 src/strategy/raids/karazhan/RaidKarazhanActionContext.h create mode 100644 src/strategy/raids/karazhan/RaidKarazhanActions.cpp create mode 100644 src/strategy/raids/karazhan/RaidKarazhanActions.h create mode 100644 src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp create mode 100644 src/strategy/raids/karazhan/RaidKarazhanHelpers.h create mode 100644 src/strategy/raids/karazhan/RaidKarazhanStrategy.cpp create mode 100644 src/strategy/raids/karazhan/RaidKarazhanStrategy.h create mode 100644 src/strategy/raids/karazhan/RaidKarazhanTriggerContext.h create mode 100644 src/strategy/raids/karazhan/RaidKarazhanTriggers.cpp create mode 100644 src/strategy/raids/karazhan/RaidKarazhanTriggers.h 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 From fe9791e1ec180a0492b92f65a3626f0f225b628e Mon Sep 17 00:00:00 2001 From: crow Date: Tue, 9 Sep 2025 22:45:20 -0500 Subject: [PATCH 2/9] Revert edit not intended for PR --- src/factory/PlayerbotFactory.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/factory/PlayerbotFactory.cpp b/src/factory/PlayerbotFactory.cpp index d789bd21..585f2633 100644 --- a/src/factory/PlayerbotFactory.cpp +++ b/src/factory/PlayerbotFactory.cpp @@ -2346,6 +2346,9 @@ 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); From 311bf32da57481d38d17bc9229421622ecb6b43f Mon Sep 17 00:00:00 2001 From: crow Date: Wed, 10 Sep 2025 01:05:56 -0500 Subject: [PATCH 3/9] Update Shadow Nova action Fix error with isUseful check that caused bots not to run away from Shadow Nova when no Infernals were spawned + some tightening of code to avoid Shadow Nova --- src/strategy/raids/karazhan/RaidKarazhanActions.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/strategy/raids/karazhan/RaidKarazhanActions.cpp b/src/strategy/raids/karazhan/RaidKarazhanActions.cpp index 41ecc0bf..799e7476 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanActions.cpp +++ b/src/strategy/raids/karazhan/RaidKarazhanActions.cpp @@ -974,8 +974,8 @@ bool KarazhanPrinceMalchezaarRunAwayFromShadowNovaAction::Execute(Event event) float currentBossDistance = bot->GetDistance2d(boss); if (currentBossDistance < safeBossDistance) { - const float stepSize = 2.0f; - const int numAngles = 16; + const float stepSize = 0.5f; + const int numAngles = 64; for (int i = 0; i < numAngles; ++i) { float angle = (2 * M_PI * i) / numAngles; @@ -1007,7 +1007,7 @@ bool KarazhanPrinceMalchezaarRunAwayFromShadowNovaAction::Execute(Event event) 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 MoveTo(bot->GetMapId(), destX, destY, destZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED); } } } @@ -1017,10 +1017,6 @@ bool KarazhanPrinceMalchezaarRunAwayFromShadowNovaAction::Execute(Event event) 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); } From e60876a1cb3b67688eaf9f340f40f5ae27b4396b Mon Sep 17 00:00:00 2001 From: crow Date: Fri, 12 Sep 2025 08:46:17 -0500 Subject: [PATCH 4/9] Update Aran, Netherspite, Prince --- .../karazhan/RaidKarazhanActionContext.h | 16 +- .../raids/karazhan/RaidKarazhanActions.cpp | 345 ++++++++++++------ .../raids/karazhan/RaidKarazhanActions.h | 97 ++--- .../raids/karazhan/RaidKarazhanHelpers.cpp | 94 ++++- .../raids/karazhan/RaidKarazhanHelpers.h | 7 +- .../karazhan/RaidKarazhanMultipliers.cpp | 183 ++++++++++ .../raids/karazhan/RaidKarazhanMultipliers.h | 34 ++ .../raids/karazhan/RaidKarazhanStrategy.cpp | 14 +- .../raids/karazhan/RaidKarazhanStrategy.h | 5 +- .../karazhan/RaidKarazhanTriggerContext.h | 2 +- .../raids/karazhan/RaidKarazhanTriggers.cpp | 2 +- 11 files changed, 607 insertions(+), 192 deletions(-) create mode 100644 src/strategy/raids/karazhan/RaidKarazhanMultipliers.cpp create mode 100644 src/strategy/raids/karazhan/RaidKarazhanMultipliers.h diff --git a/src/strategy/raids/karazhan/RaidKarazhanActionContext.h b/src/strategy/raids/karazhan/RaidKarazhanActionContext.h index f58fe4b5..ac338cba 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanActionContext.h +++ b/src/strategy/raids/karazhan/RaidKarazhanActionContext.h @@ -1,10 +1,8 @@ #ifndef _PLAYERBOT_RAIDKARAZHANACTIONS_CONTEXT_H #define _PLAYERBOT_RAIDKARAZHANACTIONS_CONTEXT_H -#include "Action.h" -#include "NamedObjectContext.h" #include "RaidKarazhanActions.h" - +#include "NamedObjectContext.h" class RaidKarazhanActionContext : public NamedObjectContext { @@ -32,7 +30,7 @@ public: 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 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; @@ -42,8 +40,8 @@ public: 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; + 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; } private: @@ -68,7 +66,7 @@ private: 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_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); } @@ -78,8 +76,8 @@ private: 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); } + 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); } }; #endif diff --git a/src/strategy/raids/karazhan/RaidKarazhanActions.cpp b/src/strategy/raids/karazhan/RaidKarazhanActions.cpp index 799e7476..5f0e6064 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanActions.cpp +++ b/src/strategy/raids/karazhan/RaidKarazhanActions.cpp @@ -1,14 +1,9 @@ -#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 "Playerbots.h" #include "PlayerbotMgr.h" #include "PlayerbotAI.h" -#include "MovementActions.h" namespace { @@ -344,8 +339,6 @@ bool KarazhanTerestianIllhoofMarkTargetAction::Execute(Event event) 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); @@ -356,64 +349,30 @@ bool KarazhanShadeOfAranArcaneExplosionRunAwayAction::Execute(Event event) { 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; + return boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION); } -bool KarazhanShadeOfAranFlameWreathStopBotAction::Execute(Event event) +bool KarazhanShadeOfAranFlameWreathStopMovementAction::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 true; } return false; } @@ -422,11 +381,11 @@ bool KarazhanShadeOfAranMarkConjuredElementalAction::Execute(Event event) { RaidKarazhanHelpers karazhanHelper(botAI); Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran"); - if (!boss || !boss->IsAlive() || karazhanHelper.IsFlameWreathActive()) + if (!boss || !boss->IsAlive()) return false; Unit* target = karazhanHelper.GetFirstAliveUnitByEntry(NPC_CONJURED_ELEMENTAL); - if (!target || target->HasAura(SPELL_WARLOCK_BANISH) || !target->IsAlive()) + if (!target || !target->IsAlive() || target->HasAura(SPELL_WARLOCK_BANISH)) { return false; } @@ -474,7 +433,8 @@ bool KarazhanShadeOfAranSpreadRangedAction::isUseful() RaidKarazhanHelpers karazhanHelper(botAI); - return botAI->IsRanged(bot) && !karazhanHelper.IsFlameWreathActive() && !(boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION)); + 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) @@ -524,7 +484,8 @@ bool KarazhanNetherspiteBlockRedBeamAction::Execute(Event event) } if (!lastBeamMoveSideways[botGuid]) { - return MoveTo(bot->GetMapId(), beamPos.GetPositionX(), beamPos.GetPositionY(), beamPos.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_FORCED); + return MoveTo(bot->GetMapId(), beamPos.GetPositionX(), beamPos.GetPositionY(), beamPos.GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_FORCED); } else { @@ -654,7 +615,8 @@ bool KarazhanNetherspiteBlockBlueBeamAction::Execute(Event event) } if (found) { - return MoveTo(bot->GetMapId(), bestPos.GetPositionX(), bestPos.GetPositionY(), bestPos.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_FORCED); + return MoveTo(bot->GetMapId(), bestPos.GetPositionX(), bestPos.GetPositionY(), bestPos.GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_FORCED); } return false; } @@ -744,7 +706,8 @@ bool KarazhanNetherspiteBlockGreenBeamAction::Execute(Event event) } if (found) { - return MoveTo(bot->GetMapId(), bestPos.GetPositionX(), bestPos.GetPositionY(), bestPos.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_FORCED); + return MoveTo(bot->GetMapId(), bestPos.GetPositionX(), bestPos.GetPositionY(), bestPos.GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_FORCED); } return false; } @@ -881,10 +844,9 @@ bool KarazhanNetherspiteAvoidBeamAndVoidZoneAction::Execute(Event event) } 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; } @@ -910,9 +872,7 @@ bool KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction::Execute(Event event) for (Unit* vz : voidZones) { if (vz->GetEntry() == NPC_VOID_ZONE && bot->GetExactDist2d(vz) < 4.0f) - { return FleePosition(vz->GetPosition(), 4.0f); - } } return false; } @@ -928,95 +888,252 @@ bool KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction::isUseful() 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) +// For Enfeebled bots to avoid Shadow Nova and all non-tank bots to avoid infernals +bool KarazhanPrinceMalchezaarNonTankAvoidHazardAction::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 minSafeBossDistance = 35.0f; + const float maxSafeBossDistance = 40.0f; + const float safeInfernalDistance = 22.0f; + const float stepSize = 0.5f; + const int numAngles = 64; + float bx = bot->GetPositionX(); + float by = bot->GetPositionY(); + float bz = bot->GetPositionZ(); + float bossX = boss->GetPositionX(); + float bossY = boss->GetPositionY(); + float bossZ = boss->GetPositionZ(); + float bestMoveDist = std::numeric_limits::max(); + float bestDestX = 0.0f, bestDestY = 0.0f, bestDestZ = bz; + bool found = false; + + if (bot->HasAura(SPELL_ENFEEBLE)) { - const float stepSize = 0.5f; - const int numAngles = 64; 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) + for (float dist = minSafeBossDistance; dist <= maxSafeBossDistance; dist += stepSize) { - float x = bot->GetPositionX() + dx * dist; - float y = bot->GetPositionY() + dy * dist; - for (Unit* infernal : infernals) + float x = bossX + dx * dist; + float y = bossY + dy * dist; + float destZ = bossZ; + if (!bot->IsWithinLOS(x, y, destZ)) + continue; + bool pathSafe = karazhanHelper.IsStraightPathSafe(Position(bx, by, bz), Position(x, y, destZ), infernals, safeInfernalDistance, stepSize); + float moveDist = sqrt(pow(x - bx, 2) + pow(y - by, 2)); + if (pathSafe && moveDist < bestMoveDist) { - float infernalDist = sqrt(pow(x - infernal->GetPositionX(), 2) + pow(y - infernal->GetPositionY(), 2)); - if (infernalDist < safeInfernalDistance) + bestMoveDist = moveDist; + bestDestX = x; + bestDestY = y; + bestDestZ = destZ; + found = true; + } + } + } + if (found) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(false); + + return MoveTo(bot->GetMapId(), bestDestX, bestDestY, bestDestZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED); + } + return false; + } + + if (!bot->HasAura(SPELL_ENFEEBLE)) + { + bool nearInfernal = false; + for (Unit* infernal : infernals) + { + float infernalDist = sqrt(pow(bx - infernal->GetPositionX(), 2) + pow(by - infernal->GetPositionY(), 2)); + if (infernalDist < safeInfernalDistance) + { + nearInfernal = true; + break; + } + } + if (nearInfernal) + { + float bestMoveDist = std::numeric_limits::max(); + float bestDestX = bx, bestDestY = by, bestDestZ = bz; + bool found = false; + bool usedArc = false; + for (int i = 0; i < numAngles; ++i) + { + float angle = (2 * M_PI * i) / numAngles; + float dx = cos(angle); + float dy = sin(angle); + for (float dist = stepSize; dist <= 35.0f; dist += stepSize) + { + float x = bossX + dx * dist; + float y = bossY + dy * dist; + float destZ = bossZ; + if (!bot->IsWithinLOS(x, y, destZ)) + continue; + bool pathSafe = karazhanHelper.IsStraightPathSafe(Position(bx, by, bz), Position(x, y, destZ), infernals, safeInfernalDistance, stepSize); + float moveDist = sqrt(pow(x - bx, 2) + pow(y - by, 2)); + if (pathSafe && moveDist < bestMoveDist) { - pathIsSafe = false; - break; + bestMoveDist = moveDist; + bestDestX = x; + bestDestY = y; + bestDestZ = destZ; + found = true; + usedArc = false; + } + if (!pathSafe) + { + Position arcPoint = karazhanHelper.CalculateArcPoint(Position(bx, by, bz), Position(x, y, destZ), Position(bossX, bossY, bossZ)); + if (!bot->IsWithinLOS(arcPoint.GetPositionX(), arcPoint.GetPositionY(), arcPoint.GetPositionZ())) + continue; + bool arcSafe = true; + for (Unit* infernal : infernals) + { + float infernalDist = sqrt(pow(arcPoint.GetPositionX() - infernal->GetPositionX(), 2) + pow(arcPoint.GetPositionY() - infernal->GetPositionY(), 2)); + if (infernalDist < safeInfernalDistance) + { + arcSafe = false; + break; + } + } + float arcMoveDist = sqrt(pow(arcPoint.GetPositionX() - bx, 2) + pow(arcPoint.GetPositionY() - by, 2)); + if (arcSafe && arcMoveDist < bestMoveDist) + { + bestMoveDist = arcMoveDist; + bestDestX = arcPoint.GetPositionX(); + bestDestY = arcPoint.GetPositionY(); + bestDestZ = arcPoint.GetPositionZ(); + found = true; + usedArc = true; + } } } - if (!pathIsSafe) - break; } - if (pathIsSafe) + if (found) { - 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_FORCED); + + return MoveTo(bot->GetMapId(), bestDestX, bestDestY, bestDestZ, false, false, false, true, MovementPriority::MOVEMENT_COMBAT); } } } return false; } -bool KarazhanPrinceMalchezaarRunAwayFromShadowNovaAction::isUseful() +bool KarazhanPrinceMalchezaarNonTankAvoidHazardAction::isUseful() { Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar"); - - return boss && bot->HasAura(SPELL_ENFEEBLE); + + return boss && !(botAI->IsTank(bot) && botAI->HasAggro(boss) && boss->GetVictim() == bot); +} + +// For tank to avoid infernals (with buffer distance) +bool KarazhanPrinceMalchezaarTankAvoidHazardAction::Execute(Event event) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar"); + RaidKarazhanHelpers karazhanHelper(botAI); + std::vector infernals = karazhanHelper.GetSpawnedInfernals(); + + const float safeInfernalDistance = 30.0f; + const float stepSize = 0.5f; + const int numAngles = 64; + const float maxSampleDist = 60.0f; + float bx = bot->GetPositionX(); + float by = bot->GetPositionY(); + float bz = bot->GetPositionZ(); + float bestMoveDist = std::numeric_limits::max(); + float bestDestX = bx, bestDestY = by, bestDestZ = bz; + bool found = false; + bool usedArc = false; + + bool nearInfernal = false; + for (Unit* infernal : infernals) + { + float infernalDist = sqrt(pow(bx - infernal->GetPositionX(), 2) + pow(by - infernal->GetPositionY(), 2)); + if (infernalDist < safeInfernalDistance) + { + nearInfernal = true; + break; + } + } + if (nearInfernal) + { + for (int i = 0; i < numAngles; ++i) + { + float angle = (2 * M_PI * i) / numAngles; + float dx = cos(angle); + float dy = sin(angle); + for (float dist = stepSize; dist <= maxSampleDist; dist += stepSize) + { + float x = bx + dx * dist; + float y = by + dy * dist; + float z = bz; + if (!bot->IsWithinLOS(x, y, z)) + continue; + bool safe = karazhanHelper.IsStraightPathSafe(Position(bx, by, bz), Position(x, y, z), infernals, safeInfernalDistance, stepSize); + float moveDist = sqrt(pow(x - bx, 2) + pow(y - by, 2)); + if (safe && moveDist < bestMoveDist) + { + bestMoveDist = moveDist; + bestDestX = x; + bestDestY = y; + bestDestZ = z; + found = true; + usedArc = false; + } + if (!safe) + { + Position arcPoint = karazhanHelper.CalculateArcPoint(Position(bx, by, bz), Position(x, y, z), Position(bx, by, bz)); + if (!bot->IsWithinLOS(arcPoint.GetPositionX(), arcPoint.GetPositionY(), arcPoint.GetPositionZ())) + continue; + bool arcSafe = true; + for (Unit* infernal : infernals) + { + float infernalDist = sqrt(pow(arcPoint.GetPositionX() - infernal->GetPositionX(), 2) + pow(arcPoint.GetPositionY() - infernal->GetPositionY(), 2)); + if (infernalDist < safeInfernalDistance) + { + arcSafe = false; + break; + } + } + float arcMoveDist = sqrt(pow(arcPoint.GetPositionX() - bx, 2) + pow(arcPoint.GetPositionY() - by, 2)); + if (arcSafe && arcMoveDist < bestMoveDist) + { + bestMoveDist = arcMoveDist; + bestDestX = arcPoint.GetPositionX(); + bestDestY = arcPoint.GetPositionY(); + bestDestZ = arcPoint.GetPositionZ(); + found = true; + usedArc = true; + } + } + } + } + if (found) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(false); + return MoveTo(bot->GetMapId(), bestDestX, bestDestY, bestDestZ, false, false, false, true, MovementPriority::MOVEMENT_COMBAT); + } + } + return false; +} + +bool KarazhanPrinceMalchezaarTankAvoidHazardAction::isUseful() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar"); + + return boss && botAI->IsTank(bot) && botAI->HasAggro(boss) && boss->GetVictim() == bot; } diff --git a/src/strategy/raids/karazhan/RaidKarazhanActions.h b/src/strategy/raids/karazhan/RaidKarazhanActions.h index 37d69b14..c9b34d03 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanActions.h +++ b/src/strategy/raids/karazhan/RaidKarazhanActions.h @@ -1,48 +1,48 @@ #ifndef _PLAYERBOT_RAIDKARAZHANACTIONS_H #define _PLAYERBOT_RAIDKARAZHANACTIONS_H -#include "AttackAction.h" -#include "RaidKarazhanHelpers.h" +#include "Action.h" +#include "MovementActions.h" -class KarazhanAttumenTheHuntsmanStackBehindAction : public AttackAction +class KarazhanAttumenTheHuntsmanStackBehindAction : public MovementAction { public: - KarazhanAttumenTheHuntsmanStackBehindAction(PlayerbotAI* botAI, std::string const name = "karazhan attumen the huntsman stack behind") : AttackAction(botAI, name) {} + 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 AttackAction +class KarazhanMoroesMarkTargetAction : public Action { public: - KarazhanMoroesMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan moroes mark target") : AttackAction(botAI, name) {} + KarazhanMoroesMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan moroes mark target") : Action(botAI, name) {} bool Execute(Event event) override; }; -class KarazhanMaidenOfVirtuePositionBossAction : public AttackAction +class KarazhanMaidenOfVirtuePositionBossAction : public MovementAction { public: - KarazhanMaidenOfVirtuePositionBossAction(PlayerbotAI* botAI, std::string const name = "karazhan maiden of virtue position boss") : AttackAction(botAI, name) {} + KarazhanMaidenOfVirtuePositionBossAction(PlayerbotAI* botAI, std::string const name = "karazhan maiden of virtue position boss") : MovementAction(botAI, name) {} bool Execute(Event event) override; bool isUseful() override; }; -class KarazhanMaidenOfVirtuePositionRangedAction : public AttackAction +class KarazhanMaidenOfVirtuePositionRangedAction : public MovementAction { public: - KarazhanMaidenOfVirtuePositionRangedAction(PlayerbotAI* botAI, std::string const name = "karazhan maiden of virtue position ranged") : AttackAction(botAI, name) {} + KarazhanMaidenOfVirtuePositionRangedAction(PlayerbotAI* botAI, std::string const name = "karazhan maiden of virtue position ranged") : MovementAction(botAI, name) {} bool Execute(Event event) override; bool isUseful() override; }; -class KarazhanBigBadWolfRunAwayAction : public AttackAction +class KarazhanBigBadWolfRunAwayAction : public MovementAction { public: - KarazhanBigBadWolfRunAwayAction(PlayerbotAI* botAI, std::string const name = "karazhan big bad wolf run away") : AttackAction(botAI, name) {} + KarazhanBigBadWolfRunAwayAction(PlayerbotAI* botAI, std::string const name = "karazhan big bad wolf run away") : MovementAction(botAI, name) {} bool Execute(Event event) override; bool isUseful() override; @@ -51,155 +51,156 @@ private: size_t currentIndex = 0; }; -class KarazhanRomuloAndJulianneMarkTargetAction : public AttackAction +class KarazhanRomuloAndJulianneMarkTargetAction : public Action { public: - KarazhanRomuloAndJulianneMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan romulo and julianne mark target") : AttackAction(botAI, name) {} + KarazhanRomuloAndJulianneMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan romulo and julianne mark target") : Action(botAI, name) {} bool Execute(Event event) override; }; -class KarazhanWizardOfOzMarkTargetAction : public AttackAction +class KarazhanWizardOfOzMarkTargetAction : public Action { public: - KarazhanWizardOfOzMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan wizard of oz mark target") : AttackAction(botAI, name) {} + KarazhanWizardOfOzMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan wizard of oz mark target") : Action(botAI, name) {} bool Execute(Event event) override; }; -class KarazhanWizardOfOzScorchStrawmanAction : public AttackAction +class KarazhanWizardOfOzScorchStrawmanAction : public Action { public: - KarazhanWizardOfOzScorchStrawmanAction(PlayerbotAI* botAI, std::string const name = "karazhan wizard of oz scorch strawman") : AttackAction(botAI, name) {} + KarazhanWizardOfOzScorchStrawmanAction(PlayerbotAI* botAI, std::string const name = "karazhan wizard of oz scorch strawman") : Action(botAI, name) {} bool Execute(Event event) override; }; -class KarazhanTheCuratorMarkTargetAction : public AttackAction +class KarazhanTheCuratorMarkTargetAction : public Action { public: - KarazhanTheCuratorMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan the curator mark target") : AttackAction(botAI, name) {} + KarazhanTheCuratorMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan the curator mark target") : Action(botAI, name) {} bool Execute(Event event) override; }; -class KarazhanTheCuratorPositionBossAction : public AttackAction +class KarazhanTheCuratorPositionBossAction : public MovementAction { public: - KarazhanTheCuratorPositionBossAction(PlayerbotAI* botAI, std::string const name = "karazhan the curator position boss") : AttackAction(botAI, name) {} + 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 AttackAction +class KarazhanTheCuratorSpreadRangedAction : public MovementAction { public: - KarazhanTheCuratorSpreadRangedAction(PlayerbotAI* botAI, std::string const name = "karazhan the curator spread ranged") : AttackAction(botAI, name) {} + 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 AttackAction +class KarazhanTerestianIllhoofMarkTargetAction : public Action { public: - KarazhanTerestianIllhoofMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan terestian illhoof mark target") : AttackAction(botAI, name) {} + KarazhanTerestianIllhoofMarkTargetAction(PlayerbotAI* botAI, std::string const name = "karazhan terestian illhoof mark target") : Action(botAI, name) {} bool Execute(Event event) override; }; -class KarazhanShadeOfAranArcaneExplosionRunAwayAction : public AttackAction +class KarazhanShadeOfAranArcaneExplosionRunAwayAction : public MovementAction { public: - KarazhanShadeOfAranArcaneExplosionRunAwayAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran arcane explosion run away") : AttackAction(botAI, name) {} + 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 KarazhanShadeOfAranFlameWreathStopBotAction : public AttackAction +class KarazhanShadeOfAranFlameWreathStopMovementAction : public MovementAction { public: - KarazhanShadeOfAranFlameWreathStopBotAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran flame wreath stop bot") : AttackAction(botAI, name) {} + KarazhanShadeOfAranFlameWreathStopMovementAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran flame wreath stop bot") : MovementAction(botAI, name) {} bool Execute(Event event) override; }; -class KarazhanShadeOfAranMarkConjuredElementalAction : public AttackAction +class KarazhanShadeOfAranMarkConjuredElementalAction : public Action { public: - KarazhanShadeOfAranMarkConjuredElementalAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran mark conjured elemental") : AttackAction(botAI, name) {} + KarazhanShadeOfAranMarkConjuredElementalAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran mark conjured elemental") : Action(botAI, name) {} bool Execute(Event event) override; }; -class KarazhanShadeOfAranSpreadRangedAction : public AttackAction +class KarazhanShadeOfAranSpreadRangedAction : public MovementAction { public: - KarazhanShadeOfAranSpreadRangedAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran spread ranged") : AttackAction(botAI, name) {} + KarazhanShadeOfAranSpreadRangedAction(PlayerbotAI* botAI, std::string const name = "karazhan shade of aran spread ranged") : MovementAction(botAI, name) {} bool Execute(Event event) override; bool isUseful() override; }; -class KarazhanNetherspiteBlockRedBeamAction : public AttackAction +class KarazhanNetherspiteBlockRedBeamAction : public MovementAction { public: - KarazhanNetherspiteBlockRedBeamAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite block red beam") : AttackAction(botAI, name) {} + KarazhanNetherspiteBlockRedBeamAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite block red beam") : MovementAction(botAI, name) {} bool Execute(Event event) override; bool isUseful() override; }; -class KarazhanNetherspiteBlockBlueBeamAction : public AttackAction +class KarazhanNetherspiteBlockBlueBeamAction : public MovementAction { public: - KarazhanNetherspiteBlockBlueBeamAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite block blue beam") : AttackAction(botAI, name) {} + KarazhanNetherspiteBlockBlueBeamAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite block blue beam") : MovementAction(botAI, name) {} bool Execute(Event event) override; bool isUseful() override; }; -class KarazhanNetherspiteBlockGreenBeamAction : public AttackAction +class KarazhanNetherspiteBlockGreenBeamAction : public MovementAction { public: - KarazhanNetherspiteBlockGreenBeamAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite block green beam") : AttackAction(botAI, name) {} + KarazhanNetherspiteBlockGreenBeamAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite block green beam") : MovementAction(botAI, name) {} bool Execute(Event event) override; bool isUseful() override; }; -class KarazhanNetherspiteAvoidBeamAndVoidZoneAction : public AttackAction +class KarazhanNetherspiteAvoidBeamAndVoidZoneAction : public MovementAction { public: - KarazhanNetherspiteAvoidBeamAndVoidZoneAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite avoid beam and void zone") : AttackAction(botAI, name) {} + 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 AttackAction +class KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction : public MovementAction { public: - KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction(PlayerbotAI* botAI, std::string const name = "karazhan netherspite banish phase avoid void zone") : AttackAction(botAI, name) {} + 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 KarazhanPrinceMalchezaarAvoidInfernalAction : public AttackAction +class KarazhanPrinceMalchezaarNonTankAvoidHazardAction : public MovementAction { public: - KarazhanPrinceMalchezaarAvoidInfernalAction(PlayerbotAI* botAI, std::string const name = "karazhan prince malchezaar avoid infernal") : AttackAction(botAI, name) {} + 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 KarazhanPrinceMalchezaarRunAwayFromShadowNovaAction : public AttackAction +class KarazhanPrinceMalchezaarTankAvoidHazardAction : public MovementAction { public: - KarazhanPrinceMalchezaarRunAwayFromShadowNovaAction(PlayerbotAI* botAI, std::string const name = "karazhan prince malchezaar run away from shadow nova") : AttackAction(botAI, name) {} + KarazhanPrinceMalchezaarTankAvoidHazardAction(PlayerbotAI* botAI, std::string const name = "karazhan prince malchezaar tank avoid hazard") : MovementAction(botAI, name) {} bool Execute(Event event) override; bool isUseful() override; diff --git a/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp b/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp index 3a1360cc..21b08e47 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp +++ b/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp @@ -1,13 +1,13 @@ -#include "RaidKarazhanActions.h" -#include "RaidKarazhanHelpers.h" -#include "PlayerbotMgr.h" -#include "AiObjectContext.h" -#include "Position.h" -#include "Spell.h" - #include #include +#include "RaidKarazhanHelpers.h" +#include "RaidKarazhanActions.h" +#include "AiObjectContext.h" +#include "PlayerbotMgr.h" +#include "Position.h" +#include "Spell.h" + void RaidKarazhanHelpers::MarkTargetWithSkull(Unit* target) { if (!target) @@ -71,7 +71,7 @@ bool RaidKarazhanHelpers::IsFlameWreathActive() return false; Spell* currentSpell = boss->GetCurrentSpell(CURRENT_GENERIC_SPELL); - if (currentSpell && currentSpell->m_spellInfo && currentSpell->m_spellInfo->Id == SPELL_FLAME_WREATH_CAST) + if (currentSpell && currentSpell->m_spellInfo && currentSpell->m_spellInfo->Id == SPELL_FLAME_WREATH) { bot->Yell("I will not move when Flame Wreath is cast or the raid blows up.", LANG_UNIVERSAL); return true; @@ -84,7 +84,7 @@ bool RaidKarazhanHelpers::IsFlameWreathActive() Player* member = itr->GetSource(); if (!member || !member->IsAlive()) continue; - if (member->HasAura(SPELL_FLAME_WREATH_AURA)) + if (member->HasAura(SPELL_AURA_FLAME_WREATH)) return true; } } @@ -260,3 +260,79 @@ std::vector RaidKarazhanHelpers::GetSpawnedInfernals() const } 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) + { + 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) + { + float hazardDist = std::sqrt(std::pow(checkX - hazard->GetPositionX(), 2) + std::pow(checkY - hazard->GetPositionY(), 2)); + if (hazardDist < hazardRadius) + return false; + } + } + return true; +} + +Position RaidKarazhanHelpers::CalculateArcPoint(const Position& current, const Position& target, const Position& center) +{ + float arcFraction = 0.25f; + // Calculate vectors from center to current position and target + float currentX = current.GetPositionX() - center.GetPositionX(); + float currentY = current.GetPositionY() - center.GetPositionY(); + float targetX = target.GetPositionX() - center.GetPositionX(); + float targetY = target.GetPositionY() - center.GetPositionY(); + + // Calculate distances + float currentDist = std::sqrt(currentX * currentX + currentY * currentY); + float targetDist = std::sqrt(targetX * targetX + targetY * targetY); + if (currentDist == 0.0f || targetDist == 0.0f) + return current; + + // Normalize vectors + currentX /= currentDist; + currentY /= currentDist; + targetX /= targetDist; + targetY /= targetDist; + + // Calculate dot product to find the angle between vectors + float dotProduct = currentX * targetX + currentY * targetY; + dotProduct = std::max(-1.0f, std::min(1.0f, dotProduct)); // Clamp to [-1, 1] + float angle = std::acos(dotProduct); + + // Determine rotation direction (clockwise or counterclockwise) + float crossProduct = currentX * targetY - currentY * targetX; + float stepAngle = angle * arcFraction; // Move arcFraction along the arc + if (crossProduct < 0) + stepAngle = -stepAngle; // Clockwise + + // Calculate rotation matrix components + float cos_a = std::cos(stepAngle); + float sin_a = std::sin(stepAngle); + + // Rotate current vector + float rotatedX = currentX * cos_a - currentY * sin_a; + float rotatedY = currentX * sin_a + currentY * cos_a; + + // Smoothing: blend current and target radius + float desiredDist = currentDist * 0.9f + targetDist * 0.1f; + + // Calculate the new position + return Position(center.GetPositionX() + rotatedX * desiredDist, + center.GetPositionY() + rotatedY * desiredDist, + current.GetPositionZ()); +} diff --git a/src/strategy/raids/karazhan/RaidKarazhanHelpers.h b/src/strategy/raids/karazhan/RaidKarazhanHelpers.h index bb218f45..39335de6 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanHelpers.h +++ b/src/strategy/raids/karazhan/RaidKarazhanHelpers.h @@ -3,7 +3,6 @@ #include "AiObject.h" #include "Playerbots.h" -#include "MovementActions.h" enum KarazhanSpells { @@ -15,8 +14,8 @@ enum KarazhanSpells SPELL_FEAR = 6215, // Rank 3 // Shade of Aran - SPELL_FLAME_WREATH_CAST = 30004, - SPELL_FLAME_WREATH_AURA = 29946, + SPELL_FLAME_WREATH = 30004, + SPELL_AURA_FLAME_WREATH = 29946, SPELL_ARCANE_EXPLOSION = 29973, SPELL_WARLOCK_BANISH = 18647, // Rank 2 @@ -100,6 +99,8 @@ public: 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); + Position CalculateArcPoint(const Position& current, const Position& target, const Position& center); }; #endif diff --git a/src/strategy/raids/karazhan/RaidKarazhanMultipliers.cpp b/src/strategy/raids/karazhan/RaidKarazhanMultipliers.cpp new file mode 100644 index 00000000..4843e27b --- /dev/null +++ b/src/strategy/raids/karazhan/RaidKarazhanMultipliers.cpp @@ -0,0 +1,183 @@ +#include "RaidKarazhanMultipliers.h" +#include "RaidKarazhanActions.h" +#include "RaidKarazhanHelpers.h" +#include "AiObjectContext.h" +#include "DruidBearActions.h" +#include "DruidCatActions.h" +#include "WarriorActions.h" + +static bool IsChargeAction(Action* action) +{ + return dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action); +} + +float KarazhanShadeOfAranMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran"); + if (!boss) + return 1.0f; + + if (boss && boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION)) + { + if (IsChargeAction(action)) + return 0.0f; + + if (dynamic_cast(action) || IsChargeAction(action)) + { + const float safeDistance = 20.0f; + if (bot->GetDistance2d(boss) >= safeDistance) + return 0.0f; + } + } + + bool flameWreathActive = boss->HasAura(SPELL_FLAME_WREATH); + + if (!flameWreathActive && bot->GetGroup()) + { + for (GroupReference* itr = bot->GetGroup()->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (member && member->HasAura(SPELL_AURA_FLAME_WREATH)) + { + flameWreathActive = true; + break; + } + } + } + + if (flameWreathActive) + { + if (dynamic_cast(action) || IsChargeAction(action)) + return 0.0f; + } + return 1.0f; +} + +float KarazhanNetherspiteBlueAndGreenBeamMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); + if (!boss || !boss->IsAlive()) + return 1.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 < 5.0f && 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; + + 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()); + + 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; + } + } + } + return 1.0f; +} + +float KarazhanPrinceMalchezaarMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar"); + if (!boss) + return 1.0f; + + if (boss && bot->HasAura(SPELL_ENFEEBLE)) + { + if (IsChargeAction(action)) + return 0.0f; + } + return 1.0f; +} diff --git a/src/strategy/raids/karazhan/RaidKarazhanMultipliers.h b/src/strategy/raids/karazhan/RaidKarazhanMultipliers.h new file mode 100644 index 00000000..e2066554 --- /dev/null +++ b/src/strategy/raids/karazhan/RaidKarazhanMultipliers.h @@ -0,0 +1,34 @@ +#ifndef _PLAYERBOT_RAIDKARAZHANMULTIPLIERS_H +#define _PLAYERBOT_RAIDKARAZHANMULTIPLIERS_H + +#include "Multiplier.h" + +class KarazhanShadeOfAranMultiplier : public Multiplier +{ +public: + KarazhanShadeOfAranMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan shade of aran multiplier") {} + virtual float GetValue(Action* action); +}; + +class KarazhanNetherspiteBlueAndGreenBeamMultiplier : public Multiplier +{ +public: + KarazhanNetherspiteBlueAndGreenBeamMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan netherspite blue and green beam multiplier") {} + virtual float GetValue(Action* action); +}; + +class KarazhanNetherspiteRedBeamMultiplier : public Multiplier +{ +public: + KarazhanNetherspiteRedBeamMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan netherspite red beam multiplier") {} + virtual float GetValue(Action* action); +}; + +class KarazhanPrinceMalchezaarMultiplier : public Multiplier +{ +public: + KarazhanPrinceMalchezaarMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan prince malchezaar multiplier") {} + virtual float GetValue(Action* action); +}; + +#endif diff --git a/src/strategy/raids/karazhan/RaidKarazhanStrategy.cpp b/src/strategy/raids/karazhan/RaidKarazhanStrategy.cpp index 79544687..8e39f254 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanStrategy.cpp +++ b/src/strategy/raids/karazhan/RaidKarazhanStrategy.cpp @@ -1,4 +1,5 @@ #include "RaidKarazhanStrategy.h" +#include "RaidKarazhanMultipliers.h" void RaidKarazhanStrategy::InitTriggers(std::vector& triggers) { @@ -48,7 +49,7 @@ void RaidKarazhanStrategy::InitTriggers(std::vector& triggers) 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 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), @@ -65,12 +66,15 @@ void RaidKarazhanStrategy::InitTriggers(std::vector& triggers) 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), + new NextAction("karazhan prince malchezaar non tank avoid hazard", ACTION_EMERGENCY + 6), + new NextAction("karazhan prince malchezaar tank avoid hazard", ACTION_EMERGENCY + 6), nullptr))); } -void RaidKarazhanStrategy::InitMultipliers(std::vector& /*multipliers*/) +void RaidKarazhanStrategy::InitMultipliers(std::vector& multipliers) { - // No multipliers for this strategy + multipliers.push_back(new KarazhanShadeOfAranMultiplier(botAI)); + multipliers.push_back(new KarazhanNetherspiteBlueAndGreenBeamMultiplier(botAI)); + multipliers.push_back(new KarazhanNetherspiteRedBeamMultiplier(botAI)); + multipliers.push_back(new KarazhanPrinceMalchezaarMultiplier(botAI)); } diff --git a/src/strategy/raids/karazhan/RaidKarazhanStrategy.h b/src/strategy/raids/karazhan/RaidKarazhanStrategy.h index 070259b4..c03e4285 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanStrategy.h +++ b/src/strategy/raids/karazhan/RaidKarazhanStrategy.h @@ -2,6 +2,7 @@ #define _PLAYERBOT_RAIDKARAZHANSTRATEGY_H_ #include "Strategy.h" +#include "Multiplier.h" class RaidKarazhanStrategy : public Strategy { @@ -10,8 +11,8 @@ public: std::string const getName() override { return "karazhan"; } - void InitTriggers(std::vector& /*triggers*/) override; - void InitMultipliers(std::vector& /*multipliers*/) override; + 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 index 4451212c..0b6a29e8 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanTriggerContext.h +++ b/src/strategy/raids/karazhan/RaidKarazhanTriggerContext.h @@ -1,8 +1,8 @@ #ifndef _PLAYERBOT_RAIDKARAZHANTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDKARAZHANTRIGGERCONTEXT_H -#include "AiObjectContext.h" #include "RaidKarazhanTriggers.h" +#include "AiObjectContext.h" class RaidKarazhanTriggerContext : public NamedObjectContext { diff --git a/src/strategy/raids/karazhan/RaidKarazhanTriggers.cpp b/src/strategy/raids/karazhan/RaidKarazhanTriggers.cpp index 206ae531..55aa175e 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanTriggers.cpp +++ b/src/strategy/raids/karazhan/RaidKarazhanTriggers.cpp @@ -1,7 +1,7 @@ -#include "Playerbots.h" #include "RaidKarazhanTriggers.h" #include "RaidKarazhanHelpers.h" #include "RaidKarazhanActions.h" +#include "Playerbots.h" bool KarazhanAttumenTheHuntsmanTrigger::IsActive() { From 22d1cc9d57b6fa2e99c05dda752459b6d1b0ad82 Mon Sep 17 00:00:00 2001 From: crow Date: Sun, 21 Sep 2025 15:41:29 -0500 Subject: [PATCH 5/9] BBW and other edits --- src/PlayerbotAI.cpp | 12 +- src/strategy/AiObjectContext.cpp | 53 +++----- src/strategy/raids/RaidStrategyContext.h | 27 ++-- .../karazhan/RaidKarazhanActionContext.h | 2 + .../raids/karazhan/RaidKarazhanActions.cpp | 116 ++++++++++++------ .../raids/karazhan/RaidKarazhanActions.h | 9 ++ .../raids/karazhan/RaidKarazhanHelpers.cpp | 24 ++++ .../raids/karazhan/RaidKarazhanHelpers.h | 37 ++---- .../karazhan/RaidKarazhanMultipliers.cpp | 20 ++- .../raids/karazhan/RaidKarazhanMultipliers.h | 7 ++ .../raids/karazhan/RaidKarazhanStrategy.cpp | 1 + 11 files changed, 182 insertions(+), 126 deletions(-) diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 1d5de49b..b1d5558b 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -1513,22 +1513,22 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster) switch (mapId) { case 249: - strategyName = "onyxia"; + strategyName = "onyxia"; // Onyxia's Lair break; case 409: - strategyName = "mc"; + strategyName = "mc"; // Molten Core break; case 469: - strategyName = "bwl"; + strategyName = "bwl"; // Blackwing Lair break; case 509: - strategyName = "aq20"; + strategyName = "aq20"; // Ruins of Ahn'Qiraj break; case 532: - strategyName = "karazhan"; + strategyName = "karazhan"; // Karazhan break; case 533: - strategyName = "naxx"; + strategyName = "naxx"; // Naxxramas break; case 574: strategyName = "wotlk-uk"; // Utgarde Keep diff --git a/src/strategy/AiObjectContext.cpp b/src/strategy/AiObjectContext.cpp index 2e516c54..e525ec75 100644 --- a/src/strategy/AiObjectContext.cpp +++ b/src/strategy/AiObjectContext.cpp @@ -27,49 +27,30 @@ #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" #include "raids/RaidStrategyContext.h" #include "raids/aq20/RaidAq20ActionContext.h" #include "raids/aq20/RaidAq20TriggerContext.h" -#include "raids/blackwinglair/RaidBwlActionContext.h" -#include "raids/blackwinglair/RaidBwlTriggerContext.h" -#include "raids/eyeofeternity/RaidEoEActionContext.h" -#include "raids/eyeofeternity/RaidEoETriggerContext.h" -#include "raids/icecrown/RaidIccActionContext.h" -#include "raids/icecrown/RaidIccTriggerContext.h" #include "raids/moltencore/RaidMcActionContext.h" #include "raids/moltencore/RaidMcTriggerContext.h" +#include "raids/blackwinglair/RaidBwlActionContext.h" +#include "raids/blackwinglair/RaidBwlTriggerContext.h" +#include "raids/karazhan/RaidKarazhanActionContext.h" +#include "raids/karazhan/RaidKarazhanTriggerContext.h" #include "raids/naxxramas/RaidNaxxActionContext.h" #include "raids/naxxramas/RaidNaxxTriggerContext.h" +#include "raids/eyeofeternity/RaidEoEActionContext.h" +#include "raids/eyeofeternity/RaidEoETriggerContext.h" +#include "raids/vaultofarchavon/RaidVoAActionContext.h" +#include "raids/vaultofarchavon/RaidVoATriggerContext.h" #include "raids/obsidiansanctum/RaidOsActionContext.h" #include "raids/obsidiansanctum/RaidOsTriggerContext.h" #include "raids/onyxia/RaidOnyxiaActionContext.h" #include "raids/onyxia/RaidOnyxiaTriggerContext.h" -#include "raids/vaultofarchavon/RaidVoAActionContext.h" -#include "raids/vaultofarchavon/RaidVoATriggerContext.h" +#include "raids/icecrown/RaidIccActionContext.h" +#include "raids/icecrown/RaidIccTriggerContext.h" SharedNamedObjectContextList AiObjectContext::sharedStrategyContexts; SharedNamedObjectContextList AiObjectContext::sharedActionContexts; @@ -117,8 +98,8 @@ void AiObjectContext::BuildSharedStrategyContexts(SharedNamedObjectContextList& actionContexts) @@ -126,17 +107,17 @@ void AiObjectContext::BuildSharedActionContexts(SharedNamedObjectContextList { public: RaidStrategyContext() : NamedObjectContext(false, true) { - // TODO should we give these prefixes (eg: "naxx" -> "raid naxx")? because if we don't it's going to end up - // very crowded (with possible conflicts) once we have strats for all raids and some dungeons - // (mc already very similiar to nc) + creators["aq20"] = &RaidStrategyContext::aq20; creators["mc"] = &RaidStrategyContext::mc; creators["bwl"] = &RaidStrategyContext::bwl; - creators["aq20"] = &RaidStrategyContext::aq20; + creators["karazhan"] = &RaidStrategyContext::karazhan; creators["naxx"] = &RaidStrategyContext::naxx; creators["wotlk-os"] = &RaidStrategyContext::wotlk_os; creators["wotlk-eoe"] = &RaidStrategyContext::wotlk_eoe; creators["voa"] = &RaidStrategyContext::voa; creators["uld"] = &RaidStrategyContext::uld; - creators["icc"] = &RaidStrategyContext::icc; creators["onyxia"] = &RaidStrategyContext::onyxia; - creators["karazhan"] = &RaidStrategyContext::karazhan; + creators["icc"] = &RaidStrategyContext::icc; } private: + static Strategy* aq20(PlayerbotAI* botAI) { return new RaidAq20Strategy(botAI); } static Strategy* mc(PlayerbotAI* botAI) { return new RaidMcStrategy(botAI); } static Strategy* bwl(PlayerbotAI* botAI) { return new RaidBwlStrategy(botAI); } - static Strategy* aq20(PlayerbotAI* botAI) { return new RaidAq20Strategy(botAI); } + static Strategy* karazhan(PlayerbotAI* botAI) { return new RaidKarazhanStrategy(botAI); } static Strategy* naxx(PlayerbotAI* botAI) { return new RaidNaxxStrategy(botAI); } static Strategy* wotlk_os(PlayerbotAI* botAI) { return new RaidOsStrategy(botAI); } static Strategy* wotlk_eoe(PlayerbotAI* botAI) { return new RaidEoEStrategy(botAI); } static Strategy* voa(PlayerbotAI* botAI) { return new RaidVoAStrategy(botAI); } + static Strategy* onyxia(PlayerbotAI* botAI) { return new RaidOnyxiaStrategy(botAI); } 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 index ac338cba..2c1f7168 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanActionContext.h +++ b/src/strategy/raids/karazhan/RaidKarazhanActionContext.h @@ -16,6 +16,7 @@ public: 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 position boss"] = &RaidKarazhanActionContext::karazhan_big_bad_wolf_position_boss; 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; @@ -52,6 +53,7 @@ private: 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_position_boss(PlayerbotAI* botAI) { return new KarazhanBigBadWolfPositionBossAction(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); } diff --git a/src/strategy/raids/karazhan/RaidKarazhanActions.cpp b/src/strategy/raids/karazhan/RaidKarazhanActions.cpp index 5f0e6064..24335251 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanActions.cpp +++ b/src/strategy/raids/karazhan/RaidKarazhanActions.cpp @@ -4,9 +4,13 @@ #include "Playerbots.h" #include "PlayerbotMgr.h" #include "PlayerbotAI.h" +#include "Position.h" namespace { + // Big Bad Wolf + static int currentIndex = 0; + // Netherspite static std::map beamMoveTimes; static std::map lastBeamMoveSideways; } @@ -149,24 +153,56 @@ bool KarazhanMaidenOfVirtuePositionRangedAction::isUseful() return boss && botAI->IsRanged(bot); } -bool KarazhanBigBadWolfRunAwayAction::Execute(Event event) +bool KarazhanBigBadWolfPositionBossAction::Execute(Event event) { Unit* boss = AI_VALUE2(Unit*, "find target", "the big bad wolf"); - bot->AttackStop(); - bot->InterruptNonMeleeSpells(false); + 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]; - if (bot->GetExactDist2d(target.GetPositionX(), target.GetPositionY()) < threshold) + while (bot->GetExactDist2d(target.GetPositionX(), target.GetPositionY()) < threshold) { currentIndex = (currentIndex + 1) % 4; target = KARAZHAN_BIG_BAD_WOLF_RUN_POSITION[currentIndex]; } - return MoveTo(bot->GetMapId(), target.GetPositionX(), target.GetPositionY(), target.GetPositionZ(), false, false, - false, true, MovementPriority::MOVEMENT_FORCED); + return MoveTo(bot->GetMapId(), target.GetPositionX(), target.GetPositionY(), target.GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_FORCED); } bool KarazhanBigBadWolfRunAwayAction::isUseful() @@ -222,8 +258,6 @@ bool KarazhanWizardOfOzScorchStrawmanAction::Execute(Event event) 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(); @@ -237,19 +271,10 @@ bool KarazhanWizardOfOzScorchStrawmanAction::Execute(Event event) if (!mageAI) continue; - uint32 knownScorchId = 0; - for (uint32 spellId : scorchSpellIds) + if (mageAI->CanCastSpell("scorch", strawman)) { - if (member->HasSpell(spellId)) - { - knownScorchId = spellId; - break; - } + mageAI->CastSpell("scorch", strawman); } - if (!knownScorchId) - continue; - - mageAI->CastSpell(knownScorchId, strawman); } return false; } @@ -507,7 +532,8 @@ bool KarazhanNetherspiteBlockRedBeamAction::Execute(Event event) 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(bot->GetMapId(), sideX, sideY, sideZ, false, false, false, true, + MovementPriority::MOVEMENT_FORCED); } } return false; @@ -595,7 +621,8 @@ bool KarazhanNetherspiteBlockBlueBeamAction::Execute(Event event) bool outsideAllVoidZones = true; for (Unit* voidZone : voidZones) { - float voidZoneDist = sqrt(pow(candidateX - voidZone->GetPositionX(), 2) + pow(candidateY - voidZone->GetPositionY(), 2)); + float voidZoneDist = sqrt(pow(candidateX - voidZone->GetPositionX(), 2) + + pow(candidateY - voidZone->GetPositionY(), 2)); if (voidZoneDist < 4.0f) { outsideAllVoidZones = false; @@ -635,8 +662,8 @@ bool KarazhanNetherspiteBlockBlueBeamAction::isUseful() 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 +// 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 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) { @@ -686,7 +713,8 @@ bool KarazhanNetherspiteBlockGreenBeamAction::Execute(Event event) bool outsideAllVoidZones = true; for (Unit* voidZone : voidZones) { - float voidZoneDist = sqrt(pow(candidateX - voidZone->GetPositionX(), 2) + pow(candidateY - voidZone->GetPositionY(), 2)); + float voidZoneDist = sqrt(pow(candidateX - voidZone->GetPositionX(), 2) + + pow(candidateY - voidZone->GetPositionY(), 2)); if (voidZoneDist < 4.0f) { outsideAllVoidZones = false; @@ -842,11 +870,12 @@ bool KarazhanNetherspiteAvoidBeamAndVoidZoneAction::Execute(Event event) } } } - if (found && karazhanHelper.IsSafePosition(bestCandidate.GetPositionX(), bestCandidate.GetPositionY(), bestCandidate.GetPositionZ(), + 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 MoveTo(bot->GetMapId(), bestCandidate.GetPositionX(), bestCandidate.GetPositionY(), + bestCandidate.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT); return false; } @@ -929,7 +958,8 @@ bool KarazhanPrinceMalchezaarNonTankAvoidHazardAction::Execute(Event event) float destZ = bossZ; if (!bot->IsWithinLOS(x, y, destZ)) continue; - bool pathSafe = karazhanHelper.IsStraightPathSafe(Position(bx, by, bz), Position(x, y, destZ), infernals, safeInfernalDistance, stepSize); + bool pathSafe = karazhanHelper.IsStraightPathSafe(Position(bx, by, bz), Position(x, y, destZ), + infernals, safeInfernalDistance, stepSize); float moveDist = sqrt(pow(x - bx, 2) + pow(y - by, 2)); if (pathSafe && moveDist < bestMoveDist) { @@ -946,7 +976,8 @@ bool KarazhanPrinceMalchezaarNonTankAvoidHazardAction::Execute(Event event) bot->AttackStop(); bot->InterruptNonMeleeSpells(false); - return MoveTo(bot->GetMapId(), bestDestX, bestDestY, bestDestZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED); + return MoveTo(bot->GetMapId(), bestDestX, bestDestY, bestDestZ, false, false, false, true, + MovementPriority::MOVEMENT_FORCED); } return false; } @@ -981,7 +1012,8 @@ bool KarazhanPrinceMalchezaarNonTankAvoidHazardAction::Execute(Event event) float destZ = bossZ; if (!bot->IsWithinLOS(x, y, destZ)) continue; - bool pathSafe = karazhanHelper.IsStraightPathSafe(Position(bx, by, bz), Position(x, y, destZ), infernals, safeInfernalDistance, stepSize); + bool pathSafe = karazhanHelper.IsStraightPathSafe(Position(bx, by, bz), Position(x, y, destZ), + infernals, safeInfernalDistance, stepSize); float moveDist = sqrt(pow(x - bx, 2) + pow(y - by, 2)); if (pathSafe && moveDist < bestMoveDist) { @@ -994,20 +1026,23 @@ bool KarazhanPrinceMalchezaarNonTankAvoidHazardAction::Execute(Event event) } if (!pathSafe) { - Position arcPoint = karazhanHelper.CalculateArcPoint(Position(bx, by, bz), Position(x, y, destZ), Position(bossX, bossY, bossZ)); + Position arcPoint = karazhanHelper.CalculateArcPoint(Position(bx, by, bz), Position(x, y, destZ), + Position(bossX, bossY, bossZ)); if (!bot->IsWithinLOS(arcPoint.GetPositionX(), arcPoint.GetPositionY(), arcPoint.GetPositionZ())) continue; bool arcSafe = true; for (Unit* infernal : infernals) { - float infernalDist = sqrt(pow(arcPoint.GetPositionX() - infernal->GetPositionX(), 2) + pow(arcPoint.GetPositionY() - infernal->GetPositionY(), 2)); + float infernalDist = sqrt(pow(arcPoint.GetPositionX() - infernal->GetPositionX(), 2) + + pow(arcPoint.GetPositionY() - infernal->GetPositionY(), 2)); if (infernalDist < safeInfernalDistance) { arcSafe = false; break; } } - float arcMoveDist = sqrt(pow(arcPoint.GetPositionX() - bx, 2) + pow(arcPoint.GetPositionY() - by, 2)); + float arcMoveDist = sqrt(pow(arcPoint.GetPositionX() - bx, 2) + + pow(arcPoint.GetPositionY() - by, 2)); if (arcSafe && arcMoveDist < bestMoveDist) { bestMoveDist = arcMoveDist; @@ -1025,7 +1060,8 @@ bool KarazhanPrinceMalchezaarNonTankAvoidHazardAction::Execute(Event event) bot->AttackStop(); bot->InterruptNonMeleeSpells(false); - return MoveTo(bot->GetMapId(), bestDestX, bestDestY, bestDestZ, false, false, false, true, MovementPriority::MOVEMENT_COMBAT); + return MoveTo(bot->GetMapId(), bestDestX, bestDestY, bestDestZ, false, false, false, true, + MovementPriority::MOVEMENT_COMBAT); } } } @@ -1082,7 +1118,8 @@ bool KarazhanPrinceMalchezaarTankAvoidHazardAction::Execute(Event event) float z = bz; if (!bot->IsWithinLOS(x, y, z)) continue; - bool safe = karazhanHelper.IsStraightPathSafe(Position(bx, by, bz), Position(x, y, z), infernals, safeInfernalDistance, stepSize); + bool safe = karazhanHelper.IsStraightPathSafe(Position(bx, by, bz), Position(x, y, z), + infernals, safeInfernalDistance, stepSize); float moveDist = sqrt(pow(x - bx, 2) + pow(y - by, 2)); if (safe && moveDist < bestMoveDist) { @@ -1095,13 +1132,15 @@ bool KarazhanPrinceMalchezaarTankAvoidHazardAction::Execute(Event event) } if (!safe) { - Position arcPoint = karazhanHelper.CalculateArcPoint(Position(bx, by, bz), Position(x, y, z), Position(bx, by, bz)); + Position arcPoint = karazhanHelper.CalculateArcPoint(Position(bx, by, bz), Position(x, y, z), + Position(bx, by, bz)); if (!bot->IsWithinLOS(arcPoint.GetPositionX(), arcPoint.GetPositionY(), arcPoint.GetPositionZ())) continue; bool arcSafe = true; for (Unit* infernal : infernals) { - float infernalDist = sqrt(pow(arcPoint.GetPositionX() - infernal->GetPositionX(), 2) + pow(arcPoint.GetPositionY() - infernal->GetPositionY(), 2)); + float infernalDist = sqrt(pow(arcPoint.GetPositionX() - infernal->GetPositionX(), 2) + + pow(arcPoint.GetPositionY() - infernal->GetPositionY(), 2)); if (infernalDist < safeInfernalDistance) { arcSafe = false; @@ -1125,7 +1164,8 @@ bool KarazhanPrinceMalchezaarTankAvoidHazardAction::Execute(Event event) { bot->AttackStop(); bot->InterruptNonMeleeSpells(false); - return MoveTo(bot->GetMapId(), bestDestX, bestDestY, bestDestZ, false, false, false, true, MovementPriority::MOVEMENT_COMBAT); + return MoveTo(bot->GetMapId(), bestDestX, bestDestY, bestDestZ, false, false, false, true, + MovementPriority::MOVEMENT_COMBAT); } } return false; diff --git a/src/strategy/raids/karazhan/RaidKarazhanActions.h b/src/strategy/raids/karazhan/RaidKarazhanActions.h index c9b34d03..4ab24ed9 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanActions.h +++ b/src/strategy/raids/karazhan/RaidKarazhanActions.h @@ -39,6 +39,15 @@ public: bool isUseful() override; }; +class KarazhanBigBadWolfPositionBossAction : public MovementAction +{ +public: + KarazhanBigBadWolfPositionBossAction(PlayerbotAI* botAI, std::string const name = "karazhan big bad wolf position boss") : MovementAction(botAI, name) {} + + bool Execute(Event event) override; + bool isUseful() override; +}; + class KarazhanBigBadWolfRunAwayAction : public MovementAction { public: diff --git a/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp b/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp index 21b08e47..81479e7e 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp +++ b/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp @@ -8,6 +8,30 @@ #include "Position.h" #include "Spell.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] = +{ + { -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 }, +}; + +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) diff --git a/src/strategy/raids/karazhan/RaidKarazhanHelpers.h b/src/strategy/raids/karazhan/RaidKarazhanHelpers.h index 39335de6..42db6580 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanHelpers.h +++ b/src/strategy/raids/karazhan/RaidKarazhanHelpers.h @@ -3,6 +3,7 @@ #include "AiObject.h" #include "Playerbots.h" +#include "Position.h" enum KarazhanSpells { @@ -11,7 +12,6 @@ enum KarazhanSpells // Opera Event SPELL_LITTLE_RED_RIDING_HOOD = 30756, - SPELL_FEAR = 6215, // Rank 3 // Shade of Aran SPELL_FLAME_WREATH = 30004, @@ -29,9 +29,6 @@ enum KarazhanSpells // Prince Malchezaar SPELL_ENFEEBLE = 30843, - - // Nightbane - SPELL_CHARRED_EARTH = 30129 }; enum KarazhanNpcs @@ -54,32 +51,13 @@ enum KarazhanNpcs // 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); +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 { @@ -99,7 +77,8 @@ public: 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); + bool IsStraightPathSafe(const Position& start, const Position& target, + const std::vector& hazards, float hazardRadius, float stepSize); Position CalculateArcPoint(const Position& current, const Position& target, const Position& center); }; diff --git a/src/strategy/raids/karazhan/RaidKarazhanMultipliers.cpp b/src/strategy/raids/karazhan/RaidKarazhanMultipliers.cpp index 4843e27b..a1b25ee5 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanMultipliers.cpp +++ b/src/strategy/raids/karazhan/RaidKarazhanMultipliers.cpp @@ -2,6 +2,7 @@ #include "RaidKarazhanActions.h" #include "RaidKarazhanHelpers.h" #include "AiObjectContext.h" +#include "AttackAction.h" #include "DruidBearActions.h" #include "DruidCatActions.h" #include "WarriorActions.h" @@ -14,18 +15,33 @@ static bool IsChargeAction(Action* action) dynamic_cast(action); } +float KarazhanBigBadWolfMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "the big bad wolf"); + if (!boss) + return 1.0f; + + if (bot->HasAura(SPELL_LITTLE_RED_RIDING_HOOD)) + { + if ((dynamic_cast(action) && !dynamic_cast(action)) || + (dynamic_cast(action))) + 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 && boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION)) + if (boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION)) { if (IsChargeAction(action)) return 0.0f; - if (dynamic_cast(action) || IsChargeAction(action)) + if (dynamic_cast(action)) { const float safeDistance = 20.0f; if (bot->GetDistance2d(boss) >= safeDistance) diff --git a/src/strategy/raids/karazhan/RaidKarazhanMultipliers.h b/src/strategy/raids/karazhan/RaidKarazhanMultipliers.h index e2066554..d29aa027 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanMultipliers.h +++ b/src/strategy/raids/karazhan/RaidKarazhanMultipliers.h @@ -3,6 +3,13 @@ #include "Multiplier.h" +class KarazhanBigBadWolfMultiplier : public Multiplier +{ +public: + KarazhanBigBadWolfMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan big bad wolf multiplier") {} + virtual float GetValue(Action* action); +}; + class KarazhanShadeOfAranMultiplier : public Multiplier { public: diff --git a/src/strategy/raids/karazhan/RaidKarazhanStrategy.cpp b/src/strategy/raids/karazhan/RaidKarazhanStrategy.cpp index 8e39f254..f93c923c 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanStrategy.cpp +++ b/src/strategy/raids/karazhan/RaidKarazhanStrategy.cpp @@ -22,6 +22,7 @@ void RaidKarazhanStrategy::InitTriggers(std::vector& triggers) 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))); triggers.push_back(new TriggerNode( From e8954f6cba039150886f1d68c5d57e18090006ab Mon Sep 17 00:00:00 2001 From: crow Date: Fri, 26 Sep 2025 10:01:12 -0500 Subject: [PATCH 6/9] resolve some comments + more Formatting edits and improved Malchezaar --- .../raids/karazhan/RaidKarazhanActions.cpp | 212 ++++++++++++------ .../raids/karazhan/RaidKarazhanHelpers.cpp | 74 ++++-- .../raids/karazhan/RaidKarazhanHelpers.h | 44 ++-- .../karazhan/RaidKarazhanMultipliers.cpp | 46 +++- .../raids/karazhan/RaidKarazhanTriggers.cpp | 77 ++----- 5 files changed, 283 insertions(+), 170 deletions(-) diff --git a/src/strategy/raids/karazhan/RaidKarazhanActions.cpp b/src/strategy/raids/karazhan/RaidKarazhanActions.cpp index 24335251..eb23d72c 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanActions.cpp +++ b/src/strategy/raids/karazhan/RaidKarazhanActions.cpp @@ -18,7 +18,7 @@ namespace bool KarazhanAttumenTheHuntsmanStackBehindAction::Execute(Event event) { RaidKarazhanHelpers karazhanHelper(botAI); - Unit* boss = karazhanHelper.GetFirstAliveUnitByEntry(NPC_ATTUMEN_THE_HUNTSMAN); + Unit* boss = karazhanHelper.GetFirstAliveUnitByEntry(NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED); float distance = 5.0f; float orientation = boss->GetOrientation() + M_PI; @@ -34,12 +34,9 @@ bool KarazhanAttumenTheHuntsmanStackBehindAction::Execute(Event event) bool KarazhanAttumenTheHuntsmanStackBehindAction::isUseful() { RaidKarazhanHelpers karazhanHelper(botAI); - Unit* boss = karazhanHelper.GetFirstAliveUnitByEntry(NPC_ATTUMEN_THE_HUNTSMAN); + Unit* boss = karazhanHelper.GetFirstAliveUnitByEntry(NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED); - if (boss && botAI->IsTank(bot) && botAI->HasAggro(boss) && boss->GetVictim() == bot) - return false; - - return boss != nullptr; + return boss && !(botAI->IsTank(bot) && botAI->HasAggro(boss) && boss->GetVictim() == bot); } bool KarazhanMoroesMarkTargetAction::Execute(Event event) @@ -70,7 +67,9 @@ bool KarazhanMaidenOfVirtuePositionBossAction::Execute(Event event) { Player* member = itr->GetSource(); if (!member || !member->IsAlive() || !botAI->IsHeal(member) || !member->HasAura(SPELL_REPENTANCE)) + { continue; + } healer = member; break; } @@ -82,9 +81,10 @@ bool KarazhanMaidenOfVirtuePositionBossAction::Execute(Event event) 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); + { + return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, true, + MovementPriority::MOVEMENT_COMBAT); + } } const float maxDistance = 3.0f; @@ -96,9 +96,10 @@ bool KarazhanMaidenOfVirtuePositionBossAction::Execute(Event event) 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 MoveTo(bot->GetMapId(), mX, mY, + bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); + } } return false; } @@ -121,8 +122,10 @@ bool KarazhanMaidenOfVirtuePositionRangedAction::Execute(Event event) { Unit* member = botAI->GetUnit(memberGuid); - if (!member || !botAI->IsRanged(member->ToPlayer())) + if (!member || !botAI->IsRanged(member->ToPlayer())) + { continue; + } if (member == bot) break; @@ -137,12 +140,12 @@ bool KarazhanMaidenOfVirtuePositionRangedAction::Execute(Event event) 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; } @@ -173,11 +176,9 @@ bool KarazhanBigBadWolfPositionBossAction::Execute(Event event) { return false; } - return MoveTo(bot->GetMapId(), mX, mY, bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); } - return false; } @@ -200,7 +201,6 @@ bool KarazhanBigBadWolfRunAwayAction::Execute(Event event) 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); } @@ -220,11 +220,17 @@ bool KarazhanRomuloAndJulianneMarkTargetAction::Execute(Event event) 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); @@ -252,25 +258,33 @@ 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; + } 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; - + } if (mageAI->CanCastSpell("scorch", strawman)) { mageAI->CastSpell("scorch", strawman); @@ -283,7 +297,9 @@ 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); @@ -294,7 +310,6 @@ bool KarazhanTheCuratorMarkTargetAction::Execute(Event event) 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); @@ -302,13 +317,13 @@ bool KarazhanTheCuratorPositionBossAction::Execute(Event event) { 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 MoveTo(bot->GetMapId(), mX, mY, + bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, + false); + } } return false; } @@ -327,8 +342,9 @@ bool KarazhanTheCuratorSpreadRangedAction::Execute(Event event) Unit* nearestPlayer = karazhanHelper.GetNearestPlayerInRadius(minDistance); if (nearestPlayer) + { return FleePosition(nearestPlayer->GetPosition(), minDistance); - + } return false; } @@ -343,11 +359,12 @@ 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); @@ -357,7 +374,7 @@ bool KarazhanTerestianIllhoofMarkTargetAction::Execute(Event event) } } karazhanHelper.MarkTargetWithSkull(target); - + return false; } @@ -374,17 +391,15 @@ bool KarazhanShadeOfAranArcaneExplosionRunAwayAction::Execute(Event event) { return MoveAway(boss, safeDistance - distance); } - return false; } bool KarazhanShadeOfAranArcaneExplosionRunAwayAction::isUseful() { Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran"); - if (!boss || !boss->IsAlive()) - return false; - return boss->HasUnitState(UNIT_STATE_CASTING) && boss->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION); + return boss && boss->IsAlive() && boss->HasUnitState(UNIT_STATE_CASTING) && + boss->FindCurrentSpellBySpellId(SPELL_ARCANE_EXPLOSION); } bool KarazhanShadeOfAranFlameWreathStopMovementAction::Execute(Event event) @@ -395,8 +410,9 @@ bool KarazhanShadeOfAranFlameWreathStopMovementAction::Execute(Event event) AI_VALUE(LastMovement&, "last movement").Set(nullptr); bot->GetMotionMaster()->Clear(); if (bot->isMoving()) + { bot->StopMoving(); - + } return true; } return false; @@ -407,8 +423,9 @@ bool KarazhanShadeOfAranMarkConjuredElementalAction::Execute(Event event) RaidKarazhanHelpers karazhanHelper(botAI); Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran"); if (!boss || !boss->IsAlive()) + { return false; - + } Unit* target = karazhanHelper.GetFirstAliveUnitByEntry(NPC_CONJURED_ELEMENTAL); if (!target || !target->IsAlive() || target->HasAura(SPELL_WARLOCK_BANISH)) { @@ -430,23 +447,23 @@ bool KarazhanShadeOfAranSpreadRangedAction::Execute(Event event) 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); + { + 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; } @@ -454,7 +471,9 @@ bool KarazhanShadeOfAranSpreadRangedAction::isUseful() { Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran"); if (!boss || !boss->IsAlive()) + { return false; + } RaidKarazhanHelpers karazhanHelper(botAI); @@ -471,19 +490,27 @@ bool KarazhanNetherspiteBlockRedBeamAction::Execute(Event event) 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; } @@ -522,8 +549,9 @@ bool KarazhanNetherspiteBlockRedBeamAction::Execute(Event event) float dy = py - by; float length = sqrt(dx*dx + dy*dy); if (length == 0.0f) + { return false; - + } dx /= length; dy /= length; float perpDx = -dy; @@ -549,8 +577,9 @@ bool KarazhanNetherspiteBlockRedBeamAction::isUseful() bool bossIsBanished = boss && boss->HasAura(SPELL_NETHERSPITE_BANISHED); if (!boss || !redPortal) + { return false; - + } if (lastBossBanishState[botGuid] != bossIsBanished) { if (!bossIsBanished) @@ -560,10 +589,10 @@ bool KarazhanNetherspiteBlockRedBeamAction::isUseful() } lastBossBanishState[botGuid] = bossIsBanished; } - if (bossIsBanished) + { return false; - + } return true; } @@ -606,7 +635,9 @@ bool KarazhanNetherspiteBlockBlueBeamAction::Execute(Event event) float dy = py - by; float length = sqrt(dx*dx + dy*dy); if (length == 0.0f) + { return false; + } dx /= length; dy /= length; @@ -630,8 +661,9 @@ bool KarazhanNetherspiteBlockBlueBeamAction::Execute(Event event) } } if (!outsideAllVoidZones) + { continue; - + } float distToIdeal = fabs(dist - 18.0f); if (!found || distToIdeal < bestDist) { @@ -698,8 +730,9 @@ bool KarazhanNetherspiteBlockGreenBeamAction::Execute(Event event) 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; @@ -722,8 +755,9 @@ bool KarazhanNetherspiteBlockGreenBeamAction::Execute(Event event) } } if (!outsideAllVoidZones) + { continue; - + } float distToIdeal = fabs(dist - 18.0f); if (!found || distToIdeal < bestDist) { @@ -771,27 +805,30 @@ bool KarazhanNetherspiteAvoidBeamAndVoidZoneAction::Execute(Event event) 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) { + 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) { + 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) { + if (greenPortal) + { float bx = boss->GetPositionX(), by = boss->GetPositionY(); float px = greenPortal->GetPositionX(), py = greenPortal->GetPositionY(); float dx = px - bx, dy = py - by; @@ -806,8 +843,10 @@ bool KarazhanNetherspiteAvoidBeamAndVoidZoneAction::Execute(Event event) 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; - + if (length == 0.0f) + {continue; + continue; + } dx /= length; dy /= length; float botdx = bot->GetPositionX() - bx, botdy = bot->GetPositionY() - by; float t = (botdx * dx + botdy * dy); @@ -821,7 +860,9 @@ bool KarazhanNetherspiteAvoidBeamAndVoidZoneAction::Execute(Event event) } 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(); @@ -836,8 +877,9 @@ bool KarazhanNetherspiteAvoidBeamAndVoidZoneAction::Execute(Event event) 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) { @@ -845,7 +887,10 @@ bool KarazhanNetherspiteAvoidBeamAndVoidZoneAction::Execute(Event event) 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; + if (length == 0.0f) + { + continue; + } dx /= length; dy /= length; float botdx = cx - bx, botdy = cy - by; float t = (botdx * dx + botdy * dy); @@ -857,11 +902,15 @@ bool KarazhanNetherspiteAvoidBeamAndVoidZoneAction::Execute(Event event) break; } } - if (tooCloseToBeam) continue; - + if (tooCloseToBeam) + { + continue; + } float moveDist = sqrt(pow(cx - bot->GetPositionX(), 2) + pow(cy - bot->GetPositionY(), 2)); - if (moveDist < minMoveDist) continue; - + if (moveDist < minMoveDist) + { + continue; + } if (!found || moveDist < bestDist) { bestCandidate = Position(cx, cy, cz); @@ -873,9 +922,10 @@ bool KarazhanNetherspiteAvoidBeamAndVoidZoneAction::Execute(Event event) 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; } @@ -883,13 +933,15 @@ 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; } @@ -901,7 +953,9 @@ bool KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction::Execute(Event event) for (Unit* vz : voidZones) { if (vz->GetEntry() == NPC_VOID_ZONE && bot->GetExactDist2d(vz) < 4.0f) + { return FleePosition(vz->GetPosition(), 4.0f); + } } return false; } @@ -910,19 +964,21 @@ 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) + if (bot->GetExactDist2d(vz) < 4.0f) + { return true; + } } return false; } -// For Enfeebled bots to avoid Shadow Nova and all non-tank bots to avoid infernals bool KarazhanPrinceMalchezaarNonTankAvoidHazardAction::Execute(Event event) { Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar"); @@ -957,8 +1013,10 @@ bool KarazhanPrinceMalchezaarNonTankAvoidHazardAction::Execute(Event event) float y = bossY + dy * dist; float destZ = bossZ; if (!bot->IsWithinLOS(x, y, destZ)) + { continue; - bool pathSafe = karazhanHelper.IsStraightPathSafe(Position(bx, by, bz), Position(x, y, destZ), + } + bool pathSafe = karazhanHelper.IsStraightPathSafe(Position(bx, by, bz), Position(x, y, destZ), infernals, safeInfernalDistance, stepSize); float moveDist = sqrt(pow(x - bx, 2) + pow(y - by, 2)); if (pathSafe && moveDist < bestMoveDist) @@ -1011,8 +1069,10 @@ bool KarazhanPrinceMalchezaarNonTankAvoidHazardAction::Execute(Event event) float y = bossY + dy * dist; float destZ = bossZ; if (!bot->IsWithinLOS(x, y, destZ)) + { continue; - bool pathSafe = karazhanHelper.IsStraightPathSafe(Position(bx, by, bz), Position(x, y, destZ), + } + bool pathSafe = karazhanHelper.IsStraightPathSafe(Position(bx, by, bz), Position(x, y, destZ), infernals, safeInfernalDistance, stepSize); float moveDist = sqrt(pow(x - bx, 2) + pow(y - by, 2)); if (pathSafe && moveDist < bestMoveDist) @@ -1029,7 +1089,9 @@ bool KarazhanPrinceMalchezaarNonTankAvoidHazardAction::Execute(Event event) Position arcPoint = karazhanHelper.CalculateArcPoint(Position(bx, by, bz), Position(x, y, destZ), Position(bossX, bossY, bossZ)); if (!bot->IsWithinLOS(arcPoint.GetPositionX(), arcPoint.GetPositionY(), arcPoint.GetPositionZ())) + { continue; + } bool arcSafe = true; for (Unit* infernal : infernals) { @@ -1075,7 +1137,6 @@ bool KarazhanPrinceMalchezaarNonTankAvoidHazardAction::isUseful() return boss && !(botAI->IsTank(bot) && botAI->HasAggro(boss) && boss->GetVictim() == bot); } -// For tank to avoid infernals (with buffer distance) bool KarazhanPrinceMalchezaarTankAvoidHazardAction::Execute(Event event) { Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar"); @@ -1117,7 +1178,9 @@ bool KarazhanPrinceMalchezaarTankAvoidHazardAction::Execute(Event event) float y = by + dy * dist; float z = bz; if (!bot->IsWithinLOS(x, y, z)) + { continue; + } bool safe = karazhanHelper.IsStraightPathSafe(Position(bx, by, bz), Position(x, y, z), infernals, safeInfernalDistance, stepSize); float moveDist = sqrt(pow(x - bx, 2) + pow(y - by, 2)); @@ -1134,8 +1197,14 @@ bool KarazhanPrinceMalchezaarTankAvoidHazardAction::Execute(Event event) { Position arcPoint = karazhanHelper.CalculateArcPoint(Position(bx, by, bz), Position(x, y, z), Position(bx, by, bz)); - if (!bot->IsWithinLOS(arcPoint.GetPositionX(), arcPoint.GetPositionY(), arcPoint.GetPositionZ())) + float arcX = arcPoint.GetPositionX(); + float arcY = arcPoint.GetPositionY(); + float arcZ = arcPoint.GetPositionZ(); + float arcDestX = arcX, arcDestY = arcY, arcDestZ = arcZ; + if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bx, by, bz, arcDestX, arcDestY, arcDestZ)) + { continue; + } bool arcSafe = true; for (Unit* infernal : infernals) { @@ -1164,6 +1233,7 @@ bool KarazhanPrinceMalchezaarTankAvoidHazardAction::Execute(Event event) { bot->AttackStop(); bot->InterruptNonMeleeSpells(false); + return MoveTo(bot->GetMapId(), bestDestX, bestDestY, bestDestZ, false, false, false, true, MovementPriority::MOVEMENT_COMBAT); } diff --git a/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp b/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp index 81479e7e..939e73b9 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp +++ b/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp @@ -35,7 +35,9 @@ const Position KARAZHAN_THE_CURATOR_BOSS_POSITION = Position(-11139.463f, -1884. void RaidKarazhanHelpers::MarkTargetWithSkull(Unit* target) { if (!target) + { return; + } if (Group* group = bot->GetGroup()) { @@ -43,15 +45,21 @@ void RaidKarazhanHelpers::MarkTargetWithSkull(Unit* target) 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; } @@ -65,8 +73,11 @@ Unit* RaidKarazhanHelpers::GetFirstAliveUnitByEntry(uint32 entry) Unit* unit = botAI->GetUnit(npcGuid); if (unit && unit->IsAlive() && unit->GetEntry() == entry) + { return unit; + } } + return nullptr; } @@ -79,10 +90,14 @@ Unit* RaidKarazhanHelpers::GetNearestPlayerInRadius(float radius) Player* member = itr->GetSource(); if (!member || !member->IsAlive() || member == bot) + { continue; + } if (bot->GetExactDist2d(member) < radius) + { return member; + } } } return nullptr; @@ -92,12 +107,13 @@ 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) { - bot->Yell("I will not move when Flame Wreath is cast or the raid blows up.", LANG_UNIVERSAL); return true; } @@ -107,9 +123,13 @@ bool RaidKarazhanHelpers::IsFlameWreathActive() { Player* member = itr->GetSource(); if (!member || !member->IsAlive()) + { continue; + } if (member->HasAura(SPELL_AURA_FLAME_WREATH)) + { return true; + } } } return false; @@ -125,7 +145,9 @@ std::vector RaidKarazhanHelpers::GetBlueBlockers() { Player* member = itr->GetSource(); if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member)) + { continue; + } bool isDps = botAI->IsDps(member); bool isWarrior = member->getClass() == CLASS_WARRIOR; @@ -155,7 +177,9 @@ std::vector RaidKarazhanHelpers::GetGreenBlockers() { 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); @@ -188,7 +212,9 @@ Position RaidKarazhanHelpers::GetPositionOnBeam(Unit* boss, Unit* portal, float float length = sqrt(dx*dx + dy*dy); if (length == 0.0f) + { return Position(bx, by, bz); + } dx /= length; dy /= length; @@ -213,26 +239,37 @@ std::tuple RaidKarazhanHelpers::GetCurrentBeamBlocker { Player* member = itr->GetSource(); if (!member || !member->IsAlive()) + { continue; - PlayerbotAI* memberAI = sPlayerbotsMgr->GetPlayerbotAI(member); - if (!memberAI || !memberAI->IsTank(member)) + } + if (!botAI->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); } @@ -246,7 +283,9 @@ std::vector RaidKarazhanHelpers::GetAllVoidZones() { Unit* unit = botAI->GetUnit(npcGuid); if (!unit || unit->GetEntry() != NPC_VOID_ZONE) + { continue; + } float dist = bot->GetExactDist2d(unit); if (dist < radius) @@ -265,7 +304,9 @@ bool RaidKarazhanHelpers::IsSafePosition(float x, float y, float z, float dist = std::sqrt(std::pow(x - hazard->GetPositionX(), 2) + std::pow(y - hazard->GetPositionY(), 2)); if (dist < hazardRadius) + { return false; + } } return true; } @@ -280,7 +321,9 @@ std::vector RaidKarazhanHelpers::GetSpawnedInfernals() const Unit* unit = botAI->GetUnit(npcGuid); if (unit && unit->GetEntry() == NPC_NETHERSPITE_INFERNAL) + { infernals.push_back(unit); + } } return infernals; } @@ -295,7 +338,10 @@ bool RaidKarazhanHelpers::IsStraightPathSafe(const Position& start, const Positi 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) { float t = checkDist / totalDist; @@ -306,56 +352,54 @@ bool RaidKarazhanHelpers::IsStraightPathSafe(const Position& start, const Positi { float hazardDist = std::sqrt(std::pow(checkX - hazard->GetPositionX(), 2) + std::pow(checkY - hazard->GetPositionY(), 2)); if (hazardDist < hazardRadius) + { return false; + } } } + return true; } Position RaidKarazhanHelpers::CalculateArcPoint(const Position& current, const Position& target, const Position& center) { float arcFraction = 0.25f; - // Calculate vectors from center to current position and target float currentX = current.GetPositionX() - center.GetPositionX(); float currentY = current.GetPositionY() - center.GetPositionY(); float targetX = target.GetPositionX() - center.GetPositionX(); float targetY = target.GetPositionY() - center.GetPositionY(); - // Calculate distances float currentDist = std::sqrt(currentX * currentX + currentY * currentY); float targetDist = std::sqrt(targetX * targetX + targetY * targetY); if (currentDist == 0.0f || targetDist == 0.0f) + { return current; + } - // Normalize vectors currentX /= currentDist; currentY /= currentDist; targetX /= targetDist; targetY /= targetDist; - // Calculate dot product to find the angle between vectors float dotProduct = currentX * targetX + currentY * targetY; - dotProduct = std::max(-1.0f, std::min(1.0f, dotProduct)); // Clamp to [-1, 1] + dotProduct = std::max(-1.0f, std::min(1.0f, dotProduct)); float angle = std::acos(dotProduct); - // Determine rotation direction (clockwise or counterclockwise) float crossProduct = currentX * targetY - currentY * targetX; - float stepAngle = angle * arcFraction; // Move arcFraction along the arc + float stepAngle = angle * arcFraction; if (crossProduct < 0) - stepAngle = -stepAngle; // Clockwise + { + stepAngle = -stepAngle; + } - // Calculate rotation matrix components float cos_a = std::cos(stepAngle); float sin_a = std::sin(stepAngle); - // Rotate current vector float rotatedX = currentX * cos_a - currentY * sin_a; float rotatedY = currentX * sin_a + currentY * cos_a; - // Smoothing: blend current and target radius float desiredDist = currentDist * 0.9f + targetDist * 0.1f; - // Calculate the new position return Position(center.GetPositionX() + rotatedX * desiredDist, center.GetPositionY() + rotatedY * desiredDist, current.GetPositionZ()); diff --git a/src/strategy/raids/karazhan/RaidKarazhanHelpers.h b/src/strategy/raids/karazhan/RaidKarazhanHelpers.h index 42db6580..2746b09d 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanHelpers.h +++ b/src/strategy/raids/karazhan/RaidKarazhanHelpers.h @@ -8,49 +8,49 @@ enum KarazhanSpells { // Maiden of Virtue - SPELL_REPENTANCE = 29511, + SPELL_REPENTANCE = 29511, // Opera Event - SPELL_LITTLE_RED_RIDING_HOOD = 30756, + 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 + SPELL_FLAME_WREATH = 30004, + SPELL_AURA_FLAME_WREATH = 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, + 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, + SPELL_ENFEEBLE = 30843, }; enum KarazhanNpcs { // Attumen the Huntsman - NPC_ATTUMEN_THE_HUNTSMAN = 16152, // Mounted ID + NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED = 16152, // Terestian Illhoof - NPC_KILREK = 17229, - NPC_DEMON_CHAINS = 17248, + NPC_KILREK = 17229, + NPC_DEMON_CHAINS = 17248, // Shade of Aran - NPC_CONJURED_ELEMENTAL = 17167, + NPC_CONJURED_ELEMENTAL = 17167, // Netherspite - NPC_VOID_ZONE = 16697, - NPC_RED_PORTAL = 17369, - NPC_BLUE_PORTAL = 17368, - NPC_GREEN_PORTAL = 17367, + NPC_VOID_ZONE = 16697, + NPC_RED_PORTAL = 17369, + NPC_BLUE_PORTAL = 17368, + NPC_GREEN_PORTAL = 17367, // Prince Malchezaar - NPC_NETHERSPITE_INFERNAL = 17646, + NPC_NETHERSPITE_INFERNAL = 17646, }; extern const Position KARAZHAN_MAIDEN_OF_VIRTUE_BOSS_POSITION; diff --git a/src/strategy/raids/karazhan/RaidKarazhanMultipliers.cpp b/src/strategy/raids/karazhan/RaidKarazhanMultipliers.cpp index a1b25ee5..f5306f8b 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanMultipliers.cpp +++ b/src/strategy/raids/karazhan/RaidKarazhanMultipliers.cpp @@ -19,13 +19,17 @@ float KarazhanBigBadWolfMultiplier::GetValue(Action* action) { Unit* boss = AI_VALUE2(Unit*, "find target", "the big bad wolf"); if (!boss) + { return 1.0f; + } if (bot->HasAura(SPELL_LITTLE_RED_RIDING_HOOD)) { if ((dynamic_cast(action) && !dynamic_cast(action)) || (dynamic_cast(action))) - return 0.0f; + { + return 0.0f; + } } return 1.0f; } @@ -34,18 +38,24 @@ 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) + { return 0.0f; + } } } @@ -67,7 +77,9 @@ float KarazhanShadeOfAranMultiplier::GetValue(Action* action) if (flameWreathActive) { if (dynamic_cast(action) || IsChargeAction(action)) + { return 0.0f; + } } return 1.0f; } @@ -76,7 +88,9 @@ float KarazhanNetherspiteBlueAndGreenBeamMultiplier::GetValue(Action* action) { Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); if (!boss || !boss->IsAlive()) + { return 1.0f; + } RaidKarazhanHelpers karazhanHelper(botAI); auto [redBlocker /*unused*/, greenBlocker, blueBlocker] = karazhanHelper.GetCurrentBeamBlockers(); @@ -88,18 +102,25 @@ float KarazhanNetherspiteBlueAndGreenBeamMultiplier::GetValue(Action* action) bool inBeam = false; for (Unit* portal : {bluePortal, greenPortal}) { - if (!portal) continue; + 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; + 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 > 0.0f && t < length) { + if (distToBeam < 5.0f && t > 0.0f && t < length) + { inBeam = true; break; } @@ -118,7 +139,9 @@ float KarazhanNetherspiteBlueAndGreenBeamMultiplier::GetValue(Action* action) if (!inVoidZone) { if (dynamic_cast(action) || IsChargeAction(action)) + { return 0.0f; + } } } } @@ -129,7 +152,9 @@ float KarazhanNetherspiteRedBeamMultiplier::GetValue(Action* action) { Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); if (!boss || !boss->IsAlive()) + { return 1.0f; + } RaidKarazhanHelpers karazhanHelper(botAI); auto [redBlocker, greenBlocker /*unused*/, blueBlocker /*unused*/] = karazhanHelper.GetCurrentBeamBlockers(); @@ -177,7 +202,9 @@ float KarazhanNetherspiteRedBeamMultiplier::GetValue(Action* action) if (distToTarget < positionTolerance) { if (dynamic_cast(action) || IsChargeAction(action)) + { return 0.0f; + } } } } @@ -187,13 +214,14 @@ float KarazhanNetherspiteRedBeamMultiplier::GetValue(Action* action) float KarazhanPrinceMalchezaarMultiplier::GetValue(Action* action) { Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar"); - if (!boss) - return 1.0f; - if (boss && bot->HasAura(SPELL_ENFEEBLE)) + if (boss && botAI->IsMelee(bot) && bot->HasAura(SPELL_ENFEEBLE)) { - if (IsChargeAction(action)) - return 0.0f; + if (dynamic_cast(action)) + { + return 1.0f; + } + return 0.0f; } return 1.0f; } diff --git a/src/strategy/raids/karazhan/RaidKarazhanTriggers.cpp b/src/strategy/raids/karazhan/RaidKarazhanTriggers.cpp index 55aa175e..f70ae70f 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanTriggers.cpp +++ b/src/strategy/raids/karazhan/RaidKarazhanTriggers.cpp @@ -6,8 +6,9 @@ bool KarazhanAttumenTheHuntsmanTrigger::IsActive() { RaidKarazhanHelpers helpers(botAI); - Unit* boss = helpers.GetFirstAliveUnitByEntry(NPC_ATTUMEN_THE_HUNTSMAN); - return boss != nullptr; + Unit* boss = helpers.GetFirstAliveUnitByEntry(NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED); + + return boss && boss->IsAlive(); } bool KarazhanMoroesTrigger::IsActive() @@ -20,36 +21,27 @@ bool KarazhanMoroesTrigger::IsActive() 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; + return ((moroes && moroes->IsAlive()) || + (dorothea && dorothea->IsAlive()) || + (catriona && catriona->IsAlive()) || + (keira && keira->IsAlive()) || + (rafe && rafe->IsAlive()) || + (robin && robin->IsAlive()) || + (crispin && crispin->IsAlive())); } bool KarazhanMaidenOfVirtueTrigger::IsActive() { Unit* boss = AI_VALUE2(Unit*, "find target", "maiden of virtue"); - if (!boss || !boss->IsAlive()) - return false; - - return true; + return boss && boss->IsAlive(); } bool KarazhanBigBadWolfTrigger::IsActive() { Unit* boss = AI_VALUE2(Unit*, "find target", "the big bad wolf"); - if (!boss || !boss->IsAlive()) - return false; - - return true; + return boss && boss->IsAlive(); } bool KarazhanRomuloAndJulianneTrigger::IsActive() @@ -57,10 +49,7 @@ 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; + return julianne && julianne->IsAlive() && romulo && romulo->IsAlive(); } bool KarazhanWizardOfOzTrigger::IsActive() @@ -72,63 +61,45 @@ bool KarazhanWizardOfOzTrigger::IsActive() 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; + return ((dorothee && dorothee->IsAlive()) || + (tito && tito->IsAlive()) || + (roar && roar->IsAlive()) || + (strawman && strawman->IsAlive()) || + (tinhead && tinhead->IsAlive()) || + (crone && crone->IsAlive())); } bool KarazhanTheCuratorTrigger::IsActive() { Unit* boss = AI_VALUE2(Unit*, "find target", "the curator"); - if (!boss || !boss->IsAlive()) - return false; - - return true; + return boss && boss->IsAlive(); } bool KarazhanTerestianIllhoofTrigger::IsActive() { Unit* boss = AI_VALUE2(Unit*, "find target", "terestian illhoof"); - if (!boss || !boss->IsAlive()) - return false; - - return true; + return boss && boss->IsAlive(); } bool KarazhanShadeOfAranTrigger::IsActive() { Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran"); - if (!boss) - return false; - - return true; + return boss && boss->IsAlive(); } bool KarazhanNetherspiteTrigger::IsActive() { Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); - if (!boss || !boss->IsAlive()) - return false; - - return true; + return boss && boss->IsAlive(); } bool KarazhanPrinceMalchezaarTrigger::IsActive() { Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar"); - if (!boss || !boss->IsAlive()) - return false; - - return true; + return boss && boss->IsAlive(); } From 59555b2248ea24bb01f1701bfd6407e14e4c69ef Mon Sep 17 00:00:00 2001 From: crow Date: Sat, 27 Sep 2025 23:03:15 -0500 Subject: [PATCH 7/9] Moved yells to database --- ...9_28_00_netherspite_beam_blocker_texts.sql | 29 +++ .../raids/karazhan/RaidKarazhanActions.cpp | 191 +++++++++++------- .../raids/karazhan/RaidKarazhanHelpers.cpp | 60 ++---- .../karazhan/RaidKarazhanMultipliers.cpp | 13 +- 4 files changed, 179 insertions(+), 114 deletions(-) create mode 100644 data/sql/playerbots/updates/2025_09_28_00_netherspite_beam_blocker_texts.sql diff --git a/data/sql/playerbots/updates/2025_09_28_00_netherspite_beam_blocker_texts.sql b/data/sql/playerbots/updates/2025_09_28_00_netherspite_beam_blocker_texts.sql new file mode 100644 index 00000000..ece4d100 --- /dev/null +++ b/data/sql/playerbots/updates/2025_09_28_00_netherspite_beam_blocker_texts.sql @@ -0,0 +1,29 @@ +DELETE FROM ai_playerbot_texts WHERE name IN ( + 'netherspite_beam_blocking_red', + 'netherspite_beam_blocking_blue', + 'netherspite_beam_blocking_green', + 'netherspite_beam_leaving_blue', + 'netherspite_beam_leaving_green' +); + +DELETE FROM ai_playerbot_texts_chance WHERE name IN ( + 'netherspite_beam_blocking_red', + 'netherspite_beam_blocking_blue', + 'netherspite_beam_blocking_green', + 'netherspite_beam_leaving_blue', + 'netherspite_beam_leaving_green' +); + +INSERT INTO ai_playerbot_texts (name, text, say_type, reply_type, text_loc1, text_loc2, text_loc3, text_loc4, text_loc5, text_loc6, text_loc7, text_loc8) VALUES + ('netherspite_beam_blocking_red', '%player is moving to block the red beam!', 0, 0, '', '', '', '', '', '', '', ''), + ('netherspite_beam_blocking_blue', '%player is moving to block the blue beam!', 0, 0, '', '', '', '', '', '', '', ''), + ('netherspite_beam_blocking_green', '%player is moving to block the green beam!', 0, 0, '', '', '', '', '', '', '', ''), + ('netherspite_beam_leaving_blue', '%player is leaving the blue beam--next blocker up!', 0, 0, '', '', '', '', '', '', '', ''), + ('netherspite_beam_leaving_green', '%player is leaving the green beam--next blocker up!', 0, 0, '', '', '', '', '', '', '', ''); + +INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES + ('netherspite_beam_blocking_red', 100), + ('netherspite_beam_blocking_blue', 100), + ('netherspite_beam_blocking_green', 100), + ('netherspite_beam_leaving_blue', 100), + ('netherspite_beam_leaving_green', 100); diff --git a/src/strategy/raids/karazhan/RaidKarazhanActions.cpp b/src/strategy/raids/karazhan/RaidKarazhanActions.cpp index eb23d72c..59672e84 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanActions.cpp +++ b/src/strategy/raids/karazhan/RaidKarazhanActions.cpp @@ -101,6 +101,7 @@ bool KarazhanMaidenOfVirtuePositionBossAction::Execute(Event event) bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); } } + return false; } @@ -146,6 +147,7 @@ bool KarazhanMaidenOfVirtuePositionRangedAction::Execute(Event event) KARAZHAN_MAIDEN_OF_VIRTUE_RANGED_POSITION[index].GetPositionY(), bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); } + return false; } @@ -176,9 +178,11 @@ bool KarazhanBigBadWolfPositionBossAction::Execute(Event event) { return false; } + return MoveTo(bot->GetMapId(), mX, mY, bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); } + return false; } @@ -193,7 +197,6 @@ bool KarazhanBigBadWolfPositionBossAction::isUseful() 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) @@ -201,6 +204,7 @@ bool KarazhanBigBadWolfRunAwayAction::Execute(Event event) 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); } @@ -257,13 +261,8 @@ bool KarazhanWizardOfOzMarkTargetAction::Execute(Event event) 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) + if (!strawman || !strawman->IsAlive() || !group) { return false; } @@ -271,18 +270,9 @@ bool KarazhanWizardOfOzScorchStrawmanAction::Execute(Event event) 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) - { + if (!member || !member->IsAlive() || member->getClass() != CLASS_MAGE || !mageAI) + { continue; } if (mageAI->CanCastSpell("scorch", strawman)) @@ -290,6 +280,7 @@ bool KarazhanWizardOfOzScorchStrawmanAction::Execute(Event event) mageAI->CastSpell("scorch", strawman); } } + return false; } @@ -325,6 +316,7 @@ bool KarazhanTheCuratorPositionBossAction::Execute(Event event) false); } } + return false; } @@ -345,6 +337,7 @@ bool KarazhanTheCuratorSpreadRangedAction::Execute(Event event) { return FleePosition(nearestPlayer->GetPosition(), minDistance); } + return false; } @@ -391,6 +384,7 @@ bool KarazhanShadeOfAranArcaneExplosionRunAwayAction::Execute(Event event) { return MoveAway(boss, safeDistance - distance); } + return false; } @@ -415,6 +409,7 @@ bool KarazhanShadeOfAranFlameWreathStopMovementAction::Execute(Event event) } return true; } + return false; } @@ -422,15 +417,14 @@ bool KarazhanShadeOfAranMarkConjuredElementalAction::Execute(Event event) { RaidKarazhanHelpers karazhanHelper(botAI); Unit* boss = AI_VALUE2(Unit*, "find target", "shade of aran"); - if (!boss || !boss->IsAlive()) - { - return false; - } Unit* target = karazhanHelper.GetFirstAliveUnitByEntry(NPC_CONJURED_ELEMENTAL); - if (!target || !target->IsAlive() || target->HasAura(SPELL_WARLOCK_BANISH)) + + if (!boss || !boss->IsAlive() || + !target || !target->IsAlive() || target->HasAura(SPELL_WARLOCK_BANISH)) { return false; } + karazhanHelper.MarkTargetWithSkull(target); return false; @@ -464,21 +458,17 @@ bool KarazhanShadeOfAranSpreadRangedAction::Execute(Event event) { 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)); + return boss && boss->IsAlive() && 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) @@ -498,29 +488,33 @@ bool KarazhanNetherspiteBlockRedBeamAction::Execute(Event event) 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)) + if (!member || !member->IsAlive() || !memberAI || !memberAI->IsTank(member) || + member->HasAura(SPELL_NETHER_EXHAUSTION_RED)) { continue; } eligibleTank = member; break; } - RaidKarazhanHelpers karazhanHelper(botAI); Position beamPos = karazhanHelper.GetPositionOnBeam(boss, redPortal, 18.0f); if (bot == eligibleTank) { - bot->Yell("I'm moving to block the red beam!", LANG_UNIVERSAL); + std::string msg; + std::map ph; + ph["%player"] = bot->GetName(); + bool got = sPlayerbotTextMgr->GetBotText("netherspite_beam_blocking_red", msg, ph); + if (got && !msg.empty()) + { + bot->Yell(msg, LANG_UNIVERSAL); + } + else + { + bot->Yell(bot->GetName() + " is moving to block the red beam!", LANG_UNIVERSAL); + } + ObjectGuid botGuid = bot->GetGUID(); uint32 intervalSecs = 5; @@ -539,6 +533,7 @@ bool KarazhanNetherspiteBlockRedBeamAction::Execute(Event event) return MoveTo(bot->GetMapId(), beamPos.GetPositionX(), beamPos.GetPositionY(), beamPos.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_FORCED); } + else { float bx = boss->GetPositionX(); @@ -552,6 +547,7 @@ bool KarazhanNetherspiteBlockRedBeamAction::Execute(Event event) { return false; } + dx /= length; dy /= length; float perpDx = -dy; @@ -564,6 +560,7 @@ bool KarazhanNetherspiteBlockRedBeamAction::Execute(Event event) MovementPriority::MOVEMENT_FORCED); } } + return false; } @@ -576,23 +573,21 @@ bool KarazhanNetherspiteBlockRedBeamAction::isUseful() static std::map lastBossBanishState; bool bossIsBanished = boss && boss->HasAura(SPELL_NETHERSPITE_BANISHED); - if (!boss || !redPortal) + if (!boss || !redPortal || bossIsBanished) { return false; } - if (lastBossBanishState[botGuid] != bossIsBanished) + + if (lastBossBanishState[botGuid] != bossIsBanished) { - if (!bossIsBanished) + if (!bossIsBanished) { beamMoveTimes[botGuid] = 0; lastBeamMoveSideways[botGuid] = false; } lastBossBanishState[botGuid] = bossIsBanished; } - if (bossIsBanished) - { - return false; - } + return true; } @@ -613,18 +608,42 @@ bool KarazhanNetherspiteBlockBlueBeamAction::Execute(Event event) if (wasBlocking && !isBlockingNow) { - bot->Yell("I'm leaving the blue beam--next blocker up!", LANG_UNIVERSAL); + std::string msg; + std::map ph; + ph["%player"] = bot->GetName(); + bool got = sPlayerbotTextMgr->GetBotText("netherspite_beam_leaving_blue", msg, ph); + if (got && !msg.empty()) + { + bot->Yell(msg, LANG_UNIVERSAL); + } + else + { + bot->Yell(bot->GetName() + " is leaving the blue beam--next blocker up!", LANG_UNIVERSAL); + } + wasBlockingBlueBeam[botGuid] = false; + return false; } - else if (isBlockingNow) - { - wasBlockingBlueBeam[botGuid] = true; - } + if (isBlockingNow) { if (!wasBlocking) - bot->Yell("I'm moving to block the blue beam!", LANG_UNIVERSAL); + { + std::string msg; + std::map ph; + ph["%player"] = bot->GetName(); + bool got = sPlayerbotTextMgr->GetBotText("netherspite_beam_blocking_blue", msg, ph); + if (got && !msg.empty()) + { + bot->Yell(msg, LANG_UNIVERSAL); + } + else + { + bot->Yell(bot->GetName() + " is moving to block the blue beam!", LANG_UNIVERSAL); + } + } wasBlockingBlueBeam[botGuid] = true; + std::vector voidZones = karazhanHelper.GetAllVoidZones(); float bx = boss->GetPositionX(); float by = boss->GetPositionY(); @@ -677,12 +696,11 @@ bool KarazhanNetherspiteBlockBlueBeamAction::Execute(Event event) 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; - } + + wasBlockingBlueBeam[botGuid] = false; return false; } @@ -712,14 +730,42 @@ bool KarazhanNetherspiteBlockGreenBeamAction::Execute(Event event) if (wasBlocking && !isBlockingNow) { - bot->Yell("I'm leaving the green beam--next blocker up!", LANG_UNIVERSAL); + std::string msg; + std::map ph; + ph["%player"] = bot->GetName(); + bool got = sPlayerbotTextMgr->GetBotText("netherspite_beam_leaving_green", msg, ph); + if (got && !msg.empty()) + { + bot->Yell(msg, LANG_UNIVERSAL); + } + else + { + bot->Yell(bot->GetName() + " TESTRUN is leaving the green beam--next blocker up!", LANG_UNIVERSAL); + } + wasBlockingGreenBeam[botGuid] = false; + return false; } - else if (isBlockingNow) + + if (isBlockingNow) { if (!wasBlocking) - bot->Yell("I'm moving to block the green beam!", LANG_UNIVERSAL); + { + std::string msg; + std::map ph; + ph["%player"] = bot->GetName(); + bool got = sPlayerbotTextMgr->GetBotText("netherspite_beam_blocking_green", msg, ph); + if (got && !msg.empty()) + { + bot->Yell(msg, LANG_UNIVERSAL); + } + else + { + bot->Yell(bot->GetName() + " TESTRUN is moving to block the green beam!", LANG_UNIVERSAL); + } + } wasBlockingGreenBeam[botGuid] = true; + std::vector voidZones = karazhanHelper.GetAllVoidZones(); float bx = boss->GetPositionX(); float by = boss->GetPositionY(); @@ -733,6 +779,7 @@ bool KarazhanNetherspiteBlockGreenBeamAction::Execute(Event event) { return false; } + dx /= length; dy /= length; float bestDist = 150.0f; @@ -747,7 +794,7 @@ bool KarazhanNetherspiteBlockGreenBeamAction::Execute(Event event) for (Unit* voidZone : voidZones) { float voidZoneDist = sqrt(pow(candidateX - voidZone->GetPositionX(), 2) + - pow(candidateY - voidZone->GetPositionY(), 2)); + pow(candidateY - voidZone->GetPositionY(), 2)); if (voidZoneDist < 4.0f) { outsideAllVoidZones = false; @@ -771,12 +818,11 @@ bool KarazhanNetherspiteBlockGreenBeamAction::Execute(Event event) 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; - } + + wasBlockingGreenBeam[botGuid] = false; return false; } @@ -835,7 +881,6 @@ bool KarazhanNetherspiteAvoidBeamAndVoidZoneAction::Execute(Event event) float length = sqrt(dx*dx + dy*dy); beams.push_back({greenPortal, 0.0f, length}); } - bool nearBeam = false; for (const auto& beam : beams) { @@ -844,7 +889,7 @@ bool KarazhanNetherspiteAvoidBeamAndVoidZoneAction::Execute(Event event) float dx = px - bx, dy = py - by; float length = sqrt(dx*dx + dy*dy); if (length == 0.0f) - {continue; + { continue; } dx /= length; dy /= length; @@ -858,7 +903,6 @@ bool KarazhanNetherspiteAvoidBeamAndVoidZoneAction::Execute(Event event) break; } } - if (!nearVoidZone && !nearBeam) { return false; @@ -936,12 +980,14 @@ bool KarazhanNetherspiteAvoidBeamAndVoidZoneAction::isUseful() { return false; } + RaidKarazhanHelpers karazhanHelper(botAI); auto [redBlocker, greenBlocker, blueBlocker] = karazhanHelper.GetCurrentBeamBlockers(); if (bot == redBlocker || bot == blueBlocker || bot == greenBlocker) { return false; } + return true; } @@ -957,6 +1003,7 @@ bool KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction::Execute(Event event) return FleePosition(vz->GetPosition(), 4.0f); } } + return false; } @@ -967,6 +1014,7 @@ bool KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction::isUseful() { return false; } + RaidKarazhanHelpers karazhanHelper(botAI); std::vector voidZones = karazhanHelper.GetAllVoidZones(); for (Unit* vz : voidZones) @@ -976,6 +1024,7 @@ bool KarazhanNetherspiteBanishPhaseAvoidVoidZoneAction::isUseful() return true; } } + return false; } @@ -1037,6 +1086,7 @@ bool KarazhanPrinceMalchezaarNonTankAvoidHazardAction::Execute(Event event) return MoveTo(bot->GetMapId(), bestDestX, bestDestY, bestDestZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED); } + return false; } @@ -1127,6 +1177,7 @@ bool KarazhanPrinceMalchezaarNonTankAvoidHazardAction::Execute(Event event) } } } + return false; } @@ -1165,6 +1216,7 @@ bool KarazhanPrinceMalchezaarTankAvoidHazardAction::Execute(Event event) break; } } + if (nearInfernal) { for (int i = 0; i < numAngles; ++i) @@ -1238,6 +1290,7 @@ bool KarazhanPrinceMalchezaarTankAvoidHazardAction::Execute(Event event) MovementPriority::MOVEMENT_COMBAT); } } + return false; } diff --git a/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp b/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp index 939e73b9..39e1cecc 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp +++ b/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp @@ -100,18 +100,14 @@ Unit* RaidKarazhanHelpers::GetNearestPlayerInRadius(float radius) } } } + 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); + Spell* currentSpell = boss ? boss->GetCurrentSpell(CURRENT_GENERIC_SPELL) : nullptr; if (currentSpell && currentSpell->m_spellInfo && currentSpell->m_spellInfo->Id == SPELL_FLAME_WREATH) { return true; @@ -132,6 +128,7 @@ bool RaidKarazhanHelpers::IsFlameWreathActive() } } } + return false; } @@ -148,20 +145,19 @@ std::vector RaidKarazhanHelpers::GetBlueBlockers() { 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; } @@ -180,7 +176,6 @@ std::vector RaidKarazhanHelpers::GetGreenBlockers() { continue; } - bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_GREEN); Aura* greenBuff = member->GetAura(SPELL_GREEN_BEAM_DEBUFF); bool overStack = greenBuff && greenBuff->GetStackAmount() >= 25; @@ -189,13 +184,13 @@ std::vector RaidKarazhanHelpers::GetGreenBlockers() bool eligibleRogueWarrior = (isRogue || isDpsWarrior) && !hasExhaustion; bool isHealer = botAI->IsHeal(member); bool eligibleHealer = isHealer && !hasExhaustion && !overStack; - if (eligibleRogueWarrior || eligibleHealer) { greenBlockers.push_back(member); } } } + return greenBlockers; } @@ -210,7 +205,6 @@ Position RaidKarazhanHelpers::GetPositionOnBeam(Unit* boss, Unit* portal, float float dx = px - bx; float dy = py - by; float length = sqrt(dx*dx + dy*dy); - if (length == 0.0f) { return Position(bx, by, bz); @@ -218,7 +212,6 @@ Position RaidKarazhanHelpers::GetPositionOnBeam(Unit* boss, Unit* portal, float dx /= length; dy /= length; - float targetX = bx + dx * distanceFromBoss; float targetY = by + dy * distanceFromBoss; float targetZ = bz; @@ -233,38 +226,29 @@ std::tuple RaidKarazhanHelpers::GetCurrentBeamBlocker Player* blueBlocker = nullptr; std::vector redBlockers; - if (Group* group = bot->GetGroup()) +if (Group* group = bot->GetGroup()) +{ + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) { - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + Player* member = itr->GetSource(); + PlayerbotAI* memberAI = sPlayerbotsMgr->GetPlayerbotAI(member); + if (!member || !member->IsAlive() || !memberAI || !memberAI->IsTank(member) || + member->HasAura(SPELL_NETHER_EXHAUSTION_RED)) { - Player* member = itr->GetSource(); - if (!member || !member->IsAlive()) - { - continue; - } - if (!botAI->IsTank(member)) - { - continue; - } - if (member->HasAura(SPELL_NETHER_EXHAUSTION_RED)) - { - continue; - } - redBlockers.push_back(member); + 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()) { @@ -286,13 +270,13 @@ std::vector RaidKarazhanHelpers::GetAllVoidZones() { continue; } - float dist = bot->GetExactDist2d(unit); if (dist < radius) { voidZones.push_back(unit); } } + return voidZones; } @@ -302,12 +286,12 @@ bool RaidKarazhanHelpers::IsSafePosition(float x, float y, float z, 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; } @@ -315,16 +299,15 @@ 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; } @@ -341,7 +324,7 @@ bool RaidKarazhanHelpers::IsStraightPathSafe(const Position& start, const Positi { return true; } - + for (float checkDist = 0.0f; checkDist <= totalDist; checkDist += stepSize) { float t = checkDist / totalDist; @@ -357,7 +340,7 @@ bool RaidKarazhanHelpers::IsStraightPathSafe(const Position& start, const Positi } } } - + return true; } @@ -384,7 +367,6 @@ Position RaidKarazhanHelpers::CalculateArcPoint(const Position& current, const P float dotProduct = currentX * targetX + currentY * targetY; dotProduct = std::max(-1.0f, std::min(1.0f, dotProduct)); float angle = std::acos(dotProduct); - float crossProduct = currentX * targetY - currentY * targetX; float stepAngle = angle * arcFraction; if (crossProduct < 0) @@ -394,10 +376,8 @@ Position RaidKarazhanHelpers::CalculateArcPoint(const Position& current, const P float cos_a = std::cos(stepAngle); float sin_a = std::sin(stepAngle); - float rotatedX = currentX * cos_a - currentY * sin_a; float rotatedY = currentX * sin_a + currentY * cos_a; - float desiredDist = currentDist * 0.9f + targetDist * 0.1f; return Position(center.GetPositionX() + rotatedX * desiredDist, diff --git a/src/strategy/raids/karazhan/RaidKarazhanMultipliers.cpp b/src/strategy/raids/karazhan/RaidKarazhanMultipliers.cpp index f5306f8b..f326cf00 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanMultipliers.cpp +++ b/src/strategy/raids/karazhan/RaidKarazhanMultipliers.cpp @@ -31,6 +31,7 @@ float KarazhanBigBadWolfMultiplier::GetValue(Action* action) return 0.0f; } } + return 1.0f; } @@ -81,6 +82,7 @@ float KarazhanShadeOfAranMultiplier::GetValue(Action* action) return 0.0f; } } + return 1.0f; } @@ -125,6 +127,7 @@ float KarazhanNetherspiteBlueAndGreenBeamMultiplier::GetValue(Action* action) break; } } + if (inBeam) { std::vector voidZones = karazhanHelper.GetAllVoidZones(); @@ -145,6 +148,7 @@ float KarazhanNetherspiteBlueAndGreenBeamMultiplier::GetValue(Action* action) } } } + return 1.0f; } @@ -208,6 +212,7 @@ float KarazhanNetherspiteRedBeamMultiplier::GetValue(Action* action) } } } + return 1.0f; } @@ -215,13 +220,11 @@ float KarazhanPrinceMalchezaarMultiplier::GetValue(Action* action) { Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar"); - if (boss && botAI->IsMelee(bot) && bot->HasAura(SPELL_ENFEEBLE)) + if (boss && botAI->IsMelee(bot) && bot->HasAura(SPELL_ENFEEBLE) && + !dynamic_cast(action)) { - if (dynamic_cast(action)) - { - return 1.0f; - } return 0.0f; } + return 1.0f; } From 55b58a2ef63446b8280fcc88220b225b84308b2a Mon Sep 17 00:00:00 2001 From: crow Date: Sun, 28 Sep 2025 20:31:57 -0500 Subject: [PATCH 8/9] Simplify checks & implement getbottext method --- .../raids/karazhan/RaidKarazhanActions.cpp | 101 +++++------------- .../raids/karazhan/RaidKarazhanHelpers.cpp | 25 +++-- .../karazhan/RaidKarazhanMultipliers.cpp | 6 +- 3 files changed, 43 insertions(+), 89 deletions(-) diff --git a/src/strategy/raids/karazhan/RaidKarazhanActions.cpp b/src/strategy/raids/karazhan/RaidKarazhanActions.cpp index 59672e84..35b1e285 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanActions.cpp +++ b/src/strategy/raids/karazhan/RaidKarazhanActions.cpp @@ -1,9 +1,10 @@ #include "RaidKarazhanActions.h" #include "RaidKarazhanHelpers.h" #include "AiObjectContext.h" -#include "Playerbots.h" -#include "PlayerbotMgr.h" #include "PlayerbotAI.h" +#include "PlayerbotMgr.h" +#include "PlayerbotTextMgr.h" +#include "Playerbots.h" #include "Position.h" namespace @@ -261,26 +262,16 @@ bool KarazhanWizardOfOzMarkTargetAction::Execute(Event event) bool KarazhanWizardOfOzScorchStrawmanAction::Execute(Event event) { Unit* strawman = AI_VALUE2(Unit*, "find target", "strawman"); - Group* group = bot->GetGroup(); - if (!strawman || !strawman->IsAlive() || !group) + if (!strawman || !strawman->IsAlive() || bot->getClass() != CLASS_MAGE) { return false; } - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + if (botAI->CanCastSpell("scorch", strawman)) { - Player* member = itr->GetSource(); - PlayerbotAI* mageAI = sPlayerbotsMgr->GetPlayerbotAI(member); - if (!member || !member->IsAlive() || member->getClass() != CLASS_MAGE || !mageAI) - { - continue; - } - if (mageAI->CanCastSpell("scorch", strawman)) - { - mageAI->CastSpell("scorch", strawman); - } + botAI->CastSpell("scorch", strawman); } - + return false; } @@ -471,8 +462,8 @@ bool KarazhanShadeOfAranSpreadRangedAction::isUseful() !(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 +// One tank bot per phase will dance in and out of the red beam (5 seconds in, 5 seconds out) +// Tank bots will ignore void zones--their positioning is too important bool KarazhanNetherspiteBlockRedBeamAction::Execute(Event event) { Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); @@ -488,8 +479,7 @@ bool KarazhanNetherspiteBlockRedBeamAction::Execute(Event event) for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) { Player* member = itr->GetSource(); - PlayerbotAI* memberAI = sPlayerbotsMgr->GetPlayerbotAI(member); - if (!member || !member->IsAlive() || !memberAI || !memberAI->IsTank(member) || + if (!member || !member->IsAlive() || !botAI->IsTank(member) || !GET_PLAYERBOT_AI(member) || member->HasAura(SPELL_NETHER_EXHAUSTION_RED)) { continue; @@ -502,18 +492,11 @@ bool KarazhanNetherspiteBlockRedBeamAction::Execute(Event event) if (bot == eligibleTank) { - std::string msg; std::map ph; ph["%player"] = bot->GetName(); - bool got = sPlayerbotTextMgr->GetBotText("netherspite_beam_blocking_red", msg, ph); - if (got && !msg.empty()) - { - bot->Yell(msg, LANG_UNIVERSAL); - } - else - { - bot->Yell(bot->GetName() + " is moving to block the red beam!", LANG_UNIVERSAL); - } + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "netherspite_beam_blocking_red", "%player is moving to block the red beam!", ph); + bot->Yell(text, LANG_UNIVERSAL); ObjectGuid botGuid = bot->GetGUID(); uint32 intervalSecs = 5; @@ -591,7 +574,7 @@ bool KarazhanNetherspiteBlockRedBeamAction::isUseful() return true; } -// Two non-Rogue/Warrior DPS will block the blue beam for each phase (swap at 25 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) { @@ -608,18 +591,11 @@ bool KarazhanNetherspiteBlockBlueBeamAction::Execute(Event event) if (wasBlocking && !isBlockingNow) { - std::string msg; std::map ph; ph["%player"] = bot->GetName(); - bool got = sPlayerbotTextMgr->GetBotText("netherspite_beam_leaving_blue", msg, ph); - if (got && !msg.empty()) - { - bot->Yell(msg, LANG_UNIVERSAL); - } - else - { - bot->Yell(bot->GetName() + " is leaving the blue beam--next blocker up!", LANG_UNIVERSAL); - } + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "netherspite_beam_leaving_blue", "%player is leaving the blue beam--next blocker up!", ph); + bot->Yell(text, LANG_UNIVERSAL); wasBlockingBlueBeam[botGuid] = false; return false; @@ -629,18 +605,11 @@ bool KarazhanNetherspiteBlockBlueBeamAction::Execute(Event event) { if (!wasBlocking) { - std::string msg; std::map ph; ph["%player"] = bot->GetName(); - bool got = sPlayerbotTextMgr->GetBotText("netherspite_beam_blocking_blue", msg, ph); - if (got && !msg.empty()) - { - bot->Yell(msg, LANG_UNIVERSAL); - } - else - { - bot->Yell(bot->GetName() + " is moving to block the blue beam!", LANG_UNIVERSAL); - } + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "netherspite_beam_blocking_blue", "%player is moving to block the blue beam!", ph); + bot->Yell(text, LANG_UNIVERSAL); } wasBlockingBlueBeam[botGuid] = true; @@ -712,8 +681,8 @@ bool KarazhanNetherspiteBlockBlueBeamAction::isUseful() 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 an entire phase (if they begin the phase as the blocker) +// 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) { @@ -730,18 +699,11 @@ bool KarazhanNetherspiteBlockGreenBeamAction::Execute(Event event) if (wasBlocking && !isBlockingNow) { - std::string msg; std::map ph; ph["%player"] = bot->GetName(); - bool got = sPlayerbotTextMgr->GetBotText("netherspite_beam_leaving_green", msg, ph); - if (got && !msg.empty()) - { - bot->Yell(msg, LANG_UNIVERSAL); - } - else - { - bot->Yell(bot->GetName() + " TESTRUN is leaving the green beam--next blocker up!", LANG_UNIVERSAL); - } + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "netherspite_beam_leaving_green", "%player is leaving the green beam--next blocker up!", ph); + bot->Yell(text, LANG_UNIVERSAL); wasBlockingGreenBeam[botGuid] = false; return false; @@ -751,18 +713,11 @@ bool KarazhanNetherspiteBlockGreenBeamAction::Execute(Event event) { if (!wasBlocking) { - std::string msg; std::map ph; ph["%player"] = bot->GetName(); - bool got = sPlayerbotTextMgr->GetBotText("netherspite_beam_blocking_green", msg, ph); - if (got && !msg.empty()) - { - bot->Yell(msg, LANG_UNIVERSAL); - } - else - { - bot->Yell(bot->GetName() + " TESTRUN is moving to block the green beam!", LANG_UNIVERSAL); - } + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "netherspite_beam_blocking_green", "%player is moving to block the green beam!", ph); + bot->Yell(text, LANG_UNIVERSAL); } wasBlockingGreenBeam[botGuid] = true; diff --git a/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp b/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp index 39e1cecc..19dd0b33 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp +++ b/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp @@ -132,7 +132,7 @@ bool RaidKarazhanHelpers::IsFlameWreathActive() return false; } -// Blue beam blockers: non-Rogue/Warrior DPS, no Nether Exhaustion Blue and <25 stacks of Blue Beam debuff +// 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; @@ -162,8 +162,8 @@ std::vector RaidKarazhanHelpers::GetBlueBlockers() } // 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 +// (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; @@ -226,20 +226,19 @@ std::tuple RaidKarazhanHelpers::GetCurrentBeamBlocker Player* blueBlocker = nullptr; std::vector redBlockers; -if (Group* group = bot->GetGroup()) -{ - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + if (Group* group = bot->GetGroup()) { - Player* member = itr->GetSource(); - PlayerbotAI* memberAI = sPlayerbotsMgr->GetPlayerbotAI(member); - if (!member || !member->IsAlive() || !memberAI || !memberAI->IsTank(member) || - member->HasAura(SPELL_NETHER_EXHAUSTION_RED)) + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) { - continue; + 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); } - redBlockers.push_back(member); } -} if (!redBlockers.empty()) { redBlocker = redBlockers.front(); diff --git a/src/strategy/raids/karazhan/RaidKarazhanMultipliers.cpp b/src/strategy/raids/karazhan/RaidKarazhanMultipliers.cpp index f326cf00..01c1c179 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanMultipliers.cpp +++ b/src/strategy/raids/karazhan/RaidKarazhanMultipliers.cpp @@ -27,9 +27,9 @@ float KarazhanBigBadWolfMultiplier::GetValue(Action* action) { if ((dynamic_cast(action) && !dynamic_cast(action)) || (dynamic_cast(action))) - { - return 0.0f; - } + { + return 0.0f; + } } return 1.0f; From 2e1507b794751c7d4d1ef8a7a498ca3b8ebedca3 Mon Sep 17 00:00:00 2001 From: crow Date: Mon, 29 Sep 2025 11:28:13 -0500 Subject: [PATCH 9/9] Various corrections --- .../raids/karazhan/RaidKarazhanActions.cpp | 296 +++++++++--------- .../raids/karazhan/RaidKarazhanHelpers.cpp | 160 ++++++---- .../raids/karazhan/RaidKarazhanHelpers.h | 2 +- .../karazhan/RaidKarazhanMultipliers.cpp | 58 +++- .../raids/karazhan/RaidKarazhanMultipliers.h | 7 + 5 files changed, 302 insertions(+), 221 deletions(-) diff --git a/src/strategy/raids/karazhan/RaidKarazhanActions.cpp b/src/strategy/raids/karazhan/RaidKarazhanActions.cpp index 35b1e285..8ec3ea91 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanActions.cpp +++ b/src/strategy/raids/karazhan/RaidKarazhanActions.cpp @@ -29,7 +29,12 @@ bool KarazhanAttumenTheHuntsmanStackBehindAction::Execute(Event event) 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); + if (bot->GetExactDist2d(rx, ry) > 1.0f) + { + return MoveTo(bot->GetMapId(), rx, ry, z, false, false, false, false, MovementPriority::MOVEMENT_COMBAT); + } + + return false; } bool KarazhanAttumenTheHuntsmanStackBehindAction::isUseful() @@ -83,6 +88,9 @@ bool KarazhanMaidenOfVirtuePositionBossAction::Execute(Event event) float targetY = healer->GetPositionY() + sin(angle) * 6.0f; float targetZ = healer->GetPositionZ(); { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(false); + return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, true, MovementPriority::MOVEMENT_COMBAT); } @@ -98,6 +106,9 @@ bool KarazhanMaidenOfVirtuePositionBossAction::Execute(Event event) float mX = KARAZHAN_MAIDEN_OF_VIRTUE_BOSS_POSITION.GetPositionX() + (dX / distanceToBossPosition) * maxDistance; float mY = KARAZHAN_MAIDEN_OF_VIRTUE_BOSS_POSITION.GetPositionY() + (dY / distanceToBossPosition) * maxDistance; { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(false); + return MoveTo(bot->GetMapId(), mX, mY, bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); } @@ -144,6 +155,9 @@ bool KarazhanMaidenOfVirtuePositionRangedAction::Execute(Event event) const float maxDistance = 2.0f; if (distance > maxDistance) { + 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); @@ -206,6 +220,9 @@ bool KarazhanBigBadWolfRunAwayAction::Execute(Event event) 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); } @@ -326,6 +343,9 @@ bool KarazhanTheCuratorSpreadRangedAction::Execute(Event event) if (nearestPlayer) { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(false); + return FleePosition(nearestPlayer->GetPosition(), minDistance); } @@ -368,11 +388,11 @@ bool KarazhanShadeOfAranArcaneExplosionRunAwayAction::Execute(Event event) const float safeDistance = 20.0f; const float distance = bot->GetDistance2d(boss); - bot->AttackStop(); - bot->InterruptNonMeleeSpells(false); - if (distance < safeDistance) { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(false); + return MoveAway(boss, safeDistance - distance); } @@ -469,36 +489,27 @@ bool KarazhanNetherspiteBlockRedBeamAction::Execute(Event event) Unit* boss = AI_VALUE2(Unit*, "find target", "netherspite"); Unit* redPortal = bot->FindNearestCreature(NPC_RED_PORTAL, 150.0f); - Group* group = bot->GetGroup(); - if (!group) - { - return false; - } - - Player* eligibleTank = nullptr; - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* member = itr->GetSource(); - if (!member || !member->IsAlive() || !botAI->IsTank(member) || !GET_PLAYERBOT_AI(member) || - member->HasAura(SPELL_NETHER_EXHAUSTION_RED)) - { - continue; - } - eligibleTank = member; - break; - } RaidKarazhanHelpers karazhanHelper(botAI); + static std::map wasBlockingRedBeam; + ObjectGuid botGuid = bot->GetGUID(); + auto [redBlocker, greenBlocker, blueBlocker] = karazhanHelper.GetCurrentBeamBlockers(); + bool isBlockingNow = (bot == redBlocker); + bool wasBlocking = wasBlockingRedBeam[botGuid]; + Position beamPos = karazhanHelper.GetPositionOnBeam(boss, redPortal, 18.0f); - if (bot == eligibleTank) + if (isBlockingNow) { - std::map ph; - ph["%player"] = bot->GetName(); - std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( - "netherspite_beam_blocking_red", "%player is moving to block the red beam!", ph); - bot->Yell(text, LANG_UNIVERSAL); + if (!wasBlocking) + { + std::map ph; + ph["%player"] = bot->GetName(); + std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( + "netherspite_beam_blocking_red", "%player is moving to block the red beam!", ph); + bot->Yell(text, LANG_UNIVERSAL); + } + wasBlockingRedBeam[botGuid] = true; - ObjectGuid botGuid = bot->GetGUID(); uint32 intervalSecs = 5; if (beamMoveTimes[botGuid] == 0) @@ -516,7 +527,6 @@ bool KarazhanNetherspiteBlockRedBeamAction::Execute(Event event) return MoveTo(bot->GetMapId(), beamPos.GetPositionX(), beamPos.GetPositionY(), beamPos.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_FORCED); } - else { float bx = boss->GetPositionX(); @@ -544,6 +554,7 @@ bool KarazhanNetherspiteBlockRedBeamAction::Execute(Event event) } } + wasBlockingRedBeam[botGuid] = false; return false; } @@ -556,11 +567,6 @@ bool KarazhanNetherspiteBlockRedBeamAction::isUseful() static std::map lastBossBanishState; bool bossIsBanished = boss && boss->HasAura(SPELL_NETHERSPITE_BANISHED); - if (!boss || !redPortal || bossIsBanished) - { - return false; - } - if (lastBossBanishState[botGuid] != bossIsBanished) { if (!bossIsBanished) @@ -571,10 +577,10 @@ bool KarazhanNetherspiteBlockRedBeamAction::isUseful() lastBossBanishState[botGuid] = bossIsBanished; } - return true; + return boss && redPortal && !bossIsBanished; } -// Two non-Rogue/Warrior DPS bots will block the blue beam for each phase (swap at 25 debuff stacks) +// Two non-Rogue/Warrior DPS bots will block the blue beam for each phase (swap at 26 debuff stacks) // When avoiding void zones, blocking bots will move along the beam to continue blocking bool KarazhanNetherspiteBlockBlueBeamAction::Execute(Event event) { @@ -582,11 +588,10 @@ bool KarazhanNetherspiteBlockBlueBeamAction::Execute(Event event) 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); + auto [redBlocker, greenBlocker, blueBlocker] = karazhanHelper.GetCurrentBeamBlockers(); + bool isBlockingNow = (bot == blueBlocker); bool wasBlocking = wasBlockingBlueBeam[botGuid]; if (wasBlocking && !isBlockingNow) @@ -596,8 +601,8 @@ bool KarazhanNetherspiteBlockBlueBeamAction::Execute(Event event) std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( "netherspite_beam_leaving_blue", "%player is leaving the blue beam--next blocker up!", ph); bot->Yell(text, LANG_UNIVERSAL); - wasBlockingBlueBeam[botGuid] = false; + return false; } @@ -632,7 +637,7 @@ bool KarazhanNetherspiteBlockBlueBeamAction::Execute(Event event) float bestDist = 150.0f; Position bestPos; bool found = false; - for (float dist = 18.0f; dist <= 25.0f; dist += 0.5f) + for (float dist = 18.0f; dist <= 30.0f; dist += 0.5f) { float candidateX = bx + dx * dist; float candidateY = by + dy * dist; @@ -662,6 +667,9 @@ bool KarazhanNetherspiteBlockBlueBeamAction::Execute(Event event) } if (found) { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(false); + return MoveTo(bot->GetMapId(), bestPos.GetPositionX(), bestPos.GetPositionY(), bestPos.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_FORCED); } @@ -681,7 +689,7 @@ bool KarazhanNetherspiteBlockBlueBeamAction::isUseful() return boss && bluePortal && !boss->HasAura(SPELL_NETHERSPITE_BANISHED); } -// Two healer bots will block the green beam for each phase (swap at 25 debuff stacks) +// Two healer bots will block the green beam for each phase (swap at 26 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) @@ -690,11 +698,10 @@ bool KarazhanNetherspiteBlockGreenBeamAction::Execute(Event event) 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); + auto [redBlocker, greenBlocker, blueBlocker] = karazhanHelper.GetCurrentBeamBlockers(); + bool isBlockingNow = (bot == greenBlocker); bool wasBlocking = wasBlockingGreenBeam[botGuid]; if (wasBlocking && !isBlockingNow) @@ -704,8 +711,8 @@ bool KarazhanNetherspiteBlockGreenBeamAction::Execute(Event event) std::string text = sPlayerbotTextMgr->GetBotTextOrDefault( "netherspite_beam_leaving_green", "%player is leaving the green beam--next blocker up!", ph); bot->Yell(text, LANG_UNIVERSAL); - wasBlockingGreenBeam[botGuid] = false; + return false; } @@ -740,7 +747,7 @@ bool KarazhanNetherspiteBlockGreenBeamAction::Execute(Event event) float bestDist = 150.0f; Position bestPos; bool found = false; - for (float dist = 18.0f; dist <= 25.0f; dist += 0.5f) + for (float dist = 18.0f; dist <= 30.0f; dist += 0.5f) { float candidateX = bx + dx * dist; float candidateY = by + dy * dist; @@ -770,6 +777,9 @@ bool KarazhanNetherspiteBlockGreenBeamAction::Execute(Event event) } if (found) { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(false); + return MoveTo(bot->GetMapId(), bestPos.GetPositionX(), bestPos.GetPositionY(), bestPos.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_FORCED); } @@ -796,7 +806,6 @@ bool KarazhanNetherspiteAvoidBeamAndVoidZoneAction::Execute(Event event) RaidKarazhanHelpers karazhanHelper(botAI); auto [redBlocker, greenBlocker, blueBlocker] = karazhanHelper.GetCurrentBeamBlockers(); std::vector voidZones = karazhanHelper.GetAllVoidZones(); - bool nearVoidZone = false; for (Unit* vz : voidZones) { @@ -806,7 +815,6 @@ bool KarazhanNetherspiteAvoidBeamAndVoidZoneAction::Execute(Event event) break; } } - struct BeamAvoid { Unit* portal; float minDist, maxDist; }; std::vector beams; Unit* redPortal = bot->FindNearestCreature(NPC_RED_PORTAL, 150.0f); @@ -863,14 +871,14 @@ bool KarazhanNetherspiteAvoidBeamAndVoidZoneAction::Execute(Event event) return false; } - const float minMoveDist = 3.0f, maxSearchDist = 20.0f, stepAngle = M_PI/18.0f, stepDist = 0.5f; + const float minMoveDist = 2.0f, maxSearchDist = 30.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) + for (float dist = 2.0f; dist <= maxSearchDist; dist += stepDist) { float cx = bot->GetPositionX() + cos(angle) * dist; float cy = bot->GetPositionY() + sin(angle) * dist; @@ -922,9 +930,13 @@ bool KarazhanNetherspiteAvoidBeamAndVoidZoneAction::Execute(Event event) bestCandidate.GetPositionY(), bestCandidate.GetPositionZ(), voidZones, 4.0f)) { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(false); + return MoveTo(bot->GetMapId(), bestCandidate.GetPositionX(), bestCandidate.GetPositionY(), bestCandidate.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT); } + return false; } @@ -989,9 +1001,9 @@ bool KarazhanPrinceMalchezaarNonTankAvoidHazardAction::Execute(Event event) RaidKarazhanHelpers karazhanHelper(botAI); std::vector infernals = karazhanHelper.GetSpawnedInfernals(); - const float minSafeBossDistance = 35.0f; - const float maxSafeBossDistance = 40.0f; - const float safeInfernalDistance = 22.0f; + const float minSafeBossDistance = 34.0f; + const float maxSafeBossDistance = 60.0f; + const float safeInfernalDistance = 23.0f; const float stepSize = 0.5f; const int numAngles = 64; float bx = bot->GetPositionX(); @@ -1016,19 +1028,25 @@ bool KarazhanPrinceMalchezaarNonTankAvoidHazardAction::Execute(Event event) float x = bossX + dx * dist; float y = bossY + dy * dist; float destZ = bossZ; - if (!bot->IsWithinLOS(x, y, destZ)) + float destX = x, destY = y, destZ2 = destZ; + if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bx, by, bz, destX, destY, destZ2, true)) { continue; } - bool pathSafe = karazhanHelper.IsStraightPathSafe(Position(bx, by, bz), Position(x, y, destZ), + 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(x - bx, 2) + pow(y - by, 2)); + float moveDist = sqrt(pow(destX - bx, 2) + pow(destY - by, 2)); if (pathSafe && moveDist < bestMoveDist) { bestMoveDist = moveDist; - bestDestX = x; - bestDestY = y; - bestDestZ = destZ; + bestDestX = destX; + bestDestY = destY; + bestDestZ = destZ2; found = true; } } @@ -1062,63 +1080,41 @@ bool KarazhanPrinceMalchezaarNonTankAvoidHazardAction::Execute(Event event) float bestMoveDist = std::numeric_limits::max(); float bestDestX = bx, bestDestY = by, bestDestZ = bz; bool found = false; - bool usedArc = false; for (int i = 0; i < numAngles; ++i) { float angle = (2 * M_PI * i) / numAngles; float dx = cos(angle); float dy = sin(angle); - for (float dist = stepSize; dist <= 35.0f; dist += stepSize) + for (float dist = stepSize; dist <= maxSafeBossDistance; dist += stepSize) { float x = bossX + dx * dist; float y = bossY + dy * dist; float destZ = bossZ; - if (!bot->IsWithinLOS(x, y, destZ)) + float destX = x, destY = y, destZ2 = destZ; + if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bossX, bossY, bossZ, destX, destY, destZ2, true)) { continue; } - bool pathSafe = karazhanHelper.IsStraightPathSafe(Position(bx, by, bz), Position(x, y, destZ), - infernals, safeInfernalDistance, stepSize); - float moveDist = sqrt(pow(x - bx, 2) + pow(y - by, 2)); - if (pathSafe && moveDist < bestMoveDist) + 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 = x; - bestDestY = y; - bestDestZ = destZ; + bestDestX = destX; + bestDestY = destY; + bestDestZ = destZ2; found = true; - usedArc = false; - } - if (!pathSafe) - { - Position arcPoint = karazhanHelper.CalculateArcPoint(Position(bx, by, bz), Position(x, y, destZ), - Position(bossX, bossY, bossZ)); - if (!bot->IsWithinLOS(arcPoint.GetPositionX(), arcPoint.GetPositionY(), arcPoint.GetPositionZ())) - { - continue; - } - bool arcSafe = true; - for (Unit* infernal : infernals) - { - float infernalDist = sqrt(pow(arcPoint.GetPositionX() - infernal->GetPositionX(), 2) + - pow(arcPoint.GetPositionY() - infernal->GetPositionY(), 2)); - if (infernalDist < safeInfernalDistance) - { - arcSafe = false; - break; - } - } - float arcMoveDist = sqrt(pow(arcPoint.GetPositionX() - bx, 2) + - pow(arcPoint.GetPositionY() - by, 2)); - if (arcSafe && arcMoveDist < bestMoveDist) - { - bestMoveDist = arcMoveDist; - bestDestX = arcPoint.GetPositionX(); - bestDestY = arcPoint.GetPositionY(); - bestDestZ = arcPoint.GetPositionZ(); - found = true; - usedArc = true; - } } } } @@ -1149,17 +1145,13 @@ bool KarazhanPrinceMalchezaarTankAvoidHazardAction::Execute(Event event) RaidKarazhanHelpers karazhanHelper(botAI); std::vector infernals = karazhanHelper.GetSpawnedInfernals(); - const float safeInfernalDistance = 30.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(); - float bestMoveDist = std::numeric_limits::max(); - float bestDestX = bx, bestDestY = by, bestDestZ = bz; - bool found = false; - bool usedArc = false; bool nearInfernal = false; for (Unit* infernal : infernals) @@ -1172,6 +1164,10 @@ bool KarazhanPrinceMalchezaarTankAvoidHazardAction::Execute(Event event) } } + 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) @@ -1184,54 +1180,72 @@ bool KarazhanPrinceMalchezaarTankAvoidHazardAction::Execute(Event event) float x = bx + dx * dist; float y = by + dy * dist; float z = bz; - if (!bot->IsWithinLOS(x, y, z)) - { + + 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; + } } - bool safe = karazhanHelper.IsStraightPathSafe(Position(bx, by, bz), Position(x, y, z), - infernals, safeInfernalDistance, stepSize); - float moveDist = sqrt(pow(x - bx, 2) + pow(y - by, 2)); - if (safe && moveDist < bestMoveDist) + 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 = x; - bestDestY = y; - bestDestZ = z; + bestDestX = destX; + bestDestY = destY; + bestDestZ = destZ; found = true; - usedArc = false; } - if (!safe) + } + } + 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) { - Position arcPoint = karazhanHelper.CalculateArcPoint(Position(bx, by, bz), Position(x, y, z), - Position(bx, by, bz)); - float arcX = arcPoint.GetPositionX(); - float arcY = arcPoint.GetPositionY(); - float arcZ = arcPoint.GetPositionZ(); - float arcDestX = arcX, arcDestY = arcY, arcDestZ = arcZ; - if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bx, by, bz, arcDestX, arcDestY, arcDestZ)) - { + 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 arcSafe = true; + + bool destSafe = true; for (Unit* infernal : infernals) { - float infernalDist = sqrt(pow(arcPoint.GetPositionX() - infernal->GetPositionX(), 2) + - pow(arcPoint.GetPositionY() - infernal->GetPositionY(), 2)); + float infernalDist = sqrt(pow(destX - infernal->GetPositionX(), 2) + pow(destY - infernal->GetPositionY(), 2)); if (infernalDist < safeInfernalDistance) { - arcSafe = false; + destSafe = false; break; } } - float arcMoveDist = sqrt(pow(arcPoint.GetPositionX() - bx, 2) + pow(arcPoint.GetPositionY() - by, 2)); - if (arcSafe && arcMoveDist < bestMoveDist) + float moveDist = sqrt(pow(destX - bx, 2) + pow(destY - by, 2)); + if (destSafe && moveDist < bestMoveDist) { - bestMoveDist = arcMoveDist; - bestDestX = arcPoint.GetPositionX(); - bestDestY = arcPoint.GetPositionY(); - bestDestZ = arcPoint.GetPositionZ(); + bestMoveDist = moveDist; + bestDestX = destX; + bestDestY = destY; + bestDestZ = destZ; found = true; - usedArc = true; } } } @@ -1240,12 +1254,12 @@ bool KarazhanPrinceMalchezaarTankAvoidHazardAction::Execute(Event event) { bot->AttackStop(); bot->InterruptNonMeleeSpells(false); - + return MoveTo(bot->GetMapId(), bestDestX, bestDestY, bestDestZ, false, false, false, true, MovementPriority::MOVEMENT_COMBAT); } } - + return false; } diff --git a/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp b/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp index 19dd0b33..b4d6d5df 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp +++ b/src/strategy/raids/karazhan/RaidKarazhanHelpers.cpp @@ -132,7 +132,28 @@ bool RaidKarazhanHelpers::IsFlameWreathActive() return false; } -// Blue beam blockers: non-Rogue/Warrior DPS bots, no Nether Exhaustion Blue and <25 stacks of Blue Beam debuff +// Red beam blockers: tank bots, no Nether Exhaustion Red +std::vector RaidKarazhanHelpers::GetRedBlockers() +{ + 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() || !botAI->IsTank(member) || !GET_PLAYERBOT_AI(member) || + member->HasAura(SPELL_NETHER_EXHAUSTION_RED)) + { + continue; + } + redBlockers.push_back(member); + } + } + + return redBlockers; +} + +// 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; @@ -150,7 +171,7 @@ std::vector RaidKarazhanHelpers::GetBlueBlockers() 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; + bool overStack = blueBuff && blueBuff->GetStackAmount() >= 26; if (isDps && !isWarrior && !isRogue && !hasExhaustion && !overStack) { blueBlockers.push_back(member); @@ -163,7 +184,7 @@ std::vector RaidKarazhanHelpers::GetBlueBlockers() // 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 +// (2) Healer bots, no Nether Exhaustion Green and ≤25 stacks of Green Beam debuff std::vector RaidKarazhanHelpers::GetGreenBlockers() { std::vector greenBlockers; @@ -178,7 +199,7 @@ std::vector RaidKarazhanHelpers::GetGreenBlockers() } bool hasExhaustion = member->HasAura(SPELL_NETHER_EXHAUSTION_GREEN); Aura* greenBuff = member->GetAura(SPELL_GREEN_BEAM_DEBUFF); - bool overStack = greenBuff && greenBuff->GetStackAmount() >= 25; + 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; @@ -221,47 +242,91 @@ Position RaidKarazhanHelpers::GetPositionOnBeam(Unit* boss, Unit* portal, float 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; - if (Group* group = bot->GetGroup()) - { - 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); - } - } + std::vector redBlockers = GetRedBlockers(); if (!redBlockers.empty()) { - redBlocker = redBlockers.front(); + auto it = std::find_if(redBlockers.begin(), redBlockers.end(), [](Player* p) + { + return p && p->GetGUID() == currentRedBlocker; + }); + if (it != redBlockers.end()) + { + redBlocker = *it; + } + else + { + redBlocker = redBlockers.front(); + } + currentRedBlocker = redBlocker ? redBlocker->GetGUID() : ObjectGuid::Empty; } + else + { + currentRedBlocker = ObjectGuid::Empty; + redBlocker = nullptr; + } + std::vector greenBlockers = GetGreenBlockers(); if (!greenBlockers.empty()) { - greenBlocker = greenBlockers.front(); + auto it = std::find_if(greenBlockers.begin(), greenBlockers.end(), [](Player* p) + { + return p && p->GetGUID() == currentGreenBlocker; + }); + if (it != greenBlockers.end()) + { + greenBlocker = *it; + } + else + { + greenBlocker = greenBlockers.front(); + } + currentGreenBlocker = greenBlocker ? greenBlocker->GetGUID() : ObjectGuid::Empty; } - std::vector blueBlockers = GetBlueBlockers(); - if (!blueBlockers.empty()) + else { - blueBlocker = blueBlockers.front(); + currentGreenBlocker = ObjectGuid::Empty; + greenBlocker = nullptr; } + std::vector blueBlockers = GetBlueBlockers(); + if (!blueBlockers.empty()) + { + auto it = std::find_if(blueBlockers.begin(), blueBlockers.end(), [](Player* p) + { + return p && p->GetGUID() == currentBlueBlocker; + }); + if (it != blueBlockers.end()) + { + blueBlocker = *it; + } + else + { + blueBlocker = blueBlockers.front(); + } + currentBlueBlocker = blueBlocker ? blueBlocker->GetGUID() : ObjectGuid::Empty; + } + else + { + currentBlueBlocker = ObjectGuid::Empty; + blueBlocker = nullptr; + } + 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(); + const float radius = 30.0f; + const GuidVector npcs = botAI->GetAiObjectContext()->GetValue("nearest npcs")->Get(); for (const auto& npcGuid : npcs) { Unit* unit = botAI->GetUnit(npcGuid); @@ -280,7 +345,7 @@ std::vector RaidKarazhanHelpers::GetAllVoidZones() } bool RaidKarazhanHelpers::IsSafePosition(float x, float y, float z, - const std::vector& hazards, float hazardRadius) + const std::vector& hazards, float hazardRadius) { for (Unit* hazard : hazards) { @@ -297,7 +362,7 @@ bool RaidKarazhanHelpers::IsSafePosition(float x, float y, float z, std::vector RaidKarazhanHelpers::GetSpawnedInfernals() const { std::vector infernals; - const GuidVector npcs = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); + const GuidVector npcs = botAI->GetAiObjectContext()->GetValue("nearest npcs")->Get(); for (const auto& npcGuid : npcs) { Unit* unit = botAI->GetUnit(npcGuid); @@ -342,44 +407,3 @@ bool RaidKarazhanHelpers::IsStraightPathSafe(const Position& start, const Positi return true; } - -Position RaidKarazhanHelpers::CalculateArcPoint(const Position& current, const Position& target, const Position& center) -{ - float arcFraction = 0.25f; - float currentX = current.GetPositionX() - center.GetPositionX(); - float currentY = current.GetPositionY() - center.GetPositionY(); - float targetX = target.GetPositionX() - center.GetPositionX(); - float targetY = target.GetPositionY() - center.GetPositionY(); - - float currentDist = std::sqrt(currentX * currentX + currentY * currentY); - float targetDist = std::sqrt(targetX * targetX + targetY * targetY); - if (currentDist == 0.0f || targetDist == 0.0f) - { - return current; - } - - currentX /= currentDist; - currentY /= currentDist; - targetX /= targetDist; - targetY /= targetDist; - - float dotProduct = currentX * targetX + currentY * targetY; - dotProduct = std::max(-1.0f, std::min(1.0f, dotProduct)); - float angle = std::acos(dotProduct); - float crossProduct = currentX * targetY - currentY * targetX; - float stepAngle = angle * arcFraction; - if (crossProduct < 0) - { - stepAngle = -stepAngle; - } - - float cos_a = std::cos(stepAngle); - float sin_a = std::sin(stepAngle); - float rotatedX = currentX * cos_a - currentY * sin_a; - float rotatedY = currentX * sin_a + currentY * cos_a; - float desiredDist = currentDist * 0.9f + targetDist * 0.1f; - - return Position(center.GetPositionX() + rotatedX * desiredDist, - center.GetPositionY() + rotatedY * desiredDist, - current.GetPositionZ()); -} diff --git a/src/strategy/raids/karazhan/RaidKarazhanHelpers.h b/src/strategy/raids/karazhan/RaidKarazhanHelpers.h index 2746b09d..89d06a95 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanHelpers.h +++ b/src/strategy/raids/karazhan/RaidKarazhanHelpers.h @@ -70,6 +70,7 @@ public: 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(); @@ -79,7 +80,6 @@ public: std::vector GetSpawnedInfernals() const; bool IsStraightPathSafe(const Position& start, const Position& target, const std::vector& hazards, float hazardRadius, float stepSize); - Position CalculateArcPoint(const Position& current, const Position& target, const Position& center); }; #endif diff --git a/src/strategy/raids/karazhan/RaidKarazhanMultipliers.cpp b/src/strategy/raids/karazhan/RaidKarazhanMultipliers.cpp index 01c1c179..ac235603 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanMultipliers.cpp +++ b/src/strategy/raids/karazhan/RaidKarazhanMultipliers.cpp @@ -5,6 +5,7 @@ #include "AttackAction.h" #include "DruidBearActions.h" #include "DruidCatActions.h" +#include "RogueActions.h" #include "WarriorActions.h" static bool IsChargeAction(Action* action) @@ -15,6 +16,20 @@ static bool IsChargeAction(Action* action) dynamic_cast(action); } +float KarazhanAttumenTheHuntsmanMultiplier::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))) + { + return 0.0f; + } + + return 1.0f; +} + float KarazhanBigBadWolfMultiplier::GetValue(Action* action) { Unit* boss = AI_VALUE2(Unit*, "find target", "the big bad wolf"); @@ -61,7 +76,6 @@ float KarazhanShadeOfAranMultiplier::GetValue(Action* action) } bool flameWreathActive = boss->HasAura(SPELL_FLAME_WREATH); - if (!flameWreathActive && bot->GetGroup()) { for (GroupReference* itr = bot->GetGroup()->GetFirstMember(); itr != nullptr; itr = itr->next()) @@ -74,7 +88,6 @@ float KarazhanShadeOfAranMultiplier::GetValue(Action* action) } } } - if (flameWreathActive) { if (dynamic_cast(action) || IsChargeAction(action)) @@ -94,6 +107,11 @@ float KarazhanNetherspiteBlueAndGreenBeamMultiplier::GetValue(Action* action) 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); @@ -101,9 +119,9 @@ float KarazhanNetherspiteBlueAndGreenBeamMultiplier::GetValue(Action* action) { 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}) { + for (Unit* portal : {bluePortal, greenPortal}) + { if (!portal) { continue; @@ -121,18 +139,18 @@ float KarazhanNetherspiteBlueAndGreenBeamMultiplier::GetValue(Action* action) 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 > 0.0f && t < length) + 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) { + for (Unit* vz : voidZones) + { if (bot->GetExactDist2d(vz) < 4.0f) { inVoidZone = true; @@ -160,9 +178,13 @@ float KarazhanNetherspiteRedBeamMultiplier::GetValue(Action* action) return 1.0f; } + if (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(); @@ -198,11 +220,9 @@ float KarazhanNetherspiteRedBeamMultiplier::GetValue(Action* action) 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)) @@ -219,12 +239,28 @@ float KarazhanNetherspiteRedBeamMultiplier::GetValue(Action* action) float KarazhanPrinceMalchezaarMultiplier::GetValue(Action* action) { Unit* boss = AI_VALUE2(Unit*, "find target", "prince malchezaar"); + if (!boss || !boss->IsAlive()) + { + return 1.0f; + } - if (boss && botAI->IsMelee(bot) && bot->HasAura(SPELL_ENFEEBLE) && + if (dynamic_cast(action)) + { + 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; + } + return 1.0f; } diff --git a/src/strategy/raids/karazhan/RaidKarazhanMultipliers.h b/src/strategy/raids/karazhan/RaidKarazhanMultipliers.h index d29aa027..c8a4ba37 100644 --- a/src/strategy/raids/karazhan/RaidKarazhanMultipliers.h +++ b/src/strategy/raids/karazhan/RaidKarazhanMultipliers.h @@ -3,6 +3,13 @@ #include "Multiplier.h" +class KarazhanAttumenTheHuntsmanMultiplier : public Multiplier +{ +public: + KarazhanAttumenTheHuntsmanMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "karazhan attumen the huntsman multiplier") {} + virtual float GetValue(Action* action); +}; + class KarazhanBigBadWolfMultiplier : public Multiplier { public: