From 33b69a9a0442bf60f2d36666e5fd60d45d35f932 Mon Sep 17 00:00:00 2001 From: crow Date: Tue, 25 Nov 2025 00:12:50 -0600 Subject: [PATCH 01/25] SSC strategy All bosses --- conf/playerbots.conf.dist | 4 +- src/PlayerbotAI.cpp | 4 + src/strategy/AiObjectContext.cpp | 4 + src/strategy/raids/RaidStrategyContext.h | 3 + .../RaidSSCActionContext.h | 129 + .../serpentshrinecavern/RaidSSCActions.cpp | 2913 +++++++++++++++++ .../serpentshrinecavern/RaidSSCActions.h | 409 +++ .../serpentshrinecavern/RaidSSCHelpers.cpp | 623 ++++ .../serpentshrinecavern/RaidSSCHelpers.h | 203 ++ .../RaidSSCMultipliers.cpp | 586 ++++ .../serpentshrinecavern/RaidSSCMultipliers.h | 160 + .../serpentshrinecavern/RaidSSCStrategy.cpp | 193 ++ .../serpentshrinecavern/RaidSSCStrategy.h | 18 + .../RaidSSCTriggerContext.h | 125 + .../serpentshrinecavern/RaidSSCTriggers.cpp | 619 ++++ .../serpentshrinecavern/RaidSSCTriggers.h | 349 ++ 16 files changed, 6340 insertions(+), 2 deletions(-) create mode 100644 src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h create mode 100644 src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp create mode 100644 src/strategy/raids/serpentshrinecavern/RaidSSCActions.h create mode 100644 src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp create mode 100644 src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h create mode 100644 src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp create mode 100644 src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h create mode 100644 src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp create mode 100644 src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.h create mode 100644 src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h create mode 100644 src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp create mode 100644 src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index a782479550..531c91aaef 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -547,7 +547,7 @@ AiPlayerbot.AutoGearScoreLimit = 0 # "mana" (bots have infinite mana) # "power" (bots have infinite energy, rage, and runic power) # "taxi" (bots may use all flight paths, though they will not actually learn them) -# "raid" (bots use cheats implemented into raid strategies (currently only for Ulduar)) +# "raid" (bots use cheats implemented into raid strategies (currently only for SSC and Ulduar)) # To use multiple cheats, separate them by commas below (e.g., to enable all, use "gold,health,mana,power,raid,taxi") # Default: food, taxi, and raid are enabled AiPlayerbot.BotCheats = "food,taxi,raid" @@ -630,7 +630,7 @@ AiPlayerbot.RandomBotHordeRatio = 50 AiPlayerbot.DisableDeathKnightLogin = 0 # Enable simulated expansion limitation for talents and glyphs -# If enabled, limits talent trees to 5 rows plus the middle talent of the 6th row for bots until level 61 +# If enabled, limits talent trees to 5 rows plus the middle talent of the 6th row for bots until level 61 # and 7 rows plus the middle talent of the 8th row for bots from level 61 until level 71 # Default: 0 (disabled) AiPlayerbot.LimitTalentsExpansion = 0 diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 23d073f54c..68674855ab 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -1477,6 +1477,10 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster) break; case 544: strategyName = "magtheridon"; // Magtheridon's Lair + break; + case 548: + strategyName = "ssc"; // Serpentshrine Cavern + break; case 565: strategyName = "gruulslair"; // Gruul's Lair break; diff --git a/src/strategy/AiObjectContext.cpp b/src/strategy/AiObjectContext.cpp index 7a1da0f8a6..1a4b5d1b3f 100644 --- a/src/strategy/AiObjectContext.cpp +++ b/src/strategy/AiObjectContext.cpp @@ -43,6 +43,8 @@ #include "raids/magtheridon/RaidMagtheridonTriggerContext.h" #include "raids/gruulslair/RaidGruulsLairActionContext.h" #include "raids/gruulslair/RaidGruulsLairTriggerContext.h" +#include "raids/serpentshrinecavern/RaidSSCActionContext.h" +#include "raids/serpentshrinecavern/RaidSSCTriggerContext.h" #include "raids/naxxramas/RaidNaxxActionContext.h" #include "raids/naxxramas/RaidNaxxTriggerContext.h" #include "raids/eyeofeternity/RaidEoEActionContext.h" @@ -117,6 +119,7 @@ void AiObjectContext::BuildSharedActionContexts(SharedNamedObjectContextList creators["karazhan"] = &RaidStrategyContext::karazhan; creators["magtheridon"] = &RaidStrategyContext::magtheridon; creators["gruulslair"] = &RaidStrategyContext::gruulslair; + creators["ssc"] = &RaidStrategyContext::ssc; creators["naxx"] = &RaidStrategyContext::naxx; creators["wotlk-os"] = &RaidStrategyContext::wotlk_os; creators["wotlk-eoe"] = &RaidStrategyContext::wotlk_eoe; @@ -43,6 +45,7 @@ class RaidStrategyContext : public NamedObjectContext static Strategy* karazhan(PlayerbotAI* botAI) { return new RaidKarazhanStrategy(botAI); } static Strategy* magtheridon(PlayerbotAI* botAI) { return new RaidMagtheridonStrategy(botAI); } static Strategy* gruulslair(PlayerbotAI* botAI) { return new RaidGruulsLairStrategy(botAI); } + static Strategy* ssc(PlayerbotAI* botAI) { return new RaidSSCStrategy(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); } diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h b/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h new file mode 100644 index 0000000000..7e3c8c25e1 --- /dev/null +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h @@ -0,0 +1,129 @@ +#ifndef _PLAYERBOT_RAIDSSCACTIONCONTEXT_H +#define _PLAYERBOT_RAIDSSCACTIONCONTEXT_H + +#include "RaidSSCActions.h" +#include "NamedObjectContext.h" + +class RaidSSCActionContext : public NamedObjectContext +{ +public: + RaidSSCActionContext() + { + creators["underbog colossus escape toxic pool"] = &RaidSSCActionContext::underbog_colossus_escape_toxic_pool; + creators["greyheart tidecaller mark water elemental totem"] = &RaidSSCActionContext::greyheart_tidecaller_mark_water_elemental_totem; + creators["rancid mushroom move away from mushroom spore cloud"] = &RaidSSCActionContext::rancid_mushroom_move_away_from_mushroom_spore_cloud; + + creators["hydross the unstable position frost tank"] = &RaidSSCActionContext::hydross_the_unstable_position_frost_tank; + creators["hydross the unstable position nature tank"] = &RaidSSCActionContext::hydross_the_unstable_position_nature_tank; + creators["hydross the unstable prioritize elemental adds"] = &RaidSSCActionContext::hydross_the_unstable_prioritize_elemental_adds; + creators["hydross the unstable frost phase spread out"] = &RaidSSCActionContext::hydross_the_unstable_frost_phase_spread_out; + creators["hydross the unstable misdirect boss to tank"] = &RaidSSCActionContext::hydross_the_unstable_misdirect_boss_to_tank; + creators["hydross the unstable stop dps upon phase change"] = &RaidSSCActionContext::hydross_the_unstable_stop_dps_upon_phase_change; + creators["hydross the unstable manage timers"] = &RaidSSCActionContext::hydross_the_unstable_manage_timers; + + creators["the lurker below run around behind boss"] = &RaidSSCActionContext::the_lurker_below_run_around_behind_boss; + creators["the lurker below position main tank"] = &RaidSSCActionContext::the_lurker_below_position_main_tank; + creators["the lurker below spread ranged"] = &RaidSSCActionContext::the_lurker_below_spread_ranged; + creators["the lurker below manage spout timer"] = &RaidSSCActionContext::the_lurker_below_manage_spout_timer; + + creators["leotheras the blind target spellbinders"] = &RaidSSCActionContext::leotheras_the_blind_target_spellbinders; + creators["leotheras the blind demon form tank attack boss"] = &RaidSSCActionContext::leotheras_the_blind_demon_form_tank_attack_boss; + creators["leotheras the blind position ranged"] = &RaidSSCActionContext::leotheras_the_blind_position_ranged; + creators["leotheras the blind run away from whirlwind"] = &RaidSSCActionContext::leotheras_the_blind_run_away_from_whirlwind; + creators["leotheras the blind demon form position melee"] = &RaidSSCActionContext::leotheras_the_blind_demon_form_position_melee; + creators["leotheras the blind inner demon cheat"] = &RaidSSCActionContext::leotheras_the_blind_inner_demon_cheat; + creators["leotheras the blind final phase assign dps priority"] = &RaidSSCActionContext::leotheras_the_blind_final_phase_assign_dps_priority; + creators["leotheras the blind misdirect boss to demon form tank"] = &RaidSSCActionContext::leotheras_the_blind_misdirect_boss_to_demon_form_tank; + creators["leotheras the blind manage timers and trackers"] = &RaidSSCActionContext::leotheras_the_blind_manage_timers_and_trackers; + + creators["fathom-lord karathress main tank position boss"] = &RaidSSCActionContext::fathom_lord_karathress_main_tank_position_boss; + creators["fathom-lord karathress first assist tank position sharkkis"] = &RaidSSCActionContext::fathom_lord_karathress_first_assist_tank_position_sharkkis; + creators["fathom-lord karathress second assist tank position tidalvess"] = &RaidSSCActionContext::fathom_lord_karathress_second_assist_tank_position_tidalvess; + creators["fathom-lord karathress third assist tank position caribdis"] = &RaidSSCActionContext::fathom_lord_karathress_third_assist_tank_position_caribdis; + creators["fathom-lord karathress position caribdis tank healer"] = &RaidSSCActionContext::fathom_lord_karathress_position_caribdis_tank_healer; + creators["fathom-lord karathress misdirect bosses to tanks"] = &RaidSSCActionContext::fathom_lord_karathress_misdirect_bosses_to_tanks; + creators["fathom-lord karathress assign dps priority"] = &RaidSSCActionContext::fathom_lord_karathress_assign_dps_priority; + creators["fathom-lord karathress manage dps timer"] = &RaidSSCActionContext::fathom_lord_karathress_manage_dps_timer; + + creators["morogrim tidewalker misdirect boss to main tank"] = &RaidSSCActionContext::morogrim_tidewalker_misdirect_boss_to_main_tank; + creators["morogrim tidewalker move boss to tank position"] = &RaidSSCActionContext::morogrim_tidewalker_move_boss_to_tank_position; + creators["morogrim tidewalker phase 2 reposition ranged"] = &RaidSSCActionContext::morogrim_tidewalker_phase_2_reposition_ranged; + creators["morogrim tidewalker reset phase transition steps"] = &RaidSSCActionContext::morogrim_tidewalker_reset_phase_transition_steps; + + creators["lady vashj main tank position boss"] = &RaidSSCActionContext::lady_vashj_main_tank_position_boss; + creators["lady vashj phase 1 position ranged"] = &RaidSSCActionContext::lady_vashj_phase_1_position_ranged; + creators["lady vashj set grounding totem in main tank group"] = &RaidSSCActionContext::lady_vashj_set_grounding_totem_in_main_tank_group; + creators["lady vashj static charge move away from group"] = &RaidSSCActionContext::lady_vashj_static_charge_move_away_from_group; + creators["lady vashj misdirect boss to main tank"] = &RaidSSCActionContext::lady_vashj_misdirect_boss_to_main_tank; + creators["lady vashj misdirect strider to first assist tank"] = &RaidSSCActionContext::lady_vashj_misdirect_strider_to_first_assist_tank; + creators["lady vashj tank attack and move away strider"] = &RaidSSCActionContext::lady_vashj_tank_attack_and_move_away_strider; + creators["lady vashj assign dps priority"] = &RaidSSCActionContext::lady_vashj_assign_dps_priority; + creators["lady vashj loot tainted core"] = &RaidSSCActionContext::lady_vashj_loot_tainted_core; + creators["lady vashj teleport to tainted elemental"] = &RaidSSCActionContext::lady_vashj_teleport_to_tainted_elemental; + creators["lady vashj pass the tainted core"] = &RaidSSCActionContext::lady_vashj_pass_the_tainted_core; + creators["lady vashj destroy tainted core"] = &RaidSSCActionContext::lady_vashj_destroy_tainted_core; + creators["lady vashj avoid toxic spores"] = &RaidSSCActionContext::lady_vashj_avoid_toxic_spores; + creators["lady vashj use free action abilities"] = &RaidSSCActionContext::lady_vashj_use_free_action_abilities; + creators["lady vashj manage trackers"] = &RaidSSCActionContext::lady_vashj_manage_trackers; + } + +private: + static Action* underbog_colossus_escape_toxic_pool(PlayerbotAI* botAI) { return new UnderbogColossusEscapeToxicPoolAction(botAI); } + static Action* greyheart_tidecaller_mark_water_elemental_totem(PlayerbotAI* botAI) { return new GreyheartTidecallerMarkWaterElementalTotemAction(botAI); } + static Action* rancid_mushroom_move_away_from_mushroom_spore_cloud(PlayerbotAI* botAI) { return new RancidMushroomMoveAwayFromMushroomSporeCloudAction(botAI); } + + static Action* hydross_the_unstable_position_frost_tank(PlayerbotAI* botAI) { return new HydrossTheUnstablePositionFrostTankAction(botAI); } + static Action* hydross_the_unstable_position_nature_tank(PlayerbotAI* botAI) { return new HydrossTheUnstablePositionNatureTankAction(botAI); } + static Action* hydross_the_unstable_prioritize_elemental_adds(PlayerbotAI* botAI) { return new HydrossTheUnstablePrioritizeElementalAddsAction(botAI); } + static Action* hydross_the_unstable_frost_phase_spread_out(PlayerbotAI* botAI) { return new HydrossTheUnstableFrostPhaseSpreadOutAction(botAI); } + static Action* hydross_the_unstable_misdirect_boss_to_tank(PlayerbotAI* botAI) { return new HydrossTheUnstableMisdirectBossToTankAction(botAI); } + static Action* hydross_the_unstable_stop_dps_upon_phase_change(PlayerbotAI* botAI) { return new HydrossTheUnstableStopDpsUponPhaseChangeAction(botAI); } + static Action* hydross_the_unstable_manage_timers(PlayerbotAI* botAI) { return new HydrossTheUnstableManageTimersAction(botAI); } + + static Action* the_lurker_below_run_around_behind_boss(PlayerbotAI* botAI) { return new TheLurkerBelowRunAroundBehindBossAction(botAI); } + static Action* the_lurker_below_position_main_tank(PlayerbotAI* botAI) { return new TheLurkerBelowPositionMainTankAction(botAI); } + static Action* the_lurker_below_spread_ranged(PlayerbotAI* botAI) { return new TheLurkerBelowSpreadRangedAction(botAI); } + static Action* the_lurker_below_manage_spout_timer(PlayerbotAI* botAI) { return new TheLurkerBelowManageSpoutTimerAction(botAI); } + + static Action* leotheras_the_blind_target_spellbinders(PlayerbotAI* botAI) { return new LeotherasTheBlindTargetSpellbindersAction(botAI); } + static Action* leotheras_the_blind_demon_form_tank_attack_boss(PlayerbotAI* botAI) { return new LeotherasTheBlindDemonFormTankAttackBossAction(botAI); } + static Action* leotheras_the_blind_position_ranged(PlayerbotAI* botAI) { return new LeotherasTheBlindPositionRangedAction(botAI); } + static Action* leotheras_the_blind_run_away_from_whirlwind(PlayerbotAI* botAI) { return new LeotherasTheBlindRunAwayFromWhirlwindAction(botAI); } + static Action* leotheras_the_blind_demon_form_position_melee(PlayerbotAI* botAI) { return new LeotherasTheBlindDemonFormPositionMeleeAction(botAI); } + static Action* leotheras_the_blind_inner_demon_cheat(PlayerbotAI* botAI) { return new LeotherasTheBlindInnerDemonCheatAction(botAI); } + static Action* leotheras_the_blind_misdirect_boss_to_demon_form_tank(PlayerbotAI* botAI) { return new LeotherasTheBlindMisdirectBossToDemonFormTankAction(botAI); } + static Action* leotheras_the_blind_final_phase_assign_dps_priority(PlayerbotAI* botAI) { return new LeotherasTheBlindFinalPhaseAssignDpsPriorityAction(botAI); } + static Action* leotheras_the_blind_manage_timers_and_trackers(PlayerbotAI* botAI) { return new LeotherasTheBlindManageTimersAndTrackersAction(botAI); } + + static Action* fathom_lord_karathress_main_tank_position_boss(PlayerbotAI* botAI) { return new FathomLordKarathressMainTankPositionBossAction(botAI); } + static Action* fathom_lord_karathress_first_assist_tank_position_sharkkis(PlayerbotAI* botAI) { return new FathomLordKarathressFirstAssistTankPositionSharkkisAction(botAI); } + static Action* fathom_lord_karathress_second_assist_tank_position_tidalvess(PlayerbotAI* botAI) { return new FathomLordKarathressSecondAssistTankPositionTidalvessAction(botAI); } + static Action* fathom_lord_karathress_third_assist_tank_position_caribdis(PlayerbotAI* botAI) { return new FathomLordKarathressThirdAssistTankPositionCaribdisAction(botAI); } + static Action* fathom_lord_karathress_position_caribdis_tank_healer(PlayerbotAI* botAI) { return new FathomLordKarathressPositionCaribdisTankHealerAction(botAI); } + static Action* fathom_lord_karathress_misdirect_bosses_to_tanks(PlayerbotAI* botAI) { return new FathomLordKarathressMisdirectBossesToTanksAction(botAI); } + static Action* fathom_lord_karathress_assign_dps_priority(PlayerbotAI* botAI) { return new FathomLordKarathressAssignDpsPriorityAction(botAI); } + static Action* fathom_lord_karathress_manage_dps_timer(PlayerbotAI* botAI) { return new FathomLordKarathressManageDpsTimerAction(botAI); } + + static Action* morogrim_tidewalker_misdirect_boss_to_main_tank(PlayerbotAI* botAI) { return new MorogrimTidewalkerMisdirectBossToMainTankAction(botAI); } + static Action* morogrim_tidewalker_move_boss_to_tank_position(PlayerbotAI* botAI) { return new MorogrimTidewalkerMoveBossToTankPositionAction(botAI); } + static Action* morogrim_tidewalker_phase_2_reposition_ranged(PlayerbotAI* botAI) { return new MorogrimTidewalkerPhase2RepositionRangedAction(botAI); } + static Action* morogrim_tidewalker_reset_phase_transition_steps(PlayerbotAI* botAI) { return new MorogrimTidewalkerResetPhaseTransitionStepsAction(botAI); } + + static Action* lady_vashj_main_tank_position_boss(PlayerbotAI* botAI) { return new LadyVashjMainTankPositionBossAction(botAI); } + static Action* lady_vashj_phase_1_position_ranged(PlayerbotAI* botAI) { return new LadyVashjPhase1PositionRangedAction(botAI); } + static Action* lady_vashj_set_grounding_totem_in_main_tank_group(PlayerbotAI* botAI) { return new LadyVashjSetGroundingTotemInMainTankGroupAction(botAI); } + static Action* lady_vashj_static_charge_move_away_from_group(PlayerbotAI* botAI) { return new LadyVashjStaticChargeMoveAwayFromGroupAction(botAI); } + static Action* lady_vashj_misdirect_boss_to_main_tank(PlayerbotAI* botAI) { return new LadyVashjMisdirectBossToMainTankAction(botAI); } + static Action* lady_vashj_misdirect_strider_to_first_assist_tank(PlayerbotAI* botAI) { return new LadyVashjMisdirectStriderToFirstAssistTankAction(botAI); } + static Action* lady_vashj_tank_attack_and_move_away_strider(PlayerbotAI* botAI) { return new LadyVashjTankAttackAndMoveAwayStriderAction(botAI); } + static Action* lady_vashj_assign_dps_priority(PlayerbotAI* botAI) { return new LadyVashjAssignDpsPriorityAction(botAI); } + static Action* lady_vashj_teleport_to_tainted_elemental(PlayerbotAI* botAI) { return new LadyVashjTeleportToTaintedElementalAction(botAI); } + static Action* lady_vashj_loot_tainted_core(PlayerbotAI* botAI) { return new LadyVashjLootTaintedCoreAction(botAI); } + static Action* lady_vashj_pass_the_tainted_core(PlayerbotAI* botAI) { return new LadyVashjPassTheTaintedCoreAction(botAI); } + static Action* lady_vashj_destroy_tainted_core(PlayerbotAI* botAI) { return new LadyVashjDestroyTaintedCoreAction(botAI); } + static Action* lady_vashj_avoid_toxic_spores(PlayerbotAI* botAI) { return new LadyVashjAvoidToxicSporesAction(botAI); } + static Action* lady_vashj_use_free_action_abilities(PlayerbotAI* botAI) { return new LadyVashjUseFreeActionAbilitiesAction(botAI); } + static Action* lady_vashj_manage_trackers(PlayerbotAI* botAI) { return new LadyVashjManageTrackersAction(botAI); } +}; + +#endif diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp new file mode 100644 index 0000000000..2e300d6187 --- /dev/null +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp @@ -0,0 +1,2913 @@ +#include "RaidSSCActions.h" +#include "RaidSSCHelpers.h" +#include "AiFactory.h" +#include "Corpse.h" +#include "LootAction.h" +#include "LootObjectStack.h" +#include "ObjectAccessor.h" +#include "Playerbots.h" +#include "RtiTargetValue.h" + +using namespace SerpentShrineCavernHelpers; +using namespace SerpentShrineCavernPositions; + +// Trash Mobs + +// Non-combat method; some colossi leave a toxic pool upon death +// Without this method, bots just stand (or drink) in the pool and die +bool UnderbogColossusEscapeToxicPoolAction::Execute(Event event) +{ + Aura* aura = bot->GetAura(SPELL_TOXIC_POOL); + if (!aura) + return false; + + DynamicObject* dynObj = aura->GetDynobjOwner(); + if (!dynObj) + return false; + + float radius = dynObj->GetRadius(); + if (radius <= 0.0f) + { + const SpellInfo* sInfo = sSpellMgr->GetSpellInfo(dynObj->GetSpellId()); + if (sInfo) + { + for (int e = 0; e < MAX_SPELL_EFFECTS; ++e) + { + auto const& eff = sInfo->Effects[e]; + if (eff.Effect == SPELL_EFFECT_SCHOOL_DAMAGE || + (eff.Effect == SPELL_EFFECT_APPLY_AURA && eff.ApplyAuraName == SPELL_AURA_PERIODIC_DAMAGE)) + { + radius = eff.CalcRadius(); + break; + } + } + } + } + + if (radius <= 0.0f) + return false; + + const float buffer = 3.0f; + const float centerThreshold = 1.0f; + float dx = bot->GetPositionX() - dynObj->GetPositionX(); + float dy = bot->GetPositionY() - dynObj->GetPositionY(); + float distSq = dx * dx + dy * dy; + const float insideThresh = radius + centerThreshold; + const float insideThreshSq = insideThresh * insideThresh; + + if (distSq > insideThreshSq) + return false; + + float safeDist = radius + buffer; + float moveX, moveY; + + if (distSq == 0.0f) + { + float angle = frand(0.0f, static_cast(M_PI * 2.0)); + moveX = dynObj->GetPositionX() + std::cos(angle) * safeDist; + moveY = dynObj->GetPositionY() + std::sin(angle) * safeDist; + } + else + { + float dist = std::hypot(dx, dy); + float inv = 1.0f / dist; + moveX = dynObj->GetPositionX() + (dx * inv) * safeDist; + moveY = dynObj->GetPositionY() + (dy * inv) * safeDist; + } + + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + + return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); +} + +bool GreyheartTidecallerMarkWaterElementalTotemAction::Execute(Event event) +{ + Unit* totem = GetFirstAliveUnitByEntry(botAI, NPC_WATER_ELEMENTAL_TOTEM); + if (!totem) + return false; + + MarkTargetWithSkull(bot, totem); + return false; +} + +bool RancidMushroomMoveAwayFromMushroomSporeCloudAction::Execute(Event event) +{ + Unit* mushroom = GetFirstAliveUnitByEntry(botAI, NPC_RANCID_MUSHROOM); + if (!mushroom) + return false; + + float currentDistance = bot->GetExactDist2d(mushroom); + const float safeDistance = 10.0f; + if (currentDistance < safeDistance) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(false); + return MoveAway(mushroom, safeDistance - currentDistance + 2.0f, false); + } + + return false; +} + +// Hydross the Unstable + +// (1) When tanking, move to designated tanking spot on frost side +// (2) 5 seconds into 100% Mark of Hydross, move to nature tank's spot to hand off boss +// (3) When Hydross is in nature form, move back to frost tank spot and wait for transition +bool HydrossTheUnstablePositionFrostTankAction::Execute(Event event) +{ + Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); + if (!hydross) + return false; + + if (!hydross->HasAura(SPELL_CORRUPTION) && !HasMarkOfHydrossAt100Percent(bot)) + { + MarkTargetWithSquare(bot, hydross); + SetRtiTarget(botAI, "square", hydross); + + if (bot->GetVictim() != hydross) + return Attack(hydross); + + if (hydross->GetVictim() == bot && bot->IsWithinMeleeRange(hydross)) + { + const Position& position = HydrossFrostTankPosition; + if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 3.0f) + { + float dX = position.GetPositionX() - bot->GetPositionX(); + float dY = position.GetPositionY() - bot->GetPositionY(); + float dist = std::hypot(dX, dY); + float moveDist = std::min(4.5f, dist); + float moveX = bot->GetPositionX() + (dX / dist) * moveDist; + float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + + return MoveTo(bot->GetMapId(), moveX, moveY, position.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT, true, true); + } + } + } + + if (!hydross->HasAura(SPELL_CORRUPTION) && HasMarkOfHydrossAt100Percent(bot) && hydross->GetVictim() == bot) + { + const uint32 mapId = hydross->GetMapId(); + const time_t now = std::time(nullptr); + auto it = hydrossChangeToNaturePhaseTimer.find(mapId); + + if (it != hydrossChangeToNaturePhaseTimer.end() && (now - it->second) >= 5) + { + const Position& position = HydrossNatureTankPosition; + if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 3.0f) + { + float dX = position.GetPositionX() - bot->GetPositionX(); + float dY = position.GetPositionY() - bot->GetPositionY(); + float dist = std::hypot(dX, dY); + float moveDist = std::min(4.5f, dist); + float moveX = bot->GetPositionX() + (dX / dist) * moveDist; + float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + + return MoveTo(bot->GetMapId(), moveX, moveY, position.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT, true, true); + } + else + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + return true; + } + } + } + + if (hydross->HasAura(SPELL_CORRUPTION)) + { + const Position& position = HydrossFrostTankPosition; + if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 3.0f) + { + float dX = position.GetPositionX() - bot->GetPositionX(); + float dY = position.GetPositionY() - bot->GetPositionY(); + float dist = std::hypot(dX, dY); + float moveDist = std::min(7.0f, dist); + float moveX = bot->GetPositionX() + (dX / dist) * moveDist; + float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + + return MoveTo(bot->GetMapId(), moveX, moveY, position.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + else + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + return true; + } + } + + return false; +} + +// (1) When tanking, move to designated tanking spot on nature side +// (2) 5 seconds into 100% Mark of Corruption, move to frost tank's spot to hand off boss +// (3) When Hydross is in frost form, move back to nature tank spot and wait for transition +bool HydrossTheUnstablePositionNatureTankAction::Execute(Event event) +{ + Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); + if (!hydross) + return false; + + if (hydross->HasAura(SPELL_CORRUPTION) && !HasMarkOfCorruptionAt100Percent(bot)) + { + MarkTargetWithTriangle(bot, hydross); + SetRtiTarget(botAI, "triangle", hydross); + + if (bot->GetVictim() != hydross) + return Attack(hydross); + + if (hydross->GetVictim() == bot && bot->IsWithinMeleeRange(hydross)) + { + const Position& position = HydrossNatureTankPosition; + if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 3.0f) + { + float dX = position.GetPositionX() - bot->GetPositionX(); + float dY = position.GetPositionY() - bot->GetPositionY(); + float dist = std::hypot(dX, dY); + float moveDist = std::min(4.5f, dist); + float moveX = bot->GetPositionX() + (dX / dist) * moveDist; + float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + + return MoveTo(bot->GetMapId(), moveX, moveY, position.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT, true, true); + } + } + } + + if (hydross->HasAura(SPELL_CORRUPTION) && HasMarkOfCorruptionAt100Percent(bot) && hydross->GetVictim() == bot) + { + const uint32 mapId = hydross->GetMapId(); + const time_t now = std::time(nullptr); + auto it = hydrossChangeToFrostPhaseTimer.find(mapId); + + if (it != hydrossChangeToFrostPhaseTimer.end() && (now - it->second) >= 5) + { + const Position& position = HydrossFrostTankPosition; + if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 3.0f) + { + float dX = position.GetPositionX() - bot->GetPositionX(); + float dY = position.GetPositionY() - bot->GetPositionY(); + float dist = std::hypot(dX, dY); + float moveDist = std::min(4.5f, dist); + float moveX = bot->GetPositionX() + (dX / dist) * moveDist; + float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + + return MoveTo(bot->GetMapId(), moveX, moveY, position.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT, true, true); + } + else + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + return true; + } + } + } + + if (!hydross->HasAura(SPELL_CORRUPTION)) + { + const Position& position = HydrossNatureTankPosition; + if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 3.0f) + { + float dX = position.GetPositionX() - bot->GetPositionX(); + float dY = position.GetPositionY() - bot->GetPositionY(); + float dist = std::hypot(dX, dY); + float moveDist = std::min(7.0f, dist); + float moveX = bot->GetPositionX() + (dX / dist) * moveDist; + float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + + return MoveTo(bot->GetMapId(), moveX, moveY, position.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + else + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + return true; + } + } + + return false; +} + +bool HydrossTheUnstablePrioritizeElementalAddsAction::Execute(Event event) +{ + Unit* waterElemental = GetFirstAliveUnitByEntry(botAI, NPC_PURE_SPAWN_OF_HYDROSS); + if (waterElemental) + { + if (IsMapIDTimerManager(botAI, bot)) + MarkTargetWithSkull(bot, waterElemental); + + SetRtiTarget(botAI, "skull", waterElemental); + + if (bot->GetTarget() != waterElemental->GetGUID()) + { + bot->SetTarget(waterElemental->GetGUID()); + return Attack(waterElemental); + } + } + else if (Unit* natureElemental = GetFirstAliveUnitByEntry(botAI, NPC_TAINTED_SPAWN_OF_HYDROSS)) + { + if (IsMapIDTimerManager(botAI, bot)) + MarkTargetWithSkull(bot, natureElemental); + + SetRtiTarget(botAI, "skull", natureElemental); + + if (bot->GetTarget() != natureElemental->GetGUID()) + { + bot->SetTarget(natureElemental->GetGUID()); + return Attack(natureElemental); + } + } + + return false; +} + +// To mitigate the effect of Water Tomb +bool HydrossTheUnstableFrostPhaseSpreadOutAction::Execute(Event event) +{ + Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); + Group* group = bot->GetGroup(); + if (!hydross || !group) + return false; + + const uint32 minInterval = 500; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || member == bot || !member->IsAlive()) + continue; + + if (bot->GetExactDist2d(member) < 6.0f) + return FleePosition(member->GetPosition(), 8.0f, minInterval); + } + + return false; +} + +bool HydrossTheUnstableMisdirectBossToTankAction::Execute(Event event) +{ + Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); + Group* group = bot->GetGroup(); + if (!hydross || !group) + return false; + + if (TryMisdirectToFrostTank(hydross, group)) + return true; + + if (TryMisdirectToNatureTank(hydross, group)) + return true; + + return false; +} + +bool HydrossTheUnstableMisdirectBossToTankAction::TryMisdirectToFrostTank(Unit* hydross, Group* group) +{ + Player* frostTank = nullptr; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->IsAlive() && botAI->IsMainTank(member)) + { + frostTank = member; + break; + } + } + + if (HasNoMarkOfHydross(bot) && !hydross->HasAura(SPELL_CORRUPTION) && frostTank) + { + if (botAI->CanCastSpell("misdirection", frostTank)) + return botAI->CastSpell("misdirection", frostTank); + + if (!bot->HasAura(SPELL_MISDIRECTION)) + return false; + + if (botAI->CanCastSpell("steady shot", hydross)) + return botAI->CastSpell("steady shot", hydross); + } + + return false; +} + +bool HydrossTheUnstableMisdirectBossToTankAction::TryMisdirectToNatureTank(Unit* hydross, Group* group) +{ + Player* natureTank = nullptr; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->IsAlive() && botAI->IsAssistTankOfIndex(member, 0)) + { + natureTank = member; + break; + } + } + + if (HasNoMarkOfCorruption(bot) && hydross->HasAura(SPELL_CORRUPTION) && natureTank) + { + if (botAI->CanCastSpell("misdirection", natureTank)) + return botAI->CastSpell("misdirection", natureTank); + + if (!bot->HasAura(SPELL_MISDIRECTION)) + return false; + + if (botAI->CanCastSpell("steady shot", hydross)) + return botAI->CastSpell("steady shot", hydross); + } + + return false; +} + +bool HydrossTheUnstableStopDpsUponPhaseChangeAction::Execute(Event event) +{ + Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); + if (!hydross) + return false; + + const uint32 mapId = hydross->GetMapId(); + const time_t now = std::time(nullptr); + const int phaseEndStopSeconds = 6; + const int phaseStartStopSeconds = 5; + + bool shouldStopDps = false; + + // 6 seconds after 100% Mark of Corruption, stop DPS until transition into frost phase + auto itNature = hydrossChangeToNaturePhaseTimer.find(mapId); + if (itNature != hydrossChangeToNaturePhaseTimer.end() && (now - itNature->second) >= phaseEndStopSeconds) + shouldStopDps = true; + + // Keep DPS stopped for 5 seconds after transition into frost phase + auto itFrostDps = hydrossFrostDpsWaitTimer.find(mapId); + if (itFrostDps != hydrossFrostDpsWaitTimer.end() && (now - itFrostDps->second) < phaseStartStopSeconds) + shouldStopDps = true; + + // 6 seconds after 100% Mark of Hydross, stop DPS until transition into nature phase + auto itFrost = hydrossChangeToFrostPhaseTimer.find(mapId); + if (itFrost != hydrossChangeToFrostPhaseTimer.end() && (now - itFrost->second) >= phaseEndStopSeconds) + shouldStopDps = true; + + // Keep DPS stopped for 5 seconds after transition into nature phase + auto itNatureDps = hydrossNatureDpsWaitTimer.find(mapId); + if (itNatureDps != hydrossNatureDpsWaitTimer.end() && (now - itNatureDps->second) < phaseStartStopSeconds) + shouldStopDps = true; + + if (shouldStopDps) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + return true; + } + + return false; +} + +bool HydrossTheUnstableManageTimersAction::Execute(Event event) +{ + Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); + if (!hydross) + return false; + + const uint32 mapId = hydross->GetMapId(); + const time_t now = std::time(nullptr); + + if (hydross->GetHealth() == hydross->GetMaxHealth()) + { + hydrossFrostDpsWaitTimer.erase(mapId); + hydrossNatureDpsWaitTimer.erase(mapId); + hydrossChangeToFrostPhaseTimer.erase(mapId); + hydrossChangeToNaturePhaseTimer.erase(mapId); + } + + if (!hydross->HasAura(SPELL_CORRUPTION)) + { + hydrossFrostDpsWaitTimer.try_emplace(mapId, now); + hydrossNatureDpsWaitTimer.erase(mapId); + hydrossChangeToFrostPhaseTimer.erase(mapId); + + if (HasMarkOfHydrossAt100Percent(bot)) + hydrossChangeToNaturePhaseTimer.try_emplace(mapId, now); + } + else + { + hydrossNatureDpsWaitTimer.try_emplace(mapId, now); + hydrossFrostDpsWaitTimer.erase(mapId); + hydrossChangeToNaturePhaseTimer.erase(mapId); + + if (HasMarkOfCorruptionAt100Percent(bot)) + hydrossChangeToFrostPhaseTimer.try_emplace(mapId, now); + } + + return false; +} + +// The Lurker Below + +// Run around behind Lurker during Spout, maintaining distance of 20-24 yards +// Stay within 90-degree arc behind Lurker +bool TheLurkerBelowRunAroundBehindBossAction::Execute(Event event) +{ + Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); + if (!lurker) + return false; + + float bossFacing = lurker->GetOrientation(); + float behindAngle = bossFacing + M_PI + frand(-0.5f, 0.5f) * (M_PI / 2.0f); + float radius = frand(20.0f, 24.0f); + + float targetX = lurker->GetPositionX() + radius * std::cos(behindAngle); + float targetY = lurker->GetPositionY() + radius * std::sin(behindAngle); + + if (bot->GetExactDist2d(targetX, targetY) > 1.0f) + { + bot->InterruptNonMeleeSpells(true); + return MoveTo(lurker->GetMapId(), targetX, targetY, lurker->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_FORCED, true, false); + } + + return false; +} + +bool TheLurkerBelowPositionMainTankAction::Execute(Event event) +{ + Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); + if (!lurker) + return false; + + if (bot->GetVictim() != lurker) + return Attack(lurker); + + const Position& position = LurkerMainTankPosition; + if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 0.2f) + { + return MoveTo(bot->GetMapId(), position.GetPositionX(), position.GetPositionY(), position.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_FORCED, true, false); + } + + return false; +} + +// Assign ranged positions within a 120-degree arc behind Lurker +bool TheLurkerBelowSpreadRangedAction::Execute(Event event) +{ + Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); + Group* group = bot->GetGroup(); + if (!lurker || !group) + return false; + + if (lurker->GetHealth() == lurker->GetMaxHealth()) + lurkerRangedPositions.clear(); + + std::vector rangedMembers; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || !botAI->IsRanged(member)) + continue; + rangedMembers.push_back(member); + } + + if (rangedMembers.empty()) + return false; + + const ObjectGuid guid = bot->GetGUID(); + + auto it = lurkerRangedPositions.find(guid); + if (it == lurkerRangedPositions.end()) + { + auto findIt = std::find(rangedMembers.begin(), rangedMembers.end(), bot); + size_t botIndex = (findIt != rangedMembers.end()) ? std::distance(rangedMembers.begin(), findIt) : 0; + size_t count = rangedMembers.size(); + if (count == 0) + return false; + + const float minRadius = 25.0f; + const float maxRadius = 27.0f; + const float referenceOrientation = Position::NormalizeOrientation(2.262f + M_PI); + + const float arcSpan = 2.0f * M_PI / 3.0f; // 120° + float startAngle = referenceOrientation - arcSpan / 2.0f; + + float angle; + if (count == 1) + angle = referenceOrientation; + else + angle = startAngle + (static_cast(botIndex) / (count - 1)) * arcSpan; + + float radius = frand(minRadius, maxRadius); + + float tx = lurker->GetPositionX() + radius * std::cos(angle); + float ty = lurker->GetPositionY() + radius * std::sin(angle); + float tz = lurker->GetPositionZ(); + + lurkerRangedPositions.emplace(guid, Position(tx, ty, tz)); + it = lurkerRangedPositions.find(guid); + } + + if (it == lurkerRangedPositions.end()) + return false; + + const Position& target = it->second; + const float returnThreshold = 2.0f; + if (!bot->IsWithinDist2d(target.GetPositionX(), target.GetPositionY(), returnThreshold)) + { + return MoveTo(bot->GetMapId(), target.GetPositionX(), target.GetPositionY(), target.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + + return false; +} + +bool TheLurkerBelowManageSpoutTimerAction::Execute(Event event) +{ + Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); + if (!lurker) + return false; + + const uint32 mapId = lurker->GetMapId(); + const time_t now = std::time(nullptr); + + if (lurker->GetHealth() == lurker->GetMaxHealth()) + { + lurkerSpoutTimer.erase(mapId); + return false; + } + + auto it = lurkerSpoutTimer.find(mapId); + if (it != lurkerSpoutTimer.end() && it->second <= now) + { + lurkerSpoutTimer.erase(it); + it = lurkerSpoutTimer.end(); + } + + const time_t spoutCastTime = 20; + if (IsLurkerCastingSpout(lurker) && it == lurkerSpoutTimer.end()) + lurkerSpoutTimer.emplace(mapId, now + spoutCastTime); + + return false; +} + +// Leotheras the Blind + +bool LeotherasTheBlindTargetSpellbindersAction::Execute(Event event) +{ + Unit* spellbinder = GetFirstAliveUnitByEntry(botAI, NPC_GREYHEART_SPELLBINDER); + if (!spellbinder || !spellbinder->IsInCombat()) + return false; + + MarkTargetWithSkull(bot, spellbinder); + + return false; +} + +bool LeotherasTheBlindDemonFormTankAttackBossAction::Execute(Event event) +{ + Unit* leotherasDemon = GetActiveLeotherasDemon(botAI); + if (!leotherasDemon) + return false; + + MarkTargetWithSquare(bot, leotherasDemon); + SetRtiTarget(botAI, "square", leotherasDemon); + + if (bot->GetVictim() != leotherasDemon) + { + bot->SetTarget(leotherasDemon->GetGUID()); + return Attack(leotherasDemon); + } + + // Fallback logic for if there is no Warlock tank (not recommended) + if (botAI->IsMainTank(bot) && botAI->IsMelee(bot) && leotherasDemon->GetVictim() == bot) + { + float maxMeleeRange = bot->GetMeleeRange(leotherasDemon); + const float meleeRangeBuffer = 0.02f; + float angle = atan2(bot->GetPositionY() - leotherasDemon->GetPositionY(), + bot->GetPositionX() - leotherasDemon->GetPositionX()); + + float targetX = leotherasDemon->GetPositionX() + (maxMeleeRange - meleeRangeBuffer) * std::cos(angle); + float targetY = leotherasDemon->GetPositionY() + (maxMeleeRange - meleeRangeBuffer) * std::sin(angle); + + if (fabs(bot->GetExactDist2d(leotherasDemon) - (maxMeleeRange - meleeRangeBuffer)) > 0.1f) + { + return MoveTo(leotherasDemon->GetMapId(), targetX, targetY, bot->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_FORCED, true, false); + } + } + + return false; +} + +// Intent is to keep enough distance to be prepared for Whirlwind +bool LeotherasTheBlindPositionRangedAction::Execute(Event event) +{ + Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); + Unit* leotherasDemon = GetActiveLeotherasDemon(botAI); + Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); + Group* group = bot->GetGroup(); + if (!leotheras || !demonFormTank || !group) + return false; + + const uint32 minInterval = 500; + if (leotheras && bot->GetExactDist2d(leotheras) < 10.0f) + return FleePosition(leotheras->GetPosition(), 12.0f, minInterval); + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || member == bot || !member->IsAlive()) + continue; + + if (demonFormTank == member && leotherasDemon && bot->GetExactDist2d(member) < 10.0f) + return FleePosition(member->GetPosition(), 12.0f, minInterval); + + if (bot->GetExactDist2d(member) < 5.0f) + return FleePosition(member->GetPosition(), 6.0f, minInterval); + } + + return false; +} + +bool LeotherasTheBlindRunAwayFromWhirlwindAction::Execute(Event event) +{ + Unit* leotherasHuman = GetLeotherasHuman(botAI); + Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI); + Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); + + if (leotherasPhase3Demon && demonFormTank == bot) + return false; + + if (leotherasHuman) + { + float currentDistance = bot->GetExactDist2d(leotherasHuman); + const float safeDistance = 15.0f; + if (currentDistance < safeDistance) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + return MoveAway(leotherasHuman, safeDistance - currentDistance + 10.0f); + } + } + + return false; +} + +// Applies only if there is no Warlock tank (not recommended) +// Try to keep maximum melee distance to avoid Chaos Blast +bool LeotherasTheBlindDemonFormPositionMeleeAction::Execute(Event event) +{ + Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI); + Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI); + if (!leotherasPhase2Demon && !leotherasPhase3Demon) + return false; + + if (!botAI->IsTank(bot) && leotherasPhase2Demon && leotherasPhase2Demon->GetVictim() != bot) + { + float maxMeleeRange = bot->GetMeleeRange(leotherasPhase2Demon); + const float meleeRangeBuffer = 0.02f; + float behindAngle = Position::NormalizeOrientation(leotherasPhase2Demon->GetOrientation() + M_PI); + + float targetX = leotherasPhase2Demon->GetPositionX() + (maxMeleeRange - meleeRangeBuffer) * std::cos(behindAngle); + float targetY = leotherasPhase2Demon->GetPositionY() + (maxMeleeRange - meleeRangeBuffer) * std::sin(behindAngle); + + if (fabs(bot->GetExactDist2d(targetX, targetY) - (maxMeleeRange - meleeRangeBuffer)) > 0.1f) + { + return MoveTo(leotherasPhase2Demon->GetMapId(), targetX, targetY, bot->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + } + + if (!botAI->IsTank(bot) && leotherasPhase3Demon && leotherasPhase3Demon->GetVictim() != bot) + { + float currentDistance = bot->GetExactDist2d(leotherasPhase3Demon); + const float safeDistance = 10.0f; + if (currentDistance < safeDistance) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + return MoveAway(leotherasPhase3Demon, safeDistance - currentDistance + 5.0f); + } + } + + return false; +} + +bool LeotherasTheBlindInnerDemonCheatAction::Execute(Event event) +{ + Unit* innerDemon = GetFirstAliveUnitByEntry(botAI, NPC_INNER_DEMON); + if (innerDemon) + { + // Tanks and healers have no ability to kill their own Inner Demons + // Hunters, Affliction Warlocks, Shadow Priests, and (for some reason) Arcane Mages also struggleo + uint8 tab = AiFactory::GetPlayerSpecTab(bot); + if (botAI->IsHeal(bot) || botAI->IsTank(bot) || + bot->getClass() == CLASS_HUNTER || + (bot->getClass() == CLASS_PRIEST && tab == 2) || + (bot->getClass() == CLASS_WARLOCK && tab == 0) || + (bot->getClass() == CLASS_MAGE && tab == 0)) + { + Unit::DealDamage(bot, innerDemon, innerDemon->GetMaxHealth() / 20, nullptr, + DIRECT_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, nullptr, false, true); + return true; + } + } + + return false; +} + +// Everybody except the Demon Form tank should focus on Leotheras +bool LeotherasTheBlindFinalPhaseAssignDpsPriorityAction::Execute(Event event) +{ + Unit* leotherasHuman = GetLeotherasHuman(botAI); + Unit* leotherasDemon = GetPhase3LeotherasDemon(botAI); + if (!leotherasHuman || !leotherasDemon) + return false; + + MarkTargetWithStar(bot, leotherasHuman); + SetRtiTarget(botAI, "star", leotherasHuman); + + if (bot->GetVictim() != leotherasHuman) + { + bot->SetTarget(leotherasHuman->GetGUID()); + return Attack(leotherasHuman); + } + + if (botAI->IsTank(bot) && leotherasHuman->GetVictim() == bot) + { + if (leotherasHuman->GetExactDist2d(leotherasDemon) < 25.0f) + { + float angle = atan2(bot->GetPositionY() - leotherasDemon->GetPositionY(), + bot->GetPositionX() - leotherasDemon->GetPositionX()); + float targetX = bot->GetPositionX() + 27.0f * std::cos(angle); + float targetY = bot->GetPositionY() + 27.0f * std::sin(angle); + + return MoveTo(bot->GetMapId(), targetX, targetY, bot->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_FORCED, true, false); + } + else if (botAI->IsTank(bot) && !bot->IsWithinMeleeRange(leotherasHuman)) + { + return MoveTo(leotherasHuman->GetMapId(), leotherasHuman->GetPositionX(), + leotherasHuman->GetPositionY(), leotherasHuman->GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); + } + } + + return false; +} + +bool LeotherasTheBlindMisdirectBossToDemonFormTankAction::Execute(Event event) +{ + Unit* leotherasDemon = GetActiveLeotherasDemon(botAI); + Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); + if (!leotherasDemon || !demonFormTank) + return false; + + if (botAI->CanCastSpell("misdirection", demonFormTank)) + return botAI->CastSpell("misdirection", demonFormTank); + + if (bot->HasAura(SPELL_MISDIRECTION) && botAI->CanCastSpell("steady shot", leotherasDemon)) + return botAI->CastSpell("steady shot", leotherasDemon); + + return false; +} + +// This does not pause DPS after a Whirlwind, which is also an aggro wipe +// I find another timer for the Whirlwind wipe to be unnecessary +bool LeotherasTheBlindManageTimersAndTrackersAction::Execute(Event event) +{ + Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); + if (!leotheras) + return false; + + const uint32 mapId = leotheras->GetMapId(); + const time_t now = std::time(nullptr); + + // Encounter start/reset: clear all timers + if (leotheras->HasAura(SPELL_LEOTHERAS_BANISHED)) + { + leotherasHumanFormDpsWaitTimer.erase(mapId); + leotherasDemonFormDpsWaitTimer.erase(mapId); + leotherasFinalPhaseDpsWaitTimer.erase(mapId); + return false; + } + + // Human Phase + Unit* leotherasHuman = GetLeotherasHuman(botAI); + Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI); + if (leotherasHuman && !leotherasPhase3Demon) + { + leotherasHumanFormDpsWaitTimer.try_emplace(mapId, now); + leotherasDemonFormDpsWaitTimer.erase(mapId); + } + // Demon Phase + else if (Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI)) + { + leotherasDemonFormDpsWaitTimer.try_emplace(mapId, now); + leotherasHumanFormDpsWaitTimer.erase(mapId); + } + // Final Phase (<15% HP) + else if (leotherasHuman && leotherasPhase3Demon) + { + leotherasFinalPhaseDpsWaitTimer.try_emplace(mapId, now); + leotherasHumanFormDpsWaitTimer.erase(mapId); + leotherasDemonFormDpsWaitTimer.erase(mapId); + } + + return false; +} + +// Fathom-Lord Karathress +// Note: Four tanks are required, but +// Caribdis hits for nothing so just respec a DPS warrior and put on a shield + +// Karathress is tanked near his starting position +bool FathomLordKarathressMainTankPositionBossAction::Execute(Event event) +{ + Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); + if (!karathress) + return false; + + MarkTargetWithTriangle(bot, karathress); + SetRtiTarget(botAI, "triangle", karathress); + + if (bot->GetVictim() != karathress) + return Attack(karathress); + + if (karathress->GetVictim() == bot) + { + const Position& position = KarathressTankPosition; + if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 3.0f) + { + float dX = position.GetPositionX() - bot->GetPositionX(); + float dY = position.GetPositionY() - bot->GetPositionY(); + float dist = sqrt(dX * dX + dY * dY); + float moveDist = std::min(5.0f, dist); + float moveX = bot->GetPositionX() + (dX / dist) * moveDist; + float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + + return MoveTo(bot->GetMapId(), moveX, moveY, position.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT, true, true); + } + } + else if (!bot->IsWithinMeleeRange(karathress)) + { + return MoveTo(karathress->GetMapId(), karathress->GetPositionX(), + karathress->GetPositionY(), karathress->GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, false); + } + + return false; +} + +// Sharkkis is pulled North to the other side of the ramp +bool FathomLordKarathressFirstAssistTankPositionSharkkisAction::Execute(Event event) +{ + Unit* sharkkis = AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis"); + if (!sharkkis) + return false; + + MarkTargetWithStar(bot, sharkkis); + SetRtiTarget(botAI, "star", sharkkis); + + if (bot->GetVictim() != sharkkis) + return Attack(sharkkis); + + if (sharkkis->GetVictim() == bot) + { + const Position& position = SharkkisTankPosition; + if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 3.0f) + { + float dX = position.GetPositionX() - bot->GetPositionX(); + float dY = position.GetPositionY() - bot->GetPositionY(); + float dist = sqrt(dX * dX + dY * dY); + float moveDist = std::min(10.0f, dist); + float moveX = bot->GetPositionX() + (dX / dist) * moveDist; + float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + + return MoveTo(bot->GetMapId(), moveX, moveY, position.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT, true, true); + } + } + else if (!bot->IsWithinMeleeRange(sharkkis)) + { + return MoveTo(sharkkis->GetMapId(), sharkkis->GetPositionX(), + sharkkis->GetPositionY(), sharkkis->GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, false); + } + + return false; +} + +// Tidalvess is pulled Northwest near the pillar +bool FathomLordKarathressSecondAssistTankPositionTidalvessAction::Execute(Event event) +{ + Unit* tidalvess = AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess"); + if (!tidalvess) + return false; + + MarkTargetWithCircle(bot, tidalvess); + SetRtiTarget(botAI, "circle", tidalvess); + + if (bot->GetVictim() != tidalvess) + return Attack(tidalvess); + + if (tidalvess->GetVictim() == bot) + { + const Position& position = TidalvessTankPosition; + if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 3.0f) + { + float dX = position.GetPositionX() - bot->GetPositionX(); + float dY = position.GetPositionY() - bot->GetPositionY(); + float dist = sqrt(dX * dX + dY * dY); + float moveDist = std::min(10.0f, dist); + float moveX = bot->GetPositionX() + (dX / dist) * moveDist; + float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + + return MoveTo(bot->GetMapId(), moveX, moveY, position.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT, true, true); + } + } + else if (!bot->IsWithinMeleeRange(tidalvess)) + { + return MoveTo(tidalvess->GetMapId(), tidalvess->GetPositionX(), + tidalvess->GetPositionY(), tidalvess->GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, false); + } + + return false; +} + +// Caribdis is pulled far to the West in the corner +// Best to use a Warrior or Druid tank for interrupts +bool FathomLordKarathressThirdAssistTankPositionCaribdisAction::Execute(Event event) +{ + Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); + if (!caribdis) + return false; + + MarkTargetWithDiamond(bot, caribdis); + SetRtiTarget(botAI, "diamond", caribdis); + + if (bot->GetVictim() != caribdis) + return Attack(caribdis); + + if (caribdis->GetVictim() == bot) + { + const Position& position = CaribdisTankPosition; + if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 3.0f) + { + float dX = position.GetPositionX() - bot->GetPositionX(); + float dY = position.GetPositionY() - bot->GetPositionY(); + float dist = sqrt(dX * dX + dY * dY); + float moveDist = std::min(10.0f, dist); + float moveX = bot->GetPositionX() + (dX / dist) * moveDist; + float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + + return MoveTo(bot->GetMapId(), moveX, moveY, position.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + } + else if (!bot->IsWithinMeleeRange(caribdis)) + { + return MoveTo(caribdis->GetMapId(), caribdis->GetPositionX(), + caribdis->GetPositionY(), caribdis->GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, false); + } + + return false; +} + +// Caribdis's tank spot is far away so a dedicated healer is needed +// Use the assistant flag to select the healer (Paladin recommended) +bool FathomLordKarathressPositionCaribdisTankHealerAction::Execute(Event event) +{ + Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); + if (!caribdis) + return false; + + const Position& position = CaribdisHealerPosition; + if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 2.0f) + { + float dX = position.GetPositionX() - bot->GetPositionX(); + float dY = position.GetPositionY() - bot->GetPositionY(); + float dist = sqrt(dX * dX + dY * dY); + float moveDist = std::min(7.0f, dist); + float moveX = bot->GetPositionX() + (dX / dist) * moveDist; + float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + + return MoveTo(bot->GetMapId(), moveX, moveY, position.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + + return false; +} + +bool FathomLordKarathressMisdirectBossesToTanksAction::Execute(Event event) +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + std::vector hunters; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->IsAlive() && member->getClass() == CLASS_HUNTER && GET_PLAYERBOT_AI(member)) + hunters.push_back(member); + if (hunters.size() >= 3) + break; + } + + int hunterIndex = -1; + for (size_t i = 0; i < hunters.size(); ++i) + { + if (hunters[i] == bot) + { + hunterIndex = static_cast(i); + break; + } + } + if (hunterIndex == -1) + return false; + + Unit* bossTarget = nullptr; + Player* tankTarget = nullptr; + if (hunterIndex == 0) + { + bossTarget = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->IsAlive() && GET_PLAYERBOT_AI(member)->IsAssistTankOfIndex(member, 2)) + { + tankTarget = member; + break; + } + } + } + else if (hunterIndex == 1) + { + bossTarget = AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess"); + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->IsAlive() && GET_PLAYERBOT_AI(member)->IsAssistTankOfIndex(member, 1)) + { + tankTarget = member; + break; + } + } + } + else if (hunterIndex == 2) + { + bossTarget = AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis"); + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->IsAlive() && GET_PLAYERBOT_AI(member)->IsAssistTankOfIndex(member, 0)) + { + tankTarget = member; + break; + } + } + } + + if (!bossTarget || !tankTarget) + return false; + + if (botAI->CanCastSpell("misdirection", tankTarget)) + return botAI->CastSpell("misdirection", tankTarget); + + if (bot->HasAura(SPELL_MISDIRECTION) && botAI->CanCastSpell("steady shot", bossTarget)) + return botAI->CastSpell("steady shot", bossTarget); + + return false; +} + +// Kill order is different from what is recommended for players because bots handle +// Caribdis Cyclones poorly and need more time to get her down (normally, ranged would help get +// Sharkkis down first) +bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) +{ + // Target priority 1: Spitfire Totems for melee + Unit* totem = GetFirstAliveUnitByEntry(botAI, NPC_SPITFIRE_TOTEM); + if (totem && botAI->IsMelee(bot) && botAI->IsDps(bot)) + { + MarkTargetWithSkull(bot, totem); + SetRtiTarget(botAI, "skull", totem); + + if (bot->GetTarget() != totem->GetGUID()) + { + bot->SetTarget(totem->GetGUID()); + return Attack(totem); + } + + return false; + } + + // Target priority 2: Tidalvess for all dps + Unit* tidalvess = AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess"); + if (tidalvess && tidalvess->IsAlive()) + { + SetRtiTarget(botAI, "circle", tidalvess); + + if (bot->GetTarget() != tidalvess->GetGUID()) + { + bot->SetTarget(tidalvess->GetGUID()); + return Attack(tidalvess); + } + + return false; + } + + // Target priority 3: Caribdis for ranged + Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); + if (botAI->IsRangedDps(bot) && caribdis && caribdis->IsAlive()) + { + SetRtiTarget(botAI, "diamond", caribdis); + + const Position& position = CaribdisRangedDpsPosition; + if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 2.0f) + { + float dX = position.GetPositionX() - bot->GetPositionX(); + float dY = position.GetPositionY() - bot->GetPositionY(); + float dist = sqrt(dX * dX + dY * dY); + float moveDist = std::min(7.0f, dist); + float moveX = bot->GetPositionX() + (dX / dist) * moveDist; + float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + + return MoveTo(bot->GetMapId(), moveX, moveY, position.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + + if (bot->GetTarget() != caribdis->GetGUID()) + { + bot->SetTarget(caribdis->GetGUID()); + return Attack(caribdis); + } + + return false; + } + + // Target priority 4: Sharkkis for melee (and ranged if Caribdis is down first) + Unit* sharkkis = AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis"); + if (sharkkis && sharkkis->IsAlive()) + { + SetRtiTarget(botAI, "star", sharkkis); + + if (bot->GetTarget() != sharkkis->GetGUID()) + { + bot->SetTarget(sharkkis->GetGUID()); + return Attack(sharkkis); + } + + return false; + } + + // Target priority 5: Sharkkis pets for all dps + Unit* fathomSporebat = AI_VALUE2(Unit*, "find target", "fathom sporebat"); + if (fathomSporebat && fathomSporebat->IsAlive() && botAI->IsMelee(bot)) + { + MarkTargetWithCross(bot, fathomSporebat); + SetRtiTarget(botAI, "cross", fathomSporebat); + + if (bot->GetTarget() != fathomSporebat->GetGUID()) + { + bot->SetTarget(fathomSporebat->GetGUID()); + return Attack(fathomSporebat); + } + + return false; + } + + Unit* fathomLurker = AI_VALUE2(Unit*, "find target", "fathom lurker"); + if (fathomLurker && fathomLurker->IsAlive() && botAI->IsMelee(bot)) + { + MarkTargetWithSquare(bot, fathomLurker); + SetRtiTarget(botAI, "square", fathomLurker); + + if (bot->GetTarget() != fathomLurker->GetGUID()) + { + bot->SetTarget(fathomLurker->GetGUID()); + return Attack(fathomLurker); + } + + return false; + } + + // Target priority 6: Karathress for all dps + Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); + if (karathress && karathress->IsAlive()) + { + SetRtiTarget(botAI, "triangle", karathress); + + if (bot->GetTarget() != karathress->GetGUID()) + { + bot->SetTarget(karathress->GetGUID()); + return Attack(karathress); + } + } + + return false; +} + +bool FathomLordKarathressManageDpsTimerAction::Execute(Event event) +{ + Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); + if (!karathress) + return false; + + const uint32 mapId = karathress->GetMapId(); + const time_t now = std::time(nullptr); + + if (karathress->GetHealth() == karathress->GetMaxHealth()) + karathressDpsWaitTimer.insert_or_assign(mapId, now); + + return false; +} + +// Morogrim Tidewalker + +bool MorogrimTidewalkerMisdirectBossToMainTankAction::Execute(Event event) +{ + Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); + Group* group = bot->GetGroup(); + if (!tidewalker || !group) + return false; + + Player* mainTank = nullptr; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->IsAlive() && botAI->IsMainTank(member)) + { + mainTank = member; + break; + } + } + + if (!mainTank) + return false; + + if (botAI->CanCastSpell("misdirection", mainTank)) + return botAI->CastSpell("misdirection", mainTank); + + if (bot->HasAura(SPELL_MISDIRECTION) && botAI->CanCastSpell("steady shot", tidewalker)) + return botAI->CastSpell("steady shot", tidewalker); + + return false; +} + +bool MorogrimTidewalkerMoveBossToTankPositionAction::Execute(Event event) +{ + Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); + if (!tidewalker) + return false; + + if (bot->GetVictim() != tidewalker) + return Attack(tidewalker); + + if (tidewalker->GetVictim() == bot && bot->IsWithinMeleeRange(tidewalker)) + { + if (tidewalker->GetHealthPct() > 26.0f) + return MoveToPhase1TankPosition(tidewalker); + else + return MoveToPhase2TankPosition(tidewalker); + } + + return false; +} + +// Phase 1 tank position is up against the Northeast pillar +bool MorogrimTidewalkerMoveBossToTankPositionAction::MoveToPhase1TankPosition(Unit* tidewalker) +{ + const Position& phase1 = TidewalkerPhase1TankPosition; + if (bot->GetExactDist2d(phase1.GetPositionX(), phase1.GetPositionY()) > 1.0f) + { + float dX = phase1.GetPositionX() - bot->GetPositionX(); + float dY = phase1.GetPositionY() - bot->GetPositionY(); + float dist = sqrt(dX * dX + dY * dY); + float moveDist = std::min(4.5f, dist); + float moveX = bot->GetPositionX() + (dX / dist) * moveDist; + float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + + return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, true); + } + + return false; +} + +// Phase 2: move in two steps to get around the pillar and back into the Northeast corner +bool MorogrimTidewalkerMoveBossToTankPositionAction::MoveToPhase2TankPosition(Unit* tidewalker) +{ + const Position& phase2 = TidewalkerPhase2TankPosition; + const Position& transition = TidewalkerPhaseTransitionWaypoint; + + const ObjectGuid botGuid = bot->GetGUID(); + auto itStep = tidewalkerTankStep.find(botGuid); + uint8 step = (itStep != tidewalkerTankStep.end()) ? itStep->second : 0; + + if (step == 0) + { + if (bot->GetExactDist2d(transition.GetPositionX(), transition.GetPositionY()) > 2.0f) + { + float dX = transition.GetPositionX() - bot->GetPositionX(); + float dY = transition.GetPositionY() - bot->GetPositionY(); + float dist = sqrt(dX * dX + dY * dY); + float moveDist = std::min(4.5f, dist); + float moveX = bot->GetPositionX() + (dX / dist) * moveDist; + float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + + return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, true); + } + else + tidewalkerTankStep.emplace(botGuid, 1); + } + + if (step == 1) + { + if (bot->GetExactDist2d(phase2.GetPositionX(), phase2.GetPositionY()) > 1.0f) + { + float dX = phase2.GetPositionX() - bot->GetPositionX(); + float dY = phase2.GetPositionY() - bot->GetPositionY(); + float dist = sqrt(dX * dX + dY * dY); + float moveDist = std::min(4.5f, dist); + float moveX = bot->GetPositionX() + (dX / dist) * moveDist; + float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + + return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, true); + } + } + + return false; +} + +// Stack behind the boss in the Northeast corner in phase 2 +bool MorogrimTidewalkerPhase2RepositionRangedAction::Execute(Event event) +{ + Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); + if (!tidewalker) + return false; + + const Position& phase2 = TidewalkerPhase2RangedPosition; + const Position& transition = TidewalkerPhaseTransitionWaypoint; + + const ObjectGuid botGuid = bot->GetGUID(); + auto itStep = tidewalkerRangedStep.find(botGuid); + uint8 step = (itStep != tidewalkerRangedStep.end()) ? itStep->second : 0; + + if (step == 0) + { + if (bot->GetExactDist2d(transition.GetPositionX(), transition.GetPositionY()) > 2.0f) + { + float dX = transition.GetPositionX() - bot->GetPositionX(); + float dY = transition.GetPositionY() - bot->GetPositionY(); + float dist = sqrt(dX * dX + dY * dY); + float moveDist = std::min(7.0f, dist); + float moveX = bot->GetPositionX() + (dX / dist) * moveDist; + float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + + return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + else + { + tidewalkerRangedStep.emplace(botGuid, 1); + step = 1; + } + } + + if (step == 1) + { + if (bot->GetExactDist2d(phase2.GetPositionX(), phase2.GetPositionY()) > 1.0f) + { + float dX = phase2.GetPositionX() - bot->GetPositionX(); + float dY = phase2.GetPositionY() - bot->GetPositionY(); + float dist = sqrt(dX * dX + dY * dY); + float moveDist = std::min(7.0f, dist); + float moveX = bot->GetPositionX() + (dX / dist) * moveDist; + float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + + return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + } + + return false; +} + +bool MorogrimTidewalkerResetPhaseTransitionStepsAction::Execute(Event event) +{ + Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); + if (!tidewalker) + return false; + + const ObjectGuid botGuid = bot->GetGUID(); + + if (tidewalker->GetHealth() == tidewalker->GetMaxHealth()) + { + tidewalkerTankStep.erase(botGuid); + tidewalkerRangedStep.erase(botGuid); + } + + return false; +} + +// Lady Vashj + +// Center of room (phase 1 only) +bool LadyVashjMainTankPositionBossAction::Execute(Event event) +{ + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + if (!vashj) + return false; + + if (bot->GetVictim() != vashj) + return Attack(vashj); + + if (vashj->GetVictim() == bot && bot->IsWithinMeleeRange(vashj)) + { + if (IsLadyVashjInPhase1(botAI)) + { + const Position& position = VashjPlatformCenterPosition; + if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 2.0f) + { + float dX = position.GetPositionX() - bot->GetPositionX(); + float dY = position.GetPositionY() - bot->GetPositionY(); + float dist = sqrt(dX * dX + dY * dY); + float moveDist = std::min(4.5f, dist); + float moveX = bot->GetPositionX() + (dX / dist) * moveDist; + float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + + return MoveTo(bot->GetMapId(), moveX, moveY, position.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, true); + } + } + + if (IsLadyVashjInPhase3(botAI)) + { + Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental"); + if (enchanted) + { + float currentDistance = bot->GetExactDist2d(enchanted); + const float safeDistance = 10.0f; + if (currentDistance < safeDistance) + return MoveAway(enchanted, safeDistance - currentDistance + 5.0f); + } + } + } + + return false; +} + +// Semicircle around center of the room (to allow escape by Static Charged bots) +bool LadyVashjPhase1PositionRangedAction::Execute(Event event) +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + std::vector spreadMembers; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->IsAlive() && GET_PLAYERBOT_AI(member)) + { + PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); + if (memberAI->IsRanged(member)) + spreadMembers.push_back(member); + } + } + + const ObjectGuid guid = bot->GetGUID(); + + auto itPos = vashjRangedPositions.find(guid); + auto itReached = vashjHasReachedRangedPosition.find(guid); + if (itPos == vashjRangedPositions.end()) + { + auto it = std::find(spreadMembers.begin(), spreadMembers.end(), bot); + size_t botIndex = (it != spreadMembers.end()) ? std::distance(spreadMembers.begin(), it) : 0; + size_t count = spreadMembers.size(); + if (count == 0) + return false; + + const Position& center = VashjPlatformCenterPosition; + const float minRadius = 20.0f; + const float maxRadius = 30.0f; + + const float referenceAngle = M_PI / 2.0f; // north + const float arcSpan = M_PI; // 180° + const float startAngle = referenceAngle - arcSpan / 2.0f; + + float angle; + if (count == 1) + angle = referenceAngle; + else + angle = startAngle + (static_cast(botIndex) / (count - 1)) * arcSpan; + + float radius = frand(minRadius, maxRadius); + float targetX = center.GetPositionX() + radius * std::cos(angle); + float targetY = center.GetPositionY() + radius * std::sin(angle); + float tz = center.GetPositionZ(); + + auto res = vashjRangedPositions.emplace(guid, Position(targetX, targetY, tz)); + itPos = res.first; + vashjHasReachedRangedPosition.emplace(guid, false); + itReached = vashjHasReachedRangedPosition.find(guid); + } + + if (itPos == vashjRangedPositions.end()) + return false; + + Position targetPosition = itPos->second; + if (itReached == vashjHasReachedRangedPosition.end() || !(itReached->second)) + { + if (!bot->IsWithinDist2d(targetPosition.GetPositionX(), targetPosition.GetPositionY(), 2.0f)) + { + return MoveTo(bot->GetMapId(), targetPosition.GetPositionX(), targetPosition.GetPositionY(), targetPosition.GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); + } + if (itReached != vashjHasReachedRangedPosition.end()) + itReached->second = true; + } + + return false; +} + +// For absorbing Shock Burst +bool LadyVashjSetGroundingTotemInMainTankGroupAction::Execute(Event event) +{ + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + Group* group = bot->GetGroup(); + if (!group) + return false; + + Player* mainTank = nullptr; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->IsAlive() && botAI->IsMainTank(member)) + { + mainTank = member; + break; + } + } + + if (!mainTank) + return false; + + float dist = bot->GetExactDist2d(mainTank); + if (dist >= 27.0f) + { + float angle = atan2(bot->GetPositionY() - mainTank->GetPositionY(), + bot->GetPositionX() - mainTank->GetPositionX()); + float targetX = mainTank->GetPositionX() + 25.0f * std::cos(angle); + float targetY = mainTank->GetPositionY() + 25.0f * std::sin(angle); + + return MoveTo(mainTank->GetMapId(), targetX, targetY, mainTank->GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); + } + + if (!botAI->HasStrategy("grounding totem", BotState::BOT_STATE_COMBAT)) + botAI->ChangeStrategy("+grounding totem", BotState::BOT_STATE_COMBAT); + + if (!bot->HasAura(SPELL_GROUNDING_TOTEM_EFFECT) && botAI->CanCastSpell("grounding totem", bot)) + return botAI->CastSpell("grounding totem", bot); + + return false; +} + +bool LadyVashjMisdirectBossToMainTankAction::Execute(Event event) +{ + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + Group* group = bot->GetGroup(); + if (!vashj || !group) + return false; + + Player* mainTank = nullptr; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->IsAlive() && botAI->IsMainTank(member)) + { + mainTank = member; + break; + } + } + + if (!mainTank) + return false; + + if (botAI->CanCastSpell("misdirection", mainTank)) + return botAI->CastSpell("misdirection", mainTank); + + if (bot->HasAura(SPELL_MISDIRECTION) && botAI->CanCastSpell("steady shot", vashj)) + return botAI->CastSpell("steady shot", vashj); + + return false; +} + +bool LadyVashjStaticChargeMoveAwayFromGroupAction::Execute(Event event) +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + // If the main tank has Static Charge, other group members should move away + Player* mainTank = nullptr; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->IsAlive() && botAI->IsMainTank(member) && + member->HasAura(SPELL_STATIC_CHARGE)) + { + mainTank = member; + break; + } + } + + if (mainTank && bot != mainTank) + { + float currentDistance = bot->GetExactDist2d(mainTank); + const float safeDistance = 10.0f; + if (currentDistance < safeDistance) + return MoveFromGroup(safeDistance + 0.5f); + } + + // If any other bot has static charge, it should move away from other group members + if (!botAI->IsMainTank(bot) && bot->HasAura(SPELL_STATIC_CHARGE)) + { + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || member == bot) + continue; + + float currentDistance = bot->GetExactDist2d(member); + const float safeDistance = 10.0f; + if (currentDistance < safeDistance) + return MoveFromGroup(safeDistance + 0.5f); + } + } + + return false; +} + +bool LadyVashjMisdirectStriderToFirstAssistTankAction::Execute(Event event) +{ + // Strider is not tankable without cheat + if (!botAI->HasCheat(BotCheatMask::raid)) + return false; + + if (bot->getClass() != CLASS_HUNTER) + return false; + + Unit* strider = GetFirstAliveUnitByEntry(botAI, NPC_COILFANG_STRIDER); + Group* group = bot->GetGroup(); + if (!strider || !group) + return false; + + Player* firstAssistTank = nullptr; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->IsAlive() && botAI->IsAssistTankOfIndex(member, 0)) + { + firstAssistTank = member; + break; + } + } + + if (!firstAssistTank || strider->GetVictim() == firstAssistTank) + return false; + + if (botAI->CanCastSpell("misdirection", firstAssistTank)) + return botAI->CastSpell("misdirection", firstAssistTank); + + if (bot->HasAura(SPELL_MISDIRECTION) && botAI->CanCastSpell("steady shot", strider)) + return botAI->CastSpell("steady shot", strider); + + return false; +} + +bool LadyVashjTankAttackAndMoveAwayStriderAction::Execute(Event event) +{ + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + Unit* strider = GetFirstAliveUnitByEntry(botAI, NPC_COILFANG_STRIDER); + Group* group = bot->GetGroup(); + if (!vashj || !strider || !group) + return false; + + // Raid cheat automatically applies Fear Ward to tanks to make Strider tankable + // This simulates the real-life strategy where the Strider can be meleed by + // Bots wearing an Ogre Suit (due to the extended combat reach) + if (botAI->HasCheat(BotCheatMask::raid) && botAI->IsTank(bot)) + { + if (!bot->HasAura(SPELL_FEAR_WARD)) + bot->AddAura(SPELL_FEAR_WARD, bot); + + if (botAI->IsAssistTankOfIndex(bot, 0) && bot->GetVictim() != strider) + return Attack(strider); + + if (strider->GetVictim() == bot) + { + float currentDistance = bot->GetExactDist2d(vashj); + const float safeDistance = 20.0f; + + if (currentDistance < safeDistance) + return MoveAway(vashj, safeDistance - currentDistance + 5.0f); + + Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI); + Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI); + + // Move the Strider away from the first two passers; the third and fourth passers + // are rarely needed so they are ignored to avoid too many restrictions on movement + for (Player* passer : { firstCorePasser, secondCorePasser }) + { + if (passer && passer != bot) + { + float currentDistFromPasser = bot->GetExactDist2d(passer); + const float safeDistFromPasser = 15.0f; + if (currentDistFromPasser < safeDistFromPasser) + return MoveAway(strider, safeDistFromPasser - currentDistFromPasser + 5.0f); + } + } + } + + return false; + } + + // Don't move away if raid cheats are enabled, or in any case if the bot is a tank + if (!botAI->HasCheat(BotCheatMask::raid) || !botAI->IsTank(bot)) + { + float currentDistance = bot->GetExactDist2d(strider); + const float safeDistance = 15.0f; + if (currentDistance < safeDistance) + return MoveAway(strider, safeDistance - currentDistance + 5.0f); + } + + // Try to root/slow the Strider if it is not tankable + if (!botAI->HasCheat(BotCheatMask::raid)) + { + if (!strider->HasAura(SPELL_HEAVY_NETHERWEAVE_NET)) + { + Item* net = bot->GetItemByEntry(ITEM_HEAVY_NETHERWEAVE_NET); + if (net && botAI->HasItemInInventory(ITEM_HEAVY_NETHERWEAVE_NET) && + botAI->CanCastSpell("heavy netherweave net", strider)) + return botAI->CastSpell("heavy netherweave net", strider); + } + + if (!strider->HasAura(SPELL_FROST_SHOCK) && bot->getClass() == CLASS_SHAMAN && + botAI->CanCastSpell("frost shock", strider)) + return botAI->CastSpell("frost shock", strider); + + if (!strider->HasAura(SPELL_CURSE_OF_EXHAUSTION) && bot->getClass() == CLASS_WARLOCK && + botAI->CanCastSpell("curse of exhaustion", strider)) + return botAI->CastSpell("curse of exhaustion", strider); + + if (!strider->HasAura(SPELL_SLOW) && bot->getClass() == CLASS_MAGE && + botAI->CanCastSpell("slow", strider)) + return botAI->CastSpell("slow", strider); + } + + return false; +} + +bool LadyVashjAssignDpsPriorityAction::Execute(Event event) +{ + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + if (!vashj) + return false; + + GuidVector attackers = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); + Unit* target = nullptr; + Unit* tainted = nullptr; + Unit* enchanted = nullptr; + Unit* elite = nullptr; + Unit* strider = nullptr; + Unit* sporebat = nullptr; + + if (bot->GetVictim() == vashj && (IsLadyVashjInPhase2(botAI) || (IsLadyVashjInPhase3(botAI) && + (enchanted && enchanted->IsAlive() || elite && elite->IsAlive() || strider && strider->IsAlive())))) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + bot->SetTarget(ObjectGuid::Empty); + bot->SetSelection(ObjectGuid()); + } + + // Search and attack radius are intended to keep bots on the platform (not go down the stairs) + const Position& center = VashjPlatformCenterPosition; + const float maxSearchRange = botAI->IsRangedDps(bot) ? 60.0f : (botAI->IsMelee(bot) ? 55.0f : 40.0f); + const float maxPursueRange = maxSearchRange - 5.0f; + + for (auto guid : attackers) + { + Unit* unit = botAI->GetUnit(guid); + if (!IsValidPhase2CombatNpc(unit, botAI)) + continue; + + float distFromCenter = unit->GetExactDist2d(center.GetPositionX(), center.GetPositionY()); + if (IsLadyVashjInPhase2(botAI) && distFromCenter > maxSearchRange) + continue; + + switch (unit->GetEntry()) + { + case NPC_TAINTED_ELEMENTAL: + if (!tainted || bot->GetExactDist2d(unit) < bot->GetExactDist2d(tainted)) + tainted = unit; + break; + + case NPC_ENCHANTED_ELEMENTAL: + if (!enchanted || vashj->GetExactDist2d(unit) < vashj->GetExactDist2d(enchanted)) + enchanted = unit; + break; + + case NPC_COILFANG_ELITE: + if (!elite || unit->GetHealthPct() < elite->GetHealthPct()) + elite = unit; + break; + + case NPC_COILFANG_STRIDER: + if (!strider || unit->GetHealthPct() < strider->GetHealthPct()) + strider = unit; + break; + + case NPC_TOXIC_SPOREBAT: + if (!sporebat || unit->GetHealthPct() < sporebat->GetHealthPct()) + sporebat = unit; + break; + + case NPC_LADY_VASHJ: + vashj = unit; + break; + + default: + break; + } + } + + std::vector targets; + if (IsLadyVashjInPhase2(botAI)) + { + if (botAI->IsRanged(bot)) + { + // Hunters and Mages prioritize Enchanted Elementals, while other ranged DPS prioritize Striders + // This works well with 3 Hunters and 2 Mages; effectiveness may vary based on raid composition + if (bot->getClass() == CLASS_HUNTER || bot->getClass() == CLASS_MAGE) + targets = { tainted, enchanted, strider, elite }; + else + targets = { tainted, strider, elite, enchanted }; + } + else if (botAI->IsMelee(bot) && botAI->IsDps(bot)) + targets = { tainted, enchanted, elite }; + else if (botAI->IsTank(bot)) + { + // With raid cheats enabled, the first assist tank will tank the Strider + if (botAI->HasCheat(BotCheatMask::raid) && botAI->IsAssistTankOfIndex(bot, 0)) + targets = { strider, enchanted, tainted }; + else + targets = { elite, enchanted, tainted }; + } + else + targets = { tainted, enchanted, elite, strider }; + } + + if (IsLadyVashjInPhase3(botAI)) + { + if (botAI->IsTank(bot)) + { + if (botAI->IsMainTank(bot)) + { + MarkTargetWithDiamond(bot, vashj); + SetRtiTarget(botAI, "diamond", vashj); + targets = { vashj }; + } + else if (botAI->IsAssistTankOfIndex(bot, 0)) + { + if (botAI->HasCheat(BotCheatMask::raid)) + targets = { strider, enchanted, vashj }; + else + targets = { elite, enchanted, vashj }; + } + else + targets = { elite, enchanted, vashj }; + } + if (botAI->IsRanged(bot)) + { + // Hunters will try to kill Toxic Sporebats (in practice, they are generally not in range) + if (bot->getClass() == CLASS_HUNTER) + targets = { enchanted, sporebat, strider, elite, vashj }; + else + targets = { enchanted, strider, elite, vashj }; + } + if (botAI->IsMelee(bot) && botAI->IsDps(bot)) + targets = { enchanted, elite, vashj }; + else + targets = { enchanted, elite, strider, vashj }; + } + + for (Unit* candidate : targets) + { + if (candidate && candidate->IsAlive()) + { + target = candidate; + break; + } + } + + Unit* currentTarget = context->GetValue("current target")->Get(); + if (target && currentTarget == target && IsValidPhase2CombatNpc(currentTarget, botAI)) + return false; + + if (target && bot->GetExactDist2d(target) <= maxPursueRange && + bot->GetTarget() != target->GetGUID()) + { + bot->SetTarget(target->GetGUID()); + return Attack(target); + } + + if (currentTarget && (!currentTarget->IsAlive() || !IsValidPhase2CombatNpc(currentTarget, botAI))) + { + context->GetValue("current target")->Set(nullptr); + bot->SetTarget(ObjectGuid::Empty); + bot->SetSelection(ObjectGuid()); + } + + // If bots have wandered too far from the center and are not attacking anything, move them back + if (!bot->GetVictim()) + { + Player* master = botAI->GetMaster(); + Player* designatedLooter = GetDesignatedCoreLooter(bot->GetGroup(), master, botAI); + Player* firstCorePasser = GetFirstTaintedCorePasser(bot->GetGroup(), botAI); + // A bot will not move back to the middle if: + // (1) The designated looter is within 10 yards of a Tainted Elemental, and the bot is + // either the designated looter or the first core passer, or + // (2) It has the Paralyze aura + if (designatedLooter && tainted && designatedLooter->GetExactDist2d(tainted) < 5.0f && + (designatedLooter == bot || (firstCorePasser && firstCorePasser == bot)) || + bot->HasAura(SPELL_PARALYZE)) + return false; + + const Position& center = VashjPlatformCenterPosition; + if (bot->GetExactDist2d(center.GetPositionX(), center.GetPositionY()) > 35.0f) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + + return MoveInside(bot->GetMapId(), center.GetPositionX(), center.GetPositionY(), + center.GetPositionZ(), 30.0f, MovementPriority::MOVEMENT_COMBAT); + } + } + + return false; +} + +// If cheats are enabled, the first returned melee DPS bot will teleport to Tainted Elementals +// Such bot will recover HP and remove Poison Bolt debuff while attacking the elemental +bool LadyVashjTeleportToTaintedElementalAction::Execute(Event event) +{ + Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental"); + if (!tainted) + return false; + + lastTaintedGuid = tainted->GetGUID(); + if (bot->GetExactDist2d(tainted) >= 10.0f) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + bot->TeleportTo(tainted->GetMapId(), tainted->GetPositionX(), tainted->GetPositionY(), + tainted->GetPositionZ(), tainted->GetOrientation()); + } + + if (bot->GetVictim() != tainted) + { + MarkTargetWithStar(bot, tainted); + SetRtiTarget(botAI, "star", tainted); + + return Attack(tainted); + } + + if (bot->GetExactDist2d(tainted) < 5.0f) + { + bot->SetFullHealth(); + bot->RemoveAura(SPELL_POISON_BOLT); + } + + return false; +} + +bool LadyVashjLootTaintedCoreAction::Execute(Event) +{ + GuidVector corpses = context->GetValue("nearest corpses")->Get(); + const float maxLootRange = sPlayerbotAIConfig->lootDistance; + + for (auto const& guid : corpses) + { + LootObject loot(bot, guid); + if (!loot.IsLootPossible(bot)) + continue; + + WorldObject* object = loot.GetWorldObject(bot); + if (!object) + continue; + + // The Tainted Elemental lootable corpse is a dead Creature object, not a corpse object + Creature* creature = object->ToCreature(); + if (!creature) + continue; + + if (creature->GetEntry() != NPC_TAINTED_ELEMENTAL || creature->IsAlive()) + continue; + + context->GetValue("loot target")->Set(loot); + + float dist = bot->GetDistance(object); + + if (dist > maxLootRange) + return MoveTo(object, 2.0f, MovementPriority::MOVEMENT_FORCED); + + // Invoke OpenLootAction to request the server's StoreLoot packet for this corpse + // Attempt a forced autostore (without modifying LootAction) by scheduling a short-timer to send + // CMSG_AUTOSTORE_LOOT_ITEM (index 0) once the server has had time to send the StoreLoot packet + OpenLootAction open(botAI); + bool opened = open.Execute(Event()); + + if (!opened) + return opened; + + // If anyone in the group already has the core, skip creating a duplicate + if (Group* group = bot->GetGroup()) + { + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->HasItemCount(ITEM_TAINTED_CORE, 1, false)) + return true; + } + } + + // Schedule autostore attempt + reconcile fallback + const ObjectGuid botGuid = bot->GetGUID(); + const ObjectGuid corpseGuid = guid; + const uint8 coreIndex = 0; + + botAI->AddTimedEvent([botGuid, corpseGuid, coreIndex]() + { + Player* receiver = botGuid.IsEmpty() ? nullptr : ObjectAccessor::FindPlayer(botGuid); + if (!receiver) + return; + + // Double-check someone else didn't obtain the core in the meantime using receiver's group + if (Group* group = receiver->GetGroup()) + { + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->HasItemCount(ITEM_TAINTED_CORE, 1, false)) + return; + } + } + + // Set the loot GUID so server treats the following autostore as targeted to this corpse + receiver->SetLootGUID(corpseGuid); + + WorldPacket* packet = new WorldPacket(CMSG_AUTOSTORE_LOOT_ITEM, 1); + *packet << coreIndex; + receiver->GetSession()->QueuePacket(packet); + }, 600); + + return true; + } + + return false; +} + +bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) +{ + Player* master = botAI->GetMaster(); + Group* group = bot->GetGroup(); + if (!master || !group) + return false; + + Player* designatedLooter = GetDesignatedCoreLooter(group, master, botAI); + if (!designatedLooter) + return false; + + Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI); + Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI); + Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI); + Player* fourthCorePasser = GetFourthTaintedCorePasser(group, botAI); + Unit* closestTrigger = GetNearestActiveShieldGeneratorTriggerByEntry(bot, designatedLooter); + + if (!firstCorePasser || !secondCorePasser || !thirdCorePasser || !fourthCorePasser || !closestTrigger) + return false; + + // Passer order: HealAssistantOfIndex 0, 1, 2, then RangedDpsAssistantOfIndex 0 + if (bot == firstCorePasser && !botAI->HasItemInInventory(ITEM_TAINTED_CORE)) + { + if (LineUpFirstCorePasser(designatedLooter, closestTrigger)) + return true; + } + else if (bot == secondCorePasser && !botAI->HasItemInInventory(ITEM_TAINTED_CORE)) + { + if (LineUpSecondCorePasser(firstCorePasser, closestTrigger)) + return true; + } + else if (bot == thirdCorePasser && !botAI->HasItemInInventory(ITEM_TAINTED_CORE)) + { + if (LineUpThirdCorePasser(secondCorePasser, closestTrigger)) + return true; + } + else if (bot == fourthCorePasser && !botAI->HasItemInInventory(ITEM_TAINTED_CORE)) + { + if (LineUpFourthCorePasser(thirdCorePasser, closestTrigger)) + return true; + } + + Item* item = bot->GetItemByEntry(ITEM_TAINTED_CORE); + if (item && botAI->HasItemInInventory(ITEM_TAINTED_CORE)) + { + // Designated core looter logic--applicable only if cheat mode is on and thus looter is a bot + if (bot == designatedLooter) + { + if (IsFirstCorePasserInIntendedPosition(designatedLooter, firstCorePasser, closestTrigger)) + { + const ObjectGuid giverGuid = bot->GetGUID(); + const time_t now = std::time(nullptr); + + auto it = lastImbueAttempt.find(giverGuid); + if (it == lastImbueAttempt.end() || (now - it->second) >= 2) + { + botAI->ImbueItem(item, firstCorePasser); + lastImbueAttempt[giverGuid] = now; + ScheduleStoreCoreAfterImbue(botAI, bot, firstCorePasser); + return true; + } + } + } + // First core passer: receive core from looter at the top of the stairs, pass to second core passer + else if (bot == firstCorePasser) + { + if (IsSecondCorePasserInIntendedPosition(firstCorePasser, secondCorePasser, closestTrigger)) + { + const ObjectGuid giverGuid = bot->GetGUID(); + const time_t now = std::time(nullptr); + + auto it = lastImbueAttempt.find(giverGuid); + if (it == lastImbueAttempt.end() || (now - it->second) >= 2) + { + botAI->ImbueItem(item, secondCorePasser); + lastImbueAttempt[giverGuid] = now; + ScheduleStoreCoreAfterImbue(botAI, bot, secondCorePasser); + return true; + } + } + } + // Second core passer: if closest usable generator is within passing distance of the first passer, move + // to the generator; otherwise, move as close as possible to the generator while remaining in passing range + // Usually, the second core passer will be able to use the generator, but it depends on where the elemental spawns + else if (bot == secondCorePasser) + { + if (!UseCoreOnNearestGenerator()) + { + if (IsThirdCorePasserInIntendedPosition(secondCorePasser, thirdCorePasser, closestTrigger)) + { + const ObjectGuid giverGuid = bot->GetGUID(); + const time_t now = std::time(nullptr); + + auto it = lastImbueAttempt.find(giverGuid); + if (it == lastImbueAttempt.end() || (now - it->second) >= 2) + { + botAI->ImbueItem(item, thirdCorePasser); + lastImbueAttempt[giverGuid] = now; + ScheduleStoreCoreAfterImbue(botAI, bot, thirdCorePasser); + return true; + } + } + } + } + // Third core passer: if closest usable generator is within passing distance of the second passer, move + // to the generator; otherwise, move as close as possible to the generator while remaining in passing range + else if (bot == thirdCorePasser) + { + if (!UseCoreOnNearestGenerator()) + { + if (IsFourthCorePasserInIntendedPosition(thirdCorePasser, fourthCorePasser, closestTrigger)) + { + const ObjectGuid giverGuid = bot->GetGUID(); + const time_t now = std::time(nullptr); + + auto it = lastImbueAttempt.find(giverGuid); + if (it == lastImbueAttempt.end() || (now - it->second) >= 2) + { + botAI->ImbueItem(item, fourthCorePasser); + lastImbueAttempt[giverGuid] = now; + ScheduleStoreCoreAfterImbue(botAI, bot, fourthCorePasser); + return true; + } + } + } + } + // Fourth core passer: the fourth passer is rarely needed and will always be enough to reach a usable generator + else if (bot == fourthCorePasser) + { + UseCoreOnNearestGenerator(); + } + } + + return false; +} + +bool LadyVashjPassTheTaintedCoreAction::LineUpFirstCorePasser(Player* designatedLooter, Unit* closestTrigger) +{ + const float centerX = VashjPlatformCenterPosition.GetPositionX(); + const float centerY = VashjPlatformCenterPosition.GetPositionY(); + const float radius = 57.5f; + + float mx = designatedLooter->GetPositionX(); + float my = designatedLooter->GetPositionY(); + float angle = atan2(my - centerY, mx - centerX); + + float targetX = centerX + radius * std::cos(angle); + float targetY = centerY + radius * std::sin(angle); + const float targetZ = 41.097f; + + intendedLineup.insert_or_assign(bot->GetGUID(), Position(targetX, targetY, targetZ)); + + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, + false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); +} + +bool LadyVashjPassTheTaintedCoreAction::LineUpSecondCorePasser(Player* firstCorePasser, Unit* closestTrigger) +{ + float fx = firstCorePasser->GetPositionX(); + float fy = firstCorePasser->GetPositionY(); + + float dx = closestTrigger->GetPositionX() - fx; + float dy = closestTrigger->GetPositionY() - fy; + float distToTrigger = std::sqrt(dx*dx + dy*dy); + + if (distToTrigger == 0.0f) + return false; + + dx /= distToTrigger; dy /= distToTrigger; + + // Target is on a line between firstCorePasser and closestTrigger + float targetX, targetY, targetZ; + // if firstCorePasser is within this distance of closestTrigger, go to nearTriggerDist short of closestTrigger + const float thresholdDist = 40.0f; + const float nearTriggerDist = 1.5f; + // if firstCorePasser is not thresholdDist yards from closestTrigger, go to farDistance from firstCorePasser + const float farDistance = 38.0f; + + if (distToTrigger <= thresholdDist) + { + float moveDist = std::max(distToTrigger - nearTriggerDist, 0.0f); + targetX = fx + dx * moveDist; + targetY = fy + dy * moveDist; + targetZ = 42.985f; + } + else + { + targetX = fx + dx * farDistance; + targetY = fy + dy * farDistance; + targetZ = 42.985f; + } + + intendedLineup.insert_or_assign(bot->GetGUID(), Position(targetX, targetY, targetZ)); + + bot->AttackStop(); + bot->InterruptNonMeleeSpells(false); + return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, + false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); +} + +bool LadyVashjPassTheTaintedCoreAction::LineUpThirdCorePasser(Player* secondCorePasser, Unit* closestTrigger) +{ + // Since the third passer is often not needed, wait until the second passer has the core to move + if (!secondCorePasser->HasItemCount(ITEM_TAINTED_CORE, 1, false)) + return false; + + if (secondCorePasser->GetExactDist2d(closestTrigger) <= 2.0f) + return false; + + float sx = secondCorePasser->GetPositionX(); + float sy = secondCorePasser->GetPositionY(); + + float dx = closestTrigger->GetPositionX() - sx; + float dy = closestTrigger->GetPositionY() - sy; + float distToTrigger = std::sqrt(dx*dx + dy*dy); + + if (distToTrigger == 0.0f) + return false; + + dx /= distToTrigger; dy /= distToTrigger; + + float targetX, targetY, targetZ; + const float thresholdDist = 40.0f; + const float nearTriggerDist = 1.5f; + const float farDistance = 38.0f; + + if (distToTrigger <= thresholdDist) + { + float moveDist = std::max(distToTrigger - nearTriggerDist, 0.0f); + targetX = sx + dx * moveDist; + targetY = sy + dy * moveDist; + targetZ = 42.985f; + } + else + { + targetX = sx + dx * farDistance; + targetY = sy + dy * farDistance; + targetZ = 42.985f; + } + + intendedLineup.insert_or_assign(bot->GetGUID(), Position(targetX, targetY, targetZ)); + + bot->AttackStop(); + bot->InterruptNonMeleeSpells(false); + return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, + false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); +} + +bool LadyVashjPassTheTaintedCoreAction::LineUpFourthCorePasser(Player* thirdCorePasser, Unit* closestTrigger) +{ + // Since the fourth passer is often not needed, wait until the third passer has the core to move + if (!thirdCorePasser->HasItemCount(ITEM_TAINTED_CORE, 1, false)) + return false; + + if (thirdCorePasser->GetExactDist2d(closestTrigger) <= 2.0f) + return false; + + float sx = thirdCorePasser->GetPositionX(); + float sy = thirdCorePasser->GetPositionY(); + + float tx = closestTrigger->GetPositionX(); + float ty = closestTrigger->GetPositionY(); + + float dx = tx - sx; + float dy = ty - sy; + float length = std::sqrt(dx*dx + dy*dy); + + if (length == 0.0f) + return false; + + dx /= length; dy /= length; + + float targetX = tx - dx * 2.0f; + float targetY = ty - dy * 2.0f; + const float targetZ = 42.985f; + + intendedLineup.insert_or_assign(bot->GetGUID(), Position(targetX, targetY, targetZ)); + + bot->AttackStop(); + bot->InterruptNonMeleeSpells(false); + return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, + false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); +} + +// The next four functions check if the respective core passer is within 2 yards of their intended position +// And are used to determine when the prior bot in the chain can pass the core +// Known issue: if a passer bot is feared by a strider, the chain can be broken; if this happens, it is best +// to order bots to destroy cores to reset the sequence for the next elemental spawn +bool LadyVashjPassTheTaintedCoreAction::IsFirstCorePasserInIntendedPosition( + Player* designatedLooter, Player* firstCorePasser, Unit* closestTrigger) +{ + auto itSnap = intendedLineup.find(firstCorePasser->GetGUID()); + if (itSnap != intendedLineup.end()) + { + float dist2d = firstCorePasser->GetExactDist2d(itSnap->second.GetPositionX(), itSnap->second.GetPositionY()); + return dist2d <= 2.0f; + } + + return false; +} + +bool LadyVashjPassTheTaintedCoreAction::IsSecondCorePasserInIntendedPosition( + Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger) +{ + auto itSnap = intendedLineup.find(secondCorePasser->GetGUID()); + if (itSnap != intendedLineup.end()) + { + float dist2d = secondCorePasser->GetExactDist2d(itSnap->second.GetPositionX(), itSnap->second.GetPositionY()); + return dist2d <= 2.0f; + } + + return false; +} + +bool LadyVashjPassTheTaintedCoreAction::IsThirdCorePasserInIntendedPosition( + Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger) +{ + auto itSnap = intendedLineup.find(thirdCorePasser->GetGUID()); + if (itSnap != intendedLineup.end()) + { + float dist2d = thirdCorePasser->GetExactDist2d(itSnap->second.GetPositionX(), itSnap->second.GetPositionY()); + return dist2d <= 2.0f; + } + + return false; +} + +bool LadyVashjPassTheTaintedCoreAction::IsFourthCorePasserInIntendedPosition( + Player* thirdCorePasser, Player* fourthCorePasser, Unit* closestTrigger) +{ + auto itSnap = intendedLineup.find(fourthCorePasser->GetGUID()); + if (itSnap != intendedLineup.end()) + { + float dist2d = fourthCorePasser->GetExactDist2d(itSnap->second.GetPositionX(), itSnap->second.GetPositionY()); + return dist2d <= 2.0f; + } + + return false; +} + +// Because ImbueItem() does not actually cause the receiving bot to receive the core, we have to simulate +// the passing mechanic by creating the core on the receiver (ImbueItem() does take away the core from the passer) +void LadyVashjPassTheTaintedCoreAction::ScheduleStoreCoreAfterImbue(PlayerbotAI* botAI, Player* giver, Player* receiver) +{ + if (!receiver) + return; + + const uint32 delayMs = 1500; + + const ObjectGuid giverGuid = giver ? giver->GetGUID() : ObjectGuid::Empty; + const ObjectGuid receiverGuid = receiver->GetGUID(); + + botAI->AddTimedEvent([botAI, giverGuid, receiverGuid]() + { + Player* receiverPlayer = receiverGuid.IsEmpty() ? nullptr : ObjectAccessor::FindPlayer(receiverGuid); + Player* giverPlayer = giverGuid.IsEmpty() ? nullptr : ObjectAccessor::FindPlayer(giverGuid); + + if (!receiverPlayer) + { + intendedLineup.erase(receiverGuid); + intendedLineup.erase(giverGuid); + return; + } + + // Detect if anyone already has the core + if (Group* group = receiverPlayer->GetGroup()) + { + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + + if (!member) + continue; + + if (member->HasItemCount(ITEM_TAINTED_CORE, 1, false)) + { + intendedLineup.erase(receiverGuid); + intendedLineup.erase(giverGuid); + return; + } + } + } + + if (receiverPlayer->HasItemCount(ITEM_TAINTED_CORE, 1, false)) + { + intendedLineup.erase(receiverGuid); + intendedLineup.erase(giverGuid); + return; + } + + // Store a new core into receiver inventory (sends client/db update) + ItemPosCountVec dest; + uint32 count = 1; + int canStore = receiverPlayer->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, ITEM_TAINTED_CORE, count); + + if (canStore == EQUIP_ERR_OK) + { + Item* created = receiverPlayer->StoreNewItem(dest, ITEM_TAINTED_CORE, true, Item::GenerateItemRandomPropertyId(ITEM_TAINTED_CORE)); + if (created) + { + time_t now = std::time(nullptr); + lastImbueAttempt[giverGuid] = now; + intendedLineup.erase(receiverGuid); + intendedLineup.erase(giverGuid); + } + } + else + { + intendedLineup.erase(receiverGuid); + intendedLineup.erase(giverGuid); + } + }, delayMs); +} + +bool LadyVashjPassTheTaintedCoreAction::UseCoreOnNearestGenerator() +{ + std::vector generators = GetAllGeneratorInfosByDbGuids(bot->GetMap(), SHIELD_GENERATOR_DB_GUIDS); + const GeneratorInfo* nearestGen = GetNearestGeneratorToBot(bot, generators); + if (!nearestGen) + return false; + + GameObject* generator = botAI->GetGameObject(nearestGen->guid); + if (!generator) + return false; + + float dist = bot->GetExactDist2d(generator); + if (dist > 3.0f) + return false; + + if (Item* core = bot->GetItemByEntry(ITEM_TAINTED_CORE)) + { + const uint8 bagIndex = core->GetBagSlot(); + const uint8 slot = core->GetSlot(); + const uint8 cast_count = 0; + uint32 spellId = 0; + + for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) + { + if (core->GetTemplate()->Spells[i].SpellId > 0) + { + spellId = core->GetTemplate()->Spells[i].SpellId; + break; + } + } + + const ObjectGuid item_guid = core->GetGUID(); + const uint32 glyphIndex = 0; + const uint8 castFlags = 0; + + WorldPacket packet(CMSG_USE_ITEM); + packet << bagIndex; + packet << slot; + packet << cast_count; + packet << spellId; + packet << item_guid; + packet << glyphIndex; + packet << castFlags; + packet << (uint32)TARGET_FLAG_GAMEOBJECT; + packet << generator->GetGUID().WriteAsPacked(); + + bot->GetSession()->HandleUseItemOpcode(packet); + return true; + } + + return false; +} + +// For dead bots to destroy their cores so the logic can reset for the next attempt +bool LadyVashjDestroyTaintedCoreAction::Execute(Event event) +{ + if (Item* core = bot->GetItemByEntry(ITEM_TAINTED_CORE)) + { + bot->DestroyItem(core->GetBagSlot(), core->GetSlot(), true); + return true; + } + + return false; +} + +// Custom Toxic Spore avoidance action +// The standard "avoid aoe" strategy does work, but I find it doesn't provide enough +// buffer distance for the toxic pools, and the standard strategy has a tendency to +// take bots down the stairs and get them stuck or out of LoS +bool LadyVashjAvoidToxicSporesAction::Execute(Event event) +{ + std::vector spores = GetAllSporeDropTriggers(botAI, bot); + if (spores.empty()) + return false; + + const float hazardRadius = 7.0f; + bool inDanger = false; + for (Unit* spore : spores) + { + if (bot->GetExactDist2d(spore) < hazardRadius) + { + inDanger = true; + break; + } + } + + if (!inDanger) + return false; + + const Position& vashjCenter = VashjPlatformCenterPosition; + const float maxRadius = 65.0f; + + Position safestPos = FindSafestNearbyPosition(spores, vashjCenter, maxRadius, hazardRadius); + + return MoveTo(bot->GetMapId(), safestPos.GetPositionX(), safestPos.GetPositionY(), + safestPos.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, false); +} + +Position LadyVashjAvoidToxicSporesAction::FindSafestNearbyPosition(const std::vector& spores, + const Position& vashjCenter, float maxRadius, float hazardRadius) +{ + const float searchStep = M_PI / 8.0f; + const float minDistance = 2.0f; + const float maxDistance = 30.0f; + const float distanceStep = 1.0f; + + Position bestPos; + float minMoveDistance = 1000.0f; + bool foundSafe = false; + + for (float distance = minDistance; distance <= maxDistance; distance += distanceStep) + { + for (float angle = 0.0f; angle < 2 * M_PI; angle += searchStep) + { + float x = bot->GetPositionX() + distance * std::cos(angle); + float y = bot->GetPositionY() + distance * std::sin(angle); + float z = bot->GetPositionZ(); + + if (vashjCenter.GetExactDist2d(x, y) > maxRadius) + continue; + + Position testPos(x, y, z); + + bool isSafe = true; + for (Unit* spore : spores) + { + if (spore->GetExactDist2d(x, y) < hazardRadius) + { + isSafe = false; + break; + } + } + + if (!isSafe) + continue; + + bool pathSafe = IsPathSafeFromSpores(bot->GetPosition(), testPos, spores, hazardRadius); + if (pathSafe || !foundSafe) + { + float moveDistance = bot->GetExactDist2d(x, y); + + if (pathSafe && (!foundSafe || moveDistance < minMoveDistance)) + { + bestPos = testPos; + minMoveDistance = moveDistance; + foundSafe = true; + } + else if (!foundSafe && moveDistance < minMoveDistance) + { + bestPos = testPos; + minMoveDistance = moveDistance; + } + } + } + + if (foundSafe) + break; + } + + return bestPos; +} + +bool LadyVashjAvoidToxicSporesAction::IsPathSafeFromSpores(const Position& start, + const Position& end, const std::vector& spores, float hazardRadius) +{ + const int numChecks = 10; + float dx = end.GetPositionX() - start.GetPositionX(); + float dy = end.GetPositionY() - start.GetPositionY(); + + for (int i = 1; i <= numChecks; ++i) + { + float ratio = static_cast(i) / numChecks; + float checkX = start.GetPositionX() + dx * ratio; + float checkY = start.GetPositionY() + dy * ratio; + + for (Unit* spore : spores) + { + float distToSpore = spore->GetExactDist2d(checkX, checkY); + if (distToSpore < hazardRadius) + return false; + } + } + + return true; +} + +// When Toxic Sporebats spit poison, they summon "Spore Drop Trigger" NPCs that create the toxic pools +std::vector LadyVashjAvoidToxicSporesAction::GetAllSporeDropTriggers(PlayerbotAI* botAI, Player* bot) +{ + std::vector sporeDropTriggers; + const GuidVector npcs = botAI->GetAiObjectContext()->GetValue("nearest npcs")->Get(); + for (auto const& npcGuid : npcs) + { + const float maxSearchRadius = 40.0f; + Unit* unit = botAI->GetUnit(npcGuid); + if (unit && unit->GetEntry() == NPC_SPORE_DROP_TRIGGER && bot->GetExactDist2d(unit) < maxSearchRadius) + sporeDropTriggers.push_back(unit); + } + + return sporeDropTriggers; +} + +bool LadyVashjUseFreeActionAbilitiesAction::Execute(Event event) +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + std::vector spores = LadyVashjAvoidToxicSporesAction::GetAllSporeDropTriggers(botAI, bot); + const float toxicSporeRadius = 6.0f; + + // If Rogues are Entangled and either have Static Charge or are near a spore, use Cloak of Shadows + if (bot->getClass() == CLASS_ROGUE && bot->HasAura(SPELL_ENTANGLE)) + { + bool nearSpore = false; + for (Unit* spore : spores) + { + if (bot->GetExactDist2d(spore) < toxicSporeRadius) + { + nearSpore = true; + break; + } + } + if ((bot->HasAura(SPELL_STATIC_CHARGE) || nearSpore) && + botAI->CanCastSpell("cloak of shadows", bot)) + return botAI->CastSpell("cloak of shadows", bot); + } + + // The remainder of the logic is for Paladins to use Hand of Freedom + Player* mainTankToxic = nullptr; + Player* anyToxic = nullptr; + Player* mainTankStatic = nullptr; + Player* anyStatic = nullptr; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive()) + continue; + + if (!member->HasAura(SPELL_ENTANGLE)) + continue; + + if (!botAI->IsMelee(member)) + continue; + + bool nearToxicSpore = false; + for (Unit* spore : spores) + { + if (member->GetExactDist2d(spore) < toxicSporeRadius) + { + nearToxicSpore = true; + break; + } + } + + if (nearToxicSpore) + { + if (botAI->IsMainTank(member)) + mainTankToxic = member; + if (!anyToxic) + anyToxic = member; + } + + if (member->HasAura(SPELL_STATIC_CHARGE)) + { + if (botAI->IsMainTank(member)) + mainTankStatic = member; + if (!anyStatic) + anyStatic = member; + } + } + + if (bot->getClass() == CLASS_PALADIN) + { + // Priority 1: Entangled in Toxic Spores (prefer main tank) + Player* toxicTarget = mainTankToxic ? mainTankToxic : anyToxic; + if (toxicTarget) + { + if (botAI->CanCastSpell("hand of freedom", toxicTarget)) + return botAI->CastSpell("hand of freedom", toxicTarget); + } + + // Priority 2: Entangled with Static Charge (prefer main tank) + Player* staticTarget = mainTankStatic ? mainTankStatic : anyStatic; + if (staticTarget) + { + if (botAI->CanCastSpell("hand of freedom", staticTarget)) + return botAI->CastSpell("hand of freedom", staticTarget); + } + } + + return false; +} + +bool LadyVashjManageTrackersAction::Execute(Event event) +{ + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + if (!vashj) + return false; + + vashjRangedPositions.clear(); + vashjHasReachedRangedPosition.clear(); + lastImbueAttempt.clear(); + intendedLineup.clear(); + + return false; +} diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h new file mode 100644 index 0000000000..c64d094fc6 --- /dev/null +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h @@ -0,0 +1,409 @@ +#ifndef _PLAYERBOT_RAIDSSCACTIONS_H +#define _PLAYERBOT_RAIDSSCACTIONS_H + +#include "Action.h" +#include "AttackAction.h" +#include "MovementActions.h" + +class UnderbogColossusEscapeToxicPoolAction : public MovementAction +{ +public: + UnderbogColossusEscapeToxicPoolAction(PlayerbotAI* botAI, std::string const name = "underbog colossus escape toxic pool") : MovementAction(botAI, name) {} + + bool Execute(Event event) override; +}; + +class GreyheartTidecallerMarkWaterElementalTotemAction : public Action +{ +public: + GreyheartTidecallerMarkWaterElementalTotemAction(PlayerbotAI* botAI, std::string const name = "greyheart tidecaller mark water elemental totem") : Action(botAI, name) {} + + bool Execute(Event event) override; +}; + +class RancidMushroomMoveAwayFromMushroomSporeCloudAction : public MovementAction +{ +public: + RancidMushroomMoveAwayFromMushroomSporeCloudAction(PlayerbotAI* botAI, std::string const name = "rancid mushroom move away from mushroom spore cloud") : MovementAction(botAI, name) {} + + bool Execute(Event event) override; +}; + +class HydrossTheUnstablePositionFrostTankAction : public AttackAction +{ +public: + HydrossTheUnstablePositionFrostTankAction(PlayerbotAI* botAI, std::string const name = "hydross the unstable position frost tank") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; +}; + +class HydrossTheUnstablePositionNatureTankAction : public AttackAction +{ +public: + HydrossTheUnstablePositionNatureTankAction(PlayerbotAI* botAI, std::string const name = "hydross the unstable position nature tank") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; +}; + +class HydrossTheUnstablePrioritizeElementalAddsAction : public AttackAction +{ +public: + HydrossTheUnstablePrioritizeElementalAddsAction(PlayerbotAI* botAI, std::string const name = "hydross the unstable prioritize elemental adds") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; +}; + +class HydrossTheUnstableFrostPhaseSpreadOutAction : public MovementAction +{ +public: + HydrossTheUnstableFrostPhaseSpreadOutAction(PlayerbotAI* botAI, std::string const name = "hydross the unstable frost phase spread out") : MovementAction(botAI, name) {} + + bool Execute(Event event) override; +}; + +class HydrossTheUnstableMisdirectBossToTankAction : public Action +{ +public: + HydrossTheUnstableMisdirectBossToTankAction(PlayerbotAI* botAI, std::string const name = "hydross the unstable misdirect boss to tank") : Action(botAI, name) {} + + bool Execute(Event event) override; + +private: + bool TryMisdirectToFrostTank(Unit* hydross, Group* group); + bool TryMisdirectToNatureTank(Unit* hydross, Group* group); +}; + +class HydrossTheUnstableStopDpsUponPhaseChangeAction : public Action +{ +public: + HydrossTheUnstableStopDpsUponPhaseChangeAction(PlayerbotAI* botAI, std::string const name = "hydross the unstable stop dps upon phase change") : Action(botAI, name) {} + + bool Execute(Event event) override; +}; + +class HydrossTheUnstableManageTimersAction : public Action +{ +public: + HydrossTheUnstableManageTimersAction(PlayerbotAI* botAI, std::string const name = "hydross the unstable manage timers") : Action(botAI, name) {} + + bool Execute(Event event) override; +}; + +class TheLurkerBelowRunAroundBehindBossAction : public MovementAction +{ +public: + TheLurkerBelowRunAroundBehindBossAction(PlayerbotAI* botAI, std::string const name = "the lurker below run around behind boss") : MovementAction(botAI, name) {} + + bool Execute(Event event) override; +}; + +class TheLurkerBelowPositionMainTankAction : public AttackAction +{ +public: + TheLurkerBelowPositionMainTankAction(PlayerbotAI* botAI, std::string const name = "the lurker below position main tank") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; +}; + +class TheLurkerBelowSpreadRangedAction : public MovementAction +{ +public: + TheLurkerBelowSpreadRangedAction(PlayerbotAI* botAI, std::string const name = "the lurker below spread ranged") : MovementAction(botAI, name) {} + + bool Execute(Event event) override; +}; + +class TheLurkerBelowManageSpoutTimerAction : public Action +{ +public: + TheLurkerBelowManageSpoutTimerAction(PlayerbotAI* botAI, std::string const name = "the lurker below manage spout timer") : Action(botAI, name) {} + + bool Execute(Event event) override; +}; + +class LeotherasTheBlindTargetSpellbindersAction : public Action +{ +public: + LeotherasTheBlindTargetSpellbindersAction(PlayerbotAI* botAI, std::string const name = "leotheras the blind target spellbinders") : Action(botAI, name) {} + + bool Execute(Event event) override; +}; + +class LeotherasTheBlindPositionRangedAction : public MovementAction +{ +public: + LeotherasTheBlindPositionRangedAction(PlayerbotAI* botAI, std::string const name = "leotheras the blind position ranged") : MovementAction(botAI, name) {} + + bool Execute(Event event) override; +}; + +class LeotherasTheBlindDemonFormTankAttackBossAction : public AttackAction +{ +public: + LeotherasTheBlindDemonFormTankAttackBossAction(PlayerbotAI* botAI, std::string const name = "leotheras the blind demon form tank attack boss") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; +}; + +class LeotherasTheBlindRunAwayFromWhirlwindAction : public MovementAction +{ +public: + LeotherasTheBlindRunAwayFromWhirlwindAction(PlayerbotAI* botAI, std::string const name = "leotheras the blind run away from whirlwind") : MovementAction(botAI, name) {} + + bool Execute(Event event) override; +}; + +class LeotherasTheBlindDemonFormPositionMeleeAction : public MovementAction +{ +public: + LeotherasTheBlindDemonFormPositionMeleeAction(PlayerbotAI* botAI, std::string const name = "leotheras the blind demon form position melee") : MovementAction(botAI, name) {} + + bool Execute(Event event) override; +}; + +class LeotherasTheBlindInnerDemonCheatAction : public AttackAction +{ +public: + LeotherasTheBlindInnerDemonCheatAction(PlayerbotAI* botAI, std::string const name = "leotheras the blind inner demon cheat") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; +}; + +class LeotherasTheBlindFinalPhaseAssignDpsPriorityAction : public AttackAction +{ +public: + LeotherasTheBlindFinalPhaseAssignDpsPriorityAction(PlayerbotAI* botAI, std::string const name = "leotheras the blind final phase assign dps priority") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; +}; + +class LeotherasTheBlindMisdirectBossToDemonFormTankAction : public AttackAction +{ +public: + LeotherasTheBlindMisdirectBossToDemonFormTankAction(PlayerbotAI* botAI, std::string const name = "leotheras the blind misdirect boss to demon form tank") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; +}; + +class LeotherasTheBlindManageTimersAndTrackersAction : public Action +{ +public: + LeotherasTheBlindManageTimersAndTrackersAction(PlayerbotAI* botAI, std::string const name = "leotheras the blind manage timers and trackers") : Action(botAI, name) {} + + bool Execute(Event event) override; +}; + +class FathomLordKarathressMainTankPositionBossAction : public AttackAction +{ +public: + FathomLordKarathressMainTankPositionBossAction(PlayerbotAI* botAI, std::string const name = "fathom-lord karathress main tank position boss") : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class FathomLordKarathressFirstAssistTankPositionSharkkisAction : public AttackAction +{ +public: + FathomLordKarathressFirstAssistTankPositionSharkkisAction(PlayerbotAI* botAI, std::string const name = "fathom-lord karathress first assist tank position sharkkis") : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class FathomLordKarathressSecondAssistTankPositionTidalvessAction : public AttackAction +{ +public: + FathomLordKarathressSecondAssistTankPositionTidalvessAction(PlayerbotAI* botAI, std::string const name = "fathom-lord karathress second assist tank position tidalvess") : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class FathomLordKarathressThirdAssistTankPositionCaribdisAction : public AttackAction +{ +public: + FathomLordKarathressThirdAssistTankPositionCaribdisAction(PlayerbotAI* botAI, std::string const name = "fathom-lord karathress third assist tank position caribdis") : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class FathomLordKarathressPositionCaribdisTankHealerAction : public MovementAction +{ +public: + FathomLordKarathressPositionCaribdisTankHealerAction(PlayerbotAI* botAI, std::string const name = "fathom-lord karathress position caribdis tank healer") : MovementAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class FathomLordKarathressMisdirectBossesToTanksAction : public AttackAction +{ +public: + FathomLordKarathressMisdirectBossesToTanksAction(PlayerbotAI* botAI, std::string const name = "fathom-lord karathress misdirect bosses to tanks") : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class FathomLordKarathressAssignDpsPriorityAction : public AttackAction +{ +public: + FathomLordKarathressAssignDpsPriorityAction(PlayerbotAI* botAI, std::string const name = "fathom-lord karathress assign dps priority") : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class FathomLordKarathressManageDpsTimerAction : public Action +{ +public: + FathomLordKarathressManageDpsTimerAction(PlayerbotAI* botAI, std::string const name = "fathom-lord karathress manage dps timer") : Action(botAI, name) {} + bool Execute(Event event) override; +}; + +class MorogrimTidewalkerMisdirectBossToMainTankAction : public AttackAction +{ +public: + MorogrimTidewalkerMisdirectBossToMainTankAction(PlayerbotAI* botAI, std::string const name = "morogrim tidewalker misdirect boss to main tank") : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class MorogrimTidewalkerMoveBossToTankPositionAction : public AttackAction +{ +public: + MorogrimTidewalkerMoveBossToTankPositionAction(PlayerbotAI* botAI, std::string const name = "morogrim tidewalker move boss to tank position") : AttackAction(botAI, name) {} + bool Execute(Event event) override; + +private: + bool MoveToPhase1TankPosition(Unit* tidewalker); + bool MoveToPhase2TankPosition(Unit* tidewalker); +}; + +class MorogrimTidewalkerPhase2RepositionRangedAction : public MovementAction +{ +public: + MorogrimTidewalkerPhase2RepositionRangedAction(PlayerbotAI* botAI, std::string const name = "morogrim tidewalker phase 2 reposition ranged") : MovementAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class MorogrimTidewalkerResetPhaseTransitionStepsAction : public Action +{ +public: + MorogrimTidewalkerResetPhaseTransitionStepsAction(PlayerbotAI* botAI, std::string const name = "morogrim tidewalker reset phase transition steps") : Action(botAI, name) {} + bool Execute(Event event) override; +}; + +class LadyVashjMainTankPositionBossAction : public AttackAction +{ +public: + LadyVashjMainTankPositionBossAction(PlayerbotAI* botAI, std::string const name = "lady vashj main tank position boss") : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class LadyVashjPhase1PositionRangedAction : public MovementAction +{ +public: + LadyVashjPhase1PositionRangedAction(PlayerbotAI* botAI, std::string const name = "lady vashj phase 1 position ranged") : MovementAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class LadyVashjSetGroundingTotemInMainTankGroupAction : public MovementAction +{ +public: + LadyVashjSetGroundingTotemInMainTankGroupAction(PlayerbotAI* botAI, std::string const name = "lady vashj set grounding totem in main tank group") : MovementAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class LadyVashjStaticChargeMoveAwayFromGroupAction : public MovementAction +{ +public: + LadyVashjStaticChargeMoveAwayFromGroupAction(PlayerbotAI* botAI, std::string const name = "lady vashj static charge move away from group") : MovementAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class LadyVashjMisdirectBossToMainTankAction : public AttackAction +{ +public: + LadyVashjMisdirectBossToMainTankAction(PlayerbotAI* botAI, std::string const name = "lady vashj misdirect boss to main tank") : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class LadyVashjMisdirectStriderToFirstAssistTankAction : public AttackAction +{ +public: + LadyVashjMisdirectStriderToFirstAssistTankAction(PlayerbotAI* botAI, std::string const name = "lady vashj misdirect strider to first assist tank") : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class LadyVashjTankAttackAndMoveAwayStriderAction : public AttackAction +{ +public: + LadyVashjTankAttackAndMoveAwayStriderAction(PlayerbotAI* botAI, std::string const name = "lady vashj tank attack and move away strider") : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class LadyVashjAssignDpsPriorityAction : public AttackAction +{ +public: + LadyVashjAssignDpsPriorityAction(PlayerbotAI* botAI, std::string const name = "lady vashj assign dps priority") : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class LadyVashjTeleportToTaintedElementalAction : public AttackAction +{ +public: + LadyVashjTeleportToTaintedElementalAction(PlayerbotAI* botAI, std::string const name = "lady vashj teleport to tainted elemental") : AttackAction(botAI, name) {} + bool Execute(Event event) override; + +private: + ObjectGuid lastTaintedGuid; +}; + +class LadyVashjLootTaintedCoreAction : public MovementAction +{ +public: + LadyVashjLootTaintedCoreAction(PlayerbotAI* botAI, std::string const name = "lady vashj loot tainted core") : MovementAction(botAI, name) {} + bool Execute(Event event) override; +}; + +class LadyVashjPassTheTaintedCoreAction : public MovementAction +{ +public: + LadyVashjPassTheTaintedCoreAction(PlayerbotAI* botAI, std::string const name = "lady vashj pass the tainted core") : MovementAction(botAI, name) {} + bool Execute(Event event) override; + +private: + bool LineUpFirstCorePasser(Player* designatedLooter, Unit* closestTrigger); + bool LineUpSecondCorePasser(Player* firstCorePasser, Unit* closestTrigger); + bool LineUpThirdCorePasser(Player* secondCorePasser, Unit* closestTrigger); + bool LineUpFourthCorePasser(Player* thirdCorePasser, Unit* closestTrigger); + bool IsFirstCorePasserInIntendedPosition(Player* designatedLooter, Player* firstCorePasser, Unit* closestTrigger); + bool IsSecondCorePasserInIntendedPosition(Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger); + bool IsThirdCorePasserInIntendedPosition(Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger); + bool IsFourthCorePasserInIntendedPosition(Player* thirdCorePasser, Player* fourthCorePasser, Unit* closestTrigger); + void ScheduleStoreCoreAfterImbue(PlayerbotAI* botAI, Player* giver, Player* receiver); + bool UseCoreOnNearestGenerator(); +}; + +class LadyVashjDestroyTaintedCoreAction : public Action +{ +public: + LadyVashjDestroyTaintedCoreAction(PlayerbotAI* botAI, std::string const name = "lady vashj destroy tainted core") : Action(botAI, name) {} + bool Execute(Event event) override; +}; + +class LadyVashjAvoidToxicSporesAction : public MovementAction +{ +public: + LadyVashjAvoidToxicSporesAction(PlayerbotAI* botAI, std::string const name = "lady vashj avoid toxic spores") : MovementAction(botAI, name) {} + bool Execute(Event event) override; + static std::vector GetAllSporeDropTriggers(PlayerbotAI* botAI, Player* bot); + +private: + Position FindSafestNearbyPosition(const std::vector& spores, const Position& position, float maxRadius, float hazardRadius); + bool IsPathSafeFromSpores(const Position& start, const Position& end, const std::vector& spores, float hazardRadius); +}; + +class LadyVashjUseFreeActionAbilitiesAction : public Action +{ +public: + LadyVashjUseFreeActionAbilitiesAction(PlayerbotAI* botAI, std::string const name = "lady vashj use free action abilities") : Action(botAI, name) {} + bool Execute(Event event) override; +}; + +class LadyVashjManageTrackersAction : public Action +{ +public: + LadyVashjManageTrackersAction(PlayerbotAI* botAI, std::string const name = "lady vashj manage trackers") : Action(botAI, name) {} + bool Execute(Event event) override; +}; + +#endif diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp new file mode 100644 index 0000000000..1e2cdc8518 --- /dev/null +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp @@ -0,0 +1,623 @@ +#include "RaidSSCHelpers.h" +#include "AiFactory.h" +#include "Creature.h" +#include "Group.h" +#include "ObjectAccessor.h" +#include "Playerbots.h" +#include "RtiTargetValue.h" + +namespace SerpentShrineCavernHelpers +{ + std::unordered_map hydrossFrostDpsWaitTimer; + std::unordered_map hydrossNatureDpsWaitTimer; + std::unordered_map hydrossChangeToFrostPhaseTimer; + std::unordered_map hydrossChangeToNaturePhaseTimer; + + std::unordered_map lurkerSpoutTimer; + std::unordered_map lurkerRangedPositions; + + std::unordered_map leotherasHumanFormDpsWaitTimer; + std::unordered_map leotherasDemonFormDpsWaitTimer; + std::unordered_map leotherasFinalPhaseDpsWaitTimer; + + std::unordered_map karathressDpsWaitTimer; + + std::unordered_map tidewalkerTankStep; + std::unordered_map tidewalkerRangedStep; + + std::unordered_map vashjRangedPositions; + std::unordered_map vashjHasReachedRangedPosition; + std::unordered_map intendedLineup; + std::unordered_map lastImbueAttempt; + std::unordered_map lastParalyzeTime; + + namespace SerpentShrineCavernPositions + { + const Position HydrossFrostTankPosition = { -236.669f, -358.352f, -0.828f }; + const Position HydrossNatureTankPosition = { -225.471f, -327.790f, -3.682f }; + + const Position LurkerMainTankPosition = { 23.706f, -406.038f, -19.686f }; + + const Position KarathressTankPosition = { 474.403f, -531.118f, -7.548f }; + const Position TidalvessTankPosition = { 511.282f, -501.162f, -13.158f }; + const Position SharkkisTankPosition = { 508.057f, -541.109f, -10.133f }; + const Position CaribdisTankPosition = { 464.462f, -475.820f, -13.158f }; + const Position CaribdisHealerPosition = { 466.203f, -503.201f, -13.158f }; + const Position CaribdisRangedDpsPosition = { 463.197f, -501.190f, -13.158f }; + + const Position TidewalkerPhase1TankPosition = { 410.925f, -741.916f, -7.146f }; + const Position TidewalkerPhaseTransitionWaypoint = { 407.035f, -759.479f, -7.168f }; + const Position TidewalkerPhase2TankPosition = { 446.571f, -767.155f, -7.144f }; + const Position TidewalkerPhase2RangedPosition = { 432.595f, -766.288f, -7.145f }; + + const Position VashjPlatformCenterPosition = { 29.634f, -923.541f, 42.985f }; + } + + void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId) + { + if (!target) + return; + + if (Group* group = bot->GetGroup()) + { + ObjectGuid currentGuid = group->GetTargetIcon(iconId); + if (currentGuid != target->GetGUID()) + group->SetTargetIcon(iconId, bot->GetGUID(), target->GetGUID()); + } + } + + void MarkTargetWithSkull(Player* bot, Unit* target) + { + MarkTargetWithIcon(bot, target, RtiTargetValue::skullIndex); + } + + void MarkTargetWithSquare(Player* bot, Unit* target) + { + MarkTargetWithIcon(bot, target, RtiTargetValue::squareIndex); + } + + void MarkTargetWithStar(Player* bot, Unit* target) + { + MarkTargetWithIcon(bot, target, RtiTargetValue::starIndex); + } + + void MarkTargetWithCircle(Player* bot, Unit* target) + { + MarkTargetWithIcon(bot, target, RtiTargetValue::circleIndex); + } + + void MarkTargetWithDiamond(Player* bot, Unit* target) + { + MarkTargetWithIcon(bot, target, RtiTargetValue::diamondIndex); + } + + void MarkTargetWithTriangle(Player* bot, Unit* target) + { + MarkTargetWithIcon(bot, target, RtiTargetValue::triangleIndex); + } + + void MarkTargetWithCross(Player* bot, Unit* target) + { + MarkTargetWithIcon(bot, target, RtiTargetValue::crossIndex); + } + + void MarkTargetWithMoon(Player* bot, Unit* target) + { + MarkTargetWithIcon(bot, target, RtiTargetValue::moonIndex); + } + + void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target) + { + if (!target) + return; + + std::string currentRti = botAI->GetAiObjectContext()->GetValue("rti")->Get(); + Unit* currentTarget = botAI->GetAiObjectContext()->GetValue("rti target")->Get(); + + if (currentRti != rtiName || currentTarget != target) + { + botAI->GetAiObjectContext()->GetValue("rti")->Set(rtiName); + botAI->GetAiObjectContext()->GetValue("rti target")->Set(target); + } + } + + // Dps bot selected for marking and managing timers and trackers + bool IsMapIDTimerManager(PlayerbotAI* botAI, Player* bot) + { + if (Group* group = bot->GetGroup()) + { + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->IsAlive() && botAI->IsDps(member) && GET_PLAYERBOT_AI(member)) + return member == bot; + } + } + + return false; + } + + Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry) + { + const GuidVector npcs = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); + for (auto const& npcGuid : npcs) + { + Unit* unit = botAI->GetUnit(npcGuid); + if (unit && unit->IsAlive() && unit->GetEntry() == entry) + return unit; + } + + return nullptr; + } + + bool HasMarkOfHydrossAt100Percent(Player* bot) + { + return bot->HasAura(SPELL_MARK_OF_HYDROSS_100) || + bot->HasAura(SPELL_MARK_OF_HYDROSS_250) || + bot->HasAura(SPELL_MARK_OF_HYDROSS_500); + } + + bool HasNoMarkOfHydross(Player* bot) + { + return !bot->HasAura(SPELL_MARK_OF_HYDROSS_10) && + !bot->HasAura(SPELL_MARK_OF_HYDROSS_25) && + !bot->HasAura(SPELL_MARK_OF_HYDROSS_50) && + !bot->HasAura(SPELL_MARK_OF_HYDROSS_100) && + !bot->HasAura(SPELL_MARK_OF_HYDROSS_250) && + !bot->HasAura(SPELL_MARK_OF_HYDROSS_500); + } + + bool HasMarkOfCorruptionAt100Percent(Player* bot) + { + return bot->HasAura(SPELL_MARK_OF_CORRUPTION_100) || + bot->HasAura(SPELL_MARK_OF_CORRUPTION_250) || + bot->HasAura(SPELL_MARK_OF_CORRUPTION_500); + } + + bool HasNoMarkOfCorruption(Player* bot) + { + return + !bot->HasAura(SPELL_MARK_OF_CORRUPTION_10) && + !bot->HasAura(SPELL_MARK_OF_CORRUPTION_25) && + !bot->HasAura(SPELL_MARK_OF_CORRUPTION_50) && + !bot->HasAura(SPELL_MARK_OF_CORRUPTION_100) && + !bot->HasAura(SPELL_MARK_OF_CORRUPTION_250) && + !bot->HasAura(SPELL_MARK_OF_CORRUPTION_500); + } + + bool IsLurkerCastingSpout(Unit* lurker) + { + if (!lurker || !lurker->HasUnitState(UNIT_STATE_CASTING)) + return false; + + Spell* currentSpell = lurker->GetCurrentSpell(CURRENT_GENERIC_SPELL); + if (!currentSpell) + return false; + + uint32 spellId = currentSpell->m_spellInfo->Id; + bool isSpout = spellId == SPELL_SPOUT_VISUAL; + + return isSpout; + } + + Unit* GetLeotherasHuman(PlayerbotAI* botAI) + { + const GuidVector npcs = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); + for (auto const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->IsAlive() && unit->GetEntry() == NPC_LEOTHERAS_THE_BLIND && + unit->IsInCombat() && !unit->HasAura(SPELL_METAMORPHOSIS)) + return unit; + } + return nullptr; + } + + Unit* GetPhase2LeotherasDemon(PlayerbotAI* botAI) + { + const GuidVector npcs = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); + for (auto const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->IsAlive() && unit->GetEntry() == NPC_LEOTHERAS_THE_BLIND && + unit->HasAura(SPELL_METAMORPHOSIS)) + return unit; + } + return nullptr; + } + + Unit* GetPhase3LeotherasDemon(PlayerbotAI* botAI) + { + const GuidVector npcs = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); + for (auto const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->IsAlive() && unit->GetEntry() == NPC_SHADOW_OF_LEOTHERAS) + return unit; + } + return nullptr; + } + + Unit* GetActiveLeotherasDemon(PlayerbotAI* botAI) + { + Unit* phase2 = GetPhase2LeotherasDemon(botAI); + Unit* phase3 = GetPhase3LeotherasDemon(botAI); + return phase2 ? phase2 : phase3; + } + + Player* GetLeotherasDemonFormTank(PlayerbotAI* botAI, Player* bot) + { + Group* group = bot->GetGroup(); + if (!group) + return nullptr; + + Player* mainTankCandidate = nullptr; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member)) + continue; + + if (member->getClass() == CLASS_WARLOCK && GET_PLAYERBOT_AI(member)->HasStrategy("tank", BotState::BOT_STATE_COMBAT)) + return member; + + if (!mainTankCandidate && GET_PLAYERBOT_AI(member)->IsMainTank(member)) + mainTankCandidate = member; + } + + return mainTankCandidate; + } + + bool IsMainTankInSameSubgroup(Player* bot) + { + Group* group = bot->GetGroup(); + if (!group || !group->isRaidGroup()) + return false; + + uint8 botSubGroup = group->GetMemberGroup(bot->GetGUID()); + if (botSubGroup >= MAX_RAID_SUBGROUPS) + return false; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || member == bot || !member->IsAlive()) + continue; + + if (group->GetMemberGroup(member->GetGUID()) != botSubGroup) + continue; + + if (PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member)) + { + if (memberAI->IsMainTank(member)) + return true; + } + } + + return false; + } + + bool IsLadyVashjInPhase1(PlayerbotAI* botAI) + { + Unit* vashj = botAI->GetAiObjectContext()->GetValue("find target", "lady vashj")->Get(); + if (!vashj) + return false; + + Creature* vashjCreature = vashj->ToCreature(); + return vashjCreature && vashjCreature->GetHealthPct() > 70.0f && vashjCreature->GetReactState() != REACT_PASSIVE; + } + + bool IsLadyVashjInPhase2(PlayerbotAI* botAI) + { + Unit* vashj = botAI->GetAiObjectContext()->GetValue("find target", "lady vashj")->Get(); + if (!vashj) + return false; + + Creature* vashjCreature = vashj->ToCreature(); + return vashjCreature && vashjCreature->GetReactState() == REACT_PASSIVE; + } + + bool IsLadyVashjInPhase3(PlayerbotAI* botAI) + { + Unit* vashj = botAI->GetAiObjectContext()->GetValue("find target", "lady vashj")->Get(); + if (!vashj) + return false; + + Creature* vashjCreature = vashj->ToCreature(); + return vashjCreature && vashjCreature->GetHealthPct() <= 50.0f && vashjCreature->GetReactState() != REACT_PASSIVE; + } + + bool IsValidPhase2CombatNpc(Unit* unit, PlayerbotAI* botAI) + { + if (!unit || !unit->IsAlive()) + return false; + + uint32 entry = unit->GetEntry(); + + if (IsLadyVashjInPhase2(botAI)) + { + return entry == NPC_TAINTED_ELEMENTAL || entry == NPC_ENCHANTED_ELEMENTAL || + entry == NPC_COILFANG_ELITE || entry == NPC_COILFANG_STRIDER; + } + else if (IsLadyVashjInPhase3(botAI)) + { + return entry == NPC_TAINTED_ELEMENTAL || entry == NPC_ENCHANTED_ELEMENTAL || + entry == NPC_COILFANG_ELITE || entry == NPC_COILFANG_STRIDER || + entry == NPC_TOXIC_SPOREBAT || entry == NPC_LADY_VASHJ; + } + + return false; + } + + bool AnyRecentParalyze(Group* group, uint32 mapId, uint32 graceSeconds) + { + const time_t now = std::time(nullptr); + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member) + continue; + + if (member->IsAlive() && member->HasAura(SPELL_PARALYZE)) + { + lastParalyzeTime[mapId] = now; + return true; + } + } + + auto it = lastParalyzeTime.find(mapId); + if (it != lastParalyzeTime.end()) + { + if ((now - it->second) <= static_cast(graceSeconds)) + return true; + } + + return false; + } + + Player* GetDesignatedCoreLooter(Group* group, Player* master, PlayerbotAI* botAI) + { + if (!botAI->HasCheat(BotCheatMask::raid)) + return master; + + Player* fallback = nullptr; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || member == master) + continue; + + PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); + if (!memberAI) + continue; + + if (memberAI->IsMelee(member) && memberAI->IsDps(member)) + return member; + + if (!fallback && memberAI->IsRangedDps(member)) + fallback = member; + } + + return fallback ? fallback : master; + } + + Player* GetFirstTaintedCorePasser(Group* group, PlayerbotAI* botAI) + { + Player* designatedLooter = GetDesignatedCoreLooter(group, botAI->GetMaster(), botAI); + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || member == designatedLooter) + continue; + + PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); + if (!memberAI) + continue; + + if (memberAI->IsHealAssistantOfIndex(member, 0)) + return member; + } + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) || + botAI->IsTank(member) || member == designatedLooter) + continue; + return member; + } + + return nullptr; + } + + Player* GetSecondTaintedCorePasser(Group* group, PlayerbotAI* botAI) + { + Player* designatedLooter = GetDesignatedCoreLooter(group, botAI->GetMaster(), botAI); + Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI); + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || member == designatedLooter || + member == firstCorePasser) + continue; + + PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); + if (!memberAI) + continue; + + if (memberAI->IsHealAssistantOfIndex(member, 1)) + return member; + } + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) || botAI->IsTank(member) || + member == designatedLooter || member == firstCorePasser) + continue; + return member; + } + + return nullptr; + } + + Player* GetThirdTaintedCorePasser(Group* group, PlayerbotAI* botAI) + { + Player* designatedLooter = GetDesignatedCoreLooter(group, botAI->GetMaster(), botAI); + Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI); + Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI); + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || member == designatedLooter || + member == firstCorePasser || member == secondCorePasser) + continue; + + PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); + if (!memberAI) + continue; + + if (memberAI->IsHealAssistantOfIndex(member, 2)) + return member; + } + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) || botAI->IsTank(member) || + member == designatedLooter || member == firstCorePasser || member == secondCorePasser) + continue; + return member; + } + + return nullptr; + } + + Player* GetFourthTaintedCorePasser(Group* group, PlayerbotAI* botAI) + { + Player* designatedLooter = GetDesignatedCoreLooter(group, botAI->GetMaster(), botAI); + Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI); + Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI); + Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI); + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || member == designatedLooter || + member == firstCorePasser || member == secondCorePasser || member == thirdCorePasser) + continue; + + PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); + if (!memberAI) + continue; + + if (memberAI->IsRangedDpsAssistantOfIndex(member, 0)) + return member; + } + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) || botAI->IsTank(member) || + member == designatedLooter || member == firstCorePasser || member == secondCorePasser || + member == thirdCorePasser) + continue; + return member; + } + + return nullptr; + } + + const std::vector SHIELD_GENERATOR_DB_GUIDS = { 47482, 47483, 47484, 47485 }; // NW, NE, SE, SW + + // Get the positions of all active Shield Generators by their database GUIDs + std::vector GetAllGeneratorInfosByDbGuids(Map* map, const std::vector& generatorDbGuids) + { + std::vector generators; + if (!map) + return generators; + + for (uint32 dbGuid : generatorDbGuids) + { + auto bounds = map->GetGameObjectBySpawnIdStore().equal_range(dbGuid); + if (bounds.first == bounds.second) + continue; + + GameObject* go = bounds.first->second; + if (!go) + continue; + + if (go->GetGoState() != GO_STATE_READY) + continue; + + GeneratorInfo info; + info.guid = go->GetGUID(); + info.x = go->GetPositionX(); + info.y = go->GetPositionY(); + info.z = go->GetPositionZ(); + generators.push_back(info); + } + + return generators; + } + + // Returns the nearest active Shield Generator to the bot + // Active generators are powered by NPC_WORLD_INVISIBLE_TRIGGER creatures, which depawn after use + Unit* GetNearestActiveShieldGeneratorTriggerByEntry(Player* bot, Unit* reference) + { + if (!bot || !reference) + return nullptr; + + Map* map = bot->GetMap(); + if (!map) + return nullptr; + + std::list triggers; + float searchRange = 150.0f; + reference->GetCreatureListWithEntryInGrid(triggers, NPC_WORLD_INVISIBLE_TRIGGER, searchRange); + + Creature* nearest = nullptr; + float minDist = std::numeric_limits::max(); + + for (Creature* creature : triggers) + { + if (!creature->IsAlive()) + continue; + + float dist = reference->GetDistance(creature); + if (dist < minDist) + { + minDist = dist; + nearest = creature; + } + } + + return nearest; + } + + const GeneratorInfo* GetNearestGeneratorToBot(Player* bot, const std::vector& generators) + { + if (!bot || generators.empty()) + return nullptr; + + const GeneratorInfo* nearest = nullptr; + float minDist = std::numeric_limits::max(); + + for (auto const& gen : generators) + { + float dist = bot->GetExactDist(gen.x, gen.y, gen.z); + if (dist < minDist) + { + minDist = dist; + nearest = &gen; + } + } + + return nearest; + } +} diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h new file mode 100644 index 0000000000..addf15ce0b --- /dev/null +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h @@ -0,0 +1,203 @@ +#ifndef _PLAYERBOT_RAIDSSCHELPERS_H_ +#define _PLAYERBOT_RAIDSSCHELPERS_H_ + +#include +#include + +#include "AiObject.h" +#include "Position.h" +#include "Unit.h" + +namespace SerpentShrineCavernHelpers +{ + enum SerpentShrineCavernSpells + { + // Trash Mob + SPELL_TOXIC_POOL = 38718, + + // Hydross the Unstable + SPELL_MARK_OF_HYDROSS_10 = 38215, + SPELL_MARK_OF_HYDROSS_25 = 38216, + SPELL_MARK_OF_HYDROSS_50 = 38217, + SPELL_MARK_OF_HYDROSS_100 = 38218, + SPELL_MARK_OF_HYDROSS_250 = 38231, + SPELL_MARK_OF_HYDROSS_500 = 40584, + SPELL_MARK_OF_CORRUPTION_10 = 38219, + SPELL_MARK_OF_CORRUPTION_25 = 38220, + SPELL_MARK_OF_CORRUPTION_50 = 38221, + SPELL_MARK_OF_CORRUPTION_100 = 38222, + SPELL_MARK_OF_CORRUPTION_250 = 38230, + SPELL_MARK_OF_CORRUPTION_500 = 40583, + SPELL_CORRUPTION = 37961, + + // The Lurker Below + SPELL_SPOUT_VISUAL = 37431, + + // Leotheras the Blind + SPELL_LEOTHERAS_BANISHED = 37546, + SPELL_WHIRLWIND = 37640, + SPELL_WHIRLWIND_CHANNEL = 37641, + SPELL_METAMORPHOSIS = 37673, + SPELL_INSIDIOUS_WHISPER = 37676, + + // Lady Vashj + SPELL_FEAR_WARD = 6346, + SPELL_PARALYZE = 38132, + SPELL_POISON_BOLT = 38253, + SPELL_STATIC_CHARGE = 38280, + SPELL_ENTANGLE = 38316, + + // Hunter + SPELL_MISDIRECTION = 35079, + + // Mage + SPELL_SLOW = 31589, + + // Shaman + SPELL_GROUNDING_TOTEM_EFFECT = 8178, + SPELL_FROST_SHOCK = 25464, + + // Warlock + SPELL_CURSE_OF_EXHAUSTION = 18223, + + // Item + SPELL_HEAVY_NETHERWEAVE_NET = 31368, + }; + + enum SerpentShrineCavernNPCs + { + // Trash Mobs + NPC_WATER_ELEMENTAL_TOTEM = 22236, + NPC_RANCID_MUSHROOM = 22250, + + // Hydross the Unstable + NPC_HYDROSS_FROST_FORM = 21216, + NPC_HYDROSS_NATURE_FORM = 21232, + NPC_PURE_SPAWN_OF_HYDROSS = 22035, + NPC_TAINTED_SPAWN_OF_HYDROSS = 22036, + + // Leotheras the Blind + NPC_LEOTHERAS_THE_BLIND = 21215, + NPC_GREYHEART_SPELLBINDER = 21806, + NPC_SHADOW_OF_LEOTHERAS = 21875, + NPC_INNER_DEMON = 21857, + + // Fathom-Lord Karathress + NPC_SPITFIRE_TOTEM = 22091, + + // Lady Vashj + NPC_WORLD_INVISIBLE_TRIGGER = 12999, + NPC_LADY_VASHJ = 21212, + NPC_ENCHANTED_ELEMENTAL = 21958, + NPC_TAINTED_ELEMENTAL = 22009, + NPC_COILFANG_ELITE = 22055, + NPC_COILFANG_STRIDER = 22056, + NPC_TOXIC_SPOREBAT = 22140, + NPC_SPORE_DROP_TRIGGER = 22207, + }; + + enum SerpentShrineCavernItems + { + // Lady Vashj + ITEM_TAINTED_CORE = 31088, + + // Tailoring + ITEM_HEAVY_NETHERWEAVE_NET = 24269, + }; + + extern std::unordered_map hydrossFrostDpsWaitTimer; + extern std::unordered_map hydrossNatureDpsWaitTimer; + extern std::unordered_map hydrossChangeToFrostPhaseTimer; + extern std::unordered_map hydrossChangeToNaturePhaseTimer; + + extern std::unordered_map lurkerSpoutTimer; + extern std::unordered_map lurkerRangedPositions; + + extern std::unordered_map leotherasHumanFormDpsWaitTimer; + extern std::unordered_map leotherasDemonFormDpsWaitTimer; + extern std::unordered_map leotherasFinalPhaseDpsWaitTimer; + + extern std::unordered_map karathressDpsWaitTimer; + + extern std::unordered_map tidewalkerTankStep; + extern std::unordered_map tidewalkerRangedStep; + + extern std::unordered_map vashjRangedPositions; + extern std::unordered_map vashjHasReachedRangedPosition; + extern std::unordered_map intendedLineup; + extern std::unordered_map lastImbueAttempt; + extern std::unordered_map lastParalyzeTime; + + namespace SerpentShrineCavernPositions + { + extern const Position HydrossFrostTankPosition; + extern const Position HydrossNatureTankPosition; + + extern const Position LurkerMainTankPosition; + + extern const Position KarathressTankPosition; + extern const Position TidalvessTankPosition; + extern const Position SharkkisTankPosition; + extern const Position CaribdisTankPosition; + extern const Position CaribdisHealerPosition; + extern const Position CaribdisRangedDpsPosition; + + extern const Position TidewalkerPhase1TankPosition; + extern const Position TidewalkerPhaseTransitionWaypoint; + extern const Position TidewalkerPhase2TankPosition; + extern const Position TidewalkerPhase2RangedPosition; + + extern const Position VashjPlatformCenterPosition; + } + + void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId); + void MarkTargetWithSkull(Player* bot, Unit* target); + void MarkTargetWithSquare(Player* bot, Unit* target); + void MarkTargetWithStar(Player* bot, Unit* target); + void MarkTargetWithCircle(Player* bot, Unit* target); + void MarkTargetWithDiamond(Player* bot, Unit* target); + void MarkTargetWithTriangle(Player* bot, Unit* target); + void MarkTargetWithCross(Player* bot, Unit* target); + void MarkTargetWithMoon(Player* bot, Unit* target); + void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target); + bool IsMapIDTimerManager(PlayerbotAI* botAI, Player* bot); + Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry); + + bool HasMarkOfHydrossAt100Percent(Player* bot); + bool HasNoMarkOfHydross(Player* bot); + bool HasMarkOfCorruptionAt100Percent(Player* bot); + bool HasNoMarkOfCorruption(Player* bot); + + bool IsLurkerCastingSpout(Unit* lurker); + + Unit* GetLeotherasHuman(PlayerbotAI* botAI); + Unit* GetPhase2LeotherasDemon(PlayerbotAI* botAI); + Unit* GetPhase3LeotherasDemon(PlayerbotAI* botAI); + Unit* GetActiveLeotherasDemon(PlayerbotAI* botAI); + Player* GetLeotherasDemonFormTank(PlayerbotAI* botAI, Player* bot); + + bool IsMainTankInSameSubgroup(Player* bot); + bool IsLadyVashjInPhase1(PlayerbotAI* botAI); + bool IsLadyVashjInPhase2(PlayerbotAI* botAI); + bool IsLadyVashjInPhase3(PlayerbotAI* botAI); + bool IsValidPhase2CombatNpc(Unit* unit, PlayerbotAI* botAI); + bool AnyRecentParalyze(Group* group, uint32 mapId, uint32 graceSeconds = 3); + Player* GetDesignatedCoreLooter(Group* group, Player* master, PlayerbotAI* botAI); + Player* GetFirstTaintedCorePasser(Group* group, PlayerbotAI* botAI); + Player* GetSecondTaintedCorePasser(Group* group, PlayerbotAI* botAI); + Player* GetThirdTaintedCorePasser(Group* group, PlayerbotAI* botAI); + Player* GetFourthTaintedCorePasser(Group* group, PlayerbotAI* botAI); + + struct GeneratorInfo + { + ObjectGuid guid; + float x, y, z; + }; + + extern const std::vector SHIELD_GENERATOR_DB_GUIDS; + std::vector GetAllGeneratorInfosByDbGuids(Map* map, const std::vector& generatorDbGuids); + Unit* GetNearestActiveShieldGeneratorTriggerByEntry(Player* bot, Unit* reference); + const GeneratorInfo* GetNearestGeneratorToBot(Player* bot, const std::vector& generators); +} + +#endif diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp new file mode 100644 index 0000000000..80fb591a9b --- /dev/null +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp @@ -0,0 +1,586 @@ +#include "RaidSSCMultipliers.h" +#include "RaidSSCActions.h" +#include "RaidSSCHelpers.h" +#include "ChooseTargetActions.h" +#include "DestroyItemAction.h" +#include "FollowActions.h" +#include "GenericSpellActions.h" +#include "HunterActions.h" +#include "LootAction.h" +#include "MageActions.h" +#include "PaladinActions.h" +#include "Playerbots.h" +#include "ReachTargetActions.h" +#include "RogueActions.h" +#include "ShamanActions.h" +#include "WarlockActions.h" +#include "WipeAction.h" + +using namespace SerpentShrineCavernHelpers; + +// Trash + +float UnderbogColossusEscapeToxicPoolMultiplier::GetValue(Action* action) +{ + Aura* aura = bot->GetAura(SPELL_TOXIC_POOL); + if (!aura) + return 1.0f; + + DynamicObject* dynObj = aura->GetDynobjOwner(); + if (!dynObj) + return 1.0f; + + if (dynamic_cast(action) && + !dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + +// Hydross the Unstable + +float HydrossTheUnstableDisableTankActionsMultiplier::GetValue(Action* action) +{ + Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross"); + if (!hydross || dynamic_cast(action)) + return 1.0f; + + if (dynamic_cast(action)) + return 0.0f; + + if (botAI->IsMainTank(bot)) + { + if (hydross->HasAura(SPELL_CORRUPTION)) + { + if (!dynamic_cast(action)) + return 0.0f; + } + } + + if (botAI->IsAssistTankOfIndex(bot, 0)) + { + if (!hydross->HasAura(SPELL_CORRUPTION)) + { + if (!dynamic_cast(action)) + return 0.0f; + } + } + + return 1.0f; +} + +float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action) +{ + Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); + if (!hydross) + return 1.0f; + + if (botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0)) + return 1.0f; + + Unit* waterElemental = AI_VALUE2(Unit*, "find target", "pure spawn of hydross"); + Unit* natureElemental = AI_VALUE2(Unit*, "find target", "tainted spawn of hydross"); + if (botAI->IsAssistTank(bot) && !botAI->IsAssistTankOfIndex(bot, 0) && + (waterElemental || natureElemental)) + return 1.0f; + + if (dynamic_cast(action)) + return 1.0f; + + const uint32 mapId = hydross->GetMapId(); + const time_t now = std::time(nullptr); + const uint8 dpsWaitSeconds = 5; + const uint8 phaseChangeWaitSeconds = 6; + + if (!hydross->HasAura(SPELL_CORRUPTION)) + { + if (botAI->IsAssistTankOfIndex(bot, 0)) + { + if (dynamic_cast(action) && + !dynamic_cast(action)) + return 0.0f; + } + else if (botAI->IsTank(bot)) + return 1.0f; + + auto itDps = hydrossFrostDpsWaitTimer.find(mapId); + auto itPhase = hydrossChangeToFrostPhaseTimer.find(mapId); + + bool justChanged = (itDps == hydrossFrostDpsWaitTimer.end() || + (now - itDps->second) < dpsWaitSeconds); + + bool aboutToChange = (itPhase != hydrossChangeToFrostPhaseTimer.end() && + (now - itPhase->second) > phaseChangeWaitSeconds); + + if (justChanged || aboutToChange) + { + if (dynamic_cast(action) || + (dynamic_cast(action) && !dynamic_cast(action))) + return 0.0f; + } + } + + if (hydross->HasAura(SPELL_CORRUPTION)) + { + if (botAI->IsMainTank(bot)) + { + if (dynamic_cast(action) && + !dynamic_cast(action)) + return 0.0f; + } + else if (botAI->IsTank(bot)) + return 1.0f; + + auto itDps = hydrossNatureDpsWaitTimer.find(mapId); + auto itPhase = hydrossChangeToNaturePhaseTimer.find(mapId); + + bool justChanged = (itDps == hydrossNatureDpsWaitTimer.end() || + (now - itDps->second) < dpsWaitSeconds); + + bool aboutToChange = (itPhase != hydrossChangeToNaturePhaseTimer.end() && + (now - itPhase->second) > phaseChangeWaitSeconds); + + if (justChanged || aboutToChange) + { + if (dynamic_cast(action) || + (dynamic_cast(action) && !dynamic_cast(action))) + return 0.0f; + } + } + + return 1.0f; +} + +float HydrossTheUnstableControlMisdirectionMultiplier::GetValue(Action* action) +{ + Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); + if (!hydross) + return 1.0f; + + if (dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + +// The Lurker Below + +float TheLurkerBelowStayAwayFromSpoutMultiplier::GetValue(Action* action) +{ + Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); + if (!lurker) + return 1.0f; + + const time_t now = std::time(nullptr); + + auto it = lurkerSpoutTimer.find(lurker->GetMapId()); + if (it != lurkerSpoutTimer.end() && it->second > now) + { + if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + +// Leotheras the Blind + +float LeotherasTheBlindAvoidWhirlwindMultiplier::GetValue(Action* action) +{ + Unit* leotherasHuman = GetLeotherasHuman(botAI); + if (!leotherasHuman) + return 1.0f; + + if (!leotherasHuman->HasAura(SPELL_LEOTHERAS_BANISHED) && + (leotherasHuman->HasAura(SPELL_WHIRLWIND) || leotherasHuman->HasAura(SPELL_WHIRLWIND_CHANNEL))) + { + if (dynamic_cast(action)) + return 0.0f; + + if (!botAI->IsTank(bot)) + { + if (dynamic_cast(action) && + !dynamic_cast(action)) + return 0.0f; + } + } + + return 1.0f; +} + +// Applies only if there is a Warlock tank +float LeotherasTheBlindDisableTankActionsMultiplier::GetValue(Action* action) +{ + // (1) Multipliers that apply during Phase 2 or 3 + Unit* leotherasDemon = GetActiveLeotherasDemon(botAI); + if (!leotherasDemon || + dynamic_cast(action) || + dynamic_cast(action)) + return 1.0f; + + Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); + if (!demonFormTank || demonFormTank->getClass() != CLASS_WARLOCK) + return 1.0f; + + if (dynamic_cast(action)) + return 0.0f; + + // (2) Phase 2 only: Tanks other than the Demon Form tank should do absolutely nothing + Unit* leotherasDemonPhase2 = GetPhase2LeotherasDemon(botAI); + if (botAI->IsTank(bot) && bot != demonFormTank && leotherasDemonPhase2) + return 0.0f; + + return 1.0f; +} + +// Applies only if there is no Warlock tank +float LeotherasTheBlindMeleeTankMaintainDemonFormPositionMultiplier::GetValue(Action* action) +{ + Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); + if (!leotheras) + return 1.0f; + + Unit* leotherasDemon = GetActiveLeotherasDemon(botAI); + if (!leotherasDemon) + return 1.0f; + + Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); + if (demonFormTank && demonFormTank->getClass() != CLASS_WARLOCK) + return 1.0f; + + if (botAI->IsTank(bot) && leotherasDemon->GetVictim() == bot) + { + if (dynamic_cast(action) && + !dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + +// Applies only if there is no Warlock tank +float LeotherasTheBlindDemonFormDisableMeleeActionsMultiplier::GetValue(Action* action) +{ + Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); + if (!leotheras) + return 1.0f; + + Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); + if (demonFormTank && demonFormTank->getClass() == CLASS_WARLOCK) + return 1.0f; + + Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI); + if (!leotherasPhase2Demon || leotherasPhase2Demon->GetVictim() == bot || + bot->HasAura(SPELL_INSIDIOUS_WHISPER)) + return 1.0f; + + if (botAI->IsMelee(bot) && botAI->IsDps(bot)) + { + if (dynamic_cast(action) || (dynamic_cast(action) && + !dynamic_cast(action))) + return 0.0f; + } + + return 1.0f; +} + +float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) +{ + Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); + if (!leotheras) + return 1.0f; + + if (dynamic_cast(action)) + return 1.0f; + + const uint32 mapId = leotheras->GetMapId(); + const time_t now = std::time(nullptr); + + const uint8 dpsWaitSecondsPhase1 = 5; + Unit* leotherasHuman = GetLeotherasHuman(botAI); + Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI); + if (leotherasHuman && !leotherasHuman->HasAura(SPELL_LEOTHERAS_BANISHED) && !leotherasPhase3Demon) + { + if (botAI->IsTank(bot)) + return 1.0f; + + auto it = leotherasHumanFormDpsWaitTimer.find(mapId); + if (it == leotherasHumanFormDpsWaitTimer.end() || (now - it->second) < dpsWaitSecondsPhase1) + { + if (dynamic_cast(action) || + (dynamic_cast(action) && !dynamic_cast(action))) + return 0.0f; + } + } + + const uint8 dpsWaitSecondsPhase2 = 10; + Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI); + Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); + if (leotherasPhase2Demon) + { + if (demonFormTank == bot) + return 1.0f; + + auto it = leotherasDemonFormDpsWaitTimer.find(mapId); + if (it == leotherasDemonFormDpsWaitTimer.end() || (now - it->second) < dpsWaitSecondsPhase2) + { + if (dynamic_cast(action) || + (dynamic_cast(action) && !dynamic_cast(action))) + return 0.0f; + } + } + + const uint8 dpsWaitSecondsPhase3 = 12; + if (leotherasPhase3Demon) + { + if (demonFormTank == bot || botAI->IsTank(bot)) + return 1.0f; + + auto it = leotherasFinalPhaseDpsWaitTimer.find(mapId); + if (it == leotherasFinalPhaseDpsWaitTimer.end() || (now - it->second) < dpsWaitSecondsPhase3) + { + if (dynamic_cast(action) || + (dynamic_cast(action) && !dynamic_cast(action))) + return 0.0f; + } + } + + return 1.0f; +} + +// Wait until the final phase to use Bloodlust/Heroism +float LeotherasTheBlindDelayBloodlustAndHeroismMultiplier::GetValue(Action* action) +{ + Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); + if (!leotheras) + return 1.0f; + + Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI); + if (!leotherasPhase3Demon) + { + if (dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + +// Fathom-Lord Karathress + +float FathomLordKarathressDisableTankAssistMultiplier::GetValue(Action* action) +{ + Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); + if (!karathress) + return 1.0f; + + if (dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + +float FathomLordKarathressDisableAoeMultiplier::GetValue(Action* action) +{ + Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); + if (!karathress) + return 1.0f; + + if (dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + +float FathomLordKarathressControlMisdirectionMultiplier::GetValue(Action* action) +{ + Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); + if (!karathress) + return 1.0f; + + if (dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + +float FathomLordKarathressWaitForDpsMultiplier::GetValue(Action* action) +{ + Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); + if (!karathress) + return 1.0f; + + if (botAI->IsTank(bot)) + return 1.0f; + + if (dynamic_cast(action)) + return 1.0f; + + const time_t now = std::time(nullptr); + const uint8 dpsWaitSeconds = 8; + + auto it = karathressDpsWaitTimer.find(karathress->GetMapId()); + if (it == karathressDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds) + { + if (dynamic_cast(action) || + (dynamic_cast(action) && !dynamic_cast(action))) + return 0.0f; + } + + return 1.0f; +} + +float FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier::GetValue(Action* action) +{ + Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); + if (!caribdis || !caribdis->IsAlive()) + return 1.0f; + + if (botAI->IsHealAssistantOfIndex(bot, 0)) + { + if (dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + +// Use Bloodlust/Heroism after the first Murloc spawn +float MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier::GetValue(Action* action) +{ + Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); + if (!tidewalker) + return 1.0f; + + Unit* murloc = AI_VALUE2(Unit*, "find target", "tidewalker lurker"); + if (!murloc) + { + if (dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + +float MorogrimTidewalkerDisablePhase2FleeActionMultiplier::GetValue(Action* action) +{ + Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); + if (!tidewalker) + return 1.0f; + + if (tidewalker->GetHealthPct() < 25.0f) + { + if (dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + +// Wait until phase 3 to use Bloodlust/Heroism +float LadyVashjDelayBloodlustAndHeroismMultiplier::GetValue(Action* action) +{ + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + if (!vashj) + return 1.0f; + + if (!IsLadyVashjInPhase3(botAI)) + { + if (dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + +float LadyVashjStaticChargeStayAwayFromGroupMultiplier::GetValue(Action* action) +{ + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + if (!vashj || IsLadyVashjInPhase2(botAI)) + return 1.0f; + + if (!botAI->IsMainTank(bot) && bot->HasAura(SPELL_STATIC_CHARGE)) + { + if ((dynamic_cast(action) && + !dynamic_cast(action)) || + dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + +// If raid cheat (which enables bot looting of the core) is not enabled +// Bots should not loot the core +float LadyVashjDoNotLootTheTaintedCoreMultiplier::GetValue(Action* action) +{ + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + if (!vashj || !botAI->HasCheat(BotCheatMask::raid)) + return 1.0f; + + if (dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + +// All of phase 2 and 3 require a custom movement and targeting system +// So the standard target selection system must be disabled +float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *action) +{ + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + if (!vashj) + return 1.0f; + + if (dynamic_cast(action)) + return 0.0f; + + if (IsLadyVashjInPhase2(botAI)) + { + if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + + if (!botAI->IsHeal(bot) && dynamic_cast(action)) + return 0.0f; + + Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental"); + if (enchanted && enchanted->IsAlive() && bot->GetVictim() == enchanted) + { + if (dynamic_cast(action)) + return 0.0f; + } + } + + if (IsLadyVashjInPhase3(botAI)) + { + if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + + Unit* strider = AI_VALUE2(Unit*, "find target", "coilfang strider"); + Unit* elite = AI_VALUE2(Unit*, "find target", "coilfang elite"); + Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental"); + + if (enchanted && enchanted->IsAlive()) + { + if (bot->GetVictim() == enchanted) + { + if (dynamic_cast(action)) + return 0.0f; + } + } + + if ((!enchanted || !enchanted->IsAlive()) && (!strider || !strider->IsAlive()) && + (!elite || !elite->IsAlive())) + { + if (dynamic_cast(action)) + return 0.0f; + } + } + + return 1.0f; +} diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h new file mode 100644 index 0000000000..f8dac424aa --- /dev/null +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h @@ -0,0 +1,160 @@ +#ifndef _PLAYERBOT_RAIDSSCMULTIPLIERS_H +#define _PLAYERBOT_RAIDSSCMULTIPLIERS_H + +#include "Multiplier.h" + +class UnderbogColossusEscapeToxicPoolMultiplier : public Multiplier +{ +public: + UnderbogColossusEscapeToxicPoolMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "underbog colossus escape toxic pool") {} + virtual float GetValue(Action* action); +}; + +class HydrossTheUnstableDisableTankActionsMultiplier : public Multiplier +{ +public: + HydrossTheUnstableDisableTankActionsMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "hydross the unstable disable tank actions") {} + virtual float GetValue(Action* action); +}; + +class HydrossTheUnstableWaitForDpsMultiplier : public Multiplier +{ +public: + HydrossTheUnstableWaitForDpsMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "hydross the unstable wait for dps") {} + virtual float GetValue(Action* action); +}; + +class HydrossTheUnstableControlMisdirectionMultiplier : public Multiplier +{ +public: + HydrossTheUnstableControlMisdirectionMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "hydross the unstable control misdirection") {} + virtual float GetValue(Action* action); +}; + +class TheLurkerBelowStayAwayFromSpoutMultiplier : public Multiplier +{ +public: + TheLurkerBelowStayAwayFromSpoutMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "the lurker below stay away from spout") {} + virtual float GetValue(Action* action); +}; + +class LeotherasTheBlindAvoidWhirlwindMultiplier : public Multiplier +{ +public: + LeotherasTheBlindAvoidWhirlwindMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind avoid whirlwind") {} + virtual float GetValue(Action* action); +}; + +class LeotherasTheBlindDisableTankActionsMultiplier : public Multiplier +{ +public: + LeotherasTheBlindDisableTankActionsMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind disable tank actions") {} + virtual float GetValue(Action* action); +}; + +class LeotherasTheBlindMeleeTankMaintainDemonFormPositionMultiplier : public Multiplier +{ +public: + LeotherasTheBlindMeleeTankMaintainDemonFormPositionMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind melee tank maintain demon form position") {} + virtual float GetValue(Action* action); +}; + +class LeotherasTheBlindDemonFormDisableMeleeActionsMultiplier : public Multiplier +{ +public: + LeotherasTheBlindDemonFormDisableMeleeActionsMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind demon form disable melee actions") {} + virtual float GetValue(Action* action); +}; + +class LeotherasTheBlindWaitForDpsMultiplier : public Multiplier +{ +public: + LeotherasTheBlindWaitForDpsMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind wait for dps") {} + virtual float GetValue(Action* action); +}; + +class LeotherasTheBlindDelayBloodlustAndHeroismMultiplier : public Multiplier +{ +public: + LeotherasTheBlindDelayBloodlustAndHeroismMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind delay bloodlust and heroism") {} + virtual float GetValue(Action* action); +}; + +class FathomLordKarathressDisableTankAssistMultiplier : public Multiplier +{ +public: + FathomLordKarathressDisableTankAssistMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "fathom-lord karathress disable tank assist") {} + virtual float GetValue(Action* action); +}; + +class FathomLordKarathressDisableAoeMultiplier : public Multiplier +{ +public: + FathomLordKarathressDisableAoeMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "fathom-lord karathress disable aoe") {} + virtual float GetValue(Action* action); +}; + +class FathomLordKarathressControlMisdirectionMultiplier : public Multiplier +{ +public: + FathomLordKarathressControlMisdirectionMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "fathom-lord karathress control misdirection") {} + virtual float GetValue(Action* action); +}; + +class FathomLordKarathressWaitForDpsMultiplier : public Multiplier +{ +public: + FathomLordKarathressWaitForDpsMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "fathom-lord karathress wait for dps") {} + virtual float GetValue(Action* action); +}; + +class FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier : public Multiplier +{ +public: + FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "fathom-lord karathress caribdis tank healer maintain position") {} + virtual float GetValue(Action* action); +}; + +class MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier : public Multiplier +{ +public: + MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "morogrim tidewalker delay bloodlust and heroism") {} + virtual float GetValue(Action* action); +}; + +class MorogrimTidewalkerDisablePhase2FleeActionMultiplier : public Multiplier +{ +public: + MorogrimTidewalkerDisablePhase2FleeActionMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "morogrim tidewalker disable phase2 flee action") {} + virtual float GetValue(Action* action); +}; + +class LadyVashjDelayBloodlustAndHeroismMultiplier : public Multiplier +{ +public: + LadyVashjDelayBloodlustAndHeroismMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj delay bloodlust and heroism") {} + virtual float GetValue(Action* action); +}; + +class LadyVashjStaticChargeStayAwayFromGroupMultiplier : public Multiplier +{ +public: + LadyVashjStaticChargeStayAwayFromGroupMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj static charge stay away from group") {} + virtual float GetValue(Action* action); +}; + +class LadyVashjDoNotLootTheTaintedCoreMultiplier : public Multiplier +{ +public: + LadyVashjDoNotLootTheTaintedCoreMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj do not loot the tainted core") {} + virtual float GetValue(Action* action); +}; + +class LadyVashjDisableAutomaticTargetingAndMovementModifier : public Multiplier +{ +public: + LadyVashjDisableAutomaticTargetingAndMovementModifier(PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj disable automatic targeting and movement") {} + virtual float GetValue(Action* action); +}; + +#endif diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp new file mode 100644 index 0000000000..7face0f13b --- /dev/null +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp @@ -0,0 +1,193 @@ +#include "RaidSSCStrategy.h" +#include "RaidSSCMultipliers.h" + +void RaidSSCStrategy::InitTriggers(std::vector& triggers) +{ + // Trash Mobs + triggers.push_back(new TriggerNode("underbog colossus spawned toxic pool after death", + NextAction::array(0, new NextAction("underbog colossus escape toxic pool", ACTION_EMERGENCY + 10), nullptr) + )); + triggers.push_back(new TriggerNode("greyheart tidecaller water elemental totem spawned", + NextAction::array(0, new NextAction("greyheart tidecaller mark water elemental totem", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("rancid mushroom spawned", + NextAction::array(0, new NextAction("rancid mushroom move away from mushroom spore cloud", ACTION_EMERGENCY + 10), nullptr) + )); + + // Hydross the Unstable + triggers.push_back(new TriggerNode("hydross the unstable bot is frost tank", + NextAction::array(0, new NextAction("hydross the unstable position frost tank", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("hydross the unstable bot is nature tank", + NextAction::array(0, new NextAction("hydross the unstable position nature tank", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("hydross the unstable elementals spawned", + NextAction::array(0, new NextAction("hydross the unstable prioritize elemental adds", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("hydross the unstable danger from water tombs", + NextAction::array(0, new NextAction("hydross the unstable frost phase spread out", ACTION_EMERGENCY + 1), nullptr) + )); + triggers.push_back(new TriggerNode("hydross the unstable tank needs aggro upon phase change", + NextAction::array(0, new NextAction("hydross the unstable misdirect boss to tank", ACTION_EMERGENCY + 6), nullptr) + )); + triggers.push_back(new TriggerNode("hydross the unstable aggro resets upon phase change", + NextAction::array(0, new NextAction("hydross the unstable stop dps upon phase change", ACTION_EMERGENCY + 9), nullptr) + )); + triggers.push_back(new TriggerNode("hydross the unstable need to manage timers", + NextAction::array(0, new NextAction("hydross the unstable manage timers", ACTION_EMERGENCY + 10), nullptr) + )); + + // The Lurker Below + triggers.push_back(new TriggerNode("the lurker below spout is active", + NextAction::array(0, new NextAction("the lurker below run around behind boss", ACTION_EMERGENCY + 6), nullptr) + )); + triggers.push_back(new TriggerNode("the lurker below boss is active for main tank", + NextAction::array(0, new NextAction("the lurker below position main tank", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("the lurker below boss casts geyser", + NextAction::array(0, new NextAction("the lurker below spread ranged", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("the lurker below need to prepare timer for spout", + NextAction::array(0, new NextAction("the lurker below manage spout timer", ACTION_EMERGENCY + 10), nullptr) + )); + + // Leotheras the Blind + triggers.push_back(new TriggerNode("leotheras the blind boss is inactive", + NextAction::array(0, new NextAction("leotheras the blind target spellbinders", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("leotheras the blind engaged by demon form tank", + NextAction::array(0, new NextAction("leotheras the blind demon form tank attack boss", ACTION_EMERGENCY + 1), nullptr) + )); + triggers.push_back(new TriggerNode("leotheras the blind boss engaged by ranged", + NextAction::array(0, new NextAction("leotheras the blind position ranged", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("leotheras the blind boss channeling whirlwind", + NextAction::array(0, new NextAction("leotheras the blind run away from whirlwind", ACTION_EMERGENCY + 1), nullptr) + )); + triggers.push_back(new TriggerNode("leotheras the blind demon form engaged by melee without warlock tank", + NextAction::array(0, new NextAction("leotheras the blind demon form position melee", ACTION_EMERGENCY + 1), nullptr) + )); + triggers.push_back(new TriggerNode("leotheras the blind inner demon cheat", + NextAction::array(0, new NextAction("leotheras the blind inner demon cheat", ACTION_EMERGENCY + 6), nullptr) + )); + triggers.push_back(new TriggerNode("leotheras the blind entered final phase", + NextAction::array(0, new NextAction("leotheras the blind final phase assign dps priority", ACTION_RAID + 2), nullptr) + )); + triggers.push_back(new TriggerNode("leotheras the blind demon form tank needs aggro", + NextAction::array(0, new NextAction("leotheras the blind misdirect boss to demon form tank", ACTION_RAID + 3), nullptr) + )); + triggers.push_back(new TriggerNode("leotheras the blind need to manage timers and trackers", + NextAction::array(0, new NextAction("leotheras the blind manage timers and trackers", ACTION_EMERGENCY + 10), nullptr) + )); + + // Fathom-Lord Karathress + triggers.push_back(new TriggerNode("fathom-lord karathress boss engaged by main tank", + NextAction::array(0, new NextAction("fathom-lord karathress main tank position boss", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("fathom-lord karathress sharkkis engaged by first assist tank", + NextAction::array(0, new NextAction("fathom-lord karathress first assist tank position sharkkis", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("fathom-lord karathress tidalvess engaged by second assist tank", + NextAction::array(0, new NextAction("fathom-lord karathress second assist tank position tidalvess", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("fathom-lord karathress caribdis engaged by third assist tank", + NextAction::array(0, new NextAction("fathom-lord karathress third assist tank position caribdis", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("fathom-lord karathress caribdis tank needs dedicated healer", + NextAction::array(0, new NextAction("fathom-lord karathress position caribdis tank healer", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("fathom-lord karathress pulling bosses", + NextAction::array(0, new NextAction("fathom-lord karathress misdirect bosses to tanks", ACTION_RAID + 2), nullptr) + )); + triggers.push_back(new TriggerNode("fathom-lord karathress determining kill order", + NextAction::array(0, new NextAction("fathom-lord karathress assign dps priority", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("fathom-lord karathress tanks need to establish aggro", + NextAction::array(0, new NextAction("fathom-lord karathress manage dps timer", ACTION_EMERGENCY + 10), nullptr) + )); + + // Morogrim Tidewalker + triggers.push_back(new TriggerNode("morogrim tidewalker boss engaged by main tank", + NextAction::array(0, new NextAction("morogrim tidewalker move boss to tank position", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("morogrim tidewalker water globules are incoming", + NextAction::array(0, new NextAction("morogrim tidewalker phase 2 reposition ranged", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("morogrim tidewalker pulling boss", + NextAction::array(0, new NextAction("morogrim tidewalker misdirect boss to main tank", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("morogrim tidewalker encounter reset", + NextAction::array(0, new NextAction("morogrim tidewalker reset phase transition steps", ACTION_RAID + 2), nullptr) + )); + + // Lady Vashj + triggers.push_back(new TriggerNode("lady vashj boss engaged by main tank", + NextAction::array(0, new NextAction("lady vashj main tank position boss", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("lady vashj boss engaged by ranged in phase 1", + NextAction::array(0, new NextAction("lady vashj phase 1 position ranged", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("lady vashj casts shock blast on highest aggro", + NextAction::array(0, new NextAction("lady vashj set grounding totem in main tank group", ACTION_EMERGENCY + 1), nullptr) + )); + triggers.push_back(new TriggerNode("lady vashj bot has static charge", + NextAction::array(0, new NextAction("lady vashj static charge move away from group", ACTION_EMERGENCY + 7), nullptr) + )); + triggers.push_back(new TriggerNode("lady vashj pulling boss in phase 1 and phase 3", + NextAction::array(0, new NextAction("lady vashj misdirect boss to main tank", ACTION_EMERGENCY + 1), nullptr) + )); + triggers.push_back(new TriggerNode("lady vashj coilfang strider is approaching", + NextAction::array(0, + new NextAction("lady vashj misdirect strider to first assist tank", ACTION_EMERGENCY + 1), + new NextAction("lady vashj tank attack and move away strider", ACTION_EMERGENCY + 1), nullptr) + )); + triggers.push_back(new TriggerNode("lady vashj tainted elemental cheat", + NextAction::array(0, + new NextAction("lady vashj teleport to tainted elemental", ACTION_EMERGENCY + 10), + new NextAction("lady vashj loot tainted core", ACTION_EMERGENCY + 10), nullptr) + )); + triggers.push_back(new TriggerNode("lady vashj tainted core was looted", + NextAction::array(0, new NextAction("lady vashj pass the tainted core", ACTION_EMERGENCY + 1), nullptr) + )); + triggers.push_back(new TriggerNode("lady vashj core handler is dead", + NextAction::array(0, new NextAction("lady vashj destroy tainted core", ACTION_EMERGENCY + 1), nullptr) + )); + triggers.push_back(new TriggerNode("lady vashj determining kill order of adds", + NextAction::array(0, new NextAction("lady vashj assign dps priority", ACTION_RAID + 1), nullptr) + )); + triggers.push_back(new TriggerNode("lady vashj toxic sporebats are spewing poison clouds", + NextAction::array(0, new NextAction("lady vashj avoid toxic spores", ACTION_EMERGENCY + 6), nullptr) + )); + triggers.push_back(new TriggerNode("lady vashj bot is entangled in toxic spores or static charge", + NextAction::array(0, new NextAction("lady vashj use free action abilities", ACTION_EMERGENCY + 7), nullptr) + )); + triggers.push_back(new TriggerNode("lady vashj need to manage trackers", + NextAction::array(0, new NextAction("lady vashj manage trackers", ACTION_EMERGENCY + 10), nullptr) + )); +} + +void RaidSSCStrategy::InitMultipliers(std::vector& multipliers) +{ + multipliers.push_back(new UnderbogColossusEscapeToxicPoolMultiplier(botAI)); + multipliers.push_back(new HydrossTheUnstableDisableTankActionsMultiplier(botAI)); + multipliers.push_back(new HydrossTheUnstableWaitForDpsMultiplier(botAI)); + multipliers.push_back(new HydrossTheUnstableControlMisdirectionMultiplier(botAI)); + multipliers.push_back(new TheLurkerBelowStayAwayFromSpoutMultiplier(botAI)); + multipliers.push_back(new LeotherasTheBlindAvoidWhirlwindMultiplier(botAI)); + multipliers.push_back(new LeotherasTheBlindDisableTankActionsMultiplier(botAI)); + multipliers.push_back(new LeotherasTheBlindMeleeTankMaintainDemonFormPositionMultiplier(botAI)); + multipliers.push_back(new LeotherasTheBlindDemonFormDisableMeleeActionsMultiplier(botAI)); + multipliers.push_back(new LeotherasTheBlindWaitForDpsMultiplier(botAI)); + multipliers.push_back(new LeotherasTheBlindDelayBloodlustAndHeroismMultiplier(botAI)); + multipliers.push_back(new FathomLordKarathressDisableTankAssistMultiplier(botAI)); + multipliers.push_back(new FathomLordKarathressDisableAoeMultiplier(botAI)); + multipliers.push_back(new FathomLordKarathressControlMisdirectionMultiplier(botAI)); + multipliers.push_back(new FathomLordKarathressWaitForDpsMultiplier(botAI)); + multipliers.push_back(new FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier(botAI)); + multipliers.push_back(new MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier(botAI)); + multipliers.push_back(new MorogrimTidewalkerDisablePhase2FleeActionMultiplier(botAI)); + multipliers.push_back(new LadyVashjDelayBloodlustAndHeroismMultiplier(botAI)); + multipliers.push_back(new LadyVashjStaticChargeStayAwayFromGroupMultiplier(botAI)); + multipliers.push_back(new LadyVashjDoNotLootTheTaintedCoreMultiplier(botAI)); + multipliers.push_back(new LadyVashjDisableAutomaticTargetingAndMovementModifier(botAI)); +} diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.h b/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.h new file mode 100644 index 0000000000..0f90e6784e --- /dev/null +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.h @@ -0,0 +1,18 @@ +#ifndef _PLAYERBOT_RAIDSSCSTRATEGY_H_ +#define _PLAYERBOT_RAIDSSCSTRATEGY_H_ + +#include "Strategy.h" +#include "Multiplier.h" + +class RaidSSCStrategy : public Strategy +{ +public: + RaidSSCStrategy(PlayerbotAI* botAI) : Strategy(botAI) {} + + std::string const getName() override { return "ssc"; } + + void InitTriggers(std::vector& triggerSs) override; + void InitMultipliers(std::vector& multipliers) override; +}; + +#endif diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h new file mode 100644 index 0000000000..814bb3ca3a --- /dev/null +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h @@ -0,0 +1,125 @@ +#ifndef _PLAYERBOT_RAIDSSCTRIGGERCONTEXT_H +#define _PLAYERBOT_RAIDSSCTRIGGERCONTEXT_H + +#include "RaidSSCTriggers.h" +#include "AiObjectContext.h" + +class RaidSSCTriggerContext : public NamedObjectContext +{ +public: + RaidSSCTriggerContext() + { + creators["underbog colossus spawned toxic pool after death"] = &RaidSSCTriggerContext::underbog_colossus_spawned_toxic_pool_after_death; + creators["greyheart tidecaller water elemental totem spawned"] = &RaidSSCTriggerContext::greyheart_tidecaller_water_elemental_totem_spawned; + creators["rancid mushroom spawned"] = &RaidSSCTriggerContext::rancid_mushroom_spawned; + + creators["hydross the unstable bot is frost tank"] = &RaidSSCTriggerContext::hydross_the_unstable_bot_is_frost_tank; + creators["hydross the unstable bot is nature tank"] = &RaidSSCTriggerContext::hydross_the_unstable_bot_is_nature_tank; + creators["hydross the unstable elementals spawned"] = &RaidSSCTriggerContext::hydross_the_unstable_elementals_spawned; + creators["hydross the unstable danger from water tombs"] = &RaidSSCTriggerContext::hydross_the_unstable_danger_from_water_tombs; + creators["hydross the unstable tank needs aggro upon phase change"] = &RaidSSCTriggerContext::hydross_the_unstable_tank_needs_aggro_upon_phase_change; + creators["hydross the unstable aggro resets upon phase change"] = &RaidSSCTriggerContext::hydross_the_unstable_aggro_resets_upon_phase_change; + creators["hydross the unstable need to manage timers"] = &RaidSSCTriggerContext::hydross_the_unstable_need_to_manage_timers; + + creators["the lurker below spout is active"] = &RaidSSCTriggerContext::the_lurker_below_spout_is_active; + creators["the lurker below boss is active for main tank"] = &RaidSSCTriggerContext::the_lurker_below_boss_is_active_for_main_tank; + creators["the lurker below boss casts geyser"] = &RaidSSCTriggerContext::the_lurker_below_boss_casts_geyser; + creators["the lurker below need to prepare timer for spout"] = &RaidSSCTriggerContext::the_lurker_below_need_to_prepare_timer_for_spout; + + creators["leotheras the blind boss is inactive"] = &RaidSSCTriggerContext::leotheras_the_blind_boss_is_inactive; + creators["leotheras the blind engaged by demon form tank"] = &RaidSSCTriggerContext::leotheras_the_blind_engaged_by_demon_form_tank; + creators["leotheras the blind boss engaged by ranged"] = &RaidSSCTriggerContext::leotheras_the_blind_boss_engaged_by_ranged; + creators["leotheras the blind boss channeling whirlwind"] = &RaidSSCTriggerContext::leotheras_the_blind_boss_channeling_whirlwind; + creators["leotheras the blind demon form engaged by melee without warlock tank"] = &RaidSSCTriggerContext::leotheras_the_blind_demon_form_engaged_by_melee_without_warlock_tank; + creators["leotheras the blind inner demon cheat"] = &RaidSSCTriggerContext::leotheras_the_blind_inner_demon_cheat; + creators["leotheras the blind entered final phase"] = &RaidSSCTriggerContext::leotheras_the_blind_entered_final_phase; + creators["leotheras the blind demon form tank needs aggro"] = &RaidSSCTriggerContext::leotheras_the_blind_demon_form_tank_needs_aggro; + creators["leotheras the blind need to manage timers and trackers"] = &RaidSSCTriggerContext::leotheras_the_blind_need_to_manage_timers_and_trackers; + + creators["fathom-lord karathress boss engaged by main tank"] = &RaidSSCTriggerContext::fathom_lord_karathress_boss_engaged_by_main_tank; + creators["fathom-lord karathress sharkkis engaged by first assist tank"] = &RaidSSCTriggerContext::fathom_lord_karathress_sharkkis_engaged_by_first_assist_tank; + creators["fathom-lord karathress tidalvess engaged by second assist tank"] = &RaidSSCTriggerContext::fathom_lord_karathress_tidalvess_engaged_by_second_assist_tank; + creators["fathom-lord karathress caribdis engaged by third assist tank"] = &RaidSSCTriggerContext::fathom_lord_karathress_caribdis_engaged_by_third_assist_tank; + creators["fathom-lord karathress caribdis tank needs dedicated healer"] = &RaidSSCTriggerContext::fathom_lord_karathress_caribdis_tank_needs_dedicated_healer; + creators["fathom-lord karathress pulling bosses"] = &RaidSSCTriggerContext::fathom_lord_karathress_pulling_bosses; + creators["fathom-lord karathress determining kill order"] = &RaidSSCTriggerContext::fathom_lord_karathress_determining_kill_order; + creators["fathom-lord karathress tanks need to establish aggro"] = &RaidSSCTriggerContext::fathom_lord_karathress_tanks_need_to_establish_aggro; + + creators["morogrim tidewalker boss engaged by main tank"] = &RaidSSCTriggerContext::morogrim_tidewalker_boss_engaged_by_main_tank; + creators["morogrim tidewalker pulling boss"] = &RaidSSCTriggerContext::morogrim_tidewalker_pulling_boss; + creators["morogrim tidewalker water globules are incoming"] = &RaidSSCTriggerContext::morogrim_tidewalker_water_globules_are_incoming; + creators["morogrim tidewalker encounter reset"] = &RaidSSCTriggerContext::morogrim_tidewalker_encounter_reset; + + creators["lady vashj boss engaged by main tank"] = &RaidSSCTriggerContext::lady_vashj_boss_engaged_by_main_tank; + creators["lady vashj boss engaged by ranged in phase 1"] = &RaidSSCTriggerContext::lady_vashj_boss_engaged_by_ranged_in_phase_1; + creators["lady vashj casts shock blast on highest aggro"] = &RaidSSCTriggerContext::lady_vashj_casts_shock_blast_on_highest_aggro; + creators["lady vashj bot has static charge"] = &RaidSSCTriggerContext::lady_vashj_bot_has_static_charge; + creators["lady vashj pulling boss in phase 1 and phase 3"] = &RaidSSCTriggerContext::lady_vashj_pulling_boss_in_phase_1_and_phase_3; + creators["lady vashj coilfang strider is approaching"] = &RaidSSCTriggerContext::lady_vashj_coilfang_strider_is_approaching; + creators["lady vashj determining kill order of adds"] = &RaidSSCTriggerContext::lady_vashj_determining_kill_order_of_adds; + creators["lady vashj tainted elemental cheat"] = &RaidSSCTriggerContext::lady_vashj_tainted_elemental_cheat; + creators["lady vashj tainted core was looted"] = &RaidSSCTriggerContext::lady_vashj_tainted_core_was_looted; + creators["lady vashj core handler is dead"] = &RaidSSCTriggerContext::lady_vashj_core_handler_is_dead; + creators["lady vashj toxic sporebats are spewing poison clouds"] = &RaidSSCTriggerContext::lady_vashj_toxic_sporebats_are_spewing_poison_clouds; + creators["lady vashj bot is entangled in toxic spores or static charge"] = &RaidSSCTriggerContext::lady_vashj_bot_is_entangled_in_toxic_spores_or_static_charge; + creators["lady vashj need to manage trackers"] = &RaidSSCTriggerContext::lady_vashj_need_to_manage_trackers; + } + +private: + static Trigger* underbog_colossus_spawned_toxic_pool_after_death(PlayerbotAI* botAI) { return new UnderbogColossusSpawnedToxicPoolAfterDeathTrigger(botAI); } + static Trigger* greyheart_tidecaller_water_elemental_totem_spawned(PlayerbotAI* botAI) { return new GreyheartTidecallerWaterElementalTotemSpawnedTrigger(botAI); } + static Trigger* rancid_mushroom_spawned(PlayerbotAI* botAI) { return new RancidMushroomSpawnedTrigger(botAI); } + + static Trigger* hydross_the_unstable_bot_is_frost_tank(PlayerbotAI* botAI) { return new HydrossTheUnstableBotIsFrostTankTrigger(botAI); } + static Trigger* hydross_the_unstable_bot_is_nature_tank(PlayerbotAI* botAI) { return new HydrossTheUnstableBotIsNatureTankTrigger(botAI); } + static Trigger* hydross_the_unstable_elementals_spawned(PlayerbotAI* botAI) { return new HydrossTheUnstableElementalsSpawnedTrigger(botAI); } + static Trigger* hydross_the_unstable_danger_from_water_tombs(PlayerbotAI* botAI) { return new HydrossTheUnstableDangerFromWaterTombsTrigger(botAI); } + static Trigger* hydross_the_unstable_tank_needs_aggro_upon_phase_change(PlayerbotAI* botAI) { return new HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger(botAI); } + static Trigger* hydross_the_unstable_aggro_resets_upon_phase_change(PlayerbotAI* botAI) { return new HydrossTheUnstableAggroResetsUponPhaseChangeTrigger(botAI); } + static Trigger* hydross_the_unstable_need_to_manage_timers(PlayerbotAI* botAI) { return new HydrossTheUnstableNeedToManageTimersTrigger(botAI); } + + static Trigger* the_lurker_below_spout_is_active(PlayerbotAI* botAI) { return new TheLurkerBelowSpoutIsActiveTrigger(botAI); } + static Trigger* the_lurker_below_boss_is_active_for_main_tank(PlayerbotAI* botAI) { return new TheLurkerBelowBossIsActiveForMainTankTrigger(botAI); } + static Trigger* the_lurker_below_boss_casts_geyser(PlayerbotAI* botAI) { return new TheLurkerBelowBossCastsGeyserTrigger(botAI); } + static Trigger* the_lurker_below_need_to_prepare_timer_for_spout(PlayerbotAI* botAI) { return new TheLurkerBelowNeedToPrepareTimerForSpoutTrigger(botAI); } + + static Trigger* leotheras_the_blind_boss_is_inactive(PlayerbotAI* botAI) { return new LeotherasTheBlindBossIsInactiveTrigger(botAI); } + static Trigger* leotheras_the_blind_engaged_by_demon_form_tank(PlayerbotAI* botAI) { return new LeotherasTheBlindEngagedByDemonFormTankTrigger(botAI); } + static Trigger* leotheras_the_blind_boss_engaged_by_ranged(PlayerbotAI* botAI) { return new LeotherasTheBlindBossEngagedByRangedTrigger(botAI); } + static Trigger* leotheras_the_blind_boss_channeling_whirlwind(PlayerbotAI* botAI) { return new LeotherasTheBlindBossChannelingWhirlwindTrigger(botAI); } + static Trigger* leotheras_the_blind_demon_form_engaged_by_melee_without_warlock_tank(PlayerbotAI* botAI) { return new LeotherasTheBlindDemonFormEngagedByMeleeWithoutWarlockTankTrigger(botAI); } + static Trigger* leotheras_the_blind_inner_demon_cheat(PlayerbotAI* botAI) { return new LeotherasTheBlindInnerDemonCheatTrigger(botAI); } + static Trigger* leotheras_the_blind_entered_final_phase(PlayerbotAI* botAI) { return new LeotherasTheBlindEnteredFinalPhaseTrigger(botAI); } + static Trigger* leotheras_the_blind_demon_form_tank_needs_aggro(PlayerbotAI* botAI) { return new LeotherasTheBlindDemonFormTankNeedsAggro(botAI); } + static Trigger* leotheras_the_blind_need_to_manage_timers_and_trackers(PlayerbotAI* botAI) { return new LeotherasTheBlindNeedToManageTimersAndTrackersTrigger(botAI); } + + static Trigger* fathom_lord_karathress_boss_engaged_by_main_tank(PlayerbotAI* botAI) { return new FathomLordKarathressBossEngagedByMainTankTrigger(botAI); } + static Trigger* fathom_lord_karathress_sharkkis_engaged_by_first_assist_tank(PlayerbotAI* botAI) { return new FathomLordKarathressSharkkisEngagedByFirstAssistTankTrigger(botAI); } + static Trigger* fathom_lord_karathress_tidalvess_engaged_by_second_assist_tank(PlayerbotAI* botAI) { return new FathomLordKarathressTidalvessEngagedBySecondAssistTankTrigger(botAI); } + static Trigger* fathom_lord_karathress_caribdis_engaged_by_third_assist_tank(PlayerbotAI* botAI) { return new FathomLordKarathressCaribdisEngagedByThirdAssistTankTrigger(botAI); } + static Trigger* fathom_lord_karathress_caribdis_tank_needs_dedicated_healer(PlayerbotAI* botAI) { return new FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger(botAI); } + static Trigger* fathom_lord_karathress_pulling_bosses(PlayerbotAI* botAI) { return new FathomLordKarathressPullingBossesTrigger(botAI); } + static Trigger* fathom_lord_karathress_determining_kill_order(PlayerbotAI* botAI) { return new FathomLordKarathressDeterminingKillOrderTrigger(botAI); } + static Trigger* fathom_lord_karathress_tanks_need_to_establish_aggro(PlayerbotAI* botAI) { return new FathomLordKarathressTanksNeedToEstablishAggroTrigger(botAI); } + + static Trigger* morogrim_tidewalker_boss_engaged_by_main_tank(PlayerbotAI* botAI) { return new MorogrimTidewalkerBossEngagedByMainTankTrigger(botAI); } + static Trigger* morogrim_tidewalker_pulling_boss(PlayerbotAI* botAI) { return new MorogrimTidewalkerPullingBossTrigger(botAI); } + static Trigger* morogrim_tidewalker_water_globules_are_incoming(PlayerbotAI* botAI) { return new MorogrimTidewalkerWaterGlobulesAreIncomingTrigger(botAI); } + static Trigger* morogrim_tidewalker_encounter_reset(PlayerbotAI* botAI) { return new MorogrimTidewalkerEncounterResetTrigger(botAI); } + + static Trigger* lady_vashj_boss_engaged_by_main_tank(PlayerbotAI* botAI) { return new LadyVashjBossEngagedByMainTankTrigger(botAI); } + static Trigger* lady_vashj_boss_engaged_by_ranged_in_phase_1(PlayerbotAI* botAI) { return new LadyVashjBossEngagedByRangedInPhase1Trigger(botAI); } + static Trigger* lady_vashj_casts_shock_blast_on_highest_aggro(PlayerbotAI* botAI) { return new LadyVashjCastsShockBlastOnHighestAggroTrigger(botAI); } + static Trigger* lady_vashj_bot_has_static_charge(PlayerbotAI* botAI) { return new LadyVashjBotHasStaticChargeTrigger(botAI); } + static Trigger* lady_vashj_pulling_boss_in_phase_1_and_phase_3(PlayerbotAI* botAI) { return new LadyVashjPullingBossInPhase1AndPhase3Trigger(botAI); } + static Trigger* lady_vashj_coilfang_strider_is_approaching(PlayerbotAI* botAI) { return new LadyVashjCoilfangStriderIsApproachingTrigger(botAI); } + static Trigger* lady_vashj_determining_kill_order_of_adds(PlayerbotAI* botAI) { return new LadyVashjDeterminingKillOrderOfAddsTrigger(botAI); } + static Trigger* lady_vashj_tainted_elemental_cheat(PlayerbotAI* botAI) { return new LadyVashjTaintedElementalCheatTrigger(botAI); } + static Trigger* lady_vashj_tainted_core_was_looted(PlayerbotAI* botAI) { return new LadyVashjTaintedCoreWasLootedTrigger(botAI); } + static Trigger* lady_vashj_core_handler_is_dead(PlayerbotAI* botAI) { return new LadyVashjCoreHandlerIsDeadTrigger(botAI); } + static Trigger* lady_vashj_toxic_sporebats_are_spewing_poison_clouds(PlayerbotAI* botAI) { return new LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger(botAI); } + static Trigger* lady_vashj_bot_is_entangled_in_toxic_spores_or_static_charge(PlayerbotAI* botAI) { return new LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger(botAI); } + static Trigger* lady_vashj_need_to_manage_trackers(PlayerbotAI* botAI) { return new LadyVashjNeedToManageTrackersTrigger(botAI); } +}; + +#endif \ No newline at end of file diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp new file mode 100644 index 0000000000..1e08193aa6 --- /dev/null +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp @@ -0,0 +1,619 @@ +#include "RaidSSCTriggers.h" +#include "RaidSSCHelpers.h" +#include "RaidSSCActions.h" +#include "AiFactory.h" +#include "Corpse.h" +#include "LootObjectStack.h" +#include "ObjectAccessor.h" +#include "Playerbots.h" + +using namespace SerpentShrineCavernHelpers; + +// Trash Mobs + +bool UnderbogColossusSpawnedToxicPoolAfterDeathTrigger::IsActive() +{ + return bot->HasAura(SPELL_TOXIC_POOL); +} + +bool GreyheartTidecallerWaterElementalTotemSpawnedTrigger::IsActive() +{ + if (!botAI->IsDps(bot)) + return false; + + Unit* totem = GetFirstAliveUnitByEntry(botAI, NPC_WATER_ELEMENTAL_TOTEM); + return totem != nullptr; +} + +bool RancidMushroomSpawnedTrigger::IsActive() +{ + Unit* mushroom = GetFirstAliveUnitByEntry(botAI, NPC_RANCID_MUSHROOM); + return mushroom != nullptr; +} + +// Hydross the Unstable + +bool HydrossTheUnstableBotIsFrostTankTrigger::IsActive() +{ + if (!botAI->IsMainTank(bot)) + return false; + + Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); + return hydross != nullptr; +} + +bool HydrossTheUnstableBotIsNatureTankTrigger::IsActive() +{ + if (!botAI->IsAssistTankOfIndex(bot, 0)) + return false; + + Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); + return hydross != nullptr; +} + +bool HydrossTheUnstableElementalsSpawnedTrigger::IsActive() +{ + if (botAI->IsHeal(bot) || botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0)) + return false; + + Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); + if (!hydross || hydross->GetHealthPct() < 10.0f) + return false; + + Unit* waterElemental = AI_VALUE2(Unit*, "find target", "pure spawn of hydross"); + Unit* natureElemental = AI_VALUE2(Unit*, "find target", "tainted spawn of hydross"); + return waterElemental != nullptr || natureElemental != nullptr; +} + +bool HydrossTheUnstableDangerFromWaterTombsTrigger::IsActive() +{ + if (!botAI->IsRanged(bot)) + return false; + + Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); + return hydross != nullptr; +} + +bool HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger::IsActive() +{ + if (bot->getClass() != CLASS_HUNTER) + return false; + + Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); + return hydross != nullptr; +} + +bool HydrossTheUnstableAggroResetsUponPhaseChangeTrigger::IsActive() +{ + if (bot->getClass() == CLASS_HUNTER) + return false; + + if (!botAI->IsDps(bot)) + return false; + + Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); + return hydross != nullptr; +} + +bool HydrossTheUnstableNeedToManageTimersTrigger::IsActive() +{ + if (!IsMapIDTimerManager(botAI, bot)) + return false; + + Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); + return hydross != nullptr; +} + +// The Lurker Below + +bool TheLurkerBelowSpoutIsActiveTrigger::IsActive() +{ + Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); + if (!lurker) + return false; + + const time_t now = std::time(nullptr); + + auto it = lurkerSpoutTimer.find(lurker->GetMapId()); + return it != lurkerSpoutTimer.end() && it->second > now; +} + +bool TheLurkerBelowBossIsActiveForMainTankTrigger::IsActive() +{ + if (!botAI->IsMainTank(bot)) + return false; + + Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); + if (!lurker) + return false; + + const time_t now = std::time(nullptr); + + auto it = lurkerSpoutTimer.find(lurker->GetMapId()); + return lurker->getStandState() != UNIT_STAND_STATE_SUBMERGED && + (it == lurkerSpoutTimer.end() || it->second <= now); +} + +bool TheLurkerBelowBossCastsGeyserTrigger::IsActive() +{ + if (!botAI->IsRanged(bot)) + return false; + + Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); + if (!lurker) + return false; + + const time_t now = std::time(nullptr); + + auto it = lurkerSpoutTimer.find(lurker->GetMapId()); + return lurker->getStandState() != UNIT_STAND_STATE_SUBMERGED && + (it == lurkerSpoutTimer.end() || it->second <= now); +} + +bool TheLurkerBelowNeedToPrepareTimerForSpoutTrigger::IsActive() +{ + if (!IsMapIDTimerManager(botAI, bot)) + return false; + + Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); + return lurker != nullptr; +} + +// Leotheras the Blind + +bool LeotherasTheBlindBossIsInactiveTrigger::IsActive() +{ + Unit* spellbinder = AI_VALUE2(Unit*, "find target", "greyheart spellbinder"); + return spellbinder && spellbinder->IsAlive(); +} + +bool LeotherasTheBlindEngagedByDemonFormTankTrigger::IsActive() +{ + Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); + if (demonFormTank && bot != demonFormTank) + return false; + + Unit* leotherasDemon = GetActiveLeotherasDemon(botAI); + return leotherasDemon != nullptr; +} + +bool LeotherasTheBlindBossEngagedByRangedTrigger::IsActive() +{ + if (!botAI->IsRanged(bot)) + return false; + + Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); + if (demonFormTank && demonFormTank == bot) + return false; + + Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); + return leotheras && !leotheras->HasAura(SPELL_LEOTHERAS_BANISHED); +} + +bool LeotherasTheBlindBossChannelingWhirlwindTrigger::IsActive() +{ + if (botAI->IsTank(bot) && botAI->IsMelee(bot)) + return false; + + Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); + return leotheras && !leotheras->HasAura(SPELL_LEOTHERAS_BANISHED) && + (leotheras->HasAura(SPELL_WHIRLWIND) || leotheras->HasAura(SPELL_WHIRLWIND_CHANNEL)); +} + +bool LeotherasTheBlindDemonFormEngagedByMeleeWithoutWarlockTankTrigger::IsActive() +{ + Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); + if (demonFormTank && demonFormTank->getClass() == CLASS_WARLOCK) + return false; + + if (!botAI->IsMelee(bot) || botAI->IsMainTank(bot)) + return false; + + Unit* leotherasDemon = GetActiveLeotherasDemon(botAI); + return leotherasDemon != nullptr; +} + +bool LeotherasTheBlindInnerDemonCheatTrigger::IsActive() +{ + if (!botAI->HasCheat(BotCheatMask::raid)) + return false; + + if (!bot->HasAura(SPELL_INSIDIOUS_WHISPER)) + return false; + + Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); + return leotheras != nullptr; +} + +bool LeotherasTheBlindEnteredFinalPhaseTrigger::IsActive() +{ + if (botAI->IsHeal(bot)) + return false; + + Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); + if (demonFormTank && demonFormTank == bot) + return false; + + Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI); + if (!leotherasPhase3Demon) + return false; + + Unit* leotherasHuman = GetLeotherasHuman(botAI); + return leotherasHuman != nullptr; +} + +bool LeotherasTheBlindDemonFormTankNeedsAggro::IsActive() +{ + if (bot->getClass() != CLASS_HUNTER) + return false; + + if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) + return false; + + Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); + return leotheras != nullptr; +} + +bool LeotherasTheBlindNeedToManageTimersAndTrackersTrigger::IsActive() +{ + if (!IsMapIDTimerManager(botAI, bot)) + return false; + + Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); + return leotheras != nullptr; +} + +// Fathom-Lord Karathress + +bool FathomLordKarathressBossEngagedByMainTankTrigger::IsActive() +{ + if (!botAI->IsMainTank(bot)) + return false; + + Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); + return karathress != nullptr; +} + +bool FathomLordKarathressSharkkisEngagedByFirstAssistTankTrigger::IsActive() +{ + if (!botAI->IsAssistTankOfIndex(bot, 0)) + return false; + + Unit* sharkkis = AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis"); + return sharkkis && sharkkis->IsAlive(); +} + +bool FathomLordKarathressTidalvessEngagedBySecondAssistTankTrigger::IsActive() +{ + if (!botAI->IsAssistTankOfIndex(bot, 1)) + return false; + + Unit* tidalvess = AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess"); + return tidalvess && tidalvess->IsAlive(); +} + +bool FathomLordKarathressCaribdisEngagedByThirdAssistTankTrigger::IsActive() +{ + if (!botAI->IsAssistTankOfIndex(bot, 2)) + return false; + + Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); + return caribdis && caribdis->IsAlive(); +} + +bool FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger::IsActive() +{ + if (!botAI->IsHealAssistantOfIndex(bot, 0)) + return false; + + Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); + return caribdis && caribdis->IsAlive(); +} + +bool FathomLordKarathressPullingBossesTrigger::IsActive() +{ + if (bot->getClass() != CLASS_HUNTER) + return false; + + Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); + return karathress && karathress->GetHealthPct() > 98.0f; +} + +bool FathomLordKarathressDeterminingKillOrderTrigger::IsActive() +{ + Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); + if (!karathress) + return false; + + Unit* sharkkis = AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis"); + Unit* tidalvess = AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess"); + Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); + + return (botAI->IsDps(bot) || + (botAI->IsAssistTankOfIndex(bot, 0) && (!sharkkis || !sharkkis->IsAlive())) || + (botAI->IsAssistTankOfIndex(bot, 1) && (!tidalvess || !tidalvess->IsAlive())) || + (botAI->IsAssistTankOfIndex(bot, 2) && (!caribdis || !caribdis->IsAlive()))); +} + +bool FathomLordKarathressTanksNeedToEstablishAggroTrigger::IsActive() +{ + if (!IsMapIDTimerManager(botAI, bot)) + return false; + + Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); + return karathress != nullptr; +} + +// Morogrim Tidewalker + +bool MorogrimTidewalkerPullingBossTrigger::IsActive() +{ + if (bot->getClass() != CLASS_HUNTER) + return false; + + Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); + return tidewalker && tidewalker->GetHealthPct() > 95.0f; +} + +bool MorogrimTidewalkerBossEngagedByMainTankTrigger::IsActive() +{ + if (!botAI->IsMainTank(bot)) + return false; + + Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); + return tidewalker != nullptr; +} + +bool MorogrimTidewalkerWaterGlobulesAreIncomingTrigger::IsActive() +{ + if (!botAI->IsRanged(bot)) + return false; + + Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); + return tidewalker && tidewalker->GetHealthPct() < 25.0f; +} + +bool MorogrimTidewalkerEncounterResetTrigger::IsActive() +{ + Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); + return tidewalker && tidewalker->GetHealth() == tidewalker->GetMaxHealth(); +} + +// Lady Vashj + +bool LadyVashjBossEngagedByMainTankTrigger::IsActive() +{ + if (!botAI->IsMainTank(bot)) + return false; + + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + return vashj && (IsLadyVashjInPhase1(botAI) || IsLadyVashjInPhase3(botAI)); +} + +bool LadyVashjBossEngagedByRangedInPhase1Trigger::IsActive() +{ + if (!botAI->IsRanged(bot)) + return false; + + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + return vashj && IsLadyVashjInPhase1(botAI); +} + +bool LadyVashjCastsShockBlastOnHighestAggroTrigger::IsActive() +{ + uint8 tab = AiFactory::GetPlayerSpecTab(bot); + if (bot->getClass() != CLASS_SHAMAN || tab != 2) + return false; + + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + return vashj && (IsLadyVashjInPhase1(botAI) || IsLadyVashjInPhase3(botAI)); +} + +bool LadyVashjBotHasStaticChargeTrigger::IsActive() +{ + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + if (!vashj) + return false; + + if (Group* group = bot->GetGroup()) + { + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->IsAlive() && member->HasAura(SPELL_STATIC_CHARGE)) + return true; + } + } + + return false; +} + +bool LadyVashjPullingBossInPhase1AndPhase3Trigger::IsActive() +{ + if (bot->getClass() != CLASS_HUNTER) + return false; + + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + + return vashj && ((vashj->GetHealthPct() <= 100.0f && vashj->GetHealthPct() > 90.0f) || + (!vashj->HasUnitState(UNIT_STATE_ROOT) && vashj->GetHealthPct() <= 50.0f && vashj->GetHealthPct() > 40.0f)); +} + +bool LadyVashjCoilfangStriderIsApproachingTrigger::IsActive() +{ + Unit* strider = AI_VALUE2(Unit*, "find target", "coilfang strider"); + return strider && strider->IsAlive() && + (IsLadyVashjInPhase2(botAI) || IsLadyVashjInPhase3(botAI)); +} + +bool LadyVashjDeterminingKillOrderOfAddsTrigger::IsActive() +{ + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + return vashj && (IsLadyVashjInPhase2(botAI) || IsLadyVashjInPhase3(botAI)); +} + +bool LadyVashjTaintedElementalCheatTrigger::IsActive() +{ + if (!botAI->HasCheat(BotCheatMask::raid)) + return false; + + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + if (!vashj) + return false; + + bool taintedPresent = false; + Unit* taintedUnit = AI_VALUE2(Unit*, "find target", "tainted elemental"); + if (taintedUnit) + taintedPresent = true; + else + { + GuidVector corpses = AI_VALUE(GuidVector, "nearest corpses"); + for (auto const& guid : corpses) + { + LootObject loot(bot, guid); + WorldObject* object = loot.GetWorldObject(bot); + if (!object) + continue; + + if (Creature* creature = object->ToCreature()) + { + if (creature->GetEntry() == NPC_TAINTED_ELEMENTAL && !creature->IsAlive()) + { + taintedPresent = true; + break; + } + } + } + } + + if (!taintedPresent) + return false; + + Group* group = bot->GetGroup(); + Player* master = botAI->GetMaster(); + if (!group || !master) + return false; + + Player* designatedLooter = GetDesignatedCoreLooter(group, master, botAI); + return (designatedLooter && designatedLooter == bot && + !bot->HasItemCount(ITEM_TAINTED_CORE, 1, false)); +} + +bool LadyVashjTaintedCoreWasLootedTrigger::IsActive() +{ + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + if (!vashj) + return false; + + if (!IsLadyVashjInPhase2(botAI)) + return false; + + Player* master = botAI->GetMaster(); + Group* group = bot->GetGroup(); + if (!master || !group) + return false; + + Player* designatedLooter = GetDesignatedCoreLooter(group, master, botAI); + Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI); + Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI); + Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI); + Player* fourthCorePasser = GetFourthTaintedCorePasser(group, botAI); + + auto hasCore = [](Player* p) -> bool { return p && p->HasItemCount(ITEM_TAINTED_CORE, 1, false); }; + + if (bot == designatedLooter) + { + if (hasCore(firstCorePasser) || hasCore(secondCorePasser) || hasCore(thirdCorePasser) || hasCore(fourthCorePasser)) + return false; + } + else if (bot == firstCorePasser) + { + if (hasCore(secondCorePasser) || hasCore(thirdCorePasser) || hasCore(fourthCorePasser)) + return false; + } + else if (bot == secondCorePasser) + { + if (hasCore(thirdCorePasser) || hasCore(fourthCorePasser)) + return false; + } + else if (bot == thirdCorePasser) + { + if (hasCore(fourthCorePasser)) + return false; + } + + if (AnyRecentParalyze(group, vashj->GetMapId())) + return true; + + Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental"); + if (tainted && designatedLooter->GetExactDist2d(tainted) < 5.0f) + return true; + + return false; +} + +bool LadyVashjCoreHandlerIsDeadTrigger::IsActive() +{ + Player* master = botAI->GetMaster(); + Group* group = bot->GetGroup(); + if (!master || !group) + return false; + + Player* coreHandlers[] = + { + GetDesignatedCoreLooter(group, master, botAI), + GetFirstTaintedCorePasser(group, botAI), + GetSecondTaintedCorePasser(group, botAI), + GetThirdTaintedCorePasser(group, botAI), + GetFourthTaintedCorePasser(group, botAI) + }; + + if (bot->HasItemCount(ITEM_TAINTED_CORE, 1, false)) + { + for (Player* coreHandler : coreHandlers) + { + if (coreHandler && bot == coreHandler) + return false; + } + return true; + } + + return false; +} + +bool LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger::IsActive() +{ + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + return vashj && IsLadyVashjInPhase3(botAI); +} + +bool LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger::IsActive() +{ + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + if (!vashj) + return false; + + if (Group* group = bot->GetGroup()) + { + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive()) + continue; + + if (!member->HasAura(SPELL_ENTANGLE)) + continue; + + if (botAI->IsMelee(member)) + return true; + } + } + + return false; +} + +bool LadyVashjNeedToManageTrackersTrigger::IsActive() +{ + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + return vashj && vashj->GetHealth() == vashj->GetMaxHealth(); +} diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h new file mode 100644 index 0000000000..3d31af95b8 --- /dev/null +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h @@ -0,0 +1,349 @@ +#ifndef _PLAYERBOT_RAIDSSCTRIGGERS_H +#define _PLAYERBOT_RAIDSSCTRIGGERS_H + +#include "Trigger.h" + +class UnderbogColossusSpawnedToxicPoolAfterDeathTrigger : public Trigger +{ +public: + UnderbogColossusSpawnedToxicPoolAfterDeathTrigger(PlayerbotAI* botAI) : Trigger(botAI, "underbog colossus spawned toxic pool after death") {} + bool IsActive() override; +}; + +class GreyheartTidecallerWaterElementalTotemSpawnedTrigger : public Trigger +{ +public: + GreyheartTidecallerWaterElementalTotemSpawnedTrigger(PlayerbotAI* botAI) : Trigger(botAI, "greyheart tidecaller water elemental totem spawned") {} + bool IsActive() override; +}; + +class RancidMushroomSpawnedTrigger : public Trigger +{ +public: + RancidMushroomSpawnedTrigger(PlayerbotAI* botAI) : Trigger(botAI, "rancid mushroom spawned") {} + bool IsActive() override; +}; + +class HydrossTheUnstableBotIsFrostTankTrigger : public Trigger +{ +public: + HydrossTheUnstableBotIsFrostTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable bot is frost tank") {} + bool IsActive() override; +}; + +class HydrossTheUnstableBotIsNatureTankTrigger : public Trigger +{ +public: + HydrossTheUnstableBotIsNatureTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable bot is nature tank") {} + bool IsActive() override; +}; + +class HydrossTheUnstableElementalsSpawnedTrigger : public Trigger +{ +public: + HydrossTheUnstableElementalsSpawnedTrigger(PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable elementals spawned") {} + bool IsActive() override; +}; + +class HydrossTheUnstableDangerFromWaterTombsTrigger : public Trigger +{ +public: + HydrossTheUnstableDangerFromWaterTombsTrigger(PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable danger from water tombs") {} + bool IsActive() override; +}; + +class HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger : public Trigger +{ +public: + HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger(PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable tank needs aggro upon phase change") {} + bool IsActive() override; +}; + +class HydrossTheUnstableAggroResetsUponPhaseChangeTrigger : public Trigger +{ +public: + HydrossTheUnstableAggroResetsUponPhaseChangeTrigger(PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable aggro resets upon phase change") {} + bool IsActive() override; +}; + +class HydrossTheUnstableNeedToManageTimersTrigger : public Trigger +{ +public: + HydrossTheUnstableNeedToManageTimersTrigger(PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable need to manage timers") {} + bool IsActive() override; +}; + +class TheLurkerBelowSpoutIsActiveTrigger : public Trigger +{ +public: + TheLurkerBelowSpoutIsActiveTrigger(PlayerbotAI* botAI) : Trigger(botAI, "the lurker below spout is active") {} + bool IsActive() override; +}; + +class TheLurkerBelowBossIsActiveForMainTankTrigger : public Trigger +{ +public: + TheLurkerBelowBossIsActiveForMainTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "the lurker below boss is active for main tank") {} + bool IsActive() override; +}; + +class TheLurkerBelowBossCastsGeyserTrigger : public Trigger +{ +public: + TheLurkerBelowBossCastsGeyserTrigger(PlayerbotAI* botAI) : Trigger(botAI, "the lurker below boss casts geyser") {} + bool IsActive() override; +}; + +class TheLurkerBelowNeedToPrepareTimerForSpoutTrigger : public Trigger +{ +public: + TheLurkerBelowNeedToPrepareTimerForSpoutTrigger(PlayerbotAI* botAI) : Trigger(botAI, "the lurker below need to prepare timer for spout") {} + bool IsActive() override; +}; + +class LeotherasTheBlindBossIsInactiveTrigger : public Trigger +{ +public: + LeotherasTheBlindBossIsInactiveTrigger(PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind boss is inactive") {} + bool IsActive() override; +}; + +class LeotherasTheBlindHumanFormEngagedByMainTankTrigger : public Trigger +{ +public: + LeotherasTheBlindHumanFormEngagedByMainTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind human form engaged by main tank") {} + bool IsActive() override; +}; + +class LeotherasTheBlindEngagedByDemonFormTankTrigger : public Trigger +{ +public: + LeotherasTheBlindEngagedByDemonFormTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind engaged by demon form tank") {} + bool IsActive() override; +}; + +class LeotherasTheBlindBossEngagedByRangedTrigger : public Trigger +{ +public: + LeotherasTheBlindBossEngagedByRangedTrigger(PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind boss engaged by ranged") {} + bool IsActive() override; +}; + +class LeotherasTheBlindBossChannelingWhirlwindTrigger : public Trigger +{ +public: + LeotherasTheBlindBossChannelingWhirlwindTrigger(PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind boss channeling whirlwind") {} + bool IsActive() override; +}; + +class LeotherasTheBlindDemonFormEngagedByMeleeWithoutWarlockTankTrigger : public Trigger +{ +public: + LeotherasTheBlindDemonFormEngagedByMeleeWithoutWarlockTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind demon form engaged by melee without warlock tank") {} + bool IsActive() override; +}; + +class LeotherasTheBlindInnerDemonCheatTrigger : public Trigger +{ +public: + LeotherasTheBlindInnerDemonCheatTrigger(PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind inner demon cheat") {} + bool IsActive() override; +}; + +class LeotherasTheBlindEnteredFinalPhaseTrigger : public Trigger +{ +public: + LeotherasTheBlindEnteredFinalPhaseTrigger(PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind entered final phase") {} + bool IsActive() override; +}; + +class LeotherasTheBlindDemonFormTankNeedsAggro : public Trigger +{ +public: + LeotherasTheBlindDemonFormTankNeedsAggro(PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind demon form tank needs aggro") {} + bool IsActive() override; +}; + +class LeotherasTheBlindNeedToManageTimersAndTrackersTrigger : public Trigger +{ +public: + LeotherasTheBlindNeedToManageTimersAndTrackersTrigger(PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind need to manage timers and trackers") {} + bool IsActive() override; +}; + +class FathomLordKarathressBossEngagedByMainTankTrigger : public Trigger +{ +public: + FathomLordKarathressBossEngagedByMainTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress boss engaged by main tank") {} + bool IsActive() override; +}; + +class FathomLordKarathressSharkkisEngagedByFirstAssistTankTrigger : public Trigger +{ +public: + FathomLordKarathressSharkkisEngagedByFirstAssistTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress sharkkis engaged by first assist tank") {} + bool IsActive() override; +}; + +class FathomLordKarathressTidalvessEngagedBySecondAssistTankTrigger : public Trigger +{ +public: + FathomLordKarathressTidalvessEngagedBySecondAssistTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress tidalvess engaged by second assist tank") {} + bool IsActive() override; +}; + +class FathomLordKarathressCaribdisEngagedByThirdAssistTankTrigger : public Trigger +{ +public: + FathomLordKarathressCaribdisEngagedByThirdAssistTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress caribdis engaged by third assist tank") {} + bool IsActive() override; +}; + +class FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger : public Trigger +{ +public: + FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger(PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress caribdis tank needs dedicated healer") {} + bool IsActive() override; +}; + +class FathomLordKarathressPullingBossesTrigger : public Trigger +{ +public: + FathomLordKarathressPullingBossesTrigger(PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress pulling bosses") {} + bool IsActive() override; +}; + +class FathomLordKarathressDeterminingKillOrderTrigger : public Trigger +{ +public: + FathomLordKarathressDeterminingKillOrderTrigger(PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress determining kill order") {} + bool IsActive() override; +}; + +class FathomLordKarathressTanksNeedToEstablishAggroTrigger : public Trigger +{ +public: + FathomLordKarathressTanksNeedToEstablishAggroTrigger(PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress tanks need to establish aggro") {} + bool IsActive() override; +}; + +class MorogrimTidewalkerPullingBossTrigger : public Trigger +{ +public: + MorogrimTidewalkerPullingBossTrigger(PlayerbotAI* botAI) : Trigger(botAI, "morogrim tidewalker pulling boss") {} + bool IsActive() override; +}; + +class MorogrimTidewalkerBossEngagedByMainTankTrigger : public Trigger +{ +public: + MorogrimTidewalkerBossEngagedByMainTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "morogrim tidewalker boss engaged by main tank") {} + bool IsActive() override; +}; + +class MorogrimTidewalkerWaterGlobulesAreIncomingTrigger : public Trigger +{ +public: + MorogrimTidewalkerWaterGlobulesAreIncomingTrigger(PlayerbotAI* botAI) : Trigger(botAI, "morogrim tidewalker water globules are incoming") {} + bool IsActive() override; +}; + +class MorogrimTidewalkerEncounterResetTrigger : public Trigger +{ +public: + MorogrimTidewalkerEncounterResetTrigger(PlayerbotAI* botAI) : Trigger(botAI, "morogrim tidewalker encounter reset") {} + bool IsActive() override; +}; + +class LadyVashjBossEngagedByMainTankTrigger : public Trigger +{ +public: + LadyVashjBossEngagedByMainTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "lady vashj boss engaged by main tank") {} + bool IsActive() override; +}; + +class LadyVashjBossEngagedByRangedInPhase1Trigger : public Trigger +{ +public: + LadyVashjBossEngagedByRangedInPhase1Trigger(PlayerbotAI* botAI) : Trigger(botAI, "lady vashj boss engaged by ranged in phase 1") {} + bool IsActive() override; +}; + +class LadyVashjCastsShockBlastOnHighestAggroTrigger : public Trigger +{ +public: + LadyVashjCastsShockBlastOnHighestAggroTrigger(PlayerbotAI* botAI) : Trigger(botAI, "lady vashj casts shock blast on highest aggro") {} + bool IsActive() override; +}; + +class LadyVashjBotHasStaticChargeTrigger : public Trigger +{ +public: + LadyVashjBotHasStaticChargeTrigger(PlayerbotAI* botAI) : Trigger(botAI, "lady vashj bot has static charge") {} + bool IsActive() override; +}; + +class LadyVashjPullingBossInPhase1AndPhase3Trigger : public Trigger +{ +public: + LadyVashjPullingBossInPhase1AndPhase3Trigger(PlayerbotAI* botAI) : Trigger(botAI, "lady vashj pulling boss in phase 1 and phase 3") {} + bool IsActive() override; +}; + +class LadyVashjCoilfangStriderIsApproachingTrigger : public Trigger +{ +public: + LadyVashjCoilfangStriderIsApproachingTrigger(PlayerbotAI* botAI) : Trigger(botAI, "lady vashj coilfang strider is approaching") {} + bool IsActive() override; +}; + +class LadyVashjDeterminingKillOrderOfAddsTrigger : public Trigger +{ +public: +LadyVashjDeterminingKillOrderOfAddsTrigger(PlayerbotAI* botAI) : Trigger(botAI, "lady vashj determining kill order of adds") {} + bool IsActive() override; +}; + +class LadyVashjTaintedElementalCheatTrigger : public Trigger +{ +public: + LadyVashjTaintedElementalCheatTrigger(PlayerbotAI* botAI) : Trigger(botAI, "lady vashj tainted elemental cheat") {} + bool IsActive() override; +}; + +class LadyVashjTaintedCoreWasLootedTrigger : public Trigger +{ +public: + LadyVashjTaintedCoreWasLootedTrigger(PlayerbotAI* botAI) : Trigger(botAI, "lady vashj tainted core was looted") {} + bool IsActive() override; +}; + +class LadyVashjCoreHandlerIsDeadTrigger : public Trigger +{ +public: + LadyVashjCoreHandlerIsDeadTrigger(PlayerbotAI* botAI) : Trigger(botAI, "lady vashj core handler is dead") {} + bool IsActive() override; +}; + +class LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger : public Trigger +{ +public: + LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger(PlayerbotAI* botAI) : Trigger(botAI, "lady vashj toxic sporebats are spewing poison clouds") {} + bool IsActive() override; +}; + +class LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger : public Trigger +{ +public: + LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger(PlayerbotAI* botAI) : Trigger(botAI, "lady vashj bot is entangled in toxic spores or static charge") {} + bool IsActive() override; +}; + +class LadyVashjNeedToManageTrackersTrigger : public Trigger +{ +public: + LadyVashjNeedToManageTrackersTrigger(PlayerbotAI* botAI) : Trigger(botAI, "lady vashj need to manage trackers") {} + bool IsActive() override; +}; + +#endif From 36a67a43153bc450823fa2696ddebab10dfaa38a Mon Sep 17 00:00:00 2001 From: crow Date: Fri, 28 Nov 2025 11:59:31 -0600 Subject: [PATCH 02/25] a few cleanups including removing Tainted Elementals from general target priority logic --- .../serpentshrinecavern/RaidSSCActions.cpp | 17 ++++++----------- .../serpentshrinecavern/RaidSSCHelpers.cpp | 2 +- .../raids/serpentshrinecavern/RaidSSCHelpers.h | 4 +--- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp index 2e300d6187..4d85518ff1 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp @@ -1917,11 +1917,6 @@ bool LadyVashjAssignDpsPriorityAction::Execute(Event event) switch (unit->GetEntry()) { - case NPC_TAINTED_ELEMENTAL: - if (!tainted || bot->GetExactDist2d(unit) < bot->GetExactDist2d(tainted)) - tainted = unit; - break; - case NPC_ENCHANTED_ELEMENTAL: if (!enchanted || vashj->GetExactDist2d(unit) < vashj->GetExactDist2d(enchanted)) enchanted = unit; @@ -1959,22 +1954,22 @@ bool LadyVashjAssignDpsPriorityAction::Execute(Event event) // Hunters and Mages prioritize Enchanted Elementals, while other ranged DPS prioritize Striders // This works well with 3 Hunters and 2 Mages; effectiveness may vary based on raid composition if (bot->getClass() == CLASS_HUNTER || bot->getClass() == CLASS_MAGE) - targets = { tainted, enchanted, strider, elite }; + targets = { enchanted, strider, elite }; else - targets = { tainted, strider, elite, enchanted }; + targets = { strider, elite, enchanted }; } else if (botAI->IsMelee(bot) && botAI->IsDps(bot)) - targets = { tainted, enchanted, elite }; + targets = { enchanted, elite }; else if (botAI->IsTank(bot)) { // With raid cheats enabled, the first assist tank will tank the Strider if (botAI->HasCheat(BotCheatMask::raid) && botAI->IsAssistTankOfIndex(bot, 0)) - targets = { strider, enchanted, tainted }; + targets = { strider, enchanted }; else - targets = { elite, enchanted, tainted }; + targets = { elite, enchanted }; } else - targets = { tainted, enchanted, elite, strider }; + targets = { enchanted, elite, strider }; } if (IsLadyVashjInPhase3(botAI)) diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp index 1e2cdc8518..b6e7170650 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp @@ -578,7 +578,7 @@ namespace SerpentShrineCavernHelpers return nullptr; std::list triggers; - float searchRange = 150.0f; + const float searchRange = 150.0f; reference->GetCreatureListWithEntryInGrid(triggers, NPC_WORLD_INVISIBLE_TRIGGER, searchRange); Creature* nearest = nullptr; diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h index addf15ce0b..ee972272c8 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h @@ -71,16 +71,14 @@ namespace SerpentShrineCavernHelpers NPC_RANCID_MUSHROOM = 22250, // Hydross the Unstable - NPC_HYDROSS_FROST_FORM = 21216, - NPC_HYDROSS_NATURE_FORM = 21232, NPC_PURE_SPAWN_OF_HYDROSS = 22035, NPC_TAINTED_SPAWN_OF_HYDROSS = 22036, // Leotheras the Blind NPC_LEOTHERAS_THE_BLIND = 21215, NPC_GREYHEART_SPELLBINDER = 21806, - NPC_SHADOW_OF_LEOTHERAS = 21875, NPC_INNER_DEMON = 21857, + NPC_SHADOW_OF_LEOTHERAS = 21875, // Fathom-Lord Karathress NPC_SPITFIRE_TOTEM = 22091, From 48ae5338b4b9f96c6d272a7af2cc749d23fcd30f Mon Sep 17 00:00:00 2001 From: crow Date: Sun, 30 Nov 2025 21:29:49 -0600 Subject: [PATCH 03/25] updates following testing --- .../RaidSSCActionContext.h | 18 +- .../serpentshrinecavern/RaidSSCActions.cpp | 717 +++++++++--------- .../serpentshrinecavern/RaidSSCActions.h | 29 +- .../serpentshrinecavern/RaidSSCHelpers.cpp | 73 +- .../serpentshrinecavern/RaidSSCHelpers.h | 24 +- .../RaidSSCMultipliers.cpp | 206 +++-- .../serpentshrinecavern/RaidSSCMultipliers.h | 30 +- .../serpentshrinecavern/RaidSSCStrategy.cpp | 35 +- .../RaidSSCTriggerContext.h | 26 +- .../serpentshrinecavern/RaidSSCTriggers.cpp | 167 ++-- .../serpentshrinecavern/RaidSSCTriggers.h | 41 +- 11 files changed, 740 insertions(+), 626 deletions(-) diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h b/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h index 7e3c8c25e1..0fbb75fc2f 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h @@ -11,7 +11,6 @@ class RaidSSCActionContext : public NamedObjectContext { creators["underbog colossus escape toxic pool"] = &RaidSSCActionContext::underbog_colossus_escape_toxic_pool; creators["greyheart tidecaller mark water elemental totem"] = &RaidSSCActionContext::greyheart_tidecaller_mark_water_elemental_totem; - creators["rancid mushroom move away from mushroom spore cloud"] = &RaidSSCActionContext::rancid_mushroom_move_away_from_mushroom_spore_cloud; creators["hydross the unstable position frost tank"] = &RaidSSCActionContext::hydross_the_unstable_position_frost_tank; creators["hydross the unstable position nature tank"] = &RaidSSCActionContext::hydross_the_unstable_position_nature_tank; @@ -24,22 +23,22 @@ class RaidSSCActionContext : public NamedObjectContext creators["the lurker below run around behind boss"] = &RaidSSCActionContext::the_lurker_below_run_around_behind_boss; creators["the lurker below position main tank"] = &RaidSSCActionContext::the_lurker_below_position_main_tank; creators["the lurker below spread ranged"] = &RaidSSCActionContext::the_lurker_below_spread_ranged; + creators["the lurker below tanks pick up adds"] = &RaidSSCActionContext::the_lurker_below_tanks_pick_up_adds; creators["the lurker below manage spout timer"] = &RaidSSCActionContext::the_lurker_below_manage_spout_timer; creators["leotheras the blind target spellbinders"] = &RaidSSCActionContext::leotheras_the_blind_target_spellbinders; creators["leotheras the blind demon form tank attack boss"] = &RaidSSCActionContext::leotheras_the_blind_demon_form_tank_attack_boss; creators["leotheras the blind position ranged"] = &RaidSSCActionContext::leotheras_the_blind_position_ranged; creators["leotheras the blind run away from whirlwind"] = &RaidSSCActionContext::leotheras_the_blind_run_away_from_whirlwind; - creators["leotheras the blind demon form position melee"] = &RaidSSCActionContext::leotheras_the_blind_demon_form_position_melee; creators["leotheras the blind inner demon cheat"] = &RaidSSCActionContext::leotheras_the_blind_inner_demon_cheat; creators["leotheras the blind final phase assign dps priority"] = &RaidSSCActionContext::leotheras_the_blind_final_phase_assign_dps_priority; creators["leotheras the blind misdirect boss to demon form tank"] = &RaidSSCActionContext::leotheras_the_blind_misdirect_boss_to_demon_form_tank; creators["leotheras the blind manage timers and trackers"] = &RaidSSCActionContext::leotheras_the_blind_manage_timers_and_trackers; creators["fathom-lord karathress main tank position boss"] = &RaidSSCActionContext::fathom_lord_karathress_main_tank_position_boss; - creators["fathom-lord karathress first assist tank position sharkkis"] = &RaidSSCActionContext::fathom_lord_karathress_first_assist_tank_position_sharkkis; - creators["fathom-lord karathress second assist tank position tidalvess"] = &RaidSSCActionContext::fathom_lord_karathress_second_assist_tank_position_tidalvess; - creators["fathom-lord karathress third assist tank position caribdis"] = &RaidSSCActionContext::fathom_lord_karathress_third_assist_tank_position_caribdis; + creators["fathom-lord karathress first assist tank position caribdis"] = &RaidSSCActionContext::fathom_lord_karathress_first_assist_tank_position_caribdis; + creators["fathom-lord karathress second assist tank position sharkkis"] = &RaidSSCActionContext::fathom_lord_karathress_second_assist_tank_position_sharkkis; + creators["fathom-lord karathress third assist tank position tidalvess"] = &RaidSSCActionContext::fathom_lord_karathress_third_assist_tank_position_tidalvess; creators["fathom-lord karathress position caribdis tank healer"] = &RaidSSCActionContext::fathom_lord_karathress_position_caribdis_tank_healer; creators["fathom-lord karathress misdirect bosses to tanks"] = &RaidSSCActionContext::fathom_lord_karathress_misdirect_bosses_to_tanks; creators["fathom-lord karathress assign dps priority"] = &RaidSSCActionContext::fathom_lord_karathress_assign_dps_priority; @@ -70,7 +69,6 @@ class RaidSSCActionContext : public NamedObjectContext private: static Action* underbog_colossus_escape_toxic_pool(PlayerbotAI* botAI) { return new UnderbogColossusEscapeToxicPoolAction(botAI); } static Action* greyheart_tidecaller_mark_water_elemental_totem(PlayerbotAI* botAI) { return new GreyheartTidecallerMarkWaterElementalTotemAction(botAI); } - static Action* rancid_mushroom_move_away_from_mushroom_spore_cloud(PlayerbotAI* botAI) { return new RancidMushroomMoveAwayFromMushroomSporeCloudAction(botAI); } static Action* hydross_the_unstable_position_frost_tank(PlayerbotAI* botAI) { return new HydrossTheUnstablePositionFrostTankAction(botAI); } static Action* hydross_the_unstable_position_nature_tank(PlayerbotAI* botAI) { return new HydrossTheUnstablePositionNatureTankAction(botAI); } @@ -83,22 +81,22 @@ class RaidSSCActionContext : public NamedObjectContext static Action* the_lurker_below_run_around_behind_boss(PlayerbotAI* botAI) { return new TheLurkerBelowRunAroundBehindBossAction(botAI); } static Action* the_lurker_below_position_main_tank(PlayerbotAI* botAI) { return new TheLurkerBelowPositionMainTankAction(botAI); } static Action* the_lurker_below_spread_ranged(PlayerbotAI* botAI) { return new TheLurkerBelowSpreadRangedAction(botAI); } + static Action* the_lurker_below_tanks_pick_up_adds(PlayerbotAI* botAI) { return new TheLurkerBelowTanksPickUpAddsAction(botAI); } static Action* the_lurker_below_manage_spout_timer(PlayerbotAI* botAI) { return new TheLurkerBelowManageSpoutTimerAction(botAI); } static Action* leotheras_the_blind_target_spellbinders(PlayerbotAI* botAI) { return new LeotherasTheBlindTargetSpellbindersAction(botAI); } static Action* leotheras_the_blind_demon_form_tank_attack_boss(PlayerbotAI* botAI) { return new LeotherasTheBlindDemonFormTankAttackBossAction(botAI); } static Action* leotheras_the_blind_position_ranged(PlayerbotAI* botAI) { return new LeotherasTheBlindPositionRangedAction(botAI); } static Action* leotheras_the_blind_run_away_from_whirlwind(PlayerbotAI* botAI) { return new LeotherasTheBlindRunAwayFromWhirlwindAction(botAI); } - static Action* leotheras_the_blind_demon_form_position_melee(PlayerbotAI* botAI) { return new LeotherasTheBlindDemonFormPositionMeleeAction(botAI); } static Action* leotheras_the_blind_inner_demon_cheat(PlayerbotAI* botAI) { return new LeotherasTheBlindInnerDemonCheatAction(botAI); } static Action* leotheras_the_blind_misdirect_boss_to_demon_form_tank(PlayerbotAI* botAI) { return new LeotherasTheBlindMisdirectBossToDemonFormTankAction(botAI); } static Action* leotheras_the_blind_final_phase_assign_dps_priority(PlayerbotAI* botAI) { return new LeotherasTheBlindFinalPhaseAssignDpsPriorityAction(botAI); } static Action* leotheras_the_blind_manage_timers_and_trackers(PlayerbotAI* botAI) { return new LeotherasTheBlindManageTimersAndTrackersAction(botAI); } static Action* fathom_lord_karathress_main_tank_position_boss(PlayerbotAI* botAI) { return new FathomLordKarathressMainTankPositionBossAction(botAI); } - static Action* fathom_lord_karathress_first_assist_tank_position_sharkkis(PlayerbotAI* botAI) { return new FathomLordKarathressFirstAssistTankPositionSharkkisAction(botAI); } - static Action* fathom_lord_karathress_second_assist_tank_position_tidalvess(PlayerbotAI* botAI) { return new FathomLordKarathressSecondAssistTankPositionTidalvessAction(botAI); } - static Action* fathom_lord_karathress_third_assist_tank_position_caribdis(PlayerbotAI* botAI) { return new FathomLordKarathressThirdAssistTankPositionCaribdisAction(botAI); } + static Action* fathom_lord_karathress_first_assist_tank_position_caribdis(PlayerbotAI* botAI) { return new FathomLordKarathressFirstAssistTankPositionCaribdisAction(botAI); } + static Action* fathom_lord_karathress_second_assist_tank_position_sharkkis(PlayerbotAI* botAI) { return new FathomLordKarathressSecondAssistTankPositionSharkkisAction(botAI); } + static Action* fathom_lord_karathress_third_assist_tank_position_tidalvess(PlayerbotAI* botAI) { return new FathomLordKarathressThirdAssistTankPositionTidalvessAction(botAI); } static Action* fathom_lord_karathress_position_caribdis_tank_healer(PlayerbotAI* botAI) { return new FathomLordKarathressPositionCaribdisTankHealerAction(botAI); } static Action* fathom_lord_karathress_misdirect_bosses_to_tanks(PlayerbotAI* botAI) { return new FathomLordKarathressMisdirectBossesToTanksAction(botAI); } static Action* fathom_lord_karathress_assign_dps_priority(PlayerbotAI* botAI) { return new FathomLordKarathressAssignDpsPriorityAction(botAI); } diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp index 4d85518ff1..c00842a804 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp @@ -69,7 +69,7 @@ bool UnderbogColossusEscapeToxicPoolAction::Execute(Event event) } else { - float dist = std::hypot(dx, dy); + float dist = sqrt(dx * dx + dy * dy); float inv = 1.0f / dist; moveX = dynObj->GetPositionX() + (dx * inv) * safeDist; moveY = dynObj->GetPositionY() + (dy * inv) * safeDist; @@ -77,9 +77,8 @@ bool UnderbogColossusEscapeToxicPoolAction::Execute(Event event) bot->AttackStop(); bot->InterruptNonMeleeSpells(true); - - return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), - false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); + return MoveTo(SSC_MAP_ID, moveX, moveY, bot->GetPositionZ(), + false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); } bool GreyheartTidecallerMarkWaterElementalTotemAction::Execute(Event event) @@ -92,24 +91,6 @@ bool GreyheartTidecallerMarkWaterElementalTotemAction::Execute(Event event) return false; } -bool RancidMushroomMoveAwayFromMushroomSporeCloudAction::Execute(Event event) -{ - Unit* mushroom = GetFirstAliveUnitByEntry(botAI, NPC_RANCID_MUSHROOM); - if (!mushroom) - return false; - - float currentDistance = bot->GetExactDist2d(mushroom); - const float safeDistance = 10.0f; - if (currentDistance < safeDistance) - { - bot->AttackStop(); - bot->InterruptNonMeleeSpells(false); - return MoveAway(mushroom, safeDistance - currentDistance + 2.0f, false); - } - - return false; -} - // Hydross the Unstable // (1) When tanking, move to designated tanking spot on frost side @@ -129,19 +110,19 @@ bool HydrossTheUnstablePositionFrostTankAction::Execute(Event event) if (bot->GetVictim() != hydross) return Attack(hydross); - if (hydross->GetVictim() == bot && bot->IsWithinMeleeRange(hydross)) + if (hydross->GetVictim() == bot) { const Position& position = HydrossFrostTankPosition; if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 3.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); - float dist = std::hypot(dX, dY); + float dist = sqrt(dX * dX + dY * dY); float moveDist = std::min(4.5f, dist); float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - return MoveTo(bot->GetMapId(), moveX, moveY, position.GetPositionZ(), false, false, false, true, + return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, true); } } @@ -149,9 +130,8 @@ bool HydrossTheUnstablePositionFrostTankAction::Execute(Event event) if (!hydross->HasAura(SPELL_CORRUPTION) && HasMarkOfHydrossAt100Percent(bot) && hydross->GetVictim() == bot) { - const uint32 mapId = hydross->GetMapId(); const time_t now = std::time(nullptr); - auto it = hydrossChangeToNaturePhaseTimer.find(mapId); + auto it = hydrossChangeToNaturePhaseTimer.find(SSC_MAP_ID); if (it != hydrossChangeToNaturePhaseTimer.end() && (now - it->second) >= 5) { @@ -160,12 +140,12 @@ bool HydrossTheUnstablePositionFrostTankAction::Execute(Event event) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); - float dist = std::hypot(dX, dY); + float dist = sqrt(dX * dX + dY * dY); float moveDist = std::min(4.5f, dist); float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - return MoveTo(bot->GetMapId(), moveX, moveY, position.GetPositionZ(), false, false, false, true, + return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, true); } else @@ -184,12 +164,12 @@ bool HydrossTheUnstablePositionFrostTankAction::Execute(Event event) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); - float dist = std::hypot(dX, dY); + float dist = sqrt(dX * dX + dY * dY); float moveDist = std::min(7.0f, dist); float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - return MoveTo(bot->GetMapId(), moveX, moveY, position.GetPositionZ(), false, false, false, true, + return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, false); } else @@ -220,19 +200,19 @@ bool HydrossTheUnstablePositionNatureTankAction::Execute(Event event) if (bot->GetVictim() != hydross) return Attack(hydross); - if (hydross->GetVictim() == bot && bot->IsWithinMeleeRange(hydross)) + if (hydross->GetVictim() == bot) { const Position& position = HydrossNatureTankPosition; if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 3.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); - float dist = std::hypot(dX, dY); + float dist = sqrt(dX * dX + dY * dY); float moveDist = std::min(4.5f, dist); float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - return MoveTo(bot->GetMapId(), moveX, moveY, position.GetPositionZ(), false, false, false, true, + return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, true); } } @@ -240,9 +220,8 @@ bool HydrossTheUnstablePositionNatureTankAction::Execute(Event event) if (hydross->HasAura(SPELL_CORRUPTION) && HasMarkOfCorruptionAt100Percent(bot) && hydross->GetVictim() == bot) { - const uint32 mapId = hydross->GetMapId(); const time_t now = std::time(nullptr); - auto it = hydrossChangeToFrostPhaseTimer.find(mapId); + auto it = hydrossChangeToFrostPhaseTimer.find(SSC_MAP_ID); if (it != hydrossChangeToFrostPhaseTimer.end() && (now - it->second) >= 5) { @@ -251,12 +230,12 @@ bool HydrossTheUnstablePositionNatureTankAction::Execute(Event event) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); - float dist = std::hypot(dX, dY); + float dist = sqrt(dX * dX + dY * dY); float moveDist = std::min(4.5f, dist); float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - return MoveTo(bot->GetMapId(), moveX, moveY, position.GetPositionZ(), false, false, false, true, + return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, true); } else @@ -275,12 +254,12 @@ bool HydrossTheUnstablePositionNatureTankAction::Execute(Event event) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); - float dist = std::hypot(dX, dY); + float dist = sqrt(dX * dX + dY * dY); float moveDist = std::min(7.0f, dist); float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - return MoveTo(bot->GetMapId(), moveX, moveY, position.GetPositionZ(), false, false, false, true, + return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, false); } else @@ -428,7 +407,6 @@ bool HydrossTheUnstableStopDpsUponPhaseChangeAction::Execute(Event event) if (!hydross) return false; - const uint32 mapId = hydross->GetMapId(); const time_t now = std::time(nullptr); const int phaseEndStopSeconds = 6; const int phaseStartStopSeconds = 5; @@ -436,22 +414,22 @@ bool HydrossTheUnstableStopDpsUponPhaseChangeAction::Execute(Event event) bool shouldStopDps = false; // 6 seconds after 100% Mark of Corruption, stop DPS until transition into frost phase - auto itNature = hydrossChangeToNaturePhaseTimer.find(mapId); + auto itNature = hydrossChangeToNaturePhaseTimer.find(SSC_MAP_ID); if (itNature != hydrossChangeToNaturePhaseTimer.end() && (now - itNature->second) >= phaseEndStopSeconds) shouldStopDps = true; // Keep DPS stopped for 5 seconds after transition into frost phase - auto itFrostDps = hydrossFrostDpsWaitTimer.find(mapId); + auto itFrostDps = hydrossFrostDpsWaitTimer.find(SSC_MAP_ID); if (itFrostDps != hydrossFrostDpsWaitTimer.end() && (now - itFrostDps->second) < phaseStartStopSeconds) shouldStopDps = true; // 6 seconds after 100% Mark of Hydross, stop DPS until transition into nature phase - auto itFrost = hydrossChangeToFrostPhaseTimer.find(mapId); + auto itFrost = hydrossChangeToFrostPhaseTimer.find(SSC_MAP_ID); if (itFrost != hydrossChangeToFrostPhaseTimer.end() && (now - itFrost->second) >= phaseEndStopSeconds) shouldStopDps = true; // Keep DPS stopped for 5 seconds after transition into nature phase - auto itNatureDps = hydrossNatureDpsWaitTimer.find(mapId); + auto itNatureDps = hydrossNatureDpsWaitTimer.find(SSC_MAP_ID); if (itNatureDps != hydrossNatureDpsWaitTimer.end() && (now - itNatureDps->second) < phaseStartStopSeconds) shouldStopDps = true; @@ -471,34 +449,33 @@ bool HydrossTheUnstableManageTimersAction::Execute(Event event) if (!hydross) return false; - const uint32 mapId = hydross->GetMapId(); const time_t now = std::time(nullptr); if (hydross->GetHealth() == hydross->GetMaxHealth()) { - hydrossFrostDpsWaitTimer.erase(mapId); - hydrossNatureDpsWaitTimer.erase(mapId); - hydrossChangeToFrostPhaseTimer.erase(mapId); - hydrossChangeToNaturePhaseTimer.erase(mapId); + hydrossFrostDpsWaitTimer.erase(SSC_MAP_ID); + hydrossNatureDpsWaitTimer.erase(SSC_MAP_ID); + hydrossChangeToFrostPhaseTimer.erase(SSC_MAP_ID); + hydrossChangeToNaturePhaseTimer.erase(SSC_MAP_ID); } if (!hydross->HasAura(SPELL_CORRUPTION)) { - hydrossFrostDpsWaitTimer.try_emplace(mapId, now); - hydrossNatureDpsWaitTimer.erase(mapId); - hydrossChangeToFrostPhaseTimer.erase(mapId); + hydrossFrostDpsWaitTimer.try_emplace(SSC_MAP_ID, now); + hydrossNatureDpsWaitTimer.erase(SSC_MAP_ID); + hydrossChangeToFrostPhaseTimer.erase(SSC_MAP_ID); if (HasMarkOfHydrossAt100Percent(bot)) - hydrossChangeToNaturePhaseTimer.try_emplace(mapId, now); + hydrossChangeToNaturePhaseTimer.try_emplace(SSC_MAP_ID, now); } else { - hydrossNatureDpsWaitTimer.try_emplace(mapId, now); - hydrossFrostDpsWaitTimer.erase(mapId); - hydrossChangeToNaturePhaseTimer.erase(mapId); + hydrossNatureDpsWaitTimer.try_emplace(SSC_MAP_ID, now); + hydrossFrostDpsWaitTimer.erase(SSC_MAP_ID); + hydrossChangeToNaturePhaseTimer.erase(SSC_MAP_ID); if (HasMarkOfCorruptionAt100Percent(bot)) - hydrossChangeToFrostPhaseTimer.try_emplace(mapId, now); + hydrossChangeToFrostPhaseTimer.try_emplace(SSC_MAP_ID, now); } return false; @@ -514,6 +491,9 @@ bool TheLurkerBelowRunAroundBehindBossAction::Execute(Event event) if (!lurker) return false; + if (bot->HasAura(SPELL_TREE_OF_LIFE) && botAI->CanCastSpell("tree of life", bot)) + return botAI->CastSpell("tree of life", bot); + float bossFacing = lurker->GetOrientation(); float behindAngle = bossFacing + M_PI + frand(-0.5f, 0.5f) * (M_PI / 2.0f); float radius = frand(20.0f, 24.0f); @@ -524,7 +504,7 @@ bool TheLurkerBelowRunAroundBehindBossAction::Execute(Event event) if (bot->GetExactDist2d(targetX, targetY) > 1.0f) { bot->InterruptNonMeleeSpells(true); - return MoveTo(lurker->GetMapId(), targetX, targetY, lurker->GetPositionZ(), false, false, false, false, + return MoveTo(SSC_MAP_ID, targetX, targetY, lurker->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_FORCED, true, false); } @@ -543,8 +523,8 @@ bool TheLurkerBelowPositionMainTankAction::Execute(Event event) const Position& position = LurkerMainTankPosition; if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 0.2f) { - return MoveTo(bot->GetMapId(), position.GetPositionX(), position.GetPositionY(), position.GetPositionZ(), false, false, false, false, - MovementPriority::MOVEMENT_FORCED, true, false); + return MoveTo(SSC_MAP_ID, position.GetPositionX(), position.GetPositionY(), position.GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_FORCED, true, false); } return false; @@ -567,6 +547,7 @@ bool TheLurkerBelowSpreadRangedAction::Execute(Event event) Player* member = ref->GetSource(); if (!member || !member->IsAlive() || !botAI->IsRanged(member)) continue; + rangedMembers.push_back(member); } @@ -614,8 +595,75 @@ bool TheLurkerBelowSpreadRangedAction::Execute(Event event) const float returnThreshold = 2.0f; if (!bot->IsWithinDist2d(target.GetPositionX(), target.GetPositionY(), returnThreshold)) { - return MoveTo(bot->GetMapId(), target.GetPositionX(), target.GetPositionY(), target.GetPositionZ(), false, false, false, false, - MovementPriority::MOVEMENT_COMBAT, true, false); + return MoveTo(SSC_MAP_ID, target.GetPositionX(), target.GetPositionY(), target.GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); + } + + return false; +} + +// If >= 3 tanks in the raid, the first 3 will each pick up 1 Guardian +bool TheLurkerBelowTanksPickUpAddsAction::Execute(Event event) +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + Player* mainTank = nullptr; + Player* firstAssistTank = nullptr; + Player* secondAssistTank = nullptr; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive()) + continue; + + if (!mainTank && botAI->IsMainTank(member)) + mainTank = member; + else if (!firstAssistTank && botAI->IsAssistTankOfIndex(member, 0)) + firstAssistTank = member; + else if (!secondAssistTank && botAI->IsAssistTankOfIndex(member, 1)) + secondAssistTank = member; + } + + if (!mainTank || !firstAssistTank || !secondAssistTank) + return false; + + std::vector guardians; + GuidVector npcs = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); + for (auto guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + if (unit && unit->IsAlive() && unit->GetEntry() == NPC_COILFANG_GUARDIAN) + guardians.push_back(unit); + } + + if (guardians.size() < 3) + return false; + + std::vector tanks = { mainTank, firstAssistTank, secondAssistTank }; + std::vector rtiIndices = + { + RtiTargetValue::starIndex, + RtiTargetValue::circleIndex, + RtiTargetValue::diamondIndex + }; + std::vector rtiNames = { "star", "circle", "diamond" }; + + for (size_t i = 0; i < 3; ++i) + { + Player* tank = tanks[i]; + Unit* guardian = guardians[i]; + if (bot == tank) + { + MarkTargetWithIcon(bot, guardian, rtiIndices[i]); + SetRtiTarget(botAI, rtiNames[i], guardian); + if (bot->GetVictim() != guardian) + { + bot->SetTarget(guardian->GetGUID()); + return Attack(guardian); + } + } } return false; @@ -627,16 +675,15 @@ bool TheLurkerBelowManageSpoutTimerAction::Execute(Event event) if (!lurker) return false; - const uint32 mapId = lurker->GetMapId(); const time_t now = std::time(nullptr); if (lurker->GetHealth() == lurker->GetMaxHealth()) { - lurkerSpoutTimer.erase(mapId); + lurkerSpoutTimer.erase(SSC_MAP_ID); return false; } - auto it = lurkerSpoutTimer.find(mapId); + auto it = lurkerSpoutTimer.find(SSC_MAP_ID); if (it != lurkerSpoutTimer.end() && it->second <= now) { lurkerSpoutTimer.erase(it); @@ -645,7 +692,7 @@ bool TheLurkerBelowManageSpoutTimerAction::Execute(Event event) const time_t spoutCastTime = 20; if (IsLurkerCastingSpout(lurker) && it == lurkerSpoutTimer.end()) - lurkerSpoutTimer.emplace(mapId, now + spoutCastTime); + lurkerSpoutTimer.emplace(SSC_MAP_ID, now + spoutCastTime); return false; } @@ -672,58 +719,46 @@ bool LeotherasTheBlindDemonFormTankAttackBossAction::Execute(Event event) MarkTargetWithSquare(bot, leotherasDemon); SetRtiTarget(botAI, "square", leotherasDemon); - if (bot->GetVictim() != leotherasDemon) + if (bot->GetTarget() != leotherasDemon->GetGUID()) { bot->SetTarget(leotherasDemon->GetGUID()); return Attack(leotherasDemon); } - // Fallback logic for if there is no Warlock tank (not recommended) - if (botAI->IsMainTank(bot) && botAI->IsMelee(bot) && leotherasDemon->GetVictim() == bot) - { - float maxMeleeRange = bot->GetMeleeRange(leotherasDemon); - const float meleeRangeBuffer = 0.02f; - float angle = atan2(bot->GetPositionY() - leotherasDemon->GetPositionY(), - bot->GetPositionX() - leotherasDemon->GetPositionX()); - - float targetX = leotherasDemon->GetPositionX() + (maxMeleeRange - meleeRangeBuffer) * std::cos(angle); - float targetY = leotherasDemon->GetPositionY() + (maxMeleeRange - meleeRangeBuffer) * std::sin(angle); - - if (fabs(bot->GetExactDist2d(leotherasDemon) - (maxMeleeRange - meleeRangeBuffer)) > 0.1f) - { - return MoveTo(leotherasDemon->GetMapId(), targetX, targetY, bot->GetPositionZ(), false, false, false, false, - MovementPriority::MOVEMENT_FORCED, true, false); - } - } - return false; } -// Intent is to keep enough distance to be prepared for Whirlwind +// Intent is to keep enough distance from Leotheras and spread to prepare for Whirlwind +// And stay away from the Warlock tank to avoid Chaos Blasts bool LeotherasTheBlindPositionRangedAction::Execute(Event event) { Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); - Unit* leotherasDemon = GetActiveLeotherasDemon(botAI); - Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); - Group* group = bot->GetGroup(); - if (!leotheras || !demonFormTank || !group) + if (!leotheras) return false; const uint32 minInterval = 500; - if (leotheras && bot->GetExactDist2d(leotheras) < 10.0f) + if (bot->GetExactDist2d(leotheras) < 10.0f) return FleePosition(leotheras->GetPosition(), 12.0f, minInterval); - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + if (Group* group = bot->GetGroup()) { - Player* member = ref->GetSource(); - if (!member || member == bot || !member->IsAlive()) - continue; - - if (demonFormTank == member && leotherasDemon && bot->GetExactDist2d(member) < 10.0f) - return FleePosition(member->GetPosition(), 12.0f, minInterval); + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || member == bot || !member->IsAlive()) + continue; - if (bot->GetExactDist2d(member) < 5.0f) - return FleePosition(member->GetPosition(), 6.0f, minInterval); + Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); + Unit* leotherasDemon = GetActiveLeotherasDemon(botAI); + if (demonFormTank && demonFormTank == member && + leotherasDemon) + { + if (bot->GetExactDist2d(member) < 10.0f) + return FleePosition(member->GetPosition(), 12.0f, minInterval); + } + else if (bot->GetExactDist2d(member) < 5.0f) + return FleePosition(member->GetPosition(), 6.0f, minInterval); + } } return false; @@ -731,84 +766,61 @@ bool LeotherasTheBlindPositionRangedAction::Execute(Event event) bool LeotherasTheBlindRunAwayFromWhirlwindAction::Execute(Event event) { - Unit* leotherasHuman = GetLeotherasHuman(botAI); Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI); Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); if (leotherasPhase3Demon && demonFormTank == bot) return false; + Unit* leotherasHuman = GetLeotherasHuman(botAI); if (leotherasHuman) { float currentDistance = bot->GetExactDist2d(leotherasHuman); - const float safeDistance = 15.0f; + const float safeDistance = 20.0f; if (currentDistance < safeDistance) { bot->AttackStop(); bot->InterruptNonMeleeSpells(true); - return MoveAway(leotherasHuman, safeDistance - currentDistance + 10.0f); + return MoveAway(leotherasHuman, safeDistance - currentDistance + 5.0f); } } return false; } -// Applies only if there is no Warlock tank (not recommended) -// Try to keep maximum melee distance to avoid Chaos Blast -bool LeotherasTheBlindDemonFormPositionMeleeAction::Execute(Event event) +// Tanks and healers have no ability to kill their own Inner Demons +// Hunters, Affliction Warlocks, Shadow Priests, and (for some reason) Arcane Mages also struggle +bool LeotherasTheBlindInnerDemonCheatAction::Execute(Event event) { - Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI); - Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI); - if (!leotherasPhase2Demon && !leotherasPhase3Demon) - return false; - - if (!botAI->IsTank(bot) && leotherasPhase2Demon && leotherasPhase2Demon->GetVictim() != bot) + Unit* innerDemon = nullptr; + GuidVector npcs = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); + for (auto const& guid : npcs) { - float maxMeleeRange = bot->GetMeleeRange(leotherasPhase2Demon); - const float meleeRangeBuffer = 0.02f; - float behindAngle = Position::NormalizeOrientation(leotherasPhase2Demon->GetOrientation() + M_PI); - - float targetX = leotherasPhase2Demon->GetPositionX() + (maxMeleeRange - meleeRangeBuffer) * std::cos(behindAngle); - float targetY = leotherasPhase2Demon->GetPositionY() + (maxMeleeRange - meleeRangeBuffer) * std::sin(behindAngle); - - if (fabs(bot->GetExactDist2d(targetX, targetY) - (maxMeleeRange - meleeRangeBuffer)) > 0.1f) - { - return MoveTo(leotherasPhase2Demon->GetMapId(), targetX, targetY, bot->GetPositionZ(), false, false, false, false, - MovementPriority::MOVEMENT_COMBAT, true, false); - } - } - - if (!botAI->IsTank(bot) && leotherasPhase3Demon && leotherasPhase3Demon->GetVictim() != bot) - { - float currentDistance = bot->GetExactDist2d(leotherasPhase3Demon); - const float safeDistance = 10.0f; - if (currentDistance < safeDistance) + Unit* unit = botAI->GetUnit(guid); + Creature* creature = unit ? unit->ToCreature() : nullptr; + if (creature && creature->IsAlive() && creature->GetEntry() == NPC_INNER_DEMON + && creature->GetSummonerGUID() == bot->GetGUID()) { - bot->AttackStop(); - bot->InterruptNonMeleeSpells(true); - return MoveAway(leotherasPhase3Demon, safeDistance - currentDistance + 5.0f); + innerDemon = creature; + break; } } - return false; -} - -bool LeotherasTheBlindInnerDemonCheatAction::Execute(Event event) -{ - Unit* innerDemon = GetFirstAliveUnitByEntry(botAI, NPC_INNER_DEMON); if (innerDemon) { - // Tanks and healers have no ability to kill their own Inner Demons - // Hunters, Affliction Warlocks, Shadow Priests, and (for some reason) Arcane Mages also struggleo uint8 tab = AiFactory::GetPlayerSpecTab(bot); + Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); + if (botAI->IsHeal(bot) || botAI->IsTank(bot) || bot->getClass() == CLASS_HUNTER || (bot->getClass() == CLASS_PRIEST && tab == 2) || (bot->getClass() == CLASS_WARLOCK && tab == 0) || - (bot->getClass() == CLASS_MAGE && tab == 0)) + (bot->getClass() == CLASS_MAGE && tab == 0) || + (demonFormTank && demonFormTank == bot)) { - Unit::DealDamage(bot, innerDemon, innerDemon->GetMaxHealth() / 20, nullptr, + Unit::DealDamage(bot, innerDemon, innerDemon->GetMaxHealth() / 25, nullptr, DIRECT_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, nullptr, false, true); + return true; } } @@ -816,7 +828,7 @@ bool LeotherasTheBlindInnerDemonCheatAction::Execute(Event event) return false; } -// Everybody except the Demon Form tank should focus on Leotheras +// Everybody except the Warlock tank should focus on Leotheras in Phase 3 bool LeotherasTheBlindFinalPhaseAssignDpsPriorityAction::Execute(Event event) { Unit* leotherasHuman = GetLeotherasHuman(botAI); @@ -842,15 +854,9 @@ bool LeotherasTheBlindFinalPhaseAssignDpsPriorityAction::Execute(Event event) float targetX = bot->GetPositionX() + 27.0f * std::cos(angle); float targetY = bot->GetPositionY() + 27.0f * std::sin(angle); - return MoveTo(bot->GetMapId(), targetX, targetY, bot->GetPositionZ(), false, false, false, false, + return MoveTo(SSC_MAP_ID, targetX, targetY, bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_FORCED, true, false); } - else if (botAI->IsTank(bot) && !bot->IsWithinMeleeRange(leotherasHuman)) - { - return MoveTo(leotherasHuman->GetMapId(), leotherasHuman->GetPositionX(), - leotherasHuman->GetPositionY(), leotherasHuman->GetPositionZ(), - false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); - } } return false; @@ -873,22 +879,20 @@ bool LeotherasTheBlindMisdirectBossToDemonFormTankAction::Execute(Event event) } // This does not pause DPS after a Whirlwind, which is also an aggro wipe -// I find another timer for the Whirlwind wipe to be unnecessary bool LeotherasTheBlindManageTimersAndTrackersAction::Execute(Event event) { Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); if (!leotheras) return false; - const uint32 mapId = leotheras->GetMapId(); const time_t now = std::time(nullptr); // Encounter start/reset: clear all timers if (leotheras->HasAura(SPELL_LEOTHERAS_BANISHED)) { - leotherasHumanFormDpsWaitTimer.erase(mapId); - leotherasDemonFormDpsWaitTimer.erase(mapId); - leotherasFinalPhaseDpsWaitTimer.erase(mapId); + leotherasHumanFormDpsWaitTimer.erase(SSC_MAP_ID); + leotherasDemonFormDpsWaitTimer.erase(SSC_MAP_ID); + leotherasFinalPhaseDpsWaitTimer.erase(SSC_MAP_ID); return false; } @@ -897,28 +901,28 @@ bool LeotherasTheBlindManageTimersAndTrackersAction::Execute(Event event) Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI); if (leotherasHuman && !leotherasPhase3Demon) { - leotherasHumanFormDpsWaitTimer.try_emplace(mapId, now); - leotherasDemonFormDpsWaitTimer.erase(mapId); + leotherasHumanFormDpsWaitTimer.try_emplace(SSC_MAP_ID, now); + leotherasDemonFormDpsWaitTimer.erase(SSC_MAP_ID); } // Demon Phase else if (Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI)) { - leotherasDemonFormDpsWaitTimer.try_emplace(mapId, now); - leotherasHumanFormDpsWaitTimer.erase(mapId); + leotherasDemonFormDpsWaitTimer.try_emplace(SSC_MAP_ID, now); + leotherasHumanFormDpsWaitTimer.erase(SSC_MAP_ID); } // Final Phase (<15% HP) else if (leotherasHuman && leotherasPhase3Demon) { - leotherasFinalPhaseDpsWaitTimer.try_emplace(mapId, now); - leotherasHumanFormDpsWaitTimer.erase(mapId); - leotherasDemonFormDpsWaitTimer.erase(mapId); + leotherasFinalPhaseDpsWaitTimer.try_emplace(SSC_MAP_ID, now); + leotherasHumanFormDpsWaitTimer.erase(SSC_MAP_ID); + leotherasDemonFormDpsWaitTimer.erase(SSC_MAP_ID); } return false; } // Fathom-Lord Karathress -// Note: Four tanks are required, but +// Note: Four tanks are required for the full strategy, but // Caribdis hits for nothing so just respec a DPS warrior and put on a shield // Karathress is tanked near his starting position @@ -946,36 +950,31 @@ bool FathomLordKarathressMainTankPositionBossAction::Execute(Event event) float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - return MoveTo(bot->GetMapId(), moveX, moveY, position.GetPositionZ(), false, false, false, true, + return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, true); } } - else if (!bot->IsWithinMeleeRange(karathress)) - { - return MoveTo(karathress->GetMapId(), karathress->GetPositionX(), - karathress->GetPositionY(), karathress->GetPositionZ(), - false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, false); - } return false; } -// Sharkkis is pulled North to the other side of the ramp -bool FathomLordKarathressFirstAssistTankPositionSharkkisAction::Execute(Event event) +// Caribdis is pulled far to the West in the corner +// Best to use a Warrior or Druid tank for interrupts +bool FathomLordKarathressFirstAssistTankPositionCaribdisAction::Execute(Event event) { - Unit* sharkkis = AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis"); - if (!sharkkis) + Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); + if (!caribdis) return false; - MarkTargetWithStar(bot, sharkkis); - SetRtiTarget(botAI, "star", sharkkis); + MarkTargetWithDiamond(bot, caribdis); + SetRtiTarget(botAI, "diamond", caribdis); - if (bot->GetVictim() != sharkkis) - return Attack(sharkkis); + if (bot->GetVictim() != caribdis) + return Attack(caribdis); - if (sharkkis->GetVictim() == bot) + if (caribdis->GetVictim() == bot) { - const Position& position = SharkkisTankPosition; + const Position& position = CaribdisTankPosition; if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 3.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); @@ -985,36 +984,30 @@ bool FathomLordKarathressFirstAssistTankPositionSharkkisAction::Execute(Event ev float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - return MoveTo(bot->GetMapId(), moveX, moveY, position.GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_COMBAT, true, true); + return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT, true, false); } } - else if (!bot->IsWithinMeleeRange(sharkkis)) - { - return MoveTo(sharkkis->GetMapId(), sharkkis->GetPositionX(), - sharkkis->GetPositionY(), sharkkis->GetPositionZ(), - false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, false); - } return false; } -// Tidalvess is pulled Northwest near the pillar -bool FathomLordKarathressSecondAssistTankPositionTidalvessAction::Execute(Event event) +// Sharkkis is pulled North to the other side of the ramp +bool FathomLordKarathressSecondAssistTankPositionSharkkisAction::Execute(Event event) { - Unit* tidalvess = AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess"); - if (!tidalvess) + Unit* sharkkis = AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis"); + if (!sharkkis) return false; - MarkTargetWithCircle(bot, tidalvess); - SetRtiTarget(botAI, "circle", tidalvess); + MarkTargetWithStar(bot, sharkkis); + SetRtiTarget(botAI, "star", sharkkis); - if (bot->GetVictim() != tidalvess) - return Attack(tidalvess); + if (bot->GetVictim() != sharkkis) + return Attack(sharkkis); - if (tidalvess->GetVictim() == bot) + if (sharkkis->GetVictim() == bot) { - const Position& position = TidalvessTankPosition; + const Position& position = SharkkisTankPosition; if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 3.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); @@ -1024,37 +1017,30 @@ bool FathomLordKarathressSecondAssistTankPositionTidalvessAction::Execute(Event float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - return MoveTo(bot->GetMapId(), moveX, moveY, position.GetPositionZ(), false, false, false, true, + return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, true); } } - else if (!bot->IsWithinMeleeRange(tidalvess)) - { - return MoveTo(tidalvess->GetMapId(), tidalvess->GetPositionX(), - tidalvess->GetPositionY(), tidalvess->GetPositionZ(), - false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, false); - } return false; } -// Caribdis is pulled far to the West in the corner -// Best to use a Warrior or Druid tank for interrupts -bool FathomLordKarathressThirdAssistTankPositionCaribdisAction::Execute(Event event) +// Tidalvess is pulled Northwest near the pillar +bool FathomLordKarathressThirdAssistTankPositionTidalvessAction::Execute(Event event) { - Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); - if (!caribdis) + Unit* tidalvess = AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess"); + if (!tidalvess) return false; - MarkTargetWithDiamond(bot, caribdis); - SetRtiTarget(botAI, "diamond", caribdis); + MarkTargetWithCircle(bot, tidalvess); + SetRtiTarget(botAI, "circle", tidalvess); - if (bot->GetVictim() != caribdis) - return Attack(caribdis); + if (bot->GetVictim() != tidalvess) + return Attack(tidalvess); - if (caribdis->GetVictim() == bot) + if (tidalvess->GetVictim() == bot) { - const Position& position = CaribdisTankPosition; + const Position& position = TidalvessTankPosition; if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 3.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); @@ -1064,16 +1050,10 @@ bool FathomLordKarathressThirdAssistTankPositionCaribdisAction::Execute(Event ev float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - return MoveTo(bot->GetMapId(), moveX, moveY, position.GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_COMBAT, true, false); + return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT, true, true); } } - else if (!bot->IsWithinMeleeRange(caribdis)) - { - return MoveTo(caribdis->GetMapId(), caribdis->GetPositionX(), - caribdis->GetPositionY(), caribdis->GetPositionZ(), - false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, false); - } return false; } @@ -1096,13 +1076,14 @@ bool FathomLordKarathressPositionCaribdisTankHealerAction::Execute(Event event) float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - return MoveTo(bot->GetMapId(), moveX, moveY, position.GetPositionZ(), false, false, false, true, + return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, false); } return false; } +// Misdirect priority: (1) Caribdis tank, (2) Tidalvess tank, (3) Sharkkis tank bool FathomLordKarathressMisdirectBossesToTanksAction::Execute(Event event) { Group* group = bot->GetGroup(); @@ -1115,6 +1096,7 @@ bool FathomLordKarathressMisdirectBossesToTanksAction::Execute(Event event) Player* member = ref->GetSource(); if (member && member->IsAlive() && member->getClass() == CLASS_HUNTER && GET_PLAYERBOT_AI(member)) hunters.push_back(member); + if (hunters.size() >= 3) break; } @@ -1139,7 +1121,7 @@ bool FathomLordKarathressMisdirectBossesToTanksAction::Execute(Event event) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (member && member->IsAlive() && GET_PLAYERBOT_AI(member)->IsAssistTankOfIndex(member, 2)) + if (member && member->IsAlive() && botAI->IsAssistTankOfIndex(member, 0)) { tankTarget = member; break; @@ -1152,7 +1134,7 @@ bool FathomLordKarathressMisdirectBossesToTanksAction::Execute(Event event) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (member && member->IsAlive() && GET_PLAYERBOT_AI(member)->IsAssistTankOfIndex(member, 1)) + if (member && member->IsAlive() && botAI->IsAssistTankOfIndex(member, 2)) { tankTarget = member; break; @@ -1165,7 +1147,7 @@ bool FathomLordKarathressMisdirectBossesToTanksAction::Execute(Event event) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (member && member->IsAlive() && GET_PLAYERBOT_AI(member)->IsAssistTankOfIndex(member, 0)) + if (member && member->IsAlive() && botAI->IsAssistTankOfIndex(member, 1)) { tankTarget = member; break; @@ -1190,7 +1172,7 @@ bool FathomLordKarathressMisdirectBossesToTanksAction::Execute(Event event) // Sharkkis down first) bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) { - // Target priority 1: Spitfire Totems for melee + // Target priority 1: Spitfire Totems for melee dps Unit* totem = GetFirstAliveUnitByEntry(botAI, NPC_SPITFIRE_TOTEM); if (totem && botAI->IsMelee(bot) && botAI->IsDps(bot)) { @@ -1210,6 +1192,7 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) Unit* tidalvess = AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess"); if (tidalvess && tidalvess->IsAlive()) { + MarkTargetWithCircle(bot, tidalvess); SetRtiTarget(botAI, "circle", tidalvess); if (bot->GetTarget() != tidalvess->GetGUID()) @@ -1225,6 +1208,7 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); if (botAI->IsRangedDps(bot) && caribdis && caribdis->IsAlive()) { + MarkTargetWithDiamond(bot, caribdis); SetRtiTarget(botAI, "diamond", caribdis); const Position& position = CaribdisRangedDpsPosition; @@ -1237,7 +1221,7 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - return MoveTo(bot->GetMapId(), moveX, moveY, position.GetPositionZ(), false, false, false, true, + return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, false); } @@ -1254,6 +1238,7 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) Unit* sharkkis = AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis"); if (sharkkis && sharkkis->IsAlive()) { + MarkTargetWithStar(bot, sharkkis); SetRtiTarget(botAI, "star", sharkkis); if (bot->GetTarget() != sharkkis->GetGUID()) @@ -1300,6 +1285,7 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); if (karathress && karathress->IsAlive()) { + MarkTargetWithTriangle(bot, karathress); SetRtiTarget(botAI, "triangle", karathress); if (bot->GetTarget() != karathress->GetGUID()) @@ -1318,11 +1304,10 @@ bool FathomLordKarathressManageDpsTimerAction::Execute(Event event) if (!karathress) return false; - const uint32 mapId = karathress->GetMapId(); const time_t now = std::time(nullptr); if (karathress->GetHealth() == karathress->GetMaxHealth()) - karathressDpsWaitTimer.insert_or_assign(mapId, now); + karathressDpsWaitTimer.insert_or_assign(SSC_MAP_ID, now); return false; } @@ -1368,7 +1353,7 @@ bool MorogrimTidewalkerMoveBossToTankPositionAction::Execute(Event event) if (bot->GetVictim() != tidewalker) return Attack(tidewalker); - if (tidewalker->GetVictim() == bot && bot->IsWithinMeleeRange(tidewalker)) + if (tidewalker->GetVictim() == bot) { if (tidewalker->GetHealthPct() > 26.0f) return MoveToPhase1TankPosition(tidewalker); @@ -1392,8 +1377,8 @@ bool MorogrimTidewalkerMoveBossToTankPositionAction::MoveToPhase1TankPosition(Un float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, false, - MovementPriority::MOVEMENT_COMBAT, true, true); + return MoveTo(SSC_MAP_ID, moveX, moveY, phase1.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, true); } return false; @@ -1420,7 +1405,7 @@ bool MorogrimTidewalkerMoveBossToTankPositionAction::MoveToPhase2TankPosition(Un float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, false, + return MoveTo(SSC_MAP_ID, moveX, moveY, transition.GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, true); } else @@ -1438,7 +1423,7 @@ bool MorogrimTidewalkerMoveBossToTankPositionAction::MoveToPhase2TankPosition(Un float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, false, + return MoveTo(SSC_MAP_ID, moveX, moveY, phase2.GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, true); } } @@ -1471,7 +1456,7 @@ bool MorogrimTidewalkerPhase2RepositionRangedAction::Execute(Event event) float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, false, + return MoveTo(SSC_MAP_ID, moveX, moveY, transition.GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); } else @@ -1492,7 +1477,7 @@ bool MorogrimTidewalkerPhase2RepositionRangedAction::Execute(Event event) float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - return MoveTo(bot->GetMapId(), moveX, moveY, bot->GetPositionZ(), false, false, false, false, + return MoveTo(SSC_MAP_ID, moveX, moveY, phase2.GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); } } @@ -1529,7 +1514,7 @@ bool LadyVashjMainTankPositionBossAction::Execute(Event event) if (bot->GetVictim() != vashj) return Attack(vashj); - if (vashj->GetVictim() == bot && bot->IsWithinMeleeRange(vashj)) + if (vashj->GetVictim() == bot) { if (IsLadyVashjInPhase1(botAI)) { @@ -1543,8 +1528,8 @@ bool LadyVashjMainTankPositionBossAction::Execute(Event event) float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - return MoveTo(bot->GetMapId(), moveX, moveY, position.GetPositionZ(), false, false, false, false, - MovementPriority::MOVEMENT_COMBAT, true, true); + return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, true); } } @@ -1577,8 +1562,7 @@ bool LadyVashjPhase1PositionRangedAction::Execute(Event event) Player* member = ref->GetSource(); if (member && member->IsAlive() && GET_PLAYERBOT_AI(member)) { - PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); - if (memberAI->IsRanged(member)) + if (botAI->IsRanged(member)) spreadMembers.push_back(member); } } @@ -1586,7 +1570,7 @@ bool LadyVashjPhase1PositionRangedAction::Execute(Event event) const ObjectGuid guid = bot->GetGUID(); auto itPos = vashjRangedPositions.find(guid); - auto itReached = vashjHasReachedRangedPosition.find(guid); + auto itReached = hasReachedVashjRangedPosition.find(guid); if (itPos == vashjRangedPositions.end()) { auto it = std::find(spreadMembers.begin(), spreadMembers.end(), bot); @@ -1616,22 +1600,22 @@ bool LadyVashjPhase1PositionRangedAction::Execute(Event event) auto res = vashjRangedPositions.emplace(guid, Position(targetX, targetY, tz)); itPos = res.first; - vashjHasReachedRangedPosition.emplace(guid, false); - itReached = vashjHasReachedRangedPosition.find(guid); + hasReachedVashjRangedPosition.emplace(guid, false); + itReached = hasReachedVashjRangedPosition.find(guid); } if (itPos == vashjRangedPositions.end()) return false; Position targetPosition = itPos->second; - if (itReached == vashjHasReachedRangedPosition.end() || !(itReached->second)) + if (itReached == hasReachedVashjRangedPosition.end() || !(itReached->second)) { if (!bot->IsWithinDist2d(targetPosition.GetPositionX(), targetPosition.GetPositionY(), 2.0f)) { - return MoveTo(bot->GetMapId(), targetPosition.GetPositionX(), targetPosition.GetPositionY(), targetPosition.GetPositionZ(), + return MoveTo(SSC_MAP_ID, targetPosition.GetPositionX(), targetPosition.GetPositionY(), targetPosition.GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); } - if (itReached != vashjHasReachedRangedPosition.end()) + if (itReached != hasReachedVashjRangedPosition.end()) itReached->second = true; } @@ -1668,8 +1652,8 @@ bool LadyVashjSetGroundingTotemInMainTankGroupAction::Execute(Event event) float targetX = mainTank->GetPositionX() + 25.0f * std::cos(angle); float targetY = mainTank->GetPositionY() + 25.0f * std::sin(angle); - return MoveTo(mainTank->GetMapId(), targetX, targetY, mainTank->GetPositionZ(), - false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); + return MoveTo(SSC_MAP_ID, targetX, targetY, mainTank->GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); } if (!botAI->HasStrategy("grounding totem", BotState::BOT_STATE_COMBAT)) @@ -1816,26 +1800,10 @@ bool LadyVashjTankAttackAndMoveAwayStriderAction::Execute(Event event) if (strider->GetVictim() == bot) { float currentDistance = bot->GetExactDist2d(vashj); - const float safeDistance = 20.0f; + const float safeDistance = 25.0f; if (currentDistance < safeDistance) return MoveAway(vashj, safeDistance - currentDistance + 5.0f); - - Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI); - Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI); - - // Move the Strider away from the first two passers; the third and fourth passers - // are rarely needed so they are ignored to avoid too many restrictions on movement - for (Player* passer : { firstCorePasser, secondCorePasser }) - { - if (passer && passer != bot) - { - float currentDistFromPasser = bot->GetExactDist2d(passer); - const float safeDistFromPasser = 15.0f; - if (currentDistFromPasser < safeDistFromPasser) - return MoveAway(strider, safeDistFromPasser - currentDistFromPasser + 5.0f); - } - } } return false; @@ -1891,15 +1859,6 @@ bool LadyVashjAssignDpsPriorityAction::Execute(Event event) Unit* strider = nullptr; Unit* sporebat = nullptr; - if (bot->GetVictim() == vashj && (IsLadyVashjInPhase2(botAI) || (IsLadyVashjInPhase3(botAI) && - (enchanted && enchanted->IsAlive() || elite && elite->IsAlive() || strider && strider->IsAlive())))) - { - bot->AttackStop(); - bot->InterruptNonMeleeSpells(true); - bot->SetTarget(ObjectGuid::Empty); - bot->SetSelection(ObjectGuid()); - } - // Search and attack radius are intended to keep bots on the platform (not go down the stairs) const Position& center = VashjPlatformCenterPosition; const float maxSearchRange = botAI->IsRangedDps(bot) ? 60.0f : (botAI->IsMelee(bot) ? 55.0f : 40.0f); @@ -1908,7 +1867,7 @@ bool LadyVashjAssignDpsPriorityAction::Execute(Event event) for (auto guid : attackers) { Unit* unit = botAI->GetUnit(guid); - if (!IsValidPhase2CombatNpc(unit, botAI)) + if (!IsValidLadyVashjCombatNpc(unit, botAI)) continue; float distFromCenter = unit->GetExactDist2d(center.GetPositionX(), center.GetPositionY()); @@ -1992,7 +1951,7 @@ bool LadyVashjAssignDpsPriorityAction::Execute(Event event) else targets = { elite, enchanted, vashj }; } - if (botAI->IsRanged(bot)) + else if (botAI->IsRanged(bot)) { // Hunters will try to kill Toxic Sporebats (in practice, they are generally not in range) if (bot->getClass() == CLASS_HUNTER) @@ -2000,7 +1959,7 @@ bool LadyVashjAssignDpsPriorityAction::Execute(Event event) else targets = { enchanted, strider, elite, vashj }; } - if (botAI->IsMelee(bot) && botAI->IsDps(bot)) + else if (botAI->IsMelee(bot) && botAI->IsDps(bot)) targets = { enchanted, elite, vashj }; else targets = { enchanted, elite, strider, vashj }; @@ -2015,8 +1974,31 @@ bool LadyVashjAssignDpsPriorityAction::Execute(Event event) } } + if (bot->GetVictim() == vashj) + { + if (IsLadyVashjInPhase2(botAI)) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + bot->SetTarget(ObjectGuid::Empty); + bot->SetSelection(ObjectGuid()); + } + else if (IsLadyVashjInPhase3(botAI) && !botAI->IsMainTank(bot)) + { + if ((enchanted && enchanted->IsAlive()) || + (elite && elite->IsAlive()) || + (strider && strider->IsAlive())) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + bot->SetTarget(ObjectGuid::Empty); + bot->SetSelection(ObjectGuid()); + } + } + } + Unit* currentTarget = context->GetValue("current target")->Get(); - if (target && currentTarget == target && IsValidPhase2CombatNpc(currentTarget, botAI)) + if (target && currentTarget == target && IsValidLadyVashjCombatNpc(currentTarget, botAI)) return false; if (target && bot->GetExactDist2d(target) <= maxPursueRange && @@ -2026,7 +2008,7 @@ bool LadyVashjAssignDpsPriorityAction::Execute(Event event) return Attack(target); } - if (currentTarget && (!currentTarget->IsAlive() || !IsValidPhase2CombatNpc(currentTarget, botAI))) + if (currentTarget && (!currentTarget->IsAlive() || !IsValidLadyVashjCombatNpc(currentTarget, botAI))) { context->GetValue("current target")->Set(nullptr); bot->SetTarget(ObjectGuid::Empty); @@ -2036,16 +2018,8 @@ bool LadyVashjAssignDpsPriorityAction::Execute(Event event) // If bots have wandered too far from the center and are not attacking anything, move them back if (!bot->GetVictim()) { - Player* master = botAI->GetMaster(); - Player* designatedLooter = GetDesignatedCoreLooter(bot->GetGroup(), master, botAI); - Player* firstCorePasser = GetFirstTaintedCorePasser(bot->GetGroup(), botAI); - // A bot will not move back to the middle if: - // (1) The designated looter is within 10 yards of a Tainted Elemental, and the bot is - // either the designated looter or the first core passer, or - // (2) It has the Paralyze aura - if (designatedLooter && tainted && designatedLooter->GetExactDist2d(tainted) < 5.0f && - (designatedLooter == bot || (firstCorePasser && firstCorePasser == bot)) || - bot->HasAura(SPELL_PARALYZE)) + Player* designatedLooter = GetDesignatedCoreLooter(bot->GetGroup(), botAI); + if (designatedLooter && tainted && designatedLooter->GetExactDist2d(tainted) < 5.0f) return false; const Position& center = VashjPlatformCenterPosition; @@ -2054,7 +2028,7 @@ bool LadyVashjAssignDpsPriorityAction::Execute(Event event) bot->AttackStop(); bot->InterruptNonMeleeSpells(true); - return MoveInside(bot->GetMapId(), center.GetPositionX(), center.GetPositionY(), + return MoveInside(SSC_MAP_ID, center.GetPositionX(), center.GetPositionY(), center.GetPositionZ(), 30.0f, MovementPriority::MOVEMENT_COMBAT); } } @@ -2075,7 +2049,7 @@ bool LadyVashjTeleportToTaintedElementalAction::Execute(Event event) { bot->AttackStop(); bot->InterruptNonMeleeSpells(true); - bot->TeleportTo(tainted->GetMapId(), tainted->GetPositionX(), tainted->GetPositionY(), + bot->TeleportTo(SSC_MAP_ID, tainted->GetPositionX(), tainted->GetPositionY(), tainted->GetPositionZ(), tainted->GetOrientation()); } @@ -2184,12 +2158,11 @@ bool LadyVashjLootTaintedCoreAction::Execute(Event) bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) { - Player* master = botAI->GetMaster(); Group* group = bot->GetGroup(); - if (!master || !group) + if (!group) return false; - Player* designatedLooter = GetDesignatedCoreLooter(group, master, botAI); + Player* designatedLooter = GetDesignatedCoreLooter(group, botAI); if (!designatedLooter) return false; @@ -2202,23 +2175,31 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) if (!firstCorePasser || !secondCorePasser || !thirdCorePasser || !fourthCorePasser || !closestTrigger) return false; + if (botAI->HasCheat(BotCheatMask::raid)) + { + if (!bot->HasAura(SPELL_FEAR_WARD)) + bot->AddAura(SPELL_FEAR_WARD, bot); + } + + const uint8 neededPassers = ComputeNeededPassers(designatedLooter, closestTrigger); + // Passer order: HealAssistantOfIndex 0, 1, 2, then RangedDpsAssistantOfIndex 0 - if (bot == firstCorePasser && !botAI->HasItemInInventory(ITEM_TAINTED_CORE)) + if (bot == firstCorePasser && !botAI->HasItemInInventory(ITEM_TAINTED_CORE) && neededPassers >= 1) { if (LineUpFirstCorePasser(designatedLooter, closestTrigger)) return true; } - else if (bot == secondCorePasser && !botAI->HasItemInInventory(ITEM_TAINTED_CORE)) + else if (bot == secondCorePasser && !botAI->HasItemInInventory(ITEM_TAINTED_CORE) && neededPassers >= 2) { if (LineUpSecondCorePasser(firstCorePasser, closestTrigger)) return true; } - else if (bot == thirdCorePasser && !botAI->HasItemInInventory(ITEM_TAINTED_CORE)) + else if (bot == thirdCorePasser && !botAI->HasItemInInventory(ITEM_TAINTED_CORE) && neededPassers >= 3) { if (LineUpThirdCorePasser(secondCorePasser, closestTrigger)) return true; } - else if (bot == fourthCorePasser && !botAI->HasItemInInventory(ITEM_TAINTED_CORE)) + else if (bot == fourthCorePasser && !botAI->HasItemInInventory(ITEM_TAINTED_CORE) && neededPassers >= 4) { if (LineUpFourthCorePasser(thirdCorePasser, closestTrigger)) return true; @@ -2232,14 +2213,19 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) { if (IsFirstCorePasserInIntendedPosition(designatedLooter, firstCorePasser, closestTrigger)) { - const ObjectGuid giverGuid = bot->GetGUID(); const time_t now = std::time(nullptr); - auto it = lastImbueAttempt.find(giverGuid); - if (it == lastImbueAttempt.end() || (now - it->second) >= 2) + auto [it, inserted] = lastImbueAttempt.try_emplace(SSC_MAP_ID, now); + if (inserted) + { + botAI->ImbueItem(item, firstCorePasser); + ScheduleStoreCoreAfterImbue(botAI, bot, firstCorePasser); + return true; + } + if ((now - it->second) >= 2) { + it->second = now; botAI->ImbueItem(item, firstCorePasser); - lastImbueAttempt[giverGuid] = now; ScheduleStoreCoreAfterImbue(botAI, bot, firstCorePasser); return true; } @@ -2250,14 +2236,19 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) { if (IsSecondCorePasserInIntendedPosition(firstCorePasser, secondCorePasser, closestTrigger)) { - const ObjectGuid giverGuid = bot->GetGUID(); const time_t now = std::time(nullptr); - auto it = lastImbueAttempt.find(giverGuid); - if (it == lastImbueAttempt.end() || (now - it->second) >= 2) + auto [it, inserted] = lastImbueAttempt.try_emplace(SSC_MAP_ID, now); + if (inserted) + { + botAI->ImbueItem(item, secondCorePasser); + ScheduleStoreCoreAfterImbue(botAI, bot, secondCorePasser); + return true; + } + if ((now - it->second) >= 2) { + it->second = now; botAI->ImbueItem(item, secondCorePasser); - lastImbueAttempt[giverGuid] = now; ScheduleStoreCoreAfterImbue(botAI, bot, secondCorePasser); return true; } @@ -2272,14 +2263,19 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) { if (IsThirdCorePasserInIntendedPosition(secondCorePasser, thirdCorePasser, closestTrigger)) { - const ObjectGuid giverGuid = bot->GetGUID(); const time_t now = std::time(nullptr); - auto it = lastImbueAttempt.find(giverGuid); - if (it == lastImbueAttempt.end() || (now - it->second) >= 2) + auto [it, inserted] = lastImbueAttempt.try_emplace(SSC_MAP_ID, now); + if (inserted) + { + botAI->ImbueItem(item, thirdCorePasser); + ScheduleStoreCoreAfterImbue(botAI, bot, thirdCorePasser); + return true; + } + if ((now - it->second) >= 2) { + it->second = now; botAI->ImbueItem(item, thirdCorePasser); - lastImbueAttempt[giverGuid] = now; ScheduleStoreCoreAfterImbue(botAI, bot, thirdCorePasser); return true; } @@ -2294,14 +2290,19 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) { if (IsFourthCorePasserInIntendedPosition(thirdCorePasser, fourthCorePasser, closestTrigger)) { - const ObjectGuid giverGuid = bot->GetGUID(); const time_t now = std::time(nullptr); - auto it = lastImbueAttempt.find(giverGuid); - if (it == lastImbueAttempt.end() || (now - it->second) >= 2) + auto [it, inserted] = lastImbueAttempt.try_emplace(SSC_MAP_ID, now); + if (inserted) { botAI->ImbueItem(item, fourthCorePasser); - lastImbueAttempt[giverGuid] = now; + ScheduleStoreCoreAfterImbue(botAI, bot, fourthCorePasser); + return true; + } + if ((now - it->second) >= 2) + { + it->second = now; + botAI->ImbueItem(item, fourthCorePasser); ScheduleStoreCoreAfterImbue(botAI, bot, fourthCorePasser); return true; } @@ -2318,6 +2319,25 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) return false; } +// Determine, by distance to the closest trigger, how many core passers are needed +uint8 LadyVashjPassTheTaintedCoreAction::ComputeNeededPassers(Player* designatedLooter, Unit* closestTrigger) +{ + if (!designatedLooter || !closestTrigger) + return static_cast(1); + + const float farDistance = 38.0f; + float dist = designatedLooter->GetExactDist2d(closestTrigger); + + int needed = 1; + if (farDistance > 0.0f) + needed = static_cast(std::ceil(dist / farDistance)); + + if (needed < 1) needed = 1; + if (needed > 4) needed = 4; + + return static_cast(needed); +} + bool LadyVashjPassTheTaintedCoreAction::LineUpFirstCorePasser(Player* designatedLooter, Unit* closestTrigger) { const float centerX = VashjPlatformCenterPosition.GetPositionX(); @@ -2336,7 +2356,7 @@ bool LadyVashjPassTheTaintedCoreAction::LineUpFirstCorePasser(Player* designated bot->AttackStop(); bot->InterruptNonMeleeSpells(true); - return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, + return MoveTo(SSC_MAP_ID, targetX, targetY, targetZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); } @@ -2380,19 +2400,12 @@ bool LadyVashjPassTheTaintedCoreAction::LineUpSecondCorePasser(Player* firstCore bot->AttackStop(); bot->InterruptNonMeleeSpells(false); - return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, + return MoveTo(SSC_MAP_ID, targetX, targetY, targetZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); } bool LadyVashjPassTheTaintedCoreAction::LineUpThirdCorePasser(Player* secondCorePasser, Unit* closestTrigger) { - // Since the third passer is often not needed, wait until the second passer has the core to move - if (!secondCorePasser->HasItemCount(ITEM_TAINTED_CORE, 1, false)) - return false; - - if (secondCorePasser->GetExactDist2d(closestTrigger) <= 2.0f) - return false; - float sx = secondCorePasser->GetPositionX(); float sy = secondCorePasser->GetPositionY(); @@ -2428,19 +2441,12 @@ bool LadyVashjPassTheTaintedCoreAction::LineUpThirdCorePasser(Player* secondCore bot->AttackStop(); bot->InterruptNonMeleeSpells(false); - return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, + return MoveTo(SSC_MAP_ID, targetX, targetY, targetZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); } bool LadyVashjPassTheTaintedCoreAction::LineUpFourthCorePasser(Player* thirdCorePasser, Unit* closestTrigger) { - // Since the fourth passer is often not needed, wait until the third passer has the core to move - if (!thirdCorePasser->HasItemCount(ITEM_TAINTED_CORE, 1, false)) - return false; - - if (thirdCorePasser->GetExactDist2d(closestTrigger) <= 2.0f) - return false; - float sx = thirdCorePasser->GetPositionX(); float sy = thirdCorePasser->GetPositionY(); @@ -2464,7 +2470,7 @@ bool LadyVashjPassTheTaintedCoreAction::LineUpFourthCorePasser(Player* thirdCore bot->AttackStop(); bot->InterruptNonMeleeSpells(false); - return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, + return MoveTo(SSC_MAP_ID, targetX, targetY, targetZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); } @@ -2581,11 +2587,10 @@ void LadyVashjPassTheTaintedCoreAction::ScheduleStoreCoreAfterImbue(PlayerbotAI* if (canStore == EQUIP_ERR_OK) { - Item* created = receiverPlayer->StoreNewItem(dest, ITEM_TAINTED_CORE, true, Item::GenerateItemRandomPropertyId(ITEM_TAINTED_CORE)); + Item* created = receiverPlayer->StoreNewItem(dest, ITEM_TAINTED_CORE, true, + Item::GenerateItemRandomPropertyId(ITEM_TAINTED_CORE)); if (created) { - time_t now = std::time(nullptr); - lastImbueAttempt[giverGuid] = now; intendedLineup.erase(receiverGuid); intendedLineup.erase(giverGuid); } @@ -2610,7 +2615,7 @@ bool LadyVashjPassTheTaintedCoreAction::UseCoreOnNearestGenerator() return false; float dist = bot->GetExactDist2d(generator); - if (dist > 3.0f) + if (dist > 4.5f) return false; if (Item* core = bot->GetItemByEntry(ITEM_TAINTED_CORE)) @@ -2652,6 +2657,7 @@ bool LadyVashjPassTheTaintedCoreAction::UseCoreOnNearestGenerator() } // For dead bots to destroy their cores so the logic can reset for the next attempt +// Or residual cores to be destroyed in Phase 3 bool LadyVashjDestroyTaintedCoreAction::Execute(Event event) { if (Item* core = bot->GetItemByEntry(ITEM_TAINTED_CORE)) @@ -2688,12 +2694,21 @@ bool LadyVashjAvoidToxicSporesAction::Execute(Event event) return false; const Position& vashjCenter = VashjPlatformCenterPosition; - const float maxRadius = 65.0f; + const float maxRadius = 60.0f; Position safestPos = FindSafestNearbyPosition(spores, vashjCenter, maxRadius, hazardRadius); - return MoveTo(bot->GetMapId(), safestPos.GetPositionX(), safestPos.GetPositionY(), - safestPos.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, false); + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + if (vashj && vashj->GetVictim() == bot) + { + return MoveTo(SSC_MAP_ID, safestPos.GetPositionX(), safestPos.GetPositionY(), + safestPos.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, true); + } + else + { + return MoveTo(SSC_MAP_ID, safestPos.GetPositionX(), safestPos.GetPositionY(), + safestPos.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, false); + } } Position LadyVashjAvoidToxicSporesAction::FindSafestNearbyPosition(const std::vector& spores, @@ -2701,7 +2716,7 @@ Position LadyVashjAvoidToxicSporesAction::FindSafestNearbyPosition(const std::ve { const float searchStep = M_PI / 8.0f; const float minDistance = 2.0f; - const float maxDistance = 30.0f; + const float maxDistance = 40.0f; const float distanceStep = 1.0f; Position bestPos; @@ -2719,8 +2734,6 @@ Position LadyVashjAvoidToxicSporesAction::FindSafestNearbyPosition(const std::ve if (vashjCenter.GetExactDist2d(x, y) > maxRadius) continue; - Position testPos(x, y, z); - bool isSafe = true; for (Unit* spore : spores) { @@ -2734,6 +2747,8 @@ Position LadyVashjAvoidToxicSporesAction::FindSafestNearbyPosition(const std::ve if (!isSafe) continue; + Position testPos(x, y, z); + bool pathSafe = IsPathSafeFromSpores(bot->GetPosition(), testPos, spores, hazardRadius); if (pathSafe || !foundSafe) { @@ -2858,6 +2873,7 @@ bool LadyVashjUseFreeActionAbilitiesAction::Execute(Event event) { if (botAI->IsMainTank(member)) mainTankToxic = member; + if (!anyToxic) anyToxic = member; } @@ -2866,6 +2882,7 @@ bool LadyVashjUseFreeActionAbilitiesAction::Execute(Event event) { if (botAI->IsMainTank(member)) mainTankStatic = member; + if (!anyStatic) anyStatic = member; } @@ -2900,7 +2917,7 @@ bool LadyVashjManageTrackersAction::Execute(Event event) return false; vashjRangedPositions.clear(); - vashjHasReachedRangedPosition.clear(); + hasReachedVashjRangedPosition.clear(); lastImbueAttempt.clear(); intendedLineup.clear(); diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h index c64d094fc6..9f8500ecb5 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h @@ -113,6 +113,14 @@ class TheLurkerBelowSpreadRangedAction : public MovementAction bool Execute(Event event) override; }; +class TheLurkerBelowTanksPickUpAddsAction : public AttackAction +{ +public: + TheLurkerBelowTanksPickUpAddsAction(PlayerbotAI* botAI, std::string const name = "the lurker below tanks pick up adds") : AttackAction(botAI, name) {} + + bool Execute(Event event) override; +}; + class TheLurkerBelowManageSpoutTimerAction : public Action { public: @@ -153,14 +161,6 @@ class LeotherasTheBlindRunAwayFromWhirlwindAction : public MovementAction bool Execute(Event event) override; }; -class LeotherasTheBlindDemonFormPositionMeleeAction : public MovementAction -{ -public: - LeotherasTheBlindDemonFormPositionMeleeAction(PlayerbotAI* botAI, std::string const name = "leotheras the blind demon form position melee") : MovementAction(botAI, name) {} - - bool Execute(Event event) override; -}; - class LeotherasTheBlindInnerDemonCheatAction : public AttackAction { public: @@ -200,24 +200,24 @@ class FathomLordKarathressMainTankPositionBossAction : public AttackAction bool Execute(Event event) override; }; -class FathomLordKarathressFirstAssistTankPositionSharkkisAction : public AttackAction +class FathomLordKarathressFirstAssistTankPositionCaribdisAction : public AttackAction { public: - FathomLordKarathressFirstAssistTankPositionSharkkisAction(PlayerbotAI* botAI, std::string const name = "fathom-lord karathress first assist tank position sharkkis") : AttackAction(botAI, name) {} + FathomLordKarathressFirstAssistTankPositionCaribdisAction(PlayerbotAI* botAI, std::string const name = "fathom-lord karathress first assist tank position caribdis") : AttackAction(botAI, name) {} bool Execute(Event event) override; }; -class FathomLordKarathressSecondAssistTankPositionTidalvessAction : public AttackAction +class FathomLordKarathressSecondAssistTankPositionSharkkisAction : public AttackAction { public: - FathomLordKarathressSecondAssistTankPositionTidalvessAction(PlayerbotAI* botAI, std::string const name = "fathom-lord karathress second assist tank position tidalvess") : AttackAction(botAI, name) {} + FathomLordKarathressSecondAssistTankPositionSharkkisAction(PlayerbotAI* botAI, std::string const name = "fathom-lord karathress second assist tank position sharkkis") : AttackAction(botAI, name) {} bool Execute(Event event) override; }; -class FathomLordKarathressThirdAssistTankPositionCaribdisAction : public AttackAction +class FathomLordKarathressThirdAssistTankPositionTidalvessAction : public AttackAction { public: - FathomLordKarathressThirdAssistTankPositionCaribdisAction(PlayerbotAI* botAI, std::string const name = "fathom-lord karathress third assist tank position caribdis") : AttackAction(botAI, name) {} + FathomLordKarathressThirdAssistTankPositionTidalvessAction(PlayerbotAI* botAI, std::string const name = "fathom-lord karathress third assist tank position tidalvess") : AttackAction(botAI, name) {} bool Execute(Event event) override; }; @@ -361,6 +361,7 @@ class LadyVashjPassTheTaintedCoreAction : public MovementAction bool Execute(Event event) override; private: + uint8 ComputeNeededPassers(Player* designatedLooter, Unit* closestTrigger); bool LineUpFirstCorePasser(Player* designatedLooter, Unit* closestTrigger); bool LineUpSecondCorePasser(Player* firstCorePasser, Unit* closestTrigger); bool LineUpThirdCorePasser(Player* secondCorePasser, Unit* closestTrigger); diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp index b6e7170650..823d49db3b 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp @@ -1,7 +1,6 @@ #include "RaidSSCHelpers.h" #include "AiFactory.h" #include "Creature.h" -#include "Group.h" #include "ObjectAccessor.h" #include "Playerbots.h" #include "RtiTargetValue.h" @@ -26,10 +25,10 @@ namespace SerpentShrineCavernHelpers std::unordered_map tidewalkerRangedStep; std::unordered_map vashjRangedPositions; - std::unordered_map vashjHasReachedRangedPosition; + std::unordered_map hasReachedVashjRangedPosition; std::unordered_map intendedLineup; - std::unordered_map lastImbueAttempt; - std::unordered_map lastParalyzeTime; + std::unordered_map lastImbueAttempt; + std::unordered_map lastCoreInInventoryTime; namespace SerpentShrineCavernPositions { @@ -251,22 +250,19 @@ namespace SerpentShrineCavernHelpers if (!group) return nullptr; - Player* mainTankCandidate = nullptr; - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member)) + if (!member || !member->IsAlive()) continue; - if (member->getClass() == CLASS_WARLOCK && GET_PLAYERBOT_AI(member)->HasStrategy("tank", BotState::BOT_STATE_COMBAT)) + PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); + if (member->getClass() == CLASS_WARLOCK && + memberAI && memberAI->HasStrategy("tank", BotState::BOT_STATE_COMBAT)) return member; - - if (!mainTankCandidate && GET_PLAYERBOT_AI(member)->IsMainTank(member)) - mainTankCandidate = member; } - return mainTankCandidate; + return nullptr; } bool IsMainTankInSameSubgroup(Player* bot) @@ -328,7 +324,7 @@ namespace SerpentShrineCavernHelpers return vashjCreature && vashjCreature->GetHealthPct() <= 50.0f && vashjCreature->GetReactState() != REACT_PASSIVE; } - bool IsValidPhase2CombatNpc(Unit* unit, PlayerbotAI* botAI) + bool IsValidLadyVashjCombatNpc(Unit* unit, PlayerbotAI* botAI) { if (!unit || !unit->IsAlive()) return false; @@ -350,8 +346,11 @@ namespace SerpentShrineCavernHelpers return false; } - bool AnyRecentParalyze(Group* group, uint32 mapId, uint32 graceSeconds) + bool AnyRecentCoreInInventory(Group* group, uint32 graceSeconds) { + if (!group) + return false; + const time_t now = std::time(nullptr); for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) @@ -360,15 +359,15 @@ namespace SerpentShrineCavernHelpers if (!member) continue; - if (member->IsAlive() && member->HasAura(SPELL_PARALYZE)) + if (member->IsAlive() && member->HasItemCount(ITEM_TAINTED_CORE, 1, false)) { - lastParalyzeTime[mapId] = now; + lastCoreInInventoryTime[SSC_MAP_ID] = now; return true; } } - auto it = lastParalyzeTime.find(mapId); - if (it != lastParalyzeTime.end()) + auto it = lastCoreInInventoryTime.find(SSC_MAP_ID); + if (it != lastCoreInInventoryTime.end()) { if ((now - it->second) <= static_cast(graceSeconds)) return true; @@ -377,16 +376,24 @@ namespace SerpentShrineCavernHelpers return false; } - Player* GetDesignatedCoreLooter(Group* group, Player* master, PlayerbotAI* botAI) + Player* GetDesignatedCoreLooter(Group* group, PlayerbotAI* botAI) { + if (!group) + return nullptr; + + Player* leader = nullptr; + ObjectGuid leaderGuid = group->GetLeaderGUID(); + if (!leaderGuid.IsEmpty()) + leader = ObjectAccessor::FindPlayer(leaderGuid); + if (!botAI->HasCheat(BotCheatMask::raid)) - return master; + return leader; - Player* fallback = nullptr; + Player* fallback = leader; for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (!member || !member->IsAlive() || member == master) + if (!member || !member->IsAlive() || member == leader) continue; PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); @@ -400,12 +407,15 @@ namespace SerpentShrineCavernHelpers fallback = member; } - return fallback ? fallback : master; + return fallback ? fallback : leader; } Player* GetFirstTaintedCorePasser(Group* group, PlayerbotAI* botAI) { - Player* designatedLooter = GetDesignatedCoreLooter(group, botAI->GetMaster(), botAI); + if (!group) + return nullptr; + + Player* designatedLooter = GetDesignatedCoreLooter(group, botAI); for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { @@ -435,7 +445,10 @@ namespace SerpentShrineCavernHelpers Player* GetSecondTaintedCorePasser(Group* group, PlayerbotAI* botAI) { - Player* designatedLooter = GetDesignatedCoreLooter(group, botAI->GetMaster(), botAI); + if (!group) + return nullptr; + + Player* designatedLooter = GetDesignatedCoreLooter(group, botAI); Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI); for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) @@ -467,7 +480,10 @@ namespace SerpentShrineCavernHelpers Player* GetThirdTaintedCorePasser(Group* group, PlayerbotAI* botAI) { - Player* designatedLooter = GetDesignatedCoreLooter(group, botAI->GetMaster(), botAI); + if (!group) + return nullptr; + + Player* designatedLooter = GetDesignatedCoreLooter(group, botAI); Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI); Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI); @@ -500,7 +516,10 @@ namespace SerpentShrineCavernHelpers Player* GetFourthTaintedCorePasser(Group* group, PlayerbotAI* botAI) { - Player* designatedLooter = GetDesignatedCoreLooter(group, botAI->GetMaster(), botAI); + if (!group) + return nullptr; + + Player* designatedLooter = GetDesignatedCoreLooter(group, botAI); Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI); Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI); Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI); diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h index ee972272c8..2a2cdf3726 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h @@ -12,7 +12,7 @@ namespace SerpentShrineCavernHelpers { enum SerpentShrineCavernSpells { - // Trash Mob + // Trash Mobs SPELL_TOXIC_POOL = 38718, // Hydross the Unstable @@ -42,11 +42,13 @@ namespace SerpentShrineCavernHelpers // Lady Vashj SPELL_FEAR_WARD = 6346, - SPELL_PARALYZE = 38132, SPELL_POISON_BOLT = 38253, SPELL_STATIC_CHARGE = 38280, SPELL_ENTANGLE = 38316, + // Druid + SPELL_TREE_OF_LIFE = 33891, + // Hunter SPELL_MISDIRECTION = 35079, @@ -68,12 +70,14 @@ namespace SerpentShrineCavernHelpers { // Trash Mobs NPC_WATER_ELEMENTAL_TOTEM = 22236, - NPC_RANCID_MUSHROOM = 22250, // Hydross the Unstable NPC_PURE_SPAWN_OF_HYDROSS = 22035, NPC_TAINTED_SPAWN_OF_HYDROSS = 22036, + // The Lurker Below + NPC_COILFANG_GUARDIAN = 21873, + // Leotheras the Blind NPC_LEOTHERAS_THE_BLIND = 21215, NPC_GREYHEART_SPELLBINDER = 21806, @@ -103,6 +107,8 @@ namespace SerpentShrineCavernHelpers ITEM_HEAVY_NETHERWEAVE_NET = 24269, }; + constexpr uint32 SSC_MAP_ID = 548; + extern std::unordered_map hydrossFrostDpsWaitTimer; extern std::unordered_map hydrossNatureDpsWaitTimer; extern std::unordered_map hydrossChangeToFrostPhaseTimer; @@ -121,10 +127,10 @@ namespace SerpentShrineCavernHelpers extern std::unordered_map tidewalkerRangedStep; extern std::unordered_map vashjRangedPositions; - extern std::unordered_map vashjHasReachedRangedPosition; + extern std::unordered_map hasReachedVashjRangedPosition; extern std::unordered_map intendedLineup; - extern std::unordered_map lastImbueAttempt; - extern std::unordered_map lastParalyzeTime; + extern std::unordered_map lastImbueAttempt; + extern std::unordered_map lastCoreInInventoryTime; namespace SerpentShrineCavernPositions { @@ -178,9 +184,9 @@ namespace SerpentShrineCavernHelpers bool IsLadyVashjInPhase1(PlayerbotAI* botAI); bool IsLadyVashjInPhase2(PlayerbotAI* botAI); bool IsLadyVashjInPhase3(PlayerbotAI* botAI); - bool IsValidPhase2CombatNpc(Unit* unit, PlayerbotAI* botAI); - bool AnyRecentParalyze(Group* group, uint32 mapId, uint32 graceSeconds = 3); - Player* GetDesignatedCoreLooter(Group* group, Player* master, PlayerbotAI* botAI); + bool IsValidLadyVashjCombatNpc(Unit* unit, PlayerbotAI* botAI); + bool AnyRecentCoreInInventory(Group* group, uint32 graceSeconds = 3); + Player* GetDesignatedCoreLooter(Group* group, PlayerbotAI* botAI); Player* GetFirstTaintedCorePasser(Group* group, PlayerbotAI* botAI); Player* GetSecondTaintedCorePasser(Group* group, PlayerbotAI* botAI); Player* GetThirdTaintedCorePasser(Group* group, PlayerbotAI* botAI); diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp index 80fb591a9b..87069b612d 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp @@ -87,7 +87,6 @@ float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action) if (dynamic_cast(action)) return 1.0f; - const uint32 mapId = hydross->GetMapId(); const time_t now = std::time(nullptr); const uint8 dpsWaitSeconds = 5; const uint8 phaseChangeWaitSeconds = 6; @@ -103,8 +102,8 @@ float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action) else if (botAI->IsTank(bot)) return 1.0f; - auto itDps = hydrossFrostDpsWaitTimer.find(mapId); - auto itPhase = hydrossChangeToFrostPhaseTimer.find(mapId); + auto itDps = hydrossFrostDpsWaitTimer.find(SSC_MAP_ID); + auto itPhase = hydrossChangeToFrostPhaseTimer.find(SSC_MAP_ID); bool justChanged = (itDps == hydrossFrostDpsWaitTimer.end() || (now - itDps->second) < dpsWaitSeconds); @@ -131,8 +130,8 @@ float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action) else if (botAI->IsTank(bot)) return 1.0f; - auto itDps = hydrossNatureDpsWaitTimer.find(mapId); - auto itPhase = hydrossChangeToNaturePhaseTimer.find(mapId); + auto itDps = hydrossNatureDpsWaitTimer.find(SSC_MAP_ID); + auto itPhase = hydrossChangeToNaturePhaseTimer.find(SSC_MAP_ID); bool justChanged = (itDps == hydrossNatureDpsWaitTimer.end() || (now - itDps->second) < dpsWaitSeconds); @@ -173,18 +172,47 @@ float TheLurkerBelowStayAwayFromSpoutMultiplier::GetValue(Action* action) const time_t now = std::time(nullptr); - auto it = lurkerSpoutTimer.find(lurker->GetMapId()); + auto it = lurkerSpoutTimer.find(SSC_MAP_ID); if (it != lurkerSpoutTimer.end() && it->second > now) { if (dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action)) + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action)) return 0.0f; } return 1.0f; } +// Disable tank assist during Submerge only if there are 3 or more tanks in the raid +float TheLurkerBelowDisableTankAssistMultiplier::GetValue(Action* action) +{ + Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); + if (!lurker || lurker->getStandState() != UNIT_STAND_STATE_SUBMERGED) + return 1.0f; + + Group* group = bot->GetGroup(); + if (!group) + return 1.0f; + + uint8 tankCount = 0; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive()) + continue; + + if (botAI->IsTank(member)) + ++tankCount; + } + + if (tankCount >= 3 && dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + // Leotheras the Blind float LeotherasTheBlindAvoidWhirlwindMultiplier::GetValue(Action* action) @@ -196,11 +224,11 @@ float LeotherasTheBlindAvoidWhirlwindMultiplier::GetValue(Action* action) if (!leotherasHuman->HasAura(SPELL_LEOTHERAS_BANISHED) && (leotherasHuman->HasAura(SPELL_WHIRLWIND) || leotherasHuman->HasAura(SPELL_WHIRLWIND_CHANNEL))) { - if (dynamic_cast(action)) - return 0.0f; - if (!botAI->IsTank(bot)) { + if (dynamic_cast(action)) + return 0.0f; + if (dynamic_cast(action) && !dynamic_cast(action)) return 0.0f; @@ -213,21 +241,19 @@ float LeotherasTheBlindAvoidWhirlwindMultiplier::GetValue(Action* action) // Applies only if there is a Warlock tank float LeotherasTheBlindDisableTankActionsMultiplier::GetValue(Action* action) { - // (1) Multipliers that apply during Phase 2 or 3 Unit* leotherasDemon = GetActiveLeotherasDemon(botAI); if (!leotherasDemon || dynamic_cast(action) || dynamic_cast(action)) return 1.0f; + // (1) Warlock tank will not use Shadow Ward + // Shadow Ward is coded into the Warlock tank strategy (for Twin Emps) but is useless here Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); - if (!demonFormTank || demonFormTank->getClass() != CLASS_WARLOCK) - return 1.0f; - - if (dynamic_cast(action)) + if (demonFormTank && dynamic_cast(action)) return 0.0f; - // (2) Phase 2 only: Tanks other than the Demon Form tank should do absolutely nothing + // (2) Phase 2 only: Tanks other than the Warlock tank should do absolutely nothing Unit* leotherasDemonPhase2 = GetPhase2LeotherasDemon(botAI); if (botAI->IsTank(bot) && bot != demonFormTank && leotherasDemonPhase2) return 0.0f; @@ -235,57 +261,6 @@ float LeotherasTheBlindDisableTankActionsMultiplier::GetValue(Action* action) return 1.0f; } -// Applies only if there is no Warlock tank -float LeotherasTheBlindMeleeTankMaintainDemonFormPositionMultiplier::GetValue(Action* action) -{ - Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); - if (!leotheras) - return 1.0f; - - Unit* leotherasDemon = GetActiveLeotherasDemon(botAI); - if (!leotherasDemon) - return 1.0f; - - Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); - if (demonFormTank && demonFormTank->getClass() != CLASS_WARLOCK) - return 1.0f; - - if (botAI->IsTank(bot) && leotherasDemon->GetVictim() == bot) - { - if (dynamic_cast(action) && - !dynamic_cast(action)) - return 0.0f; - } - - return 1.0f; -} - -// Applies only if there is no Warlock tank -float LeotherasTheBlindDemonFormDisableMeleeActionsMultiplier::GetValue(Action* action) -{ - Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); - if (!leotheras) - return 1.0f; - - Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); - if (demonFormTank && demonFormTank->getClass() == CLASS_WARLOCK) - return 1.0f; - - Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI); - if (!leotherasPhase2Demon || leotherasPhase2Demon->GetVictim() == bot || - bot->HasAura(SPELL_INSIDIOUS_WHISPER)) - return 1.0f; - - if (botAI->IsMelee(bot) && botAI->IsDps(bot)) - { - if (dynamic_cast(action) || (dynamic_cast(action) && - !dynamic_cast(action))) - return 0.0f; - } - - return 1.0f; -} - float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) { Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); @@ -295,7 +270,6 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) if (dynamic_cast(action)) return 1.0f; - const uint32 mapId = leotheras->GetMapId(); const time_t now = std::time(nullptr); const uint8 dpsWaitSecondsPhase1 = 5; @@ -306,7 +280,7 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) if (botAI->IsTank(bot)) return 1.0f; - auto it = leotherasHumanFormDpsWaitTimer.find(mapId); + auto it = leotherasHumanFormDpsWaitTimer.find(SSC_MAP_ID); if (it == leotherasHumanFormDpsWaitTimer.end() || (now - it->second) < dpsWaitSecondsPhase1) { if (dynamic_cast(action) || @@ -323,7 +297,7 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) if (demonFormTank == bot) return 1.0f; - auto it = leotherasDemonFormDpsWaitTimer.find(mapId); + auto it = leotherasDemonFormDpsWaitTimer.find(SSC_MAP_ID); if (it == leotherasDemonFormDpsWaitTimer.end() || (now - it->second) < dpsWaitSecondsPhase2) { if (dynamic_cast(action) || @@ -332,13 +306,13 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) } } - const uint8 dpsWaitSecondsPhase3 = 12; + const uint8 dpsWaitSecondsPhase3 = 8; if (leotherasPhase3Demon) { if (demonFormTank == bot || botAI->IsTank(bot)) return 1.0f; - auto it = leotherasFinalPhaseDpsWaitTimer.find(mapId); + auto it = leotherasFinalPhaseDpsWaitTimer.find(SSC_MAP_ID); if (it == leotherasFinalPhaseDpsWaitTimer.end() || (now - it->second) < dpsWaitSecondsPhase3) { if (dynamic_cast(action) || @@ -420,7 +394,7 @@ float FathomLordKarathressWaitForDpsMultiplier::GetValue(Action* action) const time_t now = std::time(nullptr); const uint8 dpsWaitSeconds = 8; - auto it = karathressDpsWaitTimer.find(karathress->GetMapId()); + auto it = karathressDpsWaitTimer.find(SSC_MAP_ID); if (it == karathressDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds) { if (dynamic_cast(action) || @@ -463,7 +437,7 @@ float MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier::GetValue(Action* act return 1.0f; } -float MorogrimTidewalkerDisablePhase2FleeActionMultiplier::GetValue(Action* action) +float MorogrimTidewalkerDisablePhase2MovementActionsMultiplier::GetValue(Action* action) { Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); if (!tidewalker) @@ -471,7 +445,8 @@ float MorogrimTidewalkerDisablePhase2FleeActionMultiplier::GetValue(Action* acti if (tidewalker->GetHealthPct() < 25.0f) { - if (dynamic_cast(action)) + if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action)) return 0.0f; } @@ -482,15 +457,12 @@ float MorogrimTidewalkerDisablePhase2FleeActionMultiplier::GetValue(Action* acti float LadyVashjDelayBloodlustAndHeroismMultiplier::GetValue(Action* action) { Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - if (!vashj) + if (!vashj || IsLadyVashjInPhase3(botAI)) return 1.0f; - if (!IsLadyVashjInPhase3(botAI)) - { - if (dynamic_cast(action) || - dynamic_cast(action)) - return 0.0f; - } + if (dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; return 1.0f; } @@ -498,7 +470,7 @@ float LadyVashjDelayBloodlustAndHeroismMultiplier::GetValue(Action* action) float LadyVashjStaticChargeStayAwayFromGroupMultiplier::GetValue(Action* action) { Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - if (!vashj || IsLadyVashjInPhase2(botAI)) + if (!vashj) return 1.0f; if (!botAI->IsMainTank(bot) && bot->HasAura(SPELL_STATIC_CHARGE)) @@ -527,6 +499,74 @@ float LadyVashjDoNotLootTheTaintedCoreMultiplier::GetValue(Action* action) return 1.0f; } +float LadyVashjCorePassersPrioritizePositioningMultiplier::GetValue(Action* action) +{ + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + if (!vashj || !IsLadyVashjInPhase2(botAI)) + return 1.0f; + + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 1.0f; + + Group* group = bot->GetGroup(); + if (!group) + return 1.0f; + + Player* designatedLooter = GetDesignatedCoreLooter(group, botAI); + Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI); + Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI); + Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI); + Player* fourthCorePasser = GetFourthTaintedCorePasser(group, botAI); + + auto hasCore = [](Player* player) + { + return player && player->HasItemCount(ITEM_TAINTED_CORE, 1, false); + }; + + if (bot == designatedLooter) + { + if (hasCore(firstCorePasser) || hasCore(secondCorePasser) || + hasCore(thirdCorePasser) || hasCore(fourthCorePasser)) + return 1.0f; + } + else if (bot == firstCorePasser) + { + if (hasCore(secondCorePasser) || hasCore(thirdCorePasser) || + hasCore(fourthCorePasser)) + return 1.0f; + } + else if (bot == secondCorePasser) + { + if (hasCore(thirdCorePasser) || hasCore(fourthCorePasser)) + return 1.0f; + } + else if (bot == thirdCorePasser) + { + if (hasCore(fourthCorePasser)) + return 1.0f; + } + else if (bot != fourthCorePasser) + return 1.0f; + + Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental"); + if (tainted && (bot == firstCorePasser || bot == secondCorePasser)) + { + if (dynamic_cast(action) && + !dynamic_cast(action)) + return 0.0f; + } + + if (AnyRecentCoreInInventory(group)) + { + if (!dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + // All of phase 2 and 3 require a custom movement and targeting system // So the standard target selection system must be disabled float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *action) diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h index f8dac424aa..207b1905a2 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h @@ -38,31 +38,24 @@ class TheLurkerBelowStayAwayFromSpoutMultiplier : public Multiplier virtual float GetValue(Action* action); }; -class LeotherasTheBlindAvoidWhirlwindMultiplier : public Multiplier +class TheLurkerBelowDisableTankAssistMultiplier : public Multiplier { public: - LeotherasTheBlindAvoidWhirlwindMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind avoid whirlwind") {} - virtual float GetValue(Action* action); -}; - -class LeotherasTheBlindDisableTankActionsMultiplier : public Multiplier -{ -public: - LeotherasTheBlindDisableTankActionsMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind disable tank actions") {} + TheLurkerBelowDisableTankAssistMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "the lurker below disable tank assist") {} virtual float GetValue(Action* action); }; -class LeotherasTheBlindMeleeTankMaintainDemonFormPositionMultiplier : public Multiplier +class LeotherasTheBlindAvoidWhirlwindMultiplier : public Multiplier { public: - LeotherasTheBlindMeleeTankMaintainDemonFormPositionMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind melee tank maintain demon form position") {} + LeotherasTheBlindAvoidWhirlwindMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind avoid whirlwind") {} virtual float GetValue(Action* action); }; -class LeotherasTheBlindDemonFormDisableMeleeActionsMultiplier : public Multiplier +class LeotherasTheBlindDisableTankActionsMultiplier : public Multiplier { public: - LeotherasTheBlindDemonFormDisableMeleeActionsMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind demon form disable melee actions") {} + LeotherasTheBlindDisableTankActionsMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind disable tank actions") {} virtual float GetValue(Action* action); }; @@ -122,10 +115,10 @@ class MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier : public Multiplier virtual float GetValue(Action* action); }; -class MorogrimTidewalkerDisablePhase2FleeActionMultiplier : public Multiplier +class MorogrimTidewalkerDisablePhase2MovementActionsMultiplier : public Multiplier { public: - MorogrimTidewalkerDisablePhase2FleeActionMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "morogrim tidewalker disable phase2 flee action") {} + MorogrimTidewalkerDisablePhase2MovementActionsMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "morogrim tidewalker disable phase2 movement actions") {} virtual float GetValue(Action* action); }; @@ -150,6 +143,13 @@ class LadyVashjDoNotLootTheTaintedCoreMultiplier : public Multiplier virtual float GetValue(Action* action); }; +class LadyVashjCorePassersPrioritizePositioningMultiplier : public Multiplier +{ +public: + LadyVashjCorePassersPrioritizePositioningMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj core passers prioritize positioning") {} + virtual float GetValue(Action* action); +}; + class LadyVashjDisableAutomaticTargetingAndMovementModifier : public Multiplier { public: diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp index 7face0f13b..b9c80ed414 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp @@ -10,9 +10,6 @@ void RaidSSCStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("greyheart tidecaller water elemental totem spawned", NextAction::array(0, new NextAction("greyheart tidecaller mark water elemental totem", ACTION_RAID + 1), nullptr) )); - triggers.push_back(new TriggerNode("rancid mushroom spawned", - NextAction::array(0, new NextAction("rancid mushroom move away from mushroom spore cloud", ACTION_EMERGENCY + 10), nullptr) - )); // Hydross the Unstable triggers.push_back(new TriggerNode("hydross the unstable bot is frost tank", @@ -47,6 +44,9 @@ void RaidSSCStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("the lurker below boss casts geyser", NextAction::array(0, new NextAction("the lurker below spread ranged", ACTION_RAID + 1), nullptr) )); + triggers.push_back(new TriggerNode("the lurker below boss is submerged", + NextAction::array(0, new NextAction("the lurker below tanks pick up adds", ACTION_EMERGENCY + 1), nullptr) + )); triggers.push_back(new TriggerNode("the lurker below need to prepare timer for spout", NextAction::array(0, new NextAction("the lurker below manage spout timer", ACTION_EMERGENCY + 10), nullptr) )); @@ -55,8 +55,8 @@ void RaidSSCStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("leotheras the blind boss is inactive", NextAction::array(0, new NextAction("leotheras the blind target spellbinders", ACTION_RAID + 1), nullptr) )); - triggers.push_back(new TriggerNode("leotheras the blind engaged by demon form tank", - NextAction::array(0, new NextAction("leotheras the blind demon form tank attack boss", ACTION_EMERGENCY + 1), nullptr) + triggers.push_back(new TriggerNode("leotheras the blind boss transformed into demon form", + NextAction::array(0, new NextAction("leotheras the blind demon form tank attack boss", ACTION_EMERGENCY + 6), nullptr) )); triggers.push_back(new TriggerNode("leotheras the blind boss engaged by ranged", NextAction::array(0, new NextAction("leotheras the blind position ranged", ACTION_RAID + 1), nullptr) @@ -64,9 +64,6 @@ void RaidSSCStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("leotheras the blind boss channeling whirlwind", NextAction::array(0, new NextAction("leotheras the blind run away from whirlwind", ACTION_EMERGENCY + 1), nullptr) )); - triggers.push_back(new TriggerNode("leotheras the blind demon form engaged by melee without warlock tank", - NextAction::array(0, new NextAction("leotheras the blind demon form position melee", ACTION_EMERGENCY + 1), nullptr) - )); triggers.push_back(new TriggerNode("leotheras the blind inner demon cheat", NextAction::array(0, new NextAction("leotheras the blind inner demon cheat", ACTION_EMERGENCY + 6), nullptr) )); @@ -84,14 +81,14 @@ void RaidSSCStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("fathom-lord karathress boss engaged by main tank", NextAction::array(0, new NextAction("fathom-lord karathress main tank position boss", ACTION_RAID + 1), nullptr) )); - triggers.push_back(new TriggerNode("fathom-lord karathress sharkkis engaged by first assist tank", - NextAction::array(0, new NextAction("fathom-lord karathress first assist tank position sharkkis", ACTION_RAID + 1), nullptr) + triggers.push_back(new TriggerNode("fathom-lord karathress caribdis engaged by first assist tank", + NextAction::array(0, new NextAction("fathom-lord karathress first assist tank position caribdis", ACTION_RAID + 1), nullptr) )); - triggers.push_back(new TriggerNode("fathom-lord karathress tidalvess engaged by second assist tank", - NextAction::array(0, new NextAction("fathom-lord karathress second assist tank position tidalvess", ACTION_RAID + 1), nullptr) + triggers.push_back(new TriggerNode("fathom-lord karathress sharkkis engaged by second assist tank", + NextAction::array(0, new NextAction("fathom-lord karathress second assist tank position sharkkis", ACTION_RAID + 1), nullptr) )); - triggers.push_back(new TriggerNode("fathom-lord karathress caribdis engaged by third assist tank", - NextAction::array(0, new NextAction("fathom-lord karathress third assist tank position caribdis", ACTION_RAID + 1), nullptr) + triggers.push_back(new TriggerNode("fathom-lord karathress tidalvess engaged by third assist tank", + NextAction::array(0, new NextAction("fathom-lord karathress third assist tank position tidalvess", ACTION_RAID + 1), nullptr) )); triggers.push_back(new TriggerNode("fathom-lord karathress caribdis tank needs dedicated healer", NextAction::array(0, new NextAction("fathom-lord karathress position caribdis tank healer", ACTION_RAID + 1), nullptr) @@ -147,9 +144,9 @@ void RaidSSCStrategy::InitTriggers(std::vector& triggers) new NextAction("lady vashj loot tainted core", ACTION_EMERGENCY + 10), nullptr) )); triggers.push_back(new TriggerNode("lady vashj tainted core was looted", - NextAction::array(0, new NextAction("lady vashj pass the tainted core", ACTION_EMERGENCY + 1), nullptr) + NextAction::array(0, new NextAction("lady vashj pass the tainted core", ACTION_EMERGENCY + 10), nullptr) )); - triggers.push_back(new TriggerNode("lady vashj core handler is dead", + triggers.push_back(new TriggerNode("lady vashj tainted core is unusable", NextAction::array(0, new NextAction("lady vashj destroy tainted core", ACTION_EMERGENCY + 1), nullptr) )); triggers.push_back(new TriggerNode("lady vashj determining kill order of adds", @@ -173,10 +170,9 @@ void RaidSSCStrategy::InitMultipliers(std::vector& multipliers) multipliers.push_back(new HydrossTheUnstableWaitForDpsMultiplier(botAI)); multipliers.push_back(new HydrossTheUnstableControlMisdirectionMultiplier(botAI)); multipliers.push_back(new TheLurkerBelowStayAwayFromSpoutMultiplier(botAI)); + multipliers.push_back(new TheLurkerBelowDisableTankAssistMultiplier(botAI)); multipliers.push_back(new LeotherasTheBlindAvoidWhirlwindMultiplier(botAI)); multipliers.push_back(new LeotherasTheBlindDisableTankActionsMultiplier(botAI)); - multipliers.push_back(new LeotherasTheBlindMeleeTankMaintainDemonFormPositionMultiplier(botAI)); - multipliers.push_back(new LeotherasTheBlindDemonFormDisableMeleeActionsMultiplier(botAI)); multipliers.push_back(new LeotherasTheBlindWaitForDpsMultiplier(botAI)); multipliers.push_back(new LeotherasTheBlindDelayBloodlustAndHeroismMultiplier(botAI)); multipliers.push_back(new FathomLordKarathressDisableTankAssistMultiplier(botAI)); @@ -185,9 +181,10 @@ void RaidSSCStrategy::InitMultipliers(std::vector& multipliers) multipliers.push_back(new FathomLordKarathressWaitForDpsMultiplier(botAI)); multipliers.push_back(new FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier(botAI)); multipliers.push_back(new MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier(botAI)); - multipliers.push_back(new MorogrimTidewalkerDisablePhase2FleeActionMultiplier(botAI)); + multipliers.push_back(new MorogrimTidewalkerDisablePhase2MovementActionsMultiplier(botAI)); multipliers.push_back(new LadyVashjDelayBloodlustAndHeroismMultiplier(botAI)); multipliers.push_back(new LadyVashjStaticChargeStayAwayFromGroupMultiplier(botAI)); multipliers.push_back(new LadyVashjDoNotLootTheTaintedCoreMultiplier(botAI)); + multipliers.push_back(new LadyVashjCorePassersPrioritizePositioningMultiplier(botAI)); multipliers.push_back(new LadyVashjDisableAutomaticTargetingAndMovementModifier(botAI)); } diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h index 814bb3ca3a..956bc17213 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h @@ -11,7 +11,6 @@ class RaidSSCTriggerContext : public NamedObjectContext { creators["underbog colossus spawned toxic pool after death"] = &RaidSSCTriggerContext::underbog_colossus_spawned_toxic_pool_after_death; creators["greyheart tidecaller water elemental totem spawned"] = &RaidSSCTriggerContext::greyheart_tidecaller_water_elemental_totem_spawned; - creators["rancid mushroom spawned"] = &RaidSSCTriggerContext::rancid_mushroom_spawned; creators["hydross the unstable bot is frost tank"] = &RaidSSCTriggerContext::hydross_the_unstable_bot_is_frost_tank; creators["hydross the unstable bot is nature tank"] = &RaidSSCTriggerContext::hydross_the_unstable_bot_is_nature_tank; @@ -24,22 +23,22 @@ class RaidSSCTriggerContext : public NamedObjectContext creators["the lurker below spout is active"] = &RaidSSCTriggerContext::the_lurker_below_spout_is_active; creators["the lurker below boss is active for main tank"] = &RaidSSCTriggerContext::the_lurker_below_boss_is_active_for_main_tank; creators["the lurker below boss casts geyser"] = &RaidSSCTriggerContext::the_lurker_below_boss_casts_geyser; + creators["the lurker below boss is submerged"] = &RaidSSCTriggerContext::the_lurker_below_boss_is_submerged; creators["the lurker below need to prepare timer for spout"] = &RaidSSCTriggerContext::the_lurker_below_need_to_prepare_timer_for_spout; creators["leotheras the blind boss is inactive"] = &RaidSSCTriggerContext::leotheras_the_blind_boss_is_inactive; - creators["leotheras the blind engaged by demon form tank"] = &RaidSSCTriggerContext::leotheras_the_blind_engaged_by_demon_form_tank; + creators["leotheras the blind boss transformed into demon form"] = &RaidSSCTriggerContext::leotheras_the_blind_boss_transformed_into_demon_form; creators["leotheras the blind boss engaged by ranged"] = &RaidSSCTriggerContext::leotheras_the_blind_boss_engaged_by_ranged; creators["leotheras the blind boss channeling whirlwind"] = &RaidSSCTriggerContext::leotheras_the_blind_boss_channeling_whirlwind; - creators["leotheras the blind demon form engaged by melee without warlock tank"] = &RaidSSCTriggerContext::leotheras_the_blind_demon_form_engaged_by_melee_without_warlock_tank; creators["leotheras the blind inner demon cheat"] = &RaidSSCTriggerContext::leotheras_the_blind_inner_demon_cheat; creators["leotheras the blind entered final phase"] = &RaidSSCTriggerContext::leotheras_the_blind_entered_final_phase; creators["leotheras the blind demon form tank needs aggro"] = &RaidSSCTriggerContext::leotheras_the_blind_demon_form_tank_needs_aggro; creators["leotheras the blind need to manage timers and trackers"] = &RaidSSCTriggerContext::leotheras_the_blind_need_to_manage_timers_and_trackers; creators["fathom-lord karathress boss engaged by main tank"] = &RaidSSCTriggerContext::fathom_lord_karathress_boss_engaged_by_main_tank; - creators["fathom-lord karathress sharkkis engaged by first assist tank"] = &RaidSSCTriggerContext::fathom_lord_karathress_sharkkis_engaged_by_first_assist_tank; - creators["fathom-lord karathress tidalvess engaged by second assist tank"] = &RaidSSCTriggerContext::fathom_lord_karathress_tidalvess_engaged_by_second_assist_tank; - creators["fathom-lord karathress caribdis engaged by third assist tank"] = &RaidSSCTriggerContext::fathom_lord_karathress_caribdis_engaged_by_third_assist_tank; + creators["fathom-lord karathress caribdis engaged by first assist tank"] = &RaidSSCTriggerContext::fathom_lord_karathress_caribdis_engaged_by_first_assist_tank; + creators["fathom-lord karathress sharkkis engaged by second assist tank"] = &RaidSSCTriggerContext::fathom_lord_karathress_sharkkis_engaged_by_second_assist_tank; + creators["fathom-lord karathress tidalvess engaged by third assist tank"] = &RaidSSCTriggerContext::fathom_lord_karathress_tidalvess_engaged_by_third_assist_tank; creators["fathom-lord karathress caribdis tank needs dedicated healer"] = &RaidSSCTriggerContext::fathom_lord_karathress_caribdis_tank_needs_dedicated_healer; creators["fathom-lord karathress pulling bosses"] = &RaidSSCTriggerContext::fathom_lord_karathress_pulling_bosses; creators["fathom-lord karathress determining kill order"] = &RaidSSCTriggerContext::fathom_lord_karathress_determining_kill_order; @@ -59,7 +58,7 @@ class RaidSSCTriggerContext : public NamedObjectContext creators["lady vashj determining kill order of adds"] = &RaidSSCTriggerContext::lady_vashj_determining_kill_order_of_adds; creators["lady vashj tainted elemental cheat"] = &RaidSSCTriggerContext::lady_vashj_tainted_elemental_cheat; creators["lady vashj tainted core was looted"] = &RaidSSCTriggerContext::lady_vashj_tainted_core_was_looted; - creators["lady vashj core handler is dead"] = &RaidSSCTriggerContext::lady_vashj_core_handler_is_dead; + creators["lady vashj tainted core is unusable"] = &RaidSSCTriggerContext::lady_vashj_tainted_core_is_unusable; creators["lady vashj toxic sporebats are spewing poison clouds"] = &RaidSSCTriggerContext::lady_vashj_toxic_sporebats_are_spewing_poison_clouds; creators["lady vashj bot is entangled in toxic spores or static charge"] = &RaidSSCTriggerContext::lady_vashj_bot_is_entangled_in_toxic_spores_or_static_charge; creators["lady vashj need to manage trackers"] = &RaidSSCTriggerContext::lady_vashj_need_to_manage_trackers; @@ -68,7 +67,6 @@ class RaidSSCTriggerContext : public NamedObjectContext private: static Trigger* underbog_colossus_spawned_toxic_pool_after_death(PlayerbotAI* botAI) { return new UnderbogColossusSpawnedToxicPoolAfterDeathTrigger(botAI); } static Trigger* greyheart_tidecaller_water_elemental_totem_spawned(PlayerbotAI* botAI) { return new GreyheartTidecallerWaterElementalTotemSpawnedTrigger(botAI); } - static Trigger* rancid_mushroom_spawned(PlayerbotAI* botAI) { return new RancidMushroomSpawnedTrigger(botAI); } static Trigger* hydross_the_unstable_bot_is_frost_tank(PlayerbotAI* botAI) { return new HydrossTheUnstableBotIsFrostTankTrigger(botAI); } static Trigger* hydross_the_unstable_bot_is_nature_tank(PlayerbotAI* botAI) { return new HydrossTheUnstableBotIsNatureTankTrigger(botAI); } @@ -81,22 +79,22 @@ class RaidSSCTriggerContext : public NamedObjectContext static Trigger* the_lurker_below_spout_is_active(PlayerbotAI* botAI) { return new TheLurkerBelowSpoutIsActiveTrigger(botAI); } static Trigger* the_lurker_below_boss_is_active_for_main_tank(PlayerbotAI* botAI) { return new TheLurkerBelowBossIsActiveForMainTankTrigger(botAI); } static Trigger* the_lurker_below_boss_casts_geyser(PlayerbotAI* botAI) { return new TheLurkerBelowBossCastsGeyserTrigger(botAI); } + static Trigger* the_lurker_below_boss_is_submerged(PlayerbotAI* botAI) { return new TheLurkerBelowBossIsSubmergedTrigger(botAI); } static Trigger* the_lurker_below_need_to_prepare_timer_for_spout(PlayerbotAI* botAI) { return new TheLurkerBelowNeedToPrepareTimerForSpoutTrigger(botAI); } static Trigger* leotheras_the_blind_boss_is_inactive(PlayerbotAI* botAI) { return new LeotherasTheBlindBossIsInactiveTrigger(botAI); } - static Trigger* leotheras_the_blind_engaged_by_demon_form_tank(PlayerbotAI* botAI) { return new LeotherasTheBlindEngagedByDemonFormTankTrigger(botAI); } + static Trigger* leotheras_the_blind_boss_transformed_into_demon_form(PlayerbotAI* botAI) { return new LeotherasTheBlindBossTransformedIntoDemonFormTrigger(botAI); } static Trigger* leotheras_the_blind_boss_engaged_by_ranged(PlayerbotAI* botAI) { return new LeotherasTheBlindBossEngagedByRangedTrigger(botAI); } static Trigger* leotheras_the_blind_boss_channeling_whirlwind(PlayerbotAI* botAI) { return new LeotherasTheBlindBossChannelingWhirlwindTrigger(botAI); } - static Trigger* leotheras_the_blind_demon_form_engaged_by_melee_without_warlock_tank(PlayerbotAI* botAI) { return new LeotherasTheBlindDemonFormEngagedByMeleeWithoutWarlockTankTrigger(botAI); } static Trigger* leotheras_the_blind_inner_demon_cheat(PlayerbotAI* botAI) { return new LeotherasTheBlindInnerDemonCheatTrigger(botAI); } static Trigger* leotheras_the_blind_entered_final_phase(PlayerbotAI* botAI) { return new LeotherasTheBlindEnteredFinalPhaseTrigger(botAI); } static Trigger* leotheras_the_blind_demon_form_tank_needs_aggro(PlayerbotAI* botAI) { return new LeotherasTheBlindDemonFormTankNeedsAggro(botAI); } static Trigger* leotheras_the_blind_need_to_manage_timers_and_trackers(PlayerbotAI* botAI) { return new LeotherasTheBlindNeedToManageTimersAndTrackersTrigger(botAI); } static Trigger* fathom_lord_karathress_boss_engaged_by_main_tank(PlayerbotAI* botAI) { return new FathomLordKarathressBossEngagedByMainTankTrigger(botAI); } - static Trigger* fathom_lord_karathress_sharkkis_engaged_by_first_assist_tank(PlayerbotAI* botAI) { return new FathomLordKarathressSharkkisEngagedByFirstAssistTankTrigger(botAI); } - static Trigger* fathom_lord_karathress_tidalvess_engaged_by_second_assist_tank(PlayerbotAI* botAI) { return new FathomLordKarathressTidalvessEngagedBySecondAssistTankTrigger(botAI); } - static Trigger* fathom_lord_karathress_caribdis_engaged_by_third_assist_tank(PlayerbotAI* botAI) { return new FathomLordKarathressCaribdisEngagedByThirdAssistTankTrigger(botAI); } + static Trigger* fathom_lord_karathress_caribdis_engaged_by_first_assist_tank(PlayerbotAI* botAI) { return new FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger(botAI); } + static Trigger* fathom_lord_karathress_sharkkis_engaged_by_second_assist_tank(PlayerbotAI* botAI) { return new FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger(botAI); } + static Trigger* fathom_lord_karathress_tidalvess_engaged_by_third_assist_tank(PlayerbotAI* botAI) { return new FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger(botAI); } static Trigger* fathom_lord_karathress_caribdis_tank_needs_dedicated_healer(PlayerbotAI* botAI) { return new FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger(botAI); } static Trigger* fathom_lord_karathress_pulling_bosses(PlayerbotAI* botAI) { return new FathomLordKarathressPullingBossesTrigger(botAI); } static Trigger* fathom_lord_karathress_determining_kill_order(PlayerbotAI* botAI) { return new FathomLordKarathressDeterminingKillOrderTrigger(botAI); } @@ -116,7 +114,7 @@ class RaidSSCTriggerContext : public NamedObjectContext static Trigger* lady_vashj_determining_kill_order_of_adds(PlayerbotAI* botAI) { return new LadyVashjDeterminingKillOrderOfAddsTrigger(botAI); } static Trigger* lady_vashj_tainted_elemental_cheat(PlayerbotAI* botAI) { return new LadyVashjTaintedElementalCheatTrigger(botAI); } static Trigger* lady_vashj_tainted_core_was_looted(PlayerbotAI* botAI) { return new LadyVashjTaintedCoreWasLootedTrigger(botAI); } - static Trigger* lady_vashj_core_handler_is_dead(PlayerbotAI* botAI) { return new LadyVashjCoreHandlerIsDeadTrigger(botAI); } + static Trigger* lady_vashj_tainted_core_is_unusable(PlayerbotAI* botAI) { return new LadyVashjTaintedCoreIsUnusableTrigger(botAI); } static Trigger* lady_vashj_toxic_sporebats_are_spewing_poison_clouds(PlayerbotAI* botAI) { return new LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger(botAI); } static Trigger* lady_vashj_bot_is_entangled_in_toxic_spores_or_static_charge(PlayerbotAI* botAI) { return new LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger(botAI); } static Trigger* lady_vashj_need_to_manage_trackers(PlayerbotAI* botAI) { return new LadyVashjNeedToManageTrackersTrigger(botAI); } diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp index 1e08193aa6..7559b6fa6a 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp @@ -25,12 +25,6 @@ bool GreyheartTidecallerWaterElementalTotemSpawnedTrigger::IsActive() return totem != nullptr; } -bool RancidMushroomSpawnedTrigger::IsActive() -{ - Unit* mushroom = GetFirstAliveUnitByEntry(botAI, NPC_RANCID_MUSHROOM); - return mushroom != nullptr; -} - // Hydross the Unstable bool HydrossTheUnstableBotIsFrostTankTrigger::IsActive() @@ -114,7 +108,7 @@ bool TheLurkerBelowSpoutIsActiveTrigger::IsActive() const time_t now = std::time(nullptr); - auto it = lurkerSpoutTimer.find(lurker->GetMapId()); + auto it = lurkerSpoutTimer.find(SSC_MAP_ID); return it != lurkerSpoutTimer.end() && it->second > now; } @@ -129,7 +123,7 @@ bool TheLurkerBelowBossIsActiveForMainTankTrigger::IsActive() const time_t now = std::time(nullptr); - auto it = lurkerSpoutTimer.find(lurker->GetMapId()); + auto it = lurkerSpoutTimer.find(SSC_MAP_ID); return lurker->getStandState() != UNIT_STAND_STATE_SUBMERGED && (it == lurkerSpoutTimer.end() || it->second <= now); } @@ -145,11 +139,50 @@ bool TheLurkerBelowBossCastsGeyserTrigger::IsActive() const time_t now = std::time(nullptr); - auto it = lurkerSpoutTimer.find(lurker->GetMapId()); + auto it = lurkerSpoutTimer.find(SSC_MAP_ID); return lurker->getStandState() != UNIT_STAND_STATE_SUBMERGED && (it == lurkerSpoutTimer.end() || it->second <= now); } +// Trigger will be active only if there are at least 3 tanks in the raid +bool TheLurkerBelowBossIsSubmergedTrigger::IsActive() +{ + Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); + if (!lurker || lurker->getStandState() != UNIT_STAND_STATE_SUBMERGED) + return false; + + Player* mainTank = nullptr; + Player* firstAssistTank = nullptr; + Player* secondAssistTank = nullptr; + + Group* group = bot->GetGroup(); + if (!group) + return false; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive()) + continue; + + PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); + if (!memberAI) + continue; + + if (!mainTank && memberAI->IsMainTank(member)) + mainTank = member; + else if (!firstAssistTank && memberAI->IsAssistTankOfIndex(member, 0)) + firstAssistTank = member; + else if (!secondAssistTank && memberAI->IsAssistTankOfIndex(member, 1)) + secondAssistTank = member; + } + + if (!mainTank || !firstAssistTank || !secondAssistTank) + return false; + + return bot == mainTank || bot == firstAssistTank || bot == secondAssistTank; +} + bool TheLurkerBelowNeedToPrepareTimerForSpoutTrigger::IsActive() { if (!IsMapIDTimerManager(botAI, bot)) @@ -167,10 +200,10 @@ bool LeotherasTheBlindBossIsInactiveTrigger::IsActive() return spellbinder && spellbinder->IsAlive(); } -bool LeotherasTheBlindEngagedByDemonFormTankTrigger::IsActive() +bool LeotherasTheBlindBossTransformedIntoDemonFormTrigger::IsActive() { Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); - if (demonFormTank && bot != demonFormTank) + if (!demonFormTank || bot != demonFormTank) return false; Unit* leotherasDemon = GetActiveLeotherasDemon(botAI); @@ -200,29 +233,12 @@ bool LeotherasTheBlindBossChannelingWhirlwindTrigger::IsActive() (leotheras->HasAura(SPELL_WHIRLWIND) || leotheras->HasAura(SPELL_WHIRLWIND_CHANNEL)); } -bool LeotherasTheBlindDemonFormEngagedByMeleeWithoutWarlockTankTrigger::IsActive() -{ - Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); - if (demonFormTank && demonFormTank->getClass() == CLASS_WARLOCK) - return false; - - if (!botAI->IsMelee(bot) || botAI->IsMainTank(bot)) - return false; - - Unit* leotherasDemon = GetActiveLeotherasDemon(botAI); - return leotherasDemon != nullptr; -} - bool LeotherasTheBlindInnerDemonCheatTrigger::IsActive() { if (!botAI->HasCheat(BotCheatMask::raid)) return false; - if (!bot->HasAura(SPELL_INSIDIOUS_WHISPER)) - return false; - - Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); - return leotheras != nullptr; + return bot->HasAura(SPELL_INSIDIOUS_WHISPER); } bool LeotherasTheBlindEnteredFinalPhaseTrigger::IsActive() @@ -274,31 +290,31 @@ bool FathomLordKarathressBossEngagedByMainTankTrigger::IsActive() return karathress != nullptr; } -bool FathomLordKarathressSharkkisEngagedByFirstAssistTankTrigger::IsActive() +bool FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger::IsActive() { if (!botAI->IsAssistTankOfIndex(bot, 0)) return false; - Unit* sharkkis = AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis"); - return sharkkis && sharkkis->IsAlive(); + Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); + return caribdis && caribdis->IsAlive(); } -bool FathomLordKarathressTidalvessEngagedBySecondAssistTankTrigger::IsActive() +bool FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger::IsActive() { if (!botAI->IsAssistTankOfIndex(bot, 1)) return false; - Unit* tidalvess = AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess"); - return tidalvess && tidalvess->IsAlive(); + Unit* sharkkis = AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis"); + return sharkkis && sharkkis->IsAlive(); } -bool FathomLordKarathressCaribdisEngagedByThirdAssistTankTrigger::IsActive() +bool FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger::IsActive() { if (!botAI->IsAssistTankOfIndex(bot, 2)) return false; - Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); - return caribdis && caribdis->IsAlive(); + Unit* tidalvess = AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess"); + return tidalvess && tidalvess->IsAlive(); } bool FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger::IsActive() @@ -307,7 +323,28 @@ bool FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger::IsActive() return false; Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); - return caribdis && caribdis->IsAlive(); + if (!caribdis || !caribdis->IsAlive()) + return false; + + Group* group = bot->GetGroup(); + if (!group) + return false; + + Player* firstAssistTank = nullptr; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive()) + continue; + + if (botAI->IsAssistTankOfIndex(member, 0)) + { + firstAssistTank = member; + break; + } + } + + return firstAssistTank != nullptr; } bool FathomLordKarathressPullingBossesTrigger::IsActive() @@ -325,14 +362,14 @@ bool FathomLordKarathressDeterminingKillOrderTrigger::IsActive() if (!karathress) return false; + Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); Unit* sharkkis = AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis"); Unit* tidalvess = AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess"); - Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); return (botAI->IsDps(bot) || - (botAI->IsAssistTankOfIndex(bot, 0) && (!sharkkis || !sharkkis->IsAlive())) || - (botAI->IsAssistTankOfIndex(bot, 1) && (!tidalvess || !tidalvess->IsAlive())) || - (botAI->IsAssistTankOfIndex(bot, 2) && (!caribdis || !caribdis->IsAlive()))); + (botAI->IsAssistTankOfIndex(bot, 0) && (!caribdis || !caribdis->IsAlive())) || + (botAI->IsAssistTankOfIndex(bot, 1) && (!sharkkis || !sharkkis->IsAlive())) || + (botAI->IsAssistTankOfIndex(bot, 2) && (!tidalvess || !tidalvess->IsAlive()))); } bool FathomLordKarathressTanksNeedToEstablishAggroTrigger::IsActive() @@ -401,8 +438,7 @@ bool LadyVashjBossEngagedByRangedInPhase1Trigger::IsActive() bool LadyVashjCastsShockBlastOnHighestAggroTrigger::IsActive() { - uint8 tab = AiFactory::GetPlayerSpecTab(bot); - if (bot->getClass() != CLASS_SHAMAN || tab != 2) + if (bot->getClass() != CLASS_SHAMAN) return false; Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); @@ -436,7 +472,8 @@ bool LadyVashjPullingBossInPhase1AndPhase3Trigger::IsActive() Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); return vashj && ((vashj->GetHealthPct() <= 100.0f && vashj->GetHealthPct() > 90.0f) || - (!vashj->HasUnitState(UNIT_STATE_ROOT) && vashj->GetHealthPct() <= 50.0f && vashj->GetHealthPct() > 40.0f)); + (!vashj->HasUnitState(UNIT_STATE_ROOT) && vashj->GetHealthPct() <= 50.0f && + vashj->GetHealthPct() > 40.0f)); } bool LadyVashjCoilfangStriderIsApproachingTrigger::IsActive() @@ -490,11 +527,10 @@ bool LadyVashjTaintedElementalCheatTrigger::IsActive() return false; Group* group = bot->GetGroup(); - Player* master = botAI->GetMaster(); - if (!group || !master) + if (!group) return false; - Player* designatedLooter = GetDesignatedCoreLooter(group, master, botAI); + Player* designatedLooter = GetDesignatedCoreLooter(group, botAI); return (designatedLooter && designatedLooter == bot && !bot->HasItemCount(ITEM_TAINTED_CORE, 1, false)); } @@ -508,27 +544,31 @@ bool LadyVashjTaintedCoreWasLootedTrigger::IsActive() if (!IsLadyVashjInPhase2(botAI)) return false; - Player* master = botAI->GetMaster(); Group* group = bot->GetGroup(); - if (!master || !group) + if (!group) return false; - Player* designatedLooter = GetDesignatedCoreLooter(group, master, botAI); + Player* designatedLooter = GetDesignatedCoreLooter(group, botAI); Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI); Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI); Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI); Player* fourthCorePasser = GetFourthTaintedCorePasser(group, botAI); - auto hasCore = [](Player* p) -> bool { return p && p->HasItemCount(ITEM_TAINTED_CORE, 1, false); }; + auto hasCore = [](Player* player) -> bool + { + return player && player->HasItemCount(ITEM_TAINTED_CORE, 1, false); + }; if (bot == designatedLooter) { - if (hasCore(firstCorePasser) || hasCore(secondCorePasser) || hasCore(thirdCorePasser) || hasCore(fourthCorePasser)) + if (hasCore(firstCorePasser) || hasCore(secondCorePasser) || + hasCore(thirdCorePasser) || hasCore(fourthCorePasser)) return false; } else if (bot == firstCorePasser) { - if (hasCore(secondCorePasser) || hasCore(thirdCorePasser) || hasCore(fourthCorePasser)) + if (hasCore(secondCorePasser) || hasCore(thirdCorePasser) || + hasCore(fourthCorePasser)) return false; } else if (bot == secondCorePasser) @@ -541,27 +581,32 @@ bool LadyVashjTaintedCoreWasLootedTrigger::IsActive() if (hasCore(fourthCorePasser)) return false; } + else if (bot != fourthCorePasser) + return false; - if (AnyRecentParalyze(group, vashj->GetMapId())) + if (AnyRecentCoreInInventory(group)) return true; + // First and second passers move to positions as soon as the elemental appears Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental"); - if (tainted && designatedLooter->GetExactDist2d(tainted) < 5.0f) + if (tainted && (bot == firstCorePasser || bot == secondCorePasser)) return true; return false; } -bool LadyVashjCoreHandlerIsDeadTrigger::IsActive() +bool LadyVashjTaintedCoreIsUnusableTrigger::IsActive() { - Player* master = botAI->GetMaster(); + if (IsLadyVashjInPhase3(botAI)) + return true; + Group* group = bot->GetGroup(); - if (!master || !group) + if (!group) return false; Player* coreHandlers[] = { - GetDesignatedCoreLooter(group, master, botAI), + GetDesignatedCoreLooter(group, botAI), GetFirstTaintedCorePasser(group, botAI), GetSecondTaintedCorePasser(group, botAI), GetThirdTaintedCorePasser(group, botAI), diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h index 3d31af95b8..9659071af5 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h @@ -17,13 +17,6 @@ class GreyheartTidecallerWaterElementalTotemSpawnedTrigger : public Trigger bool IsActive() override; }; -class RancidMushroomSpawnedTrigger : public Trigger -{ -public: - RancidMushroomSpawnedTrigger(PlayerbotAI* botAI) : Trigger(botAI, "rancid mushroom spawned") {} - bool IsActive() override; -}; - class HydrossTheUnstableBotIsFrostTankTrigger : public Trigger { public: @@ -94,6 +87,13 @@ class TheLurkerBelowBossCastsGeyserTrigger : public Trigger bool IsActive() override; }; +class TheLurkerBelowBossIsSubmergedTrigger : public Trigger +{ +public: + TheLurkerBelowBossIsSubmergedTrigger(PlayerbotAI* botAI) : Trigger(botAI, "the lurker below boss is submerged") {} + bool IsActive() override; +}; + class TheLurkerBelowNeedToPrepareTimerForSpoutTrigger : public Trigger { public: @@ -115,10 +115,10 @@ class LeotherasTheBlindHumanFormEngagedByMainTankTrigger : public Trigger bool IsActive() override; }; -class LeotherasTheBlindEngagedByDemonFormTankTrigger : public Trigger +class LeotherasTheBlindBossTransformedIntoDemonFormTrigger : public Trigger { public: - LeotherasTheBlindEngagedByDemonFormTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind engaged by demon form tank") {} + LeotherasTheBlindBossTransformedIntoDemonFormTrigger(PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind boss transformed into demon form") {} bool IsActive() override; }; @@ -136,13 +136,6 @@ class LeotherasTheBlindBossChannelingWhirlwindTrigger : public Trigger bool IsActive() override; }; -class LeotherasTheBlindDemonFormEngagedByMeleeWithoutWarlockTankTrigger : public Trigger -{ -public: - LeotherasTheBlindDemonFormEngagedByMeleeWithoutWarlockTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind demon form engaged by melee without warlock tank") {} - bool IsActive() override; -}; - class LeotherasTheBlindInnerDemonCheatTrigger : public Trigger { public: @@ -178,24 +171,24 @@ class FathomLordKarathressBossEngagedByMainTankTrigger : public Trigger bool IsActive() override; }; -class FathomLordKarathressSharkkisEngagedByFirstAssistTankTrigger : public Trigger +class FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger : public Trigger { public: - FathomLordKarathressSharkkisEngagedByFirstAssistTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress sharkkis engaged by first assist tank") {} + FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress caribdis engaged by first assist tank") {} bool IsActive() override; }; -class FathomLordKarathressTidalvessEngagedBySecondAssistTankTrigger : public Trigger +class FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger : public Trigger { public: - FathomLordKarathressTidalvessEngagedBySecondAssistTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress tidalvess engaged by second assist tank") {} + FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress sharkkis engaged by second assist tank") {} bool IsActive() override; }; -class FathomLordKarathressCaribdisEngagedByThirdAssistTankTrigger : public Trigger +class FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger : public Trigger { public: - FathomLordKarathressCaribdisEngagedByThirdAssistTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress caribdis engaged by third assist tank") {} + FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress tidalvess engaged by third assist tank") {} bool IsActive() override; }; @@ -318,10 +311,10 @@ class LadyVashjTaintedCoreWasLootedTrigger : public Trigger bool IsActive() override; }; -class LadyVashjCoreHandlerIsDeadTrigger : public Trigger +class LadyVashjTaintedCoreIsUnusableTrigger : public Trigger { public: - LadyVashjCoreHandlerIsDeadTrigger(PlayerbotAI* botAI) : Trigger(botAI, "lady vashj core handler is dead") {} + LadyVashjTaintedCoreIsUnusableTrigger(PlayerbotAI* botAI) : Trigger(botAI, "lady vashj tainted core is unusable") {} bool IsActive() override; }; From f0d87e8472cc83f450423ddc4007670f1f7e16fa Mon Sep 17 00:00:00 2001 From: crow Date: Mon, 8 Dec 2025 02:28:29 -0600 Subject: [PATCH 04/25] numerous code and strategy changes --- .../RaidSSCActionContext.h | 422 ++++++--- .../serpentshrinecavern/RaidSSCActions.cpp | 855 +++++++++--------- .../serpentshrinecavern/RaidSSCActions.h | 181 ++-- .../serpentshrinecavern/RaidSSCHelpers.cpp | 83 +- .../serpentshrinecavern/RaidSSCHelpers.h | 37 +- .../RaidSSCMultipliers.cpp | 149 +-- .../serpentshrinecavern/RaidSSCMultipliers.h | 74 +- .../serpentshrinecavern/RaidSSCStrategy.cpp | 8 +- .../RaidSSCTriggerContext.h | 408 ++++++--- .../serpentshrinecavern/RaidSSCTriggers.cpp | 50 +- .../serpentshrinecavern/RaidSSCTriggers.h | 154 +++- 11 files changed, 1490 insertions(+), 931 deletions(-) diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h b/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h index 0fbb75fc2f..0655d1f090 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h @@ -9,119 +9,321 @@ class RaidSSCActionContext : public NamedObjectContext public: RaidSSCActionContext() { - creators["underbog colossus escape toxic pool"] = &RaidSSCActionContext::underbog_colossus_escape_toxic_pool; - creators["greyheart tidecaller mark water elemental totem"] = &RaidSSCActionContext::greyheart_tidecaller_mark_water_elemental_totem; - - creators["hydross the unstable position frost tank"] = &RaidSSCActionContext::hydross_the_unstable_position_frost_tank; - creators["hydross the unstable position nature tank"] = &RaidSSCActionContext::hydross_the_unstable_position_nature_tank; - creators["hydross the unstable prioritize elemental adds"] = &RaidSSCActionContext::hydross_the_unstable_prioritize_elemental_adds; - creators["hydross the unstable frost phase spread out"] = &RaidSSCActionContext::hydross_the_unstable_frost_phase_spread_out; - creators["hydross the unstable misdirect boss to tank"] = &RaidSSCActionContext::hydross_the_unstable_misdirect_boss_to_tank; - creators["hydross the unstable stop dps upon phase change"] = &RaidSSCActionContext::hydross_the_unstable_stop_dps_upon_phase_change; - creators["hydross the unstable manage timers"] = &RaidSSCActionContext::hydross_the_unstable_manage_timers; - - creators["the lurker below run around behind boss"] = &RaidSSCActionContext::the_lurker_below_run_around_behind_boss; - creators["the lurker below position main tank"] = &RaidSSCActionContext::the_lurker_below_position_main_tank; - creators["the lurker below spread ranged"] = &RaidSSCActionContext::the_lurker_below_spread_ranged; - creators["the lurker below tanks pick up adds"] = &RaidSSCActionContext::the_lurker_below_tanks_pick_up_adds; - creators["the lurker below manage spout timer"] = &RaidSSCActionContext::the_lurker_below_manage_spout_timer; - - creators["leotheras the blind target spellbinders"] = &RaidSSCActionContext::leotheras_the_blind_target_spellbinders; - creators["leotheras the blind demon form tank attack boss"] = &RaidSSCActionContext::leotheras_the_blind_demon_form_tank_attack_boss; - creators["leotheras the blind position ranged"] = &RaidSSCActionContext::leotheras_the_blind_position_ranged; - creators["leotheras the blind run away from whirlwind"] = &RaidSSCActionContext::leotheras_the_blind_run_away_from_whirlwind; - creators["leotheras the blind inner demon cheat"] = &RaidSSCActionContext::leotheras_the_blind_inner_demon_cheat; - creators["leotheras the blind final phase assign dps priority"] = &RaidSSCActionContext::leotheras_the_blind_final_phase_assign_dps_priority; - creators["leotheras the blind misdirect boss to demon form tank"] = &RaidSSCActionContext::leotheras_the_blind_misdirect_boss_to_demon_form_tank; - creators["leotheras the blind manage timers and trackers"] = &RaidSSCActionContext::leotheras_the_blind_manage_timers_and_trackers; - - creators["fathom-lord karathress main tank position boss"] = &RaidSSCActionContext::fathom_lord_karathress_main_tank_position_boss; - creators["fathom-lord karathress first assist tank position caribdis"] = &RaidSSCActionContext::fathom_lord_karathress_first_assist_tank_position_caribdis; - creators["fathom-lord karathress second assist tank position sharkkis"] = &RaidSSCActionContext::fathom_lord_karathress_second_assist_tank_position_sharkkis; - creators["fathom-lord karathress third assist tank position tidalvess"] = &RaidSSCActionContext::fathom_lord_karathress_third_assist_tank_position_tidalvess; - creators["fathom-lord karathress position caribdis tank healer"] = &RaidSSCActionContext::fathom_lord_karathress_position_caribdis_tank_healer; - creators["fathom-lord karathress misdirect bosses to tanks"] = &RaidSSCActionContext::fathom_lord_karathress_misdirect_bosses_to_tanks; - creators["fathom-lord karathress assign dps priority"] = &RaidSSCActionContext::fathom_lord_karathress_assign_dps_priority; - creators["fathom-lord karathress manage dps timer"] = &RaidSSCActionContext::fathom_lord_karathress_manage_dps_timer; - - creators["morogrim tidewalker misdirect boss to main tank"] = &RaidSSCActionContext::morogrim_tidewalker_misdirect_boss_to_main_tank; - creators["morogrim tidewalker move boss to tank position"] = &RaidSSCActionContext::morogrim_tidewalker_move_boss_to_tank_position; - creators["morogrim tidewalker phase 2 reposition ranged"] = &RaidSSCActionContext::morogrim_tidewalker_phase_2_reposition_ranged; - creators["morogrim tidewalker reset phase transition steps"] = &RaidSSCActionContext::morogrim_tidewalker_reset_phase_transition_steps; - - creators["lady vashj main tank position boss"] = &RaidSSCActionContext::lady_vashj_main_tank_position_boss; - creators["lady vashj phase 1 position ranged"] = &RaidSSCActionContext::lady_vashj_phase_1_position_ranged; - creators["lady vashj set grounding totem in main tank group"] = &RaidSSCActionContext::lady_vashj_set_grounding_totem_in_main_tank_group; - creators["lady vashj static charge move away from group"] = &RaidSSCActionContext::lady_vashj_static_charge_move_away_from_group; - creators["lady vashj misdirect boss to main tank"] = &RaidSSCActionContext::lady_vashj_misdirect_boss_to_main_tank; - creators["lady vashj misdirect strider to first assist tank"] = &RaidSSCActionContext::lady_vashj_misdirect_strider_to_first_assist_tank; - creators["lady vashj tank attack and move away strider"] = &RaidSSCActionContext::lady_vashj_tank_attack_and_move_away_strider; - creators["lady vashj assign dps priority"] = &RaidSSCActionContext::lady_vashj_assign_dps_priority; - creators["lady vashj loot tainted core"] = &RaidSSCActionContext::lady_vashj_loot_tainted_core; - creators["lady vashj teleport to tainted elemental"] = &RaidSSCActionContext::lady_vashj_teleport_to_tainted_elemental; - creators["lady vashj pass the tainted core"] = &RaidSSCActionContext::lady_vashj_pass_the_tainted_core; - creators["lady vashj destroy tainted core"] = &RaidSSCActionContext::lady_vashj_destroy_tainted_core; - creators["lady vashj avoid toxic spores"] = &RaidSSCActionContext::lady_vashj_avoid_toxic_spores; - creators["lady vashj use free action abilities"] = &RaidSSCActionContext::lady_vashj_use_free_action_abilities; - creators["lady vashj manage trackers"] = &RaidSSCActionContext::lady_vashj_manage_trackers; + // Trash + creators["underbog colossus escape toxic pool"] = + &RaidSSCActionContext::underbog_colossus_escape_toxic_pool; + + creators["greyheart tidecaller mark water elemental totem"] = + &RaidSSCActionContext::greyheart_tidecaller_mark_water_elemental_totem; + + // Hydross the Unstable + creators["hydross the unstable position frost tank"] = + &RaidSSCActionContext::hydross_the_unstable_position_frost_tank; + + creators["hydross the unstable position nature tank"] = + &RaidSSCActionContext::hydross_the_unstable_position_nature_tank; + + creators["hydross the unstable prioritize elemental adds"] = + &RaidSSCActionContext::hydross_the_unstable_prioritize_elemental_adds; + + creators["hydross the unstable frost phase spread out"] = + &RaidSSCActionContext::hydross_the_unstable_frost_phase_spread_out; + + creators["hydross the unstable misdirect boss to tank"] = + &RaidSSCActionContext::hydross_the_unstable_misdirect_boss_to_tank; + + creators["hydross the unstable stop dps upon phase change"] = + &RaidSSCActionContext::hydross_the_unstable_stop_dps_upon_phase_change; + + creators["hydross the unstable manage timers"] = + &RaidSSCActionContext::hydross_the_unstable_manage_timers; + + // The Lurker Below + creators["the lurker below run around behind boss"] = + &RaidSSCActionContext::the_lurker_below_run_around_behind_boss; + + creators["the lurker below position main tank"] = + &RaidSSCActionContext::the_lurker_below_position_main_tank; + + creators["the lurker below spread ranged"] = + &RaidSSCActionContext::the_lurker_below_spread_ranged; + + creators["the lurker below tanks pick up adds"] = + &RaidSSCActionContext::the_lurker_below_tanks_pick_up_adds; + + creators["the lurker below manage spout timer"] = + &RaidSSCActionContext::the_lurker_below_manage_spout_timer; + + // Leotheras the Blind + creators["leotheras the blind target spellbinders"] = + &RaidSSCActionContext::leotheras_the_blind_target_spellbinders; + + creators["leotheras the blind demon form tank attack boss"] = + &RaidSSCActionContext::leotheras_the_blind_demon_form_tank_attack_boss; + + creators["leotheras the blind position ranged"] = + &RaidSSCActionContext::leotheras_the_blind_position_ranged; + + creators["leotheras the blind run away from whirlwind"] = + &RaidSSCActionContext::leotheras_the_blind_run_away_from_whirlwind; + + creators["leotheras the blind melee dps run away from boss"] = + &RaidSSCActionContext::leotheras_the_blind_melee_dps_run_away_from_boss; + + creators["leotheras the blind inner demon cheat"] = + &RaidSSCActionContext::leotheras_the_blind_inner_demon_cheat; + + creators["leotheras the blind final phase assign dps priority"] = + &RaidSSCActionContext::leotheras_the_blind_final_phase_assign_dps_priority; + + creators["leotheras the blind misdirect boss to demon form tank"] = + &RaidSSCActionContext::leotheras_the_blind_misdirect_boss_to_demon_form_tank; + + creators["leotheras the blind manage dps wait timers"] = + &RaidSSCActionContext::leotheras_the_blind_manage_dps_wait_timers; + + // Fathom-Lord Karathress + creators["fathom-lord karathress main tank position boss"] = + &RaidSSCActionContext::fathom_lord_karathress_main_tank_position_boss; + + creators["fathom-lord karathress first assist tank position caribdis"] = + &RaidSSCActionContext::fathom_lord_karathress_first_assist_tank_position_caribdis; + + creators["fathom-lord karathress second assist tank position sharkkis"] = + &RaidSSCActionContext::fathom_lord_karathress_second_assist_tank_position_sharkkis; + + creators["fathom-lord karathress third assist tank position tidalvess"] = + &RaidSSCActionContext::fathom_lord_karathress_third_assist_tank_position_tidalvess; + + creators["fathom-lord karathress position caribdis tank healer"] = + &RaidSSCActionContext::fathom_lord_karathress_position_caribdis_tank_healer; + + creators["fathom-lord karathress misdirect bosses to tanks"] = + &RaidSSCActionContext::fathom_lord_karathress_misdirect_bosses_to_tanks; + + creators["fathom-lord karathress assign dps priority"] = + &RaidSSCActionContext::fathom_lord_karathress_assign_dps_priority; + + creators["fathom-lord karathress manage dps timer"] = + &RaidSSCActionContext::fathom_lord_karathress_manage_dps_timer; + + // Morogrim Tidewalker + creators["morogrim tidewalker misdirect boss to main tank"] = + &RaidSSCActionContext::morogrim_tidewalker_misdirect_boss_to_main_tank; + + creators["morogrim tidewalker move boss to tank position"] = + &RaidSSCActionContext::morogrim_tidewalker_move_boss_to_tank_position; + + creators["morogrim tidewalker phase 2 reposition ranged"] = + &RaidSSCActionContext::morogrim_tidewalker_phase_2_reposition_ranged; + + creators["morogrim tidewalker reset phase transition steps"] = + &RaidSSCActionContext::morogrim_tidewalker_reset_phase_transition_steps; + + // Lady Vashj + creators["lady vashj main tank position boss"] = + &RaidSSCActionContext::lady_vashj_main_tank_position_boss; + + creators["lady vashj phase 1 position ranged"] = + &RaidSSCActionContext::lady_vashj_phase_1_position_ranged; + + creators["lady vashj set grounding totem in main tank group"] = + &RaidSSCActionContext::lady_vashj_set_grounding_totem_in_main_tank_group; + + creators["lady vashj static charge move away from group"] = + &RaidSSCActionContext::lady_vashj_static_charge_move_away_from_group; + + creators["lady vashj misdirect boss to main tank"] = + &RaidSSCActionContext::lady_vashj_misdirect_boss_to_main_tank; + + creators["lady vashj misdirect strider to first assist tank"] = + &RaidSSCActionContext::lady_vashj_misdirect_strider_to_first_assist_tank; + + creators["lady vashj tank attack and move away strider"] = + &RaidSSCActionContext::lady_vashj_tank_attack_and_move_away_strider; + + creators["lady vashj assign dps priority"] = + &RaidSSCActionContext::lady_vashj_assign_dps_priority; + + creators["lady vashj loot tainted core"] = + &RaidSSCActionContext::lady_vashj_loot_tainted_core; + + creators["lady vashj teleport to tainted elemental"] = + &RaidSSCActionContext::lady_vashj_teleport_to_tainted_elemental; + + creators["lady vashj pass the tainted core"] = + &RaidSSCActionContext::lady_vashj_pass_the_tainted_core; + + creators["lady vashj destroy tainted core"] = + &RaidSSCActionContext::lady_vashj_destroy_tainted_core; + + creators["lady vashj avoid toxic spores"] = + &RaidSSCActionContext::lady_vashj_avoid_toxic_spores; + + creators["lady vashj use free action abilities"] = + &RaidSSCActionContext::lady_vashj_use_free_action_abilities; + + creators["lady vashj manage trackers"] = + &RaidSSCActionContext::lady_vashj_manage_trackers; } private: - static Action* underbog_colossus_escape_toxic_pool(PlayerbotAI* botAI) { return new UnderbogColossusEscapeToxicPoolAction(botAI); } - static Action* greyheart_tidecaller_mark_water_elemental_totem(PlayerbotAI* botAI) { return new GreyheartTidecallerMarkWaterElementalTotemAction(botAI); } - - static Action* hydross_the_unstable_position_frost_tank(PlayerbotAI* botAI) { return new HydrossTheUnstablePositionFrostTankAction(botAI); } - static Action* hydross_the_unstable_position_nature_tank(PlayerbotAI* botAI) { return new HydrossTheUnstablePositionNatureTankAction(botAI); } - static Action* hydross_the_unstable_prioritize_elemental_adds(PlayerbotAI* botAI) { return new HydrossTheUnstablePrioritizeElementalAddsAction(botAI); } - static Action* hydross_the_unstable_frost_phase_spread_out(PlayerbotAI* botAI) { return new HydrossTheUnstableFrostPhaseSpreadOutAction(botAI); } - static Action* hydross_the_unstable_misdirect_boss_to_tank(PlayerbotAI* botAI) { return new HydrossTheUnstableMisdirectBossToTankAction(botAI); } - static Action* hydross_the_unstable_stop_dps_upon_phase_change(PlayerbotAI* botAI) { return new HydrossTheUnstableStopDpsUponPhaseChangeAction(botAI); } - static Action* hydross_the_unstable_manage_timers(PlayerbotAI* botAI) { return new HydrossTheUnstableManageTimersAction(botAI); } - - static Action* the_lurker_below_run_around_behind_boss(PlayerbotAI* botAI) { return new TheLurkerBelowRunAroundBehindBossAction(botAI); } - static Action* the_lurker_below_position_main_tank(PlayerbotAI* botAI) { return new TheLurkerBelowPositionMainTankAction(botAI); } - static Action* the_lurker_below_spread_ranged(PlayerbotAI* botAI) { return new TheLurkerBelowSpreadRangedAction(botAI); } - static Action* the_lurker_below_tanks_pick_up_adds(PlayerbotAI* botAI) { return new TheLurkerBelowTanksPickUpAddsAction(botAI); } - static Action* the_lurker_below_manage_spout_timer(PlayerbotAI* botAI) { return new TheLurkerBelowManageSpoutTimerAction(botAI); } - - static Action* leotheras_the_blind_target_spellbinders(PlayerbotAI* botAI) { return new LeotherasTheBlindTargetSpellbindersAction(botAI); } - static Action* leotheras_the_blind_demon_form_tank_attack_boss(PlayerbotAI* botAI) { return new LeotherasTheBlindDemonFormTankAttackBossAction(botAI); } - static Action* leotheras_the_blind_position_ranged(PlayerbotAI* botAI) { return new LeotherasTheBlindPositionRangedAction(botAI); } - static Action* leotheras_the_blind_run_away_from_whirlwind(PlayerbotAI* botAI) { return new LeotherasTheBlindRunAwayFromWhirlwindAction(botAI); } - static Action* leotheras_the_blind_inner_demon_cheat(PlayerbotAI* botAI) { return new LeotherasTheBlindInnerDemonCheatAction(botAI); } - static Action* leotheras_the_blind_misdirect_boss_to_demon_form_tank(PlayerbotAI* botAI) { return new LeotherasTheBlindMisdirectBossToDemonFormTankAction(botAI); } - static Action* leotheras_the_blind_final_phase_assign_dps_priority(PlayerbotAI* botAI) { return new LeotherasTheBlindFinalPhaseAssignDpsPriorityAction(botAI); } - static Action* leotheras_the_blind_manage_timers_and_trackers(PlayerbotAI* botAI) { return new LeotherasTheBlindManageTimersAndTrackersAction(botAI); } - - static Action* fathom_lord_karathress_main_tank_position_boss(PlayerbotAI* botAI) { return new FathomLordKarathressMainTankPositionBossAction(botAI); } - static Action* fathom_lord_karathress_first_assist_tank_position_caribdis(PlayerbotAI* botAI) { return new FathomLordKarathressFirstAssistTankPositionCaribdisAction(botAI); } - static Action* fathom_lord_karathress_second_assist_tank_position_sharkkis(PlayerbotAI* botAI) { return new FathomLordKarathressSecondAssistTankPositionSharkkisAction(botAI); } - static Action* fathom_lord_karathress_third_assist_tank_position_tidalvess(PlayerbotAI* botAI) { return new FathomLordKarathressThirdAssistTankPositionTidalvessAction(botAI); } - static Action* fathom_lord_karathress_position_caribdis_tank_healer(PlayerbotAI* botAI) { return new FathomLordKarathressPositionCaribdisTankHealerAction(botAI); } - static Action* fathom_lord_karathress_misdirect_bosses_to_tanks(PlayerbotAI* botAI) { return new FathomLordKarathressMisdirectBossesToTanksAction(botAI); } - static Action* fathom_lord_karathress_assign_dps_priority(PlayerbotAI* botAI) { return new FathomLordKarathressAssignDpsPriorityAction(botAI); } - static Action* fathom_lord_karathress_manage_dps_timer(PlayerbotAI* botAI) { return new FathomLordKarathressManageDpsTimerAction(botAI); } - - static Action* morogrim_tidewalker_misdirect_boss_to_main_tank(PlayerbotAI* botAI) { return new MorogrimTidewalkerMisdirectBossToMainTankAction(botAI); } - static Action* morogrim_tidewalker_move_boss_to_tank_position(PlayerbotAI* botAI) { return new MorogrimTidewalkerMoveBossToTankPositionAction(botAI); } - static Action* morogrim_tidewalker_phase_2_reposition_ranged(PlayerbotAI* botAI) { return new MorogrimTidewalkerPhase2RepositionRangedAction(botAI); } - static Action* morogrim_tidewalker_reset_phase_transition_steps(PlayerbotAI* botAI) { return new MorogrimTidewalkerResetPhaseTransitionStepsAction(botAI); } - - static Action* lady_vashj_main_tank_position_boss(PlayerbotAI* botAI) { return new LadyVashjMainTankPositionBossAction(botAI); } - static Action* lady_vashj_phase_1_position_ranged(PlayerbotAI* botAI) { return new LadyVashjPhase1PositionRangedAction(botAI); } - static Action* lady_vashj_set_grounding_totem_in_main_tank_group(PlayerbotAI* botAI) { return new LadyVashjSetGroundingTotemInMainTankGroupAction(botAI); } - static Action* lady_vashj_static_charge_move_away_from_group(PlayerbotAI* botAI) { return new LadyVashjStaticChargeMoveAwayFromGroupAction(botAI); } - static Action* lady_vashj_misdirect_boss_to_main_tank(PlayerbotAI* botAI) { return new LadyVashjMisdirectBossToMainTankAction(botAI); } - static Action* lady_vashj_misdirect_strider_to_first_assist_tank(PlayerbotAI* botAI) { return new LadyVashjMisdirectStriderToFirstAssistTankAction(botAI); } - static Action* lady_vashj_tank_attack_and_move_away_strider(PlayerbotAI* botAI) { return new LadyVashjTankAttackAndMoveAwayStriderAction(botAI); } - static Action* lady_vashj_assign_dps_priority(PlayerbotAI* botAI) { return new LadyVashjAssignDpsPriorityAction(botAI); } - static Action* lady_vashj_teleport_to_tainted_elemental(PlayerbotAI* botAI) { return new LadyVashjTeleportToTaintedElementalAction(botAI); } - static Action* lady_vashj_loot_tainted_core(PlayerbotAI* botAI) { return new LadyVashjLootTaintedCoreAction(botAI); } - static Action* lady_vashj_pass_the_tainted_core(PlayerbotAI* botAI) { return new LadyVashjPassTheTaintedCoreAction(botAI); } - static Action* lady_vashj_destroy_tainted_core(PlayerbotAI* botAI) { return new LadyVashjDestroyTaintedCoreAction(botAI); } - static Action* lady_vashj_avoid_toxic_spores(PlayerbotAI* botAI) { return new LadyVashjAvoidToxicSporesAction(botAI); } - static Action* lady_vashj_use_free_action_abilities(PlayerbotAI* botAI) { return new LadyVashjUseFreeActionAbilitiesAction(botAI); } - static Action* lady_vashj_manage_trackers(PlayerbotAI* botAI) { return new LadyVashjManageTrackersAction(botAI); } + // Trash + static Action* underbog_colossus_escape_toxic_pool( + PlayerbotAI* botAI) { return new UnderbogColossusEscapeToxicPoolAction(botAI); } + + static Action* greyheart_tidecaller_mark_water_elemental_totem( + PlayerbotAI* botAI) { return new GreyheartTidecallerMarkWaterElementalTotemAction(botAI); } + + // Hydross the Unstable + static Action* hydross_the_unstable_position_frost_tank( + PlayerbotAI* botAI) { return new HydrossTheUnstablePositionFrostTankAction(botAI); } + + static Action* hydross_the_unstable_position_nature_tank( + PlayerbotAI* botAI) { return new HydrossTheUnstablePositionNatureTankAction(botAI); } + + static Action* hydross_the_unstable_prioritize_elemental_adds( + PlayerbotAI* botAI) { return new HydrossTheUnstablePrioritizeElementalAddsAction(botAI); } + + static Action* hydross_the_unstable_frost_phase_spread_out( + PlayerbotAI* botAI) { return new HydrossTheUnstableFrostPhaseSpreadOutAction(botAI); } + + static Action* hydross_the_unstable_misdirect_boss_to_tank( + PlayerbotAI* botAI) { return new HydrossTheUnstableMisdirectBossToTankAction(botAI); } + + static Action* hydross_the_unstable_stop_dps_upon_phase_change( + PlayerbotAI* botAI) { return new HydrossTheUnstableStopDpsUponPhaseChangeAction(botAI); } + + static Action* hydross_the_unstable_manage_timers( + PlayerbotAI* botAI) { return new HydrossTheUnstableManageTimersAction(botAI); } + + // The Lurker Below + static Action* the_lurker_below_run_around_behind_boss( + PlayerbotAI* botAI) { return new TheLurkerBelowRunAroundBehindBossAction(botAI); } + + static Action* the_lurker_below_position_main_tank( + PlayerbotAI* botAI) { return new TheLurkerBelowPositionMainTankAction(botAI); } + + static Action* the_lurker_below_spread_ranged( + PlayerbotAI* botAI) { return new TheLurkerBelowSpreadRangedAction(botAI); } + + static Action* the_lurker_below_tanks_pick_up_adds( + PlayerbotAI* botAI) { return new TheLurkerBelowTanksPickUpAddsAction(botAI); } + + static Action* the_lurker_below_manage_spout_timer( + PlayerbotAI* botAI) { return new TheLurkerBelowManageSpoutTimerAction(botAI); } + + // Leotheras the Blind + static Action* leotheras_the_blind_target_spellbinders( + PlayerbotAI* botAI) { return new LeotherasTheBlindTargetSpellbindersAction(botAI); } + + static Action* leotheras_the_blind_demon_form_tank_attack_boss( + PlayerbotAI* botAI) { return new LeotherasTheBlindDemonFormTankAttackBossAction(botAI); } + + static Action* leotheras_the_blind_position_ranged( + PlayerbotAI* botAI) { return new LeotherasTheBlindPositionRangedAction(botAI); } + + static Action* leotheras_the_blind_run_away_from_whirlwind( + PlayerbotAI* botAI) { return new LeotherasTheBlindRunAwayFromWhirlwindAction(botAI); } + + static Action* leotheras_the_blind_melee_dps_run_away_from_boss( + PlayerbotAI* botAI) { return new LeotherasTheBlindMeleeDpsRunAwayFromBossAction(botAI); } + + static Action* leotheras_the_blind_inner_demon_cheat( + PlayerbotAI* botAI) { return new LeotherasTheBlindInnerDemonCheatAction(botAI); } + + static Action* leotheras_the_blind_misdirect_boss_to_demon_form_tank( + PlayerbotAI* botAI) { return new LeotherasTheBlindMisdirectBossToDemonFormTankAction(botAI); } + + static Action* leotheras_the_blind_final_phase_assign_dps_priority( + PlayerbotAI* botAI) { return new LeotherasTheBlindFinalPhaseAssignDpsPriorityAction(botAI); } + + static Action* leotheras_the_blind_manage_dps_wait_timers( + PlayerbotAI* botAI) { return new LeotherasTheBlindManageDpsWaitTimersAction(botAI); } + + // Fathom-Lord Karathress + static Action* fathom_lord_karathress_main_tank_position_boss( + PlayerbotAI* botAI) { return new FathomLordKarathressMainTankPositionBossAction(botAI); } + + static Action* fathom_lord_karathress_first_assist_tank_position_caribdis( + PlayerbotAI* botAI) { return new FathomLordKarathressFirstAssistTankPositionCaribdisAction(botAI); } + + static Action* fathom_lord_karathress_second_assist_tank_position_sharkkis( + PlayerbotAI* botAI) { return new FathomLordKarathressSecondAssistTankPositionSharkkisAction(botAI); } + + static Action* fathom_lord_karathress_third_assist_tank_position_tidalvess( + PlayerbotAI* botAI) { return new FathomLordKarathressThirdAssistTankPositionTidalvessAction(botAI); } + + static Action* fathom_lord_karathress_position_caribdis_tank_healer( + PlayerbotAI* botAI) { return new FathomLordKarathressPositionCaribdisTankHealerAction(botAI); } + + static Action* fathom_lord_karathress_misdirect_bosses_to_tanks( + PlayerbotAI* botAI) { return new FathomLordKarathressMisdirectBossesToTanksAction(botAI); } + + static Action* fathom_lord_karathress_assign_dps_priority( + PlayerbotAI* botAI) { return new FathomLordKarathressAssignDpsPriorityAction(botAI); } + + static Action* fathom_lord_karathress_manage_dps_timer( + PlayerbotAI* botAI) { return new FathomLordKarathressManageDpsTimerAction(botAI); } + + // Morogrim Tidewalker + static Action* morogrim_tidewalker_misdirect_boss_to_main_tank( + PlayerbotAI* botAI) { return new MorogrimTidewalkerMisdirectBossToMainTankAction(botAI); } + + static Action* morogrim_tidewalker_move_boss_to_tank_position( + PlayerbotAI* botAI) { return new MorogrimTidewalkerMoveBossToTankPositionAction(botAI); } + + static Action* morogrim_tidewalker_phase_2_reposition_ranged( + PlayerbotAI* botAI) { return new MorogrimTidewalkerPhase2RepositionRangedAction(botAI); } + + static Action* morogrim_tidewalker_reset_phase_transition_steps( + PlayerbotAI* botAI) { return new MorogrimTidewalkerResetPhaseTransitionStepsAction(botAI); } + + // Lady Vashj + static Action* lady_vashj_main_tank_position_boss( + PlayerbotAI* botAI) { return new LadyVashjMainTankPositionBossAction(botAI); } + + static Action* lady_vashj_phase_1_position_ranged( + PlayerbotAI* botAI) { return new LadyVashjPhase1PositionRangedAction(botAI); } + + static Action* lady_vashj_set_grounding_totem_in_main_tank_group( + PlayerbotAI* botAI) { return new LadyVashjSetGroundingTotemInMainTankGroupAction(botAI); } + + static Action* lady_vashj_static_charge_move_away_from_group( + PlayerbotAI* botAI) { return new LadyVashjStaticChargeMoveAwayFromGroupAction(botAI); } + + static Action* lady_vashj_misdirect_boss_to_main_tank( + PlayerbotAI* botAI) { return new LadyVashjMisdirectBossToMainTankAction(botAI); } + + static Action* lady_vashj_misdirect_strider_to_first_assist_tank( + PlayerbotAI* botAI) { return new LadyVashjMisdirectStriderToFirstAssistTankAction(botAI); } + + static Action* lady_vashj_tank_attack_and_move_away_strider( + PlayerbotAI* botAI) { return new LadyVashjTankAttackAndMoveAwayStriderAction(botAI); } + + static Action* lady_vashj_assign_dps_priority( + PlayerbotAI* botAI) { return new LadyVashjAssignDpsPriorityAction(botAI); } + + static Action* lady_vashj_teleport_to_tainted_elemental( + PlayerbotAI* botAI) { return new LadyVashjTeleportToTaintedElementalAction(botAI); } + + static Action* lady_vashj_loot_tainted_core( + PlayerbotAI* botAI) { return new LadyVashjLootTaintedCoreAction(botAI); } + + static Action* lady_vashj_pass_the_tainted_core( + PlayerbotAI* botAI) { return new LadyVashjPassTheTaintedCoreAction(botAI); } + + static Action* lady_vashj_destroy_tainted_core( + PlayerbotAI* botAI) { return new LadyVashjDestroyTaintedCoreAction(botAI); } + + static Action* lady_vashj_avoid_toxic_spores( + PlayerbotAI* botAI) { return new LadyVashjAvoidToxicSporesAction(botAI); } + + static Action* lady_vashj_use_free_action_abilities( + PlayerbotAI* botAI) { return new LadyVashjUseFreeActionAbilitiesAction(botAI); } + + static Action* lady_vashj_manage_trackers( + PlayerbotAI* botAI) { return new LadyVashjManageTrackersAction(botAI); } }; #endif diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp index c00842a804..2d8326b05c 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp @@ -9,7 +9,6 @@ #include "RtiTargetValue.h" using namespace SerpentShrineCavernHelpers; -using namespace SerpentShrineCavernPositions; // Trash Mobs @@ -69,7 +68,7 @@ bool UnderbogColossusEscapeToxicPoolAction::Execute(Event event) } else { - float dist = sqrt(dx * dx + dy * dy); + float dist = bot->GetExactDist2d(dynObj->GetPositionX(), dynObj->GetPositionY()); float inv = 1.0f / dist; moveX = dynObj->GetPositionX() + (dx * inv) * safeDist; moveY = dynObj->GetPositionY() + (dy * inv) * safeDist; @@ -94,7 +93,7 @@ bool GreyheartTidecallerMarkWaterElementalTotemAction::Execute(Event event) // Hydross the Unstable // (1) When tanking, move to designated tanking spot on frost side -// (2) 5 seconds into 100% Mark of Hydross, move to nature tank's spot to hand off boss +// (2) At 100% Mark of Hydross, move to nature tank's spot to hand off boss // (3) When Hydross is in nature form, move back to frost tank spot and wait for transition bool HydrossTheUnstablePositionFrostTankAction::Execute(Event event) { @@ -112,41 +111,42 @@ bool HydrossTheUnstablePositionFrostTankAction::Execute(Event event) if (hydross->GetVictim() == bot) { - const Position& position = HydrossFrostTankPosition; - if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 3.0f) + const Position& position = HYDROSS_FROST_TANK_POSITION; + float dist = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); + if (dist > 2.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); - float dist = sqrt(dX * dX + dY * dY); - float moveDist = std::min(4.5f, dist); + float moveDist = std::min(5.0f, dist); float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_COMBAT, true, true); + return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, + false, true, MovementPriority::MOVEMENT_COMBAT, true, true); } } } - if (!hydross->HasAura(SPELL_CORRUPTION) && HasMarkOfHydrossAt100Percent(bot) && hydross->GetVictim() == bot) + if (!hydross->HasAura(SPELL_CORRUPTION) && HasMarkOfHydrossAt100Percent(bot) && + hydross->GetVictim() == bot) { const time_t now = std::time(nullptr); auto it = hydrossChangeToNaturePhaseTimer.find(SSC_MAP_ID); - if (it != hydrossChangeToNaturePhaseTimer.end() && (now - it->second) >= 5) + if (it != hydrossChangeToNaturePhaseTimer.end() && (now - it->second) >= 1) { - const Position& position = HydrossNatureTankPosition; - if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 3.0f) + const Position& position = HYDROSS_NATURE_TANK_POSITION; + float dist = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); + if (dist > 2.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); - float dist = sqrt(dX * dX + dY * dY); - float moveDist = std::min(4.5f, dist); + float moveDist = std::min(5.0f, dist); float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_COMBAT, true, true); + return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, + false, true, MovementPriority::MOVEMENT_COMBAT, true, true); } else { @@ -159,17 +159,11 @@ bool HydrossTheUnstablePositionFrostTankAction::Execute(Event event) if (hydross->HasAura(SPELL_CORRUPTION)) { - const Position& position = HydrossFrostTankPosition; - if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 3.0f) + const Position& position = HYDROSS_FROST_TANK_POSITION; + if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 2.0f) { - float dX = position.GetPositionX() - bot->GetPositionX(); - float dY = position.GetPositionY() - bot->GetPositionY(); - float dist = sqrt(dX * dX + dY * dY); - float moveDist = std::min(7.0f, dist); - float moveX = bot->GetPositionX() + (dX / dist) * moveDist; - float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - - return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, + return MoveTo(SSC_MAP_ID, position.GetPositionX(), position.GetPositionY(), + position.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, false); } else @@ -184,7 +178,7 @@ bool HydrossTheUnstablePositionFrostTankAction::Execute(Event event) } // (1) When tanking, move to designated tanking spot on nature side -// (2) 5 seconds into 100% Mark of Corruption, move to frost tank's spot to hand off boss +// (2) At 100% Mark of Corruption, move to frost tank's spot to hand off boss // (3) When Hydross is in frost form, move back to nature tank spot and wait for transition bool HydrossTheUnstablePositionNatureTankAction::Execute(Event event) { @@ -202,41 +196,42 @@ bool HydrossTheUnstablePositionNatureTankAction::Execute(Event event) if (hydross->GetVictim() == bot) { - const Position& position = HydrossNatureTankPosition; - if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 3.0f) + const Position& position = HYDROSS_NATURE_TANK_POSITION; + float dist = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); + if (dist > 2.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); - float dist = sqrt(dX * dX + dY * dY); - float moveDist = std::min(4.5f, dist); + float moveDist = std::min(5.0f, dist); float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_COMBAT, true, true); + return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, + false, true, MovementPriority::MOVEMENT_COMBAT, true, true); } } } - if (hydross->HasAura(SPELL_CORRUPTION) && HasMarkOfCorruptionAt100Percent(bot) && hydross->GetVictim() == bot) + if (hydross->HasAura(SPELL_CORRUPTION) && HasMarkOfCorruptionAt100Percent(bot) && + hydross->GetVictim() == bot) { const time_t now = std::time(nullptr); auto it = hydrossChangeToFrostPhaseTimer.find(SSC_MAP_ID); - if (it != hydrossChangeToFrostPhaseTimer.end() && (now - it->second) >= 5) + if (it != hydrossChangeToFrostPhaseTimer.end() && (now - it->second) >= 1) { - const Position& position = HydrossFrostTankPosition; - if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 3.0f) + const Position& position = HYDROSS_FROST_TANK_POSITION; + float dist = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); + if (dist > 2.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); - float dist = sqrt(dX * dX + dY * dY); - float moveDist = std::min(4.5f, dist); + float moveDist = std::min(5.0f, dist); float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_COMBAT, true, true); + return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, + false, true, MovementPriority::MOVEMENT_COMBAT, true, true); } else { @@ -249,17 +244,11 @@ bool HydrossTheUnstablePositionNatureTankAction::Execute(Event event) if (!hydross->HasAura(SPELL_CORRUPTION)) { - const Position& position = HydrossNatureTankPosition; - if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 3.0f) + const Position& position = HYDROSS_NATURE_TANK_POSITION; + if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 2.0f) { - float dX = position.GetPositionX() - bot->GetPositionX(); - float dY = position.GetPositionY() - bot->GetPositionY(); - float dist = sqrt(dX * dX + dY * dY); - float moveDist = std::min(7.0f, dist); - float moveX = bot->GetPositionX() + (dX / dist) * moveDist; - float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - - return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, + return MoveTo(SSC_MAP_ID, position.GetPositionX(), position.GetPositionY(), + position.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, false); } else @@ -310,20 +299,21 @@ bool HydrossTheUnstablePrioritizeElementalAddsAction::Execute(Event event) bool HydrossTheUnstableFrostPhaseSpreadOutAction::Execute(Event event) { Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); - Group* group = bot->GetGroup(); - if (!hydross || !group) + if (!hydross) return false; - const uint32 minInterval = 500; - - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + if (Group* group = bot->GetGroup()) { - Player* member = ref->GetSource(); - if (!member || member == bot || !member->IsAlive()) - continue; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || member == bot || !member->IsAlive()) + continue; - if (bot->GetExactDist2d(member) < 6.0f) - return FleePosition(member->GetPosition(), 8.0f, minInterval); + const uint32 minInterval = 500; + if (bot->GetExactDist2d(member) < 6.0f) + return FleePosition(member->GetPosition(), 8.0f, minInterval); + } } return false; @@ -332,20 +322,23 @@ bool HydrossTheUnstableFrostPhaseSpreadOutAction::Execute(Event event) bool HydrossTheUnstableMisdirectBossToTankAction::Execute(Event event) { Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); - Group* group = bot->GetGroup(); - if (!hydross || !group) + if (!hydross) return false; - if (TryMisdirectToFrostTank(hydross, group)) - return true; + if (Group* group = bot->GetGroup()) + { + if (TryMisdirectToFrostTank(hydross, group)) + return true; - if (TryMisdirectToNatureTank(hydross, group)) - return true; + if (TryMisdirectToNatureTank(hydross, group)) + return true; + } return false; } -bool HydrossTheUnstableMisdirectBossToTankAction::TryMisdirectToFrostTank(Unit* hydross, Group* group) +bool HydrossTheUnstableMisdirectBossToTankAction::TryMisdirectToFrostTank( + Unit* hydross, Group* group) { Player* frostTank = nullptr; for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) @@ -373,7 +366,8 @@ bool HydrossTheUnstableMisdirectBossToTankAction::TryMisdirectToFrostTank(Unit* return false; } -bool HydrossTheUnstableMisdirectBossToTankAction::TryMisdirectToNatureTank(Unit* hydross, Group* group) +bool HydrossTheUnstableMisdirectBossToTankAction::TryMisdirectToNatureTank( + Unit* hydross, Group* group) { Player* natureTank = nullptr; for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) @@ -408,29 +402,29 @@ bool HydrossTheUnstableStopDpsUponPhaseChangeAction::Execute(Event event) return false; const time_t now = std::time(nullptr); - const int phaseEndStopSeconds = 6; const int phaseStartStopSeconds = 5; + const int phaseEndStopSeconds = 1; bool shouldStopDps = false; - // 6 seconds after 100% Mark of Corruption, stop DPS until transition into frost phase + // 1 second after 100% Mark of Hydross, stop DPS until transition into nature phase auto itNature = hydrossChangeToNaturePhaseTimer.find(SSC_MAP_ID); if (itNature != hydrossChangeToNaturePhaseTimer.end() && (now - itNature->second) >= phaseEndStopSeconds) shouldStopDps = true; - // Keep DPS stopped for 5 seconds after transition into frost phase - auto itFrostDps = hydrossFrostDpsWaitTimer.find(SSC_MAP_ID); - if (itFrostDps != hydrossFrostDpsWaitTimer.end() && (now - itFrostDps->second) < phaseStartStopSeconds) + // Keep DPS stopped for 5 seconds after transition into nature phase + auto itNatureDps = hydrossNatureDpsWaitTimer.find(SSC_MAP_ID); + if (itNatureDps != hydrossNatureDpsWaitTimer.end() && (now - itNatureDps->second) < phaseStartStopSeconds) shouldStopDps = true; - // 6 seconds after 100% Mark of Hydross, stop DPS until transition into nature phase + // 1 second after 100% Mark of Corruption, stop DPS until transition into frost phase auto itFrost = hydrossChangeToFrostPhaseTimer.find(SSC_MAP_ID); if (itFrost != hydrossChangeToFrostPhaseTimer.end() && (now - itFrost->second) >= phaseEndStopSeconds) shouldStopDps = true; - // Keep DPS stopped for 5 seconds after transition into nature phase - auto itNatureDps = hydrossNatureDpsWaitTimer.find(SSC_MAP_ID); - if (itNatureDps != hydrossNatureDpsWaitTimer.end() && (now - itNatureDps->second) < phaseStartStopSeconds) + // Keep DPS stopped for 5 seconds after transition into frost phase + auto itFrostDps = hydrossFrostDpsWaitTimer.find(SSC_MAP_ID); + if (itFrostDps != hydrossFrostDpsWaitTimer.end() && (now - itFrostDps->second) < phaseStartStopSeconds) shouldStopDps = true; if (shouldStopDps) @@ -520,7 +514,7 @@ bool TheLurkerBelowPositionMainTankAction::Execute(Event event) if (bot->GetVictim() != lurker) return Attack(lurker); - const Position& position = LurkerMainTankPosition; + const Position& position = LURKER_MAIN_TANK_POSITION; if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 0.2f) { return MoveTo(SSC_MAP_ID, position.GetPositionX(), position.GetPositionY(), position.GetPositionZ(), @@ -534,21 +528,23 @@ bool TheLurkerBelowPositionMainTankAction::Execute(Event event) bool TheLurkerBelowSpreadRangedAction::Execute(Event event) { Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); - Group* group = bot->GetGroup(); - if (!lurker || !group) + if (!lurker) return false; if (lurker->GetHealth() == lurker->GetMaxHealth()) lurkerRangedPositions.clear(); std::vector rangedMembers; - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + if (Group* group = bot->GetGroup()) { - Player* member = ref->GetSource(); - if (!member || !member->IsAlive() || !botAI->IsRanged(member)) - continue; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || !botAI->IsRanged(member)) + continue; - rangedMembers.push_back(member); + rangedMembers.push_back(member); + } } if (rangedMembers.empty()) @@ -582,9 +578,8 @@ bool TheLurkerBelowSpreadRangedAction::Execute(Event event) float tx = lurker->GetPositionX() + radius * std::cos(angle); float ty = lurker->GetPositionY() + radius * std::sin(angle); - float tz = lurker->GetPositionZ(); - lurkerRangedPositions.emplace(guid, Position(tx, ty, tz)); + lurkerRangedPositions.try_emplace(guid, Position(tx, ty, lurker->GetPositionZ())); it = lurkerRangedPositions.find(guid); } @@ -592,8 +587,7 @@ bool TheLurkerBelowSpreadRangedAction::Execute(Event event) return false; const Position& target = it->second; - const float returnThreshold = 2.0f; - if (!bot->IsWithinDist2d(target.GetPositionX(), target.GetPositionY(), returnThreshold)) + if (bot->GetExactDist2d(target.GetPositionX(), target.GetPositionY()) > 2.0f) { return MoveTo(SSC_MAP_ID, target.GetPositionX(), target.GetPositionY(), target.GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); @@ -605,32 +599,32 @@ bool TheLurkerBelowSpreadRangedAction::Execute(Event event) // If >= 3 tanks in the raid, the first 3 will each pick up 1 Guardian bool TheLurkerBelowTanksPickUpAddsAction::Execute(Event event) { - Group* group = bot->GetGroup(); - if (!group) - return false; - Player* mainTank = nullptr; Player* firstAssistTank = nullptr; Player* secondAssistTank = nullptr; - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + + if (Group* group = bot->GetGroup()) { - Player* member = ref->GetSource(); - if (!member || !member->IsAlive()) - continue; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive()) + continue; - if (!mainTank && botAI->IsMainTank(member)) - mainTank = member; - else if (!firstAssistTank && botAI->IsAssistTankOfIndex(member, 0)) - firstAssistTank = member; - else if (!secondAssistTank && botAI->IsAssistTankOfIndex(member, 1)) - secondAssistTank = member; + if (!mainTank && botAI->IsMainTank(member)) + mainTank = member; + else if (!firstAssistTank && botAI->IsAssistTankOfIndex(member, 0)) + firstAssistTank = member; + else if (!secondAssistTank && botAI->IsAssistTankOfIndex(member, 1)) + secondAssistTank = member; + } } if (!mainTank || !firstAssistTank || !secondAssistTank) return false; std::vector guardians; - GuidVector npcs = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); + auto const& npcs = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); for (auto guid : npcs) { Unit* unit = botAI->GetUnit(guid); @@ -692,7 +686,7 @@ bool TheLurkerBelowManageSpoutTimerAction::Execute(Event event) const time_t spoutCastTime = 20; if (IsLurkerCastingSpout(lurker) && it == lurkerSpoutTimer.end()) - lurkerSpoutTimer.emplace(SSC_MAP_ID, now + spoutCastTime); + lurkerSpoutTimer.try_emplace(SSC_MAP_ID, now + spoutCastTime); return false; } @@ -732,13 +726,16 @@ bool LeotherasTheBlindDemonFormTankAttackBossAction::Execute(Event event) // And stay away from the Warlock tank to avoid Chaos Blasts bool LeotherasTheBlindPositionRangedAction::Execute(Event event) { - Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); - if (!leotheras) - return false; - const uint32 minInterval = 500; - if (bot->GetExactDist2d(leotheras) < 10.0f) - return FleePosition(leotheras->GetPosition(), 12.0f, minInterval); + + Unit* leotherasHuman = GetLeotherasHuman(botAI); + if (leotherasHuman && bot->GetExactDist2d(leotherasHuman) < 10.0f && + leotherasHuman->GetVictim() != bot) + return FleePosition(leotherasHuman->GetPosition(), 12.0f, minInterval); + + Unit* leotherasDemon = GetActiveLeotherasDemon(botAI); + if (!leotherasDemon) + return false; if (Group* group = bot->GetGroup()) { @@ -749,9 +746,7 @@ bool LeotherasTheBlindPositionRangedAction::Execute(Event event) continue; Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); - Unit* leotherasDemon = GetActiveLeotherasDemon(botAI); - if (demonFormTank && demonFormTank == member && - leotherasDemon) + if (demonFormTank && demonFormTank == member) { if (bot->GetExactDist2d(member) < 10.0f) return FleePosition(member->GetPosition(), 12.0f, minInterval); @@ -769,7 +764,7 @@ bool LeotherasTheBlindRunAwayFromWhirlwindAction::Execute(Event event) Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI); Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); - if (leotherasPhase3Demon && demonFormTank == bot) + if (leotherasPhase3Demon && demonFormTank && demonFormTank == bot) return false; Unit* leotherasHuman = GetLeotherasHuman(botAI); @@ -788,12 +783,42 @@ bool LeotherasTheBlindRunAwayFromWhirlwindAction::Execute(Event event) return false; } +// If there is no Warlock tank, then a melee tank will be picking up Demon Leo +// In that case, melee needs to get out after too many Chaos Blast stacks +bool LeotherasTheBlindMeleeDpsRunAwayFromBossAction::Execute(Event event) +{ + Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI); + Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); + + if (!leotherasPhase2Demon || demonFormTank != nullptr) + return false; + + Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST); + if (chaosBlast && chaosBlast->GetStackAmount() >= 5) + { + Unit* demonVictim = leotherasPhase2Demon->GetVictim(); + if (!demonVictim) + return false; + + float currentDistance = bot->GetExactDist2d(demonVictim); + const float safeDistance = 10.0f; + if (currentDistance < safeDistance) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + return MoveAway(demonVictim, safeDistance - currentDistance + 1.0f); + } + } + + return false; +} + // Tanks and healers have no ability to kill their own Inner Demons -// Hunters, Affliction Warlocks, Shadow Priests, and (for some reason) Arcane Mages also struggle +// Hunters, Affliction Warlocks, Shadow Priests, and (for some reason) Mages also struggle bool LeotherasTheBlindInnerDemonCheatAction::Execute(Event event) { Unit* innerDemon = nullptr; - GuidVector npcs = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); + auto const& npcs = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); for (auto const& guid : npcs) { Unit* unit = botAI->GetUnit(guid); @@ -813,9 +838,9 @@ bool LeotherasTheBlindInnerDemonCheatAction::Execute(Event event) if (botAI->IsHeal(bot) || botAI->IsTank(bot) || bot->getClass() == CLASS_HUNTER || + bot->getClass() == CLASS_MAGE || (bot->getClass() == CLASS_PRIEST && tab == 2) || (bot->getClass() == CLASS_WARLOCK && tab == 0) || - (bot->getClass() == CLASS_MAGE && tab == 0) || (demonFormTank && demonFormTank == bot)) { Unit::DealDamage(bot, innerDemon, innerDemon->GetMaxHealth() / 25, nullptr, @@ -847,15 +872,16 @@ bool LeotherasTheBlindFinalPhaseAssignDpsPriorityAction::Execute(Event event) if (botAI->IsTank(bot) && leotherasHuman->GetVictim() == bot) { - if (leotherasHuman->GetExactDist2d(leotherasDemon) < 25.0f) + Unit* demonTarget = leotherasDemon->GetVictim(); + if (demonTarget && leotherasHuman->GetExactDist2d(demonTarget) < 20.0f) { - float angle = atan2(bot->GetPositionY() - leotherasDemon->GetPositionY(), - bot->GetPositionX() - leotherasDemon->GetPositionX()); - float targetX = bot->GetPositionX() + 27.0f * std::cos(angle); - float targetY = bot->GetPositionY() + 27.0f * std::sin(angle); + float angle = atan2(bot->GetPositionY() - demonTarget->GetPositionY(), + bot->GetPositionX() - demonTarget->GetPositionX()); + float targetX = bot->GetPositionX() + 21.0f * std::cos(angle); + float targetY = bot->GetPositionY() + 21.0f * std::sin(angle); return MoveTo(SSC_MAP_ID, targetX, targetY, bot->GetPositionZ(), false, false, false, false, - MovementPriority::MOVEMENT_FORCED, true, false); + MovementPriority::MOVEMENT_FORCED, true, false); } } @@ -879,7 +905,7 @@ bool LeotherasTheBlindMisdirectBossToDemonFormTankAction::Execute(Event event) } // This does not pause DPS after a Whirlwind, which is also an aggro wipe -bool LeotherasTheBlindManageTimersAndTrackersAction::Execute(Event event) +bool LeotherasTheBlindManageDpsWaitTimersAction::Execute(Event event) { Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); if (!leotheras) @@ -940,12 +966,12 @@ bool FathomLordKarathressMainTankPositionBossAction::Execute(Event event) if (karathress->GetVictim() == bot) { - const Position& position = KarathressTankPosition; - if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 3.0f) + const Position& position = KARATHRESS_TANK_POSITION; + float dist = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); + if (dist > 3.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); - float dist = sqrt(dX * dX + dY * dY); float moveDist = std::min(5.0f, dist); float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; @@ -974,12 +1000,12 @@ bool FathomLordKarathressFirstAssistTankPositionCaribdisAction::Execute(Event ev if (caribdis->GetVictim() == bot) { - const Position& position = CaribdisTankPosition; - if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 3.0f) + const Position& position = CARIBDIS_TANK_POSITION; + float dist = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); + if (dist > 3.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); - float dist = sqrt(dX * dX + dY * dY); float moveDist = std::min(10.0f, dist); float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; @@ -1007,12 +1033,12 @@ bool FathomLordKarathressSecondAssistTankPositionSharkkisAction::Execute(Event e if (sharkkis->GetVictim() == bot) { - const Position& position = SharkkisTankPosition; - if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 3.0f) + const Position& position = SHARKKIS_TANK_POSITION; + float dist = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); + if (dist > 3.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); - float dist = sqrt(dX * dX + dY * dY); float moveDist = std::min(10.0f, dist); float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; @@ -1040,12 +1066,12 @@ bool FathomLordKarathressThirdAssistTankPositionTidalvessAction::Execute(Event e if (tidalvess->GetVictim() == bot) { - const Position& position = TidalvessTankPosition; - if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 3.0f) + const Position& position = TIDALVESS_TANK_POSITION; + float dist = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); + if (dist > 3.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); - float dist = sqrt(dX * dX + dY * dY); float moveDist = std::min(10.0f, dist); float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; @@ -1066,13 +1092,13 @@ bool FathomLordKarathressPositionCaribdisTankHealerAction::Execute(Event event) if (!caribdis) return false; - const Position& position = CaribdisHealerPosition; - if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 2.0f) + const Position& position = CARIBDIS_HEALER_POSITION; + float dist = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); + if (dist > 2.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); - float dist = sqrt(dX * dX + dY * dY); - float moveDist = std::min(7.0f, dist); + float moveDist = std::min(10.0f, dist); float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; @@ -1167,9 +1193,8 @@ bool FathomLordKarathressMisdirectBossesToTanksAction::Execute(Event event) return false; } -// Kill order is different from what is recommended for players because bots handle -// Caribdis Cyclones poorly and need more time to get her down (normally, ranged would help get -// Sharkkis down first) +// Kill order is different from what is recommended for players because bots handle Caribdis +// Cyclones poorly and need more time to get her down (normally, ranged would help with Sharkkis first) bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) { // Target priority 1: Spitfire Totems for melee dps @@ -1211,18 +1236,12 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) MarkTargetWithDiamond(bot, caribdis); SetRtiTarget(botAI, "diamond", caribdis); - const Position& position = CaribdisRangedDpsPosition; - if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 2.0f) + const Position& position = CARIBDIS_RANGED_DPS_POSITION; + float dist = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); + if (dist > 2.0f) { - float dX = position.GetPositionX() - bot->GetPositionX(); - float dY = position.GetPositionY() - bot->GetPositionY(); - float dist = sqrt(dX * dX + dY * dY); - float moveDist = std::min(7.0f, dist); - float moveX = bot->GetPositionX() + (dX / dist) * moveDist; - float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - - return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_COMBAT, true, false); + return MoveInside(SSC_MAP_ID, position.GetPositionX(), position.GetPositionY(), + position.GetPositionZ(), 8.0f, MovementPriority::MOVEMENT_COMBAT); } if (bot->GetTarget() != caribdis->GetGUID()) @@ -1317,18 +1336,20 @@ bool FathomLordKarathressManageDpsTimerAction::Execute(Event event) bool MorogrimTidewalkerMisdirectBossToMainTankAction::Execute(Event event) { Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); - Group* group = bot->GetGroup(); - if (!tidewalker || !group) + if (!tidewalker) return false; Player* mainTank = nullptr; - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + if (Group* group = bot->GetGroup()) { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsMainTank(member)) + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { - mainTank = member; - break; + Player* member = ref->GetSource(); + if (member && member->IsAlive() && botAI->IsMainTank(member)) + { + mainTank = member; + break; + } } } @@ -1367,13 +1388,13 @@ bool MorogrimTidewalkerMoveBossToTankPositionAction::Execute(Event event) // Phase 1 tank position is up against the Northeast pillar bool MorogrimTidewalkerMoveBossToTankPositionAction::MoveToPhase1TankPosition(Unit* tidewalker) { - const Position& phase1 = TidewalkerPhase1TankPosition; - if (bot->GetExactDist2d(phase1.GetPositionX(), phase1.GetPositionY()) > 1.0f) + const Position& phase1 = TIDEWALKER_PHASE_1_TANK_POSITION; + float dist = bot->GetExactDist2d(phase1.GetPositionX(), phase1.GetPositionY()); + if (dist > 1.0f) { float dX = phase1.GetPositionX() - bot->GetPositionX(); float dY = phase1.GetPositionY() - bot->GetPositionY(); - float dist = sqrt(dX * dX + dY * dY); - float moveDist = std::min(4.5f, dist); + float moveDist = std::min(5.0f, dist); float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; @@ -1387,8 +1408,8 @@ bool MorogrimTidewalkerMoveBossToTankPositionAction::MoveToPhase1TankPosition(Un // Phase 2: move in two steps to get around the pillar and back into the Northeast corner bool MorogrimTidewalkerMoveBossToTankPositionAction::MoveToPhase2TankPosition(Unit* tidewalker) { - const Position& phase2 = TidewalkerPhase2TankPosition; - const Position& transition = TidewalkerPhaseTransitionWaypoint; + const Position& phase2 = TIDEWALKER_PHASE_2_TANK_POSITION; + const Position& transition = TIDEWALKER_PHASE_TRANSITION_WAYPOINT; const ObjectGuid botGuid = bot->GetGUID(); auto itStep = tidewalkerTankStep.find(botGuid); @@ -1396,12 +1417,12 @@ bool MorogrimTidewalkerMoveBossToTankPositionAction::MoveToPhase2TankPosition(Un if (step == 0) { - if (bot->GetExactDist2d(transition.GetPositionX(), transition.GetPositionY()) > 2.0f) + float dist = bot->GetExactDist2d(transition.GetPositionX(), transition.GetPositionY()); + if (dist > 2.0f) { float dX = transition.GetPositionX() - bot->GetPositionX(); float dY = transition.GetPositionY() - bot->GetPositionY(); - float dist = sqrt(dX * dX + dY * dY); - float moveDist = std::min(4.5f, dist); + float moveDist = std::min(5.0f, dist); float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; @@ -1409,17 +1430,17 @@ bool MorogrimTidewalkerMoveBossToTankPositionAction::MoveToPhase2TankPosition(Un MovementPriority::MOVEMENT_COMBAT, true, true); } else - tidewalkerTankStep.emplace(botGuid, 1); + tidewalkerTankStep.try_emplace(botGuid, 1); } if (step == 1) { - if (bot->GetExactDist2d(phase2.GetPositionX(), phase2.GetPositionY()) > 1.0f) + float dist = bot->GetExactDist2d(phase2.GetPositionX(), phase2.GetPositionY()); + if (dist > 1.0f) { float dX = phase2.GetPositionX() - bot->GetPositionX(); float dY = phase2.GetPositionY() - bot->GetPositionY(); - float dist = sqrt(dX * dX + dY * dY); - float moveDist = std::min(4.5f, dist); + float moveDist = std::min(5.0f, dist); float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; @@ -1438,8 +1459,8 @@ bool MorogrimTidewalkerPhase2RepositionRangedAction::Execute(Event event) if (!tidewalker) return false; - const Position& phase2 = TidewalkerPhase2RangedPosition; - const Position& transition = TidewalkerPhaseTransitionWaypoint; + const Position& phase2 = TIDEWALKER_PHASE_2_RANGED_POSITION; + const Position& transition = TIDEWALKER_PHASE_TRANSITION_WAYPOINT; const ObjectGuid botGuid = bot->GetGUID(); auto itStep = tidewalkerRangedStep.find(botGuid); @@ -1447,12 +1468,12 @@ bool MorogrimTidewalkerPhase2RepositionRangedAction::Execute(Event event) if (step == 0) { - if (bot->GetExactDist2d(transition.GetPositionX(), transition.GetPositionY()) > 2.0f) + float dist = bot->GetExactDist2d(transition.GetPositionX(), transition.GetPositionY()); + if (dist > 2.0f) { float dX = transition.GetPositionX() - bot->GetPositionX(); float dY = transition.GetPositionY() - bot->GetPositionY(); - float dist = sqrt(dX * dX + dY * dY); - float moveDist = std::min(7.0f, dist); + float moveDist = std::min(10.0f, dist); float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; @@ -1461,19 +1482,19 @@ bool MorogrimTidewalkerPhase2RepositionRangedAction::Execute(Event event) } else { - tidewalkerRangedStep.emplace(botGuid, 1); + tidewalkerRangedStep.try_emplace(botGuid, 1); step = 1; } } if (step == 1) { - if (bot->GetExactDist2d(phase2.GetPositionX(), phase2.GetPositionY()) > 1.0f) + float dist = bot->GetExactDist2d(phase2.GetPositionX(), phase2.GetPositionY()); + if (dist > 1.0f) { float dX = phase2.GetPositionX() - bot->GetPositionX(); float dY = phase2.GetPositionY() - bot->GetPositionY(); - float dist = sqrt(dX * dX + dY * dY); - float moveDist = std::min(7.0f, dist); + float moveDist = std::min(10.0f, dist); float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; @@ -1518,13 +1539,13 @@ bool LadyVashjMainTankPositionBossAction::Execute(Event event) { if (IsLadyVashjInPhase1(botAI)) { - const Position& position = VashjPlatformCenterPosition; - if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 2.0f) + const Position& position = VASHJ_PLATFORM_CENTER_POSITION; + float dist = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); + if (dist > 2.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); - float dist = sqrt(dX * dX + dY * dY); - float moveDist = std::min(4.5f, dist); + float moveDist = std::min(5.0f, dist); float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; @@ -1552,18 +1573,17 @@ bool LadyVashjMainTankPositionBossAction::Execute(Event event) // Semicircle around center of the room (to allow escape by Static Charged bots) bool LadyVashjPhase1PositionRangedAction::Execute(Event event) { - Group* group = bot->GetGroup(); - if (!group) - return false; - std::vector spreadMembers; - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + if (Group* group = bot->GetGroup()) { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && GET_PLAYERBOT_AI(member)) + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { - if (botAI->IsRanged(member)) - spreadMembers.push_back(member); + Player* member = ref->GetSource(); + if (member && member->IsAlive() && GET_PLAYERBOT_AI(member)) + { + if (botAI->IsRanged(member)) + spreadMembers.push_back(member); + } } } @@ -1579,11 +1599,11 @@ bool LadyVashjPhase1PositionRangedAction::Execute(Event event) if (count == 0) return false; - const Position& center = VashjPlatformCenterPosition; + const Position& center = VASHJ_PLATFORM_CENTER_POSITION; const float minRadius = 20.0f; const float maxRadius = 30.0f; - const float referenceAngle = M_PI / 2.0f; // north + const float referenceAngle = M_PI / 2.0f; // North const float arcSpan = M_PI; // 180° const float startAngle = referenceAngle - arcSpan / 2.0f; @@ -1596,23 +1616,22 @@ bool LadyVashjPhase1PositionRangedAction::Execute(Event event) float radius = frand(minRadius, maxRadius); float targetX = center.GetPositionX() + radius * std::cos(angle); float targetY = center.GetPositionY() + radius * std::sin(angle); - float tz = center.GetPositionZ(); - auto res = vashjRangedPositions.emplace(guid, Position(targetX, targetY, tz)); + auto res = vashjRangedPositions.try_emplace(guid, Position(targetX, targetY, center.GetPositionZ())); itPos = res.first; - hasReachedVashjRangedPosition.emplace(guid, false); + hasReachedVashjRangedPosition.try_emplace(guid, false); itReached = hasReachedVashjRangedPosition.find(guid); } if (itPos == vashjRangedPositions.end()) return false; - Position targetPosition = itPos->second; + Position position = itPos->second; if (itReached == hasReachedVashjRangedPosition.end() || !(itReached->second)) { - if (!bot->IsWithinDist2d(targetPosition.GetPositionX(), targetPosition.GetPositionY(), 2.0f)) + if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 2.0f) { - return MoveTo(SSC_MAP_ID, targetPosition.GetPositionX(), targetPosition.GetPositionY(), targetPosition.GetPositionZ(), + return MoveTo(SSC_MAP_ID, position.GetPositionX(), position.GetPositionY(), position.GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); } if (itReached != hasReachedVashjRangedPosition.end()) @@ -1625,35 +1644,27 @@ bool LadyVashjPhase1PositionRangedAction::Execute(Event event) // For absorbing Shock Burst bool LadyVashjSetGroundingTotemInMainTankGroupAction::Execute(Event event) { - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - Group* group = bot->GetGroup(); - if (!group) - return false; - Player* mainTank = nullptr; - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + if (Group* group = bot->GetGroup()) { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsMainTank(member)) + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { - mainTank = member; - break; + Player* member = ref->GetSource(); + if (member && member->IsAlive() && botAI->IsMainTank(member)) + { + mainTank = member; + break; + } } } if (!mainTank) return false; - float dist = bot->GetExactDist2d(mainTank); - if (dist >= 27.0f) + if (bot->GetExactDist2d(mainTank) > 25.0f) { - float angle = atan2(bot->GetPositionY() - mainTank->GetPositionY(), - bot->GetPositionX() - mainTank->GetPositionX()); - float targetX = mainTank->GetPositionX() + 25.0f * std::cos(angle); - float targetY = mainTank->GetPositionY() + 25.0f * std::sin(angle); - - return MoveTo(SSC_MAP_ID, targetX, targetY, mainTank->GetPositionZ(), - false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); + return MoveInside(SSC_MAP_ID, mainTank->GetPositionX(), mainTank->GetPositionY(), + mainTank->GetPositionZ(), 20.0f, MovementPriority::MOVEMENT_COMBAT); } if (!botAI->HasStrategy("grounding totem", BotState::BOT_STATE_COMBAT)) @@ -1668,18 +1679,20 @@ bool LadyVashjSetGroundingTotemInMainTankGroupAction::Execute(Event event) bool LadyVashjMisdirectBossToMainTankAction::Execute(Event event) { Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - Group* group = bot->GetGroup(); - if (!vashj || !group) + if (!vashj) return false; Player* mainTank = nullptr; - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + if (Group* group = bot->GetGroup()) { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsMainTank(member)) + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { - mainTank = member; - break; + Player* member = ref->GetSource(); + if (member && member->IsAlive() && botAI->IsMainTank(member)) + { + mainTank = member; + break; + } } } @@ -1751,18 +1764,20 @@ bool LadyVashjMisdirectStriderToFirstAssistTankAction::Execute(Event event) return false; Unit* strider = GetFirstAliveUnitByEntry(botAI, NPC_COILFANG_STRIDER); - Group* group = bot->GetGroup(); - if (!strider || !group) + if (!strider) return false; Player* firstAssistTank = nullptr; - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + if (Group* group = bot->GetGroup()) { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsAssistTankOfIndex(member, 0)) + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { - firstAssistTank = member; - break; + Player* member = ref->GetSource(); + if (member && member->IsAlive() && botAI->IsAssistTankOfIndex(member, 0)) + { + firstAssistTank = member; + break; + } } } @@ -1781,9 +1796,11 @@ bool LadyVashjMisdirectStriderToFirstAssistTankAction::Execute(Event event) bool LadyVashjTankAttackAndMoveAwayStriderAction::Execute(Event event) { Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + if (!vashj) + return false; + Unit* strider = GetFirstAliveUnitByEntry(botAI, NPC_COILFANG_STRIDER); - Group* group = bot->GetGroup(); - if (!vashj || !strider || !group) + if (!strider) return false; // Raid cheat automatically applies Fear Ward to tanks to make Strider tankable @@ -1851,7 +1868,7 @@ bool LadyVashjAssignDpsPriorityAction::Execute(Event event) if (!vashj) return false; - GuidVector attackers = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); + auto const& attackers = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); Unit* target = nullptr; Unit* tainted = nullptr; Unit* enchanted = nullptr; @@ -1860,7 +1877,7 @@ bool LadyVashjAssignDpsPriorityAction::Execute(Event event) Unit* sporebat = nullptr; // Search and attack radius are intended to keep bots on the platform (not go down the stairs) - const Position& center = VashjPlatformCenterPosition; + const Position& center = VASHJ_PLATFORM_CENTER_POSITION; const float maxSearchRange = botAI->IsRangedDps(bot) ? 60.0f : (botAI->IsMelee(bot) ? 55.0f : 40.0f); const float maxPursueRange = maxSearchRange - 5.0f; @@ -1876,6 +1893,11 @@ bool LadyVashjAssignDpsPriorityAction::Execute(Event event) switch (unit->GetEntry()) { + case NPC_TAINTED_ELEMENTAL: + if (!tainted) + tainted = unit; + break; + case NPC_ENCHANTED_ELEMENTAL: if (!enchanted || vashj->GetExactDist2d(unit) < vashj->GetExactDist2d(enchanted)) enchanted = unit; @@ -1913,19 +1935,19 @@ bool LadyVashjAssignDpsPriorityAction::Execute(Event event) // Hunters and Mages prioritize Enchanted Elementals, while other ranged DPS prioritize Striders // This works well with 3 Hunters and 2 Mages; effectiveness may vary based on raid composition if (bot->getClass() == CLASS_HUNTER || bot->getClass() == CLASS_MAGE) - targets = { enchanted, strider, elite }; + targets = { tainted, enchanted, strider, elite }; else - targets = { strider, elite, enchanted }; + targets = { tainted, strider, elite, enchanted }; } else if (botAI->IsMelee(bot) && botAI->IsDps(bot)) - targets = { enchanted, elite }; + targets = { tainted, enchanted, elite }; else if (botAI->IsTank(bot)) { // With raid cheats enabled, the first assist tank will tank the Strider if (botAI->HasCheat(BotCheatMask::raid) && botAI->IsAssistTankOfIndex(bot, 0)) - targets = { strider, enchanted }; + targets = { strider, elite, enchanted }; else - targets = { elite, enchanted }; + targets = { elite, strider, enchanted }; } else targets = { enchanted, elite, strider }; @@ -1944,20 +1966,18 @@ bool LadyVashjAssignDpsPriorityAction::Execute(Event event) else if (botAI->IsAssistTankOfIndex(bot, 0)) { if (botAI->HasCheat(BotCheatMask::raid)) - targets = { strider, enchanted, vashj }; - else - targets = { elite, enchanted, vashj }; + targets = { strider, elite, enchanted, vashj }; } else - targets = { elite, enchanted, vashj }; + targets = { elite, strider, enchanted, vashj }; } else if (botAI->IsRanged(bot)) { - // Hunters will try to kill Toxic Sporebats (in practice, they are generally not in range) - if (bot->getClass() == CLASS_HUNTER) - targets = { enchanted, sporebat, strider, elite, vashj }; - else + // Ranged, other than Priests and Warlocks, will prioritize Toxic Sporebats + if (bot->getClass() == CLASS_PRIEST || bot->getClass() == CLASS_WARLOCK) targets = { enchanted, strider, elite, vashj }; + else + targets = { enchanted, sporebat, strider, elite, vashj }; } else if (botAI->IsMelee(bot) && botAI->IsDps(bot)) targets = { enchanted, elite, vashj }; @@ -2019,10 +2039,14 @@ bool LadyVashjAssignDpsPriorityAction::Execute(Event event) if (!bot->GetVictim()) { Player* designatedLooter = GetDesignatedCoreLooter(bot->GetGroup(), botAI); - if (designatedLooter && tainted && designatedLooter->GetExactDist2d(tainted) < 5.0f) + Player* firstCorePasser = GetFirstTaintedCorePasser(bot->GetGroup(), botAI); + // A bot will not move back to the middle if (1) there is a Tainted Elemental, and + // (2) the bot is either the designated looter or the first core passer + if (tainted && ((designatedLooter && designatedLooter == bot) || + (firstCorePasser && firstCorePasser == bot))) return false; - const Position& center = VashjPlatformCenterPosition; + const Position& center = VASHJ_PLATFORM_CENTER_POSITION; if (bot->GetExactDist2d(center.GetPositionX(), center.GetPositionY()) > 35.0f) { bot->AttackStop(); @@ -2044,7 +2068,6 @@ bool LadyVashjTeleportToTaintedElementalAction::Execute(Event event) if (!tainted) return false; - lastTaintedGuid = tainted->GetGUID(); if (bot->GetExactDist2d(tainted) >= 10.0f) { bot->AttackStop(); @@ -2057,7 +2080,6 @@ bool LadyVashjTeleportToTaintedElementalAction::Execute(Event event) { MarkTargetWithStar(bot, tainted); SetRtiTarget(botAI, "star", tainted); - return Attack(tainted); } @@ -2072,7 +2094,7 @@ bool LadyVashjTeleportToTaintedElementalAction::Execute(Event event) bool LadyVashjLootTaintedCoreAction::Execute(Event) { - GuidVector corpses = context->GetValue("nearest corpses")->Get(); + auto const& corpses = context->GetValue("nearest corpses")->Get(); const float maxLootRange = sPlayerbotAIConfig->lootDistance; for (auto const& guid : corpses) @@ -2085,31 +2107,22 @@ bool LadyVashjLootTaintedCoreAction::Execute(Event) if (!object) continue; - // The Tainted Elemental lootable corpse is a dead Creature object, not a corpse object Creature* creature = object->ToCreature(); - if (!creature) - continue; - - if (creature->GetEntry() != NPC_TAINTED_ELEMENTAL || creature->IsAlive()) + if (!creature || creature->GetEntry() != NPC_TAINTED_ELEMENTAL || creature->IsAlive()) continue; context->GetValue("loot target")->Set(loot); float dist = bot->GetDistance(object); - if (dist > maxLootRange) return MoveTo(object, 2.0f, MovementPriority::MOVEMENT_FORCED); - // Invoke OpenLootAction to request the server's StoreLoot packet for this corpse - // Attempt a forced autostore (without modifying LootAction) by scheduling a short-timer to send - // CMSG_AUTOSTORE_LOOT_ITEM (index 0) once the server has had time to send the StoreLoot packet OpenLootAction open(botAI); bool opened = open.Execute(Event()); if (!opened) return opened; - // If anyone in the group already has the core, skip creating a duplicate if (Group* group = bot->GetGroup()) { for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) @@ -2120,7 +2133,6 @@ bool LadyVashjLootTaintedCoreAction::Execute(Event) } } - // Schedule autostore attempt + reconcile fallback const ObjectGuid botGuid = bot->GetGUID(); const ObjectGuid corpseGuid = guid; const uint8 coreIndex = 0; @@ -2131,7 +2143,6 @@ bool LadyVashjLootTaintedCoreAction::Execute(Event) if (!receiver) return; - // Double-check someone else didn't obtain the core in the meantime using receiver's group if (Group* group = receiver->GetGroup()) { for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) @@ -2142,12 +2153,13 @@ bool LadyVashjLootTaintedCoreAction::Execute(Event) } } - // Set the loot GUID so server treats the following autostore as targeted to this corpse receiver->SetLootGUID(corpseGuid); WorldPacket* packet = new WorldPacket(CMSG_AUTOSTORE_LOOT_ITEM, 1); *packet << coreIndex; receiver->GetSession()->QueuePacket(packet); + + lastCoreInInventoryTime[SSC_MAP_ID] = std::time(nullptr); }, 600); return true; @@ -2170,43 +2182,60 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI); Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI); Player* fourthCorePasser = GetFourthTaintedCorePasser(group, botAI); - Unit* closestTrigger = GetNearestActiveShieldGeneratorTriggerByEntry(bot, designatedLooter); - if (!firstCorePasser || !secondCorePasser || !thirdCorePasser || !fourthCorePasser || !closestTrigger) - return false; - - if (botAI->HasCheat(BotCheatMask::raid)) + Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental"); + Unit* closestTrigger = nullptr; + if (tainted) { - if (!bot->HasAura(SPELL_FEAR_WARD)) - bot->AddAura(SPELL_FEAR_WARD, bot); + closestTrigger = GetNearestActiveShieldGeneratorTriggerByEntry(tainted); + if (closestTrigger) + nearestTriggerGuid.insert_or_assign(SSC_MAP_ID, closestTrigger->GetGUID()); } - const uint8 neededPassers = ComputeNeededPassers(designatedLooter, closestTrigger); - - // Passer order: HealAssistantOfIndex 0, 1, 2, then RangedDpsAssistantOfIndex 0 - if (bot == firstCorePasser && !botAI->HasItemInInventory(ITEM_TAINTED_CORE) && neededPassers >= 1) - { - if (LineUpFirstCorePasser(designatedLooter, closestTrigger)) - return true; - } - else if (bot == secondCorePasser && !botAI->HasItemInInventory(ITEM_TAINTED_CORE) && neededPassers >= 2) + auto itSnap = nearestTriggerGuid.find(SSC_MAP_ID); + if (itSnap != nearestTriggerGuid.end() && !itSnap->second.IsEmpty()) { - if (LineUpSecondCorePasser(firstCorePasser, closestTrigger)) - return true; - } - else if (bot == thirdCorePasser && !botAI->HasItemInInventory(ITEM_TAINTED_CORE) && neededPassers >= 3) - { - if (LineUpThirdCorePasser(secondCorePasser, closestTrigger)) - return true; - } - else if (bot == fourthCorePasser && !botAI->HasItemInInventory(ITEM_TAINTED_CORE) && neededPassers >= 4) - { - if (LineUpFourthCorePasser(thirdCorePasser, closestTrigger)) - return true; + Unit* snapUnit = botAI->GetUnit(itSnap->second); + if (snapUnit) + closestTrigger = snapUnit; + else + nearestTriggerGuid.clear(); } + if (!firstCorePasser || !secondCorePasser || !thirdCorePasser || !fourthCorePasser || !closestTrigger) + return false; + + // Not gated behind CheatMask because this is necessary to address an issue with bot movement, which + // is that bots cannot be rooted and therefore will move when feared while holding the Tainted Core + if (!bot->HasAura(SPELL_FEAR_WARD)) + bot->AddAura(SPELL_FEAR_WARD, bot); + Item* item = bot->GetItemByEntry(ITEM_TAINTED_CORE); - if (item && botAI->HasItemInInventory(ITEM_TAINTED_CORE)) + if (!item || !botAI->HasItemInInventory(ITEM_TAINTED_CORE)) + { + // Passer order: HealAssistantOfIndex 0, 1, 2, then RangedDpsAssistantOfIndex 0 + if (bot == firstCorePasser) + { + if (LineUpFirstCorePasser(designatedLooter, closestTrigger)) + return true; + } + else if (bot == secondCorePasser) + { + if (LineUpSecondCorePasser(firstCorePasser, closestTrigger)) + return true; + } + else if (bot == thirdCorePasser) + { + if (LineUpThirdCorePasser(designatedLooter, firstCorePasser, secondCorePasser, closestTrigger)) + return true; + } + else if (bot == fourthCorePasser) + { + if (LineUpFourthCorePasser(firstCorePasser, secondCorePasser, thirdCorePasser, closestTrigger)) + return true; + } + } + else if (item && botAI->HasItemInInventory(ITEM_TAINTED_CORE)) { // Designated core looter logic--applicable only if cheat mode is on and thus looter is a bot if (bot == designatedLooter) @@ -2215,10 +2244,12 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) { const time_t now = std::time(nullptr); + // Track lastImbueAttempt is to prevent repeated throwing animations from multiple imbue attempts auto [it, inserted] = lastImbueAttempt.try_emplace(SSC_MAP_ID, now); if (inserted) { botAI->ImbueItem(item, firstCorePasser); + lastCoreInInventoryTime[SSC_MAP_ID] = now; ScheduleStoreCoreAfterImbue(botAI, bot, firstCorePasser); return true; } @@ -2226,6 +2257,7 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) { it->second = now; botAI->ImbueItem(item, firstCorePasser); + lastCoreInInventoryTime[SSC_MAP_ID] = now; ScheduleStoreCoreAfterImbue(botAI, bot, firstCorePasser); return true; } @@ -2242,6 +2274,8 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) if (inserted) { botAI->ImbueItem(item, secondCorePasser); + lastCoreInInventoryTime[SSC_MAP_ID] = now; + intendedLineup.erase(bot->GetGUID()); ScheduleStoreCoreAfterImbue(botAI, bot, secondCorePasser); return true; } @@ -2249,14 +2283,15 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) { it->second = now; botAI->ImbueItem(item, secondCorePasser); + lastCoreInInventoryTime[SSC_MAP_ID] = now; + intendedLineup.erase(bot->GetGUID()); ScheduleStoreCoreAfterImbue(botAI, bot, secondCorePasser); return true; } } } // Second core passer: if closest usable generator is within passing distance of the first passer, move - // to the generator; otherwise, move as close as possible to the generator while remaining in passing range - // Usually, the second core passer will be able to use the generator, but it depends on where the elemental spawns + // to the generator; otherwise, move as close as possible to the generator while staying in passing range else if (bot == secondCorePasser) { if (!UseCoreOnNearestGenerator()) @@ -2269,6 +2304,8 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) if (inserted) { botAI->ImbueItem(item, thirdCorePasser); + lastCoreInInventoryTime[SSC_MAP_ID] = now; + intendedLineup.erase(bot->GetGUID()); ScheduleStoreCoreAfterImbue(botAI, bot, thirdCorePasser); return true; } @@ -2276,6 +2313,8 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) { it->second = now; botAI->ImbueItem(item, thirdCorePasser); + lastCoreInInventoryTime[SSC_MAP_ID] = now; + intendedLineup.erase(bot->GetGUID()); ScheduleStoreCoreAfterImbue(botAI, bot, thirdCorePasser); return true; } @@ -2283,7 +2322,7 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) } } // Third core passer: if closest usable generator is within passing distance of the second passer, move - // to the generator; otherwise, move as close as possible to the generator while remaining in passing range + // to the generator; otherwise, move as close as possible to the generator while staying in passing range else if (bot == thirdCorePasser) { if (!UseCoreOnNearestGenerator()) @@ -2296,6 +2335,8 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) if (inserted) { botAI->ImbueItem(item, fourthCorePasser); + lastCoreInInventoryTime[SSC_MAP_ID] = now; + intendedLineup.erase(bot->GetGUID()); ScheduleStoreCoreAfterImbue(botAI, bot, fourthCorePasser); return true; } @@ -2303,13 +2344,15 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) { it->second = now; botAI->ImbueItem(item, fourthCorePasser); + lastCoreInInventoryTime[SSC_MAP_ID] = now; + intendedLineup.erase(bot->GetGUID()); ScheduleStoreCoreAfterImbue(botAI, bot, fourthCorePasser); return true; } } } } - // Fourth core passer: the fourth passer is rarely needed and will always be enough to reach a usable generator + // Fourth core passer: the fourth passer is rarely needed (and no more than four ever are) else if (bot == fourthCorePasser) { UseCoreOnNearestGenerator(); @@ -2319,29 +2362,11 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) return false; } -// Determine, by distance to the closest trigger, how many core passers are needed -uint8 LadyVashjPassTheTaintedCoreAction::ComputeNeededPassers(Player* designatedLooter, Unit* closestTrigger) -{ - if (!designatedLooter || !closestTrigger) - return static_cast(1); - - const float farDistance = 38.0f; - float dist = designatedLooter->GetExactDist2d(closestTrigger); - - int needed = 1; - if (farDistance > 0.0f) - needed = static_cast(std::ceil(dist / farDistance)); - - if (needed < 1) needed = 1; - if (needed > 4) needed = 4; - - return static_cast(needed); -} - -bool LadyVashjPassTheTaintedCoreAction::LineUpFirstCorePasser(Player* designatedLooter, Unit* closestTrigger) +bool LadyVashjPassTheTaintedCoreAction::LineUpFirstCorePasser( + Player* designatedLooter, Unit* closestTrigger) { - const float centerX = VashjPlatformCenterPosition.GetPositionX(); - const float centerY = VashjPlatformCenterPosition.GetPositionY(); + const float centerX = VASHJ_PLATFORM_CENTER_POSITION.GetPositionX(); + const float centerY = VASHJ_PLATFORM_CENTER_POSITION.GetPositionY(); const float radius = 57.5f; float mx = designatedLooter->GetPositionX(); @@ -2356,18 +2381,19 @@ bool LadyVashjPassTheTaintedCoreAction::LineUpFirstCorePasser(Player* designated bot->AttackStop(); bot->InterruptNonMeleeSpells(true); - return MoveTo(SSC_MAP_ID, targetX, targetY, targetZ, - false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); + return MoveTo(SSC_MAP_ID, targetX, targetY, targetZ, false, false, false, true, + MovementPriority::MOVEMENT_FORCED, true, false); } -bool LadyVashjPassTheTaintedCoreAction::LineUpSecondCorePasser(Player* firstCorePasser, Unit* closestTrigger) +bool LadyVashjPassTheTaintedCoreAction::LineUpSecondCorePasser( + Player* firstCorePasser, Unit* closestTrigger) { float fx = firstCorePasser->GetPositionX(); float fy = firstCorePasser->GetPositionY(); float dx = closestTrigger->GetPositionX() - fx; float dy = closestTrigger->GetPositionY() - fy; - float distToTrigger = std::sqrt(dx*dx + dy*dy); + float distToTrigger = firstCorePasser->GetExactDist2d(closestTrigger); if (distToTrigger == 0.0f) return false; @@ -2376,10 +2402,10 @@ bool LadyVashjPassTheTaintedCoreAction::LineUpSecondCorePasser(Player* firstCore // Target is on a line between firstCorePasser and closestTrigger float targetX, targetY, targetZ; - // if firstCorePasser is within this distance of closestTrigger, go to nearTriggerDist short of closestTrigger + // If firstCorePasser is within thresholdDist of closestTrigger, go to nearTriggerDist short of closestTrigger const float thresholdDist = 40.0f; const float nearTriggerDist = 1.5f; - // if firstCorePasser is not thresholdDist yards from closestTrigger, go to farDistance from firstCorePasser + // If firstCorePasser is not thresholdDist yards from closestTrigger, go to farDistance from firstCorePasser const float farDistance = 38.0f; if (distToTrigger <= thresholdDist) @@ -2400,18 +2426,29 @@ bool LadyVashjPassTheTaintedCoreAction::LineUpSecondCorePasser(Player* firstCore bot->AttackStop(); bot->InterruptNonMeleeSpells(false); - return MoveTo(SSC_MAP_ID, targetX, targetY, targetZ, - false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); + return MoveTo(SSC_MAP_ID, targetX, targetY, targetZ, false, false, false, true, + MovementPriority::MOVEMENT_FORCED, true, false); } -bool LadyVashjPassTheTaintedCoreAction::LineUpThirdCorePasser(Player* secondCorePasser, Unit* closestTrigger) +bool LadyVashjPassTheTaintedCoreAction::LineUpThirdCorePasser( + Player* designatedLooter, Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger) { + // Wait to move until it is clear that a third passer is needed + bool needThird = + (IsFirstCorePasserInIntendedPosition(designatedLooter, firstCorePasser, closestTrigger) && + firstCorePasser->GetExactDist2d(closestTrigger) > 42.0f) || + (IsSecondCorePasserInIntendedPosition(firstCorePasser, secondCorePasser, closestTrigger) && + secondCorePasser->GetExactDist2d(closestTrigger) > 4.0f); + + if (!needThird) + return false; + float sx = secondCorePasser->GetPositionX(); float sy = secondCorePasser->GetPositionY(); float dx = closestTrigger->GetPositionX() - sx; float dy = closestTrigger->GetPositionY() - sy; - float distToTrigger = std::sqrt(dx*dx + dy*dy); + float distToTrigger = secondCorePasser->GetExactDist2d(closestTrigger); if (distToTrigger == 0.0f) return false; @@ -2441,12 +2478,25 @@ bool LadyVashjPassTheTaintedCoreAction::LineUpThirdCorePasser(Player* secondCore bot->AttackStop(); bot->InterruptNonMeleeSpells(false); - return MoveTo(SSC_MAP_ID, targetX, targetY, targetZ, - false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); + return MoveTo(SSC_MAP_ID, targetX, targetY, targetZ, false, false, false, true, + MovementPriority::MOVEMENT_FORCED, true, false); + + return false; } -bool LadyVashjPassTheTaintedCoreAction::LineUpFourthCorePasser(Player* thirdCorePasser, Unit* closestTrigger) +bool LadyVashjPassTheTaintedCoreAction::LineUpFourthCorePasser( + Player* firstCorePasser, Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger) { + // Wait to move until it is clear that a fourth passer is needed + bool needFourth = + (IsSecondCorePasserInIntendedPosition(firstCorePasser, secondCorePasser, closestTrigger) && + secondCorePasser->GetExactDist2d(closestTrigger) > 42.0f) || + (IsThirdCorePasserInIntendedPosition(secondCorePasser, thirdCorePasser, closestTrigger) && + thirdCorePasser->GetExactDist2d(closestTrigger) > 4.0f); + + if (!needFourth) + return false; + float sx = thirdCorePasser->GetPositionX(); float sy = thirdCorePasser->GetPositionY(); @@ -2455,36 +2505,36 @@ bool LadyVashjPassTheTaintedCoreAction::LineUpFourthCorePasser(Player* thirdCore float dx = tx - sx; float dy = ty - sy; - float length = std::sqrt(dx*dx + dy*dy); + float distToTrigger = thirdCorePasser->GetExactDist2d(closestTrigger); - if (length == 0.0f) + if (distToTrigger == 0.0f) return false; - dx /= length; dy /= length; + dx /= distToTrigger; dy /= distToTrigger; - float targetX = tx - dx * 2.0f; - float targetY = ty - dy * 2.0f; + const float nearTriggerDist = 1.5f; + float targetX = tx - dx * nearTriggerDist; + float targetY = ty - dy * nearTriggerDist; const float targetZ = 42.985f; intendedLineup.insert_or_assign(bot->GetGUID(), Position(targetX, targetY, targetZ)); bot->AttackStop(); bot->InterruptNonMeleeSpells(false); - return MoveTo(SSC_MAP_ID, targetX, targetY, targetZ, - false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); + return MoveTo(SSC_MAP_ID, targetX, targetY, targetZ, false, false, false, true, + MovementPriority::MOVEMENT_FORCED, true, false); } -// The next four functions check if the respective core passer is within 2 yards of their intended position -// And are used to determine when the prior bot in the chain can pass the core -// Known issue: if a passer bot is feared by a strider, the chain can be broken; if this happens, it is best -// to order bots to destroy cores to reset the sequence for the next elemental spawn +// The next four functions check if the respective core passer is within 2 yards of their intended +// position and are used to determine when the prior bot in the chain can pass the core bool LadyVashjPassTheTaintedCoreAction::IsFirstCorePasserInIntendedPosition( Player* designatedLooter, Player* firstCorePasser, Unit* closestTrigger) { auto itSnap = intendedLineup.find(firstCorePasser->GetGUID()); if (itSnap != intendedLineup.end()) { - float dist2d = firstCorePasser->GetExactDist2d(itSnap->second.GetPositionX(), itSnap->second.GetPositionY()); + float dist2d = firstCorePasser->GetExactDist2d(itSnap->second.GetPositionX(), + itSnap->second.GetPositionY()); return dist2d <= 2.0f; } @@ -2497,7 +2547,8 @@ bool LadyVashjPassTheTaintedCoreAction::IsSecondCorePasserInIntendedPosition( auto itSnap = intendedLineup.find(secondCorePasser->GetGUID()); if (itSnap != intendedLineup.end()) { - float dist2d = secondCorePasser->GetExactDist2d(itSnap->second.GetPositionX(), itSnap->second.GetPositionY()); + float dist2d = secondCorePasser->GetExactDist2d(itSnap->second.GetPositionX(), + itSnap->second.GetPositionY()); return dist2d <= 2.0f; } @@ -2510,7 +2561,8 @@ bool LadyVashjPassTheTaintedCoreAction::IsThirdCorePasserInIntendedPosition( auto itSnap = intendedLineup.find(thirdCorePasser->GetGUID()); if (itSnap != intendedLineup.end()) { - float dist2d = thirdCorePasser->GetExactDist2d(itSnap->second.GetPositionX(), itSnap->second.GetPositionY()); + float dist2d = thirdCorePasser->GetExactDist2d(itSnap->second.GetPositionX(), + itSnap->second.GetPositionY()); return dist2d <= 2.0f; } @@ -2523,89 +2575,50 @@ bool LadyVashjPassTheTaintedCoreAction::IsFourthCorePasserInIntendedPosition( auto itSnap = intendedLineup.find(fourthCorePasser->GetGUID()); if (itSnap != intendedLineup.end()) { - float dist2d = fourthCorePasser->GetExactDist2d(itSnap->second.GetPositionX(), itSnap->second.GetPositionY()); + float dist2d = fourthCorePasser->GetExactDist2d(itSnap->second.GetPositionX(), + itSnap->second.GetPositionY()); return dist2d <= 2.0f; } return false; } -// Because ImbueItem() does not actually cause the receiving bot to receive the core, we have to simulate -// the passing mechanic by creating the core on the receiver (ImbueItem() does take away the core from the passer) -void LadyVashjPassTheTaintedCoreAction::ScheduleStoreCoreAfterImbue(PlayerbotAI* botAI, Player* giver, Player* receiver) +// ImbueItem() is inconsistent in causing the receiving bot to receive the core +// So ScheduleStoreCoreAfterImbue() simulates the passing mechanic by creating the core on the receiver +// However, ImbueItem() does always take away the core from the passer +void LadyVashjPassTheTaintedCoreAction::ScheduleStoreCoreAfterImbue( + PlayerbotAI* botAI, Player* giver, Player* receiver) { if (!receiver) return; const uint32 delayMs = 1500; - - const ObjectGuid giverGuid = giver ? giver->GetGUID() : ObjectGuid::Empty; const ObjectGuid receiverGuid = receiver->GetGUID(); - botAI->AddTimedEvent([botAI, giverGuid, receiverGuid]() + botAI->AddTimedEvent([receiverGuid]() { Player* receiverPlayer = receiverGuid.IsEmpty() ? nullptr : ObjectAccessor::FindPlayer(receiverGuid); - Player* giverPlayer = giverGuid.IsEmpty() ? nullptr : ObjectAccessor::FindPlayer(giverGuid); - if (!receiverPlayer) - { - intendedLineup.erase(receiverGuid); - intendedLineup.erase(giverGuid); return; - } - - // Detect if anyone already has the core - if (Group* group = receiverPlayer->GetGroup()) - { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - - if (!member) - continue; - - if (member->HasItemCount(ITEM_TAINTED_CORE, 1, false)) - { - intendedLineup.erase(receiverGuid); - intendedLineup.erase(giverGuid); - return; - } - } - } if (receiverPlayer->HasItemCount(ITEM_TAINTED_CORE, 1, false)) - { - intendedLineup.erase(receiverGuid); - intendedLineup.erase(giverGuid); return; - } - // Store a new core into receiver inventory (sends client/db update) ItemPosCountVec dest; uint32 count = 1; int canStore = receiverPlayer->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, ITEM_TAINTED_CORE, count); if (canStore == EQUIP_ERR_OK) { - Item* created = receiverPlayer->StoreNewItem(dest, ITEM_TAINTED_CORE, true, + receiverPlayer->StoreNewItem(dest, ITEM_TAINTED_CORE, true, Item::GenerateItemRandomPropertyId(ITEM_TAINTED_CORE)); - if (created) - { - intendedLineup.erase(receiverGuid); - intendedLineup.erase(giverGuid); - } - } - else - { - intendedLineup.erase(receiverGuid); - intendedLineup.erase(giverGuid); } }, delayMs); } bool LadyVashjPassTheTaintedCoreAction::UseCoreOnNearestGenerator() { - std::vector generators = GetAllGeneratorInfosByDbGuids(bot->GetMap(), SHIELD_GENERATOR_DB_GUIDS); + auto const& generators = GetAllGeneratorInfosByDbGuids(bot->GetMap(), SHIELD_GENERATOR_DB_GUIDS); const GeneratorInfo* nearestGen = GetNearestGeneratorToBot(bot, generators); if (!nearestGen) return false; @@ -2614,8 +2627,7 @@ bool LadyVashjPassTheTaintedCoreAction::UseCoreOnNearestGenerator() if (!generator) return false; - float dist = bot->GetExactDist2d(generator); - if (dist > 4.5f) + if (bot->GetExactDist2d(generator) > 4.5f) return false; if (Item* core = bot->GetItemByEntry(ITEM_TAINTED_CORE)) @@ -2669,13 +2681,11 @@ bool LadyVashjDestroyTaintedCoreAction::Execute(Event event) return false; } -// Custom Toxic Spore avoidance action -// The standard "avoid aoe" strategy does work, but I find it doesn't provide enough -// buffer distance for the toxic pools, and the standard strategy has a tendency to -// take bots down the stairs and get them stuck or out of LoS +// The standard "avoid aoe" strategy does work for Toxic Spores, but I find it doesn't provide enough +// buffer distance, and it has a tendency to take bots down the stairs and get them stuck or out of LoS bool LadyVashjAvoidToxicSporesAction::Execute(Event event) { - std::vector spores = GetAllSporeDropTriggers(botAI, bot); + auto const& spores = GetAllSporeDropTriggers(botAI, bot); if (spores.empty()) return false; @@ -2693,7 +2703,7 @@ bool LadyVashjAvoidToxicSporesAction::Execute(Event event) if (!inDanger) return false; - const Position& vashjCenter = VashjPlatformCenterPosition; + const Position& vashjCenter = VASHJ_PLATFORM_CENTER_POSITION; const float maxRadius = 60.0f; Position safestPos = FindSafestNearbyPosition(spores, vashjCenter, maxRadius, hazardRadius); @@ -2702,12 +2712,14 @@ bool LadyVashjAvoidToxicSporesAction::Execute(Event event) if (vashj && vashj->GetVictim() == bot) { return MoveTo(SSC_MAP_ID, safestPos.GetPositionX(), safestPos.GetPositionY(), - safestPos.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, true); + safestPos.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT, true, true); } else { return MoveTo(SSC_MAP_ID, safestPos.GetPositionX(), safestPos.GetPositionY(), - safestPos.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, false); + safestPos.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT, true, false); } } @@ -2800,15 +2812,17 @@ bool LadyVashjAvoidToxicSporesAction::IsPathSafeFromSpores(const Position& start } // When Toxic Sporebats spit poison, they summon "Spore Drop Trigger" NPCs that create the toxic pools -std::vector LadyVashjAvoidToxicSporesAction::GetAllSporeDropTriggers(PlayerbotAI* botAI, Player* bot) +std::vector LadyVashjAvoidToxicSporesAction::GetAllSporeDropTriggers( + PlayerbotAI* botAI, Player* bot) { std::vector sporeDropTriggers; - const GuidVector npcs = botAI->GetAiObjectContext()->GetValue("nearest npcs")->Get(); + auto const& npcs = botAI->GetAiObjectContext()->GetValue("nearest npcs")->Get(); for (auto const& npcGuid : npcs) { const float maxSearchRadius = 40.0f; Unit* unit = botAI->GetUnit(npcGuid); - if (unit && unit->GetEntry() == NPC_SPORE_DROP_TRIGGER && bot->GetExactDist2d(unit) < maxSearchRadius) + if (unit && unit->GetEntry() == NPC_SPORE_DROP_TRIGGER && + bot->GetExactDist2d(unit) < maxSearchRadius) sporeDropTriggers.push_back(unit); } @@ -2821,7 +2835,7 @@ bool LadyVashjUseFreeActionAbilitiesAction::Execute(Event event) if (!group) return false; - std::vector spores = LadyVashjAvoidToxicSporesAction::GetAllSporeDropTriggers(botAI, bot); + auto const& spores = LadyVashjAvoidToxicSporesAction::GetAllSporeDropTriggers(botAI, bot); const float toxicSporeRadius = 6.0f; // If Rogues are Entangled and either have Static Charge or are near a spore, use Cloak of Shadows @@ -2918,6 +2932,7 @@ bool LadyVashjManageTrackersAction::Execute(Event event) vashjRangedPositions.clear(); hasReachedVashjRangedPosition.clear(); + nearestTriggerGuid.clear(); lastImbueAttempt.clear(); intendedLineup.clear(); diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h index 9f8500ecb5..8e087bbba8 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h @@ -8,64 +8,56 @@ class UnderbogColossusEscapeToxicPoolAction : public MovementAction { public: - UnderbogColossusEscapeToxicPoolAction(PlayerbotAI* botAI, std::string const name = "underbog colossus escape toxic pool") : MovementAction(botAI, name) {} - + UnderbogColossusEscapeToxicPoolAction( + PlayerbotAI* botAI, std::string const name = "underbog colossus escape toxic pool") : MovementAction(botAI, name) {} bool Execute(Event event) override; }; class GreyheartTidecallerMarkWaterElementalTotemAction : public Action { public: - GreyheartTidecallerMarkWaterElementalTotemAction(PlayerbotAI* botAI, std::string const name = "greyheart tidecaller mark water elemental totem") : Action(botAI, name) {} - - bool Execute(Event event) override; -}; - -class RancidMushroomMoveAwayFromMushroomSporeCloudAction : public MovementAction -{ -public: - RancidMushroomMoveAwayFromMushroomSporeCloudAction(PlayerbotAI* botAI, std::string const name = "rancid mushroom move away from mushroom spore cloud") : MovementAction(botAI, name) {} - + GreyheartTidecallerMarkWaterElementalTotemAction( + PlayerbotAI* botAI, std::string const name = "greyheart tidecaller mark water elemental totem") : Action(botAI, name) {} bool Execute(Event event) override; }; class HydrossTheUnstablePositionFrostTankAction : public AttackAction { public: - HydrossTheUnstablePositionFrostTankAction(PlayerbotAI* botAI, std::string const name = "hydross the unstable position frost tank") : AttackAction(botAI, name) {} - + HydrossTheUnstablePositionFrostTankAction( + PlayerbotAI* botAI, std::string const name = "hydross the unstable position frost tank") : AttackAction(botAI, name) {} bool Execute(Event event) override; }; class HydrossTheUnstablePositionNatureTankAction : public AttackAction { public: - HydrossTheUnstablePositionNatureTankAction(PlayerbotAI* botAI, std::string const name = "hydross the unstable position nature tank") : AttackAction(botAI, name) {} - + HydrossTheUnstablePositionNatureTankAction( + PlayerbotAI* botAI, std::string const name = "hydross the unstable position nature tank") : AttackAction(botAI, name) {} bool Execute(Event event) override; }; class HydrossTheUnstablePrioritizeElementalAddsAction : public AttackAction { public: - HydrossTheUnstablePrioritizeElementalAddsAction(PlayerbotAI* botAI, std::string const name = "hydross the unstable prioritize elemental adds") : AttackAction(botAI, name) {} - + HydrossTheUnstablePrioritizeElementalAddsAction( + PlayerbotAI* botAI, std::string const name = "hydross the unstable prioritize elemental adds") : AttackAction(botAI, name) {} bool Execute(Event event) override; }; class HydrossTheUnstableFrostPhaseSpreadOutAction : public MovementAction { public: - HydrossTheUnstableFrostPhaseSpreadOutAction(PlayerbotAI* botAI, std::string const name = "hydross the unstable frost phase spread out") : MovementAction(botAI, name) {} - + HydrossTheUnstableFrostPhaseSpreadOutAction( + PlayerbotAI* botAI, std::string const name = "hydross the unstable frost phase spread out") : MovementAction(botAI, name) {} bool Execute(Event event) override; }; class HydrossTheUnstableMisdirectBossToTankAction : public Action { public: - HydrossTheUnstableMisdirectBossToTankAction(PlayerbotAI* botAI, std::string const name = "hydross the unstable misdirect boss to tank") : Action(botAI, name) {} - + HydrossTheUnstableMisdirectBossToTankAction( + PlayerbotAI* botAI, std::string const name = "hydross the unstable misdirect boss to tank") : Action(botAI, name) {} bool Execute(Event event) override; private: @@ -76,190 +68,208 @@ class HydrossTheUnstableMisdirectBossToTankAction : public Action class HydrossTheUnstableStopDpsUponPhaseChangeAction : public Action { public: - HydrossTheUnstableStopDpsUponPhaseChangeAction(PlayerbotAI* botAI, std::string const name = "hydross the unstable stop dps upon phase change") : Action(botAI, name) {} - + HydrossTheUnstableStopDpsUponPhaseChangeAction( + PlayerbotAI* botAI, std::string const name = "hydross the unstable stop dps upon phase change") : Action(botAI, name) {} bool Execute(Event event) override; }; class HydrossTheUnstableManageTimersAction : public Action { public: - HydrossTheUnstableManageTimersAction(PlayerbotAI* botAI, std::string const name = "hydross the unstable manage timers") : Action(botAI, name) {} - + HydrossTheUnstableManageTimersAction( + PlayerbotAI* botAI, std::string const name = "hydross the unstable manage timers") : Action(botAI, name) {} bool Execute(Event event) override; }; class TheLurkerBelowRunAroundBehindBossAction : public MovementAction { public: - TheLurkerBelowRunAroundBehindBossAction(PlayerbotAI* botAI, std::string const name = "the lurker below run around behind boss") : MovementAction(botAI, name) {} - + TheLurkerBelowRunAroundBehindBossAction( + PlayerbotAI* botAI, std::string const name = "the lurker below run around behind boss") : MovementAction(botAI, name) {} bool Execute(Event event) override; }; class TheLurkerBelowPositionMainTankAction : public AttackAction { public: - TheLurkerBelowPositionMainTankAction(PlayerbotAI* botAI, std::string const name = "the lurker below position main tank") : AttackAction(botAI, name) {} - + TheLurkerBelowPositionMainTankAction( + PlayerbotAI* botAI, std::string const name = "the lurker below position main tank") : AttackAction(botAI, name) {} bool Execute(Event event) override; }; class TheLurkerBelowSpreadRangedAction : public MovementAction { public: - TheLurkerBelowSpreadRangedAction(PlayerbotAI* botAI, std::string const name = "the lurker below spread ranged") : MovementAction(botAI, name) {} - + TheLurkerBelowSpreadRangedAction( + PlayerbotAI* botAI, std::string const name = "the lurker below spread ranged") : MovementAction(botAI, name) {} bool Execute(Event event) override; }; class TheLurkerBelowTanksPickUpAddsAction : public AttackAction { public: - TheLurkerBelowTanksPickUpAddsAction(PlayerbotAI* botAI, std::string const name = "the lurker below tanks pick up adds") : AttackAction(botAI, name) {} - + TheLurkerBelowTanksPickUpAddsAction( + PlayerbotAI* botAI, std::string const name = "the lurker below tanks pick up adds") : AttackAction(botAI, name) {} bool Execute(Event event) override; }; class TheLurkerBelowManageSpoutTimerAction : public Action { public: - TheLurkerBelowManageSpoutTimerAction(PlayerbotAI* botAI, std::string const name = "the lurker below manage spout timer") : Action(botAI, name) {} - + TheLurkerBelowManageSpoutTimerAction( + PlayerbotAI* botAI, std::string const name = "the lurker below manage spout timer") : Action(botAI, name) {} bool Execute(Event event) override; }; class LeotherasTheBlindTargetSpellbindersAction : public Action { public: - LeotherasTheBlindTargetSpellbindersAction(PlayerbotAI* botAI, std::string const name = "leotheras the blind target spellbinders") : Action(botAI, name) {} - + LeotherasTheBlindTargetSpellbindersAction( + PlayerbotAI* botAI, std::string const name = "leotheras the blind target spellbinders") : Action(botAI, name) {} bool Execute(Event event) override; }; class LeotherasTheBlindPositionRangedAction : public MovementAction { public: - LeotherasTheBlindPositionRangedAction(PlayerbotAI* botAI, std::string const name = "leotheras the blind position ranged") : MovementAction(botAI, name) {} - + LeotherasTheBlindPositionRangedAction( + PlayerbotAI* botAI, std::string const name = "leotheras the blind position ranged") : MovementAction(botAI, name) {} bool Execute(Event event) override; }; class LeotherasTheBlindDemonFormTankAttackBossAction : public AttackAction { public: - LeotherasTheBlindDemonFormTankAttackBossAction(PlayerbotAI* botAI, std::string const name = "leotheras the blind demon form tank attack boss") : AttackAction(botAI, name) {} - + LeotherasTheBlindDemonFormTankAttackBossAction( + PlayerbotAI* botAI, std::string const name = "leotheras the blind demon form tank attack boss") : AttackAction(botAI, name) {} bool Execute(Event event) override; }; class LeotherasTheBlindRunAwayFromWhirlwindAction : public MovementAction { public: - LeotherasTheBlindRunAwayFromWhirlwindAction(PlayerbotAI* botAI, std::string const name = "leotheras the blind run away from whirlwind") : MovementAction(botAI, name) {} + LeotherasTheBlindRunAwayFromWhirlwindAction( + PlayerbotAI* botAI, std::string const name = "leotheras the blind run away from whirlwind") : MovementAction(botAI, name) {} + bool Execute(Event event) override; +}; +class LeotherasTheBlindMeleeDpsRunAwayFromBossAction : public MovementAction +{ +public: + LeotherasTheBlindMeleeDpsRunAwayFromBossAction( + PlayerbotAI* botAI, std::string const name = "leotheras the blind melee dps run away from boss") : MovementAction(botAI, name) {} bool Execute(Event event) override; }; class LeotherasTheBlindInnerDemonCheatAction : public AttackAction { public: - LeotherasTheBlindInnerDemonCheatAction(PlayerbotAI* botAI, std::string const name = "leotheras the blind inner demon cheat") : AttackAction(botAI, name) {} - + LeotherasTheBlindInnerDemonCheatAction( + PlayerbotAI* botAI, std::string const name = "leotheras the blind inner demon cheat") : AttackAction(botAI, name) {} bool Execute(Event event) override; }; class LeotherasTheBlindFinalPhaseAssignDpsPriorityAction : public AttackAction { public: - LeotherasTheBlindFinalPhaseAssignDpsPriorityAction(PlayerbotAI* botAI, std::string const name = "leotheras the blind final phase assign dps priority") : AttackAction(botAI, name) {} - + LeotherasTheBlindFinalPhaseAssignDpsPriorityAction( + PlayerbotAI* botAI, std::string const name = "leotheras the blind final phase assign dps priority") : AttackAction(botAI, name) {} bool Execute(Event event) override; }; class LeotherasTheBlindMisdirectBossToDemonFormTankAction : public AttackAction { public: - LeotherasTheBlindMisdirectBossToDemonFormTankAction(PlayerbotAI* botAI, std::string const name = "leotheras the blind misdirect boss to demon form tank") : AttackAction(botAI, name) {} - + LeotherasTheBlindMisdirectBossToDemonFormTankAction( + PlayerbotAI* botAI, std::string const name = "leotheras the blind misdirect boss to demon form tank") : AttackAction(botAI, name) {} bool Execute(Event event) override; }; -class LeotherasTheBlindManageTimersAndTrackersAction : public Action +class LeotherasTheBlindManageDpsWaitTimersAction : public Action { public: - LeotherasTheBlindManageTimersAndTrackersAction(PlayerbotAI* botAI, std::string const name = "leotheras the blind manage timers and trackers") : Action(botAI, name) {} - + LeotherasTheBlindManageDpsWaitTimersAction( + PlayerbotAI* botAI, std::string const name = "leotheras the blind manage dps wait timers") : Action(botAI, name) {} bool Execute(Event event) override; }; class FathomLordKarathressMainTankPositionBossAction : public AttackAction { public: - FathomLordKarathressMainTankPositionBossAction(PlayerbotAI* botAI, std::string const name = "fathom-lord karathress main tank position boss") : AttackAction(botAI, name) {} + FathomLordKarathressMainTankPositionBossAction( + PlayerbotAI* botAI, std::string const name = "fathom-lord karathress main tank position boss") : AttackAction(botAI, name) {} bool Execute(Event event) override; }; class FathomLordKarathressFirstAssistTankPositionCaribdisAction : public AttackAction { public: - FathomLordKarathressFirstAssistTankPositionCaribdisAction(PlayerbotAI* botAI, std::string const name = "fathom-lord karathress first assist tank position caribdis") : AttackAction(botAI, name) {} + FathomLordKarathressFirstAssistTankPositionCaribdisAction( + PlayerbotAI* botAI, std::string const name = "fathom-lord karathress first assist tank position caribdis") : AttackAction(botAI, name) {} bool Execute(Event event) override; }; class FathomLordKarathressSecondAssistTankPositionSharkkisAction : public AttackAction { public: - FathomLordKarathressSecondAssistTankPositionSharkkisAction(PlayerbotAI* botAI, std::string const name = "fathom-lord karathress second assist tank position sharkkis") : AttackAction(botAI, name) {} + FathomLordKarathressSecondAssistTankPositionSharkkisAction( + PlayerbotAI* botAI, std::string const name = "fathom-lord karathress second assist tank position sharkkis") : AttackAction(botAI, name) {} bool Execute(Event event) override; }; class FathomLordKarathressThirdAssistTankPositionTidalvessAction : public AttackAction { public: - FathomLordKarathressThirdAssistTankPositionTidalvessAction(PlayerbotAI* botAI, std::string const name = "fathom-lord karathress third assist tank position tidalvess") : AttackAction(botAI, name) {} + FathomLordKarathressThirdAssistTankPositionTidalvessAction( + PlayerbotAI* botAI, std::string const name = "fathom-lord karathress third assist tank position tidalvess") : AttackAction(botAI, name) {} bool Execute(Event event) override; }; class FathomLordKarathressPositionCaribdisTankHealerAction : public MovementAction { public: - FathomLordKarathressPositionCaribdisTankHealerAction(PlayerbotAI* botAI, std::string const name = "fathom-lord karathress position caribdis tank healer") : MovementAction(botAI, name) {} + FathomLordKarathressPositionCaribdisTankHealerAction( + PlayerbotAI* botAI, std::string const name = "fathom-lord karathress position caribdis tank healer") : MovementAction(botAI, name) {} bool Execute(Event event) override; }; class FathomLordKarathressMisdirectBossesToTanksAction : public AttackAction { public: - FathomLordKarathressMisdirectBossesToTanksAction(PlayerbotAI* botAI, std::string const name = "fathom-lord karathress misdirect bosses to tanks") : AttackAction(botAI, name) {} + FathomLordKarathressMisdirectBossesToTanksAction( + PlayerbotAI* botAI, std::string const name = "fathom-lord karathress misdirect bosses to tanks") : AttackAction(botAI, name) {} bool Execute(Event event) override; }; class FathomLordKarathressAssignDpsPriorityAction : public AttackAction { public: - FathomLordKarathressAssignDpsPriorityAction(PlayerbotAI* botAI, std::string const name = "fathom-lord karathress assign dps priority") : AttackAction(botAI, name) {} + FathomLordKarathressAssignDpsPriorityAction( + PlayerbotAI* botAI, std::string const name = "fathom-lord karathress assign dps priority") : AttackAction(botAI, name) {} bool Execute(Event event) override; }; class FathomLordKarathressManageDpsTimerAction : public Action { public: - FathomLordKarathressManageDpsTimerAction(PlayerbotAI* botAI, std::string const name = "fathom-lord karathress manage dps timer") : Action(botAI, name) {} + FathomLordKarathressManageDpsTimerAction( + PlayerbotAI* botAI, std::string const name = "fathom-lord karathress manage dps timer") : Action(botAI, name) {} bool Execute(Event event) override; }; class MorogrimTidewalkerMisdirectBossToMainTankAction : public AttackAction { public: - MorogrimTidewalkerMisdirectBossToMainTankAction(PlayerbotAI* botAI, std::string const name = "morogrim tidewalker misdirect boss to main tank") : AttackAction(botAI, name) {} + MorogrimTidewalkerMisdirectBossToMainTankAction( + PlayerbotAI* botAI, std::string const name = "morogrim tidewalker misdirect boss to main tank") : AttackAction(botAI, name) {} bool Execute(Event event) override; }; class MorogrimTidewalkerMoveBossToTankPositionAction : public AttackAction { public: - MorogrimTidewalkerMoveBossToTankPositionAction(PlayerbotAI* botAI, std::string const name = "morogrim tidewalker move boss to tank position") : AttackAction(botAI, name) {} + MorogrimTidewalkerMoveBossToTankPositionAction( + PlayerbotAI* botAI, std::string const name = "morogrim tidewalker move boss to tank position") : AttackAction(botAI, name) {} bool Execute(Event event) override; private: @@ -270,102 +280,111 @@ class MorogrimTidewalkerMoveBossToTankPositionAction : public AttackAction class MorogrimTidewalkerPhase2RepositionRangedAction : public MovementAction { public: - MorogrimTidewalkerPhase2RepositionRangedAction(PlayerbotAI* botAI, std::string const name = "morogrim tidewalker phase 2 reposition ranged") : MovementAction(botAI, name) {} + MorogrimTidewalkerPhase2RepositionRangedAction( + PlayerbotAI* botAI, std::string const name = "morogrim tidewalker phase 2 reposition ranged") : MovementAction(botAI, name) {} bool Execute(Event event) override; }; class MorogrimTidewalkerResetPhaseTransitionStepsAction : public Action { public: - MorogrimTidewalkerResetPhaseTransitionStepsAction(PlayerbotAI* botAI, std::string const name = "morogrim tidewalker reset phase transition steps") : Action(botAI, name) {} + MorogrimTidewalkerResetPhaseTransitionStepsAction( + PlayerbotAI* botAI, std::string const name = "morogrim tidewalker reset phase transition steps") : Action(botAI, name) {} bool Execute(Event event) override; }; class LadyVashjMainTankPositionBossAction : public AttackAction { public: - LadyVashjMainTankPositionBossAction(PlayerbotAI* botAI, std::string const name = "lady vashj main tank position boss") : AttackAction(botAI, name) {} + LadyVashjMainTankPositionBossAction( + PlayerbotAI* botAI, std::string const name = "lady vashj main tank position boss") : AttackAction(botAI, name) {} bool Execute(Event event) override; }; class LadyVashjPhase1PositionRangedAction : public MovementAction { public: - LadyVashjPhase1PositionRangedAction(PlayerbotAI* botAI, std::string const name = "lady vashj phase 1 position ranged") : MovementAction(botAI, name) {} + LadyVashjPhase1PositionRangedAction( + PlayerbotAI* botAI, std::string const name = "lady vashj phase 1 position ranged") : MovementAction(botAI, name) {} bool Execute(Event event) override; }; class LadyVashjSetGroundingTotemInMainTankGroupAction : public MovementAction { public: - LadyVashjSetGroundingTotemInMainTankGroupAction(PlayerbotAI* botAI, std::string const name = "lady vashj set grounding totem in main tank group") : MovementAction(botAI, name) {} + LadyVashjSetGroundingTotemInMainTankGroupAction( + PlayerbotAI* botAI, std::string const name = "lady vashj set grounding totem in main tank group") : MovementAction(botAI, name) {} bool Execute(Event event) override; }; class LadyVashjStaticChargeMoveAwayFromGroupAction : public MovementAction { public: - LadyVashjStaticChargeMoveAwayFromGroupAction(PlayerbotAI* botAI, std::string const name = "lady vashj static charge move away from group") : MovementAction(botAI, name) {} + LadyVashjStaticChargeMoveAwayFromGroupAction( + PlayerbotAI* botAI, std::string const name = "lady vashj static charge move away from group") : MovementAction(botAI, name) {} bool Execute(Event event) override; }; class LadyVashjMisdirectBossToMainTankAction : public AttackAction { public: - LadyVashjMisdirectBossToMainTankAction(PlayerbotAI* botAI, std::string const name = "lady vashj misdirect boss to main tank") : AttackAction(botAI, name) {} + LadyVashjMisdirectBossToMainTankAction( + PlayerbotAI* botAI, std::string const name = "lady vashj misdirect boss to main tank") : AttackAction(botAI, name) {} bool Execute(Event event) override; }; class LadyVashjMisdirectStriderToFirstAssistTankAction : public AttackAction { public: - LadyVashjMisdirectStriderToFirstAssistTankAction(PlayerbotAI* botAI, std::string const name = "lady vashj misdirect strider to first assist tank") : AttackAction(botAI, name) {} + LadyVashjMisdirectStriderToFirstAssistTankAction( + PlayerbotAI* botAI, std::string const name = "lady vashj misdirect strider to first assist tank") : AttackAction(botAI, name) {} bool Execute(Event event) override; }; class LadyVashjTankAttackAndMoveAwayStriderAction : public AttackAction { public: - LadyVashjTankAttackAndMoveAwayStriderAction(PlayerbotAI* botAI, std::string const name = "lady vashj tank attack and move away strider") : AttackAction(botAI, name) {} + LadyVashjTankAttackAndMoveAwayStriderAction( + PlayerbotAI* botAI, std::string const name = "lady vashj tank attack and move away strider") : AttackAction(botAI, name) {} bool Execute(Event event) override; }; class LadyVashjAssignDpsPriorityAction : public AttackAction { public: - LadyVashjAssignDpsPriorityAction(PlayerbotAI* botAI, std::string const name = "lady vashj assign dps priority") : AttackAction(botAI, name) {} + LadyVashjAssignDpsPriorityAction( + PlayerbotAI* botAI, std::string const name = "lady vashj assign dps priority") : AttackAction(botAI, name) {} bool Execute(Event event) override; }; class LadyVashjTeleportToTaintedElementalAction : public AttackAction { public: - LadyVashjTeleportToTaintedElementalAction(PlayerbotAI* botAI, std::string const name = "lady vashj teleport to tainted elemental") : AttackAction(botAI, name) {} + LadyVashjTeleportToTaintedElementalAction( + PlayerbotAI* botAI, std::string const name = "lady vashj teleport to tainted elemental") : AttackAction(botAI, name) {} bool Execute(Event event) override; - -private: - ObjectGuid lastTaintedGuid; }; class LadyVashjLootTaintedCoreAction : public MovementAction { public: - LadyVashjLootTaintedCoreAction(PlayerbotAI* botAI, std::string const name = "lady vashj loot tainted core") : MovementAction(botAI, name) {} + LadyVashjLootTaintedCoreAction( + PlayerbotAI* botAI, std::string const name = "lady vashj loot tainted core") : MovementAction(botAI, name) {} bool Execute(Event event) override; }; class LadyVashjPassTheTaintedCoreAction : public MovementAction { public: - LadyVashjPassTheTaintedCoreAction(PlayerbotAI* botAI, std::string const name = "lady vashj pass the tainted core") : MovementAction(botAI, name) {} + LadyVashjPassTheTaintedCoreAction( + PlayerbotAI* botAI, std::string const name = "lady vashj pass the tainted core") : MovementAction(botAI, name) {} bool Execute(Event event) override; private: - uint8 ComputeNeededPassers(Player* designatedLooter, Unit* closestTrigger); bool LineUpFirstCorePasser(Player* designatedLooter, Unit* closestTrigger); bool LineUpSecondCorePasser(Player* firstCorePasser, Unit* closestTrigger); - bool LineUpThirdCorePasser(Player* secondCorePasser, Unit* closestTrigger); - bool LineUpFourthCorePasser(Player* thirdCorePasser, Unit* closestTrigger); + bool LineUpThirdCorePasser(Player* designatedLooter, Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger); + bool LineUpFourthCorePasser(Player* firstCorePasser, Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger); bool IsFirstCorePasserInIntendedPosition(Player* designatedLooter, Player* firstCorePasser, Unit* closestTrigger); bool IsSecondCorePasserInIntendedPosition(Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger); bool IsThirdCorePasserInIntendedPosition(Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger); diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp index 823d49db3b..4349b17f7e 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp @@ -26,31 +26,29 @@ namespace SerpentShrineCavernHelpers std::unordered_map vashjRangedPositions; std::unordered_map hasReachedVashjRangedPosition; + std::unordered_map nearestTriggerGuid; std::unordered_map intendedLineup; std::unordered_map lastImbueAttempt; std::unordered_map lastCoreInInventoryTime; - namespace SerpentShrineCavernPositions - { - const Position HydrossFrostTankPosition = { -236.669f, -358.352f, -0.828f }; - const Position HydrossNatureTankPosition = { -225.471f, -327.790f, -3.682f }; + const Position HYDROSS_FROST_TANK_POSITION = { -236.669f, -358.352f, -0.828f }; + const Position HYDROSS_NATURE_TANK_POSITION = { -225.471f, -327.790f, -3.682f }; - const Position LurkerMainTankPosition = { 23.706f, -406.038f, -19.686f }; + const Position LURKER_MAIN_TANK_POSITION = { 23.706f, -406.038f, -19.686f }; - const Position KarathressTankPosition = { 474.403f, -531.118f, -7.548f }; - const Position TidalvessTankPosition = { 511.282f, -501.162f, -13.158f }; - const Position SharkkisTankPosition = { 508.057f, -541.109f, -10.133f }; - const Position CaribdisTankPosition = { 464.462f, -475.820f, -13.158f }; - const Position CaribdisHealerPosition = { 466.203f, -503.201f, -13.158f }; - const Position CaribdisRangedDpsPosition = { 463.197f, -501.190f, -13.158f }; + const Position KARATHRESS_TANK_POSITION = { 474.403f, -531.118f, -7.548f }; + const Position TIDALVESS_TANK_POSITION = { 511.282f, -501.162f, -13.158f }; + const Position SHARKKIS_TANK_POSITION = { 508.057f, -541.109f, -10.133f }; + const Position CARIBDIS_TANK_POSITION = { 464.462f, -475.820f, -13.158f }; + const Position CARIBDIS_HEALER_POSITION = { 466.203f, -503.201f, -13.158f }; + const Position CARIBDIS_RANGED_DPS_POSITION = { 463.197f, -501.190f, -13.158f }; - const Position TidewalkerPhase1TankPosition = { 410.925f, -741.916f, -7.146f }; - const Position TidewalkerPhaseTransitionWaypoint = { 407.035f, -759.479f, -7.168f }; - const Position TidewalkerPhase2TankPosition = { 446.571f, -767.155f, -7.144f }; - const Position TidewalkerPhase2RangedPosition = { 432.595f, -766.288f, -7.145f }; + const Position TIDEWALKER_PHASE_1_TANK_POSITION = { 410.925f, -741.916f, -7.146f }; + const Position TIDEWALKER_PHASE_TRANSITION_WAYPOINT = { 407.035f, -759.479f, -7.168f }; + const Position TIDEWALKER_PHASE_2_TANK_POSITION = { 446.571f, -767.155f, -7.144f }; + const Position TIDEWALKER_PHASE_2_RANGED_POSITION = { 432.595f, -766.288f, -7.145f }; - const Position VashjPlatformCenterPosition = { 29.634f, -923.541f, 42.985f }; - } + const Position VASHJ_PLATFORM_CENTER_POSITION = { 29.634f, -923.541f, 42.985f }; void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId) { @@ -246,20 +244,19 @@ namespace SerpentShrineCavernHelpers Player* GetLeotherasDemonFormTank(PlayerbotAI* botAI, Player* bot) { - Group* group = bot->GetGroup(); - if (!group) - return nullptr; - - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + if (Group* group = bot->GetGroup()) { - Player* member = ref->GetSource(); - if (!member || !member->IsAlive()) - continue; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive()) + continue; - PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); - if (member->getClass() == CLASS_WARLOCK && - memberAI && memberAI->HasStrategy("tank", BotState::BOT_STATE_COMBAT)) - return member; + PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); + if (member->getClass() == CLASS_WARLOCK && + memberAI && memberAI->HasStrategy("tank", BotState::BOT_STATE_COMBAT)) + return member; + } } return nullptr; @@ -348,24 +345,18 @@ namespace SerpentShrineCavernHelpers bool AnyRecentCoreInInventory(Group* group, uint32 graceSeconds) { - if (!group) - return false; - - const time_t now = std::time(nullptr); - - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + if (group) { - Player* member = ref->GetSource(); - if (!member) - continue; - - if (member->IsAlive() && member->HasItemCount(ITEM_TAINTED_CORE, 1, false)) + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { - lastCoreInInventoryTime[SSC_MAP_ID] = now; - return true; + Player* member = ref->GetSource(); + if (member && member->HasItemCount(ITEM_TAINTED_CORE, 1, false)) + return true; } } + const time_t now = std::time(nullptr); + auto it = lastCoreInInventoryTime.find(SSC_MAP_ID); if (it != lastCoreInInventoryTime.end()) { @@ -587,13 +578,9 @@ namespace SerpentShrineCavernHelpers // Returns the nearest active Shield Generator to the bot // Active generators are powered by NPC_WORLD_INVISIBLE_TRIGGER creatures, which depawn after use - Unit* GetNearestActiveShieldGeneratorTriggerByEntry(Player* bot, Unit* reference) + Unit* GetNearestActiveShieldGeneratorTriggerByEntry(Unit* reference) { - if (!bot || !reference) - return nullptr; - - Map* map = bot->GetMap(); - if (!map) + if (!reference) return nullptr; std::list triggers; diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h index 2a2cdf3726..aadeb11119 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h @@ -38,6 +38,7 @@ namespace SerpentShrineCavernHelpers SPELL_WHIRLWIND = 37640, SPELL_WHIRLWIND_CHANNEL = 37641, SPELL_METAMORPHOSIS = 37673, + SPELL_CHAOS_BLAST = 37675, SPELL_INSIDIOUS_WHISPER = 37676, // Lady Vashj @@ -107,7 +108,7 @@ namespace SerpentShrineCavernHelpers ITEM_HEAVY_NETHERWEAVE_NET = 24269, }; - constexpr uint32 SSC_MAP_ID = 548; + const uint32 SSC_MAP_ID = 548; extern std::unordered_map hydrossFrostDpsWaitTimer; extern std::unordered_map hydrossNatureDpsWaitTimer; @@ -128,31 +129,29 @@ namespace SerpentShrineCavernHelpers extern std::unordered_map vashjRangedPositions; extern std::unordered_map hasReachedVashjRangedPosition; + extern std::unordered_map nearestTriggerGuid; extern std::unordered_map intendedLineup; extern std::unordered_map lastImbueAttempt; extern std::unordered_map lastCoreInInventoryTime; - namespace SerpentShrineCavernPositions - { - extern const Position HydrossFrostTankPosition; - extern const Position HydrossNatureTankPosition; + extern const Position HYDROSS_FROST_TANK_POSITION; + extern const Position HYDROSS_NATURE_TANK_POSITION; - extern const Position LurkerMainTankPosition; + extern const Position LURKER_MAIN_TANK_POSITION; - extern const Position KarathressTankPosition; - extern const Position TidalvessTankPosition; - extern const Position SharkkisTankPosition; - extern const Position CaribdisTankPosition; - extern const Position CaribdisHealerPosition; - extern const Position CaribdisRangedDpsPosition; + extern const Position KARATHRESS_TANK_POSITION; + extern const Position TIDALVESS_TANK_POSITION; + extern const Position SHARKKIS_TANK_POSITION; + extern const Position CARIBDIS_TANK_POSITION; + extern const Position CARIBDIS_HEALER_POSITION; + extern const Position CARIBDIS_RANGED_DPS_POSITION; - extern const Position TidewalkerPhase1TankPosition; - extern const Position TidewalkerPhaseTransitionWaypoint; - extern const Position TidewalkerPhase2TankPosition; - extern const Position TidewalkerPhase2RangedPosition; + extern const Position TIDEWALKER_PHASE_1_TANK_POSITION; + extern const Position TIDEWALKER_PHASE_TRANSITION_WAYPOINT; + extern const Position TIDEWALKER_PHASE_2_TANK_POSITION; + extern const Position TIDEWALKER_PHASE_2_RANGED_POSITION; - extern const Position VashjPlatformCenterPosition; - } + extern const Position VASHJ_PLATFORM_CENTER_POSITION; void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId); void MarkTargetWithSkull(Player* bot, Unit* target); @@ -200,7 +199,7 @@ namespace SerpentShrineCavernHelpers extern const std::vector SHIELD_GENERATOR_DB_GUIDS; std::vector GetAllGeneratorInfosByDbGuids(Map* map, const std::vector& generatorDbGuids); - Unit* GetNearestActiveShieldGeneratorTriggerByEntry(Player* bot, Unit* reference); + Unit* GetNearestActiveShieldGeneratorTriggerByEntry(Unit* reference); const GeneratorInfo* GetNearestGeneratorToBot(Player* bot, const std::vector& generators); } diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp index 87069b612d..dd8ccdaafe 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp @@ -42,17 +42,21 @@ float UnderbogColossusEscapeToxicPoolMultiplier::GetValue(Action* action) float HydrossTheUnstableDisableTankActionsMultiplier::GetValue(Action* action) { Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross"); - if (!hydross || dynamic_cast(action)) + if (!hydross) return 1.0f; - if (dynamic_cast(action)) - return 0.0f; + if (botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0)) + { + if (dynamic_cast(action)) + return 0.0f; + } if (botAI->IsMainTank(bot)) { if (hydross->HasAura(SPELL_CORRUPTION)) { - if (!dynamic_cast(action)) + if (dynamic_cast(action) && + !dynamic_cast(action)) return 0.0f; } } @@ -61,7 +65,8 @@ float HydrossTheUnstableDisableTankActionsMultiplier::GetValue(Action* action) { if (!hydross->HasAura(SPELL_CORRUPTION)) { - if (!dynamic_cast(action)) + if (dynamic_cast(action) && + !dynamic_cast(action)) return 0.0f; } } @@ -75,9 +80,6 @@ float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action) if (!hydross) return 1.0f; - if (botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0)) - return 1.0f; - Unit* waterElemental = AI_VALUE2(Unit*, "find target", "pure spawn of hydross"); Unit* natureElemental = AI_VALUE2(Unit*, "find target", "tainted spawn of hydross"); if (botAI->IsAssistTank(bot) && !botAI->IsAssistTankOfIndex(bot, 0) && @@ -88,8 +90,8 @@ float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action) return 1.0f; const time_t now = std::time(nullptr); + const uint8 phaseChangeWaitSeconds = 1; const uint8 dpsWaitSeconds = 5; - const uint8 phaseChangeWaitSeconds = 6; if (!hydross->HasAura(SPELL_CORRUPTION)) { @@ -99,23 +101,22 @@ float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action) !dynamic_cast(action)) return 0.0f; } - else if (botAI->IsTank(bot)) - return 1.0f; - - auto itDps = hydrossFrostDpsWaitTimer.find(SSC_MAP_ID); - auto itPhase = hydrossChangeToFrostPhaseTimer.find(SSC_MAP_ID); - - bool justChanged = (itDps == hydrossFrostDpsWaitTimer.end() || - (now - itDps->second) < dpsWaitSeconds); + else if (!botAI->IsMainTank(bot)) + { + auto itDps = hydrossFrostDpsWaitTimer.find(SSC_MAP_ID); + auto itPhase = hydrossChangeToFrostPhaseTimer.find(SSC_MAP_ID); - bool aboutToChange = (itPhase != hydrossChangeToFrostPhaseTimer.end() && - (now - itPhase->second) > phaseChangeWaitSeconds); + bool justChanged = (itDps == hydrossFrostDpsWaitTimer.end() || + (now - itDps->second) < dpsWaitSeconds); + bool aboutToChange = (itPhase != hydrossChangeToFrostPhaseTimer.end() && + (now - itPhase->second) > phaseChangeWaitSeconds); - if (justChanged || aboutToChange) - { - if (dynamic_cast(action) || - (dynamic_cast(action) && !dynamic_cast(action))) - return 0.0f; + if (justChanged || aboutToChange) + { + if (dynamic_cast(action) || + (dynamic_cast(action) && !dynamic_cast(action))) + return 0.0f; + } } } @@ -124,26 +125,26 @@ float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action) if (botAI->IsMainTank(bot)) { if (dynamic_cast(action) && - !dynamic_cast(action)) + !dynamic_cast(action)) return 0.0f; } - else if (botAI->IsTank(bot)) - return 1.0f; - - auto itDps = hydrossNatureDpsWaitTimer.find(SSC_MAP_ID); - auto itPhase = hydrossChangeToNaturePhaseTimer.find(SSC_MAP_ID); + else if (!botAI->IsAssistTankOfIndex(bot, 0)) + { + auto itDps = hydrossNatureDpsWaitTimer.find(SSC_MAP_ID); + auto itPhase = hydrossChangeToNaturePhaseTimer.find(SSC_MAP_ID); - bool justChanged = (itDps == hydrossNatureDpsWaitTimer.end() || - (now - itDps->second) < dpsWaitSeconds); + bool justChanged = (itDps == hydrossNatureDpsWaitTimer.end() || + (now - itDps->second) < dpsWaitSeconds); + bool aboutToChange = (itPhase != hydrossChangeToNaturePhaseTimer.end() && + (now - itPhase->second) > phaseChangeWaitSeconds); - bool aboutToChange = (itPhase != hydrossChangeToNaturePhaseTimer.end() && - (now - itPhase->second) > phaseChangeWaitSeconds); - if (justChanged || aboutToChange) - { - if (dynamic_cast(action) || - (dynamic_cast(action) && !dynamic_cast(action))) - return 0.0f; + if (justChanged || aboutToChange) + { + if (dynamic_cast(action) || + (dynamic_cast(action) && !dynamic_cast(action))) + return 0.0f; + } } } @@ -207,8 +208,11 @@ float TheLurkerBelowDisableTankAssistMultiplier::GetValue(Action* action) ++tankCount; } - if (tankCount >= 3 && dynamic_cast(action)) - return 0.0f; + if (tankCount >= 3) + { + if (bot->GetVictim() != nullptr && dynamic_cast(action)) + return 0.0f; + } return 1.0f; } @@ -242,21 +246,49 @@ float LeotherasTheBlindAvoidWhirlwindMultiplier::GetValue(Action* action) float LeotherasTheBlindDisableTankActionsMultiplier::GetValue(Action* action) { Unit* leotherasDemon = GetActiveLeotherasDemon(botAI); - if (!leotherasDemon || - dynamic_cast(action) || - dynamic_cast(action)) + if (!leotherasDemon) + return 1.0f; + + Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); + if (!demonFormTank) return 1.0f; // (1) Warlock tank will not use Shadow Ward // Shadow Ward is coded into the Warlock tank strategy (for Twin Emps) but is useless here - Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); - if (demonFormTank && dynamic_cast(action)) + if (dynamic_cast(action)) return 0.0f; // (2) Phase 2 only: Tanks other than the Warlock tank should do absolutely nothing Unit* leotherasDemonPhase2 = GetPhase2LeotherasDemon(botAI); if (botAI->IsTank(bot) && bot != demonFormTank && leotherasDemonPhase2) - return 0.0f; + { + if ((dynamic_cast(action) && + !dynamic_cast(action)) || + dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + +float LeotherasTheBlindMeleeDpsAvoidChaosBlastMultiplier::GetValue(Action* action) +{ + Unit* leotherasDemonPhase2 = GetPhase2LeotherasDemon(botAI); + if (!leotherasDemonPhase2) + return 1.0f; + + if (botAI->IsMelee(bot) && !botAI->IsTank(bot)) + { + Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST); + if (chaosBlast && chaosBlast->GetStackAmount() >= 5) + { + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + } + } return 1.0f; } @@ -294,7 +326,10 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); if (leotherasPhase2Demon) { - if (demonFormTank == bot) + if (demonFormTank && demonFormTank == bot) + return 1.0f; + + if (!demonFormTank && botAI->IsTank(bot)) return 1.0f; auto it = leotherasDemonFormDpsWaitTimer.find(SSC_MAP_ID); @@ -309,7 +344,7 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) const uint8 dpsWaitSecondsPhase3 = 8; if (leotherasPhase3Demon) { - if (demonFormTank == bot || botAI->IsTank(bot)) + if ((demonFormTank && demonFormTank == bot) || botAI->IsTank(bot)) return 1.0f; auto it = leotherasFinalPhaseDpsWaitTimer.find(SSC_MAP_ID); @@ -349,7 +384,7 @@ float FathomLordKarathressDisableTankAssistMultiplier::GetValue(Action* action) if (!karathress) return 1.0f; - if (dynamic_cast(action)) + if (bot->GetVictim() != nullptr && dynamic_cast(action)) return 0.0f; return 1.0f; @@ -475,9 +510,12 @@ float LadyVashjStaticChargeStayAwayFromGroupMultiplier::GetValue(Action* action) if (!botAI->IsMainTank(bot) && bot->HasAura(SPELL_STATIC_CHARGE)) { - if ((dynamic_cast(action) && - !dynamic_cast(action)) || - dynamic_cast(action) || + if (dynamic_cast(action) && + !dynamic_cast(action) && + !dynamic_cast(action)) + return 0.0f; + + if (dynamic_cast(action) || dynamic_cast(action)) return 0.0f; } @@ -527,8 +565,7 @@ float LadyVashjCorePassersPrioritizePositioningMultiplier::GetValue(Action* acti if (bot == designatedLooter) { - if (hasCore(firstCorePasser) || hasCore(secondCorePasser) || - hasCore(thirdCorePasser) || hasCore(fourthCorePasser)) + if (!hasCore(bot)) return 1.0f; } else if (bot == firstCorePasser) @@ -567,7 +604,7 @@ float LadyVashjCorePassersPrioritizePositioningMultiplier::GetValue(Action* acti return 1.0f; } -// All of phase 2 and 3 require a custom movement and targeting system +// All of phases 2 and 3 require a custom movement and targeting system // So the standard target selection system must be disabled float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *action) { diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h index 207b1905a2..68d3578552 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h @@ -6,154 +6,184 @@ class UnderbogColossusEscapeToxicPoolMultiplier : public Multiplier { public: - UnderbogColossusEscapeToxicPoolMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "underbog colossus escape toxic pool") {} + UnderbogColossusEscapeToxicPoolMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "underbog colossus escape toxic pool") {} virtual float GetValue(Action* action); }; class HydrossTheUnstableDisableTankActionsMultiplier : public Multiplier { public: - HydrossTheUnstableDisableTankActionsMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "hydross the unstable disable tank actions") {} + HydrossTheUnstableDisableTankActionsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "hydross the unstable disable tank actions") {} virtual float GetValue(Action* action); }; class HydrossTheUnstableWaitForDpsMultiplier : public Multiplier { public: - HydrossTheUnstableWaitForDpsMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "hydross the unstable wait for dps") {} + HydrossTheUnstableWaitForDpsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "hydross the unstable wait for dps") {} virtual float GetValue(Action* action); }; class HydrossTheUnstableControlMisdirectionMultiplier : public Multiplier { public: - HydrossTheUnstableControlMisdirectionMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "hydross the unstable control misdirection") {} + HydrossTheUnstableControlMisdirectionMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "hydross the unstable control misdirection") {} virtual float GetValue(Action* action); }; class TheLurkerBelowStayAwayFromSpoutMultiplier : public Multiplier { public: - TheLurkerBelowStayAwayFromSpoutMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "the lurker below stay away from spout") {} + TheLurkerBelowStayAwayFromSpoutMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "the lurker below stay away from spout") {} virtual float GetValue(Action* action); }; class TheLurkerBelowDisableTankAssistMultiplier : public Multiplier { public: - TheLurkerBelowDisableTankAssistMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "the lurker below disable tank assist") {} + TheLurkerBelowDisableTankAssistMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "the lurker below disable tank assist") {} virtual float GetValue(Action* action); }; class LeotherasTheBlindAvoidWhirlwindMultiplier : public Multiplier { public: - LeotherasTheBlindAvoidWhirlwindMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind avoid whirlwind") {} + LeotherasTheBlindAvoidWhirlwindMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind avoid whirlwind") {} virtual float GetValue(Action* action); }; class LeotherasTheBlindDisableTankActionsMultiplier : public Multiplier { public: - LeotherasTheBlindDisableTankActionsMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind disable tank actions") {} + LeotherasTheBlindDisableTankActionsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind disable tank actions") {} + virtual float GetValue(Action* action); +}; + +class LeotherasTheBlindMeleeDpsAvoidChaosBlastMultiplier : public Multiplier +{ +public: + LeotherasTheBlindMeleeDpsAvoidChaosBlastMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind melee dps avoid chaos blast") {} virtual float GetValue(Action* action); }; class LeotherasTheBlindWaitForDpsMultiplier : public Multiplier { public: - LeotherasTheBlindWaitForDpsMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind wait for dps") {} + LeotherasTheBlindWaitForDpsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind wait for dps") {} virtual float GetValue(Action* action); }; class LeotherasTheBlindDelayBloodlustAndHeroismMultiplier : public Multiplier { public: - LeotherasTheBlindDelayBloodlustAndHeroismMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind delay bloodlust and heroism") {} + LeotherasTheBlindDelayBloodlustAndHeroismMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind delay bloodlust and heroism") {} virtual float GetValue(Action* action); }; class FathomLordKarathressDisableTankAssistMultiplier : public Multiplier { public: - FathomLordKarathressDisableTankAssistMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "fathom-lord karathress disable tank assist") {} + FathomLordKarathressDisableTankAssistMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "fathom-lord karathress disable tank assist") {} virtual float GetValue(Action* action); }; class FathomLordKarathressDisableAoeMultiplier : public Multiplier { public: - FathomLordKarathressDisableAoeMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "fathom-lord karathress disable aoe") {} + FathomLordKarathressDisableAoeMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "fathom-lord karathress disable aoe") {} virtual float GetValue(Action* action); }; class FathomLordKarathressControlMisdirectionMultiplier : public Multiplier { public: - FathomLordKarathressControlMisdirectionMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "fathom-lord karathress control misdirection") {} + FathomLordKarathressControlMisdirectionMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "fathom-lord karathress control misdirection") {} virtual float GetValue(Action* action); }; class FathomLordKarathressWaitForDpsMultiplier : public Multiplier { public: - FathomLordKarathressWaitForDpsMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "fathom-lord karathress wait for dps") {} + FathomLordKarathressWaitForDpsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "fathom-lord karathress wait for dps") {} virtual float GetValue(Action* action); }; class FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier : public Multiplier { public: - FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "fathom-lord karathress caribdis tank healer maintain position") {} + FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "fathom-lord karathress caribdis tank healer maintain position") {} virtual float GetValue(Action* action); }; class MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier : public Multiplier { public: - MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "morogrim tidewalker delay bloodlust and heroism") {} + MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "morogrim tidewalker delay bloodlust and heroism") {} virtual float GetValue(Action* action); }; class MorogrimTidewalkerDisablePhase2MovementActionsMultiplier : public Multiplier { public: - MorogrimTidewalkerDisablePhase2MovementActionsMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "morogrim tidewalker disable phase2 movement actions") {} + MorogrimTidewalkerDisablePhase2MovementActionsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "morogrim tidewalker disable phase2 movement actions") {} virtual float GetValue(Action* action); }; class LadyVashjDelayBloodlustAndHeroismMultiplier : public Multiplier { public: - LadyVashjDelayBloodlustAndHeroismMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj delay bloodlust and heroism") {} + LadyVashjDelayBloodlustAndHeroismMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj delay bloodlust and heroism") {} virtual float GetValue(Action* action); }; class LadyVashjStaticChargeStayAwayFromGroupMultiplier : public Multiplier { public: - LadyVashjStaticChargeStayAwayFromGroupMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj static charge stay away from group") {} + LadyVashjStaticChargeStayAwayFromGroupMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj static charge stay away from group") {} virtual float GetValue(Action* action); }; class LadyVashjDoNotLootTheTaintedCoreMultiplier : public Multiplier { public: - LadyVashjDoNotLootTheTaintedCoreMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj do not loot the tainted core") {} + LadyVashjDoNotLootTheTaintedCoreMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj do not loot the tainted core") {} virtual float GetValue(Action* action); }; class LadyVashjCorePassersPrioritizePositioningMultiplier : public Multiplier { public: - LadyVashjCorePassersPrioritizePositioningMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj core passers prioritize positioning") {} + LadyVashjCorePassersPrioritizePositioningMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj core passers prioritize positioning") {} virtual float GetValue(Action* action); }; class LadyVashjDisableAutomaticTargetingAndMovementModifier : public Multiplier { public: - LadyVashjDisableAutomaticTargetingAndMovementModifier(PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj disable automatic targeting and movement") {} + LadyVashjDisableAutomaticTargetingAndMovementModifier( + PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj disable automatic targeting and movement") {} virtual float GetValue(Action* action); }; diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp index b9c80ed414..b2e183ea13 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp @@ -64,6 +64,9 @@ void RaidSSCStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("leotheras the blind boss channeling whirlwind", NextAction::array(0, new NextAction("leotheras the blind run away from whirlwind", ACTION_EMERGENCY + 1), nullptr) )); + triggers.push_back(new TriggerNode("leotheras the blind bot has too many chaos blast stacks", + NextAction::array(0, new NextAction("leotheras the blind melee dps run away from boss", ACTION_EMERGENCY + 7), nullptr) + )); triggers.push_back(new TriggerNode("leotheras the blind inner demon cheat", NextAction::array(0, new NextAction("leotheras the blind inner demon cheat", ACTION_EMERGENCY + 6), nullptr) )); @@ -73,8 +76,8 @@ void RaidSSCStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("leotheras the blind demon form tank needs aggro", NextAction::array(0, new NextAction("leotheras the blind misdirect boss to demon form tank", ACTION_RAID + 3), nullptr) )); - triggers.push_back(new TriggerNode("leotheras the blind need to manage timers and trackers", - NextAction::array(0, new NextAction("leotheras the blind manage timers and trackers", ACTION_EMERGENCY + 10), nullptr) + triggers.push_back(new TriggerNode("leotheras the blind boss wipes aggro upon phase change", + NextAction::array(0, new NextAction("leotheras the blind manage dps wait timers", ACTION_EMERGENCY + 10), nullptr) )); // Fathom-Lord Karathress @@ -173,6 +176,7 @@ void RaidSSCStrategy::InitMultipliers(std::vector& multipliers) multipliers.push_back(new TheLurkerBelowDisableTankAssistMultiplier(botAI)); multipliers.push_back(new LeotherasTheBlindAvoidWhirlwindMultiplier(botAI)); multipliers.push_back(new LeotherasTheBlindDisableTankActionsMultiplier(botAI)); + multipliers.push_back(new LeotherasTheBlindMeleeDpsAvoidChaosBlastMultiplier(botAI)); multipliers.push_back(new LeotherasTheBlindWaitForDpsMultiplier(botAI)); multipliers.push_back(new LeotherasTheBlindDelayBloodlustAndHeroismMultiplier(botAI)); multipliers.push_back(new FathomLordKarathressDisableTankAssistMultiplier(botAI)); diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h index 956bc17213..31a8fbb015 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h @@ -9,115 +9,309 @@ class RaidSSCTriggerContext : public NamedObjectContext public: RaidSSCTriggerContext() { - creators["underbog colossus spawned toxic pool after death"] = &RaidSSCTriggerContext::underbog_colossus_spawned_toxic_pool_after_death; - creators["greyheart tidecaller water elemental totem spawned"] = &RaidSSCTriggerContext::greyheart_tidecaller_water_elemental_totem_spawned; - - creators["hydross the unstable bot is frost tank"] = &RaidSSCTriggerContext::hydross_the_unstable_bot_is_frost_tank; - creators["hydross the unstable bot is nature tank"] = &RaidSSCTriggerContext::hydross_the_unstable_bot_is_nature_tank; - creators["hydross the unstable elementals spawned"] = &RaidSSCTriggerContext::hydross_the_unstable_elementals_spawned; - creators["hydross the unstable danger from water tombs"] = &RaidSSCTriggerContext::hydross_the_unstable_danger_from_water_tombs; - creators["hydross the unstable tank needs aggro upon phase change"] = &RaidSSCTriggerContext::hydross_the_unstable_tank_needs_aggro_upon_phase_change; - creators["hydross the unstable aggro resets upon phase change"] = &RaidSSCTriggerContext::hydross_the_unstable_aggro_resets_upon_phase_change; - creators["hydross the unstable need to manage timers"] = &RaidSSCTriggerContext::hydross_the_unstable_need_to_manage_timers; - - creators["the lurker below spout is active"] = &RaidSSCTriggerContext::the_lurker_below_spout_is_active; - creators["the lurker below boss is active for main tank"] = &RaidSSCTriggerContext::the_lurker_below_boss_is_active_for_main_tank; - creators["the lurker below boss casts geyser"] = &RaidSSCTriggerContext::the_lurker_below_boss_casts_geyser; - creators["the lurker below boss is submerged"] = &RaidSSCTriggerContext::the_lurker_below_boss_is_submerged; - creators["the lurker below need to prepare timer for spout"] = &RaidSSCTriggerContext::the_lurker_below_need_to_prepare_timer_for_spout; - - creators["leotheras the blind boss is inactive"] = &RaidSSCTriggerContext::leotheras_the_blind_boss_is_inactive; - creators["leotheras the blind boss transformed into demon form"] = &RaidSSCTriggerContext::leotheras_the_blind_boss_transformed_into_demon_form; - creators["leotheras the blind boss engaged by ranged"] = &RaidSSCTriggerContext::leotheras_the_blind_boss_engaged_by_ranged; - creators["leotheras the blind boss channeling whirlwind"] = &RaidSSCTriggerContext::leotheras_the_blind_boss_channeling_whirlwind; - creators["leotheras the blind inner demon cheat"] = &RaidSSCTriggerContext::leotheras_the_blind_inner_demon_cheat; - creators["leotheras the blind entered final phase"] = &RaidSSCTriggerContext::leotheras_the_blind_entered_final_phase; - creators["leotheras the blind demon form tank needs aggro"] = &RaidSSCTriggerContext::leotheras_the_blind_demon_form_tank_needs_aggro; - creators["leotheras the blind need to manage timers and trackers"] = &RaidSSCTriggerContext::leotheras_the_blind_need_to_manage_timers_and_trackers; - - creators["fathom-lord karathress boss engaged by main tank"] = &RaidSSCTriggerContext::fathom_lord_karathress_boss_engaged_by_main_tank; - creators["fathom-lord karathress caribdis engaged by first assist tank"] = &RaidSSCTriggerContext::fathom_lord_karathress_caribdis_engaged_by_first_assist_tank; - creators["fathom-lord karathress sharkkis engaged by second assist tank"] = &RaidSSCTriggerContext::fathom_lord_karathress_sharkkis_engaged_by_second_assist_tank; - creators["fathom-lord karathress tidalvess engaged by third assist tank"] = &RaidSSCTriggerContext::fathom_lord_karathress_tidalvess_engaged_by_third_assist_tank; - creators["fathom-lord karathress caribdis tank needs dedicated healer"] = &RaidSSCTriggerContext::fathom_lord_karathress_caribdis_tank_needs_dedicated_healer; - creators["fathom-lord karathress pulling bosses"] = &RaidSSCTriggerContext::fathom_lord_karathress_pulling_bosses; - creators["fathom-lord karathress determining kill order"] = &RaidSSCTriggerContext::fathom_lord_karathress_determining_kill_order; - creators["fathom-lord karathress tanks need to establish aggro"] = &RaidSSCTriggerContext::fathom_lord_karathress_tanks_need_to_establish_aggro; - - creators["morogrim tidewalker boss engaged by main tank"] = &RaidSSCTriggerContext::morogrim_tidewalker_boss_engaged_by_main_tank; - creators["morogrim tidewalker pulling boss"] = &RaidSSCTriggerContext::morogrim_tidewalker_pulling_boss; - creators["morogrim tidewalker water globules are incoming"] = &RaidSSCTriggerContext::morogrim_tidewalker_water_globules_are_incoming; - creators["morogrim tidewalker encounter reset"] = &RaidSSCTriggerContext::morogrim_tidewalker_encounter_reset; - - creators["lady vashj boss engaged by main tank"] = &RaidSSCTriggerContext::lady_vashj_boss_engaged_by_main_tank; - creators["lady vashj boss engaged by ranged in phase 1"] = &RaidSSCTriggerContext::lady_vashj_boss_engaged_by_ranged_in_phase_1; - creators["lady vashj casts shock blast on highest aggro"] = &RaidSSCTriggerContext::lady_vashj_casts_shock_blast_on_highest_aggro; - creators["lady vashj bot has static charge"] = &RaidSSCTriggerContext::lady_vashj_bot_has_static_charge; - creators["lady vashj pulling boss in phase 1 and phase 3"] = &RaidSSCTriggerContext::lady_vashj_pulling_boss_in_phase_1_and_phase_3; - creators["lady vashj coilfang strider is approaching"] = &RaidSSCTriggerContext::lady_vashj_coilfang_strider_is_approaching; - creators["lady vashj determining kill order of adds"] = &RaidSSCTriggerContext::lady_vashj_determining_kill_order_of_adds; - creators["lady vashj tainted elemental cheat"] = &RaidSSCTriggerContext::lady_vashj_tainted_elemental_cheat; - creators["lady vashj tainted core was looted"] = &RaidSSCTriggerContext::lady_vashj_tainted_core_was_looted; - creators["lady vashj tainted core is unusable"] = &RaidSSCTriggerContext::lady_vashj_tainted_core_is_unusable; - creators["lady vashj toxic sporebats are spewing poison clouds"] = &RaidSSCTriggerContext::lady_vashj_toxic_sporebats_are_spewing_poison_clouds; - creators["lady vashj bot is entangled in toxic spores or static charge"] = &RaidSSCTriggerContext::lady_vashj_bot_is_entangled_in_toxic_spores_or_static_charge; - creators["lady vashj need to manage trackers"] = &RaidSSCTriggerContext::lady_vashj_need_to_manage_trackers; + // Trash + creators["underbog colossus spawned toxic pool after death"] = + &RaidSSCTriggerContext::underbog_colossus_spawned_toxic_pool_after_death; + + creators["greyheart tidecaller water elemental totem spawned"] = + &RaidSSCTriggerContext::greyheart_tidecaller_water_elemental_totem_spawned; + + // Hydross the Unstable + creators["hydross the unstable bot is frost tank"] = + &RaidSSCTriggerContext::hydross_the_unstable_bot_is_frost_tank; + + creators["hydross the unstable bot is nature tank"] = + &RaidSSCTriggerContext::hydross_the_unstable_bot_is_nature_tank; + + creators["hydross the unstable elementals spawned"] = + &RaidSSCTriggerContext::hydross_the_unstable_elementals_spawned; + + creators["hydross the unstable danger from water tombs"] = + &RaidSSCTriggerContext::hydross_the_unstable_danger_from_water_tombs; + + creators["hydross the unstable tank needs aggro upon phase change"] = + &RaidSSCTriggerContext::hydross_the_unstable_tank_needs_aggro_upon_phase_change; + + creators["hydross the unstable aggro resets upon phase change"] = + &RaidSSCTriggerContext::hydross_the_unstable_aggro_resets_upon_phase_change; + + creators["hydross the unstable need to manage timers"] = + &RaidSSCTriggerContext::hydross_the_unstable_need_to_manage_timers; + + // The Lurker Below + creators["the lurker below spout is active"] = + &RaidSSCTriggerContext::the_lurker_below_spout_is_active; + + creators["the lurker below boss is active for main tank"] = + &RaidSSCTriggerContext::the_lurker_below_boss_is_active_for_main_tank; + + creators["the lurker below boss casts geyser"] = + &RaidSSCTriggerContext::the_lurker_below_boss_casts_geyser; + + creators["the lurker below boss is submerged"] = + &RaidSSCTriggerContext::the_lurker_below_boss_is_submerged; + + creators["the lurker below need to prepare timer for spout"] = + &RaidSSCTriggerContext::the_lurker_below_need_to_prepare_timer_for_spout; + + // Leotheras the Blind + creators["leotheras the blind boss is inactive"] = + &RaidSSCTriggerContext::leotheras_the_blind_boss_is_inactive; + + creators["leotheras the blind boss transformed into demon form"] = + &RaidSSCTriggerContext::leotheras_the_blind_boss_transformed_into_demon_form; + + creators["leotheras the blind boss engaged by ranged"] = + &RaidSSCTriggerContext::leotheras_the_blind_boss_engaged_by_ranged; + + creators["leotheras the blind boss channeling whirlwind"] = + &RaidSSCTriggerContext::leotheras_the_blind_boss_channeling_whirlwind; + + creators["leotheras the blind bot has too many chaos blast stacks"] = + &RaidSSCTriggerContext::leotheras_the_blind_bot_has_too_many_chaos_blast_stacks; + + creators["leotheras the blind inner demon cheat"] = + &RaidSSCTriggerContext::leotheras_the_blind_inner_demon_cheat; + + creators["leotheras the blind entered final phase"] = + &RaidSSCTriggerContext::leotheras_the_blind_entered_final_phase; + + creators["leotheras the blind demon form tank needs aggro"] = + &RaidSSCTriggerContext::leotheras_the_blind_demon_form_tank_needs_aggro; + + creators["leotheras the blind boss wipes aggro upon phase change"] = + &RaidSSCTriggerContext::leotheras_the_blind_boss_wipes_aggro_upon_phase_change; + + // Fathom-Lord Karathress + creators["fathom-lord karathress boss engaged by main tank"] = + &RaidSSCTriggerContext::fathom_lord_karathress_boss_engaged_by_main_tank; + + creators["fathom-lord karathress caribdis engaged by first assist tank"] = + &RaidSSCTriggerContext::fathom_lord_karathress_caribdis_engaged_by_first_assist_tank; + + creators["fathom-lord karathress sharkkis engaged by second assist tank"] = + &RaidSSCTriggerContext::fathom_lord_karathress_sharkkis_engaged_by_second_assist_tank; + + creators["fathom-lord karathress tidalvess engaged by third assist tank"] = + &RaidSSCTriggerContext::fathom_lord_karathress_tidalvess_engaged_by_third_assist_tank; + + creators["fathom-lord karathress caribdis tank needs dedicated healer"] = + &RaidSSCTriggerContext::fathom_lord_karathress_caribdis_tank_needs_dedicated_healer; + + creators["fathom-lord karathress pulling bosses"] = + &RaidSSCTriggerContext::fathom_lord_karathress_pulling_bosses; + + creators["fathom-lord karathress determining kill order"] = + &RaidSSCTriggerContext::fathom_lord_karathress_determining_kill_order; + + creators["fathom-lord karathress tanks need to establish aggro"] = + &RaidSSCTriggerContext::fathom_lord_karathress_tanks_need_to_establish_aggro; + + // Morogrim Tidewalker + creators["morogrim tidewalker boss engaged by main tank"] = + &RaidSSCTriggerContext::morogrim_tidewalker_boss_engaged_by_main_tank; + + creators["morogrim tidewalker pulling boss"] = + &RaidSSCTriggerContext::morogrim_tidewalker_pulling_boss; + + creators["morogrim tidewalker water globules are incoming"] = + &RaidSSCTriggerContext::morogrim_tidewalker_water_globules_are_incoming; + + creators["morogrim tidewalker encounter reset"] = + &RaidSSCTriggerContext::morogrim_tidewalker_encounter_reset; + + // Lady Vashj + creators["lady vashj boss engaged by main tank"] = + &RaidSSCTriggerContext::lady_vashj_boss_engaged_by_main_tank; + + creators["lady vashj boss engaged by ranged in phase 1"] = + &RaidSSCTriggerContext::lady_vashj_boss_engaged_by_ranged_in_phase_1; + + creators["lady vashj casts shock blast on highest aggro"] = + &RaidSSCTriggerContext::lady_vashj_casts_shock_blast_on_highest_aggro; + + creators["lady vashj bot has static charge"] = + &RaidSSCTriggerContext::lady_vashj_bot_has_static_charge; + + creators["lady vashj pulling boss in phase 1 and phase 3"] = + &RaidSSCTriggerContext::lady_vashj_pulling_boss_in_phase_1_and_phase_3; + + creators["lady vashj coilfang strider is approaching"] = + &RaidSSCTriggerContext::lady_vashj_coilfang_strider_is_approaching; + + creators["lady vashj determining kill order of adds"] = + &RaidSSCTriggerContext::lady_vashj_determining_kill_order_of_adds; + + creators["lady vashj tainted elemental cheat"] = + &RaidSSCTriggerContext::lady_vashj_tainted_elemental_cheat; + + creators["lady vashj tainted core was looted"] = + &RaidSSCTriggerContext::lady_vashj_tainted_core_was_looted; + + creators["lady vashj tainted core is unusable"] = + &RaidSSCTriggerContext::lady_vashj_tainted_core_is_unusable; + + creators["lady vashj toxic sporebats are spewing poison clouds"] = + &RaidSSCTriggerContext::lady_vashj_toxic_sporebats_are_spewing_poison_clouds; + + creators["lady vashj bot is entangled in toxic spores or static charge"] = + &RaidSSCTriggerContext::lady_vashj_bot_is_entangled_in_toxic_spores_or_static_charge; + + creators["lady vashj need to manage trackers"] = + &RaidSSCTriggerContext::lady_vashj_need_to_manage_trackers; } private: - static Trigger* underbog_colossus_spawned_toxic_pool_after_death(PlayerbotAI* botAI) { return new UnderbogColossusSpawnedToxicPoolAfterDeathTrigger(botAI); } - static Trigger* greyheart_tidecaller_water_elemental_totem_spawned(PlayerbotAI* botAI) { return new GreyheartTidecallerWaterElementalTotemSpawnedTrigger(botAI); } - - static Trigger* hydross_the_unstable_bot_is_frost_tank(PlayerbotAI* botAI) { return new HydrossTheUnstableBotIsFrostTankTrigger(botAI); } - static Trigger* hydross_the_unstable_bot_is_nature_tank(PlayerbotAI* botAI) { return new HydrossTheUnstableBotIsNatureTankTrigger(botAI); } - static Trigger* hydross_the_unstable_elementals_spawned(PlayerbotAI* botAI) { return new HydrossTheUnstableElementalsSpawnedTrigger(botAI); } - static Trigger* hydross_the_unstable_danger_from_water_tombs(PlayerbotAI* botAI) { return new HydrossTheUnstableDangerFromWaterTombsTrigger(botAI); } - static Trigger* hydross_the_unstable_tank_needs_aggro_upon_phase_change(PlayerbotAI* botAI) { return new HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger(botAI); } - static Trigger* hydross_the_unstable_aggro_resets_upon_phase_change(PlayerbotAI* botAI) { return new HydrossTheUnstableAggroResetsUponPhaseChangeTrigger(botAI); } - static Trigger* hydross_the_unstable_need_to_manage_timers(PlayerbotAI* botAI) { return new HydrossTheUnstableNeedToManageTimersTrigger(botAI); } - - static Trigger* the_lurker_below_spout_is_active(PlayerbotAI* botAI) { return new TheLurkerBelowSpoutIsActiveTrigger(botAI); } - static Trigger* the_lurker_below_boss_is_active_for_main_tank(PlayerbotAI* botAI) { return new TheLurkerBelowBossIsActiveForMainTankTrigger(botAI); } - static Trigger* the_lurker_below_boss_casts_geyser(PlayerbotAI* botAI) { return new TheLurkerBelowBossCastsGeyserTrigger(botAI); } - static Trigger* the_lurker_below_boss_is_submerged(PlayerbotAI* botAI) { return new TheLurkerBelowBossIsSubmergedTrigger(botAI); } - static Trigger* the_lurker_below_need_to_prepare_timer_for_spout(PlayerbotAI* botAI) { return new TheLurkerBelowNeedToPrepareTimerForSpoutTrigger(botAI); } - - static Trigger* leotheras_the_blind_boss_is_inactive(PlayerbotAI* botAI) { return new LeotherasTheBlindBossIsInactiveTrigger(botAI); } - static Trigger* leotheras_the_blind_boss_transformed_into_demon_form(PlayerbotAI* botAI) { return new LeotherasTheBlindBossTransformedIntoDemonFormTrigger(botAI); } - static Trigger* leotheras_the_blind_boss_engaged_by_ranged(PlayerbotAI* botAI) { return new LeotherasTheBlindBossEngagedByRangedTrigger(botAI); } - static Trigger* leotheras_the_blind_boss_channeling_whirlwind(PlayerbotAI* botAI) { return new LeotherasTheBlindBossChannelingWhirlwindTrigger(botAI); } - static Trigger* leotheras_the_blind_inner_demon_cheat(PlayerbotAI* botAI) { return new LeotherasTheBlindInnerDemonCheatTrigger(botAI); } - static Trigger* leotheras_the_blind_entered_final_phase(PlayerbotAI* botAI) { return new LeotherasTheBlindEnteredFinalPhaseTrigger(botAI); } - static Trigger* leotheras_the_blind_demon_form_tank_needs_aggro(PlayerbotAI* botAI) { return new LeotherasTheBlindDemonFormTankNeedsAggro(botAI); } - static Trigger* leotheras_the_blind_need_to_manage_timers_and_trackers(PlayerbotAI* botAI) { return new LeotherasTheBlindNeedToManageTimersAndTrackersTrigger(botAI); } - - static Trigger* fathom_lord_karathress_boss_engaged_by_main_tank(PlayerbotAI* botAI) { return new FathomLordKarathressBossEngagedByMainTankTrigger(botAI); } - static Trigger* fathom_lord_karathress_caribdis_engaged_by_first_assist_tank(PlayerbotAI* botAI) { return new FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger(botAI); } - static Trigger* fathom_lord_karathress_sharkkis_engaged_by_second_assist_tank(PlayerbotAI* botAI) { return new FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger(botAI); } - static Trigger* fathom_lord_karathress_tidalvess_engaged_by_third_assist_tank(PlayerbotAI* botAI) { return new FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger(botAI); } - static Trigger* fathom_lord_karathress_caribdis_tank_needs_dedicated_healer(PlayerbotAI* botAI) { return new FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger(botAI); } - static Trigger* fathom_lord_karathress_pulling_bosses(PlayerbotAI* botAI) { return new FathomLordKarathressPullingBossesTrigger(botAI); } - static Trigger* fathom_lord_karathress_determining_kill_order(PlayerbotAI* botAI) { return new FathomLordKarathressDeterminingKillOrderTrigger(botAI); } - static Trigger* fathom_lord_karathress_tanks_need_to_establish_aggro(PlayerbotAI* botAI) { return new FathomLordKarathressTanksNeedToEstablishAggroTrigger(botAI); } - - static Trigger* morogrim_tidewalker_boss_engaged_by_main_tank(PlayerbotAI* botAI) { return new MorogrimTidewalkerBossEngagedByMainTankTrigger(botAI); } - static Trigger* morogrim_tidewalker_pulling_boss(PlayerbotAI* botAI) { return new MorogrimTidewalkerPullingBossTrigger(botAI); } - static Trigger* morogrim_tidewalker_water_globules_are_incoming(PlayerbotAI* botAI) { return new MorogrimTidewalkerWaterGlobulesAreIncomingTrigger(botAI); } - static Trigger* morogrim_tidewalker_encounter_reset(PlayerbotAI* botAI) { return new MorogrimTidewalkerEncounterResetTrigger(botAI); } - - static Trigger* lady_vashj_boss_engaged_by_main_tank(PlayerbotAI* botAI) { return new LadyVashjBossEngagedByMainTankTrigger(botAI); } - static Trigger* lady_vashj_boss_engaged_by_ranged_in_phase_1(PlayerbotAI* botAI) { return new LadyVashjBossEngagedByRangedInPhase1Trigger(botAI); } - static Trigger* lady_vashj_casts_shock_blast_on_highest_aggro(PlayerbotAI* botAI) { return new LadyVashjCastsShockBlastOnHighestAggroTrigger(botAI); } - static Trigger* lady_vashj_bot_has_static_charge(PlayerbotAI* botAI) { return new LadyVashjBotHasStaticChargeTrigger(botAI); } - static Trigger* lady_vashj_pulling_boss_in_phase_1_and_phase_3(PlayerbotAI* botAI) { return new LadyVashjPullingBossInPhase1AndPhase3Trigger(botAI); } - static Trigger* lady_vashj_coilfang_strider_is_approaching(PlayerbotAI* botAI) { return new LadyVashjCoilfangStriderIsApproachingTrigger(botAI); } - static Trigger* lady_vashj_determining_kill_order_of_adds(PlayerbotAI* botAI) { return new LadyVashjDeterminingKillOrderOfAddsTrigger(botAI); } - static Trigger* lady_vashj_tainted_elemental_cheat(PlayerbotAI* botAI) { return new LadyVashjTaintedElementalCheatTrigger(botAI); } - static Trigger* lady_vashj_tainted_core_was_looted(PlayerbotAI* botAI) { return new LadyVashjTaintedCoreWasLootedTrigger(botAI); } - static Trigger* lady_vashj_tainted_core_is_unusable(PlayerbotAI* botAI) { return new LadyVashjTaintedCoreIsUnusableTrigger(botAI); } - static Trigger* lady_vashj_toxic_sporebats_are_spewing_poison_clouds(PlayerbotAI* botAI) { return new LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger(botAI); } - static Trigger* lady_vashj_bot_is_entangled_in_toxic_spores_or_static_charge(PlayerbotAI* botAI) { return new LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger(botAI); } - static Trigger* lady_vashj_need_to_manage_trackers(PlayerbotAI* botAI) { return new LadyVashjNeedToManageTrackersTrigger(botAI); } + // Trash + static Trigger* underbog_colossus_spawned_toxic_pool_after_death( + PlayerbotAI* botAI) { return new UnderbogColossusSpawnedToxicPoolAfterDeathTrigger(botAI); } + + static Trigger* greyheart_tidecaller_water_elemental_totem_spawned( + PlayerbotAI* botAI) { return new GreyheartTidecallerWaterElementalTotemSpawnedTrigger(botAI); } + + // Hydross the Unstable + static Trigger* hydross_the_unstable_bot_is_frost_tank( + PlayerbotAI* botAI) { return new HydrossTheUnstableBotIsFrostTankTrigger(botAI); } + + static Trigger* hydross_the_unstable_bot_is_nature_tank( + PlayerbotAI* botAI) { return new HydrossTheUnstableBotIsNatureTankTrigger(botAI); } + + static Trigger* hydross_the_unstable_elementals_spawned( + PlayerbotAI* botAI) { return new HydrossTheUnstableElementalsSpawnedTrigger(botAI); } + + static Trigger* hydross_the_unstable_danger_from_water_tombs( + PlayerbotAI* botAI) { return new HydrossTheUnstableDangerFromWaterTombsTrigger(botAI); } + + static Trigger* hydross_the_unstable_tank_needs_aggro_upon_phase_change( + PlayerbotAI* botAI) { return new HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger(botAI); } + + static Trigger* hydross_the_unstable_aggro_resets_upon_phase_change( + PlayerbotAI* botAI) { return new HydrossTheUnstableAggroResetsUponPhaseChangeTrigger(botAI); } + + static Trigger* hydross_the_unstable_need_to_manage_timers( + PlayerbotAI* botAI) { return new HydrossTheUnstableNeedToManageTimersTrigger(botAI); } + + // The Lurker Below + static Trigger* the_lurker_below_spout_is_active( + PlayerbotAI* botAI) { return new TheLurkerBelowSpoutIsActiveTrigger(botAI); } + + static Trigger* the_lurker_below_boss_is_active_for_main_tank( + PlayerbotAI* botAI) { return new TheLurkerBelowBossIsActiveForMainTankTrigger(botAI); } + + static Trigger* the_lurker_below_boss_casts_geyser( + PlayerbotAI* botAI) { return new TheLurkerBelowBossCastsGeyserTrigger(botAI); } + + static Trigger* the_lurker_below_boss_is_submerged( + PlayerbotAI* botAI) { return new TheLurkerBelowBossIsSubmergedTrigger(botAI); } + + static Trigger* the_lurker_below_need_to_prepare_timer_for_spout( + PlayerbotAI* botAI) { return new TheLurkerBelowNeedToPrepareTimerForSpoutTrigger(botAI); } + + // Leotheras the Blind + static Trigger* leotheras_the_blind_boss_is_inactive( + PlayerbotAI* botAI) { return new LeotherasTheBlindBossIsInactiveTrigger(botAI); } + + static Trigger* leotheras_the_blind_boss_transformed_into_demon_form( + PlayerbotAI* botAI) { return new LeotherasTheBlindBossTransformedIntoDemonFormTrigger(botAI); } + + static Trigger* leotheras_the_blind_boss_engaged_by_ranged( + PlayerbotAI* botAI) { return new LeotherasTheBlindBossEngagedByRangedTrigger(botAI); } + + static Trigger* leotheras_the_blind_boss_channeling_whirlwind( + PlayerbotAI* botAI) { return new LeotherasTheBlindBossChannelingWhirlwindTrigger(botAI); } + + static Trigger* leotheras_the_blind_bot_has_too_many_chaos_blast_stacks( + PlayerbotAI* botAI) { return new LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger(botAI); } + + static Trigger* leotheras_the_blind_inner_demon_cheat( + PlayerbotAI* botAI) { return new LeotherasTheBlindInnerDemonCheatTrigger(botAI); } + + static Trigger* leotheras_the_blind_entered_final_phase( + PlayerbotAI* botAI) { return new LeotherasTheBlindEnteredFinalPhaseTrigger(botAI); } + + static Trigger* leotheras_the_blind_demon_form_tank_needs_aggro( + PlayerbotAI* botAI) { return new LeotherasTheBlindDemonFormTankNeedsAggro(botAI); } + + static Trigger* leotheras_the_blind_boss_wipes_aggro_upon_phase_change( + PlayerbotAI* botAI) { return new LeotherasTheBlindBossWipesAggroUponPhaseChangeTrigger(botAI); } + + // Fathom-Lord Karathress + static Trigger* fathom_lord_karathress_boss_engaged_by_main_tank( + PlayerbotAI* botAI) { return new FathomLordKarathressBossEngagedByMainTankTrigger(botAI); } + + static Trigger* fathom_lord_karathress_caribdis_engaged_by_first_assist_tank( + PlayerbotAI* botAI) { return new FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger(botAI); } + + static Trigger* fathom_lord_karathress_sharkkis_engaged_by_second_assist_tank( + PlayerbotAI* botAI) { return new FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger(botAI); } + + static Trigger* fathom_lord_karathress_tidalvess_engaged_by_third_assist_tank( + PlayerbotAI* botAI) { return new FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger(botAI); } + + static Trigger* fathom_lord_karathress_caribdis_tank_needs_dedicated_healer( + PlayerbotAI* botAI) { return new FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger(botAI); } + + static Trigger* fathom_lord_karathress_pulling_bosses( + PlayerbotAI* botAI) { return new FathomLordKarathressPullingBossesTrigger(botAI); } + + static Trigger* fathom_lord_karathress_determining_kill_order( + PlayerbotAI* botAI) { return new FathomLordKarathressDeterminingKillOrderTrigger(botAI); } + + static Trigger* fathom_lord_karathress_tanks_need_to_establish_aggro( + PlayerbotAI* botAI) { return new FathomLordKarathressTanksNeedToEstablishAggroTrigger(botAI); } + + // Morogrim Tidewalker + static Trigger* morogrim_tidewalker_boss_engaged_by_main_tank( + PlayerbotAI* botAI) { return new MorogrimTidewalkerBossEngagedByMainTankTrigger(botAI); } + + static Trigger* morogrim_tidewalker_pulling_boss( + PlayerbotAI* botAI) { return new MorogrimTidewalkerPullingBossTrigger(botAI); } + + static Trigger* morogrim_tidewalker_water_globules_are_incoming( + PlayerbotAI* botAI) { return new MorogrimTidewalkerWaterGlobulesAreIncomingTrigger(botAI); } + + static Trigger* morogrim_tidewalker_encounter_reset( + PlayerbotAI* botAI) { return new MorogrimTidewalkerEncounterResetTrigger(botAI); } + + // Lady Vashj + static Trigger* lady_vashj_boss_engaged_by_main_tank( + PlayerbotAI* botAI) { return new LadyVashjBossEngagedByMainTankTrigger(botAI); } + + static Trigger* lady_vashj_boss_engaged_by_ranged_in_phase_1( + PlayerbotAI* botAI) { return new LadyVashjBossEngagedByRangedInPhase1Trigger(botAI); } + + static Trigger* lady_vashj_casts_shock_blast_on_highest_aggro( + PlayerbotAI* botAI) { return new LadyVashjCastsShockBlastOnHighestAggroTrigger(botAI); } + + static Trigger* lady_vashj_bot_has_static_charge( + PlayerbotAI* botAI) { return new LadyVashjBotHasStaticChargeTrigger(botAI); } + + static Trigger* lady_vashj_pulling_boss_in_phase_1_and_phase_3( + PlayerbotAI* botAI) { return new LadyVashjPullingBossInPhase1AndPhase3Trigger(botAI); } + + static Trigger* lady_vashj_coilfang_strider_is_approaching( + PlayerbotAI* botAI) { return new LadyVashjCoilfangStriderIsApproachingTrigger(botAI); } + + static Trigger* lady_vashj_determining_kill_order_of_adds( + PlayerbotAI* botAI) { return new LadyVashjDeterminingKillOrderOfAddsTrigger(botAI); } + + static Trigger* lady_vashj_tainted_elemental_cheat( + PlayerbotAI* botAI) { return new LadyVashjTaintedElementalCheatTrigger(botAI); } + + static Trigger* lady_vashj_tainted_core_was_looted( + PlayerbotAI* botAI) { return new LadyVashjTaintedCoreWasLootedTrigger(botAI); } + + static Trigger* lady_vashj_tainted_core_is_unusable( + PlayerbotAI* botAI) { return new LadyVashjTaintedCoreIsUnusableTrigger(botAI); } + + static Trigger* lady_vashj_toxic_sporebats_are_spewing_poison_clouds( + PlayerbotAI* botAI) { return new LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger(botAI); } + + static Trigger* lady_vashj_bot_is_entangled_in_toxic_spores_or_static_charge( + PlayerbotAI* botAI) { return new LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger(botAI); } + + static Trigger* lady_vashj_need_to_manage_trackers( + PlayerbotAI* botAI) { return new LadyVashjNeedToManageTrackersTrigger(botAI); } }; -#endif \ No newline at end of file +#endif diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp index 7559b6fa6a..aa329d68b9 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp @@ -82,7 +82,7 @@ bool HydrossTheUnstableAggroResetsUponPhaseChangeTrigger::IsActive() if (bot->getClass() == CLASS_HUNTER) return false; - if (!botAI->IsDps(bot)) + if (botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0)) return false; Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); @@ -220,7 +220,8 @@ bool LeotherasTheBlindBossEngagedByRangedTrigger::IsActive() return false; Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); - return leotheras && !leotheras->HasAura(SPELL_LEOTHERAS_BANISHED); + return leotheras && !leotheras->HasAura(SPELL_LEOTHERAS_BANISHED) && + !leotheras->HasAura(SPELL_WHIRLWIND) && !leotheras->HasAura(SPELL_WHIRLWIND_CHANNEL); } bool LeotherasTheBlindBossChannelingWhirlwindTrigger::IsActive() @@ -233,6 +234,23 @@ bool LeotherasTheBlindBossChannelingWhirlwindTrigger::IsActive() (leotheras->HasAura(SPELL_WHIRLWIND) || leotheras->HasAura(SPELL_WHIRLWIND_CHANNEL)); } +bool LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger::IsActive() +{ + Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); + if (!demonFormTank) + return false; + + if (!botAI->IsMelee(bot) && !botAI->IsDps(bot)) + return false; + + Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI); + if (!leotherasPhase2Demon) + return false; + + Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST); + return chaosBlast && chaosBlast->GetStackAmount() >= 5; +} + bool LeotherasTheBlindInnerDemonCheatTrigger::IsActive() { if (!botAI->HasCheat(BotCheatMask::raid)) @@ -270,7 +288,7 @@ bool LeotherasTheBlindDemonFormTankNeedsAggro::IsActive() return leotheras != nullptr; } -bool LeotherasTheBlindNeedToManageTimersAndTrackersTrigger::IsActive() +bool LeotherasTheBlindBossWipesAggroUponPhaseChangeTrigger::IsActive() { if (!IsMapIDTimerManager(botAI, bot)) return false; @@ -326,21 +344,20 @@ bool FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger::IsActive() if (!caribdis || !caribdis->IsAlive()) return false; - Group* group = bot->GetGroup(); - if (!group) - return false; - Player* firstAssistTank = nullptr; - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + if (Group* group = bot->GetGroup()) { - Player* member = ref->GetSource(); - if (!member || !member->IsAlive()) - continue; - - if (botAI->IsAssistTankOfIndex(member, 0)) + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { - firstAssistTank = member; - break; + Player* member = ref->GetSource(); + if (!member || !member->IsAlive()) + continue; + + if (botAI->IsAssistTankOfIndex(member, 0)) + { + firstAssistTank = member; + break; + } } } @@ -561,8 +578,7 @@ bool LadyVashjTaintedCoreWasLootedTrigger::IsActive() if (bot == designatedLooter) { - if (hasCore(firstCorePasser) || hasCore(secondCorePasser) || - hasCore(thirdCorePasser) || hasCore(fourthCorePasser)) + if (!hasCore(bot)) return false; } else if (bot == firstCorePasser) diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h index 9659071af5..61466ccfaa 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h @@ -6,336 +6,392 @@ class UnderbogColossusSpawnedToxicPoolAfterDeathTrigger : public Trigger { public: - UnderbogColossusSpawnedToxicPoolAfterDeathTrigger(PlayerbotAI* botAI) : Trigger(botAI, "underbog colossus spawned toxic pool after death") {} + UnderbogColossusSpawnedToxicPoolAfterDeathTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "underbog colossus spawned toxic pool after death") {} bool IsActive() override; }; class GreyheartTidecallerWaterElementalTotemSpawnedTrigger : public Trigger { public: - GreyheartTidecallerWaterElementalTotemSpawnedTrigger(PlayerbotAI* botAI) : Trigger(botAI, "greyheart tidecaller water elemental totem spawned") {} + GreyheartTidecallerWaterElementalTotemSpawnedTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "greyheart tidecaller water elemental totem spawned") {} bool IsActive() override; }; class HydrossTheUnstableBotIsFrostTankTrigger : public Trigger { public: - HydrossTheUnstableBotIsFrostTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable bot is frost tank") {} + HydrossTheUnstableBotIsFrostTankTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable bot is frost tank") {} bool IsActive() override; }; class HydrossTheUnstableBotIsNatureTankTrigger : public Trigger { public: - HydrossTheUnstableBotIsNatureTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable bot is nature tank") {} + HydrossTheUnstableBotIsNatureTankTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable bot is nature tank") {} bool IsActive() override; }; class HydrossTheUnstableElementalsSpawnedTrigger : public Trigger { public: - HydrossTheUnstableElementalsSpawnedTrigger(PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable elementals spawned") {} + HydrossTheUnstableElementalsSpawnedTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable elementals spawned") {} bool IsActive() override; }; class HydrossTheUnstableDangerFromWaterTombsTrigger : public Trigger { public: - HydrossTheUnstableDangerFromWaterTombsTrigger(PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable danger from water tombs") {} + HydrossTheUnstableDangerFromWaterTombsTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable danger from water tombs") {} bool IsActive() override; }; class HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger : public Trigger { public: - HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger(PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable tank needs aggro upon phase change") {} + HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable tank needs aggro upon phase change") {} bool IsActive() override; }; class HydrossTheUnstableAggroResetsUponPhaseChangeTrigger : public Trigger { public: - HydrossTheUnstableAggroResetsUponPhaseChangeTrigger(PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable aggro resets upon phase change") {} + HydrossTheUnstableAggroResetsUponPhaseChangeTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable aggro resets upon phase change") {} bool IsActive() override; }; class HydrossTheUnstableNeedToManageTimersTrigger : public Trigger { public: - HydrossTheUnstableNeedToManageTimersTrigger(PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable need to manage timers") {} + HydrossTheUnstableNeedToManageTimersTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable need to manage timers") {} bool IsActive() override; }; class TheLurkerBelowSpoutIsActiveTrigger : public Trigger { public: - TheLurkerBelowSpoutIsActiveTrigger(PlayerbotAI* botAI) : Trigger(botAI, "the lurker below spout is active") {} + TheLurkerBelowSpoutIsActiveTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "the lurker below spout is active") {} bool IsActive() override; }; class TheLurkerBelowBossIsActiveForMainTankTrigger : public Trigger { public: - TheLurkerBelowBossIsActiveForMainTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "the lurker below boss is active for main tank") {} + TheLurkerBelowBossIsActiveForMainTankTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "the lurker below boss is active for main tank") {} bool IsActive() override; }; class TheLurkerBelowBossCastsGeyserTrigger : public Trigger { public: - TheLurkerBelowBossCastsGeyserTrigger(PlayerbotAI* botAI) : Trigger(botAI, "the lurker below boss casts geyser") {} + TheLurkerBelowBossCastsGeyserTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "the lurker below boss casts geyser") {} bool IsActive() override; }; class TheLurkerBelowBossIsSubmergedTrigger : public Trigger { public: - TheLurkerBelowBossIsSubmergedTrigger(PlayerbotAI* botAI) : Trigger(botAI, "the lurker below boss is submerged") {} + TheLurkerBelowBossIsSubmergedTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "the lurker below boss is submerged") {} bool IsActive() override; }; class TheLurkerBelowNeedToPrepareTimerForSpoutTrigger : public Trigger { public: - TheLurkerBelowNeedToPrepareTimerForSpoutTrigger(PlayerbotAI* botAI) : Trigger(botAI, "the lurker below need to prepare timer for spout") {} + TheLurkerBelowNeedToPrepareTimerForSpoutTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "the lurker below need to prepare timer for spout") {} bool IsActive() override; }; class LeotherasTheBlindBossIsInactiveTrigger : public Trigger { public: - LeotherasTheBlindBossIsInactiveTrigger(PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind boss is inactive") {} + LeotherasTheBlindBossIsInactiveTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind boss is inactive") {} bool IsActive() override; }; class LeotherasTheBlindHumanFormEngagedByMainTankTrigger : public Trigger { public: - LeotherasTheBlindHumanFormEngagedByMainTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind human form engaged by main tank") {} + LeotherasTheBlindHumanFormEngagedByMainTankTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind human form engaged by main tank") {} bool IsActive() override; }; class LeotherasTheBlindBossTransformedIntoDemonFormTrigger : public Trigger { public: - LeotherasTheBlindBossTransformedIntoDemonFormTrigger(PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind boss transformed into demon form") {} + LeotherasTheBlindBossTransformedIntoDemonFormTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind boss transformed into demon form") {} bool IsActive() override; }; class LeotherasTheBlindBossEngagedByRangedTrigger : public Trigger { public: - LeotherasTheBlindBossEngagedByRangedTrigger(PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind boss engaged by ranged") {} + LeotherasTheBlindBossEngagedByRangedTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind boss engaged by ranged") {} bool IsActive() override; }; class LeotherasTheBlindBossChannelingWhirlwindTrigger : public Trigger { public: - LeotherasTheBlindBossChannelingWhirlwindTrigger(PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind boss channeling whirlwind") {} + LeotherasTheBlindBossChannelingWhirlwindTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind boss channeling whirlwind") {} + bool IsActive() override; +}; + +class LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger : public Trigger +{ +public: + LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind bot has too many chaos blast stacks") {} bool IsActive() override; }; class LeotherasTheBlindInnerDemonCheatTrigger : public Trigger { public: - LeotherasTheBlindInnerDemonCheatTrigger(PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind inner demon cheat") {} + LeotherasTheBlindInnerDemonCheatTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind inner demon cheat") {} bool IsActive() override; }; class LeotherasTheBlindEnteredFinalPhaseTrigger : public Trigger { public: - LeotherasTheBlindEnteredFinalPhaseTrigger(PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind entered final phase") {} + LeotherasTheBlindEnteredFinalPhaseTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind entered final phase") {} bool IsActive() override; }; class LeotherasTheBlindDemonFormTankNeedsAggro : public Trigger { public: - LeotherasTheBlindDemonFormTankNeedsAggro(PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind demon form tank needs aggro") {} + LeotherasTheBlindDemonFormTankNeedsAggro( + PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind demon form tank needs aggro") {} bool IsActive() override; }; -class LeotherasTheBlindNeedToManageTimersAndTrackersTrigger : public Trigger +class LeotherasTheBlindBossWipesAggroUponPhaseChangeTrigger : public Trigger { public: - LeotherasTheBlindNeedToManageTimersAndTrackersTrigger(PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind need to manage timers and trackers") {} + LeotherasTheBlindBossWipesAggroUponPhaseChangeTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind boss wipes aggro upon phase change") {} bool IsActive() override; }; class FathomLordKarathressBossEngagedByMainTankTrigger : public Trigger { public: - FathomLordKarathressBossEngagedByMainTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress boss engaged by main tank") {} + FathomLordKarathressBossEngagedByMainTankTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress boss engaged by main tank") {} bool IsActive() override; }; class FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger : public Trigger { public: - FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress caribdis engaged by first assist tank") {} + FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress caribdis engaged by first assist tank") {} bool IsActive() override; }; class FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger : public Trigger { public: - FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress sharkkis engaged by second assist tank") {} + FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress sharkkis engaged by second assist tank") {} bool IsActive() override; }; class FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger : public Trigger { public: - FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress tidalvess engaged by third assist tank") {} + FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress tidalvess engaged by third assist tank") {} bool IsActive() override; }; class FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger : public Trigger { public: - FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger(PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress caribdis tank needs dedicated healer") {} + FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress caribdis tank needs dedicated healer") {} bool IsActive() override; }; class FathomLordKarathressPullingBossesTrigger : public Trigger { public: - FathomLordKarathressPullingBossesTrigger(PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress pulling bosses") {} + FathomLordKarathressPullingBossesTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress pulling bosses") {} bool IsActive() override; }; class FathomLordKarathressDeterminingKillOrderTrigger : public Trigger { public: - FathomLordKarathressDeterminingKillOrderTrigger(PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress determining kill order") {} + FathomLordKarathressDeterminingKillOrderTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress determining kill order") {} bool IsActive() override; }; class FathomLordKarathressTanksNeedToEstablishAggroTrigger : public Trigger { public: - FathomLordKarathressTanksNeedToEstablishAggroTrigger(PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress tanks need to establish aggro") {} + FathomLordKarathressTanksNeedToEstablishAggroTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress tanks need to establish aggro") {} bool IsActive() override; }; class MorogrimTidewalkerPullingBossTrigger : public Trigger { public: - MorogrimTidewalkerPullingBossTrigger(PlayerbotAI* botAI) : Trigger(botAI, "morogrim tidewalker pulling boss") {} + MorogrimTidewalkerPullingBossTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "morogrim tidewalker pulling boss") {} bool IsActive() override; }; class MorogrimTidewalkerBossEngagedByMainTankTrigger : public Trigger { public: - MorogrimTidewalkerBossEngagedByMainTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "morogrim tidewalker boss engaged by main tank") {} + MorogrimTidewalkerBossEngagedByMainTankTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "morogrim tidewalker boss engaged by main tank") {} bool IsActive() override; }; class MorogrimTidewalkerWaterGlobulesAreIncomingTrigger : public Trigger { public: - MorogrimTidewalkerWaterGlobulesAreIncomingTrigger(PlayerbotAI* botAI) : Trigger(botAI, "morogrim tidewalker water globules are incoming") {} + MorogrimTidewalkerWaterGlobulesAreIncomingTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "morogrim tidewalker water globules are incoming") {} bool IsActive() override; }; class MorogrimTidewalkerEncounterResetTrigger : public Trigger { public: - MorogrimTidewalkerEncounterResetTrigger(PlayerbotAI* botAI) : Trigger(botAI, "morogrim tidewalker encounter reset") {} + MorogrimTidewalkerEncounterResetTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "morogrim tidewalker encounter reset") {} bool IsActive() override; }; class LadyVashjBossEngagedByMainTankTrigger : public Trigger { public: - LadyVashjBossEngagedByMainTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "lady vashj boss engaged by main tank") {} + LadyVashjBossEngagedByMainTankTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "lady vashj boss engaged by main tank") {} bool IsActive() override; }; class LadyVashjBossEngagedByRangedInPhase1Trigger : public Trigger { public: - LadyVashjBossEngagedByRangedInPhase1Trigger(PlayerbotAI* botAI) : Trigger(botAI, "lady vashj boss engaged by ranged in phase 1") {} + LadyVashjBossEngagedByRangedInPhase1Trigger( + PlayerbotAI* botAI) : Trigger(botAI, "lady vashj boss engaged by ranged in phase 1") {} bool IsActive() override; }; class LadyVashjCastsShockBlastOnHighestAggroTrigger : public Trigger { public: - LadyVashjCastsShockBlastOnHighestAggroTrigger(PlayerbotAI* botAI) : Trigger(botAI, "lady vashj casts shock blast on highest aggro") {} + LadyVashjCastsShockBlastOnHighestAggroTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "lady vashj casts shock blast on highest aggro") {} bool IsActive() override; }; class LadyVashjBotHasStaticChargeTrigger : public Trigger { public: - LadyVashjBotHasStaticChargeTrigger(PlayerbotAI* botAI) : Trigger(botAI, "lady vashj bot has static charge") {} + LadyVashjBotHasStaticChargeTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "lady vashj bot has static charge") {} bool IsActive() override; }; class LadyVashjPullingBossInPhase1AndPhase3Trigger : public Trigger { public: - LadyVashjPullingBossInPhase1AndPhase3Trigger(PlayerbotAI* botAI) : Trigger(botAI, "lady vashj pulling boss in phase 1 and phase 3") {} + LadyVashjPullingBossInPhase1AndPhase3Trigger( + PlayerbotAI* botAI) : Trigger(botAI, "lady vashj pulling boss in phase 1 and phase 3") {} bool IsActive() override; }; class LadyVashjCoilfangStriderIsApproachingTrigger : public Trigger { public: - LadyVashjCoilfangStriderIsApproachingTrigger(PlayerbotAI* botAI) : Trigger(botAI, "lady vashj coilfang strider is approaching") {} + LadyVashjCoilfangStriderIsApproachingTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "lady vashj coilfang strider is approaching") {} bool IsActive() override; }; class LadyVashjDeterminingKillOrderOfAddsTrigger : public Trigger { public: -LadyVashjDeterminingKillOrderOfAddsTrigger(PlayerbotAI* botAI) : Trigger(botAI, "lady vashj determining kill order of adds") {} +LadyVashjDeterminingKillOrderOfAddsTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "lady vashj determining kill order of adds") {} bool IsActive() override; }; class LadyVashjTaintedElementalCheatTrigger : public Trigger { public: - LadyVashjTaintedElementalCheatTrigger(PlayerbotAI* botAI) : Trigger(botAI, "lady vashj tainted elemental cheat") {} + LadyVashjTaintedElementalCheatTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "lady vashj tainted elemental cheat") {} bool IsActive() override; }; class LadyVashjTaintedCoreWasLootedTrigger : public Trigger { public: - LadyVashjTaintedCoreWasLootedTrigger(PlayerbotAI* botAI) : Trigger(botAI, "lady vashj tainted core was looted") {} + LadyVashjTaintedCoreWasLootedTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "lady vashj tainted core was looted") {} bool IsActive() override; }; class LadyVashjTaintedCoreIsUnusableTrigger : public Trigger { public: - LadyVashjTaintedCoreIsUnusableTrigger(PlayerbotAI* botAI) : Trigger(botAI, "lady vashj tainted core is unusable") {} + LadyVashjTaintedCoreIsUnusableTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "lady vashj tainted core is unusable") {} bool IsActive() override; }; class LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger : public Trigger { public: - LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger(PlayerbotAI* botAI) : Trigger(botAI, "lady vashj toxic sporebats are spewing poison clouds") {} + LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "lady vashj toxic sporebats are spewing poison clouds") {} bool IsActive() override; }; class LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger : public Trigger { public: - LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger(PlayerbotAI* botAI) : Trigger(botAI, "lady vashj bot is entangled in toxic spores or static charge") {} + LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "lady vashj bot is entangled in toxic spores or static charge") {} bool IsActive() override; }; class LadyVashjNeedToManageTrackersTrigger : public Trigger { public: - LadyVashjNeedToManageTrackersTrigger(PlayerbotAI* botAI) : Trigger(botAI, "lady vashj need to manage trackers") {} + LadyVashjNeedToManageTrackersTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "lady vashj need to manage trackers") {} bool IsActive() override; }; From d395586a6a665207bbb586ad3a20fdbff348d483 Mon Sep 17 00:00:00 2001 From: crow Date: Mon, 15 Dec 2025 01:19:58 -0600 Subject: [PATCH 05/25] various fixes and cleanups --- .../RaidSSCActionContext.h | 16 +- .../serpentshrinecavern/RaidSSCActions.cpp | 271 ++++++++------- .../serpentshrinecavern/RaidSSCActions.h | 12 +- .../serpentshrinecavern/RaidSSCHelpers.cpp | 107 ++++-- .../serpentshrinecavern/RaidSSCHelpers.h | 9 +- .../RaidSSCMultipliers.cpp | 328 ++++++++++-------- .../serpentshrinecavern/RaidSSCMultipliers.h | 14 +- .../serpentshrinecavern/RaidSSCStrategy.cpp | 7 +- .../serpentshrinecavern/RaidSSCTriggers.cpp | 10 +- 9 files changed, 443 insertions(+), 331 deletions(-) diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h b/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h index 0655d1f090..a07887b461 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h @@ -45,8 +45,8 @@ class RaidSSCActionContext : public NamedObjectContext creators["the lurker below position main tank"] = &RaidSSCActionContext::the_lurker_below_position_main_tank; - creators["the lurker below spread ranged"] = - &RaidSSCActionContext::the_lurker_below_spread_ranged; + creators["the lurker below spread ranged in arc"] = + &RaidSSCActionContext::the_lurker_below_spread_ranged_in_arc; creators["the lurker below tanks pick up adds"] = &RaidSSCActionContext::the_lurker_below_tanks_pick_up_adds; @@ -124,8 +124,8 @@ class RaidSSCActionContext : public NamedObjectContext creators["lady vashj main tank position boss"] = &RaidSSCActionContext::lady_vashj_main_tank_position_boss; - creators["lady vashj phase 1 position ranged"] = - &RaidSSCActionContext::lady_vashj_phase_1_position_ranged; + creators["lady vashj phase 1 spread ranged in arc"] = + &RaidSSCActionContext::lady_vashj_phase_1_spread_ranged_in_arc; creators["lady vashj set grounding totem in main tank group"] = &RaidSSCActionContext::lady_vashj_set_grounding_totem_in_main_tank_group; @@ -204,8 +204,8 @@ class RaidSSCActionContext : public NamedObjectContext static Action* the_lurker_below_position_main_tank( PlayerbotAI* botAI) { return new TheLurkerBelowPositionMainTankAction(botAI); } - static Action* the_lurker_below_spread_ranged( - PlayerbotAI* botAI) { return new TheLurkerBelowSpreadRangedAction(botAI); } + static Action* the_lurker_below_spread_ranged_in_arc( + PlayerbotAI* botAI) { return new TheLurkerBelowSpreadRangedInArcAction(botAI); } static Action* the_lurker_below_tanks_pick_up_adds( PlayerbotAI* botAI) { return new TheLurkerBelowTanksPickUpAddsAction(botAI); } @@ -283,8 +283,8 @@ class RaidSSCActionContext : public NamedObjectContext static Action* lady_vashj_main_tank_position_boss( PlayerbotAI* botAI) { return new LadyVashjMainTankPositionBossAction(botAI); } - static Action* lady_vashj_phase_1_position_ranged( - PlayerbotAI* botAI) { return new LadyVashjPhase1PositionRangedAction(botAI); } + static Action* lady_vashj_phase_1_spread_ranged_in_arc( + PlayerbotAI* botAI) { return new LadyVashjPhase1SpreadRangedInArcAction(botAI); } static Action* lady_vashj_set_grounding_totem_in_main_tank_group( PlayerbotAI* botAI) { return new LadyVashjSetGroundingTotemInMainTankGroupAction(botAI); } diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp index 2d8326b05c..0d473919a5 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp @@ -34,7 +34,8 @@ bool UnderbogColossusEscapeToxicPoolAction::Execute(Event event) { auto const& eff = sInfo->Effects[e]; if (eff.Effect == SPELL_EFFECT_SCHOOL_DAMAGE || - (eff.Effect == SPELL_EFFECT_APPLY_AURA && eff.ApplyAuraName == SPELL_AURA_PERIODIC_DAMAGE)) + (eff.Effect == SPELL_EFFECT_APPLY_AURA && + eff.ApplyAuraName == SPELL_AURA_PERIODIC_DAMAGE)) { radius = eff.CalcRadius(); break; @@ -93,7 +94,7 @@ bool GreyheartTidecallerMarkWaterElementalTotemAction::Execute(Event event) // Hydross the Unstable // (1) When tanking, move to designated tanking spot on frost side -// (2) At 100% Mark of Hydross, move to nature tank's spot to hand off boss +// (2) 1 second after 100% Mark of Hydross, move to nature tank's spot to hand off boss // (3) When Hydross is in nature form, move back to frost tank spot and wait for transition bool HydrossTheUnstablePositionFrostTankAction::Execute(Event event) { @@ -109,7 +110,7 @@ bool HydrossTheUnstablePositionFrostTankAction::Execute(Event event) if (bot->GetVictim() != hydross) return Attack(hydross); - if (hydross->GetVictim() == bot) + if (hydross->GetVictim() == bot && bot->IsWithinMeleeRange(hydross)) { const Position& position = HYDROSS_FROST_TANK_POSITION; float dist = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); @@ -128,7 +129,7 @@ bool HydrossTheUnstablePositionFrostTankAction::Execute(Event event) } if (!hydross->HasAura(SPELL_CORRUPTION) && HasMarkOfHydrossAt100Percent(bot) && - hydross->GetVictim() == bot) + hydross->GetVictim() == bot && bot->IsWithinMeleeRange(hydross)) { const time_t now = std::time(nullptr); auto it = hydrossChangeToNaturePhaseTimer.find(SSC_MAP_ID); @@ -148,12 +149,6 @@ bool HydrossTheUnstablePositionFrostTankAction::Execute(Event event) return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, true); } - else - { - bot->AttackStop(); - bot->InterruptNonMeleeSpells(true); - return true; - } } } @@ -166,19 +161,13 @@ bool HydrossTheUnstablePositionFrostTankAction::Execute(Event event) position.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, false); } - else - { - bot->AttackStop(); - bot->InterruptNonMeleeSpells(true); - return true; - } } return false; } // (1) When tanking, move to designated tanking spot on nature side -// (2) At 100% Mark of Corruption, move to frost tank's spot to hand off boss +// (2) 1 second after 100% Mark of Corruption, move to frost tank's spot to hand off boss // (3) When Hydross is in frost form, move back to nature tank spot and wait for transition bool HydrossTheUnstablePositionNatureTankAction::Execute(Event event) { @@ -194,7 +183,7 @@ bool HydrossTheUnstablePositionNatureTankAction::Execute(Event event) if (bot->GetVictim() != hydross) return Attack(hydross); - if (hydross->GetVictim() == bot) + if (hydross->GetVictim() == bot && bot->IsWithinMeleeRange(hydross)) { const Position& position = HYDROSS_NATURE_TANK_POSITION; float dist = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); @@ -213,7 +202,7 @@ bool HydrossTheUnstablePositionNatureTankAction::Execute(Event event) } if (hydross->HasAura(SPELL_CORRUPTION) && HasMarkOfCorruptionAt100Percent(bot) && - hydross->GetVictim() == bot) + hydross->GetVictim() == bot && bot->IsWithinMeleeRange(hydross)) { const time_t now = std::time(nullptr); auto it = hydrossChangeToFrostPhaseTimer.find(SSC_MAP_ID); @@ -251,12 +240,6 @@ bool HydrossTheUnstablePositionNatureTankAction::Execute(Event event) position.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, false); } - else - { - bot->AttackStop(); - bot->InterruptNonMeleeSpells(true); - return true; - } } return false; @@ -310,7 +293,7 @@ bool HydrossTheUnstableFrostPhaseSpreadOutAction::Execute(Event event) if (!member || member == bot || !member->IsAlive()) continue; - const uint32 minInterval = 500; + const uint32 minInterval = 1000; if (bot->GetExactDist2d(member) < 6.0f) return FleePosition(member->GetPosition(), 8.0f, minInterval); } @@ -409,22 +392,26 @@ bool HydrossTheUnstableStopDpsUponPhaseChangeAction::Execute(Event event) // 1 second after 100% Mark of Hydross, stop DPS until transition into nature phase auto itNature = hydrossChangeToNaturePhaseTimer.find(SSC_MAP_ID); - if (itNature != hydrossChangeToNaturePhaseTimer.end() && (now - itNature->second) >= phaseEndStopSeconds) + if (itNature != hydrossChangeToNaturePhaseTimer.end() && + (now - itNature->second) >= phaseEndStopSeconds) shouldStopDps = true; // Keep DPS stopped for 5 seconds after transition into nature phase auto itNatureDps = hydrossNatureDpsWaitTimer.find(SSC_MAP_ID); - if (itNatureDps != hydrossNatureDpsWaitTimer.end() && (now - itNatureDps->second) < phaseStartStopSeconds) + if (itNatureDps != hydrossNatureDpsWaitTimer.end() && + (now - itNatureDps->second) < phaseStartStopSeconds) shouldStopDps = true; // 1 second after 100% Mark of Corruption, stop DPS until transition into frost phase auto itFrost = hydrossChangeToFrostPhaseTimer.find(SSC_MAP_ID); - if (itFrost != hydrossChangeToFrostPhaseTimer.end() && (now - itFrost->second) >= phaseEndStopSeconds) + if (itFrost != hydrossChangeToFrostPhaseTimer.end() && + (now - itFrost->second) >= phaseEndStopSeconds) shouldStopDps = true; // Keep DPS stopped for 5 seconds after transition into frost phase auto itFrostDps = hydrossFrostDpsWaitTimer.find(SSC_MAP_ID); - if (itFrostDps != hydrossFrostDpsWaitTimer.end() && (now - itFrostDps->second) < phaseStartStopSeconds) + if (itFrostDps != hydrossFrostDpsWaitTimer.end() && + (now - itFrostDps->second) < phaseStartStopSeconds) shouldStopDps = true; if (shouldStopDps) @@ -485,9 +472,6 @@ bool TheLurkerBelowRunAroundBehindBossAction::Execute(Event event) if (!lurker) return false; - if (bot->HasAura(SPELL_TREE_OF_LIFE) && botAI->CanCastSpell("tree of life", bot)) - return botAI->CastSpell("tree of life", bot); - float bossFacing = lurker->GetOrientation(); float behindAngle = bossFacing + M_PI + frand(-0.5f, 0.5f) * (M_PI / 2.0f); float radius = frand(20.0f, 24.0f); @@ -498,8 +482,8 @@ bool TheLurkerBelowRunAroundBehindBossAction::Execute(Event event) if (bot->GetExactDist2d(targetX, targetY) > 1.0f) { bot->InterruptNonMeleeSpells(true); - return MoveTo(SSC_MAP_ID, targetX, targetY, lurker->GetPositionZ(), false, false, false, false, - MovementPriority::MOVEMENT_FORCED, true, false); + return MoveTo(SSC_MAP_ID, targetX, targetY, lurker->GetPositionZ(), false, false, + false, false, MovementPriority::MOVEMENT_FORCED, true, false); } return false; @@ -517,15 +501,16 @@ bool TheLurkerBelowPositionMainTankAction::Execute(Event event) const Position& position = LURKER_MAIN_TANK_POSITION; if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 0.2f) { - return MoveTo(SSC_MAP_ID, position.GetPositionX(), position.GetPositionY(), position.GetPositionZ(), - false, false, false, false, MovementPriority::MOVEMENT_FORCED, true, false); + return MoveTo(SSC_MAP_ID, position.GetPositionX(), position.GetPositionY(), + position.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_FORCED, true, false); } return false; } // Assign ranged positions within a 120-degree arc behind Lurker -bool TheLurkerBelowSpreadRangedAction::Execute(Event event) +bool TheLurkerBelowSpreadRangedInArcAction::Execute(Event event) { Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); if (!lurker) @@ -556,7 +541,8 @@ bool TheLurkerBelowSpreadRangedAction::Execute(Event event) if (it == lurkerRangedPositions.end()) { auto findIt = std::find(rangedMembers.begin(), rangedMembers.end(), bot); - size_t botIndex = (findIt != rangedMembers.end()) ? std::distance(rangedMembers.begin(), findIt) : 0; + size_t botIndex = + (findIt != rangedMembers.end()) ? std::distance(rangedMembers.begin(), findIt) : 0; size_t count = rangedMembers.size(); if (count == 0) return false; @@ -589,8 +575,9 @@ bool TheLurkerBelowSpreadRangedAction::Execute(Event event) const Position& target = it->second; if (bot->GetExactDist2d(target.GetPositionX(), target.GetPositionY()) > 2.0f) { - return MoveTo(SSC_MAP_ID, target.GetPositionX(), target.GetPositionY(), target.GetPositionZ(), - false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); + return MoveTo(SSC_MAP_ID, target.GetPositionX(), target.GetPositionY(), + target.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, false); } return false; @@ -624,7 +611,8 @@ bool TheLurkerBelowTanksPickUpAddsAction::Execute(Event event) return false; std::vector guardians; - auto const& npcs = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); + auto const& npcs = + botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); for (auto guid : npcs) { Unit* unit = botAI->GetUnit(guid); @@ -814,11 +802,12 @@ bool LeotherasTheBlindMeleeDpsRunAwayFromBossAction::Execute(Event event) } // Tanks and healers have no ability to kill their own Inner Demons -// Hunters, Affliction Warlocks, Shadow Priests, and (for some reason) Mages also struggle +// Ranged DPS also struggle bool LeotherasTheBlindInnerDemonCheatAction::Execute(Event event) { Unit* innerDemon = nullptr; - auto const& npcs = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); + auto const& npcs = + botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); for (auto const& guid : npcs) { Unit* unit = botAI->GetUnit(guid); @@ -836,16 +825,10 @@ bool LeotherasTheBlindInnerDemonCheatAction::Execute(Event event) uint8 tab = AiFactory::GetPlayerSpecTab(bot); Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); - if (botAI->IsHeal(bot) || botAI->IsTank(bot) || - bot->getClass() == CLASS_HUNTER || - bot->getClass() == CLASS_MAGE || - (bot->getClass() == CLASS_PRIEST && tab == 2) || - (bot->getClass() == CLASS_WARLOCK && tab == 0) || - (demonFormTank && demonFormTank == bot)) + if (botAI->IsRanged(bot) || botAI->IsTank(bot)) { Unit::DealDamage(bot, innerDemon, innerDemon->GetMaxHealth() / 25, nullptr, DIRECT_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, nullptr, false, true); - return true; } } @@ -870,7 +853,8 @@ bool LeotherasTheBlindFinalPhaseAssignDpsPriorityAction::Execute(Event event) return Attack(leotherasHuman); } - if (botAI->IsTank(bot) && leotherasHuman->GetVictim() == bot) + if (botAI->IsTank(bot) && leotherasHuman->GetVictim() == bot && + bot->IsWithinMeleeRange(leotherasHuman)) { Unit* demonTarget = leotherasDemon->GetVictim(); if (demonTarget && leotherasHuman->GetExactDist2d(demonTarget) < 20.0f) @@ -880,8 +864,8 @@ bool LeotherasTheBlindFinalPhaseAssignDpsPriorityAction::Execute(Event event) float targetX = bot->GetPositionX() + 21.0f * std::cos(angle); float targetY = bot->GetPositionY() + 21.0f * std::sin(angle); - return MoveTo(SSC_MAP_ID, targetX, targetY, bot->GetPositionZ(), false, false, false, false, - MovementPriority::MOVEMENT_FORCED, true, false); + return MoveTo(SSC_MAP_ID, targetX, targetY, bot->GetPositionZ(), false, false, + false, false, MovementPriority::MOVEMENT_FORCED, true, false); } } @@ -898,7 +882,8 @@ bool LeotherasTheBlindMisdirectBossToDemonFormTankAction::Execute(Event event) if (botAI->CanCastSpell("misdirection", demonFormTank)) return botAI->CastSpell("misdirection", demonFormTank); - if (bot->HasAura(SPELL_MISDIRECTION) && botAI->CanCastSpell("steady shot", leotherasDemon)) + if (bot->HasAura(SPELL_MISDIRECTION) && + botAI->CanCastSpell("steady shot", leotherasDemon)) return botAI->CastSpell("steady shot", leotherasDemon); return false; @@ -964,7 +949,7 @@ bool FathomLordKarathressMainTankPositionBossAction::Execute(Event event) if (bot->GetVictim() != karathress) return Attack(karathress); - if (karathress->GetVictim() == bot) + if (karathress->GetVictim() == bot && bot->IsWithinMeleeRange(karathress)) { const Position& position = KARATHRESS_TANK_POSITION; float dist = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); @@ -976,8 +961,8 @@ bool FathomLordKarathressMainTankPositionBossAction::Execute(Event event) float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_COMBAT, true, true); + return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, + false, true, MovementPriority::MOVEMENT_COMBAT, true, true); } } @@ -1010,8 +995,8 @@ bool FathomLordKarathressFirstAssistTankPositionCaribdisAction::Execute(Event ev float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_COMBAT, true, false); + return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, + false, true, MovementPriority::MOVEMENT_COMBAT, true, false); } } @@ -1031,7 +1016,7 @@ bool FathomLordKarathressSecondAssistTankPositionSharkkisAction::Execute(Event e if (bot->GetVictim() != sharkkis) return Attack(sharkkis); - if (sharkkis->GetVictim() == bot) + if (sharkkis->GetVictim() == bot && bot->IsWithinMeleeRange(sharkkis)) { const Position& position = SHARKKIS_TANK_POSITION; float dist = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); @@ -1064,7 +1049,7 @@ bool FathomLordKarathressThirdAssistTankPositionTidalvessAction::Execute(Event e if (bot->GetVictim() != tidalvess) return Attack(tidalvess); - if (tidalvess->GetVictim() == bot) + if (tidalvess->GetVictim() == bot && bot->IsWithinMeleeRange(tidalvess)) { const Position& position = TIDALVESS_TANK_POSITION; float dist = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); @@ -1120,7 +1105,8 @@ bool FathomLordKarathressMisdirectBossesToTanksAction::Execute(Event event) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (member && member->IsAlive() && member->getClass() == CLASS_HUNTER && GET_PLAYERBOT_AI(member)) + if (member && member->IsAlive() && member->getClass() == CLASS_HUNTER && + GET_PLAYERBOT_AI(member)) hunters.push_back(member); if (hunters.size() >= 3) @@ -1193,8 +1179,9 @@ bool FathomLordKarathressMisdirectBossesToTanksAction::Execute(Event event) return false; } -// Kill order is different from what is recommended for players because bots handle Caribdis -// Cyclones poorly and need more time to get her down (normally, ranged would help with Sharkkis first) +// Kill order is different from what is recommended for players because +// bots handle Caribdis Cyclones poorly and need more time to get her down +// than real players (normally, ranged would help with Sharkkis first) bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) { // Target priority 1: Spitfire Totems for melee dps @@ -1374,7 +1361,7 @@ bool MorogrimTidewalkerMoveBossToTankPositionAction::Execute(Event event) if (bot->GetVictim() != tidewalker) return Attack(tidewalker); - if (tidewalker->GetVictim() == bot) + if (tidewalker->GetVictim() == bot && bot->IsWithinMeleeRange(tidewalker)) { if (tidewalker->GetHealthPct() > 26.0f) return MoveToPhase1TankPosition(tidewalker); @@ -1426,8 +1413,8 @@ bool MorogrimTidewalkerMoveBossToTankPositionAction::MoveToPhase2TankPosition(Un float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - return MoveTo(SSC_MAP_ID, moveX, moveY, transition.GetPositionZ(), false, false, false, false, - MovementPriority::MOVEMENT_COMBAT, true, true); + return MoveTo(SSC_MAP_ID, moveX, moveY, transition.GetPositionZ(), false, false, + false, false, MovementPriority::MOVEMENT_COMBAT, true, true); } else tidewalkerTankStep.try_emplace(botGuid, 1); @@ -1444,8 +1431,8 @@ bool MorogrimTidewalkerMoveBossToTankPositionAction::MoveToPhase2TankPosition(Un float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - return MoveTo(SSC_MAP_ID, moveX, moveY, phase2.GetPositionZ(), false, false, false, false, - MovementPriority::MOVEMENT_COMBAT, true, true); + return MoveTo(SSC_MAP_ID, moveX, moveY, phase2.GetPositionZ(), false, false, + false, false, MovementPriority::MOVEMENT_COMBAT, true, true); } } @@ -1477,8 +1464,8 @@ bool MorogrimTidewalkerPhase2RepositionRangedAction::Execute(Event event) float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - return MoveTo(SSC_MAP_ID, moveX, moveY, transition.GetPositionZ(), false, false, false, false, - MovementPriority::MOVEMENT_COMBAT, true, false); + return MoveTo(SSC_MAP_ID, moveX, moveY, transition.GetPositionZ(), false, false, + false, false, MovementPriority::MOVEMENT_COMBAT, true, false); } else { @@ -1498,8 +1485,8 @@ bool MorogrimTidewalkerPhase2RepositionRangedAction::Execute(Event event) float moveX = bot->GetPositionX() + (dX / dist) * moveDist; float moveY = bot->GetPositionY() + (dY / dist) * moveDist; - return MoveTo(SSC_MAP_ID, moveX, moveY, phase2.GetPositionZ(), false, false, false, false, - MovementPriority::MOVEMENT_COMBAT, true, false); + return MoveTo(SSC_MAP_ID, moveX, moveY, phase2.GetPositionZ(), false, false, + false, false, MovementPriority::MOVEMENT_COMBAT, true, false); } } @@ -1525,7 +1512,6 @@ bool MorogrimTidewalkerResetPhaseTransitionStepsAction::Execute(Event event) // Lady Vashj -// Center of room (phase 1 only) bool LadyVashjMainTankPositionBossAction::Execute(Event event) { Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); @@ -1535,8 +1521,9 @@ bool LadyVashjMainTankPositionBossAction::Execute(Event event) if (bot->GetVictim() != vashj) return Attack(vashj); - if (vashj->GetVictim() == bot) + if (vashj->GetVictim() == bot && bot->IsWithinMeleeRange(vashj)) { + // Phase 1: Position Vashj in the center of the platform if (IsLadyVashjInPhase1(botAI)) { const Position& position = VASHJ_PLATFORM_CENTER_POSITION; @@ -1553,8 +1540,8 @@ bool LadyVashjMainTankPositionBossAction::Execute(Event event) MovementPriority::MOVEMENT_COMBAT, true, true); } } - - if (IsLadyVashjInPhase3(botAI)) + // Phase 3: Move Vashj away from Enchanted Elementals + else if (IsLadyVashjInPhase3(botAI)) { Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental"); if (enchanted) @@ -1571,7 +1558,7 @@ bool LadyVashjMainTankPositionBossAction::Execute(Event event) } // Semicircle around center of the room (to allow escape by Static Charged bots) -bool LadyVashjPhase1PositionRangedAction::Execute(Event event) +bool LadyVashjPhase1SpreadRangedInArcAction::Execute(Event event) { std::vector spreadMembers; if (Group* group = bot->GetGroup()) @@ -1617,7 +1604,8 @@ bool LadyVashjPhase1PositionRangedAction::Execute(Event event) float targetX = center.GetPositionX() + radius * std::cos(angle); float targetY = center.GetPositionY() + radius * std::sin(angle); - auto res = vashjRangedPositions.try_emplace(guid, Position(targetX, targetY, center.GetPositionZ())); + auto res = vashjRangedPositions.try_emplace( + guid, Position(targetX, targetY, center.GetPositionZ())); itPos = res.first; hasReachedVashjRangedPosition.try_emplace(guid, false); itReached = hasReachedVashjRangedPosition.find(guid); @@ -1631,8 +1619,9 @@ bool LadyVashjPhase1PositionRangedAction::Execute(Event event) { if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 2.0f) { - return MoveTo(SSC_MAP_ID, position.GetPositionX(), position.GetPositionY(), position.GetPositionZ(), - false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); + return MoveTo(SSC_MAP_ID, position.GetPositionX(), position.GetPositionY(), + position.GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, false); } if (itReached != hasReachedVashjRangedPosition.end()) itReached->second = true; @@ -1670,7 +1659,8 @@ bool LadyVashjSetGroundingTotemInMainTankGroupAction::Execute(Event event) if (!botAI->HasStrategy("grounding totem", BotState::BOT_STATE_COMBAT)) botAI->ChangeStrategy("+grounding totem", BotState::BOT_STATE_COMBAT); - if (!bot->HasAura(SPELL_GROUNDING_TOTEM_EFFECT) && botAI->CanCastSpell("grounding totem", bot)) + if (!bot->HasAura(SPELL_GROUNDING_TOTEM_EFFECT) && + botAI->CanCastSpell("grounding totem", bot)) return botAI->CastSpell("grounding totem", bot); return false; @@ -1868,7 +1858,8 @@ bool LadyVashjAssignDpsPriorityAction::Execute(Event event) if (!vashj) return false; - auto const& attackers = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); + auto const& attackers = + botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); Unit* target = nullptr; Unit* tainted = nullptr; Unit* enchanted = nullptr; @@ -1878,7 +1869,8 @@ bool LadyVashjAssignDpsPriorityAction::Execute(Event event) // Search and attack radius are intended to keep bots on the platform (not go down the stairs) const Position& center = VASHJ_PLATFORM_CENTER_POSITION; - const float maxSearchRange = botAI->IsRangedDps(bot) ? 60.0f : (botAI->IsMelee(bot) ? 55.0f : 40.0f); + const float maxSearchRange = + botAI->IsRangedDps(bot) ? 60.0f : (botAI->IsMelee(bot) ? 55.0f : 40.0f); const float maxPursueRange = maxSearchRange - 5.0f; for (auto guid : attackers) @@ -1932,8 +1924,8 @@ bool LadyVashjAssignDpsPriorityAction::Execute(Event event) { if (botAI->IsRanged(bot)) { - // Hunters and Mages prioritize Enchanted Elementals, while other ranged DPS prioritize Striders - // This works well with 3 Hunters and 2 Mages; effectiveness may vary based on raid composition + // Hunters and Mages prioritize Enchanted Elementals, + // while other ranged DPS prioritize Striders if (bot->getClass() == CLASS_HUNTER || bot->getClass() == CLASS_MAGE) targets = { tainted, enchanted, strider, elite }; else @@ -2028,14 +2020,16 @@ bool LadyVashjAssignDpsPriorityAction::Execute(Event event) return Attack(target); } - if (currentTarget && (!currentTarget->IsAlive() || !IsValidLadyVashjCombatNpc(currentTarget, botAI))) + if (currentTarget && (!currentTarget->IsAlive() || + !IsValidLadyVashjCombatNpc(currentTarget, botAI))) { context->GetValue("current target")->Set(nullptr); bot->SetTarget(ObjectGuid::Empty); bot->SetSelection(ObjectGuid()); } - // If bots have wandered too far from the center and are not attacking anything, move them back + // If bots have wandered too far from the center and + // are not attacking anything, move them back if (!bot->GetVictim()) { Player* designatedLooter = GetDesignatedCoreLooter(bot->GetGroup(), botAI); @@ -2202,11 +2196,13 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) nearestTriggerGuid.clear(); } - if (!firstCorePasser || !secondCorePasser || !thirdCorePasser || !fourthCorePasser || !closestTrigger) + if (!firstCorePasser || !secondCorePasser || !thirdCorePasser || + !fourthCorePasser || !closestTrigger) return false; - // Not gated behind CheatMask because this is necessary to address an issue with bot movement, which - // is that bots cannot be rooted and therefore will move when feared while holding the Tainted Core + // Not gated behind CheatMask because the auto application of Fear Ward is necessary + // to address an issue with bot movement, which is that bots cannot be rooted and + // therefore will move when feared while holding the Tainted Core if (!bot->HasAura(SPELL_FEAR_WARD)) bot->AddAura(SPELL_FEAR_WARD, bot); @@ -2226,25 +2222,30 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) } else if (bot == thirdCorePasser) { - if (LineUpThirdCorePasser(designatedLooter, firstCorePasser, secondCorePasser, closestTrigger)) + if (LineUpThirdCorePasser(designatedLooter, firstCorePasser, + secondCorePasser, closestTrigger)) return true; } else if (bot == fourthCorePasser) { - if (LineUpFourthCorePasser(firstCorePasser, secondCorePasser, thirdCorePasser, closestTrigger)) + if (LineUpFourthCorePasser(firstCorePasser, secondCorePasser, + thirdCorePasser, closestTrigger)) return true; } } else if (item && botAI->HasItemInInventory(ITEM_TAINTED_CORE)) { - // Designated core looter logic--applicable only if cheat mode is on and thus looter is a bot + // Designated core looter logic + // Applicable only if cheat mode is on and thus looter is a bot if (bot == designatedLooter) { - if (IsFirstCorePasserInIntendedPosition(designatedLooter, firstCorePasser, closestTrigger)) + if (IsFirstCorePasserInIntendedPosition( + designatedLooter, firstCorePasser, closestTrigger)) { const time_t now = std::time(nullptr); - // Track lastImbueAttempt is to prevent repeated throwing animations from multiple imbue attempts + // Track lastImbueAttempt is to prevent repeated throwing animations + // from multiple imbue attempts auto [it, inserted] = lastImbueAttempt.try_emplace(SSC_MAP_ID, now); if (inserted) { @@ -2263,10 +2264,12 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) } } } - // First core passer: receive core from looter at the top of the stairs, pass to second core passer + // First core passer: receive core from looter at the top of the stairs, + // pass to second core passer else if (bot == firstCorePasser) { - if (IsSecondCorePasserInIntendedPosition(firstCorePasser, secondCorePasser, closestTrigger)) + if (IsSecondCorePasserInIntendedPosition( + firstCorePasser, secondCorePasser, closestTrigger)) { const time_t now = std::time(nullptr); @@ -2290,13 +2293,15 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) } } } - // Second core passer: if closest usable generator is within passing distance of the first passer, move - // to the generator; otherwise, move as close as possible to the generator while staying in passing range + // Second core passer: if closest usable generator is within passing distance + // of the first passer, move to the generator; otherwise, move as close as + // possible to the generator while staying in passing range else if (bot == secondCorePasser) { if (!UseCoreOnNearestGenerator()) { - if (IsThirdCorePasserInIntendedPosition(secondCorePasser, thirdCorePasser, closestTrigger)) + if (IsThirdCorePasserInIntendedPosition( + secondCorePasser, thirdCorePasser, closestTrigger)) { const time_t now = std::time(nullptr); @@ -2321,13 +2326,15 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) } } } - // Third core passer: if closest usable generator is within passing distance of the second passer, move - // to the generator; otherwise, move as close as possible to the generator while staying in passing range + // Third core passer: if closest usable generator is within passing distance + // of the second passer, move to the generator; otherwise, move as close as + // possible to the generator while staying in passing range else if (bot == thirdCorePasser) { if (!UseCoreOnNearestGenerator()) { - if (IsFourthCorePasserInIntendedPosition(thirdCorePasser, fourthCorePasser, closestTrigger)) + if (IsFourthCorePasserInIntendedPosition( + thirdCorePasser, fourthCorePasser, closestTrigger)) { const time_t now = std::time(nullptr); @@ -2352,7 +2359,8 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) } } } - // Fourth core passer: the fourth passer is rarely needed (and no more than four ever are) + // Fourth core passer: the fourth passer is rarely needed and no more than + // four ever should be, so it should use the Core on the nearest generator else if (bot == fourthCorePasser) { UseCoreOnNearestGenerator(); @@ -2402,10 +2410,12 @@ bool LadyVashjPassTheTaintedCoreAction::LineUpSecondCorePasser( // Target is on a line between firstCorePasser and closestTrigger float targetX, targetY, targetZ; - // If firstCorePasser is within thresholdDist of closestTrigger, go to nearTriggerDist short of closestTrigger + // If firstCorePasser is within thresholdDist of closestTrigger, + // go to nearTriggerDist short of closestTrigger const float thresholdDist = 40.0f; const float nearTriggerDist = 1.5f; - // If firstCorePasser is not thresholdDist yards from closestTrigger, go to farDistance from firstCorePasser + // If firstCorePasser is not thresholdDist yards from closestTrigger, + // go to farDistance from firstCorePasser const float farDistance = 38.0f; if (distToTrigger <= thresholdDist) @@ -2525,7 +2535,7 @@ bool LadyVashjPassTheTaintedCoreAction::LineUpFourthCorePasser( MovementPriority::MOVEMENT_FORCED, true, false); } -// The next four functions check if the respective core passer is within 2 yards of their intended +// The next four functions check if the respective passer is <= 2 yards of their intended // position and are used to determine when the prior bot in the chain can pass the core bool LadyVashjPassTheTaintedCoreAction::IsFirstCorePasserInIntendedPosition( Player* designatedLooter, Player* firstCorePasser, Unit* closestTrigger) @@ -2584,8 +2594,8 @@ bool LadyVashjPassTheTaintedCoreAction::IsFourthCorePasserInIntendedPosition( } // ImbueItem() is inconsistent in causing the receiving bot to receive the core -// So ScheduleStoreCoreAfterImbue() simulates the passing mechanic by creating the core on the receiver -// However, ImbueItem() does always take away the core from the passer +// So ScheduleStoreCoreAfterImbue() simulates the passing mechanic by creating the core +// on the receiver (note that ImbueItem() does always take away the core from the passer) void LadyVashjPassTheTaintedCoreAction::ScheduleStoreCoreAfterImbue( PlayerbotAI* botAI, Player* giver, Player* receiver) { @@ -2597,7 +2607,8 @@ void LadyVashjPassTheTaintedCoreAction::ScheduleStoreCoreAfterImbue( botAI->AddTimedEvent([receiverGuid]() { - Player* receiverPlayer = receiverGuid.IsEmpty() ? nullptr : ObjectAccessor::FindPlayer(receiverGuid); + Player* receiverPlayer = + receiverGuid.IsEmpty() ? nullptr : ObjectAccessor::FindPlayer(receiverGuid); if (!receiverPlayer) return; @@ -2606,7 +2617,8 @@ void LadyVashjPassTheTaintedCoreAction::ScheduleStoreCoreAfterImbue( ItemPosCountVec dest; uint32 count = 1; - int canStore = receiverPlayer->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, ITEM_TAINTED_CORE, count); + int canStore = + receiverPlayer->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, ITEM_TAINTED_CORE, count); if (canStore == EQUIP_ERR_OK) { @@ -2618,7 +2630,8 @@ void LadyVashjPassTheTaintedCoreAction::ScheduleStoreCoreAfterImbue( bool LadyVashjPassTheTaintedCoreAction::UseCoreOnNearestGenerator() { - auto const& generators = GetAllGeneratorInfosByDbGuids(bot->GetMap(), SHIELD_GENERATOR_DB_GUIDS); + auto const& generators = + GetAllGeneratorInfosByDbGuids(bot->GetMap(), SHIELD_GENERATOR_DB_GUIDS); const GeneratorInfo* nearestGen = GetNearestGeneratorToBot(bot, generators); if (!nearestGen) return false; @@ -2681,8 +2694,9 @@ bool LadyVashjDestroyTaintedCoreAction::Execute(Event event) return false; } -// The standard "avoid aoe" strategy does work for Toxic Spores, but I find it doesn't provide enough -// buffer distance, and it has a tendency to take bots down the stairs and get them stuck or out of LoS +// The standard "avoid aoe" strategy does work for Toxic Spores, but this method +// provides more buffer distance and limits the area in which bots can move +// so that they do not go down the stairs bool LadyVashjAvoidToxicSporesAction::Execute(Event event) { auto const& spores = GetAllSporeDropTriggers(botAI, bot); @@ -2723,8 +2737,9 @@ bool LadyVashjAvoidToxicSporesAction::Execute(Event event) } } -Position LadyVashjAvoidToxicSporesAction::FindSafestNearbyPosition(const std::vector& spores, - const Position& vashjCenter, float maxRadius, float hazardRadius) +Position LadyVashjAvoidToxicSporesAction::FindSafestNearbyPosition( + const std::vector& spores, const Position& vashjCenter, + float maxRadius, float hazardRadius) { const float searchStep = M_PI / 8.0f; const float minDistance = 2.0f; @@ -2732,10 +2747,11 @@ Position LadyVashjAvoidToxicSporesAction::FindSafestNearbyPosition(const std::ve const float distanceStep = 1.0f; Position bestPos; - float minMoveDistance = 1000.0f; + float minMoveDistance = std::numeric_limits::max(); bool foundSafe = false; - for (float distance = minDistance; distance <= maxDistance; distance += distanceStep) + for (float distance = minDistance; + distance <= maxDistance; distance += distanceStep) { for (float angle = 0.0f; angle < 2 * M_PI; angle += searchStep) { @@ -2761,7 +2777,8 @@ Position LadyVashjAvoidToxicSporesAction::FindSafestNearbyPosition(const std::ve Position testPos(x, y, z); - bool pathSafe = IsPathSafeFromSpores(bot->GetPosition(), testPos, spores, hazardRadius); + bool pathSafe = + IsPathSafeFromSpores(bot->GetPosition(), testPos, spores, hazardRadius); if (pathSafe || !foundSafe) { float moveDistance = bot->GetExactDist2d(x, y); @@ -2811,12 +2828,14 @@ bool LadyVashjAvoidToxicSporesAction::IsPathSafeFromSpores(const Position& start return true; } -// When Toxic Sporebats spit poison, they summon "Spore Drop Trigger" NPCs that create the toxic pools +// When Toxic Sporebats spit poison, they summon "Spore Drop Trigger" NPCs +// that create the toxic pools std::vector LadyVashjAvoidToxicSporesAction::GetAllSporeDropTriggers( PlayerbotAI* botAI, Player* bot) { std::vector sporeDropTriggers; - auto const& npcs = botAI->GetAiObjectContext()->GetValue("nearest npcs")->Get(); + auto const& npcs = + botAI->GetAiObjectContext()->GetValue("nearest npcs")->Get(); for (auto const& npcGuid : npcs) { const float maxSearchRadius = 40.0f; @@ -2835,10 +2854,12 @@ bool LadyVashjUseFreeActionAbilitiesAction::Execute(Event event) if (!group) return false; - auto const& spores = LadyVashjAvoidToxicSporesAction::GetAllSporeDropTriggers(botAI, bot); + auto const& spores = + LadyVashjAvoidToxicSporesAction::GetAllSporeDropTriggers(botAI, bot); const float toxicSporeRadius = 6.0f; - // If Rogues are Entangled and either have Static Charge or are near a spore, use Cloak of Shadows + // If Rogues are Entangled and either have Static Charge or + // are near a spore, use Cloak of Shadows if (bot->getClass() == CLASS_ROGUE && bot->HasAura(SPELL_ENTANGLE)) { bool nearSpore = false; diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h index 8e087bbba8..fd5ab3af5c 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h @@ -97,11 +97,11 @@ class TheLurkerBelowPositionMainTankAction : public AttackAction bool Execute(Event event) override; }; -class TheLurkerBelowSpreadRangedAction : public MovementAction +class TheLurkerBelowSpreadRangedInArcAction : public MovementAction { public: - TheLurkerBelowSpreadRangedAction( - PlayerbotAI* botAI, std::string const name = "the lurker below spread ranged") : MovementAction(botAI, name) {} + TheLurkerBelowSpreadRangedInArcAction( + PlayerbotAI* botAI, std::string const name = "the lurker below spread ranged in arc") : MovementAction(botAI, name) {} bool Execute(Event event) override; }; @@ -301,11 +301,11 @@ class LadyVashjMainTankPositionBossAction : public AttackAction bool Execute(Event event) override; }; -class LadyVashjPhase1PositionRangedAction : public MovementAction +class LadyVashjPhase1SpreadRangedInArcAction : public MovementAction { public: - LadyVashjPhase1PositionRangedAction( - PlayerbotAI* botAI, std::string const name = "lady vashj phase 1 position ranged") : MovementAction(botAI, name) {} + LadyVashjPhase1SpreadRangedInArcAction( + PlayerbotAI* botAI, std::string const name = "lady vashj phase 1 spread ranged in arc") : MovementAction(botAI, name) {} bool Execute(Event event) override; }; diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp index 4349b17f7e..1d14523c32 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp @@ -126,7 +126,8 @@ namespace SerpentShrineCavernHelpers for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsDps(member) && GET_PLAYERBOT_AI(member)) + if (member && member->IsAlive() && botAI->IsDps(member) && + GET_PLAYERBOT_AI(member)) return member == bot; } } @@ -136,7 +137,8 @@ namespace SerpentShrineCavernHelpers Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry) { - const GuidVector npcs = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); + auto const& npcs = + botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); for (auto const& npcGuid : npcs) { Unit* unit = botAI->GetUnit(npcGuid); @@ -199,11 +201,12 @@ namespace SerpentShrineCavernHelpers Unit* GetLeotherasHuman(PlayerbotAI* botAI) { - const GuidVector npcs = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); + auto const& npcs = + botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); for (auto const& guid : npcs) { Unit* unit = botAI->GetUnit(guid); - if (unit && unit->IsAlive() && unit->GetEntry() == NPC_LEOTHERAS_THE_BLIND && + if (unit && unit->GetEntry() == NPC_LEOTHERAS_THE_BLIND && unit->IsInCombat() && !unit->HasAura(SPELL_METAMORPHOSIS)) return unit; } @@ -212,11 +215,12 @@ namespace SerpentShrineCavernHelpers Unit* GetPhase2LeotherasDemon(PlayerbotAI* botAI) { - const GuidVector npcs = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); + auto const& npcs = + botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); for (auto const& guid : npcs) { Unit* unit = botAI->GetUnit(guid); - if (unit && unit->IsAlive() && unit->GetEntry() == NPC_LEOTHERAS_THE_BLIND && + if (unit && unit->GetEntry() == NPC_LEOTHERAS_THE_BLIND && unit->HasAura(SPELL_METAMORPHOSIS)) return unit; } @@ -225,11 +229,12 @@ namespace SerpentShrineCavernHelpers Unit* GetPhase3LeotherasDemon(PlayerbotAI* botAI) { - const GuidVector npcs = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); + auto const& npcs = + botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); for (auto const& guid : npcs) { Unit* unit = botAI->GetUnit(guid); - if (unit && unit->IsAlive() && unit->GetEntry() == NPC_SHADOW_OF_LEOTHERAS) + if (unit && unit->GetEntry() == NPC_SHADOW_OF_LEOTHERAS) return unit; } return nullptr; @@ -244,22 +249,31 @@ namespace SerpentShrineCavernHelpers Player* GetLeotherasDemonFormTank(PlayerbotAI* botAI, Player* bot) { - if (Group* group = bot->GetGroup()) + Group* group = bot->GetGroup(); + if (!group) + return nullptr; + + Player* fallbackWarlockTank = nullptr; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (!member || !member->IsAlive()) - continue; + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || member->getClass() != CLASS_WARLOCK) + continue; - PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); - if (member->getClass() == CLASS_WARLOCK && - memberAI && memberAI->HasStrategy("tank", BotState::BOT_STATE_COMBAT)) - return member; - } + // (1) Return the first assistant Warlock (real player or bot) + if (group->IsAssistant(member->GetGUID())) + return member; + + // (2) Otherwise, get the first Warlock bot with the co +tank strategy + PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); + if (!fallbackWarlockTank && memberAI && + memberAI->HasStrategy("tank", BotState::BOT_STATE_COMBAT)) + fallbackWarlockTank = member; } - return nullptr; + // (3) Return the fallback Warlock bot tank if found, + // otherwise nullptr (no Warlock tank for Leotheras) + return fallbackWarlockTank; } bool IsMainTankInSameSubgroup(Player* bot) @@ -293,17 +307,20 @@ namespace SerpentShrineCavernHelpers bool IsLadyVashjInPhase1(PlayerbotAI* botAI) { - Unit* vashj = botAI->GetAiObjectContext()->GetValue("find target", "lady vashj")->Get(); + Unit* vashj = + botAI->GetAiObjectContext()->GetValue("find target", "lady vashj")->Get(); if (!vashj) return false; Creature* vashjCreature = vashj->ToCreature(); - return vashjCreature && vashjCreature->GetHealthPct() > 70.0f && vashjCreature->GetReactState() != REACT_PASSIVE; + return vashjCreature && vashjCreature->GetHealthPct() > 70.0f && + vashjCreature->GetReactState() != REACT_PASSIVE; } bool IsLadyVashjInPhase2(PlayerbotAI* botAI) { - Unit* vashj = botAI->GetAiObjectContext()->GetValue("find target", "lady vashj")->Get(); + Unit* vashj = + botAI->GetAiObjectContext()->GetValue("find target", "lady vashj")->Get(); if (!vashj) return false; @@ -313,12 +330,14 @@ namespace SerpentShrineCavernHelpers bool IsLadyVashjInPhase3(PlayerbotAI* botAI) { - Unit* vashj = botAI->GetAiObjectContext()->GetValue("find target", "lady vashj")->Get(); + Unit* vashj = + botAI->GetAiObjectContext()->GetValue("find target", "lady vashj")->Get(); if (!vashj) return false; Creature* vashjCreature = vashj->ToCreature(); - return vashjCreature && vashjCreature->GetHealthPct() <= 50.0f && vashjCreature->GetReactState() != REACT_PASSIVE; + return vashjCreature && vashjCreature->GetHealthPct() <= 50.0f && + vashjCreature->GetReactState() != REACT_PASSIVE; } bool IsValidLadyVashjCombatNpc(Unit* unit, PlayerbotAI* botAI) @@ -460,8 +479,9 @@ namespace SerpentShrineCavernHelpers for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) || botAI->IsTank(member) || - member == designatedLooter || member == firstCorePasser) + if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) || + botAI->IsTank(member) || member == designatedLooter || + member == firstCorePasser) continue; return member; } @@ -496,8 +516,9 @@ namespace SerpentShrineCavernHelpers for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) || botAI->IsTank(member) || - member == designatedLooter || member == firstCorePasser || member == secondCorePasser) + if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) || + botAI->IsTank(member) || member == designatedLooter || + member == firstCorePasser || member == secondCorePasser) continue; return member; } @@ -519,7 +540,8 @@ namespace SerpentShrineCavernHelpers { Player* member = ref->GetSource(); if (!member || !member->IsAlive() || member == designatedLooter || - member == firstCorePasser || member == secondCorePasser || member == thirdCorePasser) + member == firstCorePasser || member == secondCorePasser || + member == thirdCorePasser) continue; PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); @@ -533,8 +555,9 @@ namespace SerpentShrineCavernHelpers for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) || botAI->IsTank(member) || - member == designatedLooter || member == firstCorePasser || member == secondCorePasser || + if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) || + botAI->IsTank(member) || member == designatedLooter || + member == firstCorePasser || member == secondCorePasser || member == thirdCorePasser) continue; return member; @@ -543,10 +566,17 @@ namespace SerpentShrineCavernHelpers return nullptr; } - const std::vector SHIELD_GENERATOR_DB_GUIDS = { 47482, 47483, 47484, 47485 }; // NW, NE, SE, SW + const std::vector SHIELD_GENERATOR_DB_GUIDS = + { + 47482, // NW + 47483, // NE + 47484, // SE + 47485 // SW + }; // Get the positions of all active Shield Generators by their database GUIDs - std::vector GetAllGeneratorInfosByDbGuids(Map* map, const std::vector& generatorDbGuids) + std::vector GetAllGeneratorInfosByDbGuids( + Map* map, const std::vector& generatorDbGuids) { std::vector generators; if (!map) @@ -577,7 +607,8 @@ namespace SerpentShrineCavernHelpers } // Returns the nearest active Shield Generator to the bot - // Active generators are powered by NPC_WORLD_INVISIBLE_TRIGGER creatures, which depawn after use + // Active generators are powered by NPC_WORLD_INVISIBLE_TRIGGER creatures, + // which depawn after use Unit* GetNearestActiveShieldGeneratorTriggerByEntry(Unit* reference) { if (!reference) @@ -585,7 +616,8 @@ namespace SerpentShrineCavernHelpers std::list triggers; const float searchRange = 150.0f; - reference->GetCreatureListWithEntryInGrid(triggers, NPC_WORLD_INVISIBLE_TRIGGER, searchRange); + reference->GetCreatureListWithEntryInGrid( + triggers, NPC_WORLD_INVISIBLE_TRIGGER, searchRange); Creature* nearest = nullptr; float minDist = std::numeric_limits::max(); @@ -606,7 +638,8 @@ namespace SerpentShrineCavernHelpers return nearest; } - const GeneratorInfo* GetNearestGeneratorToBot(Player* bot, const std::vector& generators) + const GeneratorInfo* GetNearestGeneratorToBot( + Player* bot, const std::vector& generators) { if (!bot || generators.empty()) return nullptr; diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h index aadeb11119..92986eb999 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h @@ -47,9 +47,6 @@ namespace SerpentShrineCavernHelpers SPELL_STATIC_CHARGE = 38280, SPELL_ENTANGLE = 38316, - // Druid - SPELL_TREE_OF_LIFE = 33891, - // Hunter SPELL_MISDIRECTION = 35079, @@ -198,9 +195,11 @@ namespace SerpentShrineCavernHelpers }; extern const std::vector SHIELD_GENERATOR_DB_GUIDS; - std::vector GetAllGeneratorInfosByDbGuids(Map* map, const std::vector& generatorDbGuids); + std::vector GetAllGeneratorInfosByDbGuids( + Map* map, const std::vector& generatorDbGuids); Unit* GetNearestActiveShieldGeneratorTriggerByEntry(Unit* reference); - const GeneratorInfo* GetNearestGeneratorToBot(Player* bot, const std::vector& generators); + const GeneratorInfo* GetNearestGeneratorToBot( + Player* bot, const std::vector& generators); } #endif diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp index dd8ccdaafe..e506fa3882 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp @@ -22,17 +22,12 @@ using namespace SerpentShrineCavernHelpers; float UnderbogColossusEscapeToxicPoolMultiplier::GetValue(Action* action) { - Aura* aura = bot->GetAura(SPELL_TOXIC_POOL); - if (!aura) - return 1.0f; - - DynamicObject* dynObj = aura->GetDynobjOwner(); - if (!dynObj) - return 1.0f; - - if (dynamic_cast(action) && - !dynamic_cast(action)) - return 0.0f; + if (bot->HasAura(SPELL_TOXIC_POOL)) + { + if (dynamic_cast(action) && + !dynamic_cast(action)) + return 0.0f; + } return 1.0f; } @@ -41,23 +36,26 @@ float UnderbogColossusEscapeToxicPoolMultiplier::GetValue(Action* action) float HydrossTheUnstableDisableTankActionsMultiplier::GetValue(Action* action) { - Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross"); + if (!botAI->IsMainTank(bot) && !botAI->IsAssistTankOfIndex(bot, 0)) + return 1.0f; + + Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); if (!hydross) return 1.0f; - if (botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0)) - { - if (dynamic_cast(action)) - return 0.0f; - } + if (dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; if (botAI->IsMainTank(bot)) { if (hydross->HasAura(SPELL_CORRUPTION)) { - if (dynamic_cast(action) && - !dynamic_cast(action)) - return 0.0f; + if (dynamic_cast(action) || + dynamic_cast(action) || + (dynamic_cast(action) && + !dynamic_cast(action))) + return 0.0f; } } @@ -65,9 +63,11 @@ float HydrossTheUnstableDisableTankActionsMultiplier::GetValue(Action* action) { if (!hydross->HasAura(SPELL_CORRUPTION)) { - if (dynamic_cast(action) && - !dynamic_cast(action)) - return 0.0f; + if (dynamic_cast(action) || + dynamic_cast(action) || + (dynamic_cast(action) && + !dynamic_cast(action))) + return 0.0f; } } @@ -93,58 +93,42 @@ float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action) const uint8 phaseChangeWaitSeconds = 1; const uint8 dpsWaitSeconds = 5; - if (!hydross->HasAura(SPELL_CORRUPTION)) + if (!hydross->HasAura(SPELL_CORRUPTION) && !botAI->IsMainTank(bot)) { - if (botAI->IsAssistTankOfIndex(bot, 0)) - { - if (dynamic_cast(action) && - !dynamic_cast(action)) - return 0.0f; - } - else if (!botAI->IsMainTank(bot)) - { - auto itDps = hydrossFrostDpsWaitTimer.find(SSC_MAP_ID); - auto itPhase = hydrossChangeToFrostPhaseTimer.find(SSC_MAP_ID); + auto itDps = hydrossFrostDpsWaitTimer.find(SSC_MAP_ID); + auto itPhase = hydrossChangeToFrostPhaseTimer.find(SSC_MAP_ID); - bool justChanged = (itDps == hydrossFrostDpsWaitTimer.end() || - (now - itDps->second) < dpsWaitSeconds); - bool aboutToChange = (itPhase != hydrossChangeToFrostPhaseTimer.end() && - (now - itPhase->second) > phaseChangeWaitSeconds); + bool justChanged = (itDps == hydrossFrostDpsWaitTimer.end() || + (now - itDps->second) < dpsWaitSeconds); + bool aboutToChange = (itPhase != hydrossChangeToFrostPhaseTimer.end() && + (now - itPhase->second) > phaseChangeWaitSeconds); - if (justChanged || aboutToChange) - { - if (dynamic_cast(action) || - (dynamic_cast(action) && !dynamic_cast(action))) - return 0.0f; - } + if (justChanged || aboutToChange) + { + if (dynamic_cast(action) || + (dynamic_cast(action) && + !dynamic_cast(action))) + return 0.0f; } } - if (hydross->HasAura(SPELL_CORRUPTION)) + if (hydross->HasAura(SPELL_CORRUPTION) && !botAI->IsAssistTankOfIndex(bot, 0)) { - if (botAI->IsMainTank(bot)) - { - if (dynamic_cast(action) && - !dynamic_cast(action)) - return 0.0f; - } - else if (!botAI->IsAssistTankOfIndex(bot, 0)) - { - auto itDps = hydrossNatureDpsWaitTimer.find(SSC_MAP_ID); - auto itPhase = hydrossChangeToNaturePhaseTimer.find(SSC_MAP_ID); + auto itDps = hydrossNatureDpsWaitTimer.find(SSC_MAP_ID); + auto itPhase = hydrossChangeToNaturePhaseTimer.find(SSC_MAP_ID); - bool justChanged = (itDps == hydrossNatureDpsWaitTimer.end() || - (now - itDps->second) < dpsWaitSeconds); - bool aboutToChange = (itPhase != hydrossChangeToNaturePhaseTimer.end() && - (now - itPhase->second) > phaseChangeWaitSeconds); + bool justChanged = (itDps == hydrossNatureDpsWaitTimer.end() || + (now - itDps->second) < dpsWaitSeconds); + bool aboutToChange = (itPhase != hydrossChangeToNaturePhaseTimer.end() && + (now - itPhase->second) > phaseChangeWaitSeconds); - if (justChanged || aboutToChange) - { - if (dynamic_cast(action) || - (dynamic_cast(action) && !dynamic_cast(action))) - return 0.0f; - } + if (justChanged || aboutToChange) + { + if (dynamic_cast(action) || + (dynamic_cast(action) && + !dynamic_cast(action))) + return 0.0f; } } @@ -153,12 +137,15 @@ float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action) float HydrossTheUnstableControlMisdirectionMultiplier::GetValue(Action* action) { - Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); - if (!hydross) + if (bot->getClass() != CLASS_HUNTER) return 1.0f; - if (dynamic_cast(action)) - return 0.0f; + Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); + if (hydross) + { + if (dynamic_cast(action)) + return 0.0f; + } return 1.0f; } @@ -176,10 +163,14 @@ float TheLurkerBelowStayAwayFromSpoutMultiplier::GetValue(Action* action) auto it = lurkerSpoutTimer.find(SSC_MAP_ID); if (it != lurkerSpoutTimer.end() && it->second > now) { - if (dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action)) + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) return 0.0f; } @@ -189,6 +180,9 @@ float TheLurkerBelowStayAwayFromSpoutMultiplier::GetValue(Action* action) // Disable tank assist during Submerge only if there are 3 or more tanks in the raid float TheLurkerBelowDisableTankAssistMultiplier::GetValue(Action* action) { + if (!botAI->IsTank(bot)) + return 1.0f; + Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); if (!lurker || lurker->getStandState() != UNIT_STAND_STATE_SUBMERGED) return 1.0f; @@ -221,22 +215,23 @@ float TheLurkerBelowDisableTankAssistMultiplier::GetValue(Action* action) float LeotherasTheBlindAvoidWhirlwindMultiplier::GetValue(Action* action) { + if (botAI->IsTank(bot)) + return 1.0f; + Unit* leotherasHuman = GetLeotherasHuman(botAI); if (!leotherasHuman) return 1.0f; if (!leotherasHuman->HasAura(SPELL_LEOTHERAS_BANISHED) && - (leotherasHuman->HasAura(SPELL_WHIRLWIND) || leotherasHuman->HasAura(SPELL_WHIRLWIND_CHANNEL))) + (leotherasHuman->HasAura(SPELL_WHIRLWIND) || + leotherasHuman->HasAura(SPELL_WHIRLWIND_CHANNEL))) { - if (!botAI->IsTank(bot)) - { - if (dynamic_cast(action)) - return 0.0f; + if (dynamic_cast(action)) + return 0.0f; - if (dynamic_cast(action) && - !dynamic_cast(action)) - return 0.0f; - } + if (dynamic_cast(action) && !dynamic_cast(action) && + !dynamic_cast(action)) + return 0.0f; } return 1.0f; @@ -273,21 +268,21 @@ float LeotherasTheBlindDisableTankActionsMultiplier::GetValue(Action* action) float LeotherasTheBlindMeleeDpsAvoidChaosBlastMultiplier::GetValue(Action* action) { + if (!botAI->IsMelee(bot) || botAI->IsTank(bot)) + return 1.0f; + Unit* leotherasDemonPhase2 = GetPhase2LeotherasDemon(botAI); if (!leotherasDemonPhase2) return 1.0f; - if (botAI->IsMelee(bot) && !botAI->IsTank(bot)) + Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST); + if (chaosBlast && chaosBlast->GetStackAmount() >= 5) { - Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST); - if (chaosBlast && chaosBlast->GetStackAmount() >= 5) - { - if (dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action)) - return 0.0f; - } + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; } return 1.0f; @@ -307,16 +302,19 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) const uint8 dpsWaitSecondsPhase1 = 5; Unit* leotherasHuman = GetLeotherasHuman(botAI); Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI); - if (leotherasHuman && !leotherasHuman->HasAura(SPELL_LEOTHERAS_BANISHED) && !leotherasPhase3Demon) + if (leotherasHuman && !leotherasHuman->HasAura(SPELL_LEOTHERAS_BANISHED) && + !leotherasPhase3Demon) { if (botAI->IsTank(bot)) return 1.0f; auto it = leotherasHumanFormDpsWaitTimer.find(SSC_MAP_ID); - if (it == leotherasHumanFormDpsWaitTimer.end() || (now - it->second) < dpsWaitSecondsPhase1) + if (it == leotherasHumanFormDpsWaitTimer.end() || + (now - it->second) < dpsWaitSecondsPhase1) { if (dynamic_cast(action) || - (dynamic_cast(action) && !dynamic_cast(action))) + (dynamic_cast(action) && + !dynamic_cast(action))) return 0.0f; } } @@ -333,10 +331,12 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) return 1.0f; auto it = leotherasDemonFormDpsWaitTimer.find(SSC_MAP_ID); - if (it == leotherasDemonFormDpsWaitTimer.end() || (now - it->second) < dpsWaitSecondsPhase2) + if (it == leotherasDemonFormDpsWaitTimer.end() || + (now - it->second) < dpsWaitSecondsPhase2) { if (dynamic_cast(action) || - (dynamic_cast(action) && !dynamic_cast(action))) + (dynamic_cast(action) && + !dynamic_cast(action))) return 0.0f; } } @@ -348,10 +348,12 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) return 1.0f; auto it = leotherasFinalPhaseDpsWaitTimer.find(SSC_MAP_ID); - if (it == leotherasFinalPhaseDpsWaitTimer.end() || (now - it->second) < dpsWaitSecondsPhase3) + if (it == leotherasFinalPhaseDpsWaitTimer.end() || + (now - it->second) < dpsWaitSecondsPhase3) { if (dynamic_cast(action) || - (dynamic_cast(action) && !dynamic_cast(action))) + (dynamic_cast(action) && + !dynamic_cast(action))) return 0.0f; } } @@ -362,6 +364,9 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) // Wait until the final phase to use Bloodlust/Heroism float LeotherasTheBlindDelayBloodlustAndHeroismMultiplier::GetValue(Action* action) { + if (bot->getClass() != CLASS_SHAMAN) + return 1.0f; + Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); if (!leotheras) return 1.0f; @@ -369,7 +374,8 @@ float LeotherasTheBlindDelayBloodlustAndHeroismMultiplier::GetValue(Action* acti Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI); if (!leotherasPhase3Demon) { - if (dynamic_cast(action) || dynamic_cast(action)) + if (dynamic_cast(action) || + dynamic_cast(action)) return 0.0f; } @@ -378,13 +384,17 @@ float LeotherasTheBlindDelayBloodlustAndHeroismMultiplier::GetValue(Action* acti // Fathom-Lord Karathress -float FathomLordKarathressDisableTankAssistMultiplier::GetValue(Action* action) +float FathomLordKarathressDisableTankActionsMultiplier::GetValue(Action* action) { + if (!botAI->IsTank(bot)) + return 1.0f; + Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); if (!karathress) return 1.0f; - if (bot->GetVictim() != nullptr && dynamic_cast(action)) + if ((bot->GetVictim() != nullptr && dynamic_cast(action)) || + dynamic_cast(action)) return 0.0f; return 1.0f; @@ -393,47 +403,51 @@ float FathomLordKarathressDisableTankAssistMultiplier::GetValue(Action* action) float FathomLordKarathressDisableAoeMultiplier::GetValue(Action* action) { Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); - if (!karathress) - return 1.0f; - - if (dynamic_cast(action)) - return 0.0f; + if (karathress) + { + if (dynamic_cast(action)) + return 0.0f; + } return 1.0f; } float FathomLordKarathressControlMisdirectionMultiplier::GetValue(Action* action) { - Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); - if (!karathress) + if (bot->getClass() != CLASS_HUNTER) return 1.0f; - if (dynamic_cast(action)) - return 0.0f; + Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); + if (karathress) + { + if (dynamic_cast(action)) + return 0.0f; + } return 1.0f; } float FathomLordKarathressWaitForDpsMultiplier::GetValue(Action* action) { - Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); - if (!karathress) + if (botAI->IsTank(bot)) return 1.0f; - if (botAI->IsTank(bot)) + Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); + if (!karathress) return 1.0f; if (dynamic_cast(action)) return 1.0f; const time_t now = std::time(nullptr); - const uint8 dpsWaitSeconds = 8; + const uint8 dpsWaitSeconds = 10; auto it = karathressDpsWaitTimer.find(SSC_MAP_ID); if (it == karathressDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds) { if (dynamic_cast(action) || - (dynamic_cast(action) && !dynamic_cast(action))) + (dynamic_cast(action) && + !dynamic_cast(action))) return 0.0f; } @@ -442,14 +456,15 @@ float FathomLordKarathressWaitForDpsMultiplier::GetValue(Action* action) float FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier::GetValue(Action* action) { - Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); - if (!caribdis || !caribdis->IsAlive()) + if (!botAI->IsHealAssistantOfIndex(bot, 0)) return 1.0f; - if (botAI->IsHealAssistantOfIndex(bot, 0)) + Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); + if (caribdis && caribdis->IsAlive()) { - if (dynamic_cast(action) || dynamic_cast(action)) - return 0.0f; + if (dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; } return 1.0f; @@ -458,6 +473,9 @@ float FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier::GetValue // Use Bloodlust/Heroism after the first Murloc spawn float MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier::GetValue(Action* action) { + if (bot->getClass() != CLASS_SHAMAN) + return 1.0f; + Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); if (!tidewalker) return 1.0f; @@ -465,7 +483,23 @@ float MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier::GetValue(Action* act Unit* murloc = AI_VALUE2(Unit*, "find target", "tidewalker lurker"); if (!murloc) { - if (dynamic_cast(action) || dynamic_cast(action)) + if (dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + +float MorogrimTidewalkerDisableTankActionsMultiplier::GetValue(Action* action) +{ + if (!botAI->IsMainTank(bot)) + return 1.0f; + + Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); + if (tidewalker) + { + if (dynamic_cast(action)) return 0.0f; } @@ -480,7 +514,8 @@ float MorogrimTidewalkerDisablePhase2MovementActionsMultiplier::GetValue(Action* if (tidewalker->GetHealthPct() < 25.0f) { - if (dynamic_cast(action) || dynamic_cast(action) || + if (dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action)) return 0.0f; } @@ -491,6 +526,9 @@ float MorogrimTidewalkerDisablePhase2MovementActionsMultiplier::GetValue(Action* // Wait until phase 3 to use Bloodlust/Heroism float LadyVashjDelayBloodlustAndHeroismMultiplier::GetValue(Action* action) { + if (bot->getClass() != CLASS_SHAMAN) + return 1.0f; + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); if (!vashj || IsLadyVashjInPhase3(botAI)) return 1.0f; @@ -504,21 +542,20 @@ float LadyVashjDelayBloodlustAndHeroismMultiplier::GetValue(Action* action) float LadyVashjStaticChargeStayAwayFromGroupMultiplier::GetValue(Action* action) { + if (botAI->IsMainTank(bot) || !bot->HasAura(SPELL_STATIC_CHARGE)) + return 1.0f; + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); if (!vashj) return 1.0f; - if (!botAI->IsMainTank(bot) && bot->HasAura(SPELL_STATIC_CHARGE)) - { - if (dynamic_cast(action) && - !dynamic_cast(action) && - !dynamic_cast(action)) - return 0.0f; - - if (dynamic_cast(action) || - dynamic_cast(action)) - return 0.0f; - } + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; return 1.0f; } @@ -527,12 +564,15 @@ float LadyVashjStaticChargeStayAwayFromGroupMultiplier::GetValue(Action* action) // Bots should not loot the core float LadyVashjDoNotLootTheTaintedCoreMultiplier::GetValue(Action* action) { - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - if (!vashj || !botAI->HasCheat(BotCheatMask::raid)) + if (!botAI->HasCheat(BotCheatMask::raid)) return 1.0f; - if (dynamic_cast(action)) - return 0.0f; + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + if (vashj) + { + if (dynamic_cast(action)) + return 0.0f; + } return 1.0f; } @@ -617,8 +657,10 @@ float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *ac if (IsLadyVashjInPhase2(botAI)) { - if (dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action)) + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) return 0.0f; if (!botAI->IsHeal(bot) && dynamic_cast(action)) @@ -634,8 +676,10 @@ float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *ac if (IsLadyVashjInPhase3(botAI)) { - if (dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || dynamic_cast(action)) + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) return 0.0f; Unit* strider = AI_VALUE2(Unit*, "find target", "coilfang strider"); diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h index 68d3578552..7f55d963a3 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h @@ -91,11 +91,11 @@ class LeotherasTheBlindDelayBloodlustAndHeroismMultiplier : public Multiplier virtual float GetValue(Action* action); }; -class FathomLordKarathressDisableTankAssistMultiplier : public Multiplier +class FathomLordKarathressDisableTankActionsMultiplier : public Multiplier { public: - FathomLordKarathressDisableTankAssistMultiplier( - PlayerbotAI* botAI) : Multiplier(botAI, "fathom-lord karathress disable tank assist") {} + FathomLordKarathressDisableTankActionsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "fathom-lord karathress disable tank actions") {} virtual float GetValue(Action* action); }; @@ -139,6 +139,14 @@ class MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier : public Multiplier virtual float GetValue(Action* action); }; +class MorogrimTidewalkerDisableTankActionsMultiplier : public Multiplier +{ +public: + MorogrimTidewalkerDisableTankActionsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "morogrim tidewalker disable tank actions") {} + virtual float GetValue(Action* action); +}; + class MorogrimTidewalkerDisablePhase2MovementActionsMultiplier : public Multiplier { public: diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp index b2e183ea13..2dfd0edd15 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp @@ -42,7 +42,7 @@ void RaidSSCStrategy::InitTriggers(std::vector& triggers) NextAction::array(0, new NextAction("the lurker below position main tank", ACTION_RAID + 1), nullptr) )); triggers.push_back(new TriggerNode("the lurker below boss casts geyser", - NextAction::array(0, new NextAction("the lurker below spread ranged", ACTION_RAID + 1), nullptr) + NextAction::array(0, new NextAction("the lurker below spread ranged in arc", ACTION_RAID + 1), nullptr) )); triggers.push_back(new TriggerNode("the lurker below boss is submerged", NextAction::array(0, new NextAction("the lurker below tanks pick up adds", ACTION_EMERGENCY + 1), nullptr) @@ -125,7 +125,7 @@ void RaidSSCStrategy::InitTriggers(std::vector& triggers) NextAction::array(0, new NextAction("lady vashj main tank position boss", ACTION_RAID + 1), nullptr) )); triggers.push_back(new TriggerNode("lady vashj boss engaged by ranged in phase 1", - NextAction::array(0, new NextAction("lady vashj phase 1 position ranged", ACTION_RAID + 1), nullptr) + NextAction::array(0, new NextAction("lady vashj phase 1 spread ranged in arc", ACTION_RAID + 1), nullptr) )); triggers.push_back(new TriggerNode("lady vashj casts shock blast on highest aggro", NextAction::array(0, new NextAction("lady vashj set grounding totem in main tank group", ACTION_EMERGENCY + 1), nullptr) @@ -179,12 +179,13 @@ void RaidSSCStrategy::InitMultipliers(std::vector& multipliers) multipliers.push_back(new LeotherasTheBlindMeleeDpsAvoidChaosBlastMultiplier(botAI)); multipliers.push_back(new LeotherasTheBlindWaitForDpsMultiplier(botAI)); multipliers.push_back(new LeotherasTheBlindDelayBloodlustAndHeroismMultiplier(botAI)); - multipliers.push_back(new FathomLordKarathressDisableTankAssistMultiplier(botAI)); + multipliers.push_back(new FathomLordKarathressDisableTankActionsMultiplier(botAI)); multipliers.push_back(new FathomLordKarathressDisableAoeMultiplier(botAI)); multipliers.push_back(new FathomLordKarathressControlMisdirectionMultiplier(botAI)); multipliers.push_back(new FathomLordKarathressWaitForDpsMultiplier(botAI)); multipliers.push_back(new FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier(botAI)); multipliers.push_back(new MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier(botAI)); + multipliers.push_back(new MorogrimTidewalkerDisableTankActionsMultiplier(botAI)); multipliers.push_back(new MorogrimTidewalkerDisablePhase2MovementActionsMultiplier(botAI)); multipliers.push_back(new LadyVashjDelayBloodlustAndHeroismMultiplier(botAI)); multipliers.push_back(new LadyVashjStaticChargeStayAwayFromGroupMultiplier(botAI)); diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp index aa329d68b9..d33b668079 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp @@ -441,7 +441,7 @@ bool LadyVashjBossEngagedByMainTankTrigger::IsActive() return false; Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - return vashj && (IsLadyVashjInPhase1(botAI) || IsLadyVashjInPhase3(botAI)); + return vashj && !IsLadyVashjInPhase2(botAI); } bool LadyVashjBossEngagedByRangedInPhase1Trigger::IsActive() @@ -459,7 +459,13 @@ bool LadyVashjCastsShockBlastOnHighestAggroTrigger::IsActive() return false; Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - return vashj && (IsLadyVashjInPhase1(botAI) || IsLadyVashjInPhase3(botAI)); + if (!vashj || IsLadyVashjInPhase2(botAI)) + return false; + + if (!IsMainTankInSameSubgroup(bot)) + return false; + + return true; } bool LadyVashjBotHasStaticChargeTrigger::IsActive() From 266996052edfa0c393875be62a41ed69b1f7c23b Mon Sep 17 00:00:00 2001 From: crow Date: Thu, 18 Dec 2025 01:47:03 -0600 Subject: [PATCH 06/25] switch to instanceid timers and general reorganization --- .../serpentshrinecavern/RaidSSCActions.cpp | 428 +++++++++--------- .../serpentshrinecavern/RaidSSCActions.h | 14 + .../serpentshrinecavern/RaidSSCHelpers.cpp | 110 +++-- .../serpentshrinecavern/RaidSSCHelpers.h | 95 ++-- .../RaidSSCMultipliers.cpp | 66 ++- .../serpentshrinecavern/RaidSSCMultipliers.h | 22 +- .../serpentshrinecavern/RaidSSCStrategy.cpp | 4 +- .../serpentshrinecavern/RaidSSCTriggers.cpp | 16 +- .../serpentshrinecavern/RaidSSCTriggers.h | 14 + 9 files changed, 435 insertions(+), 334 deletions(-) diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp index 0d473919a5..00e1e836ba 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp @@ -1,6 +1,5 @@ #include "RaidSSCActions.h" #include "RaidSSCHelpers.h" -#include "AiFactory.h" #include "Corpse.h" #include "LootAction.h" #include "LootObjectStack.h" @@ -47,21 +46,22 @@ bool UnderbogColossusEscapeToxicPoolAction::Execute(Event event) if (radius <= 0.0f) return false; - const float buffer = 3.0f; + const float bufferDist = 3.0f; const float centerThreshold = 1.0f; + float dx = bot->GetPositionX() - dynObj->GetPositionX(); float dy = bot->GetPositionY() - dynObj->GetPositionY(); - float distSq = dx * dx + dy * dy; + + float distToObj = bot->GetExactDist2d(dynObj->GetPositionX(), dynObj->GetPositionY()); const float insideThresh = radius + centerThreshold; - const float insideThreshSq = insideThresh * insideThresh; - if (distSq > insideThreshSq) + if (distToObj > insideThresh) return false; - float safeDist = radius + buffer; + float safeDist = radius + bufferDist; float moveX, moveY; - if (distSq == 0.0f) + if (distToObj == 0.0f) { float angle = frand(0.0f, static_cast(M_PI * 2.0)); moveX = dynObj->GetPositionX() + std::cos(angle) * safeDist; @@ -69,25 +69,23 @@ bool UnderbogColossusEscapeToxicPoolAction::Execute(Event event) } else { - float dist = bot->GetExactDist2d(dynObj->GetPositionX(), dynObj->GetPositionY()); - float inv = 1.0f / dist; - moveX = dynObj->GetPositionX() + (dx * inv) * safeDist; - moveY = dynObj->GetPositionY() + (dy * inv) * safeDist; + float invDist = 1.0f / distToObj; + moveX = dynObj->GetPositionX() + (dx * invDist) * safeDist; + moveY = dynObj->GetPositionY() + (dy * invDist) * safeDist; } bot->AttackStop(); bot->InterruptNonMeleeSpells(true); - return MoveTo(SSC_MAP_ID, moveX, moveY, bot->GetPositionZ(), - false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); + return MoveTo(SSC_MAP_ID, moveX, moveY, bot->GetPositionZ(), false, false, false, + true, MovementPriority::MOVEMENT_FORCED, true, false); } bool GreyheartTidecallerMarkWaterElementalTotemAction::Execute(Event event) { Unit* totem = GetFirstAliveUnitByEntry(botAI, NPC_WATER_ELEMENTAL_TOTEM); - if (!totem) - return false; + if (totem) + MarkTargetWithSkull(bot, totem); - MarkTargetWithSkull(bot, totem); return false; } @@ -113,14 +111,16 @@ bool HydrossTheUnstablePositionFrostTankAction::Execute(Event event) if (hydross->GetVictim() == bot && bot->IsWithinMeleeRange(hydross)) { const Position& position = HYDROSS_FROST_TANK_POSITION; - float dist = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); - if (dist > 2.0f) + float distToPosition = + bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); + + if (distToPosition > 2.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); - float moveDist = std::min(5.0f, dist); - float moveX = bot->GetPositionX() + (dX / dist) * moveDist; - float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + float moveDist = std::min(5.0f, distToPosition); + float moveX = bot->GetPositionX() + (dX / distToPosition) * moveDist; + float moveY = bot->GetPositionY() + (dY / distToPosition) * moveDist; return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, true); @@ -132,19 +132,21 @@ bool HydrossTheUnstablePositionFrostTankAction::Execute(Event event) hydross->GetVictim() == bot && bot->IsWithinMeleeRange(hydross)) { const time_t now = std::time(nullptr); - auto it = hydrossChangeToNaturePhaseTimer.find(SSC_MAP_ID); + auto it = hydrossChangeToNaturePhaseTimer.find(hydross->GetMap()->GetInstanceId()); if (it != hydrossChangeToNaturePhaseTimer.end() && (now - it->second) >= 1) { const Position& position = HYDROSS_NATURE_TANK_POSITION; - float dist = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); - if (dist > 2.0f) + float distToPosition = + bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); + + if (distToPosition > 2.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); - float moveDist = std::min(5.0f, dist); - float moveX = bot->GetPositionX() + (dX / dist) * moveDist; - float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + float moveDist = std::min(5.0f, distToPosition); + float moveX = bot->GetPositionX() + (dX / distToPosition) * moveDist; + float moveY = bot->GetPositionY() + (dY / distToPosition) * moveDist; return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, true); @@ -186,14 +188,16 @@ bool HydrossTheUnstablePositionNatureTankAction::Execute(Event event) if (hydross->GetVictim() == bot && bot->IsWithinMeleeRange(hydross)) { const Position& position = HYDROSS_NATURE_TANK_POSITION; - float dist = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); - if (dist > 2.0f) + float distToPosition = + bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); + + if (distToPosition > 2.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); - float moveDist = std::min(5.0f, dist); - float moveX = bot->GetPositionX() + (dX / dist) * moveDist; - float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + float moveDist = std::min(5.0f, distToPosition); + float moveX = bot->GetPositionX() + (dX / distToPosition) * moveDist; + float moveY = bot->GetPositionY() + (dY / distToPosition) * moveDist; return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, true); @@ -205,19 +209,21 @@ bool HydrossTheUnstablePositionNatureTankAction::Execute(Event event) hydross->GetVictim() == bot && bot->IsWithinMeleeRange(hydross)) { const time_t now = std::time(nullptr); - auto it = hydrossChangeToFrostPhaseTimer.find(SSC_MAP_ID); + auto it = hydrossChangeToFrostPhaseTimer.find(hydross->GetMap()->GetInstanceId()); if (it != hydrossChangeToFrostPhaseTimer.end() && (now - it->second) >= 1) { const Position& position = HYDROSS_FROST_TANK_POSITION; - float dist = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); - if (dist > 2.0f) + float distToPosition = + bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); + + if (distToPosition > 2.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); - float moveDist = std::min(5.0f, dist); - float moveX = bot->GetPositionX() + (dX / dist) * moveDist; - float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + float moveDist = std::min(5.0f, distToPosition); + float moveX = bot->GetPositionX() + (dX / distToPosition) * moveDist; + float moveY = bot->GetPositionY() + (dY / distToPosition) * moveDist; return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, true); @@ -250,29 +256,23 @@ bool HydrossTheUnstablePrioritizeElementalAddsAction::Execute(Event event) Unit* waterElemental = GetFirstAliveUnitByEntry(botAI, NPC_PURE_SPAWN_OF_HYDROSS); if (waterElemental) { - if (IsMapIDTimerManager(botAI, bot)) + if (IsInstanceTimerManager(botAI, bot)) MarkTargetWithSkull(bot, waterElemental); SetRtiTarget(botAI, "skull", waterElemental); if (bot->GetTarget() != waterElemental->GetGUID()) - { - bot->SetTarget(waterElemental->GetGUID()); return Attack(waterElemental); - } } else if (Unit* natureElemental = GetFirstAliveUnitByEntry(botAI, NPC_TAINTED_SPAWN_OF_HYDROSS)) { - if (IsMapIDTimerManager(botAI, bot)) + if (IsInstanceTimerManager(botAI, bot)) MarkTargetWithSkull(bot, natureElemental); SetRtiTarget(botAI, "skull", natureElemental); if (bot->GetTarget() != natureElemental->GetGUID()) - { - bot->SetTarget(natureElemental->GetGUID()); return Attack(natureElemental); - } } return false; @@ -384,6 +384,7 @@ bool HydrossTheUnstableStopDpsUponPhaseChangeAction::Execute(Event event) if (!hydross) return false; + const uint32 instanceId = hydross->GetMap()->GetInstanceId(); const time_t now = std::time(nullptr); const int phaseStartStopSeconds = 5; const int phaseEndStopSeconds = 1; @@ -391,25 +392,25 @@ bool HydrossTheUnstableStopDpsUponPhaseChangeAction::Execute(Event event) bool shouldStopDps = false; // 1 second after 100% Mark of Hydross, stop DPS until transition into nature phase - auto itNature = hydrossChangeToNaturePhaseTimer.find(SSC_MAP_ID); + auto itNature = hydrossChangeToNaturePhaseTimer.find(instanceId); if (itNature != hydrossChangeToNaturePhaseTimer.end() && (now - itNature->second) >= phaseEndStopSeconds) shouldStopDps = true; // Keep DPS stopped for 5 seconds after transition into nature phase - auto itNatureDps = hydrossNatureDpsWaitTimer.find(SSC_MAP_ID); + auto itNatureDps = hydrossNatureDpsWaitTimer.find(instanceId); if (itNatureDps != hydrossNatureDpsWaitTimer.end() && (now - itNatureDps->second) < phaseStartStopSeconds) shouldStopDps = true; // 1 second after 100% Mark of Corruption, stop DPS until transition into frost phase - auto itFrost = hydrossChangeToFrostPhaseTimer.find(SSC_MAP_ID); + auto itFrost = hydrossChangeToFrostPhaseTimer.find(instanceId); if (itFrost != hydrossChangeToFrostPhaseTimer.end() && (now - itFrost->second) >= phaseEndStopSeconds) shouldStopDps = true; // Keep DPS stopped for 5 seconds after transition into frost phase - auto itFrostDps = hydrossFrostDpsWaitTimer.find(SSC_MAP_ID); + auto itFrostDps = hydrossFrostDpsWaitTimer.find(instanceId); if (itFrostDps != hydrossFrostDpsWaitTimer.end() && (now - itFrostDps->second) < phaseStartStopSeconds) shouldStopDps = true; @@ -430,33 +431,34 @@ bool HydrossTheUnstableManageTimersAction::Execute(Event event) if (!hydross) return false; + const uint32 instanceId = hydross->GetMap()->GetInstanceId(); const time_t now = std::time(nullptr); if (hydross->GetHealth() == hydross->GetMaxHealth()) { - hydrossFrostDpsWaitTimer.erase(SSC_MAP_ID); - hydrossNatureDpsWaitTimer.erase(SSC_MAP_ID); - hydrossChangeToFrostPhaseTimer.erase(SSC_MAP_ID); - hydrossChangeToNaturePhaseTimer.erase(SSC_MAP_ID); + hydrossFrostDpsWaitTimer.erase(instanceId); + hydrossNatureDpsWaitTimer.erase(instanceId); + hydrossChangeToFrostPhaseTimer.erase(instanceId); + hydrossChangeToNaturePhaseTimer.erase(instanceId); } if (!hydross->HasAura(SPELL_CORRUPTION)) { - hydrossFrostDpsWaitTimer.try_emplace(SSC_MAP_ID, now); - hydrossNatureDpsWaitTimer.erase(SSC_MAP_ID); - hydrossChangeToFrostPhaseTimer.erase(SSC_MAP_ID); + hydrossFrostDpsWaitTimer.try_emplace(instanceId, now); + hydrossNatureDpsWaitTimer.erase(instanceId); + hydrossChangeToFrostPhaseTimer.erase(instanceId); if (HasMarkOfHydrossAt100Percent(bot)) - hydrossChangeToNaturePhaseTimer.try_emplace(SSC_MAP_ID, now); + hydrossChangeToNaturePhaseTimer.try_emplace(instanceId, now); } else { - hydrossNatureDpsWaitTimer.try_emplace(SSC_MAP_ID, now); - hydrossFrostDpsWaitTimer.erase(SSC_MAP_ID); - hydrossChangeToNaturePhaseTimer.erase(SSC_MAP_ID); + hydrossNatureDpsWaitTimer.try_emplace(instanceId, now); + hydrossFrostDpsWaitTimer.erase(instanceId); + hydrossChangeToNaturePhaseTimer.erase(instanceId); if (HasMarkOfCorruptionAt100Percent(bot)) - hydrossChangeToFrostPhaseTimer.try_emplace(SSC_MAP_ID, now); + hydrossChangeToFrostPhaseTimer.try_emplace(instanceId, now); } return false; @@ -547,18 +549,19 @@ bool TheLurkerBelowSpreadRangedInArcAction::Execute(Event event) if (count == 0) return false; - const float minRadius = 25.0f; - const float maxRadius = 27.0f; - const float referenceOrientation = Position::NormalizeOrientation(2.262f + M_PI); + const float minRadius = 27.0f; + const float maxRadius = 29.0f; + const float mainTankFacingOrientation = 2.262f; + const float arcCenter = + Position::NormalizeOrientation(mainTankFacingOrientation + M_PI); const float arcSpan = 2.0f * M_PI / 3.0f; // 120° - float startAngle = referenceOrientation - arcSpan / 2.0f; - + const float arcStart = arcCenter - arcSpan / 2.0f; float angle; if (count == 1) - angle = referenceOrientation; + angle = arcCenter; else - angle = startAngle + (static_cast(botIndex) / (count - 1)) * arcSpan; + angle = arcStart + (static_cast(botIndex) / (count - 1)) * arcSpan; float radius = frand(minRadius, maxRadius); @@ -641,10 +644,7 @@ bool TheLurkerBelowTanksPickUpAddsAction::Execute(Event event) MarkTargetWithIcon(bot, guardian, rtiIndices[i]); SetRtiTarget(botAI, rtiNames[i], guardian); if (bot->GetVictim() != guardian) - { - bot->SetTarget(guardian->GetGUID()); return Attack(guardian); - } } } @@ -657,15 +657,16 @@ bool TheLurkerBelowManageSpoutTimerAction::Execute(Event event) if (!lurker) return false; + const uint32 instanceId = lurker->GetMap()->GetInstanceId(); const time_t now = std::time(nullptr); if (lurker->GetHealth() == lurker->GetMaxHealth()) { - lurkerSpoutTimer.erase(SSC_MAP_ID); + lurkerSpoutTimer.erase(instanceId); return false; } - auto it = lurkerSpoutTimer.find(SSC_MAP_ID); + auto it = lurkerSpoutTimer.find(instanceId); if (it != lurkerSpoutTimer.end() && it->second <= now) { lurkerSpoutTimer.erase(it); @@ -674,7 +675,7 @@ bool TheLurkerBelowManageSpoutTimerAction::Execute(Event event) const time_t spoutCastTime = 20; if (IsLurkerCastingSpout(lurker) && it == lurkerSpoutTimer.end()) - lurkerSpoutTimer.try_emplace(SSC_MAP_ID, now + spoutCastTime); + lurkerSpoutTimer.try_emplace(instanceId, now + spoutCastTime); return false; } @@ -684,10 +685,8 @@ bool TheLurkerBelowManageSpoutTimerAction::Execute(Event event) bool LeotherasTheBlindTargetSpellbindersAction::Execute(Event event) { Unit* spellbinder = GetFirstAliveUnitByEntry(botAI, NPC_GREYHEART_SPELLBINDER); - if (!spellbinder || !spellbinder->IsInCombat()) - return false; - - MarkTargetWithSkull(bot, spellbinder); + if (spellbinder && spellbinder->IsInCombat()) + MarkTargetWithSkull(bot, spellbinder); return false; } @@ -702,10 +701,7 @@ bool LeotherasTheBlindDemonFormTankAttackBossAction::Execute(Event event) SetRtiTarget(botAI, "square", leotherasDemon); if (bot->GetTarget() != leotherasDemon->GetGUID()) - { - bot->SetTarget(leotherasDemon->GetGUID()); return Attack(leotherasDemon); - } return false; } @@ -822,9 +818,6 @@ bool LeotherasTheBlindInnerDemonCheatAction::Execute(Event event) if (innerDemon) { - uint8 tab = AiFactory::GetPlayerSpecTab(bot); - Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); - if (botAI->IsRanged(bot) || botAI->IsTank(bot)) { Unit::DealDamage(bot, innerDemon, innerDemon->GetMaxHealth() / 25, nullptr, @@ -840,18 +833,18 @@ bool LeotherasTheBlindInnerDemonCheatAction::Execute(Event event) bool LeotherasTheBlindFinalPhaseAssignDpsPriorityAction::Execute(Event event) { Unit* leotherasHuman = GetLeotherasHuman(botAI); + if (!leotherasHuman) + return false; + Unit* leotherasDemon = GetPhase3LeotherasDemon(botAI); - if (!leotherasHuman || !leotherasDemon) + if (!leotherasDemon) return false; MarkTargetWithStar(bot, leotherasHuman); SetRtiTarget(botAI, "star", leotherasHuman); - if (bot->GetVictim() != leotherasHuman) - { - bot->SetTarget(leotherasHuman->GetGUID()); + if (bot->GetTarget() != leotherasHuman->GetGUID()) return Attack(leotherasHuman); - } if (botAI->IsTank(bot) && leotherasHuman->GetVictim() == bot && bot->IsWithinMeleeRange(leotherasHuman)) @@ -875,8 +868,11 @@ bool LeotherasTheBlindFinalPhaseAssignDpsPriorityAction::Execute(Event event) bool LeotherasTheBlindMisdirectBossToDemonFormTankAction::Execute(Event event) { Unit* leotherasDemon = GetActiveLeotherasDemon(botAI); + if (!leotherasDemon) + return false; + Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); - if (!leotherasDemon || !demonFormTank) + if (!demonFormTank) return false; if (botAI->CanCastSpell("misdirection", demonFormTank)) @@ -896,14 +892,15 @@ bool LeotherasTheBlindManageDpsWaitTimersAction::Execute(Event event) if (!leotheras) return false; + const uint32 instanceId = leotheras->GetMap()->GetInstanceId(); const time_t now = std::time(nullptr); // Encounter start/reset: clear all timers if (leotheras->HasAura(SPELL_LEOTHERAS_BANISHED)) { - leotherasHumanFormDpsWaitTimer.erase(SSC_MAP_ID); - leotherasDemonFormDpsWaitTimer.erase(SSC_MAP_ID); - leotherasFinalPhaseDpsWaitTimer.erase(SSC_MAP_ID); + leotherasHumanFormDpsWaitTimer.erase(instanceId); + leotherasDemonFormDpsWaitTimer.erase(instanceId); + leotherasFinalPhaseDpsWaitTimer.erase(instanceId); return false; } @@ -912,21 +909,21 @@ bool LeotherasTheBlindManageDpsWaitTimersAction::Execute(Event event) Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI); if (leotherasHuman && !leotherasPhase3Demon) { - leotherasHumanFormDpsWaitTimer.try_emplace(SSC_MAP_ID, now); - leotherasDemonFormDpsWaitTimer.erase(SSC_MAP_ID); + leotherasHumanFormDpsWaitTimer.try_emplace(instanceId, now); + leotherasDemonFormDpsWaitTimer.erase(instanceId); } // Demon Phase else if (Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI)) { - leotherasDemonFormDpsWaitTimer.try_emplace(SSC_MAP_ID, now); - leotherasHumanFormDpsWaitTimer.erase(SSC_MAP_ID); + leotherasDemonFormDpsWaitTimer.try_emplace(instanceId, now); + leotherasHumanFormDpsWaitTimer.erase(instanceId); } // Final Phase (<15% HP) else if (leotherasHuman && leotherasPhase3Demon) { - leotherasFinalPhaseDpsWaitTimer.try_emplace(SSC_MAP_ID, now); - leotherasHumanFormDpsWaitTimer.erase(SSC_MAP_ID); - leotherasDemonFormDpsWaitTimer.erase(SSC_MAP_ID); + leotherasFinalPhaseDpsWaitTimer.try_emplace(instanceId, now); + leotherasHumanFormDpsWaitTimer.erase(instanceId); + leotherasDemonFormDpsWaitTimer.erase(instanceId); } return false; @@ -952,14 +949,16 @@ bool FathomLordKarathressMainTankPositionBossAction::Execute(Event event) if (karathress->GetVictim() == bot && bot->IsWithinMeleeRange(karathress)) { const Position& position = KARATHRESS_TANK_POSITION; - float dist = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); - if (dist > 3.0f) + float distToPosition = + bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); + + if (distToPosition > 3.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); - float moveDist = std::min(5.0f, dist); - float moveX = bot->GetPositionX() + (dX / dist) * moveDist; - float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + float moveDist = std::min(5.0f, distToPosition); + float moveX = bot->GetPositionX() + (dX / distToPosition) * moveDist; + float moveY = bot->GetPositionY() + (dY / distToPosition) * moveDist; return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, true); @@ -986,14 +985,16 @@ bool FathomLordKarathressFirstAssistTankPositionCaribdisAction::Execute(Event ev if (caribdis->GetVictim() == bot) { const Position& position = CARIBDIS_TANK_POSITION; - float dist = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); - if (dist > 3.0f) + float distToPosition = + bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); + + if (distToPosition > 3.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); - float moveDist = std::min(10.0f, dist); - float moveX = bot->GetPositionX() + (dX / dist) * moveDist; - float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + float moveDist = std::min(10.0f, distToPosition); + float moveX = bot->GetPositionX() + (dX / distToPosition) * moveDist; + float moveY = bot->GetPositionY() + (dY / distToPosition) * moveDist; return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, false); @@ -1019,17 +1020,19 @@ bool FathomLordKarathressSecondAssistTankPositionSharkkisAction::Execute(Event e if (sharkkis->GetVictim() == bot && bot->IsWithinMeleeRange(sharkkis)) { const Position& position = SHARKKIS_TANK_POSITION; - float dist = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); - if (dist > 3.0f) + float distToPosition = + bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); + + if (distToPosition > 3.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); - float moveDist = std::min(10.0f, dist); - float moveX = bot->GetPositionX() + (dX / dist) * moveDist; - float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + float moveDist = std::min(10.0f, distToPosition); + float moveX = bot->GetPositionX() + (dX / distToPosition) * moveDist; + float moveY = bot->GetPositionY() + (dY / distToPosition) * moveDist; - return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_COMBAT, true, true); + return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, + false, true, MovementPriority::MOVEMENT_COMBAT, true, true); } } @@ -1052,17 +1055,19 @@ bool FathomLordKarathressThirdAssistTankPositionTidalvessAction::Execute(Event e if (tidalvess->GetVictim() == bot && bot->IsWithinMeleeRange(tidalvess)) { const Position& position = TIDALVESS_TANK_POSITION; - float dist = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); - if (dist > 3.0f) + float distToPosition = + bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); + + if (distToPosition > 3.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); - float moveDist = std::min(10.0f, dist); - float moveX = bot->GetPositionX() + (dX / dist) * moveDist; - float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + float moveDist = std::min(10.0f, distToPosition); + float moveX = bot->GetPositionX() + (dX / distToPosition) * moveDist; + float moveY = bot->GetPositionY() + (dY / distToPosition) * moveDist; - return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_COMBAT, true, true); + return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, + false, true, MovementPriority::MOVEMENT_COMBAT, true, true); } } @@ -1078,17 +1083,19 @@ bool FathomLordKarathressPositionCaribdisTankHealerAction::Execute(Event event) return false; const Position& position = CARIBDIS_HEALER_POSITION; - float dist = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); - if (dist > 2.0f) + float distToPosition = + bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); + + if (distToPosition > 2.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); - float moveDist = std::min(10.0f, dist); - float moveX = bot->GetPositionX() + (dX / dist) * moveDist; - float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + float moveDist = std::min(10.0f, distToPosition); + float moveX = bot->GetPositionX() + (dX / distToPosition) * moveDist; + float moveY = bot->GetPositionY() + (dY / distToPosition) * moveDist; - return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_COMBAT, true, false); + return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, + false, true, MovementPriority::MOVEMENT_COMBAT, true, false); } return false; @@ -1192,10 +1199,7 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) SetRtiTarget(botAI, "skull", totem); if (bot->GetTarget() != totem->GetGUID()) - { - bot->SetTarget(totem->GetGUID()); return Attack(totem); - } return false; } @@ -1208,10 +1212,7 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) SetRtiTarget(botAI, "circle", tidalvess); if (bot->GetTarget() != tidalvess->GetGUID()) - { - bot->SetTarget(tidalvess->GetGUID()); return Attack(tidalvess); - } return false; } @@ -1224,18 +1225,14 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) SetRtiTarget(botAI, "diamond", caribdis); const Position& position = CARIBDIS_RANGED_DPS_POSITION; - float dist = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); - if (dist > 2.0f) + if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 2.0f) { return MoveInside(SSC_MAP_ID, position.GetPositionX(), position.GetPositionY(), position.GetPositionZ(), 8.0f, MovementPriority::MOVEMENT_COMBAT); } if (bot->GetTarget() != caribdis->GetGUID()) - { - bot->SetTarget(caribdis->GetGUID()); return Attack(caribdis); - } return false; } @@ -1248,10 +1245,7 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) SetRtiTarget(botAI, "star", sharkkis); if (bot->GetTarget() != sharkkis->GetGUID()) - { - bot->SetTarget(sharkkis->GetGUID()); return Attack(sharkkis); - } return false; } @@ -1264,10 +1258,7 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) SetRtiTarget(botAI, "cross", fathomSporebat); if (bot->GetTarget() != fathomSporebat->GetGUID()) - { - bot->SetTarget(fathomSporebat->GetGUID()); return Attack(fathomSporebat); - } return false; } @@ -1279,10 +1270,7 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) SetRtiTarget(botAI, "square", fathomLurker); if (bot->GetTarget() != fathomLurker->GetGUID()) - { - bot->SetTarget(fathomLurker->GetGUID()); return Attack(fathomLurker); - } return false; } @@ -1295,10 +1283,7 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) SetRtiTarget(botAI, "triangle", karathress); if (bot->GetTarget() != karathress->GetGUID()) - { - bot->SetTarget(karathress->GetGUID()); return Attack(karathress); - } } return false; @@ -1313,7 +1298,7 @@ bool FathomLordKarathressManageDpsTimerAction::Execute(Event event) const time_t now = std::time(nullptr); if (karathress->GetHealth() == karathress->GetMaxHealth()) - karathressDpsWaitTimer.insert_or_assign(SSC_MAP_ID, now); + karathressDpsWaitTimer.insert_or_assign(karathress->GetMap()->GetInstanceId(), now); return false; } @@ -1376,17 +1361,17 @@ bool MorogrimTidewalkerMoveBossToTankPositionAction::Execute(Event event) bool MorogrimTidewalkerMoveBossToTankPositionAction::MoveToPhase1TankPosition(Unit* tidewalker) { const Position& phase1 = TIDEWALKER_PHASE_1_TANK_POSITION; - float dist = bot->GetExactDist2d(phase1.GetPositionX(), phase1.GetPositionY()); - if (dist > 1.0f) + float distToPhase1 = bot->GetExactDist2d(phase1.GetPositionX(), phase1.GetPositionY()); + if (distToPhase1 > 1.0f) { float dX = phase1.GetPositionX() - bot->GetPositionX(); float dY = phase1.GetPositionY() - bot->GetPositionY(); - float moveDist = std::min(5.0f, dist); - float moveX = bot->GetPositionX() + (dX / dist) * moveDist; - float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + float moveDist = std::min(5.0f, distToPhase1); + float moveX = bot->GetPositionX() + (dX / distToPhase1) * moveDist; + float moveY = bot->GetPositionY() + (dY / distToPhase1) * moveDist; - return MoveTo(SSC_MAP_ID, moveX, moveY, phase1.GetPositionZ(), false, false, false, false, - MovementPriority::MOVEMENT_COMBAT, true, true); + return MoveTo(SSC_MAP_ID, moveX, moveY, phase1.GetPositionZ(), false, false, + false, false, MovementPriority::MOVEMENT_COMBAT, true, true); } return false; @@ -1404,14 +1389,16 @@ bool MorogrimTidewalkerMoveBossToTankPositionAction::MoveToPhase2TankPosition(Un if (step == 0) { - float dist = bot->GetExactDist2d(transition.GetPositionX(), transition.GetPositionY()); - if (dist > 2.0f) + float distToTransition = + bot->GetExactDist2d(transition.GetPositionX(), transition.GetPositionY()); + + if (distToTransition > 2.0f) { float dX = transition.GetPositionX() - bot->GetPositionX(); float dY = transition.GetPositionY() - bot->GetPositionY(); - float moveDist = std::min(5.0f, dist); - float moveX = bot->GetPositionX() + (dX / dist) * moveDist; - float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + float moveDist = std::min(5.0f, distToTransition); + float moveX = bot->GetPositionX() + (dX / distToTransition) * moveDist; + float moveY = bot->GetPositionY() + (dY / distToTransition) * moveDist; return MoveTo(SSC_MAP_ID, moveX, moveY, transition.GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, true); @@ -1422,14 +1409,16 @@ bool MorogrimTidewalkerMoveBossToTankPositionAction::MoveToPhase2TankPosition(Un if (step == 1) { - float dist = bot->GetExactDist2d(phase2.GetPositionX(), phase2.GetPositionY()); - if (dist > 1.0f) + float distToPhase2 = + bot->GetExactDist2d(phase2.GetPositionX(), phase2.GetPositionY()); + + if (distToPhase2 > 1.0f) { float dX = phase2.GetPositionX() - bot->GetPositionX(); float dY = phase2.GetPositionY() - bot->GetPositionY(); - float moveDist = std::min(5.0f, dist); - float moveX = bot->GetPositionX() + (dX / dist) * moveDist; - float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + float moveDist = std::min(5.0f, distToPhase2); + float moveX = bot->GetPositionX() + (dX / distToPhase2) * moveDist; + float moveY = bot->GetPositionY() + (dY / distToPhase2) * moveDist; return MoveTo(SSC_MAP_ID, moveX, moveY, phase2.GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, true); @@ -1455,14 +1444,16 @@ bool MorogrimTidewalkerPhase2RepositionRangedAction::Execute(Event event) if (step == 0) { - float dist = bot->GetExactDist2d(transition.GetPositionX(), transition.GetPositionY()); - if (dist > 2.0f) + float distToTransition = + bot->GetExactDist2d(transition.GetPositionX(), transition.GetPositionY()); + + if (distToTransition > 2.0f) { float dX = transition.GetPositionX() - bot->GetPositionX(); float dY = transition.GetPositionY() - bot->GetPositionY(); - float moveDist = std::min(10.0f, dist); - float moveX = bot->GetPositionX() + (dX / dist) * moveDist; - float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + float moveDist = std::min(10.0f, distToTransition); + float moveX = bot->GetPositionX() + (dX / distToTransition) * moveDist; + float moveY = bot->GetPositionY() + (dY / distToTransition) * moveDist; return MoveTo(SSC_MAP_ID, moveX, moveY, transition.GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); @@ -1476,14 +1467,16 @@ bool MorogrimTidewalkerPhase2RepositionRangedAction::Execute(Event event) if (step == 1) { - float dist = bot->GetExactDist2d(phase2.GetPositionX(), phase2.GetPositionY()); - if (dist > 1.0f) + float distToPhase2 = + bot->GetExactDist2d(phase2.GetPositionX(), phase2.GetPositionY()); + + if (distToPhase2 > 1.0f) { float dX = phase2.GetPositionX() - bot->GetPositionX(); float dY = phase2.GetPositionY() - bot->GetPositionY(); - float moveDist = std::min(10.0f, dist); - float moveX = bot->GetPositionX() + (dX / dist) * moveDist; - float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + float moveDist = std::min(10.0f, distToPhase2); + float moveX = bot->GetPositionX() + (dX / distToPhase2) * moveDist; + float moveY = bot->GetPositionY() + (dY / distToPhase2) * moveDist; return MoveTo(SSC_MAP_ID, moveX, moveY, phase2.GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); @@ -1527,17 +1520,19 @@ bool LadyVashjMainTankPositionBossAction::Execute(Event event) if (IsLadyVashjInPhase1(botAI)) { const Position& position = VASHJ_PLATFORM_CENTER_POSITION; - float dist = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); - if (dist > 2.0f) + float distToPosition = + bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); + + if (distToPosition > 2.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); - float moveDist = std::min(5.0f, dist); - float moveX = bot->GetPositionX() + (dX / dist) * moveDist; - float moveY = bot->GetPositionY() + (dY / dist) * moveDist; + float moveDist = std::min(5.0f, distToPosition); + float moveX = bot->GetPositionX() + (dX / distToPosition) * moveDist; + float moveY = bot->GetPositionY() + (dY / distToPosition) * moveDist; - return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false, - MovementPriority::MOVEMENT_COMBAT, true, true); + return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, + false, false, MovementPriority::MOVEMENT_COMBAT, true, true); } } // Phase 3: Move Vashj away from Enchanted Elementals @@ -1590,15 +1585,15 @@ bool LadyVashjPhase1SpreadRangedInArcAction::Execute(Event event) const float minRadius = 20.0f; const float maxRadius = 30.0f; - const float referenceAngle = M_PI / 2.0f; // North + const float arcCenter = M_PI / 2.0f; // North const float arcSpan = M_PI; // 180° - const float startAngle = referenceAngle - arcSpan / 2.0f; + const float arcStart = arcCenter - arcSpan / 2.0f; float angle; if (count == 1) - angle = referenceAngle; + angle = arcCenter; else - angle = startAngle + (static_cast(botIndex) / (count - 1)) * arcSpan; + angle = arcStart + (static_cast(botIndex) / (count - 1)) * arcSpan; float radius = frand(minRadius, maxRadius); float targetX = center.GetPositionX() + radius * std::cos(angle); @@ -2016,7 +2011,6 @@ bool LadyVashjAssignDpsPriorityAction::Execute(Event event) if (target && bot->GetExactDist2d(target) <= maxPursueRange && bot->GetTarget() != target->GetGUID()) { - bot->SetTarget(target->GetGUID()); return Attack(target); } @@ -2045,7 +2039,6 @@ bool LadyVashjAssignDpsPriorityAction::Execute(Event event) { bot->AttackStop(); bot->InterruptNonMeleeSpells(true); - return MoveInside(SSC_MAP_ID, center.GetPositionX(), center.GetPositionY(), center.GetPositionZ(), 30.0f, MovementPriority::MOVEMENT_COMBAT); } @@ -2088,6 +2081,10 @@ bool LadyVashjTeleportToTaintedElementalAction::Execute(Event event) bool LadyVashjLootTaintedCoreAction::Execute(Event) { + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + if (!vashj) + return false; + auto const& corpses = context->GetValue("nearest corpses")->Get(); const float maxLootRange = sPlayerbotAIConfig->lootDistance; @@ -2131,9 +2128,9 @@ bool LadyVashjLootTaintedCoreAction::Execute(Event) const ObjectGuid corpseGuid = guid; const uint8 coreIndex = 0; - botAI->AddTimedEvent([botGuid, corpseGuid, coreIndex]() + botAI->AddTimedEvent([botGuid, corpseGuid, coreIndex, vashj]() { - Player* receiver = botGuid.IsEmpty() ? nullptr : ObjectAccessor::FindPlayer(botGuid); + Player* receiver = botGuid.IsEmpty() ? nullptr : ObjectAccessor::FindPlayer(botGuid); if (!receiver) return; @@ -2153,7 +2150,7 @@ bool LadyVashjLootTaintedCoreAction::Execute(Event) *packet << coreIndex; receiver->GetSession()->QueuePacket(packet); - lastCoreInInventoryTime[SSC_MAP_ID] = std::time(nullptr); + lastCoreInInventoryTime[vashj->GetMap()->GetInstanceId()] = std::time(nullptr); }, 600); return true; @@ -2164,6 +2161,10 @@ bool LadyVashjLootTaintedCoreAction::Execute(Event) bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) { + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + if (!vashj) + return false; + Group* group = bot->GetGroup(); if (!group) return false; @@ -2176,6 +2177,7 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI); Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI); Player* fourthCorePasser = GetFourthTaintedCorePasser(group, botAI); + const uint32 instanceId = vashj->GetMap()->GetInstanceId(); Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental"); Unit* closestTrigger = nullptr; @@ -2183,10 +2185,10 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) { closestTrigger = GetNearestActiveShieldGeneratorTriggerByEntry(tainted); if (closestTrigger) - nearestTriggerGuid.insert_or_assign(SSC_MAP_ID, closestTrigger->GetGUID()); + nearestTriggerGuid.insert_or_assign(instanceId, closestTrigger->GetGUID()); } - auto itSnap = nearestTriggerGuid.find(SSC_MAP_ID); + auto itSnap = nearestTriggerGuid.find(instanceId); if (itSnap != nearestTriggerGuid.end() && !itSnap->second.IsEmpty()) { Unit* snapUnit = botAI->GetUnit(itSnap->second); @@ -2246,11 +2248,11 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) // Track lastImbueAttempt is to prevent repeated throwing animations // from multiple imbue attempts - auto [it, inserted] = lastImbueAttempt.try_emplace(SSC_MAP_ID, now); + auto [it, inserted] = lastImbueAttempt.try_emplace(instanceId, now); if (inserted) { botAI->ImbueItem(item, firstCorePasser); - lastCoreInInventoryTime[SSC_MAP_ID] = now; + lastCoreInInventoryTime[instanceId] = now; ScheduleStoreCoreAfterImbue(botAI, bot, firstCorePasser); return true; } @@ -2258,7 +2260,7 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) { it->second = now; botAI->ImbueItem(item, firstCorePasser); - lastCoreInInventoryTime[SSC_MAP_ID] = now; + lastCoreInInventoryTime[instanceId] = now; ScheduleStoreCoreAfterImbue(botAI, bot, firstCorePasser); return true; } @@ -2273,11 +2275,11 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) { const time_t now = std::time(nullptr); - auto [it, inserted] = lastImbueAttempt.try_emplace(SSC_MAP_ID, now); + auto [it, inserted] = lastImbueAttempt.try_emplace(instanceId, now); if (inserted) { botAI->ImbueItem(item, secondCorePasser); - lastCoreInInventoryTime[SSC_MAP_ID] = now; + lastCoreInInventoryTime[instanceId] = now; intendedLineup.erase(bot->GetGUID()); ScheduleStoreCoreAfterImbue(botAI, bot, secondCorePasser); return true; @@ -2286,7 +2288,7 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) { it->second = now; botAI->ImbueItem(item, secondCorePasser); - lastCoreInInventoryTime[SSC_MAP_ID] = now; + lastCoreInInventoryTime[instanceId] = now; intendedLineup.erase(bot->GetGUID()); ScheduleStoreCoreAfterImbue(botAI, bot, secondCorePasser); return true; @@ -2305,11 +2307,11 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) { const time_t now = std::time(nullptr); - auto [it, inserted] = lastImbueAttempt.try_emplace(SSC_MAP_ID, now); + auto [it, inserted] = lastImbueAttempt.try_emplace(instanceId, now); if (inserted) { botAI->ImbueItem(item, thirdCorePasser); - lastCoreInInventoryTime[SSC_MAP_ID] = now; + lastCoreInInventoryTime[instanceId] = now; intendedLineup.erase(bot->GetGUID()); ScheduleStoreCoreAfterImbue(botAI, bot, thirdCorePasser); return true; @@ -2318,7 +2320,7 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) { it->second = now; botAI->ImbueItem(item, thirdCorePasser); - lastCoreInInventoryTime[SSC_MAP_ID] = now; + lastCoreInInventoryTime[instanceId] = now; intendedLineup.erase(bot->GetGUID()); ScheduleStoreCoreAfterImbue(botAI, bot, thirdCorePasser); return true; @@ -2338,11 +2340,11 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) { const time_t now = std::time(nullptr); - auto [it, inserted] = lastImbueAttempt.try_emplace(SSC_MAP_ID, now); + auto [it, inserted] = lastImbueAttempt.try_emplace(instanceId, now); if (inserted) { botAI->ImbueItem(item, fourthCorePasser); - lastCoreInInventoryTime[SSC_MAP_ID] = now; + lastCoreInInventoryTime[instanceId] = now; intendedLineup.erase(bot->GetGUID()); ScheduleStoreCoreAfterImbue(botAI, bot, fourthCorePasser); return true; @@ -2351,7 +2353,7 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) { it->second = now; botAI->ImbueItem(item, fourthCorePasser); - lastCoreInInventoryTime[SSC_MAP_ID] = now; + lastCoreInInventoryTime[instanceId] = now; intendedLineup.erase(bot->GetGUID()); ScheduleStoreCoreAfterImbue(botAI, bot, fourthCorePasser); return true; diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h index fd5ab3af5c..78763b5e6a 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h @@ -5,6 +5,8 @@ #include "AttackAction.h" #include "MovementActions.h" +// Trash + class UnderbogColossusEscapeToxicPoolAction : public MovementAction { public: @@ -21,6 +23,8 @@ class GreyheartTidecallerMarkWaterElementalTotemAction : public Action bool Execute(Event event) override; }; +// Hydross the Unstable + class HydrossTheUnstablePositionFrostTankAction : public AttackAction { public: @@ -81,6 +85,8 @@ class HydrossTheUnstableManageTimersAction : public Action bool Execute(Event event) override; }; +// The Lurker Below + class TheLurkerBelowRunAroundBehindBossAction : public MovementAction { public: @@ -121,6 +127,8 @@ class TheLurkerBelowManageSpoutTimerAction : public Action bool Execute(Event event) override; }; +// Leotheras the Blind + class LeotherasTheBlindTargetSpellbindersAction : public Action { public: @@ -193,6 +201,8 @@ class LeotherasTheBlindManageDpsWaitTimersAction : public Action bool Execute(Event event) override; }; +// Fathom-Lord Karathress + class FathomLordKarathressMainTankPositionBossAction : public AttackAction { public: @@ -257,6 +267,8 @@ class FathomLordKarathressManageDpsTimerAction : public Action bool Execute(Event event) override; }; +// Morogrim Tidewalker + class MorogrimTidewalkerMisdirectBossToMainTankAction : public AttackAction { public: @@ -293,6 +305,8 @@ class MorogrimTidewalkerResetPhaseTransitionStepsAction : public Action bool Execute(Event event) override; }; +// Lady Vashj + class LadyVashjMainTankPositionBossAction : public AttackAction { public: diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp index 1d14523c32..86cb33c88f 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp @@ -7,48 +7,7 @@ namespace SerpentShrineCavernHelpers { - std::unordered_map hydrossFrostDpsWaitTimer; - std::unordered_map hydrossNatureDpsWaitTimer; - std::unordered_map hydrossChangeToFrostPhaseTimer; - std::unordered_map hydrossChangeToNaturePhaseTimer; - - std::unordered_map lurkerSpoutTimer; - std::unordered_map lurkerRangedPositions; - - std::unordered_map leotherasHumanFormDpsWaitTimer; - std::unordered_map leotherasDemonFormDpsWaitTimer; - std::unordered_map leotherasFinalPhaseDpsWaitTimer; - - std::unordered_map karathressDpsWaitTimer; - - std::unordered_map tidewalkerTankStep; - std::unordered_map tidewalkerRangedStep; - - std::unordered_map vashjRangedPositions; - std::unordered_map hasReachedVashjRangedPosition; - std::unordered_map nearestTriggerGuid; - std::unordered_map intendedLineup; - std::unordered_map lastImbueAttempt; - std::unordered_map lastCoreInInventoryTime; - - const Position HYDROSS_FROST_TANK_POSITION = { -236.669f, -358.352f, -0.828f }; - const Position HYDROSS_NATURE_TANK_POSITION = { -225.471f, -327.790f, -3.682f }; - - const Position LURKER_MAIN_TANK_POSITION = { 23.706f, -406.038f, -19.686f }; - - const Position KARATHRESS_TANK_POSITION = { 474.403f, -531.118f, -7.548f }; - const Position TIDALVESS_TANK_POSITION = { 511.282f, -501.162f, -13.158f }; - const Position SHARKKIS_TANK_POSITION = { 508.057f, -541.109f, -10.133f }; - const Position CARIBDIS_TANK_POSITION = { 464.462f, -475.820f, -13.158f }; - const Position CARIBDIS_HEALER_POSITION = { 466.203f, -503.201f, -13.158f }; - const Position CARIBDIS_RANGED_DPS_POSITION = { 463.197f, -501.190f, -13.158f }; - - const Position TIDEWALKER_PHASE_1_TANK_POSITION = { 410.925f, -741.916f, -7.146f }; - const Position TIDEWALKER_PHASE_TRANSITION_WAYPOINT = { 407.035f, -759.479f, -7.168f }; - const Position TIDEWALKER_PHASE_2_TANK_POSITION = { 446.571f, -767.155f, -7.144f }; - const Position TIDEWALKER_PHASE_2_RANGED_POSITION = { 432.595f, -766.288f, -7.145f }; - - const Position VASHJ_PLATFORM_CENTER_POSITION = { 29.634f, -923.541f, 42.985f }; + // General Helpers void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId) { @@ -119,7 +78,7 @@ namespace SerpentShrineCavernHelpers } // Dps bot selected for marking and managing timers and trackers - bool IsMapIDTimerManager(PlayerbotAI* botAI, Player* bot) + bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot) { if (Group* group = bot->GetGroup()) { @@ -149,6 +108,16 @@ namespace SerpentShrineCavernHelpers return nullptr; } + // Hydross the Unstable + + const Position HYDROSS_FROST_TANK_POSITION = { -236.669f, -358.352f, -0.828f }; + const Position HYDROSS_NATURE_TANK_POSITION = { -225.471f, -327.790f, -3.682f }; + + std::unordered_map hydrossFrostDpsWaitTimer; + std::unordered_map hydrossNatureDpsWaitTimer; + std::unordered_map hydrossChangeToFrostPhaseTimer; + std::unordered_map hydrossChangeToNaturePhaseTimer; + bool HasMarkOfHydrossAt100Percent(Player* bot) { return bot->HasAura(SPELL_MARK_OF_HYDROSS_100) || @@ -184,6 +153,13 @@ namespace SerpentShrineCavernHelpers !bot->HasAura(SPELL_MARK_OF_CORRUPTION_500); } + // The Lurker Below + + const Position LURKER_MAIN_TANK_POSITION = { 23.706f, -406.038f, -19.686f }; + + std::unordered_map lurkerSpoutTimer; + std::unordered_map lurkerRangedPositions; + bool IsLurkerCastingSpout(Unit* lurker) { if (!lurker || !lurker->HasUnitState(UNIT_STATE_CASTING)) @@ -199,6 +175,12 @@ namespace SerpentShrineCavernHelpers return isSpout; } + // Leotheras the Blind + + std::unordered_map leotherasHumanFormDpsWaitTimer; + std::unordered_map leotherasDemonFormDpsWaitTimer; + std::unordered_map leotherasFinalPhaseDpsWaitTimer; + Unit* GetLeotherasHuman(PlayerbotAI* botAI) { auto const& npcs = @@ -276,6 +258,38 @@ namespace SerpentShrineCavernHelpers return fallbackWarlockTank; } + // Fathom-Lord Karathress + + const Position KARATHRESS_TANK_POSITION = { 474.403f, -531.118f, -7.548f }; + const Position TIDALVESS_TANK_POSITION = { 511.282f, -501.162f, -13.158f }; + const Position SHARKKIS_TANK_POSITION = { 508.057f, -541.109f, -10.133f }; + const Position CARIBDIS_TANK_POSITION = { 464.462f, -475.820f, -13.158f }; + const Position CARIBDIS_HEALER_POSITION = { 466.203f, -503.201f, -13.158f }; + const Position CARIBDIS_RANGED_DPS_POSITION = { 463.197f, -501.190f, -13.158f }; + + std::unordered_map karathressDpsWaitTimer; + + // Morogrim Tidewalker + + const Position TIDEWALKER_PHASE_1_TANK_POSITION = { 410.925f, -741.916f, -7.146f }; + const Position TIDEWALKER_PHASE_TRANSITION_WAYPOINT = { 407.035f, -759.479f, -7.168f }; + const Position TIDEWALKER_PHASE_2_TANK_POSITION = { 446.571f, -767.155f, -7.144f }; + const Position TIDEWALKER_PHASE_2_RANGED_POSITION = { 432.595f, -766.288f, -7.145f }; + + std::unordered_map tidewalkerTankStep; + std::unordered_map tidewalkerRangedStep; + + // Lady Vashj + + const Position VASHJ_PLATFORM_CENTER_POSITION = { 29.634f, -923.541f, 42.985f }; + + std::unordered_map vashjRangedPositions; + std::unordered_map hasReachedVashjRangedPosition; + std::unordered_map nearestTriggerGuid; + std::unordered_map intendedLineup; + std::unordered_map lastImbueAttempt; + std::unordered_map lastCoreInInventoryTime; + bool IsMainTankInSameSubgroup(Player* bot) { Group* group = bot->GetGroup(); @@ -362,8 +376,13 @@ namespace SerpentShrineCavernHelpers return false; } - bool AnyRecentCoreInInventory(Group* group, uint32 graceSeconds) + bool AnyRecentCoreInInventory(Group* group, PlayerbotAI* botAI, uint32 graceSeconds) { + Unit* vashj = + botAI->GetAiObjectContext()->GetValue("find target", "lady vashj")->Get(); + if (!vashj) + return false; + if (group) { for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) @@ -374,9 +393,10 @@ namespace SerpentShrineCavernHelpers } } + const uint32 instanceId = vashj->GetMap()->GetInstanceId(); const time_t now = std::time(nullptr); - auto it = lastCoreInInventoryTime.find(SSC_MAP_ID); + auto it = lastCoreInInventoryTime.find(instanceId); if (it != lastCoreInInventoryTime.end()) { if ((now - it->second) <= static_cast(graceSeconds)) diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h index 92986eb999..e1e67cc869 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h @@ -105,95 +105,86 @@ namespace SerpentShrineCavernHelpers ITEM_HEAVY_NETHERWEAVE_NET = 24269, }; + // General const uint32 SSC_MAP_ID = 548; + void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId); + void MarkTargetWithSkull(Player* bot, Unit* target); + void MarkTargetWithSquare(Player* bot, Unit* target); + void MarkTargetWithStar(Player* bot, Unit* target); + void MarkTargetWithCircle(Player* bot, Unit* target); + void MarkTargetWithDiamond(Player* bot, Unit* target); + void MarkTargetWithTriangle(Player* bot, Unit* target); + void MarkTargetWithCross(Player* bot, Unit* target); + void MarkTargetWithMoon(Player* bot, Unit* target); + void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target); + bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot); + Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry); + // Hydross the Unstable + extern const Position HYDROSS_FROST_TANK_POSITION; + extern const Position HYDROSS_NATURE_TANK_POSITION; extern std::unordered_map hydrossFrostDpsWaitTimer; extern std::unordered_map hydrossNatureDpsWaitTimer; extern std::unordered_map hydrossChangeToFrostPhaseTimer; extern std::unordered_map hydrossChangeToNaturePhaseTimer; + bool HasMarkOfHydrossAt100Percent(Player* bot); + bool HasNoMarkOfHydross(Player* bot); + bool HasMarkOfCorruptionAt100Percent(Player* bot); + bool HasNoMarkOfCorruption(Player* bot); + // The Lurker Below + extern const Position LURKER_MAIN_TANK_POSITION; extern std::unordered_map lurkerSpoutTimer; extern std::unordered_map lurkerRangedPositions; + bool IsLurkerCastingSpout(Unit* lurker); + // Leotheras the Blind extern std::unordered_map leotherasHumanFormDpsWaitTimer; extern std::unordered_map leotherasDemonFormDpsWaitTimer; extern std::unordered_map leotherasFinalPhaseDpsWaitTimer; + Unit* GetLeotherasHuman(PlayerbotAI* botAI); + Unit* GetPhase2LeotherasDemon(PlayerbotAI* botAI); + Unit* GetPhase3LeotherasDemon(PlayerbotAI* botAI); + Unit* GetActiveLeotherasDemon(PlayerbotAI* botAI); + Player* GetLeotherasDemonFormTank(PlayerbotAI* botAI, Player* bot); - extern std::unordered_map karathressDpsWaitTimer; - - extern std::unordered_map tidewalkerTankStep; - extern std::unordered_map tidewalkerRangedStep; - - extern std::unordered_map vashjRangedPositions; - extern std::unordered_map hasReachedVashjRangedPosition; - extern std::unordered_map nearestTriggerGuid; - extern std::unordered_map intendedLineup; - extern std::unordered_map lastImbueAttempt; - extern std::unordered_map lastCoreInInventoryTime; - - extern const Position HYDROSS_FROST_TANK_POSITION; - extern const Position HYDROSS_NATURE_TANK_POSITION; - - extern const Position LURKER_MAIN_TANK_POSITION; - + // Fathom-Lord Karathress extern const Position KARATHRESS_TANK_POSITION; extern const Position TIDALVESS_TANK_POSITION; extern const Position SHARKKIS_TANK_POSITION; extern const Position CARIBDIS_TANK_POSITION; extern const Position CARIBDIS_HEALER_POSITION; extern const Position CARIBDIS_RANGED_DPS_POSITION; + extern std::unordered_map karathressDpsWaitTimer; + // Morogrim Tidewalker extern const Position TIDEWALKER_PHASE_1_TANK_POSITION; extern const Position TIDEWALKER_PHASE_TRANSITION_WAYPOINT; extern const Position TIDEWALKER_PHASE_2_TANK_POSITION; extern const Position TIDEWALKER_PHASE_2_RANGED_POSITION; + extern std::unordered_map tidewalkerTankStep; + extern std::unordered_map tidewalkerRangedStep; + // Lady Vashj extern const Position VASHJ_PLATFORM_CENTER_POSITION; - - void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId); - void MarkTargetWithSkull(Player* bot, Unit* target); - void MarkTargetWithSquare(Player* bot, Unit* target); - void MarkTargetWithStar(Player* bot, Unit* target); - void MarkTargetWithCircle(Player* bot, Unit* target); - void MarkTargetWithDiamond(Player* bot, Unit* target); - void MarkTargetWithTriangle(Player* bot, Unit* target); - void MarkTargetWithCross(Player* bot, Unit* target); - void MarkTargetWithMoon(Player* bot, Unit* target); - void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target); - bool IsMapIDTimerManager(PlayerbotAI* botAI, Player* bot); - Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry); - - bool HasMarkOfHydrossAt100Percent(Player* bot); - bool HasNoMarkOfHydross(Player* bot); - bool HasMarkOfCorruptionAt100Percent(Player* bot); - bool HasNoMarkOfCorruption(Player* bot); - - bool IsLurkerCastingSpout(Unit* lurker); - - Unit* GetLeotherasHuman(PlayerbotAI* botAI); - Unit* GetPhase2LeotherasDemon(PlayerbotAI* botAI); - Unit* GetPhase3LeotherasDemon(PlayerbotAI* botAI); - Unit* GetActiveLeotherasDemon(PlayerbotAI* botAI); - Player* GetLeotherasDemonFormTank(PlayerbotAI* botAI, Player* bot); - + extern std::unordered_map vashjRangedPositions; + extern std::unordered_map hasReachedVashjRangedPosition; + extern std::unordered_map nearestTriggerGuid; + extern std::unordered_map intendedLineup; + extern std::unordered_map lastImbueAttempt; + extern std::unordered_map lastCoreInInventoryTime; bool IsMainTankInSameSubgroup(Player* bot); bool IsLadyVashjInPhase1(PlayerbotAI* botAI); bool IsLadyVashjInPhase2(PlayerbotAI* botAI); bool IsLadyVashjInPhase3(PlayerbotAI* botAI); bool IsValidLadyVashjCombatNpc(Unit* unit, PlayerbotAI* botAI); - bool AnyRecentCoreInInventory(Group* group, uint32 graceSeconds = 3); + bool AnyRecentCoreInInventory(Group* group, PlayerbotAI* botAI, uint32 graceSeconds = 3); Player* GetDesignatedCoreLooter(Group* group, PlayerbotAI* botAI); Player* GetFirstTaintedCorePasser(Group* group, PlayerbotAI* botAI); Player* GetSecondTaintedCorePasser(Group* group, PlayerbotAI* botAI); Player* GetThirdTaintedCorePasser(Group* group, PlayerbotAI* botAI); Player* GetFourthTaintedCorePasser(Group* group, PlayerbotAI* botAI); - - struct GeneratorInfo - { - ObjectGuid guid; - float x, y, z; - }; - + struct GeneratorInfo { ObjectGuid guid; float x, y, z; }; extern const std::vector SHIELD_GENERATOR_DB_GUIDS; std::vector GetAllGeneratorInfosByDbGuids( Map* map, const std::vector& generatorDbGuids); diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp index e506fa3882..9dfe69210f 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp @@ -89,14 +89,15 @@ float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action) if (dynamic_cast(action)) return 1.0f; + const uint32 instanceId = hydross->GetMap()->GetInstanceId(); const time_t now = std::time(nullptr); const uint8 phaseChangeWaitSeconds = 1; const uint8 dpsWaitSeconds = 5; if (!hydross->HasAura(SPELL_CORRUPTION) && !botAI->IsMainTank(bot)) { - auto itDps = hydrossFrostDpsWaitTimer.find(SSC_MAP_ID); - auto itPhase = hydrossChangeToFrostPhaseTimer.find(SSC_MAP_ID); + auto itDps = hydrossFrostDpsWaitTimer.find(instanceId); + auto itPhase = hydrossChangeToFrostPhaseTimer.find(instanceId); bool justChanged = (itDps == hydrossFrostDpsWaitTimer.end() || (now - itDps->second) < dpsWaitSeconds); @@ -114,8 +115,8 @@ float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action) if (hydross->HasAura(SPELL_CORRUPTION) && !botAI->IsAssistTankOfIndex(bot, 0)) { - auto itDps = hydrossNatureDpsWaitTimer.find(SSC_MAP_ID); - auto itPhase = hydrossChangeToNaturePhaseTimer.find(SSC_MAP_ID); + auto itDps = hydrossNatureDpsWaitTimer.find(instanceId); + auto itPhase = hydrossChangeToNaturePhaseTimer.find(instanceId); bool justChanged = (itDps == hydrossNatureDpsWaitTimer.end() || (now - itDps->second) < dpsWaitSeconds); @@ -160,7 +161,7 @@ float TheLurkerBelowStayAwayFromSpoutMultiplier::GetValue(Action* action) const time_t now = std::time(nullptr); - auto it = lurkerSpoutTimer.find(SSC_MAP_ID); + auto it = lurkerSpoutTimer.find(lurker->GetMap()->GetInstanceId()); if (it != lurkerSpoutTimer.end() && it->second > now) { if (dynamic_cast(action) || @@ -177,6 +178,24 @@ float TheLurkerBelowStayAwayFromSpoutMultiplier::GetValue(Action* action) return 1.0f; } +float TheLurkerBelowMaintainRangedSpreadMultiplier::GetValue(Action* action) +{ + if (!botAI->IsRanged(bot)) + return 1.0f; + + Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); + if (lurker) + { + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + // Disable tank assist during Submerge only if there are 3 or more tanks in the raid float TheLurkerBelowDisableTankAssistMultiplier::GetValue(Action* action) { @@ -297,6 +316,7 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) if (dynamic_cast(action)) return 1.0f; + const uint32 instanceId = leotheras->GetMap()->GetInstanceId(); const time_t now = std::time(nullptr); const uint8 dpsWaitSecondsPhase1 = 5; @@ -308,7 +328,7 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) if (botAI->IsTank(bot)) return 1.0f; - auto it = leotherasHumanFormDpsWaitTimer.find(SSC_MAP_ID); + auto it = leotherasHumanFormDpsWaitTimer.find(instanceId); if (it == leotherasHumanFormDpsWaitTimer.end() || (now - it->second) < dpsWaitSecondsPhase1) { @@ -330,7 +350,7 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) if (!demonFormTank && botAI->IsTank(bot)) return 1.0f; - auto it = leotherasDemonFormDpsWaitTimer.find(SSC_MAP_ID); + auto it = leotherasDemonFormDpsWaitTimer.find(instanceId); if (it == leotherasDemonFormDpsWaitTimer.end() || (now - it->second) < dpsWaitSecondsPhase2) { @@ -347,7 +367,7 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) if ((demonFormTank && demonFormTank == bot) || botAI->IsTank(bot)) return 1.0f; - auto it = leotherasFinalPhaseDpsWaitTimer.find(SSC_MAP_ID); + auto it = leotherasFinalPhaseDpsWaitTimer.find(instanceId); if (it == leotherasFinalPhaseDpsWaitTimer.end() || (now - it->second) < dpsWaitSecondsPhase3) { @@ -442,7 +462,7 @@ float FathomLordKarathressWaitForDpsMultiplier::GetValue(Action* action) const time_t now = std::time(nullptr); const uint8 dpsWaitSeconds = 10; - auto it = karathressDpsWaitTimer.find(SSC_MAP_ID); + auto it = karathressDpsWaitTimer.find(karathress->GetMap()->GetInstanceId()); if (it == karathressDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds) { if (dynamic_cast(action) || @@ -506,15 +526,19 @@ float MorogrimTidewalkerDisableTankActionsMultiplier::GetValue(Action* action) return 1.0f; } -float MorogrimTidewalkerDisablePhase2MovementActionsMultiplier::GetValue(Action* action) +float MorogrimTidewalkerMaintainPhase2StackingMultiplier::GetValue(Action* action) { + if (!botAI->IsRanged(bot)) + return 1.0f; + Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); if (!tidewalker) return 1.0f; if (tidewalker->GetHealthPct() < 25.0f) { - if (dynamic_cast(action) || + if (dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action)) return 0.0f; @@ -540,6 +564,24 @@ float LadyVashjDelayBloodlustAndHeroismMultiplier::GetValue(Action* action) return 1.0f; } +float LadyVashjMaintainPhase1RangedSpreadMultiplier::GetValue(Action* action) +{ + if (!botAI->IsRanged(bot)) + return 1.0f; + + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + if (vashj && IsLadyVashjInPhase1(botAI)) + { + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + float LadyVashjStaticChargeStayAwayFromGroupMultiplier::GetValue(Action* action) { if (botAI->IsMainTank(bot) || !bot->HasAura(SPELL_STATIC_CHARGE)) @@ -635,7 +677,7 @@ float LadyVashjCorePassersPrioritizePositioningMultiplier::GetValue(Action* acti return 0.0f; } - if (AnyRecentCoreInInventory(group)) + if (AnyRecentCoreInInventory(group, botAI)) { if (!dynamic_cast(action)) return 0.0f; diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h index 7f55d963a3..e1a0efece6 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h @@ -43,6 +43,14 @@ class TheLurkerBelowStayAwayFromSpoutMultiplier : public Multiplier virtual float GetValue(Action* action); }; +class TheLurkerBelowMaintainRangedSpreadMultiplier : public Multiplier +{ +public: + TheLurkerBelowMaintainRangedSpreadMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "the lurker below maintain ranged spread") {} + virtual float GetValue(Action* action); +}; + class TheLurkerBelowDisableTankAssistMultiplier : public Multiplier { public: @@ -147,11 +155,11 @@ class MorogrimTidewalkerDisableTankActionsMultiplier : public Multiplier virtual float GetValue(Action* action); }; -class MorogrimTidewalkerDisablePhase2MovementActionsMultiplier : public Multiplier +class MorogrimTidewalkerMaintainPhase2StackingMultiplier : public Multiplier { public: - MorogrimTidewalkerDisablePhase2MovementActionsMultiplier( - PlayerbotAI* botAI) : Multiplier(botAI, "morogrim tidewalker disable phase2 movement actions") {} + MorogrimTidewalkerMaintainPhase2StackingMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "morogrim tidewalker maintain phase2 stacking") {} virtual float GetValue(Action* action); }; @@ -163,6 +171,14 @@ class LadyVashjDelayBloodlustAndHeroismMultiplier : public Multiplier virtual float GetValue(Action* action); }; +class LadyVashjMaintainPhase1RangedSpreadMultiplier : public Multiplier +{ +public: + LadyVashjMaintainPhase1RangedSpreadMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj maintain phase1 ranged spread") {} + virtual float GetValue(Action* action); +}; + class LadyVashjStaticChargeStayAwayFromGroupMultiplier : public Multiplier { public: diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp index 2dfd0edd15..a00c3c6a48 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp @@ -173,6 +173,7 @@ void RaidSSCStrategy::InitMultipliers(std::vector& multipliers) multipliers.push_back(new HydrossTheUnstableWaitForDpsMultiplier(botAI)); multipliers.push_back(new HydrossTheUnstableControlMisdirectionMultiplier(botAI)); multipliers.push_back(new TheLurkerBelowStayAwayFromSpoutMultiplier(botAI)); + multipliers.push_back(new TheLurkerBelowMaintainRangedSpreadMultiplier(botAI)); multipliers.push_back(new TheLurkerBelowDisableTankAssistMultiplier(botAI)); multipliers.push_back(new LeotherasTheBlindAvoidWhirlwindMultiplier(botAI)); multipliers.push_back(new LeotherasTheBlindDisableTankActionsMultiplier(botAI)); @@ -186,8 +187,9 @@ void RaidSSCStrategy::InitMultipliers(std::vector& multipliers) multipliers.push_back(new FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier(botAI)); multipliers.push_back(new MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier(botAI)); multipliers.push_back(new MorogrimTidewalkerDisableTankActionsMultiplier(botAI)); - multipliers.push_back(new MorogrimTidewalkerDisablePhase2MovementActionsMultiplier(botAI)); + multipliers.push_back(new MorogrimTidewalkerMaintainPhase2StackingMultiplier(botAI)); multipliers.push_back(new LadyVashjDelayBloodlustAndHeroismMultiplier(botAI)); + multipliers.push_back(new LadyVashjMaintainPhase1RangedSpreadMultiplier(botAI)); multipliers.push_back(new LadyVashjStaticChargeStayAwayFromGroupMultiplier(botAI)); multipliers.push_back(new LadyVashjDoNotLootTheTaintedCoreMultiplier(botAI)); multipliers.push_back(new LadyVashjCorePassersPrioritizePositioningMultiplier(botAI)); diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp index d33b668079..8fb9c1410f 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp @@ -91,7 +91,7 @@ bool HydrossTheUnstableAggroResetsUponPhaseChangeTrigger::IsActive() bool HydrossTheUnstableNeedToManageTimersTrigger::IsActive() { - if (!IsMapIDTimerManager(botAI, bot)) + if (!IsInstanceTimerManager(botAI, bot)) return false; Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); @@ -108,7 +108,7 @@ bool TheLurkerBelowSpoutIsActiveTrigger::IsActive() const time_t now = std::time(nullptr); - auto it = lurkerSpoutTimer.find(SSC_MAP_ID); + auto it = lurkerSpoutTimer.find(lurker->GetMap()->GetInstanceId()); return it != lurkerSpoutTimer.end() && it->second > now; } @@ -123,7 +123,7 @@ bool TheLurkerBelowBossIsActiveForMainTankTrigger::IsActive() const time_t now = std::time(nullptr); - auto it = lurkerSpoutTimer.find(SSC_MAP_ID); + auto it = lurkerSpoutTimer.find(lurker->GetMap()->GetInstanceId()); return lurker->getStandState() != UNIT_STAND_STATE_SUBMERGED && (it == lurkerSpoutTimer.end() || it->second <= now); } @@ -139,7 +139,7 @@ bool TheLurkerBelowBossCastsGeyserTrigger::IsActive() const time_t now = std::time(nullptr); - auto it = lurkerSpoutTimer.find(SSC_MAP_ID); + auto it = lurkerSpoutTimer.find(lurker->GetMap()->GetInstanceId()); return lurker->getStandState() != UNIT_STAND_STATE_SUBMERGED && (it == lurkerSpoutTimer.end() || it->second <= now); } @@ -185,7 +185,7 @@ bool TheLurkerBelowBossIsSubmergedTrigger::IsActive() bool TheLurkerBelowNeedToPrepareTimerForSpoutTrigger::IsActive() { - if (!IsMapIDTimerManager(botAI, bot)) + if (!IsInstanceTimerManager(botAI, bot)) return false; Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); @@ -290,7 +290,7 @@ bool LeotherasTheBlindDemonFormTankNeedsAggro::IsActive() bool LeotherasTheBlindBossWipesAggroUponPhaseChangeTrigger::IsActive() { - if (!IsMapIDTimerManager(botAI, bot)) + if (!IsInstanceTimerManager(botAI, bot)) return false; Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); @@ -391,7 +391,7 @@ bool FathomLordKarathressDeterminingKillOrderTrigger::IsActive() bool FathomLordKarathressTanksNeedToEstablishAggroTrigger::IsActive() { - if (!IsMapIDTimerManager(botAI, bot)) + if (!IsInstanceTimerManager(botAI, bot)) return false; Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); @@ -606,7 +606,7 @@ bool LadyVashjTaintedCoreWasLootedTrigger::IsActive() else if (bot != fourthCorePasser) return false; - if (AnyRecentCoreInInventory(group)) + if (AnyRecentCoreInInventory(group, botAI)) return true; // First and second passers move to positions as soon as the elemental appears diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h index 61466ccfaa..039efce7ef 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h @@ -3,6 +3,8 @@ #include "Trigger.h" +// Trash + class UnderbogColossusSpawnedToxicPoolAfterDeathTrigger : public Trigger { public: @@ -19,6 +21,8 @@ class GreyheartTidecallerWaterElementalTotemSpawnedTrigger : public Trigger bool IsActive() override; }; +// Hydross the Unstable + class HydrossTheUnstableBotIsFrostTankTrigger : public Trigger { public: @@ -75,6 +79,8 @@ class HydrossTheUnstableNeedToManageTimersTrigger : public Trigger bool IsActive() override; }; +// The Lurker Below + class TheLurkerBelowSpoutIsActiveTrigger : public Trigger { public: @@ -115,6 +121,8 @@ class TheLurkerBelowNeedToPrepareTimerForSpoutTrigger : public Trigger bool IsActive() override; }; +// Leotheras the Blind + class LeotherasTheBlindBossIsInactiveTrigger : public Trigger { public: @@ -195,6 +203,8 @@ class LeotherasTheBlindBossWipesAggroUponPhaseChangeTrigger : public Trigger bool IsActive() override; }; +// Fathom-Lord Karathress + class FathomLordKarathressBossEngagedByMainTankTrigger : public Trigger { public: @@ -259,6 +269,8 @@ class FathomLordKarathressTanksNeedToEstablishAggroTrigger : public Trigger bool IsActive() override; }; +// Morogrim Tidewalker + class MorogrimTidewalkerPullingBossTrigger : public Trigger { public: @@ -291,6 +303,8 @@ class MorogrimTidewalkerEncounterResetTrigger : public Trigger bool IsActive() override; }; +// Lady Vashj + class LadyVashjBossEngagedByMainTankTrigger : public Trigger { public: From f076fde613965f9464737e51543b6703201c8f69 Mon Sep 17 00:00:00 2001 From: crow Date: Sat, 20 Dec 2025 00:13:57 -0600 Subject: [PATCH 07/25] some edits for efficiency and fixes Also took Sporebats off of prio list in Vashj P3. Bots are going in midair to kill them--it looks stupid and is basically cheating, and I don't know how to stop it. --- .../RaidSSCActionContext.h | 12 +- .../serpentshrinecavern/RaidSSCActions.cpp | 334 +++++++++--------- .../serpentshrinecavern/RaidSSCActions.h | 16 +- .../serpentshrinecavern/RaidSSCHelpers.cpp | 24 +- .../serpentshrinecavern/RaidSSCHelpers.h | 2 +- .../RaidSSCMultipliers.cpp | 2 +- .../serpentshrinecavern/RaidSSCStrategy.cpp | 12 +- .../RaidSSCTriggerContext.h | 14 +- .../serpentshrinecavern/RaidSSCTriggers.cpp | 19 +- .../serpentshrinecavern/RaidSSCTriggers.h | 14 +- 10 files changed, 212 insertions(+), 237 deletions(-) diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h b/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h index a07887b461..be461b6039 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h @@ -136,15 +136,15 @@ class RaidSSCActionContext : public NamedObjectContext creators["lady vashj misdirect boss to main tank"] = &RaidSSCActionContext::lady_vashj_misdirect_boss_to_main_tank; + creators["lady vashj assign phase 2 and phase 3 dps priority"] = + &RaidSSCActionContext::lady_vashj_assign_phase_2_and_phase_3_dps_priority; + creators["lady vashj misdirect strider to first assist tank"] = &RaidSSCActionContext::lady_vashj_misdirect_strider_to_first_assist_tank; creators["lady vashj tank attack and move away strider"] = &RaidSSCActionContext::lady_vashj_tank_attack_and_move_away_strider; - creators["lady vashj assign dps priority"] = - &RaidSSCActionContext::lady_vashj_assign_dps_priority; - creators["lady vashj loot tainted core"] = &RaidSSCActionContext::lady_vashj_loot_tainted_core; @@ -295,15 +295,15 @@ class RaidSSCActionContext : public NamedObjectContext static Action* lady_vashj_misdirect_boss_to_main_tank( PlayerbotAI* botAI) { return new LadyVashjMisdirectBossToMainTankAction(botAI); } + static Action* lady_vashj_assign_phase_2_and_phase_3_dps_priority( + PlayerbotAI* botAI) { return new LadyVashjAssignPhase2AndPhase3DpsPriorityAction(botAI); } + static Action* lady_vashj_misdirect_strider_to_first_assist_tank( PlayerbotAI* botAI) { return new LadyVashjMisdirectStriderToFirstAssistTankAction(botAI); } static Action* lady_vashj_tank_attack_and_move_away_strider( PlayerbotAI* botAI) { return new LadyVashjTankAttackAndMoveAwayStriderAction(botAI); } - static Action* lady_vashj_assign_dps_priority( - PlayerbotAI* botAI) { return new LadyVashjAssignDpsPriorityAction(botAI); } - static Action* lady_vashj_teleport_to_tainted_elemental( PlayerbotAI* botAI) { return new LadyVashjTeleportToTaintedElementalAction(botAI); } diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp index 00e1e836ba..5a2033cf19 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp @@ -11,8 +11,7 @@ using namespace SerpentShrineCavernHelpers; // Trash Mobs -// Non-combat method; some colossi leave a toxic pool upon death -// Without this method, bots just stand (or drink) in the pool and die +// Non-combat method (some colossi leave a toxic pool upon death) bool UnderbogColossusEscapeToxicPoolAction::Execute(Event event) { Aura* aura = bot->GetAura(SPELL_TOXIC_POOL); @@ -542,51 +541,42 @@ bool TheLurkerBelowSpreadRangedInArcAction::Execute(Event event) auto it = lurkerRangedPositions.find(guid); if (it == lurkerRangedPositions.end()) { - auto findIt = std::find(rangedMembers.begin(), rangedMembers.end(), bot); - size_t botIndex = - (findIt != rangedMembers.end()) ? std::distance(rangedMembers.begin(), findIt) : 0; size_t count = rangedMembers.size(); - if (count == 0) - return false; - - const float minRadius = 27.0f; - const float maxRadius = 29.0f; - const float mainTankFacingOrientation = 2.262f; - const float arcCenter = - Position::NormalizeOrientation(mainTankFacingOrientation + M_PI); + auto findIt = std::find(rangedMembers.begin(), rangedMembers.end(), bot); + size_t botIndex = (findIt != rangedMembers.end()) ? + std::distance(rangedMembers.begin(), findIt) : 0; - const float arcSpan = 2.0f * M_PI / 3.0f; // 120° + const float arcSpan = 2.0f * M_PI / 3.0f; + const float arcCenter = 2.262f; const float arcStart = arcCenter - arcSpan / 2.0f; - float angle; - if (count == 1) - angle = arcCenter; - else - angle = arcStart + (static_cast(botIndex) / (count - 1)) * arcSpan; - float radius = frand(minRadius, maxRadius); + float angle = (count == 1) ? arcCenter : + (arcStart + arcSpan * static_cast(botIndex) / static_cast(count - 1)); + float radius = 28.0f; - float tx = lurker->GetPositionX() + radius * std::cos(angle); - float ty = lurker->GetPositionY() + radius * std::sin(angle); + float targetX = lurker->GetPositionX() + radius * std::sin(angle); + float targetY = lurker->GetPositionY() + radius * std::cos(angle); - lurkerRangedPositions.try_emplace(guid, Position(tx, ty, lurker->GetPositionZ())); + lurkerRangedPositions.try_emplace(guid, Position(targetX, targetY, lurker->GetPositionZ())); it = lurkerRangedPositions.find(guid); } if (it == lurkerRangedPositions.end()) return false; - const Position& target = it->second; - if (bot->GetExactDist2d(target.GetPositionX(), target.GetPositionY()) > 2.0f) + const Position& position = it->second; + if (bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 2.0f) { - return MoveTo(SSC_MAP_ID, target.GetPositionX(), target.GetPositionY(), - target.GetPositionZ(), false, false, false, false, + return MoveTo(SSC_MAP_ID, position.GetPositionX(), position.GetPositionY(), + position.GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); } return false; } -// If >= 3 tanks in the raid, the first 3 will each pick up 1 Guardian +// During the submerge phase, if there are >= 3 tanks in the raid, +// the first 3 will each pick up 1 Guardian bool TheLurkerBelowTanksPickUpAddsAction::Execute(Event event) { Player* mainTank = nullptr; @@ -691,6 +681,7 @@ bool LeotherasTheBlindTargetSpellbindersAction::Execute(Event event) return false; } +// Warlock tank action--see GetLeotherasDemonFormTank in RaidSSCHelpers.cpp bool LeotherasTheBlindDemonFormTankAttackBossAction::Execute(Event event) { Unit* leotherasDemon = GetActiveLeotherasDemon(botAI); @@ -767,18 +758,18 @@ bool LeotherasTheBlindRunAwayFromWhirlwindAction::Execute(Event event) return false; } -// If there is no Warlock tank, then a melee tank will be picking up Demon Leo -// In that case, melee needs to get out after too many Chaos Blast stacks +// This method is likely unnecessary unless the player does not use a Warlock tank +// If a melee tank is used, other melee needs to run away after too many Chaos Blast stacks bool LeotherasTheBlindMeleeDpsRunAwayFromBossAction::Execute(Event event) { Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI); - Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); + /* Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); */ - if (!leotherasPhase2Demon || demonFormTank != nullptr) + if (!leotherasPhase2Demon /* || demonFormTank != nullptr */) return false; Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST); - if (chaosBlast && chaosBlast->GetStackAmount() >= 5) + if (chaosBlast && chaosBlast->GetStackAmount() >= 4) { Unit* demonVictim = leotherasPhase2Demon->GetVictim(); if (!demonVictim) @@ -797,8 +788,8 @@ bool LeotherasTheBlindMeleeDpsRunAwayFromBossAction::Execute(Event event) return false; } -// Tanks and healers have no ability to kill their own Inner Demons -// Ranged DPS also struggle +// Tanks and healers have no ability to kill their own Inner Demons; and ranged DPS +// also struggle, so this cheat action kills their Inner Demons for them bool LeotherasTheBlindInnerDemonCheatAction::Execute(Event event) { Unit* innerDemon = nullptr; @@ -930,8 +921,8 @@ bool LeotherasTheBlindManageDpsWaitTimersAction::Execute(Event event) } // Fathom-Lord Karathress -// Note: Four tanks are required for the full strategy, but -// Caribdis hits for nothing so just respec a DPS warrior and put on a shield +// Note: 4 tanks are required for the full strategy, and having at least 2 +// is crucial to separate Caribdis from the others // Karathress is tanked near his starting position bool FathomLordKarathressMainTankPositionBossAction::Execute(Event event) @@ -1186,9 +1177,8 @@ bool FathomLordKarathressMisdirectBossesToTanksAction::Execute(Event event) return false; } -// Kill order is different from what is recommended for players because -// bots handle Caribdis Cyclones poorly and need more time to get her down -// than real players (normally, ranged would help with Sharkkis first) +// Kill order is non-standard because bots handle Caribdis Cyclones poorly and need more time to +// get her down than real players (standard approach is ranged DPS would help with Sharkkis first) bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) { // Target priority 1: Spitfire Totems for melee dps @@ -1217,7 +1207,7 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) return false; } - // Target priority 3: Caribdis for ranged + // Target priority 3: Caribdis for ranged dps Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); if (botAI->IsRangedDps(bot) && caribdis && caribdis->IsAlive()) { @@ -1237,7 +1227,7 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) return false; } - // Target priority 4: Sharkkis for melee (and ranged if Caribdis is down first) + // Target priority 4: Sharkkis for melee dps and, after Caribdis is down, ranged dps also Unit* sharkkis = AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis"); if (sharkkis && sharkkis->IsAlive()) { @@ -1337,6 +1327,8 @@ bool MorogrimTidewalkerMisdirectBossToMainTankAction::Execute(Event event) return false; } +// Separate tanking positions are used for phase 1 and phase 2 to address the +// Water Globule mechanic in phase 2 bool MorogrimTidewalkerMoveBossToTankPositionAction::Execute(Event event) { Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); @@ -1357,7 +1349,7 @@ bool MorogrimTidewalkerMoveBossToTankPositionAction::Execute(Event event) return false; } -// Phase 1 tank position is up against the Northeast pillar +// Phase 1: tank position is up against the Northeast pillar bool MorogrimTidewalkerMoveBossToTankPositionAction::MoveToPhase1TankPosition(Unit* tidewalker) { const Position& phase1 = TIDEWALKER_PHASE_1_TANK_POSITION; @@ -1377,7 +1369,7 @@ bool MorogrimTidewalkerMoveBossToTankPositionAction::MoveToPhase1TankPosition(Un return false; } -// Phase 2: move in two steps to get around the pillar and back into the Northeast corner +// Phase 2: move in two steps to get around the pillar and back up into the Northeast corner bool MorogrimTidewalkerMoveBossToTankPositionAction::MoveToPhase2TankPosition(Unit* tidewalker) { const Position& phase2 = TIDEWALKER_PHASE_2_TANK_POSITION; @@ -1428,7 +1420,8 @@ bool MorogrimTidewalkerMoveBossToTankPositionAction::MoveToPhase2TankPosition(Un return false; } -// Stack behind the boss in the Northeast corner in phase 2 +// Ranged stack behind the boss in the Northeast corner in phase 2 +// No corresponding method for melee since they will do so anyway bool MorogrimTidewalkerPhase2RepositionRangedAction::Execute(Event event) { Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); @@ -1535,7 +1528,7 @@ bool LadyVashjMainTankPositionBossAction::Execute(Event event) false, false, MovementPriority::MOVEMENT_COMBAT, true, true); } } - // Phase 3: Move Vashj away from Enchanted Elementals + // Phase 3: No fixed position, but move Vashj away from Enchanted Elementals else if (IsLadyVashjInPhase3(botAI)) { Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental"); @@ -1552,7 +1545,7 @@ bool LadyVashjMainTankPositionBossAction::Execute(Event event) return false; } -// Semicircle around center of the room (to allow escape by Static Charged bots) +// Semicircle around center of the room (to allow escape paths by Static Charged bots) bool LadyVashjPhase1SpreadRangedInArcAction::Execute(Event event) { std::vector spreadMembers; @@ -1576,7 +1569,8 @@ bool LadyVashjPhase1SpreadRangedInArcAction::Execute(Event event) if (itPos == vashjRangedPositions.end()) { auto it = std::find(spreadMembers.begin(), spreadMembers.end(), bot); - size_t botIndex = (it != spreadMembers.end()) ? std::distance(spreadMembers.begin(), it) : 0; + size_t botIndex = (it != spreadMembers.end()) ? + std::distance(spreadMembers.begin(), it) : 0; size_t count = spreadMembers.size(); if (count == 0) return false; @@ -1599,8 +1593,7 @@ bool LadyVashjPhase1SpreadRangedInArcAction::Execute(Event event) float targetX = center.GetPositionX() + radius * std::cos(angle); float targetY = center.GetPositionY() + radius * std::sin(angle); - auto res = vashjRangedPositions.try_emplace( - guid, Position(targetX, targetY, center.GetPositionZ())); + auto res = vashjRangedPositions.try_emplace(guid, Position(targetX, targetY, center.GetPositionZ())); itPos = res.first; hasReachedVashjRangedPosition.try_emplace(guid, false); itReached = hasReachedVashjRangedPosition.find(guid); @@ -1626,6 +1619,7 @@ bool LadyVashjPhase1SpreadRangedInArcAction::Execute(Event event) } // For absorbing Shock Burst +// For some reason, if you use an Enhancement Shaman for this method, they will not dps bool LadyVashjSetGroundingTotemInMainTankGroupAction::Execute(Event event) { Player* mainTank = nullptr; @@ -1720,7 +1714,7 @@ bool LadyVashjStaticChargeMoveAwayFromGroupAction::Execute(Event event) return MoveFromGroup(safeDistance + 0.5f); } - // If any other bot has static charge, it should move away from other group members + // If any other bot has Static Charge, it should move away from other group members if (!botAI->IsMainTank(bot) && bot->HasAura(SPELL_STATIC_CHARGE)) { for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) @@ -1739,115 +1733,7 @@ bool LadyVashjStaticChargeMoveAwayFromGroupAction::Execute(Event event) return false; } -bool LadyVashjMisdirectStriderToFirstAssistTankAction::Execute(Event event) -{ - // Strider is not tankable without cheat - if (!botAI->HasCheat(BotCheatMask::raid)) - return false; - - if (bot->getClass() != CLASS_HUNTER) - return false; - - Unit* strider = GetFirstAliveUnitByEntry(botAI, NPC_COILFANG_STRIDER); - if (!strider) - return false; - - Player* firstAssistTank = nullptr; - if (Group* group = bot->GetGroup()) - { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsAssistTankOfIndex(member, 0)) - { - firstAssistTank = member; - break; - } - } - } - - if (!firstAssistTank || strider->GetVictim() == firstAssistTank) - return false; - - if (botAI->CanCastSpell("misdirection", firstAssistTank)) - return botAI->CastSpell("misdirection", firstAssistTank); - - if (bot->HasAura(SPELL_MISDIRECTION) && botAI->CanCastSpell("steady shot", strider)) - return botAI->CastSpell("steady shot", strider); - - return false; -} - -bool LadyVashjTankAttackAndMoveAwayStriderAction::Execute(Event event) -{ - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - if (!vashj) - return false; - - Unit* strider = GetFirstAliveUnitByEntry(botAI, NPC_COILFANG_STRIDER); - if (!strider) - return false; - - // Raid cheat automatically applies Fear Ward to tanks to make Strider tankable - // This simulates the real-life strategy where the Strider can be meleed by - // Bots wearing an Ogre Suit (due to the extended combat reach) - if (botAI->HasCheat(BotCheatMask::raid) && botAI->IsTank(bot)) - { - if (!bot->HasAura(SPELL_FEAR_WARD)) - bot->AddAura(SPELL_FEAR_WARD, bot); - - if (botAI->IsAssistTankOfIndex(bot, 0) && bot->GetVictim() != strider) - return Attack(strider); - - if (strider->GetVictim() == bot) - { - float currentDistance = bot->GetExactDist2d(vashj); - const float safeDistance = 25.0f; - - if (currentDistance < safeDistance) - return MoveAway(vashj, safeDistance - currentDistance + 5.0f); - } - - return false; - } - - // Don't move away if raid cheats are enabled, or in any case if the bot is a tank - if (!botAI->HasCheat(BotCheatMask::raid) || !botAI->IsTank(bot)) - { - float currentDistance = bot->GetExactDist2d(strider); - const float safeDistance = 15.0f; - if (currentDistance < safeDistance) - return MoveAway(strider, safeDistance - currentDistance + 5.0f); - } - - // Try to root/slow the Strider if it is not tankable - if (!botAI->HasCheat(BotCheatMask::raid)) - { - if (!strider->HasAura(SPELL_HEAVY_NETHERWEAVE_NET)) - { - Item* net = bot->GetItemByEntry(ITEM_HEAVY_NETHERWEAVE_NET); - if (net && botAI->HasItemInInventory(ITEM_HEAVY_NETHERWEAVE_NET) && - botAI->CanCastSpell("heavy netherweave net", strider)) - return botAI->CastSpell("heavy netherweave net", strider); - } - - if (!strider->HasAura(SPELL_FROST_SHOCK) && bot->getClass() == CLASS_SHAMAN && - botAI->CanCastSpell("frost shock", strider)) - return botAI->CastSpell("frost shock", strider); - - if (!strider->HasAura(SPELL_CURSE_OF_EXHAUSTION) && bot->getClass() == CLASS_WARLOCK && - botAI->CanCastSpell("curse of exhaustion", strider)) - return botAI->CastSpell("curse of exhaustion", strider); - - if (!strider->HasAura(SPELL_SLOW) && bot->getClass() == CLASS_MAGE && - botAI->CanCastSpell("slow", strider)) - return botAI->CastSpell("slow", strider); - } - - return false; -} - -bool LadyVashjAssignDpsPriorityAction::Execute(Event event) +bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event event) { Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); if (!vashj) @@ -1860,7 +1746,7 @@ bool LadyVashjAssignDpsPriorityAction::Execute(Event event) Unit* enchanted = nullptr; Unit* elite = nullptr; Unit* strider = nullptr; - Unit* sporebat = nullptr; + // Unit* sporebat = nullptr; // Search and attack radius are intended to keep bots on the platform (not go down the stairs) const Position& center = VASHJ_PLATFORM_CENTER_POSITION; @@ -1900,10 +1786,10 @@ bool LadyVashjAssignDpsPriorityAction::Execute(Event event) strider = unit; break; - case NPC_TOXIC_SPOREBAT: + /* case NPC_TOXIC_SPOREBAT: if (!sporebat || unit->GetHealthPct() < sporebat->GetHealthPct()) sporebat = unit; - break; + break; */ case NPC_LADY_VASHJ: vashj = unit; @@ -1930,7 +1816,6 @@ bool LadyVashjAssignDpsPriorityAction::Execute(Event event) targets = { tainted, enchanted, elite }; else if (botAI->IsTank(bot)) { - // With raid cheats enabled, the first assist tank will tank the Strider if (botAI->HasCheat(BotCheatMask::raid) && botAI->IsAssistTankOfIndex(bot, 0)) targets = { strider, elite, enchanted }; else @@ -1960,11 +1845,12 @@ bool LadyVashjAssignDpsPriorityAction::Execute(Event event) } else if (botAI->IsRanged(bot)) { - // Ranged, other than Priests and Warlocks, will prioritize Toxic Sporebats - if (bot->getClass() == CLASS_PRIEST || bot->getClass() == CLASS_WARLOCK) + targets = { enchanted, strider, elite, vashj }; + // Prior iteration: ranged, other than Priests and Warlocks, prioritize Toxic Sporebats + /* if (bot->getClass() == CLASS_PRIEST || bot->getClass() == CLASS_WARLOCK) targets = { enchanted, strider, elite, vashj }; else - targets = { enchanted, sporebat, strider, elite, vashj }; + targets = { enchanted, sporebat, strider, elite, vashj }; */ } else if (botAI->IsMelee(bot) && botAI->IsDps(bot)) targets = { enchanted, elite, vashj }; @@ -2022,8 +1908,7 @@ bool LadyVashjAssignDpsPriorityAction::Execute(Event event) bot->SetSelection(ObjectGuid()); } - // If bots have wandered too far from the center and - // are not attacking anything, move them back + // If bots have wandered too far from the center and are not attacking anything, move them back if (!bot->GetVictim()) { Player* designatedLooter = GetDesignatedCoreLooter(bot->GetGroup(), botAI); @@ -2047,8 +1932,117 @@ bool LadyVashjAssignDpsPriorityAction::Execute(Event event) return false; } +bool LadyVashjMisdirectStriderToFirstAssistTankAction::Execute(Event event) +{ + // Striders are not tankable without a cheat to block Fear so there is + // no point in misdirecting if raid cheats are not enabled + if (!botAI->HasCheat(BotCheatMask::raid)) + return false; + + if (bot->getClass() != CLASS_HUNTER) + return false; + + Unit* strider = GetFirstAliveUnitByEntry(botAI, NPC_COILFANG_STRIDER); + if (!strider) + return false; + + Player* firstAssistTank = nullptr; + if (Group* group = bot->GetGroup()) + { + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->IsAlive() && botAI->IsAssistTankOfIndex(member, 0)) + { + firstAssistTank = member; + break; + } + } + } + + if (!firstAssistTank || strider->GetVictim() == firstAssistTank) + return false; + + if (botAI->CanCastSpell("misdirection", firstAssistTank)) + return botAI->CastSpell("misdirection", firstAssistTank); + + if (bot->HasAura(SPELL_MISDIRECTION) && botAI->CanCastSpell("steady shot", strider)) + return botAI->CastSpell("steady shot", strider); + + return false; +} + +bool LadyVashjTankAttackAndMoveAwayStriderAction::Execute(Event event) +{ + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + if (!vashj) + return false; + + Unit* strider = GetFirstAliveUnitByEntry(botAI, NPC_COILFANG_STRIDER); + if (!strider) + return false; + + // Raid cheat automatically applies Fear Ward to tanks to make Strider tankable + // This simulates the real-life strategy where the Strider can be meleed by + // players wearing an Ogre Suit (due to the extended combat reach) + if (botAI->HasCheat(BotCheatMask::raid) && botAI->IsTank(bot)) + { + if (!bot->HasAura(SPELL_FEAR_WARD)) + bot->AddAura(SPELL_FEAR_WARD, bot); + + if (botAI->IsAssistTankOfIndex(bot, 0) && bot->GetVictim() != strider) + return Attack(strider); + + if (strider->GetVictim() == bot) + { + float currentDistance = bot->GetExactDist2d(vashj); + const float safeDistance = 25.0f; + + if (currentDistance < safeDistance) + return MoveAway(vashj, safeDistance - currentDistance + 5.0f); + } + + return false; + } + + // Don't move away if raid cheats are enabled, or in any case if the bot is a tank + if (!botAI->HasCheat(BotCheatMask::raid) || !botAI->IsTank(bot)) + { + float currentDistance = bot->GetExactDist2d(strider); + const float safeDistance = 15.0f; + if (currentDistance < safeDistance) + return MoveAway(strider, safeDistance - currentDistance + 5.0f); + } + + // Try to root/slow the Strider if it is not tankable (poor man's kiting strategy) + if (!botAI->HasCheat(BotCheatMask::raid)) + { + if (!strider->HasAura(SPELL_HEAVY_NETHERWEAVE_NET)) + { + Item* net = bot->GetItemByEntry(ITEM_HEAVY_NETHERWEAVE_NET); + if (net && botAI->HasItemInInventory(ITEM_HEAVY_NETHERWEAVE_NET) && + botAI->CanCastSpell("heavy netherweave net", strider)) + return botAI->CastSpell("heavy netherweave net", strider); + } + + if (!strider->HasAura(SPELL_FROST_SHOCK) && bot->getClass() == CLASS_SHAMAN && + botAI->CanCastSpell("frost shock", strider)) + return botAI->CastSpell("frost shock", strider); + + if (!strider->HasAura(SPELL_CURSE_OF_EXHAUSTION) && bot->getClass() == CLASS_WARLOCK && + botAI->CanCastSpell("curse of exhaustion", strider)) + return botAI->CastSpell("curse of exhaustion", strider); + + if (!strider->HasAura(SPELL_SLOW) && bot->getClass() == CLASS_MAGE && + botAI->CanCastSpell("slow", strider)) + return botAI->CastSpell("slow", strider); + } + + return false; +} + // If cheats are enabled, the first returned melee DPS bot will teleport to Tainted Elementals -// Such bot will recover HP and remove Poison Bolt debuff while attacking the elemental +// Such bot will recover HP and remove the Poison Bolt debuff while attacking the elemental bool LadyVashjTeleportToTaintedElementalAction::Execute(Event event) { Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental"); diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h index 78763b5e6a..8b7adaa5ce 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h @@ -347,6 +347,14 @@ class LadyVashjMisdirectBossToMainTankAction : public AttackAction bool Execute(Event event) override; }; +class LadyVashjAssignPhase2AndPhase3DpsPriorityAction : public AttackAction +{ +public: + LadyVashjAssignPhase2AndPhase3DpsPriorityAction( + PlayerbotAI* botAI, std::string const name = "lady vashj assign phase 2 and phase 3 dps priority") : AttackAction(botAI, name) {} + bool Execute(Event event) override; +}; + class LadyVashjMisdirectStriderToFirstAssistTankAction : public AttackAction { public: @@ -363,14 +371,6 @@ class LadyVashjTankAttackAndMoveAwayStriderAction : public AttackAction bool Execute(Event event) override; }; -class LadyVashjAssignDpsPriorityAction : public AttackAction -{ -public: - LadyVashjAssignDpsPriorityAction( - PlayerbotAI* botAI, std::string const name = "lady vashj assign dps priority") : AttackAction(botAI, name) {} - bool Execute(Event event) override; -}; - class LadyVashjTeleportToTaintedElementalAction : public AttackAction { public: diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp index 86cb33c88f..beda48d0c1 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp @@ -235,27 +235,31 @@ namespace SerpentShrineCavernHelpers if (!group) return nullptr; - Player* fallbackWarlockTank = nullptr; + // (1) First loop: Return the first assistant Warlock (real player or bot) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); if (!member || !member->IsAlive() || member->getClass() != CLASS_WARLOCK) continue; - // (1) Return the first assistant Warlock (real player or bot) if (group->IsAssistant(member->GetGUID())) return member; + } + + // (2) Second loop: Return the first Warlock bot with the tank strategy + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || member->getClass() != CLASS_WARLOCK) + continue; - // (2) Otherwise, get the first Warlock bot with the co +tank strategy PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); - if (!fallbackWarlockTank && memberAI && - memberAI->HasStrategy("tank", BotState::BOT_STATE_COMBAT)) - fallbackWarlockTank = member; + if (memberAI && memberAI->HasStrategy("tank", BotState::BOT_STATE_COMBAT)) + return member; } - // (3) Return the fallback Warlock bot tank if found, - // otherwise nullptr (no Warlock tank for Leotheras) - return fallbackWarlockTank; + // (3) If no assistant or tank Warlock found, return nullptr + return nullptr; } // Fathom-Lord Karathress @@ -370,7 +374,7 @@ namespace SerpentShrineCavernHelpers { return entry == NPC_TAINTED_ELEMENTAL || entry == NPC_ENCHANTED_ELEMENTAL || entry == NPC_COILFANG_ELITE || entry == NPC_COILFANG_STRIDER || - entry == NPC_TOXIC_SPOREBAT || entry == NPC_LADY_VASHJ; + /* entry == NPC_TOXIC_SPOREBAT || */ entry == NPC_LADY_VASHJ; } return false; diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h index e1e67cc869..6bb85a39e3 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h @@ -92,7 +92,7 @@ namespace SerpentShrineCavernHelpers NPC_TAINTED_ELEMENTAL = 22009, NPC_COILFANG_ELITE = 22055, NPC_COILFANG_STRIDER = 22056, - NPC_TOXIC_SPOREBAT = 22140, + // NPC_TOXIC_SPOREBAT = 22140, NPC_SPORE_DROP_TRIGGER = 22207, }; diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp index 9dfe69210f..4f89d6affb 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp @@ -740,7 +740,7 @@ float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *ac if ((!enchanted || !enchanted->IsAlive()) && (!strider || !strider->IsAlive()) && (!elite || !elite->IsAlive())) { - if (dynamic_cast(action)) + if (dynamic_cast(action)) return 0.0f; } } diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp index a00c3c6a48..ee97fe02ae 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp @@ -136,11 +136,6 @@ void RaidSSCStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("lady vashj pulling boss in phase 1 and phase 3", NextAction::array(0, new NextAction("lady vashj misdirect boss to main tank", ACTION_EMERGENCY + 1), nullptr) )); - triggers.push_back(new TriggerNode("lady vashj coilfang strider is approaching", - NextAction::array(0, - new NextAction("lady vashj misdirect strider to first assist tank", ACTION_EMERGENCY + 1), - new NextAction("lady vashj tank attack and move away strider", ACTION_EMERGENCY + 1), nullptr) - )); triggers.push_back(new TriggerNode("lady vashj tainted elemental cheat", NextAction::array(0, new NextAction("lady vashj teleport to tainted elemental", ACTION_EMERGENCY + 10), @@ -152,8 +147,11 @@ void RaidSSCStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("lady vashj tainted core is unusable", NextAction::array(0, new NextAction("lady vashj destroy tainted core", ACTION_EMERGENCY + 1), nullptr) )); - triggers.push_back(new TriggerNode("lady vashj determining kill order of adds", - NextAction::array(0, new NextAction("lady vashj assign dps priority", ACTION_RAID + 1), nullptr) + triggers.push_back(new TriggerNode("lady vashj adds spawn in phase 2 and phase 3", + NextAction::array(0, + new NextAction("lady vashj assign phase 2 and phase 3 dps priority", ACTION_RAID + 1), + new NextAction("lady vashj misdirect strider to first assist tank", ACTION_EMERGENCY + 1), + new NextAction("lady vashj tank attack and move away strider", ACTION_EMERGENCY + 1), nullptr) )); triggers.push_back(new TriggerNode("lady vashj toxic sporebats are spewing poison clouds", NextAction::array(0, new NextAction("lady vashj avoid toxic spores", ACTION_EMERGENCY + 6), nullptr) diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h index 31a8fbb015..eaee4c81ba 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h @@ -136,11 +136,8 @@ class RaidSSCTriggerContext : public NamedObjectContext creators["lady vashj pulling boss in phase 1 and phase 3"] = &RaidSSCTriggerContext::lady_vashj_pulling_boss_in_phase_1_and_phase_3; - creators["lady vashj coilfang strider is approaching"] = - &RaidSSCTriggerContext::lady_vashj_coilfang_strider_is_approaching; - - creators["lady vashj determining kill order of adds"] = - &RaidSSCTriggerContext::lady_vashj_determining_kill_order_of_adds; + creators["lady vashj adds spawn in phase 2 and phase 3"] = + &RaidSSCTriggerContext::lady_vashj_adds_spawn_in_phase_2_and_phase_3; creators["lady vashj tainted elemental cheat"] = &RaidSSCTriggerContext::lady_vashj_tainted_elemental_cheat; @@ -289,11 +286,8 @@ class RaidSSCTriggerContext : public NamedObjectContext static Trigger* lady_vashj_pulling_boss_in_phase_1_and_phase_3( PlayerbotAI* botAI) { return new LadyVashjPullingBossInPhase1AndPhase3Trigger(botAI); } - static Trigger* lady_vashj_coilfang_strider_is_approaching( - PlayerbotAI* botAI) { return new LadyVashjCoilfangStriderIsApproachingTrigger(botAI); } - - static Trigger* lady_vashj_determining_kill_order_of_adds( - PlayerbotAI* botAI) { return new LadyVashjDeterminingKillOrderOfAddsTrigger(botAI); } + static Trigger* lady_vashj_adds_spawn_in_phase_2_and_phase_3( + PlayerbotAI* botAI) { return new LadyVashjAddsSpawnInPhase2AndPhase3Trigger(botAI); } static Trigger* lady_vashj_tainted_elemental_cheat( PlayerbotAI* botAI) { return new LadyVashjTaintedElementalCheatTrigger(botAI); } diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp index 8fb9c1410f..d682994db7 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp @@ -79,10 +79,10 @@ bool HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger::IsActive() bool HydrossTheUnstableAggroResetsUponPhaseChangeTrigger::IsActive() { - if (bot->getClass() == CLASS_HUNTER) - return false; - - if (botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0)) + if (bot->getClass() == CLASS_HUNTER || + botAI->IsMainTank(bot) || + botAI->IsAssistTankOfIndex(bot, 0) || + botAI->IsHeal(bot)) return false; Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); @@ -499,17 +499,10 @@ bool LadyVashjPullingBossInPhase1AndPhase3Trigger::IsActive() vashj->GetHealthPct() > 40.0f)); } -bool LadyVashjCoilfangStriderIsApproachingTrigger::IsActive() -{ - Unit* strider = AI_VALUE2(Unit*, "find target", "coilfang strider"); - return strider && strider->IsAlive() && - (IsLadyVashjInPhase2(botAI) || IsLadyVashjInPhase3(botAI)); -} - -bool LadyVashjDeterminingKillOrderOfAddsTrigger::IsActive() +bool LadyVashjAddsSpawnInPhase2AndPhase3Trigger::IsActive() { Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - return vashj && (IsLadyVashjInPhase2(botAI) || IsLadyVashjInPhase3(botAI)); + return vashj && !IsLadyVashjInPhase1(botAI); } bool LadyVashjTaintedElementalCheatTrigger::IsActive() diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h index 039efce7ef..ad3b76d524 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h @@ -345,19 +345,11 @@ class LadyVashjPullingBossInPhase1AndPhase3Trigger : public Trigger bool IsActive() override; }; -class LadyVashjCoilfangStriderIsApproachingTrigger : public Trigger +class LadyVashjAddsSpawnInPhase2AndPhase3Trigger : public Trigger { public: - LadyVashjCoilfangStriderIsApproachingTrigger( - PlayerbotAI* botAI) : Trigger(botAI, "lady vashj coilfang strider is approaching") {} - bool IsActive() override; -}; - -class LadyVashjDeterminingKillOrderOfAddsTrigger : public Trigger -{ -public: -LadyVashjDeterminingKillOrderOfAddsTrigger( - PlayerbotAI* botAI) : Trigger(botAI, "lady vashj determining kill order of adds") {} + LadyVashjAddsSpawnInPhase2AndPhase3Trigger( + PlayerbotAI* botAI) : Trigger(botAI, "lady vashj adds spawn in phase 2 and phase 3") {} bool IsActive() override; }; From 9fd36f5d296960ba6e63e7d2c6395d90d9f0b34e Mon Sep 17 00:00:00 2001 From: crow Date: Tue, 23 Dec 2025 01:49:21 -0600 Subject: [PATCH 08/25] Vashj fix + FLK tweaks --- .../serpentshrinecavern/RaidSSCActions.cpp | 42 +++++++++---------- .../serpentshrinecavern/RaidSSCHelpers.h | 4 +- .../RaidSSCMultipliers.cpp | 20 ++++++++- 3 files changed, 41 insertions(+), 25 deletions(-) diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp index 5a2033cf19..1f3db92e37 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp @@ -104,7 +104,7 @@ bool HydrossTheUnstablePositionFrostTankAction::Execute(Event event) MarkTargetWithSquare(bot, hydross); SetRtiTarget(botAI, "square", hydross); - if (bot->GetVictim() != hydross) + if (bot->GetTarget() != hydross->GetGUID()) return Attack(hydross); if (hydross->GetVictim() == bot && bot->IsWithinMeleeRange(hydross)) @@ -181,7 +181,7 @@ bool HydrossTheUnstablePositionNatureTankAction::Execute(Event event) MarkTargetWithTriangle(bot, hydross); SetRtiTarget(botAI, "triangle", hydross); - if (bot->GetVictim() != hydross) + if (bot->GetTarget() != hydross->GetGUID()) return Attack(hydross); if (hydross->GetVictim() == bot && bot->IsWithinMeleeRange(hydross)) @@ -496,7 +496,7 @@ bool TheLurkerBelowPositionMainTankAction::Execute(Event event) if (!lurker) return false; - if (bot->GetVictim() != lurker) + if (bot->GetTarget() != lurker->GetGUID()) return Attack(lurker); const Position& position = LURKER_MAIN_TANK_POSITION; @@ -633,7 +633,7 @@ bool TheLurkerBelowTanksPickUpAddsAction::Execute(Event event) { MarkTargetWithIcon(bot, guardian, rtiIndices[i]); SetRtiTarget(botAI, rtiNames[i], guardian); - if (bot->GetVictim() != guardian) + if (bot->GetTarget() != guardian->GetGUID()) return Attack(guardian); } } @@ -934,7 +934,7 @@ bool FathomLordKarathressMainTankPositionBossAction::Execute(Event event) MarkTargetWithTriangle(bot, karathress); SetRtiTarget(botAI, "triangle", karathress); - if (bot->GetVictim() != karathress) + if (bot->GetTarget() != karathress->GetGUID()) return Attack(karathress); if (karathress->GetVictim() == bot && bot->IsWithinMeleeRange(karathress)) @@ -943,7 +943,7 @@ bool FathomLordKarathressMainTankPositionBossAction::Execute(Event event) float distToPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); - if (distToPosition > 3.0f) + if (distToPosition > 2.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); @@ -970,7 +970,7 @@ bool FathomLordKarathressFirstAssistTankPositionCaribdisAction::Execute(Event ev MarkTargetWithDiamond(bot, caribdis); SetRtiTarget(botAI, "diamond", caribdis); - if (bot->GetVictim() != caribdis) + if (bot->GetTarget() != caribdis->GetGUID()) return Attack(caribdis); if (caribdis->GetVictim() == bot) @@ -979,7 +979,7 @@ bool FathomLordKarathressFirstAssistTankPositionCaribdisAction::Execute(Event ev float distToPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); - if (distToPosition > 3.0f) + if (distToPosition > 2.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); @@ -1005,7 +1005,7 @@ bool FathomLordKarathressSecondAssistTankPositionSharkkisAction::Execute(Event e MarkTargetWithStar(bot, sharkkis); SetRtiTarget(botAI, "star", sharkkis); - if (bot->GetVictim() != sharkkis) + if (bot->GetTarget() != sharkkis->GetGUID()) return Attack(sharkkis); if (sharkkis->GetVictim() == bot && bot->IsWithinMeleeRange(sharkkis)) @@ -1014,7 +1014,7 @@ bool FathomLordKarathressSecondAssistTankPositionSharkkisAction::Execute(Event e float distToPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); - if (distToPosition > 3.0f) + if (distToPosition > 2.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); @@ -1040,7 +1040,7 @@ bool FathomLordKarathressThirdAssistTankPositionTidalvessAction::Execute(Event e MarkTargetWithCircle(bot, tidalvess); SetRtiTarget(botAI, "circle", tidalvess); - if (bot->GetVictim() != tidalvess) + if (bot->GetTarget() != tidalvess->GetGUID()) return Attack(tidalvess); if (tidalvess->GetVictim() == bot && bot->IsWithinMeleeRange(tidalvess)) @@ -1049,7 +1049,7 @@ bool FathomLordKarathressThirdAssistTankPositionTidalvessAction::Execute(Event e float distToPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); - if (distToPosition > 3.0f) + if (distToPosition > 2.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); @@ -1335,7 +1335,7 @@ bool MorogrimTidewalkerMoveBossToTankPositionAction::Execute(Event event) if (!tidewalker) return false; - if (bot->GetVictim() != tidewalker) + if (bot->GetTarget() != tidewalker->GetGUID()) return Attack(tidewalker); if (tidewalker->GetVictim() == bot && bot->IsWithinMeleeRange(tidewalker)) @@ -1504,7 +1504,7 @@ bool LadyVashjMainTankPositionBossAction::Execute(Event event) if (!vashj) return false; - if (bot->GetVictim() != vashj) + if (bot->GetTarget() != vashj->GetGUID()) return Attack(vashj); if (vashj->GetVictim() == bot && bot->IsWithinMeleeRange(vashj)) @@ -1619,7 +1619,6 @@ bool LadyVashjPhase1SpreadRangedInArcAction::Execute(Event event) } // For absorbing Shock Burst -// For some reason, if you use an Enhancement Shaman for this method, they will not dps bool LadyVashjSetGroundingTotemInMainTankGroupAction::Execute(Event event) { Player* mainTank = nullptr; @@ -1645,8 +1644,8 @@ bool LadyVashjSetGroundingTotemInMainTankGroupAction::Execute(Event event) mainTank->GetPositionZ(), 20.0f, MovementPriority::MOVEMENT_COMBAT); } - if (!botAI->HasStrategy("grounding totem", BotState::BOT_STATE_COMBAT)) - botAI->ChangeStrategy("+grounding totem", BotState::BOT_STATE_COMBAT); + if (!botAI->HasStrategy("grounding", BotState::BOT_STATE_COMBAT)) + botAI->ChangeStrategy("+grounding", BotState::BOT_STATE_COMBAT); if (!bot->HasAura(SPELL_GROUNDING_TOTEM_EFFECT) && botAI->CanCastSpell("grounding totem", bot)) @@ -1847,6 +1846,7 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event event) { targets = { enchanted, strider, elite, vashj }; // Prior iteration: ranged, other than Priests and Warlocks, prioritize Toxic Sporebats + // Shelved for now because it causes bots to walk in midair to reach their targets /* if (bot->getClass() == CLASS_PRIEST || bot->getClass() == CLASS_WARLOCK) targets = { enchanted, strider, elite, vashj }; else @@ -1990,7 +1990,7 @@ bool LadyVashjTankAttackAndMoveAwayStriderAction::Execute(Event event) if (!bot->HasAura(SPELL_FEAR_WARD)) bot->AddAura(SPELL_FEAR_WARD, bot); - if (botAI->IsAssistTankOfIndex(bot, 0) && bot->GetVictim() != strider) + if (botAI->IsAssistTankOfIndex(bot, 0) && bot->GetTarget() != strider->GetGUID()) return Attack(strider); if (strider->GetVictim() == bot) @@ -2057,7 +2057,7 @@ bool LadyVashjTeleportToTaintedElementalAction::Execute(Event event) tainted->GetPositionZ(), tainted->GetOrientation()); } - if (bot->GetVictim() != tainted) + if (bot->GetTarget() != tainted->GetGUID()) { MarkTargetWithStar(bot, tainted); SetRtiTarget(botAI, "star", tainted); @@ -2803,11 +2803,11 @@ Position LadyVashjAvoidToxicSporesAction::FindSafestNearbyPosition( bool LadyVashjAvoidToxicSporesAction::IsPathSafeFromSpores(const Position& start, const Position& end, const std::vector& spores, float hazardRadius) { - const int numChecks = 10; + const uint8 numChecks = 10; float dx = end.GetPositionX() - start.GetPositionX(); float dy = end.GetPositionY() - start.GetPositionY(); - for (int i = 1; i <= numChecks; ++i) + for (uint8 i = 1; i <= numChecks; ++i) { float ratio = static_cast(i) / numChecks; float checkX = start.GetPositionX() + dx * ratio; diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h index 6bb85a39e3..803a0bd4cf 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h @@ -42,7 +42,7 @@ namespace SerpentShrineCavernHelpers SPELL_INSIDIOUS_WHISPER = 37676, // Lady Vashj - SPELL_FEAR_WARD = 6346, + SPELL_FEAR_WARD = 6346, SPELL_POISON_BOLT = 38253, SPELL_STATIC_CHARGE = 38280, SPELL_ENTANGLE = 38316, @@ -54,7 +54,7 @@ namespace SerpentShrineCavernHelpers SPELL_SLOW = 31589, // Shaman - SPELL_GROUNDING_TOTEM_EFFECT = 8178, + SPELL_GROUNDING_TOTEM_EFFECT = 8178, SPELL_FROST_SHOCK = 25464, // Warlock diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp index 4f89d6affb..1c97f75288 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp @@ -3,6 +3,8 @@ #include "RaidSSCHelpers.h" #include "ChooseTargetActions.h" #include "DestroyItemAction.h" +#include "DKActions.h" +#include "DruidBearActions.h" #include "FollowActions.h" #include "GenericSpellActions.h" #include "HunterActions.h" @@ -14,6 +16,7 @@ #include "RogueActions.h" #include "ShamanActions.h" #include "WarlockActions.h" +#include "WarriorActions.h" #include "WipeAction.h" using namespace SerpentShrineCavernHelpers; @@ -414,7 +417,20 @@ float FathomLordKarathressDisableTankActionsMultiplier::GetValue(Action* action) return 1.0f; if ((bot->GetVictim() != nullptr && dynamic_cast(action)) || - dynamic_cast(action)) + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) return 0.0f; return 1.0f; @@ -460,7 +476,7 @@ float FathomLordKarathressWaitForDpsMultiplier::GetValue(Action* action) return 1.0f; const time_t now = std::time(nullptr); - const uint8 dpsWaitSeconds = 10; + const uint8 dpsWaitSeconds = 12; auto it = karathressDpsWaitTimer.find(karathress->GetMap()->GetInstanceId()); if (it == karathressDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds) From 659c1365ea80552310a1acc300e156059749ace9 Mon Sep 17 00:00:00 2001 From: crow Date: Sun, 28 Dec 2025 01:21:02 -0600 Subject: [PATCH 09/25] Improve Lurker + Leo & Vashj tweaks --- .../serpentshrinecavern/RaidSSCActions.cpp | 169 ++++++++---------- .../serpentshrinecavern/RaidSSCHelpers.cpp | 2 +- .../serpentshrinecavern/RaidSSCHelpers.h | 2 +- .../RaidSSCMultipliers.cpp | 30 ++-- .../serpentshrinecavern/RaidSSCTriggers.cpp | 8 +- 5 files changed, 95 insertions(+), 116 deletions(-) diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp index 1f3db92e37..e450f56615 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp @@ -73,8 +73,7 @@ bool UnderbogColossusEscapeToxicPoolAction::Execute(Event event) moveY = dynObj->GetPositionY() + (dy * invDist) * safeDist; } - bot->AttackStop(); - bot->InterruptNonMeleeSpells(true); + botAI->Reset(); return MoveTo(SSC_MAP_ID, moveX, moveY, bot->GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); } @@ -150,6 +149,11 @@ bool HydrossTheUnstablePositionFrostTankAction::Execute(Event event) return MoveTo(SSC_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT, true, true); } + else + { + botAI->Reset(); + return true; + } } } @@ -229,8 +233,7 @@ bool HydrossTheUnstablePositionNatureTankAction::Execute(Event event) } else { - bot->AttackStop(); - bot->InterruptNonMeleeSpells(true); + botAI->Reset(); return true; } } @@ -416,8 +419,7 @@ bool HydrossTheUnstableStopDpsUponPhaseChangeAction::Execute(Event event) if (shouldStopDps) { - bot->AttackStop(); - bot->InterruptNonMeleeSpells(true); + botAI->Reset(); return true; } @@ -482,7 +484,7 @@ bool TheLurkerBelowRunAroundBehindBossAction::Execute(Event event) if (bot->GetExactDist2d(targetX, targetY) > 1.0f) { - bot->InterruptNonMeleeSpells(true); + botAI->Reset(); return MoveTo(SSC_MAP_ID, targetX, targetY, lurker->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_FORCED, true, false); } @@ -682,17 +684,26 @@ bool LeotherasTheBlindTargetSpellbindersAction::Execute(Event event) } // Warlock tank action--see GetLeotherasDemonFormTank in RaidSSCHelpers.cpp +// Use tank strategy for Demon Form and DPS strategy for Human Form bool LeotherasTheBlindDemonFormTankAttackBossAction::Execute(Event event) { Unit* leotherasDemon = GetActiveLeotherasDemon(botAI); - if (!leotherasDemon) - return false; + if (leotherasDemon) + { + if (!botAI->HasStrategy("tank", BotState::BOT_STATE_COMBAT)) + botAI->ChangeStrategy("+tank", BotState::BOT_STATE_COMBAT); - MarkTargetWithSquare(bot, leotherasDemon); - SetRtiTarget(botAI, "square", leotherasDemon); + MarkTargetWithSquare(bot, leotherasDemon); + SetRtiTarget(botAI, "square", leotherasDemon); - if (bot->GetTarget() != leotherasDemon->GetGUID()) - return Attack(leotherasDemon); + if (bot->GetTarget() != leotherasDemon->GetGUID()) + return Attack(leotherasDemon); + } + else if (Unit* leotherasHuman = GetLeotherasHuman(botAI)) + { + if (botAI->HasStrategy("tank", BotState::BOT_STATE_COMBAT)) + botAI->ChangeStrategy("-tank", BotState::BOT_STATE_COMBAT); + } return false; } @@ -749,8 +760,7 @@ bool LeotherasTheBlindRunAwayFromWhirlwindAction::Execute(Event event) const float safeDistance = 20.0f; if (currentDistance < safeDistance) { - bot->AttackStop(); - bot->InterruptNonMeleeSpells(true); + botAI->Reset(); return MoveAway(leotherasHuman, safeDistance - currentDistance + 5.0f); } } @@ -763,9 +773,8 @@ bool LeotherasTheBlindRunAwayFromWhirlwindAction::Execute(Event event) bool LeotherasTheBlindMeleeDpsRunAwayFromBossAction::Execute(Event event) { Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI); - /* Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); */ - if (!leotherasPhase2Demon /* || demonFormTank != nullptr */) + if (!leotherasPhase2Demon) return false; Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST); @@ -779,8 +788,7 @@ bool LeotherasTheBlindMeleeDpsRunAwayFromBossAction::Execute(Event event) const float safeDistance = 10.0f; if (currentDistance < safeDistance) { - bot->AttackStop(); - bot->InterruptNonMeleeSpells(true); + botAI->Reset(); return MoveAway(demonVictim, safeDistance - currentDistance + 1.0f); } } @@ -827,29 +835,28 @@ bool LeotherasTheBlindFinalPhaseAssignDpsPriorityAction::Execute(Event event) if (!leotherasHuman) return false; - Unit* leotherasDemon = GetPhase3LeotherasDemon(botAI); - if (!leotherasDemon) - return false; - MarkTargetWithStar(bot, leotherasHuman); SetRtiTarget(botAI, "star", leotherasHuman); if (bot->GetTarget() != leotherasHuman->GetGUID()) return Attack(leotherasHuman); - if (botAI->IsTank(bot) && leotherasHuman->GetVictim() == bot && - bot->IsWithinMeleeRange(leotherasHuman)) + Unit* leotherasDemon = GetPhase3LeotherasDemon(botAI); + if (leotherasDemon) { - Unit* demonTarget = leotherasDemon->GetVictim(); - if (demonTarget && leotherasHuman->GetExactDist2d(demonTarget) < 20.0f) + if (leotherasHuman->GetVictim() == bot) { - float angle = atan2(bot->GetPositionY() - demonTarget->GetPositionY(), - bot->GetPositionX() - demonTarget->GetPositionX()); - float targetX = bot->GetPositionX() + 21.0f * std::cos(angle); - float targetY = bot->GetPositionY() + 21.0f * std::sin(angle); + Unit* demonTarget = leotherasDemon->GetVictim(); + if (demonTarget && leotherasHuman->GetExactDist2d(demonTarget) < 20.0f) + { + float angle = atan2(bot->GetPositionY() - demonTarget->GetPositionY(), + bot->GetPositionX() - demonTarget->GetPositionX()); + float targetX = bot->GetPositionX() + 25.0f * std::cos(angle); + float targetY = bot->GetPositionY() + 25.0f * std::sin(angle); - return MoveTo(SSC_MAP_ID, targetX, targetY, bot->GetPositionZ(), false, false, - false, false, MovementPriority::MOVEMENT_FORCED, true, false); + return MoveTo(SSC_MAP_ID, targetX, targetY, bot->GetPositionZ(), false, false, + false, false, MovementPriority::MOVEMENT_FORCED, true, false); + } } } @@ -943,7 +950,7 @@ bool FathomLordKarathressMainTankPositionBossAction::Execute(Event event) float distToPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); - if (distToPosition > 2.0f) + if (distToPosition > 3.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); @@ -979,7 +986,7 @@ bool FathomLordKarathressFirstAssistTankPositionCaribdisAction::Execute(Event ev float distToPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); - if (distToPosition > 2.0f) + if (distToPosition > 3.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); @@ -1014,7 +1021,7 @@ bool FathomLordKarathressSecondAssistTankPositionSharkkisAction::Execute(Event e float distToPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); - if (distToPosition > 2.0f) + if (distToPosition > 3.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); @@ -1049,7 +1056,7 @@ bool FathomLordKarathressThirdAssistTankPositionTidalvessAction::Execute(Event e float distToPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); - if (distToPosition > 2.0f) + if (distToPosition > 3.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); @@ -1077,7 +1084,7 @@ bool FathomLordKarathressPositionCaribdisTankHealerAction::Execute(Event event) float distToPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); - if (distToPosition > 2.0f) + if (distToPosition > 3.0f) { float dX = position.GetPositionX() - bot->GetPositionX(); float dY = position.GetPositionY() - bot->GetPositionY(); @@ -1710,7 +1717,7 @@ bool LadyVashjStaticChargeMoveAwayFromGroupAction::Execute(Event event) float currentDistance = bot->GetExactDist2d(mainTank); const float safeDistance = 10.0f; if (currentDistance < safeDistance) - return MoveFromGroup(safeDistance + 0.5f); + return MoveAway(mainTank, safeDistance - currentDistance + 0.5f); } // If any other bot has Static Charge, it should move away from other group members @@ -1734,6 +1741,9 @@ bool LadyVashjStaticChargeMoveAwayFromGroupAction::Execute(Event event) bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event event) { + if (botAI->IsHeal(bot)) + return false; + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); if (!vashj) return false; @@ -1741,11 +1751,10 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event event) auto const& attackers = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); Unit* target = nullptr; - Unit* tainted = nullptr; Unit* enchanted = nullptr; Unit* elite = nullptr; Unit* strider = nullptr; - // Unit* sporebat = nullptr; + Unit* sporebat = nullptr; // Search and attack radius are intended to keep bots on the platform (not go down the stairs) const Position& center = VASHJ_PLATFORM_CENTER_POSITION; @@ -1765,11 +1774,6 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event event) switch (unit->GetEntry()) { - case NPC_TAINTED_ELEMENTAL: - if (!tainted) - tainted = unit; - break; - case NPC_ENCHANTED_ELEMENTAL: if (!enchanted || vashj->GetExactDist2d(unit) < vashj->GetExactDist2d(enchanted)) enchanted = unit; @@ -1785,10 +1789,10 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event event) strider = unit; break; - /* case NPC_TOXIC_SPOREBAT: - if (!sporebat || unit->GetHealthPct() < sporebat->GetHealthPct()) + case NPC_TOXIC_SPOREBAT: + if (!sporebat || bot->GetExactDist2d(unit) < bot->GetExactDist2d(sporebat)) sporebat = unit; - break; */ + break; case NPC_LADY_VASHJ: vashj = unit; @@ -1807,12 +1811,12 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event event) // Hunters and Mages prioritize Enchanted Elementals, // while other ranged DPS prioritize Striders if (bot->getClass() == CLASS_HUNTER || bot->getClass() == CLASS_MAGE) - targets = { tainted, enchanted, strider, elite }; + targets = { enchanted, strider, elite }; else - targets = { tainted, strider, elite, enchanted }; + targets = { strider, elite, enchanted }; } else if (botAI->IsMelee(bot) && botAI->IsDps(bot)) - targets = { tainted, enchanted, elite }; + targets = { enchanted, elite }; else if (botAI->IsTank(bot)) { if (botAI->HasCheat(BotCheatMask::raid) && botAI->IsAssistTankOfIndex(bot, 0)) @@ -1844,13 +1848,11 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event event) } else if (botAI->IsRanged(bot)) { - targets = { enchanted, strider, elite, vashj }; - // Prior iteration: ranged, other than Priests and Warlocks, prioritize Toxic Sporebats - // Shelved for now because it causes bots to walk in midair to reach their targets - /* if (bot->getClass() == CLASS_PRIEST || bot->getClass() == CLASS_WARLOCK) - targets = { enchanted, strider, elite, vashj }; + // targets = { enchanted, strider, elite, vashj }; + if (bot->getClass() == CLASS_HUNTER) + targets = { sporebat, enchanted, strider, elite, vashj }; else - targets = { enchanted, sporebat, strider, elite, vashj }; */ + targets = { enchanted, strider, elite, vashj }; } else if (botAI->IsMelee(bot) && botAI->IsDps(bot)) targets = { enchanted, elite, vashj }; @@ -1867,27 +1869,12 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event event) } } - if (bot->GetVictim() == vashj) + if (bot->GetVictim() == vashj && IsLadyVashjInPhase2(botAI)) { - if (IsLadyVashjInPhase2(botAI)) - { - bot->AttackStop(); - bot->InterruptNonMeleeSpells(true); - bot->SetTarget(ObjectGuid::Empty); - bot->SetSelection(ObjectGuid()); - } - else if (IsLadyVashjInPhase3(botAI) && !botAI->IsMainTank(bot)) - { - if ((enchanted && enchanted->IsAlive()) || - (elite && elite->IsAlive()) || - (strider && strider->IsAlive())) - { - bot->AttackStop(); - bot->InterruptNonMeleeSpells(true); - bot->SetTarget(ObjectGuid::Empty); - bot->SetSelection(ObjectGuid()); - } - } + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + bot->SetTarget(ObjectGuid::Empty); + bot->SetSelection(ObjectGuid()); } Unit* currentTarget = context->GetValue("current target")->Get(); @@ -1896,12 +1883,9 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event event) if (target && bot->GetExactDist2d(target) <= maxPursueRange && bot->GetTarget() != target->GetGUID()) - { return Attack(target); - } - if (currentTarget && (!currentTarget->IsAlive() || - !IsValidLadyVashjCombatNpc(currentTarget, botAI))) + if (currentTarget && !IsValidLadyVashjCombatNpc(currentTarget, botAI)) { context->GetValue("current target")->Set(nullptr); bot->SetTarget(ObjectGuid::Empty); @@ -1909,21 +1893,22 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event event) } // If bots have wandered too far from the center and are not attacking anything, move them back - if (!bot->GetVictim()) + if (bot->GetVictim() == nullptr) { Player* designatedLooter = GetDesignatedCoreLooter(bot->GetGroup(), botAI); Player* firstCorePasser = GetFirstTaintedCorePasser(bot->GetGroup(), botAI); // A bot will not move back to the middle if (1) there is a Tainted Elemental, and // (2) the bot is either the designated looter or the first core passer - if (tainted && ((designatedLooter && designatedLooter == bot) || - (firstCorePasser && firstCorePasser == bot))) - return false; + if (Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental")) + { + if ((designatedLooter && designatedLooter == bot) || + (firstCorePasser && firstCorePasser == bot)) + return false; + } const Position& center = VASHJ_PLATFORM_CENTER_POSITION; if (bot->GetExactDist2d(center.GetPositionX(), center.GetPositionY()) > 35.0f) { - bot->AttackStop(); - bot->InterruptNonMeleeSpells(true); return MoveInside(SSC_MAP_ID, center.GetPositionX(), center.GetPositionY(), center.GetPositionZ(), 30.0f, MovementPriority::MOVEMENT_COMBAT); } @@ -1999,7 +1984,7 @@ bool LadyVashjTankAttackAndMoveAwayStriderAction::Execute(Event event) const float safeDistance = 25.0f; if (currentDistance < safeDistance) - return MoveAway(vashj, safeDistance - currentDistance + 5.0f); + return MoveAway(vashj, safeDistance - currentDistance); } return false; @@ -2431,7 +2416,7 @@ bool LadyVashjPassTheTaintedCoreAction::LineUpSecondCorePasser( intendedLineup.insert_or_assign(bot->GetGUID(), Position(targetX, targetY, targetZ)); bot->AttackStop(); - bot->InterruptNonMeleeSpells(false); + bot->InterruptNonMeleeSpells(true); return MoveTo(SSC_MAP_ID, targetX, targetY, targetZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); } @@ -2483,7 +2468,7 @@ bool LadyVashjPassTheTaintedCoreAction::LineUpThirdCorePasser( intendedLineup.insert_or_assign(bot->GetGUID(), Position(targetX, targetY, targetZ)); bot->AttackStop(); - bot->InterruptNonMeleeSpells(false); + bot->InterruptNonMeleeSpells(true); return MoveTo(SSC_MAP_ID, targetX, targetY, targetZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); @@ -2526,7 +2511,7 @@ bool LadyVashjPassTheTaintedCoreAction::LineUpFourthCorePasser( intendedLineup.insert_or_assign(bot->GetGUID(), Position(targetX, targetY, targetZ)); bot->AttackStop(); - bot->InterruptNonMeleeSpells(false); + bot->InterruptNonMeleeSpells(true); return MoveTo(SSC_MAP_ID, targetX, targetY, targetZ, false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); } diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp index beda48d0c1..82d8f22749 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp @@ -374,7 +374,7 @@ namespace SerpentShrineCavernHelpers { return entry == NPC_TAINTED_ELEMENTAL || entry == NPC_ENCHANTED_ELEMENTAL || entry == NPC_COILFANG_ELITE || entry == NPC_COILFANG_STRIDER || - /* entry == NPC_TOXIC_SPOREBAT || */ entry == NPC_LADY_VASHJ; + entry == NPC_TOXIC_SPOREBAT || entry == NPC_LADY_VASHJ; } return false; diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h index 803a0bd4cf..c76064c906 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h @@ -92,7 +92,7 @@ namespace SerpentShrineCavernHelpers NPC_TAINTED_ELEMENTAL = 22009, NPC_COILFANG_ELITE = 22055, NPC_COILFANG_STRIDER = 22056, - // NPC_TOXIC_SPOREBAT = 22140, + NPC_TOXIC_SPOREBAT = 22140, NPC_SPORE_DROP_TRIGGER = 22207, }; diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp index 1c97f75288..ddcfac4b9f 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp @@ -282,7 +282,7 @@ float LeotherasTheBlindDisableTankActionsMultiplier::GetValue(Action* action) if ((dynamic_cast(action) && !dynamic_cast(action)) || dynamic_cast(action)) - return 0.0f; + return 0.0f; } return 1.0f; @@ -303,7 +303,8 @@ float LeotherasTheBlindMeleeDpsAvoidChaosBlastMultiplier::GetValue(Action* actio if (dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action)) + dynamic_cast(action) || + dynamic_cast(action)) return 0.0f; } @@ -342,7 +343,7 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) } } - const uint8 dpsWaitSecondsPhase2 = 10; + const uint8 dpsWaitSecondsPhase2 = 12; Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI); Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); if (leotherasPhase2Demon) @@ -417,6 +418,7 @@ float FathomLordKarathressDisableTankActionsMultiplier::GetValue(Action* action) return 1.0f; if ((bot->GetVictim() != nullptr && dynamic_cast(action)) || + dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || @@ -484,7 +486,7 @@ float FathomLordKarathressWaitForDpsMultiplier::GetValue(Action* action) if (dynamic_cast(action) || (dynamic_cast(action) && !dynamic_cast(action))) - return 0.0f; + return 0.0f; } return 1.0f; @@ -496,7 +498,7 @@ float FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier::GetValue return 1.0f; Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); - if (caribdis && caribdis->IsAlive()) + if (caribdis) { if (dynamic_cast(action) || dynamic_cast(action)) @@ -695,7 +697,8 @@ float LadyVashjCorePassersPrioritizePositioningMultiplier::GetValue(Action* acti if (AnyRecentCoreInInventory(group, botAI)) { - if (!dynamic_cast(action)) + if (dynamic_cast(action) && + !dynamic_cast(action)) return 0.0f; } @@ -740,21 +743,16 @@ float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *ac dynamic_cast(action)) return 0.0f; - Unit* strider = AI_VALUE2(Unit*, "find target", "coilfang strider"); - Unit* elite = AI_VALUE2(Unit*, "find target", "coilfang elite"); Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental"); - - if (enchanted && enchanted->IsAlive()) + if (enchanted && enchanted->IsAlive() && bot->GetVictim() == enchanted) { - if (bot->GetVictim() == enchanted) - { - if (dynamic_cast(action)) + if (dynamic_cast(action)) return 0.0f; - } } - if ((!enchanted || !enchanted->IsAlive()) && (!strider || !strider->IsAlive()) && - (!elite || !elite->IsAlive())) + Unit* strider = AI_VALUE2(Unit*, "find target", "coilfang strider"); + Unit* elite = AI_VALUE2(Unit*, "find target", "coilfang elite"); + if (!strider && !elite) { if (dynamic_cast(action)) return 0.0f; diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp index d682994db7..b69bf9a487 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp @@ -206,8 +206,8 @@ bool LeotherasTheBlindBossTransformedIntoDemonFormTrigger::IsActive() if (!demonFormTank || bot != demonFormTank) return false; - Unit* leotherasDemon = GetActiveLeotherasDemon(botAI); - return leotherasDemon != nullptr; + Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); + return leotheras != nullptr; } bool LeotherasTheBlindBossEngagedByRangedTrigger::IsActive() @@ -236,10 +236,6 @@ bool LeotherasTheBlindBossChannelingWhirlwindTrigger::IsActive() bool LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger::IsActive() { - Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); - if (!demonFormTank) - return false; - if (!botAI->IsMelee(bot) && !botAI->IsDps(bot)) return false; From f6c7e3160cd5ac6f572729560ff6a963250587c7 Mon Sep 17 00:00:00 2001 From: crow Date: Fri, 2 Jan 2026 02:13:23 -0600 Subject: [PATCH 10/25] minor tweaks --- .../serpentshrinecavern/RaidSSCActions.cpp | 50 ++---- .../RaidSSCMultipliers.cpp | 92 +++++----- .../serpentshrinecavern/RaidSSCStrategy.cpp | 13 ++ .../serpentshrinecavern/RaidSSCTriggers.cpp | 163 +++++++----------- 4 files changed, 137 insertions(+), 181 deletions(-) diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp index e450f56615..ba813cd6ff 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp @@ -80,8 +80,7 @@ bool UnderbogColossusEscapeToxicPoolAction::Execute(Event event) bool GreyheartTidecallerMarkWaterElementalTotemAction::Execute(Event event) { - Unit* totem = GetFirstAliveUnitByEntry(botAI, NPC_WATER_ELEMENTAL_TOTEM); - if (totem) + if (Unit* totem = GetFirstAliveUnitByEntry(botAI, NPC_WATER_ELEMENTAL_TOTEM)) MarkTargetWithSkull(bot, totem); return false; @@ -283,8 +282,7 @@ bool HydrossTheUnstablePrioritizeElementalAddsAction::Execute(Event event) // To mitigate the effect of Water Tomb bool HydrossTheUnstableFrostPhaseSpreadOutAction::Execute(Event event) { - Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); - if (!hydross) + if (!AI_VALUE2(Unit*, "find target", "hydross the unstable")) return false; if (Group* group = bot->GetGroup()) @@ -676,8 +674,7 @@ bool TheLurkerBelowManageSpoutTimerAction::Execute(Event event) bool LeotherasTheBlindTargetSpellbindersAction::Execute(Event event) { - Unit* spellbinder = GetFirstAliveUnitByEntry(botAI, NPC_GREYHEART_SPELLBINDER); - if (spellbinder && spellbinder->IsInCombat()) + if (Unit* spellbinder = GetFirstAliveUnitByEntry(botAI, NPC_GREYHEART_SPELLBINDER)) MarkTargetWithSkull(bot, spellbinder); return false; @@ -699,7 +696,7 @@ bool LeotherasTheBlindDemonFormTankAttackBossAction::Execute(Event event) if (bot->GetTarget() != leotherasDemon->GetGUID()) return Attack(leotherasDemon); } - else if (Unit* leotherasHuman = GetLeotherasHuman(botAI)) + else if (!GetLeotherasHuman(botAI)) { if (botAI->HasStrategy("tank", BotState::BOT_STATE_COMBAT)) botAI->ChangeStrategy("-tank", BotState::BOT_STATE_COMBAT); @@ -719,8 +716,7 @@ bool LeotherasTheBlindPositionRangedAction::Execute(Event event) leotherasHuman->GetVictim() != bot) return FleePosition(leotherasHuman->GetPosition(), 12.0f, minInterval); - Unit* leotherasDemon = GetActiveLeotherasDemon(botAI); - if (!leotherasDemon) + if (!GetActiveLeotherasDemon(botAI)) return false; if (Group* group = bot->GetGroup()) @@ -731,8 +727,7 @@ bool LeotherasTheBlindPositionRangedAction::Execute(Event event) if (!member || member == bot || !member->IsAlive()) continue; - Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); - if (demonFormTank && demonFormTank == member) + if (GetLeotherasDemonFormTank(botAI, bot) == member) { if (bot->GetExactDist2d(member) < 10.0f) return FleePosition(member->GetPosition(), 12.0f, minInterval); @@ -747,12 +742,6 @@ bool LeotherasTheBlindPositionRangedAction::Execute(Event event) bool LeotherasTheBlindRunAwayFromWhirlwindAction::Execute(Event event) { - Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI); - Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); - - if (leotherasPhase3Demon && demonFormTank && demonFormTank == bot) - return false; - Unit* leotherasHuman = GetLeotherasHuman(botAI); if (leotherasHuman) { @@ -773,7 +762,6 @@ bool LeotherasTheBlindRunAwayFromWhirlwindAction::Execute(Event event) bool LeotherasTheBlindMeleeDpsRunAwayFromBossAction::Execute(Event event) { Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI); - if (!leotherasPhase2Demon) return false; @@ -1198,6 +1186,14 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) if (bot->GetTarget() != totem->GetGUID()) return Attack(totem); + // Direct movement order due to path between Sharkkis and totem sometimes being screwy + if (!bot->IsWithinMeleeRange(totem)) + { + return MoveTo(SSC_MAP_ID, totem->GetPositionX(), totem->GetPositionY(), + totem->GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + return false; } @@ -1790,7 +1786,7 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event event) break; case NPC_TOXIC_SPOREBAT: - if (!sporebat || bot->GetExactDist2d(unit) < bot->GetExactDist2d(sporebat)) + if (!sporebat || bot->GetDistance(unit) < bot->GetDistance(sporebat)) sporebat = unit; break; @@ -2704,18 +2700,10 @@ bool LadyVashjAvoidToxicSporesAction::Execute(Event event) Position safestPos = FindSafestNearbyPosition(spores, vashjCenter, maxRadius, hazardRadius); Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - if (vashj && vashj->GetVictim() == bot) - { - return MoveTo(SSC_MAP_ID, safestPos.GetPositionX(), safestPos.GetPositionY(), - safestPos.GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_COMBAT, true, true); - } - else - { - return MoveTo(SSC_MAP_ID, safestPos.GetPositionX(), safestPos.GetPositionY(), - safestPos.GetPositionZ(), false, false, false, true, - MovementPriority::MOVEMENT_COMBAT, true, false); - } + bool backwards = (vashj && vashj->GetVictim() == bot); + return MoveTo(SSC_MAP_ID, safestPos.GetPositionX(), safestPos.GetPositionY(), + safestPos.GetPositionZ(), false, false, false, true, + MovementPriority::MOVEMENT_COMBAT, true, backwards); } Position LadyVashjAvoidToxicSporesAction::FindSafestNearbyPosition( diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp index ddcfac4b9f..ced439e05f 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp @@ -144,8 +144,7 @@ float HydrossTheUnstableControlMisdirectionMultiplier::GetValue(Action* action) if (bot->getClass() != CLASS_HUNTER) return 1.0f; - Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); - if (hydross) + if (AI_VALUE2(Unit*, "find target", "hydross the unstable")) { if (dynamic_cast(action)) return 0.0f; @@ -186,8 +185,7 @@ float TheLurkerBelowMaintainRangedSpreadMultiplier::GetValue(Action* action) if (!botAI->IsRanged(bot)) return 1.0f; - Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); - if (lurker) + if (AI_VALUE2(Unit*, "find target", "the lurker below")) { if (dynamic_cast(action) || dynamic_cast(action) || @@ -226,7 +224,7 @@ float TheLurkerBelowDisableTankAssistMultiplier::GetValue(Action* action) if (tankCount >= 3) { - if (bot->GetVictim() != nullptr && dynamic_cast(action)) + if (!bot->GetVictim() && dynamic_cast(action)) return 0.0f; } @@ -251,7 +249,8 @@ float LeotherasTheBlindAvoidWhirlwindMultiplier::GetValue(Action* action) if (dynamic_cast(action)) return 0.0f; - if (dynamic_cast(action) && !dynamic_cast(action) && + if (dynamic_cast(action) && + !dynamic_cast(action) && !dynamic_cast(action)) return 0.0f; } @@ -262,8 +261,7 @@ float LeotherasTheBlindAvoidWhirlwindMultiplier::GetValue(Action* action) // Applies only if there is a Warlock tank float LeotherasTheBlindDisableTankActionsMultiplier::GetValue(Action* action) { - Unit* leotherasDemon = GetActiveLeotherasDemon(botAI); - if (!leotherasDemon) + if (!GetActiveLeotherasDemon(botAI)) return 1.0f; Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); @@ -276,8 +274,7 @@ float LeotherasTheBlindDisableTankActionsMultiplier::GetValue(Action* action) return 0.0f; // (2) Phase 2 only: Tanks other than the Warlock tank should do absolutely nothing - Unit* leotherasDemonPhase2 = GetPhase2LeotherasDemon(botAI); - if (botAI->IsTank(bot) && bot != demonFormTank && leotherasDemonPhase2) + if (botAI->IsTank(bot) && bot != demonFormTank && GetPhase2LeotherasDemon(botAI)) { if ((dynamic_cast(action) && !dynamic_cast(action)) || @@ -293,8 +290,7 @@ float LeotherasTheBlindMeleeDpsAvoidChaosBlastMultiplier::GetValue(Action* actio if (!botAI->IsMelee(bot) || botAI->IsTank(bot)) return 1.0f; - Unit* leotherasDemonPhase2 = GetPhase2LeotherasDemon(botAI); - if (!leotherasDemonPhase2) + if (!GetPhase2LeotherasDemon(botAI)) return 1.0f; Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST); @@ -339,7 +335,7 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) if (dynamic_cast(action) || (dynamic_cast(action) && !dynamic_cast(action))) - return 0.0f; + return 0.0f; } } @@ -361,7 +357,7 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) if (dynamic_cast(action) || (dynamic_cast(action) && !dynamic_cast(action))) - return 0.0f; + return 0.0f; } } @@ -378,7 +374,7 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) if (dynamic_cast(action) || (dynamic_cast(action) && !dynamic_cast(action))) - return 0.0f; + return 0.0f; } } @@ -391,12 +387,10 @@ float LeotherasTheBlindDelayBloodlustAndHeroismMultiplier::GetValue(Action* acti if (bot->getClass() != CLASS_SHAMAN) return 1.0f; - Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); - if (!leotheras) + if (!AI_VALUE2(Unit*, "find target", "leotheras the blind")) return 1.0f; - Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI); - if (!leotherasPhase3Demon) + if (!GetPhase3LeotherasDemon(botAI)) { if (dynamic_cast(action) || dynamic_cast(action)) @@ -413,8 +407,7 @@ float FathomLordKarathressDisableTankActionsMultiplier::GetValue(Action* action) if (!botAI->IsTank(bot)) return 1.0f; - Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); - if (!karathress) + if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) return 1.0f; if ((bot->GetVictim() != nullptr && dynamic_cast(action)) || @@ -440,11 +433,16 @@ float FathomLordKarathressDisableTankActionsMultiplier::GetValue(Action* action) float FathomLordKarathressDisableAoeMultiplier::GetValue(Action* action) { - Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); - if (karathress) + if (!botAI->IsDps(bot)) + return 1.0f; + + if (AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) { - if (dynamic_cast(action)) - return 0.0f; + if (auto castSpellAction = dynamic_cast(action)) + { + if (castSpellAction->getThreatType() == Action::ActionThreatType::Aoe) + return 0.0f; + } } return 1.0f; @@ -455,8 +453,7 @@ float FathomLordKarathressControlMisdirectionMultiplier::GetValue(Action* action if (bot->getClass() != CLASS_HUNTER) return 1.0f; - Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); - if (karathress) + if (AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) { if (dynamic_cast(action)) return 0.0f; @@ -497,8 +494,7 @@ float FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier::GetValue if (!botAI->IsHealAssistantOfIndex(bot, 0)) return 1.0f; - Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); - if (caribdis) + if (AI_VALUE2(Unit*, "find target", "fathom-guard caribdis")) { if (dynamic_cast(action) || dynamic_cast(action)) @@ -514,12 +510,10 @@ float MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier::GetValue(Action* act if (bot->getClass() != CLASS_SHAMAN) return 1.0f; - Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); - if (!tidewalker) + if (!AI_VALUE2(Unit*, "find target", "morogrim tidewalker")) return 1.0f; - Unit* murloc = AI_VALUE2(Unit*, "find target", "tidewalker lurker"); - if (!murloc) + if (!AI_VALUE2(Unit*, "find target", "tidewalker lurker")) { if (dynamic_cast(action) || dynamic_cast(action)) @@ -534,8 +528,7 @@ float MorogrimTidewalkerDisableTankActionsMultiplier::GetValue(Action* action) if (!botAI->IsMainTank(bot)) return 1.0f; - Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); - if (tidewalker) + if (AI_VALUE2(Unit*, "find target", "morogrim tidewalker")) { if (dynamic_cast(action)) return 0.0f; @@ -571,8 +564,8 @@ float LadyVashjDelayBloodlustAndHeroismMultiplier::GetValue(Action* action) if (bot->getClass() != CLASS_SHAMAN) return 1.0f; - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - if (!vashj || IsLadyVashjInPhase3(botAI)) + if (!AI_VALUE2(Unit*, "find target", "lady vashj") || + IsLadyVashjInPhase3(botAI)) return 1.0f; if (dynamic_cast(action) || @@ -587,8 +580,8 @@ float LadyVashjMaintainPhase1RangedSpreadMultiplier::GetValue(Action* action) if (!botAI->IsRanged(bot)) return 1.0f; - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - if (vashj && IsLadyVashjInPhase1(botAI)) + if (AI_VALUE2(Unit*, "find target", "lady vashj") && + IsLadyVashjInPhase1(botAI)) { if (dynamic_cast(action) || dynamic_cast(action) || @@ -605,8 +598,7 @@ float LadyVashjStaticChargeStayAwayFromGroupMultiplier::GetValue(Action* action) if (botAI->IsMainTank(bot) || !bot->HasAura(SPELL_STATIC_CHARGE)) return 1.0f; - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - if (!vashj) + if (!AI_VALUE2(Unit*, "find target", "lady vashj")) return 1.0f; if (dynamic_cast(action) || @@ -627,8 +619,7 @@ float LadyVashjDoNotLootTheTaintedCoreMultiplier::GetValue(Action* action) if (!botAI->HasCheat(BotCheatMask::raid)) return 1.0f; - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - if (vashj) + if (AI_VALUE2(Unit*, "find target", "lady vashj")) { if (dynamic_cast(action)) return 0.0f; @@ -639,8 +630,8 @@ float LadyVashjDoNotLootTheTaintedCoreMultiplier::GetValue(Action* action) float LadyVashjCorePassersPrioritizePositioningMultiplier::GetValue(Action* action) { - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - if (!vashj || !IsLadyVashjInPhase2(botAI)) + if (!AI_VALUE2(Unit*, "find target", "lady vashj") || + !IsLadyVashjInPhase2(botAI)) return 1.0f; if (dynamic_cast(action) || @@ -687,8 +678,8 @@ float LadyVashjCorePassersPrioritizePositioningMultiplier::GetValue(Action* acti else if (bot != fourthCorePasser) return 1.0f; - Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental"); - if (tainted && (bot == firstCorePasser || bot == secondCorePasser)) + if (AI_VALUE2(Unit*, "find target", "tainted elemental") && + (bot == firstCorePasser || bot == secondCorePasser)) { if (dynamic_cast(action) && !dynamic_cast(action)) @@ -709,8 +700,7 @@ float LadyVashjCorePassersPrioritizePositioningMultiplier::GetValue(Action* acti // So the standard target selection system must be disabled float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *action) { - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - if (!vashj) + if (!AI_VALUE2(Unit*, "find target", "lady vashj")) return 1.0f; if (dynamic_cast(action)) @@ -728,7 +718,7 @@ float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *ac return 0.0f; Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental"); - if (enchanted && enchanted->IsAlive() && bot->GetVictim() == enchanted) + if (enchanted && bot->GetVictim() == enchanted) { if (dynamic_cast(action)) return 0.0f; @@ -744,7 +734,7 @@ float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *ac return 0.0f; Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental"); - if (enchanted && enchanted->IsAlive() && bot->GetVictim() == enchanted) + if (enchanted && bot->GetVictim() == enchanted) { if (dynamic_cast(action)) return 0.0f; diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp index ee97fe02ae..b28be97bcb 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp @@ -166,26 +166,39 @@ void RaidSSCStrategy::InitTriggers(std::vector& triggers) void RaidSSCStrategy::InitMultipliers(std::vector& multipliers) { + // Trash Mobs multipliers.push_back(new UnderbogColossusEscapeToxicPoolMultiplier(botAI)); + + // Hydross the Unstable multipliers.push_back(new HydrossTheUnstableDisableTankActionsMultiplier(botAI)); multipliers.push_back(new HydrossTheUnstableWaitForDpsMultiplier(botAI)); multipliers.push_back(new HydrossTheUnstableControlMisdirectionMultiplier(botAI)); + + // The Lurker Below multipliers.push_back(new TheLurkerBelowStayAwayFromSpoutMultiplier(botAI)); multipliers.push_back(new TheLurkerBelowMaintainRangedSpreadMultiplier(botAI)); multipliers.push_back(new TheLurkerBelowDisableTankAssistMultiplier(botAI)); + + // Leotheras the Blind multipliers.push_back(new LeotherasTheBlindAvoidWhirlwindMultiplier(botAI)); multipliers.push_back(new LeotherasTheBlindDisableTankActionsMultiplier(botAI)); multipliers.push_back(new LeotherasTheBlindMeleeDpsAvoidChaosBlastMultiplier(botAI)); multipliers.push_back(new LeotherasTheBlindWaitForDpsMultiplier(botAI)); multipliers.push_back(new LeotherasTheBlindDelayBloodlustAndHeroismMultiplier(botAI)); + + // Fathom-Lord Karathress multipliers.push_back(new FathomLordKarathressDisableTankActionsMultiplier(botAI)); multipliers.push_back(new FathomLordKarathressDisableAoeMultiplier(botAI)); multipliers.push_back(new FathomLordKarathressControlMisdirectionMultiplier(botAI)); multipliers.push_back(new FathomLordKarathressWaitForDpsMultiplier(botAI)); multipliers.push_back(new FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier(botAI)); + + // Morogrim Tidewalker multipliers.push_back(new MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier(botAI)); multipliers.push_back(new MorogrimTidewalkerDisableTankActionsMultiplier(botAI)); multipliers.push_back(new MorogrimTidewalkerMaintainPhase2StackingMultiplier(botAI)); + + // Lady Vashj multipliers.push_back(new LadyVashjDelayBloodlustAndHeroismMultiplier(botAI)); multipliers.push_back(new LadyVashjMaintainPhase1RangedSpreadMultiplier(botAI)); multipliers.push_back(new LadyVashjStaticChargeStayAwayFromGroupMultiplier(botAI)); diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp index b69bf9a487..d0f5ba3a6e 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp @@ -21,8 +21,7 @@ bool GreyheartTidecallerWaterElementalTotemSpawnedTrigger::IsActive() if (!botAI->IsDps(bot)) return false; - Unit* totem = GetFirstAliveUnitByEntry(botAI, NPC_WATER_ELEMENTAL_TOTEM); - return totem != nullptr; + return GetFirstAliveUnitByEntry(botAI, NPC_WATER_ELEMENTAL_TOTEM); } // Hydross the Unstable @@ -32,8 +31,7 @@ bool HydrossTheUnstableBotIsFrostTankTrigger::IsActive() if (!botAI->IsMainTank(bot)) return false; - Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); - return hydross != nullptr; + return AI_VALUE2(Unit*, "find target", "hydross the unstable"); } bool HydrossTheUnstableBotIsNatureTankTrigger::IsActive() @@ -41,8 +39,7 @@ bool HydrossTheUnstableBotIsNatureTankTrigger::IsActive() if (!botAI->IsAssistTankOfIndex(bot, 0)) return false; - Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); - return hydross != nullptr; + return AI_VALUE2(Unit*, "find target", "hydross the unstable"); } bool HydrossTheUnstableElementalsSpawnedTrigger::IsActive() @@ -54,9 +51,8 @@ bool HydrossTheUnstableElementalsSpawnedTrigger::IsActive() if (!hydross || hydross->GetHealthPct() < 10.0f) return false; - Unit* waterElemental = AI_VALUE2(Unit*, "find target", "pure spawn of hydross"); - Unit* natureElemental = AI_VALUE2(Unit*, "find target", "tainted spawn of hydross"); - return waterElemental != nullptr || natureElemental != nullptr; + return AI_VALUE2(Unit*, "find target", "pure spawn of hydross") || + AI_VALUE2(Unit*, "find target", "tainted spawn of hydross"); } bool HydrossTheUnstableDangerFromWaterTombsTrigger::IsActive() @@ -64,8 +60,7 @@ bool HydrossTheUnstableDangerFromWaterTombsTrigger::IsActive() if (!botAI->IsRanged(bot)) return false; - Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); - return hydross != nullptr; + return AI_VALUE2(Unit*, "find target", "hydross the unstable"); } bool HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger::IsActive() @@ -73,8 +68,7 @@ bool HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger::IsActive() if (bot->getClass() != CLASS_HUNTER) return false; - Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); - return hydross != nullptr; + return AI_VALUE2(Unit*, "find target", "hydross the unstable"); } bool HydrossTheUnstableAggroResetsUponPhaseChangeTrigger::IsActive() @@ -85,8 +79,7 @@ bool HydrossTheUnstableAggroResetsUponPhaseChangeTrigger::IsActive() botAI->IsHeal(bot)) return false; - Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); - return hydross != nullptr; + return AI_VALUE2(Unit*, "find target", "hydross the unstable"); } bool HydrossTheUnstableNeedToManageTimersTrigger::IsActive() @@ -94,8 +87,7 @@ bool HydrossTheUnstableNeedToManageTimersTrigger::IsActive() if (!IsInstanceTimerManager(botAI, bot)) return false; - Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); - return hydross != nullptr; + return AI_VALUE2(Unit*, "find target", "hydross the unstable"); } // The Lurker Below @@ -188,26 +180,20 @@ bool TheLurkerBelowNeedToPrepareTimerForSpoutTrigger::IsActive() if (!IsInstanceTimerManager(botAI, bot)) return false; - Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); - return lurker != nullptr; + return AI_VALUE2(Unit*, "find target", "the lurker below"); } // Leotheras the Blind bool LeotherasTheBlindBossIsInactiveTrigger::IsActive() { - Unit* spellbinder = AI_VALUE2(Unit*, "find target", "greyheart spellbinder"); - return spellbinder && spellbinder->IsAlive(); + return AI_VALUE2(Unit*, "find target", "greyheart spellbinder"); } bool LeotherasTheBlindBossTransformedIntoDemonFormTrigger::IsActive() { - Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); - if (!demonFormTank || bot != demonFormTank) - return false; - - Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); - return leotheras != nullptr; + return GetLeotherasDemonFormTank(botAI, bot) == bot && + AI_VALUE2(Unit*, "find target", "leotheras the blind"); } bool LeotherasTheBlindBossEngagedByRangedTrigger::IsActive() @@ -215,8 +201,7 @@ bool LeotherasTheBlindBossEngagedByRangedTrigger::IsActive() if (!botAI->IsRanged(bot)) return false; - Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); - if (demonFormTank && demonFormTank == bot) + if (GetLeotherasDemonFormTank(botAI, bot) == bot) return false; Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); @@ -239,8 +224,7 @@ bool LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger::IsActive() if (!botAI->IsMelee(bot) && !botAI->IsDps(bot)) return false; - Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI); - if (!leotherasPhase2Demon) + if (!GetPhase2LeotherasDemon(botAI)) return false; Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST); @@ -260,16 +244,11 @@ bool LeotherasTheBlindEnteredFinalPhaseTrigger::IsActive() if (botAI->IsHeal(bot)) return false; - Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); - if (demonFormTank && demonFormTank == bot) - return false; - - Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI); - if (!leotherasPhase3Demon) + if (GetLeotherasDemonFormTank(botAI, bot) == bot) return false; - Unit* leotherasHuman = GetLeotherasHuman(botAI); - return leotherasHuman != nullptr; + return GetPhase3LeotherasDemon(botAI) && + GetLeotherasHuman(botAI); } bool LeotherasTheBlindDemonFormTankNeedsAggro::IsActive() @@ -280,8 +259,7 @@ bool LeotherasTheBlindDemonFormTankNeedsAggro::IsActive() if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) return false; - Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); - return leotheras != nullptr; + return AI_VALUE2(Unit*, "find target", "leotheras the blind"); } bool LeotherasTheBlindBossWipesAggroUponPhaseChangeTrigger::IsActive() @@ -289,8 +267,7 @@ bool LeotherasTheBlindBossWipesAggroUponPhaseChangeTrigger::IsActive() if (!IsInstanceTimerManager(botAI, bot)) return false; - Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); - return leotheras != nullptr; + return AI_VALUE2(Unit*, "find target", "leotheras the blind"); } // Fathom-Lord Karathress @@ -300,8 +277,7 @@ bool FathomLordKarathressBossEngagedByMainTankTrigger::IsActive() if (!botAI->IsMainTank(bot)) return false; - Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); - return karathress != nullptr; + return AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); } bool FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger::IsActive() @@ -309,8 +285,7 @@ bool FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger::IsActive() if (!botAI->IsAssistTankOfIndex(bot, 0)) return false; - Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); - return caribdis && caribdis->IsAlive(); + return AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); } bool FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger::IsActive() @@ -318,8 +293,7 @@ bool FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger::IsActive() if (!botAI->IsAssistTankOfIndex(bot, 1)) return false; - Unit* sharkkis = AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis"); - return sharkkis && sharkkis->IsAlive(); + return AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis"); } bool FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger::IsActive() @@ -327,8 +301,7 @@ bool FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger::IsActive() if (!botAI->IsAssistTankOfIndex(bot, 2)) return false; - Unit* tidalvess = AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess"); - return tidalvess && tidalvess->IsAlive(); + return AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess"); } bool FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger::IsActive() @@ -337,7 +310,7 @@ bool FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger::IsActive() return false; Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); - if (!caribdis || !caribdis->IsAlive()) + if (!caribdis) return false; Player* firstAssistTank = nullptr; @@ -357,7 +330,7 @@ bool FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger::IsActive() } } - return firstAssistTank != nullptr; + return firstAssistTank; } bool FathomLordKarathressPullingBossesTrigger::IsActive() @@ -371,18 +344,25 @@ bool FathomLordKarathressPullingBossesTrigger::IsActive() bool FathomLordKarathressDeterminingKillOrderTrigger::IsActive() { - Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); - if (!karathress) + if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) return false; - Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); - Unit* sharkkis = AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis"); - Unit* tidalvess = AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess"); + if (botAI->IsDps(bot)) + return true; + + if (botAI->IsAssistTankOfIndex(bot, 0) && + !AI_VALUE2(Unit*, "find target", "fathom-guard caribdis")) + return true; + + if (botAI->IsAssistTankOfIndex(bot, 1) && + !AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis")) + return true; + + if (botAI->IsAssistTankOfIndex(bot, 2) && + !AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess")) + return true; - return (botAI->IsDps(bot) || - (botAI->IsAssistTankOfIndex(bot, 0) && (!caribdis || !caribdis->IsAlive())) || - (botAI->IsAssistTankOfIndex(bot, 1) && (!sharkkis || !sharkkis->IsAlive())) || - (botAI->IsAssistTankOfIndex(bot, 2) && (!tidalvess || !tidalvess->IsAlive()))); + return false; } bool FathomLordKarathressTanksNeedToEstablishAggroTrigger::IsActive() @@ -390,8 +370,7 @@ bool FathomLordKarathressTanksNeedToEstablishAggroTrigger::IsActive() if (!IsInstanceTimerManager(botAI, bot)) return false; - Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); - return karathress != nullptr; + return AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); } // Morogrim Tidewalker @@ -410,8 +389,7 @@ bool MorogrimTidewalkerBossEngagedByMainTankTrigger::IsActive() if (!botAI->IsMainTank(bot)) return false; - Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); - return tidewalker != nullptr; + return AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); } bool MorogrimTidewalkerWaterGlobulesAreIncomingTrigger::IsActive() @@ -426,7 +404,7 @@ bool MorogrimTidewalkerWaterGlobulesAreIncomingTrigger::IsActive() bool MorogrimTidewalkerEncounterResetTrigger::IsActive() { Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); - return tidewalker && tidewalker->GetHealth() == tidewalker->GetMaxHealth(); + return tidewalker && tidewalker->GetHealthPct() > 99.8f; } // Lady Vashj @@ -436,8 +414,8 @@ bool LadyVashjBossEngagedByMainTankTrigger::IsActive() if (!botAI->IsMainTank(bot)) return false; - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - return vashj && !IsLadyVashjInPhase2(botAI); + return AI_VALUE2(Unit*, "find target", "lady vashj") && + !IsLadyVashjInPhase2(botAI); } bool LadyVashjBossEngagedByRangedInPhase1Trigger::IsActive() @@ -445,8 +423,7 @@ bool LadyVashjBossEngagedByRangedInPhase1Trigger::IsActive() if (!botAI->IsRanged(bot)) return false; - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - return vashj && IsLadyVashjInPhase1(botAI); + return IsLadyVashjInPhase1(botAI); } bool LadyVashjCastsShockBlastOnHighestAggroTrigger::IsActive() @@ -454,8 +431,8 @@ bool LadyVashjCastsShockBlastOnHighestAggroTrigger::IsActive() if (bot->getClass() != CLASS_SHAMAN) return false; - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - if (!vashj || IsLadyVashjInPhase2(botAI)) + if (!AI_VALUE2(Unit*, "find target", "lady vashj") || + IsLadyVashjInPhase2(botAI)) return false; if (!IsMainTankInSameSubgroup(bot)) @@ -466,8 +443,7 @@ bool LadyVashjCastsShockBlastOnHighestAggroTrigger::IsActive() bool LadyVashjBotHasStaticChargeTrigger::IsActive() { - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - if (!vashj) + if (!AI_VALUE2(Unit*, "find target", "lady vashj")) return false; if (Group* group = bot->GetGroup()) @@ -475,7 +451,7 @@ bool LadyVashjBotHasStaticChargeTrigger::IsActive() for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (member && member->IsAlive() && member->HasAura(SPELL_STATIC_CHARGE)) + if (member && member->HasAura(SPELL_STATIC_CHARGE)) return true; } } @@ -489,7 +465,6 @@ bool LadyVashjPullingBossInPhase1AndPhase3Trigger::IsActive() return false; Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - return vashj && ((vashj->GetHealthPct() <= 100.0f && vashj->GetHealthPct() > 90.0f) || (!vashj->HasUnitState(UNIT_STATE_ROOT) && vashj->GetHealthPct() <= 50.0f && vashj->GetHealthPct() > 40.0f)); @@ -497,8 +472,8 @@ bool LadyVashjPullingBossInPhase1AndPhase3Trigger::IsActive() bool LadyVashjAddsSpawnInPhase2AndPhase3Trigger::IsActive() { - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - return vashj && !IsLadyVashjInPhase1(botAI); + return AI_VALUE2(Unit*, "find target", "lady vashj") && + !IsLadyVashjInPhase1(botAI); } bool LadyVashjTaintedElementalCheatTrigger::IsActive() @@ -506,8 +481,7 @@ bool LadyVashjTaintedElementalCheatTrigger::IsActive() if (!botAI->HasCheat(BotCheatMask::raid)) return false; - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - if (!vashj) + if (!AI_VALUE2(Unit*, "find target", "lady vashj")) return false; bool taintedPresent = false; @@ -542,18 +516,14 @@ bool LadyVashjTaintedElementalCheatTrigger::IsActive() if (!group) return false; - Player* designatedLooter = GetDesignatedCoreLooter(group, botAI); - return (designatedLooter && designatedLooter == bot && + return (GetDesignatedCoreLooter(group, botAI) == bot && !bot->HasItemCount(ITEM_TAINTED_CORE, 1, false)); } bool LadyVashjTaintedCoreWasLootedTrigger::IsActive() { - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - if (!vashj) - return false; - - if (!IsLadyVashjInPhase2(botAI)) + if (!AI_VALUE2(Unit*, "find target", "lady vashj") || + !IsLadyVashjInPhase2(botAI)) return false; Group* group = bot->GetGroup(); @@ -599,8 +569,8 @@ bool LadyVashjTaintedCoreWasLootedTrigger::IsActive() return true; // First and second passers move to positions as soon as the elemental appears - Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental"); - if (tainted && (bot == firstCorePasser || bot == secondCorePasser)) + if (AI_VALUE2(Unit*, "find target", "tainted elemental") && + (bot == firstCorePasser || bot == secondCorePasser)) return true; return false; @@ -639,14 +609,12 @@ bool LadyVashjTaintedCoreIsUnusableTrigger::IsActive() bool LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger::IsActive() { - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - return vashj && IsLadyVashjInPhase3(botAI); + return IsLadyVashjInPhase3(botAI); } bool LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger::IsActive() { - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - if (!vashj) + if (!AI_VALUE2(Unit*, "find target", "lady vashj")) return false; if (Group* group = bot->GetGroup()) @@ -654,10 +622,7 @@ bool LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger::IsActive() for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (!member || !member->IsAlive()) - continue; - - if (!member->HasAura(SPELL_ENTANGLE)) + if (!member || !member->HasAura(SPELL_ENTANGLE)) continue; if (botAI->IsMelee(member)) @@ -671,5 +636,5 @@ bool LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger::IsActive() bool LadyVashjNeedToManageTrackersTrigger::IsActive() { Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - return vashj && vashj->GetHealth() == vashj->GetMaxHealth(); + return vashj && vashj->GetHealthPct() > 99.8f; } From 1e18957aeaab2d07a0fb823635390b67d80aba6c Mon Sep 17 00:00:00 2001 From: crow Date: Tue, 6 Jan 2026 09:17:14 -0600 Subject: [PATCH 11/25] update to reflect PR #1923 --- .../serpentshrinecavern/RaidSSCMultipliers.h | 14 + .../serpentshrinecavern/RaidSSCStrategy.cpp | 283 +++++++++--------- .../serpentshrinecavern/RaidSSCStrategy.h | 2 +- 3 files changed, 152 insertions(+), 147 deletions(-) diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h index e1a0efece6..5b9d803075 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h @@ -3,6 +3,8 @@ #include "Multiplier.h" +// Trash + class UnderbogColossusEscapeToxicPoolMultiplier : public Multiplier { public: @@ -11,6 +13,8 @@ class UnderbogColossusEscapeToxicPoolMultiplier : public Multiplier virtual float GetValue(Action* action); }; +// Hydross the Unstable + class HydrossTheUnstableDisableTankActionsMultiplier : public Multiplier { public: @@ -35,6 +39,8 @@ class HydrossTheUnstableControlMisdirectionMultiplier : public Multiplier virtual float GetValue(Action* action); }; +// The Lurker Below + class TheLurkerBelowStayAwayFromSpoutMultiplier : public Multiplier { public: @@ -59,6 +65,8 @@ class TheLurkerBelowDisableTankAssistMultiplier : public Multiplier virtual float GetValue(Action* action); }; +// Leotheras the Blind + class LeotherasTheBlindAvoidWhirlwindMultiplier : public Multiplier { public: @@ -99,6 +107,8 @@ class LeotherasTheBlindDelayBloodlustAndHeroismMultiplier : public Multiplier virtual float GetValue(Action* action); }; +// Fathom-Lord Karathress + class FathomLordKarathressDisableTankActionsMultiplier : public Multiplier { public: @@ -139,6 +149,8 @@ class FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier : public virtual float GetValue(Action* action); }; +// Morogrim Tidewalker + class MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier : public Multiplier { public: @@ -163,6 +175,8 @@ class MorogrimTidewalkerMaintainPhase2StackingMultiplier : public Multiplier virtual float GetValue(Action* action); }; +// Lady Vashj + class LadyVashjDelayBloodlustAndHeroismMultiplier : public Multiplier { public: diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp index b28be97bcb..0bd292431c 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp @@ -4,164 +4,155 @@ void RaidSSCStrategy::InitTriggers(std::vector& triggers) { // Trash Mobs - triggers.push_back(new TriggerNode("underbog colossus spawned toxic pool after death", - NextAction::array(0, new NextAction("underbog colossus escape toxic pool", ACTION_EMERGENCY + 10), nullptr) - )); - triggers.push_back(new TriggerNode("greyheart tidecaller water elemental totem spawned", - NextAction::array(0, new NextAction("greyheart tidecaller mark water elemental totem", ACTION_RAID + 1), nullptr) - )); + triggers.push_back(new TriggerNode("underbog colossus spawned toxic pool after death", { + NextAction("underbog colossus escape toxic pool", ACTION_EMERGENCY + 10) })); + + triggers.push_back(new TriggerNode("greyheart tidecaller water elemental totem spawned", { + NextAction("greyheart tidecaller mark water elemental totem", ACTION_RAID + 1) })); // Hydross the Unstable - triggers.push_back(new TriggerNode("hydross the unstable bot is frost tank", - NextAction::array(0, new NextAction("hydross the unstable position frost tank", ACTION_RAID + 1), nullptr) - )); - triggers.push_back(new TriggerNode("hydross the unstable bot is nature tank", - NextAction::array(0, new NextAction("hydross the unstable position nature tank", ACTION_RAID + 1), nullptr) - )); - triggers.push_back(new TriggerNode("hydross the unstable elementals spawned", - NextAction::array(0, new NextAction("hydross the unstable prioritize elemental adds", ACTION_RAID + 1), nullptr) - )); - triggers.push_back(new TriggerNode("hydross the unstable danger from water tombs", - NextAction::array(0, new NextAction("hydross the unstable frost phase spread out", ACTION_EMERGENCY + 1), nullptr) - )); - triggers.push_back(new TriggerNode("hydross the unstable tank needs aggro upon phase change", - NextAction::array(0, new NextAction("hydross the unstable misdirect boss to tank", ACTION_EMERGENCY + 6), nullptr) - )); - triggers.push_back(new TriggerNode("hydross the unstable aggro resets upon phase change", - NextAction::array(0, new NextAction("hydross the unstable stop dps upon phase change", ACTION_EMERGENCY + 9), nullptr) - )); - triggers.push_back(new TriggerNode("hydross the unstable need to manage timers", - NextAction::array(0, new NextAction("hydross the unstable manage timers", ACTION_EMERGENCY + 10), nullptr) - )); + triggers.push_back(new TriggerNode("hydross the unstable bot is frost tank", { + NextAction("hydross the unstable position frost tank", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("hydross the unstable bot is nature tank", { + NextAction("hydross the unstable position nature tank", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("hydross the unstable elementals spawned", { + NextAction("hydross the unstable prioritize elemental adds", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("hydross the unstable danger from water tombs", { + NextAction("hydross the unstable frost phase spread out", ACTION_EMERGENCY + 1) })); + + triggers.push_back(new TriggerNode("hydross the unstable tank needs aggro upon phase change", { + NextAction("hydross the unstable misdirect boss to tank", ACTION_EMERGENCY + 6) })); + + triggers.push_back(new TriggerNode("hydross the unstable aggro resets upon phase change", { + NextAction("hydross the unstable stop dps upon phase change", ACTION_EMERGENCY + 9) })); + + triggers.push_back(new TriggerNode("hydross the unstable need to manage timers", { + NextAction("hydross the unstable manage timers", ACTION_EMERGENCY + 10) })); // The Lurker Below - triggers.push_back(new TriggerNode("the lurker below spout is active", - NextAction::array(0, new NextAction("the lurker below run around behind boss", ACTION_EMERGENCY + 6), nullptr) - )); - triggers.push_back(new TriggerNode("the lurker below boss is active for main tank", - NextAction::array(0, new NextAction("the lurker below position main tank", ACTION_RAID + 1), nullptr) - )); - triggers.push_back(new TriggerNode("the lurker below boss casts geyser", - NextAction::array(0, new NextAction("the lurker below spread ranged in arc", ACTION_RAID + 1), nullptr) - )); - triggers.push_back(new TriggerNode("the lurker below boss is submerged", - NextAction::array(0, new NextAction("the lurker below tanks pick up adds", ACTION_EMERGENCY + 1), nullptr) - )); - triggers.push_back(new TriggerNode("the lurker below need to prepare timer for spout", - NextAction::array(0, new NextAction("the lurker below manage spout timer", ACTION_EMERGENCY + 10), nullptr) - )); + triggers.push_back(new TriggerNode("the lurker below spout is active", { + NextAction("the lurker below run around behind boss", ACTION_EMERGENCY + 6) })); + + triggers.push_back(new TriggerNode("the lurker below boss is active for main tank", { + NextAction("the lurker below position main tank", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("the lurker below boss casts geyser", { + NextAction("the lurker below spread ranged in arc", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("the lurker below boss is submerged", { + NextAction("the lurker below tanks pick up adds", ACTION_EMERGENCY + 1) })); + + triggers.push_back(new TriggerNode("the lurker below need to prepare timer for spout", { + NextAction("the lurker below manage spout timer", ACTION_EMERGENCY + 10) })); // Leotheras the Blind - triggers.push_back(new TriggerNode("leotheras the blind boss is inactive", - NextAction::array(0, new NextAction("leotheras the blind target spellbinders", ACTION_RAID + 1), nullptr) - )); - triggers.push_back(new TriggerNode("leotheras the blind boss transformed into demon form", - NextAction::array(0, new NextAction("leotheras the blind demon form tank attack boss", ACTION_EMERGENCY + 6), nullptr) - )); - triggers.push_back(new TriggerNode("leotheras the blind boss engaged by ranged", - NextAction::array(0, new NextAction("leotheras the blind position ranged", ACTION_RAID + 1), nullptr) - )); - triggers.push_back(new TriggerNode("leotheras the blind boss channeling whirlwind", - NextAction::array(0, new NextAction("leotheras the blind run away from whirlwind", ACTION_EMERGENCY + 1), nullptr) - )); - triggers.push_back(new TriggerNode("leotheras the blind bot has too many chaos blast stacks", - NextAction::array(0, new NextAction("leotheras the blind melee dps run away from boss", ACTION_EMERGENCY + 7), nullptr) - )); - triggers.push_back(new TriggerNode("leotheras the blind inner demon cheat", - NextAction::array(0, new NextAction("leotheras the blind inner demon cheat", ACTION_EMERGENCY + 6), nullptr) - )); - triggers.push_back(new TriggerNode("leotheras the blind entered final phase", - NextAction::array(0, new NextAction("leotheras the blind final phase assign dps priority", ACTION_RAID + 2), nullptr) - )); - triggers.push_back(new TriggerNode("leotheras the blind demon form tank needs aggro", - NextAction::array(0, new NextAction("leotheras the blind misdirect boss to demon form tank", ACTION_RAID + 3), nullptr) - )); - triggers.push_back(new TriggerNode("leotheras the blind boss wipes aggro upon phase change", - NextAction::array(0, new NextAction("leotheras the blind manage dps wait timers", ACTION_EMERGENCY + 10), nullptr) - )); + triggers.push_back(new TriggerNode("leotheras the blind boss is inactive", { + NextAction("leotheras the blind target spellbinders", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("leotheras the blind boss transformed into demon form", { + NextAction("leotheras the blind demon form tank attack boss", ACTION_EMERGENCY + 6) })); + + triggers.push_back(new TriggerNode("leotheras the blind boss engaged by ranged", { + NextAction("leotheras the blind position ranged", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("leotheras the blind boss channeling whirlwind", { + NextAction("leotheras the blind run away from whirlwind", ACTION_EMERGENCY + 1) })); + + triggers.push_back(new TriggerNode("leotheras the blind bot has too many chaos blast stacks", { + NextAction("leotheras the blind melee dps run away from boss", ACTION_EMERGENCY + 7) })); + + triggers.push_back(new TriggerNode("leotheras the blind inner demon cheat", { + NextAction("leotheras the blind inner demon cheat", ACTION_EMERGENCY + 6) })); + + triggers.push_back(new TriggerNode("leotheras the blind entered final phase", { + NextAction("leotheras the blind final phase assign dps priority", ACTION_RAID + 2) })); + + triggers.push_back(new TriggerNode("leotheras the blind demon form tank needs aggro", { + NextAction("leotheras the blind misdirect boss to demon form tank", ACTION_RAID + 3) })); + + triggers.push_back(new TriggerNode("leotheras the blind boss wipes aggro upon phase change", { + NextAction("leotheras the blind manage dps wait timers", ACTION_EMERGENCY + 10) })); // Fathom-Lord Karathress - triggers.push_back(new TriggerNode("fathom-lord karathress boss engaged by main tank", - NextAction::array(0, new NextAction("fathom-lord karathress main tank position boss", ACTION_RAID + 1), nullptr) - )); - triggers.push_back(new TriggerNode("fathom-lord karathress caribdis engaged by first assist tank", - NextAction::array(0, new NextAction("fathom-lord karathress first assist tank position caribdis", ACTION_RAID + 1), nullptr) - )); - triggers.push_back(new TriggerNode("fathom-lord karathress sharkkis engaged by second assist tank", - NextAction::array(0, new NextAction("fathom-lord karathress second assist tank position sharkkis", ACTION_RAID + 1), nullptr) - )); - triggers.push_back(new TriggerNode("fathom-lord karathress tidalvess engaged by third assist tank", - NextAction::array(0, new NextAction("fathom-lord karathress third assist tank position tidalvess", ACTION_RAID + 1), nullptr) - )); - triggers.push_back(new TriggerNode("fathom-lord karathress caribdis tank needs dedicated healer", - NextAction::array(0, new NextAction("fathom-lord karathress position caribdis tank healer", ACTION_RAID + 1), nullptr) - )); - triggers.push_back(new TriggerNode("fathom-lord karathress pulling bosses", - NextAction::array(0, new NextAction("fathom-lord karathress misdirect bosses to tanks", ACTION_RAID + 2), nullptr) - )); - triggers.push_back(new TriggerNode("fathom-lord karathress determining kill order", - NextAction::array(0, new NextAction("fathom-lord karathress assign dps priority", ACTION_RAID + 1), nullptr) - )); - triggers.push_back(new TriggerNode("fathom-lord karathress tanks need to establish aggro", - NextAction::array(0, new NextAction("fathom-lord karathress manage dps timer", ACTION_EMERGENCY + 10), nullptr) - )); + triggers.push_back(new TriggerNode("fathom-lord karathress boss engaged by main tank", { + NextAction("fathom-lord karathress main tank position boss", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("fathom-lord karathress caribdis engaged by first assist tank", { + NextAction("fathom-lord karathress first assist tank position caribdis", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("fathom-lord karathress sharkkis engaged by second assist tank", { + NextAction("fathom-lord karathress second assist tank position sharkkis", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("fathom-lord karathress tidalvess engaged by third assist tank", { + NextAction("fathom-lord karathress third assist tank position tidalvess", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("fathom-lord karathress caribdis tank needs dedicated healer", { + NextAction("fathom-lord karathress position caribdis tank healer", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("fathom-lord karathress pulling bosses", { + NextAction("fathom-lord karathress misdirect bosses to tanks", ACTION_RAID + 2) })); + + triggers.push_back(new TriggerNode("fathom-lord karathress determining kill order", { + NextAction("fathom-lord karathress assign dps priority", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("fathom-lord karathress tanks need to establish aggro", { + NextAction("fathom-lord karathress manage dps timer", ACTION_EMERGENCY + 10) })); // Morogrim Tidewalker - triggers.push_back(new TriggerNode("morogrim tidewalker boss engaged by main tank", - NextAction::array(0, new NextAction("morogrim tidewalker move boss to tank position", ACTION_RAID + 1), nullptr) - )); - triggers.push_back(new TriggerNode("morogrim tidewalker water globules are incoming", - NextAction::array(0, new NextAction("morogrim tidewalker phase 2 reposition ranged", ACTION_RAID + 1), nullptr) - )); - triggers.push_back(new TriggerNode("morogrim tidewalker pulling boss", - NextAction::array(0, new NextAction("morogrim tidewalker misdirect boss to main tank", ACTION_RAID + 1), nullptr) - )); - triggers.push_back(new TriggerNode("morogrim tidewalker encounter reset", - NextAction::array(0, new NextAction("morogrim tidewalker reset phase transition steps", ACTION_RAID + 2), nullptr) - )); + triggers.push_back(new TriggerNode("morogrim tidewalker boss engaged by main tank", { + NextAction("morogrim tidewalker move boss to tank position", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("morogrim tidewalker water globules are incoming", { + NextAction("morogrim tidewalker phase 2 reposition ranged", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("morogrim tidewalker pulling boss", { + NextAction("morogrim tidewalker misdirect boss to main tank", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("morogrim tidewalker encounter reset", { + NextAction("morogrim tidewalker reset phase transition steps", ACTION_RAID + 2) })); // Lady Vashj - triggers.push_back(new TriggerNode("lady vashj boss engaged by main tank", - NextAction::array(0, new NextAction("lady vashj main tank position boss", ACTION_RAID + 1), nullptr) - )); - triggers.push_back(new TriggerNode("lady vashj boss engaged by ranged in phase 1", - NextAction::array(0, new NextAction("lady vashj phase 1 spread ranged in arc", ACTION_RAID + 1), nullptr) - )); - triggers.push_back(new TriggerNode("lady vashj casts shock blast on highest aggro", - NextAction::array(0, new NextAction("lady vashj set grounding totem in main tank group", ACTION_EMERGENCY + 1), nullptr) - )); - triggers.push_back(new TriggerNode("lady vashj bot has static charge", - NextAction::array(0, new NextAction("lady vashj static charge move away from group", ACTION_EMERGENCY + 7), nullptr) - )); - triggers.push_back(new TriggerNode("lady vashj pulling boss in phase 1 and phase 3", - NextAction::array(0, new NextAction("lady vashj misdirect boss to main tank", ACTION_EMERGENCY + 1), nullptr) - )); - triggers.push_back(new TriggerNode("lady vashj tainted elemental cheat", - NextAction::array(0, - new NextAction("lady vashj teleport to tainted elemental", ACTION_EMERGENCY + 10), - new NextAction("lady vashj loot tainted core", ACTION_EMERGENCY + 10), nullptr) - )); - triggers.push_back(new TriggerNode("lady vashj tainted core was looted", - NextAction::array(0, new NextAction("lady vashj pass the tainted core", ACTION_EMERGENCY + 10), nullptr) - )); - triggers.push_back(new TriggerNode("lady vashj tainted core is unusable", - NextAction::array(0, new NextAction("lady vashj destroy tainted core", ACTION_EMERGENCY + 1), nullptr) - )); - triggers.push_back(new TriggerNode("lady vashj adds spawn in phase 2 and phase 3", - NextAction::array(0, - new NextAction("lady vashj assign phase 2 and phase 3 dps priority", ACTION_RAID + 1), - new NextAction("lady vashj misdirect strider to first assist tank", ACTION_EMERGENCY + 1), - new NextAction("lady vashj tank attack and move away strider", ACTION_EMERGENCY + 1), nullptr) - )); - triggers.push_back(new TriggerNode("lady vashj toxic sporebats are spewing poison clouds", - NextAction::array(0, new NextAction("lady vashj avoid toxic spores", ACTION_EMERGENCY + 6), nullptr) - )); - triggers.push_back(new TriggerNode("lady vashj bot is entangled in toxic spores or static charge", - NextAction::array(0, new NextAction("lady vashj use free action abilities", ACTION_EMERGENCY + 7), nullptr) - )); - triggers.push_back(new TriggerNode("lady vashj need to manage trackers", - NextAction::array(0, new NextAction("lady vashj manage trackers", ACTION_EMERGENCY + 10), nullptr) - )); + triggers.push_back(new TriggerNode("lady vashj boss engaged by main tank", { + NextAction("lady vashj main tank position boss", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("lady vashj boss engaged by ranged in phase 1", { + NextAction("lady vashj phase 1 spread ranged in arc", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("lady vashj casts shock blast on highest aggro", { + NextAction("lady vashj set grounding totem in main tank group", ACTION_EMERGENCY + 1) })); + + triggers.push_back(new TriggerNode("lady vashj bot has static charge", { + NextAction("lady vashj static charge move away from group", ACTION_EMERGENCY + 7) })); + + triggers.push_back(new TriggerNode("lady vashj pulling boss in phase 1 and phase 3", { + NextAction("lady vashj misdirect boss to main tank", ACTION_EMERGENCY + 1) })); + + triggers.push_back(new TriggerNode("lady vashj tainted elemental cheat", { + NextAction("lady vashj teleport to tainted elemental", ACTION_EMERGENCY + 10), + NextAction("lady vashj loot tainted core", ACTION_EMERGENCY + 10) })); + + triggers.push_back(new TriggerNode("lady vashj tainted core was looted", { + NextAction("lady vashj pass the tainted core", ACTION_EMERGENCY + 10) })); + + triggers.push_back(new TriggerNode("lady vashj tainted core is unusable", { + NextAction("lady vashj destroy tainted core", ACTION_EMERGENCY + 1) })); + + triggers.push_back(new TriggerNode("lady vashj adds spawn in phase 2 and phase 3", { + NextAction("lady vashj assign phase 2 and phase 3 dps priority", ACTION_RAID + 1), + NextAction("lady vashj misdirect strider to first assist tank", ACTION_EMERGENCY + 1), + NextAction("lady vashj tank attack and move away strider", ACTION_EMERGENCY + 1) })); + + triggers.push_back(new TriggerNode("lady vashj toxic sporebats are spewing poison clouds", { + NextAction("lady vashj avoid toxic spores", ACTION_EMERGENCY + 6) })); + + triggers.push_back(new TriggerNode("lady vashj bot is entangled in toxic spores or static charge", { + NextAction("lady vashj use free action abilities", ACTION_EMERGENCY + 7) })); + + triggers.push_back(new TriggerNode("lady vashj need to manage trackers", { + NextAction("lady vashj manage trackers", ACTION_EMERGENCY + 10) })); } void RaidSSCStrategy::InitMultipliers(std::vector& multipliers) diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.h b/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.h index 0f90e6784e..3c2c05f586 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.h @@ -11,7 +11,7 @@ class RaidSSCStrategy : public Strategy std::string const getName() override { return "ssc"; } - void InitTriggers(std::vector& triggerSs) override; + void InitTriggers(std::vector& triggers) override; void InitMultipliers(std::vector& multipliers) override; }; From b8d7268f1f20540fe50abf7822a9ef60a3c615d9 Mon Sep 17 00:00:00 2001 From: crow Date: Tue, 6 Jan 2026 09:20:54 -0600 Subject: [PATCH 12/25] address merge conflicts --- src/strategy/AiObjectContext.cpp | 4 ---- src/strategy/raids/RaidStrategyContext.h | 3 --- 2 files changed, 7 deletions(-) diff --git a/src/strategy/AiObjectContext.cpp b/src/strategy/AiObjectContext.cpp index 1a4b5d1b3f..2be43f8c85 100644 --- a/src/strategy/AiObjectContext.cpp +++ b/src/strategy/AiObjectContext.cpp @@ -45,8 +45,6 @@ #include "raids/gruulslair/RaidGruulsLairTriggerContext.h" #include "raids/serpentshrinecavern/RaidSSCActionContext.h" #include "raids/serpentshrinecavern/RaidSSCTriggerContext.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" @@ -120,7 +118,6 @@ void AiObjectContext::BuildSharedActionContexts(SharedNamedObjectContextList creators["magtheridon"] = &RaidStrategyContext::magtheridon; creators["gruulslair"] = &RaidStrategyContext::gruulslair; creators["ssc"] = &RaidStrategyContext::ssc; - creators["naxx"] = &RaidStrategyContext::naxx; creators["wotlk-os"] = &RaidStrategyContext::wotlk_os; creators["wotlk-eoe"] = &RaidStrategyContext::wotlk_eoe; creators["voa"] = &RaidStrategyContext::voa; @@ -46,7 +44,6 @@ class RaidStrategyContext : public NamedObjectContext static Strategy* magtheridon(PlayerbotAI* botAI) { return new RaidMagtheridonStrategy(botAI); } static Strategy* gruulslair(PlayerbotAI* botAI) { return new RaidGruulsLairStrategy(botAI); } static Strategy* ssc(PlayerbotAI* botAI) { return new RaidSSCStrategy(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); } From 5dc9e67af2d352391d08c394c193ad660abb3168 Mon Sep 17 00:00:00 2001 From: crow Date: Thu, 8 Jan 2026 09:37:14 -0600 Subject: [PATCH 13/25] minor performance improvements and cleanups --- .../serpentshrinecavern/RaidSSCActions.cpp | 51 ++++++++++--------- .../RaidSSCMultipliers.cpp | 32 ++++-------- .../serpentshrinecavern/RaidSSCTriggers.cpp | 16 +++--- 3 files changed, 45 insertions(+), 54 deletions(-) diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp index ba813cd6ff..48f913d14e 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp @@ -709,17 +709,19 @@ bool LeotherasTheBlindDemonFormTankAttackBossAction::Execute(Event event) // And stay away from the Warlock tank to avoid Chaos Blasts bool LeotherasTheBlindPositionRangedAction::Execute(Event event) { - const uint32 minInterval = 500; - Unit* leotherasHuman = GetLeotherasHuman(botAI); if (leotherasHuman && bot->GetExactDist2d(leotherasHuman) < 10.0f && leotherasHuman->GetVictim() != bot) + { + const uint32 minInterval = 500; return FleePosition(leotherasHuman->GetPosition(), 12.0f, minInterval); + } - if (!GetActiveLeotherasDemon(botAI)) + Group* group = bot->GetGroup(); + if (!group) return false; - if (Group* group = bot->GetGroup()) + if (GetActiveLeotherasDemon(botAI)) { for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { @@ -727,6 +729,7 @@ bool LeotherasTheBlindPositionRangedAction::Execute(Event event) if (!member || member == bot || !member->IsAlive()) continue; + const uint32 minInterval = 0; if (GetLeotherasDemonFormTank(botAI, bot) == member) { if (bot->GetExactDist2d(member) < 10.0f) @@ -765,21 +768,19 @@ bool LeotherasTheBlindMeleeDpsRunAwayFromBossAction::Execute(Event event) if (!leotherasPhase2Demon) return false; - Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST); - if (chaosBlast && chaosBlast->GetStackAmount() >= 4) - { - Unit* demonVictim = leotherasPhase2Demon->GetVictim(); - if (!demonVictim) - return false; + Unit* demonVictim = leotherasPhase2Demon->GetVictim(); + if (!demonVictim) + return false; - float currentDistance = bot->GetExactDist2d(demonVictim); - const float safeDistance = 10.0f; - if (currentDistance < safeDistance) - { - botAI->Reset(); - return MoveAway(demonVictim, safeDistance - currentDistance + 1.0f); - } + float currentDistance = bot->GetExactDist2d(demonVictim); + const float safeDistance = 10.0f; + if (currentDistance < safeDistance) + { + botAI->Reset(); + return MoveAway(demonVictim, safeDistance - currentDistance + 1.0f); } + else + return true; return false; } @@ -795,7 +796,7 @@ bool LeotherasTheBlindInnerDemonCheatAction::Execute(Event event) { Unit* unit = botAI->GetUnit(guid); Creature* creature = unit ? unit->ToCreature() : nullptr; - if (creature && creature->IsAlive() && creature->GetEntry() == NPC_INNER_DEMON + if (creature && creature->GetEntry() == NPC_INNER_DEMON && creature->GetSummonerGUID() == bot->GetGUID()) { innerDemon = creature; @@ -1199,7 +1200,7 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) // Target priority 2: Tidalvess for all dps Unit* tidalvess = AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess"); - if (tidalvess && tidalvess->IsAlive()) + if (tidalvess) { MarkTargetWithCircle(bot, tidalvess); SetRtiTarget(botAI, "circle", tidalvess); @@ -1212,7 +1213,7 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) // Target priority 3: Caribdis for ranged dps Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); - if (botAI->IsRangedDps(bot) && caribdis && caribdis->IsAlive()) + if (botAI->IsRangedDps(bot) && caribdis) { MarkTargetWithDiamond(bot, caribdis); SetRtiTarget(botAI, "diamond", caribdis); @@ -1232,7 +1233,7 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) // Target priority 4: Sharkkis for melee dps and, after Caribdis is down, ranged dps also Unit* sharkkis = AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis"); - if (sharkkis && sharkkis->IsAlive()) + if (sharkkis) { MarkTargetWithStar(bot, sharkkis); SetRtiTarget(botAI, "star", sharkkis); @@ -1245,7 +1246,7 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) // Target priority 5: Sharkkis pets for all dps Unit* fathomSporebat = AI_VALUE2(Unit*, "find target", "fathom sporebat"); - if (fathomSporebat && fathomSporebat->IsAlive() && botAI->IsMelee(bot)) + if (fathomSporebat && botAI->IsMelee(bot)) { MarkTargetWithCross(bot, fathomSporebat); SetRtiTarget(botAI, "cross", fathomSporebat); @@ -1257,7 +1258,7 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) } Unit* fathomLurker = AI_VALUE2(Unit*, "find target", "fathom lurker"); - if (fathomLurker && fathomLurker->IsAlive() && botAI->IsMelee(bot)) + if (fathomLurker && botAI->IsMelee(bot)) { MarkTargetWithSquare(bot, fathomLurker); SetRtiTarget(botAI, "square", fathomLurker); @@ -1270,7 +1271,7 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) // Target priority 6: Karathress for all dps Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); - if (karathress && karathress->IsAlive()) + if (karathress) { MarkTargetWithTriangle(bot, karathress); SetRtiTarget(botAI, "triangle", karathress); @@ -1858,7 +1859,7 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event event) for (Unit* candidate : targets) { - if (candidate && candidate->IsAlive()) + if (candidate) { target = candidate; break; diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp index ced439e05f..8f550c3178 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp @@ -50,28 +50,14 @@ float HydrossTheUnstableDisableTankActionsMultiplier::GetValue(Action* action) dynamic_cast(action)) return 0.0f; - if (botAI->IsMainTank(bot)) - { - if (hydross->HasAura(SPELL_CORRUPTION)) - { - if (dynamic_cast(action) || - dynamic_cast(action) || - (dynamic_cast(action) && - !dynamic_cast(action))) - return 0.0f; - } - } - - if (botAI->IsAssistTankOfIndex(bot, 0)) + if (dynamic_cast(action) || + dynamic_cast(action) || + (dynamic_cast(action) && + !dynamic_cast(action))) { - if (!hydross->HasAura(SPELL_CORRUPTION)) - { - if (dynamic_cast(action) || - dynamic_cast(action) || - (dynamic_cast(action) && - !dynamic_cast(action))) - return 0.0f; - } + if ((botAI->IsMainTank(bot) && hydross->HasAura(SPELL_CORRUPTION)) || + (botAI->IsAssistTankOfIndex(bot, 0) && !hydross->HasAura(SPELL_CORRUPTION))) + return 0.0f; } return 1.0f; @@ -105,7 +91,7 @@ float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action) bool justChanged = (itDps == hydrossFrostDpsWaitTimer.end() || (now - itDps->second) < dpsWaitSeconds); bool aboutToChange = (itPhase != hydrossChangeToFrostPhaseTimer.end() && - (now - itPhase->second) > phaseChangeWaitSeconds); + (now - itPhase->second) > phaseChangeWaitSeconds); if (justChanged || aboutToChange) { @@ -124,7 +110,7 @@ float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action) bool justChanged = (itDps == hydrossNatureDpsWaitTimer.end() || (now - itDps->second) < dpsWaitSeconds); bool aboutToChange = (itPhase != hydrossChangeToNaturePhaseTimer.end() && - (now - itPhase->second) > phaseChangeWaitSeconds); + (now - itPhase->second) > phaseChangeWaitSeconds); if (justChanged || aboutToChange) diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp index d0f5ba3a6e..d745583a15 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp @@ -221,14 +221,14 @@ bool LeotherasTheBlindBossChannelingWhirlwindTrigger::IsActive() bool LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger::IsActive() { - if (!botAI->IsMelee(bot) && !botAI->IsDps(bot)) + if (!botAI->IsMelee(bot) || !botAI->IsDps(bot)) return false; - if (!GetPhase2LeotherasDemon(botAI)) + Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST); + if (!chaosBlast || chaosBlast->GetStackAmount() < 4) return false; - Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST); - return chaosBlast && chaosBlast->GetStackAmount() >= 5; + return GetPhase2LeotherasDemon(botAI); } bool LeotherasTheBlindInnerDemonCheatTrigger::IsActive() @@ -578,8 +578,9 @@ bool LadyVashjTaintedCoreWasLootedTrigger::IsActive() bool LadyVashjTaintedCoreIsUnusableTrigger::IsActive() { - if (IsLadyVashjInPhase3(botAI)) - return true; + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + if (!vashj) + return false; Group* group = bot->GetGroup(); if (!group) @@ -604,6 +605,9 @@ bool LadyVashjTaintedCoreIsUnusableTrigger::IsActive() return true; } + if (IsLadyVashjInPhase3(botAI)) + return true; + return false; } From b63996459f27dd4040e54cd204d7d21593eb1e32 Mon Sep 17 00:00:00 2001 From: crow Date: Mon, 12 Jan 2026 11:09:22 -0600 Subject: [PATCH 14/25] switch to non-combat map clearing and modify some checks for performance also require tanks to be alive to maintain roles --- .../RaidSSCActionContext.h | 14 +- .../serpentshrinecavern/RaidSSCActions.cpp | 145 ++++++++++-------- .../serpentshrinecavern/RaidSSCActions.h | 18 ++- .../RaidSSCMultipliers.cpp | 8 +- .../serpentshrinecavern/RaidSSCStrategy.cpp | 13 +- .../RaidSSCTriggerContext.h | 20 ++- .../serpentshrinecavern/RaidSSCTriggers.cpp | 45 +++--- .../serpentshrinecavern/RaidSSCTriggers.h | 26 +++- 8 files changed, 170 insertions(+), 119 deletions(-) diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h b/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h index be461b6039..e273dbe11e 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h @@ -9,6 +9,10 @@ class RaidSSCActionContext : public NamedObjectContext public: RaidSSCActionContext() { + // General + creators["serpent shrine cavern clear timers and trackers"] = + &RaidSSCActionContext::serpent_shrine_cavern_clear_timers_and_trackers; + // Trash creators["underbog colossus escape toxic pool"] = &RaidSSCActionContext::underbog_colossus_escape_toxic_pool; @@ -117,9 +121,6 @@ class RaidSSCActionContext : public NamedObjectContext creators["morogrim tidewalker phase 2 reposition ranged"] = &RaidSSCActionContext::morogrim_tidewalker_phase_2_reposition_ranged; - creators["morogrim tidewalker reset phase transition steps"] = - &RaidSSCActionContext::morogrim_tidewalker_reset_phase_transition_steps; - // Lady Vashj creators["lady vashj main tank position boss"] = &RaidSSCActionContext::lady_vashj_main_tank_position_boss; @@ -168,6 +169,10 @@ class RaidSSCActionContext : public NamedObjectContext } private: + // General + static Action* serpent_shrine_cavern_clear_timers_and_trackers( + PlayerbotAI* botAI) { return new SerpentShrineCavernClearTimersAndTrackersAction(botAI); } + // Trash static Action* underbog_colossus_escape_toxic_pool( PlayerbotAI* botAI) { return new UnderbogColossusEscapeToxicPoolAction(botAI); } @@ -276,9 +281,6 @@ class RaidSSCActionContext : public NamedObjectContext static Action* morogrim_tidewalker_phase_2_reposition_ranged( PlayerbotAI* botAI) { return new MorogrimTidewalkerPhase2RepositionRangedAction(botAI); } - static Action* morogrim_tidewalker_reset_phase_transition_steps( - PlayerbotAI* botAI) { return new MorogrimTidewalkerResetPhaseTransitionStepsAction(botAI); } - // Lady Vashj static Action* lady_vashj_main_tank_position_boss( PlayerbotAI* botAI) { return new LadyVashjMainTankPositionBossAction(botAI); } diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp index 48f913d14e..93bcad9426 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp @@ -9,6 +9,69 @@ using namespace SerpentShrineCavernHelpers; +// General + +bool SerpentShrineCavernClearTimersAndTrackersAction::Execute(Event event) +{ + bool cleared = false; + + if (!hydrossChangeToNaturePhaseTimer.empty()) + { + hydrossChangeToNaturePhaseTimer.clear(); + cleared = true; + } + + if (!hydrossChangeToFrostPhaseTimer.empty()) + { + hydrossChangeToFrostPhaseTimer.clear(); + cleared = true; + } + + if (!hydrossNatureDpsWaitTimer.empty()) + { + hydrossNatureDpsWaitTimer.clear(); + cleared = true; + } + + if (!hydrossFrostDpsWaitTimer.empty()) + { + hydrossFrostDpsWaitTimer.clear(); + cleared = true; + } + + if (!lurkerSpoutTimer.empty()) + { + lurkerSpoutTimer.clear(); + cleared = true; + } + + if (!lurkerRangedPositions.empty()) + { + lurkerRangedPositions.clear(); + cleared = true; + } + + if (!karathressDpsWaitTimer.empty()) + { + karathressDpsWaitTimer.clear(); + cleared = true; + } + + if (!tidewalkerTankStep.empty()) + { + tidewalkerTankStep.clear(); + cleared = true; + } + + if (!tidewalkerRangedStep.empty()) + { + tidewalkerRangedStep.clear(); + cleared = true; + } + + return cleared; +} + // Trash Mobs // Non-combat method (some colossi leave a toxic pool upon death) @@ -339,10 +402,7 @@ bool HydrossTheUnstableMisdirectBossToTankAction::TryMisdirectToFrostTank( if (botAI->CanCastSpell("misdirection", frostTank)) return botAI->CastSpell("misdirection", frostTank); - if (!bot->HasAura(SPELL_MISDIRECTION)) - return false; - - if (botAI->CanCastSpell("steady shot", hydross)) + if (bot->HasAura(SPELL_MISDIRECTION) && botAI->CanCastSpell("steady shot", hydross)) return botAI->CastSpell("steady shot", hydross); } @@ -356,7 +416,7 @@ bool HydrossTheUnstableMisdirectBossToTankAction::TryMisdirectToNatureTank( for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsAssistTankOfIndex(member, 0)) + if (member && botAI->IsAssistTankOfIndex(member, 0, true)) { natureTank = member; break; @@ -368,10 +428,7 @@ bool HydrossTheUnstableMisdirectBossToTankAction::TryMisdirectToNatureTank( if (botAI->CanCastSpell("misdirection", natureTank)) return botAI->CastSpell("misdirection", natureTank); - if (!bot->HasAura(SPELL_MISDIRECTION)) - return false; - - if (botAI->CanCastSpell("steady shot", hydross)) + if (bot->HasAura(SPELL_MISDIRECTION) && botAI->CanCastSpell("steady shot", hydross)) return botAI->CastSpell("steady shot", hydross); } @@ -433,14 +490,6 @@ bool HydrossTheUnstableManageTimersAction::Execute(Event event) const uint32 instanceId = hydross->GetMap()->GetInstanceId(); const time_t now = std::time(nullptr); - if (hydross->GetHealth() == hydross->GetMaxHealth()) - { - hydrossFrostDpsWaitTimer.erase(instanceId); - hydrossNatureDpsWaitTimer.erase(instanceId); - hydrossChangeToFrostPhaseTimer.erase(instanceId); - hydrossChangeToNaturePhaseTimer.erase(instanceId); - } - if (!hydross->HasAura(SPELL_CORRUPTION)) { hydrossFrostDpsWaitTimer.try_emplace(instanceId, now); @@ -517,9 +566,6 @@ bool TheLurkerBelowSpreadRangedInArcAction::Execute(Event event) if (!lurker) return false; - if (lurker->GetHealth() == lurker->GetMaxHealth()) - lurkerRangedPositions.clear(); - std::vector rangedMembers; if (Group* group = bot->GetGroup()) { @@ -593,9 +639,9 @@ bool TheLurkerBelowTanksPickUpAddsAction::Execute(Event event) if (!mainTank && botAI->IsMainTank(member)) mainTank = member; - else if (!firstAssistTank && botAI->IsAssistTankOfIndex(member, 0)) + else if (!firstAssistTank && botAI->IsAssistTankOfIndex(member, 0, true)) firstAssistTank = member; - else if (!secondAssistTank && botAI->IsAssistTankOfIndex(member, 1)) + else if (!secondAssistTank && botAI->IsAssistTankOfIndex(member, 1, true)) secondAssistTank = member; } } @@ -650,12 +696,6 @@ bool TheLurkerBelowManageSpoutTimerAction::Execute(Event event) const uint32 instanceId = lurker->GetMap()->GetInstanceId(); const time_t now = std::time(nullptr); - if (lurker->GetHealth() == lurker->GetMaxHealth()) - { - lurkerSpoutTimer.erase(instanceId); - return false; - } - auto it = lurkerSpoutTimer.find(instanceId); if (it != lurkerSpoutTimer.end() && it->second <= now) { @@ -865,8 +905,7 @@ bool LeotherasTheBlindMisdirectBossToDemonFormTankAction::Execute(Event event) if (botAI->CanCastSpell("misdirection", demonFormTank)) return botAI->CastSpell("misdirection", demonFormTank); - if (bot->HasAura(SPELL_MISDIRECTION) && - botAI->CanCastSpell("steady shot", leotherasDemon)) + if (bot->HasAura(SPELL_MISDIRECTION) && botAI->CanCastSpell("steady shot", leotherasDemon)) return botAI->CastSpell("steady shot", leotherasDemon); return false; @@ -1127,7 +1166,7 @@ bool FathomLordKarathressMisdirectBossesToTanksAction::Execute(Event event) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsAssistTankOfIndex(member, 0)) + if (member && member->IsAlive() && botAI->IsAssistTankOfIndex(member, 0, false)) { tankTarget = member; break; @@ -1140,7 +1179,7 @@ bool FathomLordKarathressMisdirectBossesToTanksAction::Execute(Event event) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsAssistTankOfIndex(member, 2)) + if (member && member->IsAlive() && botAI->IsAssistTankOfIndex(member, 2, false)) { tankTarget = member; break; @@ -1153,7 +1192,7 @@ bool FathomLordKarathressMisdirectBossesToTanksAction::Execute(Event event) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsAssistTankOfIndex(member, 1)) + if (member && member->IsAlive() && botAI->IsAssistTankOfIndex(member, 1, false)) { tankTarget = member; break; @@ -1173,8 +1212,8 @@ bool FathomLordKarathressMisdirectBossesToTanksAction::Execute(Event event) return false; } -// Kill order is non-standard because bots handle Caribdis Cyclones poorly and need more time to -// get her down than real players (standard approach is ranged DPS would help with Sharkkis first) +// Kill order is non-standard because bots handle Cyclones poorly and need more time +// to get her down than real players (standard is ranged DPS help with Sharkkis first) bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) { // Target priority 1: Spitfire Totems for melee dps @@ -1289,10 +1328,8 @@ bool FathomLordKarathressManageDpsTimerAction::Execute(Event event) if (!karathress) return false; - const time_t now = std::time(nullptr); - - if (karathress->GetHealth() == karathress->GetMaxHealth()) - karathressDpsWaitTimer.insert_or_assign(karathress->GetMap()->GetInstanceId(), now); + karathressDpsWaitTimer.try_emplace( + karathress->GetMap()->GetInstanceId(), std::time(nullptr)); return false; } @@ -1483,23 +1520,6 @@ bool MorogrimTidewalkerPhase2RepositionRangedAction::Execute(Event event) return false; } -bool MorogrimTidewalkerResetPhaseTransitionStepsAction::Execute(Event event) -{ - Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); - if (!tidewalker) - return false; - - const ObjectGuid botGuid = bot->GetGUID(); - - if (tidewalker->GetHealth() == tidewalker->GetMaxHealth()) - { - tidewalkerTankStep.erase(botGuid); - tidewalkerRangedStep.erase(botGuid); - } - - return false; -} - // Lady Vashj bool LadyVashjMainTankPositionBossAction::Execute(Event event) @@ -1738,9 +1758,6 @@ bool LadyVashjStaticChargeMoveAwayFromGroupAction::Execute(Event event) bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event event) { - if (botAI->IsHeal(bot)) - return false; - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); if (!vashj) return false; @@ -1816,7 +1833,7 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event event) targets = { enchanted, elite }; else if (botAI->IsTank(bot)) { - if (botAI->HasCheat(BotCheatMask::raid) && botAI->IsAssistTankOfIndex(bot, 0)) + if (botAI->HasCheat(BotCheatMask::raid) && botAI->IsAssistTankOfIndex(bot, 0, true)) targets = { strider, elite, enchanted }; else targets = { elite, strider, enchanted }; @@ -1835,7 +1852,7 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event event) SetRtiTarget(botAI, "diamond", vashj); targets = { vashj }; } - else if (botAI->IsAssistTankOfIndex(bot, 0)) + else if (botAI->IsAssistTankOfIndex(bot, 0, true)) { if (botAI->HasCheat(BotCheatMask::raid)) targets = { strider, elite, enchanted, vashj }; @@ -1845,7 +1862,6 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event event) } else if (botAI->IsRanged(bot)) { - // targets = { enchanted, strider, elite, vashj }; if (bot->getClass() == CLASS_HUNTER) targets = { sporebat, enchanted, strider, elite, vashj }; else @@ -1934,7 +1950,7 @@ bool LadyVashjMisdirectStriderToFirstAssistTankAction::Execute(Event event) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsAssistTankOfIndex(member, 0)) + if (member && botAI->IsAssistTankOfIndex(member, 0, true)) { firstAssistTank = member; break; @@ -1972,7 +1988,8 @@ bool LadyVashjTankAttackAndMoveAwayStriderAction::Execute(Event event) if (!bot->HasAura(SPELL_FEAR_WARD)) bot->AddAura(SPELL_FEAR_WARD, bot); - if (botAI->IsAssistTankOfIndex(bot, 0) && bot->GetTarget() != strider->GetGUID()) + if (botAI->IsAssistTankOfIndex(bot, 0, true) && + bot->GetTarget() != strider->GetGUID()) return Attack(strider); if (strider->GetVictim() == bot) diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h index 8b7adaa5ce..40d6e137e8 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h @@ -5,6 +5,16 @@ #include "AttackAction.h" #include "MovementActions.h" +// General + +class SerpentShrineCavernClearTimersAndTrackersAction : public Action +{ +public: + SerpentShrineCavernClearTimersAndTrackersAction( + PlayerbotAI* botAI, std::string const name = "serpent shrine cavern clear timers and trackers") : Action(botAI, name) {} + bool Execute(Event event) override; +}; + // Trash class UnderbogColossusEscapeToxicPoolAction : public MovementAction @@ -297,14 +307,6 @@ class MorogrimTidewalkerPhase2RepositionRangedAction : public MovementAction bool Execute(Event event) override; }; -class MorogrimTidewalkerResetPhaseTransitionStepsAction : public Action -{ -public: - MorogrimTidewalkerResetPhaseTransitionStepsAction( - PlayerbotAI* botAI, std::string const name = "morogrim tidewalker reset phase transition steps") : Action(botAI, name) {} - bool Execute(Event event) override; -}; - // Lady Vashj class LadyVashjMainTankPositionBossAction : public AttackAction diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp index 8f550c3178..c49e73578d 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp @@ -39,7 +39,7 @@ float UnderbogColossusEscapeToxicPoolMultiplier::GetValue(Action* action) float HydrossTheUnstableDisableTankActionsMultiplier::GetValue(Action* action) { - if (!botAI->IsMainTank(bot) && !botAI->IsAssistTankOfIndex(bot, 0)) + if (!botAI->IsMainTank(bot) && !botAI->IsAssistTankOfIndex(bot, 0, true)) return 1.0f; Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); @@ -56,7 +56,7 @@ float HydrossTheUnstableDisableTankActionsMultiplier::GetValue(Action* action) !dynamic_cast(action))) { if ((botAI->IsMainTank(bot) && hydross->HasAura(SPELL_CORRUPTION)) || - (botAI->IsAssistTankOfIndex(bot, 0) && !hydross->HasAura(SPELL_CORRUPTION))) + (botAI->IsAssistTankOfIndex(bot, 0, true) && !hydross->HasAura(SPELL_CORRUPTION))) return 0.0f; } @@ -71,7 +71,7 @@ float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action) Unit* waterElemental = AI_VALUE2(Unit*, "find target", "pure spawn of hydross"); Unit* natureElemental = AI_VALUE2(Unit*, "find target", "tainted spawn of hydross"); - if (botAI->IsAssistTank(bot) && !botAI->IsAssistTankOfIndex(bot, 0) && + if (botAI->IsAssistTank(bot) && !botAI->IsAssistTankOfIndex(bot, 0, true) && (waterElemental || natureElemental)) return 1.0f; @@ -102,7 +102,7 @@ float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action) } } - if (hydross->HasAura(SPELL_CORRUPTION) && !botAI->IsAssistTankOfIndex(bot, 0)) + if (hydross->HasAura(SPELL_CORRUPTION) && !botAI->IsAssistTankOfIndex(bot, 0, true)) { auto itDps = hydrossNatureDpsWaitTimer.find(instanceId); auto itPhase = hydrossChangeToNaturePhaseTimer.find(instanceId); diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp index 0bd292431c..0fff6eba2e 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp @@ -3,6 +3,10 @@ void RaidSSCStrategy::InitTriggers(std::vector& triggers) { + // General + triggers.push_back(new TriggerNode("serpent shrine cavern timer bot is not in combat", { + NextAction("serpent shrine cavern clear timers and trackers", ACTION_EMERGENCY + 11) })); + // Trash Mobs triggers.push_back(new TriggerNode("underbog colossus spawned toxic pool after death", { NextAction("underbog colossus escape toxic pool", ACTION_EMERGENCY + 10) })); @@ -111,9 +115,6 @@ void RaidSSCStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("morogrim tidewalker pulling boss", { NextAction("morogrim tidewalker misdirect boss to main tank", ACTION_RAID + 1) })); - triggers.push_back(new TriggerNode("morogrim tidewalker encounter reset", { - NextAction("morogrim tidewalker reset phase transition steps", ACTION_RAID + 2) })); - // Lady Vashj triggers.push_back(new TriggerNode("lady vashj boss engaged by main tank", { NextAction("lady vashj main tank position boss", ACTION_RAID + 1) })); @@ -141,8 +142,10 @@ void RaidSSCStrategy::InitTriggers(std::vector& triggers) NextAction("lady vashj destroy tainted core", ACTION_EMERGENCY + 1) })); triggers.push_back(new TriggerNode("lady vashj adds spawn in phase 2 and phase 3", { - NextAction("lady vashj assign phase 2 and phase 3 dps priority", ACTION_RAID + 1), - NextAction("lady vashj misdirect strider to first assist tank", ACTION_EMERGENCY + 1), + NextAction("lady vashj assign phase 2 and phase 3 dps priority", ACTION_RAID + 1) })); + + triggers.push_back(new TriggerNode("lady vashj coilfang strider is approaching", { + NextAction("lady vashj misdirect strider to first assist tank", ACTION_EMERGENCY + 2), NextAction("lady vashj tank attack and move away strider", ACTION_EMERGENCY + 1) })); triggers.push_back(new TriggerNode("lady vashj toxic sporebats are spewing poison clouds", { diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h index eaee4c81ba..71a7223a82 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h @@ -9,6 +9,10 @@ class RaidSSCTriggerContext : public NamedObjectContext public: RaidSSCTriggerContext() { + // General + creators["serpent shrine cavern timer bot is not in combat"] = + &RaidSSCTriggerContext::serpent_shrine_cavern_timer_bot_is_not_in_combat; + // Trash creators["underbog colossus spawned toxic pool after death"] = &RaidSSCTriggerContext::underbog_colossus_spawned_toxic_pool_after_death; @@ -117,9 +121,6 @@ class RaidSSCTriggerContext : public NamedObjectContext creators["morogrim tidewalker water globules are incoming"] = &RaidSSCTriggerContext::morogrim_tidewalker_water_globules_are_incoming; - creators["morogrim tidewalker encounter reset"] = - &RaidSSCTriggerContext::morogrim_tidewalker_encounter_reset; - // Lady Vashj creators["lady vashj boss engaged by main tank"] = &RaidSSCTriggerContext::lady_vashj_boss_engaged_by_main_tank; @@ -139,6 +140,9 @@ class RaidSSCTriggerContext : public NamedObjectContext creators["lady vashj adds spawn in phase 2 and phase 3"] = &RaidSSCTriggerContext::lady_vashj_adds_spawn_in_phase_2_and_phase_3; + creators["lady vashj coilfang strider is approaching"] = + &RaidSSCTriggerContext::lady_vashj_coilfang_strider_is_approaching; + creators["lady vashj tainted elemental cheat"] = &RaidSSCTriggerContext::lady_vashj_tainted_elemental_cheat; @@ -159,6 +163,10 @@ class RaidSSCTriggerContext : public NamedObjectContext } private: + // General + static Trigger* serpent_shrine_cavern_timer_bot_is_not_in_combat( + PlayerbotAI* botAI) { return new SerpentShrineCavernTimerBotIsNotInCombatTrigger(botAI); } + // Trash static Trigger* underbog_colossus_spawned_toxic_pool_after_death( PlayerbotAI* botAI) { return new UnderbogColossusSpawnedToxicPoolAfterDeathTrigger(botAI); } @@ -267,9 +275,6 @@ class RaidSSCTriggerContext : public NamedObjectContext static Trigger* morogrim_tidewalker_water_globules_are_incoming( PlayerbotAI* botAI) { return new MorogrimTidewalkerWaterGlobulesAreIncomingTrigger(botAI); } - static Trigger* morogrim_tidewalker_encounter_reset( - PlayerbotAI* botAI) { return new MorogrimTidewalkerEncounterResetTrigger(botAI); } - // Lady Vashj static Trigger* lady_vashj_boss_engaged_by_main_tank( PlayerbotAI* botAI) { return new LadyVashjBossEngagedByMainTankTrigger(botAI); } @@ -289,6 +294,9 @@ class RaidSSCTriggerContext : public NamedObjectContext static Trigger* lady_vashj_adds_spawn_in_phase_2_and_phase_3( PlayerbotAI* botAI) { return new LadyVashjAddsSpawnInPhase2AndPhase3Trigger(botAI); } + static Trigger* lady_vashj_coilfang_strider_is_approaching( + PlayerbotAI* botAI) { return new LadyVashjCoilfangStriderIsApproachingTrigger(botAI); } + static Trigger* lady_vashj_tainted_elemental_cheat( PlayerbotAI* botAI) { return new LadyVashjTaintedElementalCheatTrigger(botAI); } diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp index d745583a15..6a345fb559 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp @@ -9,6 +9,12 @@ using namespace SerpentShrineCavernHelpers; +// General +bool SerpentShrineCavernTimerBotIsNotInCombatTrigger::IsActive() +{ + return IsInstanceTimerManager(botAI, bot) && !bot->IsInCombat(); +} + // Trash Mobs bool UnderbogColossusSpawnedToxicPoolAfterDeathTrigger::IsActive() @@ -36,7 +42,7 @@ bool HydrossTheUnstableBotIsFrostTankTrigger::IsActive() bool HydrossTheUnstableBotIsNatureTankTrigger::IsActive() { - if (!botAI->IsAssistTankOfIndex(bot, 0)) + if (!botAI->IsAssistTankOfIndex(bot, 0, true)) return false; return AI_VALUE2(Unit*, "find target", "hydross the unstable"); @@ -44,7 +50,8 @@ bool HydrossTheUnstableBotIsNatureTankTrigger::IsActive() bool HydrossTheUnstableElementalsSpawnedTrigger::IsActive() { - if (botAI->IsHeal(bot) || botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0)) + if (botAI->IsHeal(bot) || botAI->IsMainTank(bot) || + botAI->IsAssistTankOfIndex(bot, 0, true)) return false; Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); @@ -75,7 +82,7 @@ bool HydrossTheUnstableAggroResetsUponPhaseChangeTrigger::IsActive() { if (bot->getClass() == CLASS_HUNTER || botAI->IsMainTank(bot) || - botAI->IsAssistTankOfIndex(bot, 0) || + botAI->IsAssistTankOfIndex(bot, 0, true) || botAI->IsHeal(bot)) return false; @@ -163,9 +170,9 @@ bool TheLurkerBelowBossIsSubmergedTrigger::IsActive() if (!mainTank && memberAI->IsMainTank(member)) mainTank = member; - else if (!firstAssistTank && memberAI->IsAssistTankOfIndex(member, 0)) + else if (!firstAssistTank && memberAI->IsAssistTankOfIndex(member, 0, true)) firstAssistTank = member; - else if (!secondAssistTank && memberAI->IsAssistTankOfIndex(member, 1)) + else if (!secondAssistTank && memberAI->IsAssistTankOfIndex(member, 1, true)) secondAssistTank = member; } @@ -282,7 +289,7 @@ bool FathomLordKarathressBossEngagedByMainTankTrigger::IsActive() bool FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger::IsActive() { - if (!botAI->IsAssistTankOfIndex(bot, 0)) + if (!botAI->IsAssistTankOfIndex(bot, 0, false)) return false; return AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); @@ -290,7 +297,7 @@ bool FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger::IsActive() bool FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger::IsActive() { - if (!botAI->IsAssistTankOfIndex(bot, 1)) + if (!botAI->IsAssistTankOfIndex(bot, 1, false)) return false; return AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis"); @@ -298,7 +305,7 @@ bool FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger::IsActive() bool FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger::IsActive() { - if (!botAI->IsAssistTankOfIndex(bot, 2)) + if (!botAI->IsAssistTankOfIndex(bot, 2, false)) return false; return AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess"); @@ -322,7 +329,7 @@ bool FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger::IsActive() if (!member || !member->IsAlive()) continue; - if (botAI->IsAssistTankOfIndex(member, 0)) + if (botAI->IsAssistTankOfIndex(member, 0, false)) { firstAssistTank = member; break; @@ -350,15 +357,15 @@ bool FathomLordKarathressDeterminingKillOrderTrigger::IsActive() if (botAI->IsDps(bot)) return true; - if (botAI->IsAssistTankOfIndex(bot, 0) && + if (botAI->IsAssistTankOfIndex(bot, 0, false) && !AI_VALUE2(Unit*, "find target", "fathom-guard caribdis")) return true; - if (botAI->IsAssistTankOfIndex(bot, 1) && + if (botAI->IsAssistTankOfIndex(bot, 1, false) && !AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis")) return true; - if (botAI->IsAssistTankOfIndex(bot, 2) && + if (botAI->IsAssistTankOfIndex(bot, 2, false) && !AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess")) return true; @@ -401,12 +408,6 @@ bool MorogrimTidewalkerWaterGlobulesAreIncomingTrigger::IsActive() return tidewalker && tidewalker->GetHealthPct() < 25.0f; } -bool MorogrimTidewalkerEncounterResetTrigger::IsActive() -{ - Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); - return tidewalker && tidewalker->GetHealthPct() > 99.8f; -} - // Lady Vashj bool LadyVashjBossEngagedByMainTankTrigger::IsActive() @@ -472,10 +473,18 @@ bool LadyVashjPullingBossInPhase1AndPhase3Trigger::IsActive() bool LadyVashjAddsSpawnInPhase2AndPhase3Trigger::IsActive() { + if (botAI->IsHeal(bot)) + return false; + return AI_VALUE2(Unit*, "find target", "lady vashj") && !IsLadyVashjInPhase1(botAI); } +bool LadyVashjCoilfangStriderIsApproachingTrigger::IsActive() +{ + return AI_VALUE2(Unit*, "find target", "coilfang strider"); +} + bool LadyVashjTaintedElementalCheatTrigger::IsActive() { if (!botAI->HasCheat(BotCheatMask::raid)) diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h index ad3b76d524..2b6e9d300f 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h @@ -3,6 +3,16 @@ #include "Trigger.h" +// General + +class SerpentShrineCavernTimerBotIsNotInCombatTrigger : public Trigger +{ +public: + SerpentShrineCavernTimerBotIsNotInCombatTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "serpent shrine cavern timer bot is not in combat") {} + bool IsActive() override; +}; + // Trash class UnderbogColossusSpawnedToxicPoolAfterDeathTrigger : public Trigger @@ -295,14 +305,6 @@ class MorogrimTidewalkerWaterGlobulesAreIncomingTrigger : public Trigger bool IsActive() override; }; -class MorogrimTidewalkerEncounterResetTrigger : public Trigger -{ -public: - MorogrimTidewalkerEncounterResetTrigger( - PlayerbotAI* botAI) : Trigger(botAI, "morogrim tidewalker encounter reset") {} - bool IsActive() override; -}; - // Lady Vashj class LadyVashjBossEngagedByMainTankTrigger : public Trigger @@ -353,6 +355,14 @@ class LadyVashjAddsSpawnInPhase2AndPhase3Trigger : public Trigger bool IsActive() override; }; +class LadyVashjCoilfangStriderIsApproachingTrigger : public Trigger +{ +public: + LadyVashjCoilfangStriderIsApproachingTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "lady vashj coilfang strider is approaching") {} + bool IsActive() override; +}; + class LadyVashjTaintedElementalCheatTrigger : public Trigger { public: From aff855cb5c8021b402f0f7ad03cf83df2d34d6fc Mon Sep 17 00:00:00 2001 From: crow Date: Wed, 14 Jan 2026 17:40:26 -0600 Subject: [PATCH 15/25] update timers and various vashj edits --- .../RaidSSCActionContext.h | 20 +- .../serpentshrinecavern/RaidSSCActions.cpp | 276 +++++++++--------- .../serpentshrinecavern/RaidSSCActions.h | 20 +- .../RaidSSCMultipliers.cpp | 46 ++- .../serpentshrinecavern/RaidSSCMultipliers.h | 6 +- .../serpentshrinecavern/RaidSSCStrategy.cpp | 12 +- .../RaidSSCTriggerContext.h | 20 +- .../serpentshrinecavern/RaidSSCTriggers.cpp | 32 +- .../serpentshrinecavern/RaidSSCTriggers.h | 22 +- 9 files changed, 239 insertions(+), 215 deletions(-) diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h b/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h index e273dbe11e..a35329fc47 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h @@ -10,8 +10,8 @@ class RaidSSCActionContext : public NamedObjectContext RaidSSCActionContext() { // General - creators["serpent shrine cavern clear timers and trackers"] = - &RaidSSCActionContext::serpent_shrine_cavern_clear_timers_and_trackers; + creators["serpent shrine cavern erase timers and trackers"] = + &RaidSSCActionContext::serpent_shrine_cavern_erase_timers_and_trackers; // Trash creators["underbog colossus escape toxic pool"] = @@ -158,20 +158,20 @@ class RaidSSCActionContext : public NamedObjectContext creators["lady vashj destroy tainted core"] = &RaidSSCActionContext::lady_vashj_destroy_tainted_core; + creators["lady vashj erase core passing trackers"] = + &RaidSSCActionContext::lady_vashj_erase_core_passing_trackers; + creators["lady vashj avoid toxic spores"] = &RaidSSCActionContext::lady_vashj_avoid_toxic_spores; creators["lady vashj use free action abilities"] = &RaidSSCActionContext::lady_vashj_use_free_action_abilities; - - creators["lady vashj manage trackers"] = - &RaidSSCActionContext::lady_vashj_manage_trackers; } private: // General - static Action* serpent_shrine_cavern_clear_timers_and_trackers( - PlayerbotAI* botAI) { return new SerpentShrineCavernClearTimersAndTrackersAction(botAI); } + static Action* serpent_shrine_cavern_erase_timers_and_trackers( + PlayerbotAI* botAI) { return new SerpentShrineCavernEraseTimersAndTrackersAction(botAI); } // Trash static Action* underbog_colossus_escape_toxic_pool( @@ -318,14 +318,14 @@ class RaidSSCActionContext : public NamedObjectContext static Action* lady_vashj_destroy_tainted_core( PlayerbotAI* botAI) { return new LadyVashjDestroyTaintedCoreAction(botAI); } + static Action* lady_vashj_erase_core_passing_trackers( + PlayerbotAI* botAI) { return new LadyVashjEraseCorePassingTrackersAction(botAI); } + static Action* lady_vashj_avoid_toxic_spores( PlayerbotAI* botAI) { return new LadyVashjAvoidToxicSporesAction(botAI); } static Action* lady_vashj_use_free_action_abilities( PlayerbotAI* botAI) { return new LadyVashjUseFreeActionAbilitiesAction(botAI); } - - static Action* lady_vashj_manage_trackers( - PlayerbotAI* botAI) { return new LadyVashjManageTrackersAction(botAI); } }; #endif diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp index 93bcad9426..315ebab1ab 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp @@ -11,65 +11,41 @@ using namespace SerpentShrineCavernHelpers; // General -bool SerpentShrineCavernClearTimersAndTrackersAction::Execute(Event event) +bool SerpentShrineCavernEraseTimersAndTrackersAction::Execute(Event event) { - bool cleared = false; - - if (!hydrossChangeToNaturePhaseTimer.empty()) - { - hydrossChangeToNaturePhaseTimer.clear(); - cleared = true; - } - - if (!hydrossChangeToFrostPhaseTimer.empty()) - { - hydrossChangeToFrostPhaseTimer.clear(); - cleared = true; - } - - if (!hydrossNatureDpsWaitTimer.empty()) - { - hydrossNatureDpsWaitTimer.clear(); - cleared = true; - } - - if (!hydrossFrostDpsWaitTimer.empty()) - { - hydrossFrostDpsWaitTimer.clear(); - cleared = true; - } - - if (!lurkerSpoutTimer.empty()) - { - lurkerSpoutTimer.clear(); - cleared = true; - } + const uint32 instanceId = bot->GetMap()->GetInstanceId(); + const ObjectGuid guid = bot->GetGUID(); - if (!lurkerRangedPositions.empty()) - { - lurkerRangedPositions.clear(); - cleared = true; - } + bool erased = false; - if (!karathressDpsWaitTimer.empty()) - { - karathressDpsWaitTimer.clear(); - cleared = true; - } + if (lurkerRangedPositions.erase(guid)) + erased = true; + if (tidewalkerTankStep.erase(guid)) + erased = true; + if (tidewalkerRangedStep.erase(guid)) + erased = true; + if (vashjRangedPositions.erase(guid)) + erased = true; + if (hasReachedVashjRangedPosition.erase(guid)) + erased = true; - if (!tidewalkerTankStep.empty()) + if (IsInstanceTimerManager(botAI, bot)) { - tidewalkerTankStep.clear(); - cleared = true; + if (hydrossChangeToNaturePhaseTimer.erase(instanceId)) + erased = true; + if (hydrossChangeToFrostPhaseTimer.erase(instanceId)) + erased = true; + if (hydrossNatureDpsWaitTimer.erase(instanceId)) + erased = true; + if (hydrossFrostDpsWaitTimer.erase(instanceId)) + erased = true; + if (lurkerSpoutTimer.erase(instanceId)) + erased = true; + if (karathressDpsWaitTimer.erase(instanceId)) + erased = true; } - if (!tidewalkerRangedStep.empty()) - { - tidewalkerRangedStep.clear(); - cleared = true; - } - - return cleared; + return erased; } // Trash Mobs @@ -1416,8 +1392,7 @@ bool MorogrimTidewalkerMoveBossToTankPositionAction::MoveToPhase2TankPosition(Un const Position& phase2 = TIDEWALKER_PHASE_2_TANK_POSITION; const Position& transition = TIDEWALKER_PHASE_TRANSITION_WAYPOINT; - const ObjectGuid botGuid = bot->GetGUID(); - auto itStep = tidewalkerTankStep.find(botGuid); + auto itStep = tidewalkerTankStep.find(bot->GetGUID()); uint8 step = (itStep != tidewalkerTankStep.end()) ? itStep->second : 0; if (step == 0) @@ -1437,7 +1412,7 @@ bool MorogrimTidewalkerMoveBossToTankPositionAction::MoveToPhase2TankPosition(Un false, false, MovementPriority::MOVEMENT_COMBAT, true, true); } else - tidewalkerTankStep.try_emplace(botGuid, 1); + tidewalkerTankStep.try_emplace(bot->GetGUID(), 1); } if (step == 1) @@ -1472,8 +1447,7 @@ bool MorogrimTidewalkerPhase2RepositionRangedAction::Execute(Event event) const Position& phase2 = TIDEWALKER_PHASE_2_RANGED_POSITION; const Position& transition = TIDEWALKER_PHASE_TRANSITION_WAYPOINT; - const ObjectGuid botGuid = bot->GetGUID(); - auto itStep = tidewalkerRangedStep.find(botGuid); + auto itStep = tidewalkerRangedStep.find(bot->GetGUID()); uint8 step = (itStep != tidewalkerRangedStep.end()) ? itStep->second : 0; if (step == 0) @@ -1494,7 +1468,7 @@ bool MorogrimTidewalkerPhase2RepositionRangedAction::Execute(Event event) } else { - tidewalkerRangedStep.try_emplace(botGuid, 1); + tidewalkerRangedStep.try_emplace(bot->GetGUID(), 1); step = 1; } } @@ -1762,6 +1736,20 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event event) if (!vashj) return false; + const Position& center = VASHJ_PLATFORM_CENTER_POSITION; + float platformZ = center.GetPositionZ(); + if (bot->GetPositionZ() - platformZ > 2.0f) + { + // This block is needed to prevent bots from floating into the air to attack sporebats + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + bot->StopMoving(); + bot->GetMotionMaster()->Clear(); + bot->TeleportTo(SSC_MAP_ID, bot->GetPositionX(), bot->GetPositionY(), + platformZ, bot->GetOrientation()); + return true; + } + auto const& attackers = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); Unit* target = nullptr; @@ -1771,9 +1759,8 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event event) Unit* sporebat = nullptr; // Search and attack radius are intended to keep bots on the platform (not go down the stairs) - const Position& center = VASHJ_PLATFORM_CENTER_POSITION; const float maxSearchRange = - botAI->IsRangedDps(bot) ? 60.0f : (botAI->IsMelee(bot) ? 55.0f : 40.0f); + botAI->IsRanged(bot) ? 60.0f : 55.0f; const float maxPursueRange = maxSearchRange - 5.0f; for (auto guid : attackers) @@ -1804,7 +1791,7 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event event) break; case NPC_TOXIC_SPOREBAT: - if (!sporebat || bot->GetDistance(unit) < bot->GetDistance(sporebat)) + if (!sporebat || bot->GetExactDist2d(unit) < bot->GetExactDist2d(sporebat)) sporebat = unit; break; @@ -1862,6 +1849,7 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event event) } else if (botAI->IsRanged(bot)) { + // Hunters are assigned to kill Sporebats in Phase 3 if (bot->getClass() == CLASS_HUNTER) targets = { sporebat, enchanted, strider, elite, vashj }; else @@ -1875,38 +1863,30 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event event) for (Unit* candidate : targets) { - if (candidate) + if (candidate && bot->GetExactDist2d(candidate) <= maxPursueRange) { target = candidate; break; } } - if (bot->GetVictim() == vashj && IsLadyVashjInPhase2(botAI)) - { - bot->AttackStop(); - bot->InterruptNonMeleeSpells(true); - bot->SetTarget(ObjectGuid::Empty); - bot->SetSelection(ObjectGuid()); - } - Unit* currentTarget = context->GetValue("current target")->Get(); - if (target && currentTarget == target && IsValidLadyVashjCombatNpc(currentTarget, botAI)) - return false; - - if (target && bot->GetExactDist2d(target) <= maxPursueRange && - bot->GetTarget() != target->GetGUID()) - return Attack(target); if (currentTarget && !IsValidLadyVashjCombatNpc(currentTarget, botAI)) { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); context->GetValue("current target")->Set(nullptr); bot->SetTarget(ObjectGuid::Empty); bot->SetSelection(ObjectGuid()); + currentTarget = nullptr; } - // If bots have wandered too far from the center and are not attacking anything, move them back - if (bot->GetVictim() == nullptr) + if (target && currentTarget != target && bot->GetTarget() != target->GetGUID()) + return Attack(target); + + // If bots have wandered too far from the center, move them back + if (bot->GetExactDist2d(center.GetPositionX(), center.GetPositionY()) > 55.0f) { Player* designatedLooter = GetDesignatedCoreLooter(bot->GetGroup(), botAI); Player* firstCorePasser = GetFirstTaintedCorePasser(bot->GetGroup(), botAI); @@ -1919,12 +1899,8 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event event) return false; } - const Position& center = VASHJ_PLATFORM_CENTER_POSITION; - if (bot->GetExactDist2d(center.GetPositionX(), center.GetPositionY()) > 35.0f) - { - return MoveInside(SSC_MAP_ID, center.GetPositionX(), center.GetPositionY(), - center.GetPositionZ(), 30.0f, MovementPriority::MOVEMENT_COMBAT); - } + return MoveInside(SSC_MAP_ID, center.GetPositionX(), center.GetPositionY(), + center.GetPositionZ(), 40.0f, MovementPriority::MOVEMENT_COMBAT); } return false; @@ -2102,10 +2078,8 @@ bool LadyVashjLootTaintedCoreAction::Execute(Event) return MoveTo(object, 2.0f, MovementPriority::MOVEMENT_FORCED); OpenLootAction open(botAI); - bool opened = open.Execute(Event()); - - if (!opened) - return opened; + if (!open.Execute(Event())) + return false; if (Group* group = bot->GetGroup()) { @@ -2172,9 +2146,8 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) Player* fourthCorePasser = GetFourthTaintedCorePasser(group, botAI); const uint32 instanceId = vashj->GetMap()->GetInstanceId(); - Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental"); Unit* closestTrigger = nullptr; - if (tainted) + if (Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental")) { closestTrigger = GetNearestActiveShieldGeneratorTriggerByEntry(tainted); if (closestTrigger) @@ -2188,7 +2161,7 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) if (snapUnit) closestTrigger = snapUnit; else - nearestTriggerGuid.clear(); + nearestTriggerGuid.erase(instanceId); } if (!firstCorePasser || !secondCorePasser || !thirdCorePasser || @@ -2244,16 +2217,16 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) auto [it, inserted] = lastImbueAttempt.try_emplace(instanceId, now); if (inserted) { - botAI->ImbueItem(item, firstCorePasser); lastCoreInInventoryTime[instanceId] = now; + botAI->ImbueItem(item, firstCorePasser); ScheduleStoreCoreAfterImbue(botAI, bot, firstCorePasser); return true; } if ((now - it->second) >= 2) { it->second = now; - botAI->ImbueItem(item, firstCorePasser); lastCoreInInventoryTime[instanceId] = now; + botAI->ImbueItem(item, firstCorePasser); ScheduleStoreCoreAfterImbue(botAI, bot, firstCorePasser); return true; } @@ -2271,8 +2244,8 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) auto [it, inserted] = lastImbueAttempt.try_emplace(instanceId, now); if (inserted) { - botAI->ImbueItem(item, secondCorePasser); lastCoreInInventoryTime[instanceId] = now; + botAI->ImbueItem(item, secondCorePasser); intendedLineup.erase(bot->GetGUID()); ScheduleStoreCoreAfterImbue(botAI, bot, secondCorePasser); return true; @@ -2280,8 +2253,8 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) if ((now - it->second) >= 2) { it->second = now; - botAI->ImbueItem(item, secondCorePasser); lastCoreInInventoryTime[instanceId] = now; + botAI->ImbueItem(item, secondCorePasser); intendedLineup.erase(bot->GetGUID()); ScheduleStoreCoreAfterImbue(botAI, bot, secondCorePasser); return true; @@ -2303,8 +2276,8 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) auto [it, inserted] = lastImbueAttempt.try_emplace(instanceId, now); if (inserted) { - botAI->ImbueItem(item, thirdCorePasser); lastCoreInInventoryTime[instanceId] = now; + botAI->ImbueItem(item, thirdCorePasser); intendedLineup.erase(bot->GetGUID()); ScheduleStoreCoreAfterImbue(botAI, bot, thirdCorePasser); return true; @@ -2312,8 +2285,8 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) if ((now - it->second) >= 2) { it->second = now; - botAI->ImbueItem(item, thirdCorePasser); lastCoreInInventoryTime[instanceId] = now; + botAI->ImbueItem(item, thirdCorePasser); intendedLineup.erase(bot->GetGUID()); ScheduleStoreCoreAfterImbue(botAI, bot, thirdCorePasser); return true; @@ -2336,8 +2309,8 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) auto [it, inserted] = lastImbueAttempt.try_emplace(instanceId, now); if (inserted) { - botAI->ImbueItem(item, fourthCorePasser); lastCoreInInventoryTime[instanceId] = now; + botAI->ImbueItem(item, fourthCorePasser); intendedLineup.erase(bot->GetGUID()); ScheduleStoreCoreAfterImbue(botAI, bot, fourthCorePasser); return true; @@ -2345,8 +2318,8 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) if ((now - it->second) >= 2) { it->second = now; - botAI->ImbueItem(item, fourthCorePasser); lastCoreInInventoryTime[instanceId] = now; + botAI->ImbueItem(item, fourthCorePasser); intendedLineup.erase(bot->GetGUID()); ScheduleStoreCoreAfterImbue(botAI, bot, fourthCorePasser); return true; @@ -2357,9 +2330,7 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) // Fourth core passer: the fourth passer is rarely needed and no more than // four ever should be, so it should use the Core on the nearest generator else if (bot == fourthCorePasser) - { UseCoreOnNearestGenerator(); - } } return false; @@ -2638,42 +2609,47 @@ bool LadyVashjPassTheTaintedCoreAction::UseCoreOnNearestGenerator() if (bot->GetExactDist2d(generator) > 4.5f) return false; - if (Item* core = bot->GetItemByEntry(ITEM_TAINTED_CORE)) - { - const uint8 bagIndex = core->GetBagSlot(); - const uint8 slot = core->GetSlot(); - const uint8 cast_count = 0; - uint32 spellId = 0; + Item* core = bot->GetItemByEntry(ITEM_TAINTED_CORE); + if (!core) + return false; - for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) - { - if (core->GetTemplate()->Spells[i].SpellId > 0) - { - spellId = core->GetTemplate()->Spells[i].SpellId; - break; - } - } + if (bot->CanUseItem(core) != EQUIP_ERR_OK) + return false; - const ObjectGuid item_guid = core->GetGUID(); - const uint32 glyphIndex = 0; - const uint8 castFlags = 0; + if (bot->IsNonMeleeSpellCast(false)) + return false; - WorldPacket packet(CMSG_USE_ITEM); - packet << bagIndex; - packet << slot; - packet << cast_count; - packet << spellId; - packet << item_guid; - packet << glyphIndex; - packet << castFlags; - packet << (uint32)TARGET_FLAG_GAMEOBJECT; - packet << generator->GetGUID().WriteAsPacked(); + const uint8 bagIndex = core->GetBagSlot(); + const uint8 slot = core->GetSlot(); + const uint8 cast_count = 0; + uint32 spellId = 0; - bot->GetSession()->HandleUseItemOpcode(packet); - return true; + for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) + { + if (core->GetTemplate()->Spells[i].SpellId > 0) + { + spellId = core->GetTemplate()->Spells[i].SpellId; + break; + } } - return false; + const ObjectGuid item_guid = core->GetGUID(); + const uint32 glyphIndex = 0; + const uint8 castFlags = 0; + + WorldPacket packet(CMSG_USE_ITEM); + packet << bagIndex; + packet << slot; + packet << cast_count; + packet << spellId; + packet << item_guid; + packet << glyphIndex; + packet << castFlags; + packet << (uint32)TARGET_FLAG_GAMEOBJECT; + packet << generator->GetGUID().WriteAsPacked(); + + bot->GetSession()->HandleUseItemOpcode(packet); + return true; } // For dead bots to destroy their cores so the logic can reset for the next attempt @@ -2689,6 +2665,29 @@ bool LadyVashjDestroyTaintedCoreAction::Execute(Event event) return false; } +// This needs to be separate from the general map erasing logic because somehow +// Bots tend to end up out of combat during the Vashj encounter +bool LadyVashjEraseCorePassingTrackersAction::Execute(Event event) +{ + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + if (!vashj) + return false; + + const uint32 instanceId = vashj->GetMap()->GetInstanceId(); + + bool erased = false; + if (nearestTriggerGuid.erase(instanceId)) + erased = true; + if (lastImbueAttempt.erase(instanceId)) + erased = true; + if (lastCoreInInventoryTime.erase(instanceId)) + erased = true; + if (intendedLineup.erase(bot->GetGUID())) + erased = true; + + return erased; +} + // The standard "avoid aoe" strategy does work for Toxic Spores, but this method // provides more buffer distance and limits the area in which bots can move // so that they do not go down the stairs @@ -2931,18 +2930,3 @@ bool LadyVashjUseFreeActionAbilitiesAction::Execute(Event event) return false; } - -bool LadyVashjManageTrackersAction::Execute(Event event) -{ - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - if (!vashj) - return false; - - vashjRangedPositions.clear(); - hasReachedVashjRangedPosition.clear(); - nearestTriggerGuid.clear(); - lastImbueAttempt.clear(); - intendedLineup.clear(); - - return false; -} diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h index 40d6e137e8..bdcdf24103 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h @@ -7,11 +7,11 @@ // General -class SerpentShrineCavernClearTimersAndTrackersAction : public Action +class SerpentShrineCavernEraseTimersAndTrackersAction : public Action { public: - SerpentShrineCavernClearTimersAndTrackersAction( - PlayerbotAI* botAI, std::string const name = "serpent shrine cavern clear timers and trackers") : Action(botAI, name) {} + SerpentShrineCavernEraseTimersAndTrackersAction( + PlayerbotAI* botAI, std::string const name = "serpent shrine cavern erase timers and trackers") : Action(botAI, name) {} bool Execute(Event event) override; }; @@ -416,6 +416,13 @@ class LadyVashjDestroyTaintedCoreAction : public Action bool Execute(Event event) override; }; +class LadyVashjEraseCorePassingTrackersAction : public Action +{ +public: + LadyVashjEraseCorePassingTrackersAction(PlayerbotAI* botAI, std::string const name = "lady vashj erase core passing trackers") : Action(botAI, name) {} + bool Execute(Event event) override; +}; + class LadyVashjAvoidToxicSporesAction : public MovementAction { public: @@ -435,11 +442,4 @@ class LadyVashjUseFreeActionAbilitiesAction : public Action bool Execute(Event event) override; }; -class LadyVashjManageTrackersAction : public Action -{ -public: - LadyVashjManageTrackersAction(PlayerbotAI* botAI, std::string const name = "lady vashj manage trackers") : Action(botAI, name) {} - bool Execute(Event event) override; -}; - #endif diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp index c49e73578d..73c5269aae 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp @@ -4,6 +4,7 @@ #include "ChooseTargetActions.h" #include "DestroyItemAction.h" #include "DKActions.h" +#include "DruidActions.h" #include "DruidBearActions.h" #include "FollowActions.h" #include "GenericSpellActions.h" @@ -545,18 +546,45 @@ float MorogrimTidewalkerMaintainPhase2StackingMultiplier::GetValue(Action* actio } // Wait until phase 3 to use Bloodlust/Heroism -float LadyVashjDelayBloodlustAndHeroismMultiplier::GetValue(Action* action) +// Don't use other major cooldowns in Phase 1, either +float LadyVashjDelayCooldownsMultiplier::GetValue(Action* action) { - if (bot->getClass() != CLASS_SHAMAN) - return 1.0f; + if (bot->getClass() == CLASS_SHAMAN) + { + if (!AI_VALUE2(Unit*, "find target", "lady vashj") || + IsLadyVashjInPhase3(botAI)) + return 1.0f; - if (!AI_VALUE2(Unit*, "find target", "lady vashj") || - IsLadyVashjInPhase3(botAI)) - return 1.0f; + if (dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + } - if (dynamic_cast(action) || - dynamic_cast(action)) - return 0.0f; + if (botAI->IsDps(bot) && !IsLadyVashjInPhase1(botAI)) + { + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; + } return 1.0f; } diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h index 5b9d803075..d477570e59 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h @@ -177,11 +177,11 @@ class MorogrimTidewalkerMaintainPhase2StackingMultiplier : public Multiplier // Lady Vashj -class LadyVashjDelayBloodlustAndHeroismMultiplier : public Multiplier +class LadyVashjDelayCooldownsMultiplier : public Multiplier { public: - LadyVashjDelayBloodlustAndHeroismMultiplier( - PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj delay bloodlust and heroism") {} + LadyVashjDelayCooldownsMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj delay cooldowns") {} virtual float GetValue(Action* action); }; diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp index 0fff6eba2e..d7562d3ff6 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp @@ -4,8 +4,8 @@ void RaidSSCStrategy::InitTriggers(std::vector& triggers) { // General - triggers.push_back(new TriggerNode("serpent shrine cavern timer bot is not in combat", { - NextAction("serpent shrine cavern clear timers and trackers", ACTION_EMERGENCY + 11) })); + triggers.push_back(new TriggerNode("serpent shrine cavern bot is not in combat", { + NextAction("serpent shrine cavern erase timers and trackers", ACTION_EMERGENCY + 11) })); // Trash Mobs triggers.push_back(new TriggerNode("underbog colossus spawned toxic pool after death", { @@ -141,6 +141,9 @@ void RaidSSCStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("lady vashj tainted core is unusable", { NextAction("lady vashj destroy tainted core", ACTION_EMERGENCY + 1) })); + triggers.push_back(new TriggerNode("lady vashj need to reset core passing trackers", { + NextAction("lady vashj erase core passing trackers", ACTION_EMERGENCY + 10) })); + triggers.push_back(new TriggerNode("lady vashj adds spawn in phase 2 and phase 3", { NextAction("lady vashj assign phase 2 and phase 3 dps priority", ACTION_RAID + 1) })); @@ -153,9 +156,6 @@ void RaidSSCStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("lady vashj bot is entangled in toxic spores or static charge", { NextAction("lady vashj use free action abilities", ACTION_EMERGENCY + 7) })); - - triggers.push_back(new TriggerNode("lady vashj need to manage trackers", { - NextAction("lady vashj manage trackers", ACTION_EMERGENCY + 10) })); } void RaidSSCStrategy::InitMultipliers(std::vector& multipliers) @@ -193,7 +193,7 @@ void RaidSSCStrategy::InitMultipliers(std::vector& multipliers) multipliers.push_back(new MorogrimTidewalkerMaintainPhase2StackingMultiplier(botAI)); // Lady Vashj - multipliers.push_back(new LadyVashjDelayBloodlustAndHeroismMultiplier(botAI)); + multipliers.push_back(new LadyVashjDelayCooldownsMultiplier(botAI)); multipliers.push_back(new LadyVashjMaintainPhase1RangedSpreadMultiplier(botAI)); multipliers.push_back(new LadyVashjStaticChargeStayAwayFromGroupMultiplier(botAI)); multipliers.push_back(new LadyVashjDoNotLootTheTaintedCoreMultiplier(botAI)); diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h index 71a7223a82..3a9f846b3a 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h @@ -10,8 +10,8 @@ class RaidSSCTriggerContext : public NamedObjectContext RaidSSCTriggerContext() { // General - creators["serpent shrine cavern timer bot is not in combat"] = - &RaidSSCTriggerContext::serpent_shrine_cavern_timer_bot_is_not_in_combat; + creators["serpent shrine cavern bot is not in combat"] = + &RaidSSCTriggerContext::serpent_shrine_cavern_bot_is_not_in_combat; // Trash creators["underbog colossus spawned toxic pool after death"] = @@ -152,20 +152,20 @@ class RaidSSCTriggerContext : public NamedObjectContext creators["lady vashj tainted core is unusable"] = &RaidSSCTriggerContext::lady_vashj_tainted_core_is_unusable; + creators["lady vashj need to reset core passing trackers"] = + &RaidSSCTriggerContext::lady_vashj_need_to_reset_core_passing_trackers; + creators["lady vashj toxic sporebats are spewing poison clouds"] = &RaidSSCTriggerContext::lady_vashj_toxic_sporebats_are_spewing_poison_clouds; creators["lady vashj bot is entangled in toxic spores or static charge"] = &RaidSSCTriggerContext::lady_vashj_bot_is_entangled_in_toxic_spores_or_static_charge; - - creators["lady vashj need to manage trackers"] = - &RaidSSCTriggerContext::lady_vashj_need_to_manage_trackers; } private: // General - static Trigger* serpent_shrine_cavern_timer_bot_is_not_in_combat( - PlayerbotAI* botAI) { return new SerpentShrineCavernTimerBotIsNotInCombatTrigger(botAI); } + static Trigger* serpent_shrine_cavern_bot_is_not_in_combat( + PlayerbotAI* botAI) { return new SerpentShrineCavernBotIsNotInCombatTrigger(botAI); } // Trash static Trigger* underbog_colossus_spawned_toxic_pool_after_death( @@ -306,14 +306,14 @@ class RaidSSCTriggerContext : public NamedObjectContext static Trigger* lady_vashj_tainted_core_is_unusable( PlayerbotAI* botAI) { return new LadyVashjTaintedCoreIsUnusableTrigger(botAI); } + static Trigger* lady_vashj_need_to_reset_core_passing_trackers( + PlayerbotAI* botAI) { return new LadyVashjNeedToResetCorePassingTrackersTrigger(botAI); } + static Trigger* lady_vashj_toxic_sporebats_are_spewing_poison_clouds( PlayerbotAI* botAI) { return new LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger(botAI); } static Trigger* lady_vashj_bot_is_entangled_in_toxic_spores_or_static_charge( PlayerbotAI* botAI) { return new LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger(botAI); } - - static Trigger* lady_vashj_need_to_manage_trackers( - PlayerbotAI* botAI) { return new LadyVashjNeedToManageTrackersTrigger(botAI); } }; #endif diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp index 6a345fb559..a1f825b2e9 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp @@ -10,9 +10,9 @@ using namespace SerpentShrineCavernHelpers; // General -bool SerpentShrineCavernTimerBotIsNotInCombatTrigger::IsActive() +bool SerpentShrineCavernBotIsNotInCombatTrigger::IsActive() { - return IsInstanceTimerManager(botAI, bot) && !bot->IsInCombat(); + return !bot->IsInCombat(); } // Trash Mobs @@ -614,12 +614,30 @@ bool LadyVashjTaintedCoreIsUnusableTrigger::IsActive() return true; } - if (IsLadyVashjInPhase3(botAI)) - return true; + if (!IsLadyVashjInPhase2(botAI)) + return bot->HasItemCount(ITEM_TAINTED_CORE, 1, false); return false; } +bool LadyVashjNeedToResetCorePassingTrackersTrigger::IsActive() +{ + Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); + if (!vashj || IsLadyVashjInPhase2(botAI)) + return false; + + Group* group = bot->GetGroup(); + if (!group) + return false; + + return IsInstanceTimerManager(botAI, bot) || + GetDesignatedCoreLooter(group, botAI) == bot || + GetFirstTaintedCorePasser(group, botAI) == bot || + GetSecondTaintedCorePasser(group, botAI) == bot || + GetThirdTaintedCorePasser(group, botAI) == bot || + GetFourthTaintedCorePasser(group, botAI) == bot; +} + bool LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger::IsActive() { return IsLadyVashjInPhase3(botAI); @@ -645,9 +663,3 @@ bool LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger::IsActive() return false; } - -bool LadyVashjNeedToManageTrackersTrigger::IsActive() -{ - Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - return vashj && vashj->GetHealthPct() > 99.8f; -} diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h index 2b6e9d300f..0c1becfde7 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h @@ -5,11 +5,11 @@ // General -class SerpentShrineCavernTimerBotIsNotInCombatTrigger : public Trigger +class SerpentShrineCavernBotIsNotInCombatTrigger : public Trigger { public: - SerpentShrineCavernTimerBotIsNotInCombatTrigger( - PlayerbotAI* botAI) : Trigger(botAI, "serpent shrine cavern timer bot is not in combat") {} + SerpentShrineCavernBotIsNotInCombatTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "serpent shrine cavern bot is not in combat") {} bool IsActive() override; }; @@ -387,6 +387,14 @@ class LadyVashjTaintedCoreIsUnusableTrigger : public Trigger bool IsActive() override; }; +class LadyVashjNeedToResetCorePassingTrackersTrigger : public Trigger +{ +public: + LadyVashjNeedToResetCorePassingTrackersTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "lady vashj need to reset core passing trackers") {} + bool IsActive() override; +}; + class LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger : public Trigger { public: @@ -403,12 +411,4 @@ class LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger : public Trigger bool IsActive() override; }; -class LadyVashjNeedToManageTrackersTrigger : public Trigger -{ -public: - LadyVashjNeedToManageTrackersTrigger( - PlayerbotAI* botAI) : Trigger(botAI, "lady vashj need to manage trackers") {} - bool IsActive() override; -}; - #endif From ec9f0215993722161da875b399075c6651950bd0 Mon Sep 17 00:00:00 2001 From: crow Date: Thu, 15 Jan 2026 02:05:12 -0600 Subject: [PATCH 16/25] add checks to map erasing --- .../serpentshrinecavern/RaidSSCActions.cpp | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp index 315ebab1ab..450dad8c7d 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp @@ -17,19 +17,7 @@ bool SerpentShrineCavernEraseTimersAndTrackersAction::Execute(Event event) const ObjectGuid guid = bot->GetGUID(); bool erased = false; - - if (lurkerRangedPositions.erase(guid)) - erased = true; - if (tidewalkerTankStep.erase(guid)) - erased = true; - if (tidewalkerRangedStep.erase(guid)) - erased = true; - if (vashjRangedPositions.erase(guid)) - erased = true; - if (hasReachedVashjRangedPosition.erase(guid)) - erased = true; - - if (IsInstanceTimerManager(botAI, bot)) + if (!AI_VALUE2(Unit*, "find target", "hydross the unstable")) { if (hydrossChangeToNaturePhaseTimer.erase(instanceId)) erased = true; @@ -39,11 +27,33 @@ bool SerpentShrineCavernEraseTimersAndTrackersAction::Execute(Event event) erased = true; if (hydrossFrostDpsWaitTimer.erase(instanceId)) erased = true; + } + else if (!AI_VALUE2(Unit*, "find target", "the lurker below")) + { + if (lurkerRangedPositions.erase(guid)) + erased = true; if (lurkerSpoutTimer.erase(instanceId)) erased = true; + } + else if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) + { if (karathressDpsWaitTimer.erase(instanceId)) erased = true; } + else if (!AI_VALUE2(Unit*, "find target", "morogrim tidewalker")) + { + if (tidewalkerTankStep.erase(guid)) + erased = true; + if (tidewalkerRangedStep.erase(guid)) + erased = true; + } + else if (!AI_VALUE2(Unit*, "find target", "lady vashj")) + { + if (vashjRangedPositions.erase(guid)) + erased = true; + if (hasReachedVashjRangedPosition.erase(guid)) + erased = true; + } return erased; } From 9199d2b8e54881d8d93f30e9fe9789102b839c9b Mon Sep 17 00:00:00 2001 From: crow Date: Fri, 16 Jan 2026 01:54:29 -0600 Subject: [PATCH 17/25] timer fix --- .../serpentshrinecavern/RaidSSCActions.cpp | 97 ++++++++----------- 1 file changed, 41 insertions(+), 56 deletions(-) diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp index 450dad8c7d..3cde7afe5a 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp @@ -19,40 +19,29 @@ bool SerpentShrineCavernEraseTimersAndTrackersAction::Execute(Event event) bool erased = false; if (!AI_VALUE2(Unit*, "find target", "hydross the unstable")) { - if (hydrossChangeToNaturePhaseTimer.erase(instanceId)) - erased = true; - if (hydrossChangeToFrostPhaseTimer.erase(instanceId)) - erased = true; - if (hydrossNatureDpsWaitTimer.erase(instanceId)) - erased = true; - if (hydrossFrostDpsWaitTimer.erase(instanceId)) - erased = true; + erased |= hydrossChangeToNaturePhaseTimer.erase(instanceId) > 0; + erased |= hydrossChangeToFrostPhaseTimer.erase(instanceId) > 0; + erased |= hydrossNatureDpsWaitTimer.erase(instanceId) > 0; + erased |= hydrossFrostDpsWaitTimer.erase(instanceId) > 0; } - else if (!AI_VALUE2(Unit*, "find target", "the lurker below")) + if (!AI_VALUE2(Unit*, "find target", "the lurker below")) { - if (lurkerRangedPositions.erase(guid)) - erased = true; - if (lurkerSpoutTimer.erase(instanceId)) - erased = true; + erased |= lurkerRangedPositions.erase(guid) > 0; + erased |= lurkerSpoutTimer.erase(instanceId) > 0; } - else if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) + if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) { - if (karathressDpsWaitTimer.erase(instanceId)) - erased = true; + erased |= karathressDpsWaitTimer.erase(instanceId) > 0; } - else if (!AI_VALUE2(Unit*, "find target", "morogrim tidewalker")) + if (!AI_VALUE2(Unit*, "find target", "morogrim tidewalker")) { - if (tidewalkerTankStep.erase(guid)) - erased = true; - if (tidewalkerRangedStep.erase(guid)) - erased = true; + erased |= tidewalkerTankStep.erase(guid) > 0; + erased |= tidewalkerRangedStep.erase(guid) > 0; } - else if (!AI_VALUE2(Unit*, "find target", "lady vashj")) + if (!AI_VALUE2(Unit*, "find target", "lady vashj")) { - if (vashjRangedPositions.erase(guid)) - erased = true; - if (hasReachedVashjRangedPosition.erase(guid)) - erased = true; + erased |= vashjRangedPositions.erase(guid) > 0; + erased |= hasReachedVashjRangedPosition.erase(guid) > 0; } return erased; @@ -476,26 +465,26 @@ bool HydrossTheUnstableManageTimersAction::Execute(Event event) const uint32 instanceId = hydross->GetMap()->GetInstanceId(); const time_t now = std::time(nullptr); + bool changed = false; if (!hydross->HasAura(SPELL_CORRUPTION)) { - hydrossFrostDpsWaitTimer.try_emplace(instanceId, now); - hydrossNatureDpsWaitTimer.erase(instanceId); - hydrossChangeToFrostPhaseTimer.erase(instanceId); + changed |= hydrossFrostDpsWaitTimer.try_emplace(instanceId, now).second; + changed |= hydrossNatureDpsWaitTimer.erase(instanceId) > 0; + changed |= hydrossChangeToFrostPhaseTimer.erase(instanceId) > 0; if (HasMarkOfHydrossAt100Percent(bot)) - hydrossChangeToNaturePhaseTimer.try_emplace(instanceId, now); + changed |= hydrossChangeToNaturePhaseTimer.try_emplace(instanceId, now).second; } else { - hydrossNatureDpsWaitTimer.try_emplace(instanceId, now); - hydrossFrostDpsWaitTimer.erase(instanceId); - hydrossChangeToNaturePhaseTimer.erase(instanceId); - + changed |= hydrossNatureDpsWaitTimer.try_emplace(instanceId, now).second; + changed |= hydrossFrostDpsWaitTimer.erase(instanceId) > 0; + changed |= hydrossChangeToNaturePhaseTimer.erase(instanceId) > 0; if (HasMarkOfCorruptionAt100Percent(bot)) - hydrossChangeToFrostPhaseTimer.try_emplace(instanceId, now); + changed |= hydrossChangeToFrostPhaseTimer.try_emplace(instanceId, now).second; } - return false; + return changed; } // The Lurker Below @@ -907,13 +896,13 @@ bool LeotherasTheBlindManageDpsWaitTimersAction::Execute(Event event) const uint32 instanceId = leotheras->GetMap()->GetInstanceId(); const time_t now = std::time(nullptr); + bool changed = false; // Encounter start/reset: clear all timers if (leotheras->HasAura(SPELL_LEOTHERAS_BANISHED)) { - leotherasHumanFormDpsWaitTimer.erase(instanceId); - leotherasDemonFormDpsWaitTimer.erase(instanceId); - leotherasFinalPhaseDpsWaitTimer.erase(instanceId); - return false; + changed |= leotherasHumanFormDpsWaitTimer.erase(instanceId) > 0; + changed |= leotherasDemonFormDpsWaitTimer.erase(instanceId) > 0; + changed |= leotherasFinalPhaseDpsWaitTimer.erase(instanceId) > 0; } // Human Phase @@ -921,24 +910,24 @@ bool LeotherasTheBlindManageDpsWaitTimersAction::Execute(Event event) Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI); if (leotherasHuman && !leotherasPhase3Demon) { - leotherasHumanFormDpsWaitTimer.try_emplace(instanceId, now); - leotherasDemonFormDpsWaitTimer.erase(instanceId); + changed |= leotherasHumanFormDpsWaitTimer.try_emplace(instanceId, now).second; + changed |= leotherasDemonFormDpsWaitTimer.erase(instanceId) > 0; } // Demon Phase else if (Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI)) { - leotherasDemonFormDpsWaitTimer.try_emplace(instanceId, now); - leotherasHumanFormDpsWaitTimer.erase(instanceId); + changed |= leotherasDemonFormDpsWaitTimer.try_emplace(instanceId, now).second; + changed |= leotherasHumanFormDpsWaitTimer.erase(instanceId) > 0; } // Final Phase (<15% HP) else if (leotherasHuman && leotherasPhase3Demon) { - leotherasFinalPhaseDpsWaitTimer.try_emplace(instanceId, now); - leotherasHumanFormDpsWaitTimer.erase(instanceId); - leotherasDemonFormDpsWaitTimer.erase(instanceId); + changed |= leotherasFinalPhaseDpsWaitTimer.try_emplace(instanceId, now).second; + changed |= leotherasHumanFormDpsWaitTimer.erase(instanceId) > 0; + changed |= leotherasDemonFormDpsWaitTimer.erase(instanceId) > 0; } - return false; + return changed; } // Fathom-Lord Karathress @@ -2686,14 +2675,10 @@ bool LadyVashjEraseCorePassingTrackersAction::Execute(Event event) const uint32 instanceId = vashj->GetMap()->GetInstanceId(); bool erased = false; - if (nearestTriggerGuid.erase(instanceId)) - erased = true; - if (lastImbueAttempt.erase(instanceId)) - erased = true; - if (lastCoreInInventoryTime.erase(instanceId)) - erased = true; - if (intendedLineup.erase(bot->GetGUID())) - erased = true; + erased |= nearestTriggerGuid.erase(instanceId) > 0; + erased |= lastImbueAttempt.erase(instanceId) > 0; + erased |= lastCoreInInventoryTime.erase(instanceId) > 0; + erased |= intendedLineup.erase(bot->GetGUID()) > 0; return erased; } From f528c68015e25eac26e03cf1fa9305d1d997c5c5 Mon Sep 17 00:00:00 2001 From: crow Date: Fri, 16 Jan 2026 18:14:29 -0600 Subject: [PATCH 18/25] redo Leo Inner Demons --- .../RaidSSCActionContext.h | 8 +- .../serpentshrinecavern/RaidSSCActions.cpp | 261 +++++++++++++++++- .../serpentshrinecavern/RaidSSCActions.h | 6 +- .../serpentshrinecavern/RaidSSCHelpers.cpp | 2 +- .../serpentshrinecavern/RaidSSCHelpers.h | 8 +- .../RaidSSCMultipliers.cpp | 36 ++- .../serpentshrinecavern/RaidSSCMultipliers.h | 8 + .../serpentshrinecavern/RaidSSCStrategy.cpp | 5 +- .../RaidSSCTriggerContext.h | 8 +- .../serpentshrinecavern/RaidSSCTriggers.cpp | 143 +++++----- .../serpentshrinecavern/RaidSSCTriggers.h | 6 +- 11 files changed, 378 insertions(+), 113 deletions(-) diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h b/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h index a35329fc47..dde6205201 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h @@ -74,8 +74,8 @@ class RaidSSCActionContext : public NamedObjectContext creators["leotheras the blind melee dps run away from boss"] = &RaidSSCActionContext::leotheras_the_blind_melee_dps_run_away_from_boss; - creators["leotheras the blind inner demon cheat"] = - &RaidSSCActionContext::leotheras_the_blind_inner_demon_cheat; + creators["leotheras the blind destroy inner demon"] = + &RaidSSCActionContext::leotheras_the_blind_destroy_inner_demon; creators["leotheras the blind final phase assign dps priority"] = &RaidSSCActionContext::leotheras_the_blind_final_phase_assign_dps_priority; @@ -234,8 +234,8 @@ class RaidSSCActionContext : public NamedObjectContext static Action* leotheras_the_blind_melee_dps_run_away_from_boss( PlayerbotAI* botAI) { return new LeotherasTheBlindMeleeDpsRunAwayFromBossAction(botAI); } - static Action* leotheras_the_blind_inner_demon_cheat( - PlayerbotAI* botAI) { return new LeotherasTheBlindInnerDemonCheatAction(botAI); } + static Action* leotheras_the_blind_destroy_inner_demon( + PlayerbotAI* botAI) { return new LeotherasTheBlindDestroyInnerDemonAction(botAI); } static Action* leotheras_the_blind_misdirect_boss_to_demon_form_tank( PlayerbotAI* botAI) { return new LeotherasTheBlindMisdirectBossToDemonFormTankAction(botAI); } diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp index 3cde7afe5a..c22ab54dce 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp @@ -1,5 +1,6 @@ #include "RaidSSCActions.h" #include "RaidSSCHelpers.h" +#include "AiFactory.h" #include "Corpse.h" #include "LootAction.h" #include "LootObjectStack.h" @@ -13,6 +14,47 @@ using namespace SerpentShrineCavernHelpers; bool SerpentShrineCavernEraseTimersAndTrackersAction::Execute(Event event) { + if (botAI->IsHeal(bot, true)) + { + if (bot->getClass() == CLASS_DRUID) + { + if (botAI->HasStrategy("caster", BotState::BOT_STATE_COMBAT)) + { + botAI->ChangeStrategy("-caster", BotState::BOT_STATE_COMBAT); + botAI->ChangeStrategy("+heal", BotState::BOT_STATE_COMBAT); + } + } + if (bot->getClass() == CLASS_PALADIN) + { + if (botAI->HasStrategy("dps", BotState::BOT_STATE_COMBAT)) + { + botAI->ChangeStrategy("-dps", BotState::BOT_STATE_COMBAT); + botAI->ChangeStrategy("+heal", BotState::BOT_STATE_COMBAT); + } + } + if (bot->getClass() == CLASS_PRIEST) + { + if (botAI->HasStrategy("holy dps", BotState::BOT_STATE_COMBAT)) + { + botAI->ChangeStrategy("-holy dps", BotState::BOT_STATE_COMBAT); + + uint8 tab = AiFactory::GetPlayerSpecTab(bot); + if (tab == PRIEST_TAB_DISCIPLINE) + botAI->ChangeStrategy("+heal", BotState::BOT_STATE_COMBAT); + else + botAI->ChangeStrategy("+holy heal", BotState::BOT_STATE_COMBAT); + } + } + if (bot->getClass() == CLASS_SHAMAN) + { + if (botAI->HasStrategy("ele", BotState::BOT_STATE_COMBAT)) + { + botAI->ChangeStrategy("-ele", BotState::BOT_STATE_COMBAT); + botAI->ChangeStrategy("+resto", BotState::BOT_STATE_COMBAT); + } + } + } + const uint32 instanceId = bot->GetMap()->GetInstanceId(); const ObjectGuid guid = bot->GetGUID(); @@ -497,6 +539,9 @@ bool TheLurkerBelowRunAroundBehindBossAction::Execute(Event event) if (!lurker) return false; + if (bot->HasAura(SPELL_TREE_OF_LIFE)) + bot->RemoveAura(SPELL_TREE_OF_LIFE); + float bossFacing = lurker->GetOrientation(); float behindAngle = bossFacing + M_PI + frand(-0.5f, 0.5f) * (M_PI / 2.0f); float radius = frand(20.0f, 24.0f); @@ -699,8 +744,30 @@ bool LeotherasTheBlindTargetSpellbindersAction::Execute(Event event) // Use tank strategy for Demon Form and DPS strategy for Human Form bool LeotherasTheBlindDemonFormTankAttackBossAction::Execute(Event event) { - Unit* leotherasDemon = GetActiveLeotherasDemon(botAI); - if (leotherasDemon) + Unit* innerDemon = nullptr; + auto const& npcs = + botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); + for (auto const& guid : npcs) + { + Unit* unit = botAI->GetUnit(guid); + Creature* creature = unit ? unit->ToCreature() : nullptr; + if (creature && creature->GetEntry() == NPC_INNER_DEMON + && creature->GetSummonerGUID() == bot->GetGUID()) + { + innerDemon = creature; + break; + } + } + + if (innerDemon) + { + if (botAI->HasStrategy("tank", BotState::BOT_STATE_COMBAT)) + botAI->ChangeStrategy("-tank", BotState::BOT_STATE_COMBAT); + + if (bot->GetTarget() != innerDemon->GetGUID()) + return Attack(innerDemon); + } + else if (Unit* leotherasDemon = GetActiveLeotherasDemon(botAI)) { if (!botAI->HasStrategy("tank", BotState::BOT_STATE_COMBAT)) botAI->ChangeStrategy("+tank", BotState::BOT_STATE_COMBAT); @@ -726,7 +793,7 @@ bool LeotherasTheBlindPositionRangedAction::Execute(Event event) { Unit* leotherasHuman = GetLeotherasHuman(botAI); if (leotherasHuman && bot->GetExactDist2d(leotherasHuman) < 10.0f && - leotherasHuman->GetVictim() != bot) + leotherasHuman->GetVictim() != bot && !bot->HasAura(SPELL_INSIDIOUS_WHISPER)) { const uint32 minInterval = 500; return FleePosition(leotherasHuman->GetPosition(), 12.0f, minInterval); @@ -750,7 +817,7 @@ bool LeotherasTheBlindPositionRangedAction::Execute(Event event) if (bot->GetExactDist2d(member) < 10.0f) return FleePosition(member->GetPosition(), 12.0f, minInterval); } - else if (bot->GetExactDist2d(member) < 5.0f) + else if (!bot->HasAura(SPELL_INSIDIOUS_WHISPER) && bot->GetExactDist2d(member) < 5.0f) return FleePosition(member->GetPosition(), 6.0f, minInterval); } } @@ -760,8 +827,7 @@ bool LeotherasTheBlindPositionRangedAction::Execute(Event event) bool LeotherasTheBlindRunAwayFromWhirlwindAction::Execute(Event event) { - Unit* leotherasHuman = GetLeotherasHuman(botAI); - if (leotherasHuman) + if (Unit* leotherasHuman = GetLeotherasHuman(botAI)) { float currentDistance = bot->GetExactDist2d(leotherasHuman); const float safeDistance = 20.0f; @@ -792,18 +858,103 @@ bool LeotherasTheBlindMeleeDpsRunAwayFromBossAction::Execute(Event event) if (currentDistance < safeDistance) { botAI->Reset(); - return MoveAway(demonVictim, safeDistance - currentDistance + 1.0f); + if (demonVictim != bot) + return MoveAway(demonVictim, safeDistance - currentDistance + 1.0f); } - else + else if (!bot->HasAura(SPELL_INSIDIOUS_WHISPER)) return true; return false; } -// Tanks and healers have no ability to kill their own Inner Demons; and ranged DPS -// also struggle, so this cheat action kills their Inner Demons for them -bool LeotherasTheBlindInnerDemonCheatAction::Execute(Event event) +// (1) When leotheras is in phase 2, bots with a tank strategy will switch to a dps strategy +// (2) When leotheras is not in phase 2, bots with a tank spec will switch to a tank strategy +// (3) When an inner demon is found that is associated with a bot's guid, bots with a healing strategy +// will switch to a dps strategy, and all bots will attack their associated inner demon +// (4) When an inner demon is not found that is associated with a bot's guid, +// bots with a healing spec will switch to a healing strategy +// Note: Second parameter of role checks is true if determined by strategy, false if determined by spec +bool LeotherasTheBlindDestroyInnerDemonAction::Execute(Event event) { + if (GetPhase2LeotherasDemon(botAI)) + { + if (botAI->IsTank(bot, false)) + { + if (bot->getClass() == CLASS_DEATH_KNIGHT) + { + if (botAI->HasStrategy("blood", BotState::BOT_STATE_COMBAT)) + { + botAI->ChangeStrategy("-blood", BotState::BOT_STATE_COMBAT); + botAI->ChangeStrategy("+frost", BotState::BOT_STATE_COMBAT); + } + } + if (bot->getClass() == CLASS_DRUID) + { + if (botAI->HasStrategy("bear", BotState::BOT_STATE_COMBAT)) + { + botAI->ChangeStrategy("-bear", BotState::BOT_STATE_COMBAT); + botAI->ChangeStrategy("+cat", BotState::BOT_STATE_COMBAT); + } + if (bot->HasAura(SPELL_DIRE_BEAR_FORM)) + bot->RemoveAura(SPELL_DIRE_BEAR_FORM); + } + if (bot->getClass() == CLASS_PALADIN) + { + if (botAI->HasStrategy("tank", BotState::BOT_STATE_COMBAT)) + { + botAI->ChangeStrategy("-tank", BotState::BOT_STATE_COMBAT); + botAI->ChangeStrategy("+dps", BotState::BOT_STATE_COMBAT); + } + } + if (bot->getClass() == CLASS_WARRIOR) + { + if (botAI->HasStrategy("tank", BotState::BOT_STATE_COMBAT)) + { + botAI->ChangeStrategy("-tank", BotState::BOT_STATE_COMBAT); + botAI->ChangeStrategy("+arms", BotState::BOT_STATE_COMBAT); + } + } + } + } + else + { + if (botAI->IsTank(bot, true)) + { + if (bot->getClass() == CLASS_DEATH_KNIGHT) + { + if (botAI->HasStrategy("frost", BotState::BOT_STATE_COMBAT)) + { + botAI->ChangeStrategy("-frost", BotState::BOT_STATE_COMBAT); + botAI->ChangeStrategy("+blood", BotState::BOT_STATE_COMBAT); + } + } + if (bot->getClass() == CLASS_DRUID) + { + if (botAI->HasStrategy("cat", BotState::BOT_STATE_COMBAT)) + { + botAI->ChangeStrategy("-cat", BotState::BOT_STATE_COMBAT); + botAI->ChangeStrategy("+bear", BotState::BOT_STATE_COMBAT); + } + } + if (bot->getClass() == CLASS_PALADIN) + { + if (botAI->HasStrategy("dps", BotState::BOT_STATE_COMBAT)) + { + botAI->ChangeStrategy("-dps", BotState::BOT_STATE_COMBAT); + botAI->ChangeStrategy("+tank", BotState::BOT_STATE_COMBAT); + } + } + if (bot->getClass() == CLASS_WARRIOR) + { + if (botAI->HasStrategy("arms", BotState::BOT_STATE_COMBAT)) + { + botAI->ChangeStrategy("-arms", BotState::BOT_STATE_COMBAT); + botAI->ChangeStrategy("+tank", BotState::BOT_STATE_COMBAT); + } + } + } + } + Unit* innerDemon = nullptr; auto const& npcs = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); @@ -821,11 +972,91 @@ bool LeotherasTheBlindInnerDemonCheatAction::Execute(Event event) if (innerDemon) { - if (botAI->IsRanged(bot) || botAI->IsTank(bot)) + // False = determine role by current strategy + if (botAI->IsHeal(bot, false)) { - Unit::DealDamage(bot, innerDemon, innerDemon->GetMaxHealth() / 25, nullptr, - DIRECT_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, nullptr, false, true); - return true; + if (bot->getClass() == CLASS_DRUID) + { + if (botAI->HasStrategy("heal", BotState::BOT_STATE_COMBAT)) + { + botAI->ChangeStrategy("-heal", BotState::BOT_STATE_COMBAT); + botAI->ChangeStrategy("+caster", BotState::BOT_STATE_COMBAT); + } + if (bot->HasAura(SPELL_TREE_OF_LIFE)) + bot->RemoveAura(SPELL_TREE_OF_LIFE); + } + if (bot->getClass() == CLASS_PALADIN) + { + if (botAI->HasStrategy("heal", BotState::BOT_STATE_COMBAT)) + { + botAI->ChangeStrategy("-heal", BotState::BOT_STATE_COMBAT); + botAI->ChangeStrategy("+dps", BotState::BOT_STATE_COMBAT); + } + } + if (bot->getClass() == CLASS_PRIEST) + { + if (botAI->HasStrategy("heal", BotState::BOT_STATE_COMBAT) || + botAI->HasStrategy("holy heal", BotState::BOT_STATE_COMBAT)) + { + botAI->ChangeStrategy("-heal", BotState::BOT_STATE_COMBAT); + botAI->ChangeStrategy("-holy heal", BotState::BOT_STATE_COMBAT); + botAI->ChangeStrategy("+holy dps", BotState::BOT_STATE_COMBAT); + } + } + if (bot->getClass() == CLASS_SHAMAN) + { + if (botAI->HasStrategy("resto", BotState::BOT_STATE_COMBAT)) + { + botAI->ChangeStrategy("-resto", BotState::BOT_STATE_COMBAT); + botAI->ChangeStrategy("+ele", BotState::BOT_STATE_COMBAT); + } + } + } + + if (bot->GetTarget() != innerDemon->GetGUID()) + return Attack(innerDemon); + } + else + { + if (botAI->IsHeal(bot, true)) + { + if (bot->getClass() == CLASS_DRUID) + { + if (botAI->HasStrategy("caster", BotState::BOT_STATE_COMBAT)) + { + botAI->ChangeStrategy("-caster", BotState::BOT_STATE_COMBAT); + botAI->ChangeStrategy("+heal", BotState::BOT_STATE_COMBAT); + } + } + if (bot->getClass() == CLASS_PALADIN) + { + if (botAI->HasStrategy("dps", BotState::BOT_STATE_COMBAT)) + { + botAI->ChangeStrategy("-dps", BotState::BOT_STATE_COMBAT); + botAI->ChangeStrategy("+heal", BotState::BOT_STATE_COMBAT); + } + } + if (bot->getClass() == CLASS_PRIEST) + { + if (botAI->HasStrategy("holy dps", BotState::BOT_STATE_COMBAT)) + { + botAI->ChangeStrategy("-holy dps", BotState::BOT_STATE_COMBAT); + + uint8 tab = AiFactory::GetPlayerSpecTab(bot); + if (tab == PRIEST_TAB_DISCIPLINE) + botAI->ChangeStrategy("+heal", BotState::BOT_STATE_COMBAT); + else + botAI->ChangeStrategy("+holy heal", BotState::BOT_STATE_COMBAT); + } + } + if (bot->getClass() == CLASS_SHAMAN) + { + if (botAI->HasStrategy("ele", BotState::BOT_STATE_COMBAT)) + { + botAI->ChangeStrategy("-ele", BotState::BOT_STATE_COMBAT); + botAI->ChangeStrategy("+resto", BotState::BOT_STATE_COMBAT); + } + } } } diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h index bdcdf24103..5c3a368298 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h @@ -179,11 +179,11 @@ class LeotherasTheBlindMeleeDpsRunAwayFromBossAction : public MovementAction bool Execute(Event event) override; }; -class LeotherasTheBlindInnerDemonCheatAction : public AttackAction +class LeotherasTheBlindDestroyInnerDemonAction : public AttackAction { public: - LeotherasTheBlindInnerDemonCheatAction( - PlayerbotAI* botAI, std::string const name = "leotheras the blind inner demon cheat") : AttackAction(botAI, name) {} + LeotherasTheBlindDestroyInnerDemonAction( + PlayerbotAI* botAI, std::string const name = "leotheras the blind destroy inner demon") : AttackAction(botAI, name) {} bool Execute(Event event) override; }; diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp index 82d8f22749..e0868153f9 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp @@ -86,7 +86,7 @@ namespace SerpentShrineCavernHelpers { Player* member = ref->GetSource(); if (member && member->IsAlive() && botAI->IsDps(member) && - GET_PLAYERBOT_AI(member)) + GET_PLAYERBOT_AI(member) && !member->HasAura(SPELL_INSIDIOUS_WHISPER)) return member == bot; } } diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h index c76064c906..d1d907c515 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h @@ -42,11 +42,15 @@ namespace SerpentShrineCavernHelpers SPELL_INSIDIOUS_WHISPER = 37676, // Lady Vashj - SPELL_FEAR_WARD = 6346, + SPELL_FEAR_WARD = 6346, SPELL_POISON_BOLT = 38253, SPELL_STATIC_CHARGE = 38280, SPELL_ENTANGLE = 38316, + // Druid + SPELL_DIRE_BEAR_FORM = 9634, + SPELL_TREE_OF_LIFE = 33891, + // Hunter SPELL_MISDIRECTION = 35079, @@ -54,7 +58,7 @@ namespace SerpentShrineCavernHelpers SPELL_SLOW = 31589, // Shaman - SPELL_GROUNDING_TOTEM_EFFECT = 8178, + SPELL_GROUNDING_TOTEM_EFFECT = 8178, SPELL_FROST_SHOCK = 25464, // Warlock diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp index 73c5269aae..c3ebadec4c 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp @@ -225,6 +225,9 @@ float LeotherasTheBlindAvoidWhirlwindMultiplier::GetValue(Action* action) if (botAI->IsTank(bot)) return 1.0f; + if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) + return 1.0f; + Unit* leotherasHuman = GetLeotherasHuman(botAI); if (!leotherasHuman) return 1.0f; @@ -260,13 +263,35 @@ float LeotherasTheBlindDisableTankActionsMultiplier::GetValue(Action* action) if (dynamic_cast(action)) return 0.0f; - // (2) Phase 2 only: Tanks other than the Warlock tank should do absolutely nothing + // (2) Phase 2 only: disable tank assist for non-demon form tanks + // This is a fallback since there is an action that switches them to dps strategy if (botAI->IsTank(bot) && bot != demonFormTank && GetPhase2LeotherasDemon(botAI)) { - if ((dynamic_cast(action) && + /* if ((dynamic_cast(action) && !dynamic_cast(action)) || dynamic_cast(action)) - return 0.0f; + return 0.0f; */ + + if (dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + +float LeotherasTheBlindFocusOnInnerDemonMultiplier::GetValue(Action* action) +{ + if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) + { + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) + return 0.0f; } return 1.0f; @@ -274,7 +299,7 @@ float LeotherasTheBlindDisableTankActionsMultiplier::GetValue(Action* action) float LeotherasTheBlindMeleeDpsAvoidChaosBlastMultiplier::GetValue(Action* action) { - if (!botAI->IsMelee(bot) || botAI->IsTank(bot)) + if (botAI->IsRanged(bot) || botAI->IsTank(bot)) return 1.0f; if (!GetPhase2LeotherasDemon(botAI)) @@ -300,6 +325,9 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) if (!leotheras) return 1.0f; + if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) + return 1.0f; + if (dynamic_cast(action)) return 1.0f; diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h index d477570e59..6630dc2060 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h @@ -91,6 +91,14 @@ class LeotherasTheBlindMeleeDpsAvoidChaosBlastMultiplier : public Multiplier virtual float GetValue(Action* action); }; +class LeotherasTheBlindFocusOnInnerDemonMultiplier : public Multiplier +{ +public: + LeotherasTheBlindFocusOnInnerDemonMultiplier( + PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind focus on inner demon") {} + virtual float GetValue(Action* action); +}; + class LeotherasTheBlindWaitForDpsMultiplier : public Multiplier { public: diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp index d7562d3ff6..62b4e22a52 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp @@ -68,8 +68,8 @@ void RaidSSCStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("leotheras the blind bot has too many chaos blast stacks", { NextAction("leotheras the blind melee dps run away from boss", ACTION_EMERGENCY + 7) })); - triggers.push_back(new TriggerNode("leotheras the blind inner demon cheat", { - NextAction("leotheras the blind inner demon cheat", ACTION_EMERGENCY + 6) })); + triggers.push_back(new TriggerNode("leotheras the blind inner demon has awakened", { + NextAction("leotheras the blind destroy inner demon", ACTION_EMERGENCY + 6) })); triggers.push_back(new TriggerNode("leotheras the blind entered final phase", { NextAction("leotheras the blind final phase assign dps priority", ACTION_RAID + 2) })); @@ -177,6 +177,7 @@ void RaidSSCStrategy::InitMultipliers(std::vector& multipliers) multipliers.push_back(new LeotherasTheBlindAvoidWhirlwindMultiplier(botAI)); multipliers.push_back(new LeotherasTheBlindDisableTankActionsMultiplier(botAI)); multipliers.push_back(new LeotherasTheBlindMeleeDpsAvoidChaosBlastMultiplier(botAI)); + multipliers.push_back(new LeotherasTheBlindFocusOnInnerDemonMultiplier(botAI)); multipliers.push_back(new LeotherasTheBlindWaitForDpsMultiplier(botAI)); multipliers.push_back(new LeotherasTheBlindDelayBloodlustAndHeroismMultiplier(botAI)); diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h index 3a9f846b3a..3fd19e524a 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h @@ -74,8 +74,8 @@ class RaidSSCTriggerContext : public NamedObjectContext creators["leotheras the blind bot has too many chaos blast stacks"] = &RaidSSCTriggerContext::leotheras_the_blind_bot_has_too_many_chaos_blast_stacks; - creators["leotheras the blind inner demon cheat"] = - &RaidSSCTriggerContext::leotheras_the_blind_inner_demon_cheat; + creators["leotheras the blind inner demon has awakened"] = + &RaidSSCTriggerContext::leotheras_the_blind_inner_demon_has_awakened; creators["leotheras the blind entered final phase"] = &RaidSSCTriggerContext::leotheras_the_blind_entered_final_phase; @@ -228,8 +228,8 @@ class RaidSSCTriggerContext : public NamedObjectContext static Trigger* leotheras_the_blind_bot_has_too_many_chaos_blast_stacks( PlayerbotAI* botAI) { return new LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger(botAI); } - static Trigger* leotheras_the_blind_inner_demon_cheat( - PlayerbotAI* botAI) { return new LeotherasTheBlindInnerDemonCheatTrigger(botAI); } + static Trigger* leotheras_the_blind_inner_demon_has_awakened( + PlayerbotAI* botAI) { return new LeotherasTheBlindInnerDemonHasAwakenedTrigger(botAI); } static Trigger* leotheras_the_blind_entered_final_phase( PlayerbotAI* botAI) { return new LeotherasTheBlindEnteredFinalPhaseTrigger(botAI); } diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp index a1f825b2e9..aebe50736c 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp @@ -34,32 +34,28 @@ bool GreyheartTidecallerWaterElementalTotemSpawnedTrigger::IsActive() bool HydrossTheUnstableBotIsFrostTankTrigger::IsActive() { - if (!botAI->IsMainTank(bot)) - return false; - - return AI_VALUE2(Unit*, "find target", "hydross the unstable"); + return AI_VALUE2(Unit*, "find target", "hydross the unstable") && + botAI->IsMainTank(bot); } bool HydrossTheUnstableBotIsNatureTankTrigger::IsActive() { - if (!botAI->IsAssistTankOfIndex(bot, 0, true)) - return false; - - return AI_VALUE2(Unit*, "find target", "hydross the unstable"); + return AI_VALUE2(Unit*, "find target", "hydross the unstable") && + botAI->IsAssistTankOfIndex(bot, 0, true); } bool HydrossTheUnstableElementalsSpawnedTrigger::IsActive() { - if (botAI->IsHeal(bot) || botAI->IsMainTank(bot) || - botAI->IsAssistTankOfIndex(bot, 0, true)) + Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); + if (hydross && hydross->GetHealthPct() < 10.0f) return false; - Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); - if (!hydross || hydross->GetHealthPct() < 10.0f) + if (!AI_VALUE2(Unit*, "find target", "pure spawn of hydross") && + !AI_VALUE2(Unit*, "find target", "tainted spawn of hydross")) return false; - return AI_VALUE2(Unit*, "find target", "pure spawn of hydross") || - AI_VALUE2(Unit*, "find target", "tainted spawn of hydross"); + return !botAI->IsHeal(bot) && !botAI->IsMainTank(bot) && + !botAI->IsAssistTankOfIndex(bot, 0, true); } bool HydrossTheUnstableDangerFromWaterTombsTrigger::IsActive() @@ -80,21 +76,19 @@ bool HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger::IsActive() bool HydrossTheUnstableAggroResetsUponPhaseChangeTrigger::IsActive() { - if (bot->getClass() == CLASS_HUNTER || - botAI->IsMainTank(bot) || - botAI->IsAssistTankOfIndex(bot, 0, true) || - botAI->IsHeal(bot)) + if (!AI_VALUE2(Unit*, "find target", "hydross the unstable")) return false; - return AI_VALUE2(Unit*, "find target", "hydross the unstable"); + return bot->getClass() != CLASS_HUNTER && + !botAI->IsHeal(bot) && + !botAI->IsMainTank(bot) && + !botAI->IsAssistTankOfIndex(bot, 0, true); } bool HydrossTheUnstableNeedToManageTimersTrigger::IsActive() { - if (!IsInstanceTimerManager(botAI, bot)) - return false; - - return AI_VALUE2(Unit*, "find target", "hydross the unstable"); + return AI_VALUE2(Unit*, "find target", "hydross the unstable") && + IsInstanceTimerManager(botAI, bot); } // The Lurker Below @@ -113,13 +107,13 @@ bool TheLurkerBelowSpoutIsActiveTrigger::IsActive() bool TheLurkerBelowBossIsActiveForMainTankTrigger::IsActive() { - if (!botAI->IsMainTank(bot)) - return false; - Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); if (!lurker) return false; + if (!botAI->IsMainTank(bot)) + return false; + const time_t now = std::time(nullptr); auto it = lurkerSpoutTimer.find(lurker->GetMap()->GetInstanceId()); @@ -184,10 +178,8 @@ bool TheLurkerBelowBossIsSubmergedTrigger::IsActive() bool TheLurkerBelowNeedToPrepareTimerForSpoutTrigger::IsActive() { - if (!IsInstanceTimerManager(botAI, bot)) - return false; - - return AI_VALUE2(Unit*, "find target", "the lurker below"); + return AI_VALUE2(Unit*, "find target", "the lurker below") && + IsInstanceTimerManager(botAI, bot); } // Leotheras the Blind @@ -199,8 +191,8 @@ bool LeotherasTheBlindBossIsInactiveTrigger::IsActive() bool LeotherasTheBlindBossTransformedIntoDemonFormTrigger::IsActive() { - return GetLeotherasDemonFormTank(botAI, bot) == bot && - AI_VALUE2(Unit*, "find target", "leotheras the blind"); + return AI_VALUE2(Unit*, "find target", "leotheras the blind") && + GetLeotherasDemonFormTank(botAI, bot) == bot; } bool LeotherasTheBlindBossEngagedByRangedTrigger::IsActive() @@ -208,27 +200,34 @@ bool LeotherasTheBlindBossEngagedByRangedTrigger::IsActive() if (!botAI->IsRanged(bot)) return false; - if (GetLeotherasDemonFormTank(botAI, bot) == bot) - return false; + Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); - return leotheras && !leotheras->HasAura(SPELL_LEOTHERAS_BANISHED) && - !leotheras->HasAura(SPELL_WHIRLWIND) && !leotheras->HasAura(SPELL_WHIRLWIND_CHANNEL); + if (!leotheras || + leotheras->HasAura(SPELL_LEOTHERAS_BANISHED) || + leotheras->HasAura(SPELL_WHIRLWIND) || + leotheras->HasAura(SPELL_WHIRLWIND_CHANNEL)) + return false; + + return GetLeotherasDemonFormTank(botAI, bot) != bot; } bool LeotherasTheBlindBossChannelingWhirlwindTrigger::IsActive() { - if (botAI->IsTank(bot) && botAI->IsMelee(bot)) + if (botAI->IsTank(bot, true) && botAI->IsMelee(bot)) return false; Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); - return leotheras && !leotheras->HasAura(SPELL_LEOTHERAS_BANISHED) && - (leotheras->HasAura(SPELL_WHIRLWIND) || leotheras->HasAura(SPELL_WHIRLWIND_CHANNEL)); + if (!leotheras || leotheras->HasAura(SPELL_LEOTHERAS_BANISHED)) + return false; + + return leotheras->HasAura(SPELL_WHIRLWIND) || + leotheras->HasAura(SPELL_WHIRLWIND_CHANNEL); } bool LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger::IsActive() { - if (!botAI->IsMelee(bot) || !botAI->IsDps(bot)) + if (botAI->IsRanged(bot) || botAI->IsTank(bot, true)) return false; Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST); @@ -238,17 +237,24 @@ bool LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger::IsActive() return GetPhase2LeotherasDemon(botAI); } -bool LeotherasTheBlindInnerDemonCheatTrigger::IsActive() +bool LeotherasTheBlindInnerDemonHasAwakenedTrigger::IsActive() { - if (!botAI->HasCheat(BotCheatMask::raid)) + /* if (!botAI->HasCheat(BotCheatMask::raid)) return false; - return bot->HasAura(SPELL_INSIDIOUS_WHISPER); + return bot->HasAura(SPELL_INSIDIOUS_WHISPER); */ + if (!AI_VALUE2(Unit*, "find target", "leotheras the blind")) + return false; + + return GetLeotherasDemonFormTank(botAI, bot) != bot; } bool LeotherasTheBlindEnteredFinalPhaseTrigger::IsActive() { - if (botAI->IsHeal(bot)) + if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) + return false; + + if (botAI->IsHeal(bot, true)) return false; if (GetLeotherasDemonFormTank(botAI, bot) == bot) @@ -271,55 +277,47 @@ bool LeotherasTheBlindDemonFormTankNeedsAggro::IsActive() bool LeotherasTheBlindBossWipesAggroUponPhaseChangeTrigger::IsActive() { - if (!IsInstanceTimerManager(botAI, bot)) + if (!AI_VALUE2(Unit*, "find target", "leotheras the blind")) return false; - return AI_VALUE2(Unit*, "find target", "leotheras the blind"); + return IsInstanceTimerManager(botAI, bot); } // Fathom-Lord Karathress bool FathomLordKarathressBossEngagedByMainTankTrigger::IsActive() { - if (!botAI->IsMainTank(bot)) - return false; - - return AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); + return AI_VALUE2(Unit*, "find target", "fathom-lord karathress") && + botAI->IsMainTank(bot); } bool FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger::IsActive() { - if (!botAI->IsAssistTankOfIndex(bot, 0, false)) - return false; - - return AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); + return AI_VALUE2(Unit*, "find target", "fathom-guard caribdis") && + botAI->IsAssistTankOfIndex(bot, 0, false); } bool FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger::IsActive() { - if (!botAI->IsAssistTankOfIndex(bot, 1, false)) - return false; - - return AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis"); + return AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis") && + botAI->IsAssistTankOfIndex(bot, 1, false); } bool FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger::IsActive() { - if (!botAI->IsAssistTankOfIndex(bot, 2, false)) - return false; - - return AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess"); + return AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess") && + botAI->IsAssistTankOfIndex(bot, 2, false); } bool FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger::IsActive() { - if (!botAI->IsHealAssistantOfIndex(bot, 0)) - return false; - Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); if (!caribdis) return false; + if (!botAI->IsHealAssistantOfIndex(bot, 0)) + return false; + Player* firstAssistTank = nullptr; if (Group* group = bot->GetGroup()) { @@ -374,10 +372,10 @@ bool FathomLordKarathressDeterminingKillOrderTrigger::IsActive() bool FathomLordKarathressTanksNeedToEstablishAggroTrigger::IsActive() { - if (!IsInstanceTimerManager(botAI, bot)) + if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) return false; - return AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); + return IsInstanceTimerManager(botAI, bot); } // Morogrim Tidewalker @@ -393,10 +391,8 @@ bool MorogrimTidewalkerPullingBossTrigger::IsActive() bool MorogrimTidewalkerBossEngagedByMainTankTrigger::IsActive() { - if (!botAI->IsMainTank(bot)) - return false; - - return AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); + return AI_VALUE2(Unit*, "find target", "morogrim tidewalker") && + botAI->IsMainTank(bot); } bool MorogrimTidewalkerWaterGlobulesAreIncomingTrigger::IsActive() @@ -412,11 +408,8 @@ bool MorogrimTidewalkerWaterGlobulesAreIncomingTrigger::IsActive() bool LadyVashjBossEngagedByMainTankTrigger::IsActive() { - if (!botAI->IsMainTank(bot)) - return false; - return AI_VALUE2(Unit*, "find target", "lady vashj") && - !IsLadyVashjInPhase2(botAI); + !IsLadyVashjInPhase2(botAI) && botAI->IsMainTank(bot); } bool LadyVashjBossEngagedByRangedInPhase1Trigger::IsActive() diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h index 0c1becfde7..6e79eaa0ef 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h +++ b/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h @@ -181,11 +181,11 @@ class LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger : public Trigger bool IsActive() override; }; -class LeotherasTheBlindInnerDemonCheatTrigger : public Trigger +class LeotherasTheBlindInnerDemonHasAwakenedTrigger : public Trigger { public: - LeotherasTheBlindInnerDemonCheatTrigger( - PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind inner demon cheat") {} + LeotherasTheBlindInnerDemonHasAwakenedTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind inner demon has awakened") {} bool IsActive() override; }; From 96cb548e43c904fd82388a8962d7871baa860b01 Mon Sep 17 00:00:00 2001 From: crow Date: Sun, 18 Jan 2026 09:21:28 -0600 Subject: [PATCH 19/25] Update to reflect new module structure --- .../RaidAi/SerpentshrineCavern/Action}/RaidSSCActions.cpp | 0 .../RaidAi/SerpentshrineCavern/Action}/RaidSSCActions.h | 0 .../SerpentshrineCavern/Multiplier}/RaidSSCMultipliers.cpp | 7 +++++-- .../SerpentshrineCavern/Multiplier}/RaidSSCMultipliers.h | 0 .../RaidAi/SerpentshrineCavern}/RaidSSCActionContext.h | 0 .../RaidAi/SerpentshrineCavern}/RaidSSCTriggerContext.h | 0 .../SerpentshrineCavern/Strategy}/RaidSSCStrategy.cpp | 0 .../RaidAi/SerpentshrineCavern/Strategy}/RaidSSCStrategy.h | 0 .../SerpentshrineCavern/Trigger}/RaidSSCTriggers.cpp | 0 .../RaidAi/SerpentshrineCavern/Trigger}/RaidSSCTriggers.h | 0 .../RaidAi/SerpentshrineCavern/Util}/RaidSSCHelpers.cpp | 0 .../RaidAi/SerpentshrineCavern/Util}/RaidSSCHelpers.h | 0 12 files changed, 5 insertions(+), 2 deletions(-) rename src/{strategy/raids/serpentshrinecavern => Scenario/RaidAi/SerpentshrineCavern/Action}/RaidSSCActions.cpp (100%) rename src/{strategy/raids/serpentshrinecavern => Scenario/RaidAi/SerpentshrineCavern/Action}/RaidSSCActions.h (100%) rename src/{strategy/raids/serpentshrinecavern => Scenario/RaidAi/SerpentshrineCavern/Multiplier}/RaidSSCMultipliers.cpp (99%) rename src/{strategy/raids/serpentshrinecavern => Scenario/RaidAi/SerpentshrineCavern/Multiplier}/RaidSSCMultipliers.h (100%) rename src/{strategy/raids/serpentshrinecavern => Scenario/RaidAi/SerpentshrineCavern}/RaidSSCActionContext.h (100%) rename src/{strategy/raids/serpentshrinecavern => Scenario/RaidAi/SerpentshrineCavern}/RaidSSCTriggerContext.h (100%) rename src/{strategy/raids/serpentshrinecavern => Scenario/RaidAi/SerpentshrineCavern/Strategy}/RaidSSCStrategy.cpp (100%) rename src/{strategy/raids/serpentshrinecavern => Scenario/RaidAi/SerpentshrineCavern/Strategy}/RaidSSCStrategy.h (100%) rename src/{strategy/raids/serpentshrinecavern => Scenario/RaidAi/SerpentshrineCavern/Trigger}/RaidSSCTriggers.cpp (100%) rename src/{strategy/raids/serpentshrinecavern => Scenario/RaidAi/SerpentshrineCavern/Trigger}/RaidSSCTriggers.h (100%) rename src/{strategy/raids/serpentshrinecavern => Scenario/RaidAi/SerpentshrineCavern/Util}/RaidSSCHelpers.cpp (100%) rename src/{strategy/raids/serpentshrinecavern => Scenario/RaidAi/SerpentshrineCavern/Util}/RaidSSCHelpers.h (100%) diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp b/src/Scenario/RaidAi/SerpentshrineCavern/Action/RaidSSCActions.cpp similarity index 100% rename from src/strategy/raids/serpentshrinecavern/RaidSSCActions.cpp rename to src/Scenario/RaidAi/SerpentshrineCavern/Action/RaidSSCActions.cpp diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActions.h b/src/Scenario/RaidAi/SerpentshrineCavern/Action/RaidSSCActions.h similarity index 100% rename from src/strategy/raids/serpentshrinecavern/RaidSSCActions.h rename to src/Scenario/RaidAi/SerpentshrineCavern/Action/RaidSSCActions.h diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp b/src/Scenario/RaidAi/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp similarity index 99% rename from src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp rename to src/Scenario/RaidAi/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp index c3ebadec4c..2ec6a5494d 100644 --- a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.cpp +++ b/src/Scenario/RaidAi/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp @@ -425,8 +425,11 @@ float FathomLordKarathressDisableTankActionsMultiplier::GetValue(Action* action) if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) return 1.0f; - if ((bot->GetVictim() != nullptr && dynamic_cast(action)) || - dynamic_cast(action) || + if (bot->GetVictim() != nullptr && dynamic_cast(action)) + return 0.0f; + + if (dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h b/src/Scenario/RaidAi/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.h similarity index 100% rename from src/strategy/raids/serpentshrinecavern/RaidSSCMultipliers.h rename to src/Scenario/RaidAi/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.h diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h b/src/Scenario/RaidAi/SerpentshrineCavern/RaidSSCActionContext.h similarity index 100% rename from src/strategy/raids/serpentshrinecavern/RaidSSCActionContext.h rename to src/Scenario/RaidAi/SerpentshrineCavern/RaidSSCActionContext.h diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h b/src/Scenario/RaidAi/SerpentshrineCavern/RaidSSCTriggerContext.h similarity index 100% rename from src/strategy/raids/serpentshrinecavern/RaidSSCTriggerContext.h rename to src/Scenario/RaidAi/SerpentshrineCavern/RaidSSCTriggerContext.h diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp b/src/Scenario/RaidAi/SerpentshrineCavern/Strategy/RaidSSCStrategy.cpp similarity index 100% rename from src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.cpp rename to src/Scenario/RaidAi/SerpentshrineCavern/Strategy/RaidSSCStrategy.cpp diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.h b/src/Scenario/RaidAi/SerpentshrineCavern/Strategy/RaidSSCStrategy.h similarity index 100% rename from src/strategy/raids/serpentshrinecavern/RaidSSCStrategy.h rename to src/Scenario/RaidAi/SerpentshrineCavern/Strategy/RaidSSCStrategy.h diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp b/src/Scenario/RaidAi/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp similarity index 100% rename from src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.cpp rename to src/Scenario/RaidAi/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h b/src/Scenario/RaidAi/SerpentshrineCavern/Trigger/RaidSSCTriggers.h similarity index 100% rename from src/strategy/raids/serpentshrinecavern/RaidSSCTriggers.h rename to src/Scenario/RaidAi/SerpentshrineCavern/Trigger/RaidSSCTriggers.h diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp b/src/Scenario/RaidAi/SerpentshrineCavern/Util/RaidSSCHelpers.cpp similarity index 100% rename from src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.cpp rename to src/Scenario/RaidAi/SerpentshrineCavern/Util/RaidSSCHelpers.cpp diff --git a/src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h b/src/Scenario/RaidAi/SerpentshrineCavern/Util/RaidSSCHelpers.h similarity index 100% rename from src/strategy/raids/serpentshrinecavern/RaidSSCHelpers.h rename to src/Scenario/RaidAi/SerpentshrineCavern/Util/RaidSSCHelpers.h From be33f7b036dc09599a4b7022734248884c5332fa Mon Sep 17 00:00:00 2001 From: crow Date: Tue, 20 Jan 2026 20:19:35 -0600 Subject: [PATCH 20/25] rework leotheras methods --- .../Action/RaidSSCActions.cpp | 382 +++++++----------- .../Action/RaidSSCActions.h | 12 + .../Multiplier/RaidSSCMultipliers.cpp | 43 +- .../Multiplier/RaidSSCMultipliers.h | 0 .../RaidSSCActionContext.h | 6 + .../SerpentshrineCavern}/RaidSSCHelpers.cpp | 21 +- .../SerpentshrineCavern}/RaidSSCHelpers.h | 3 +- .../RaidSSCTriggerContext.h | 6 + .../Strategy/RaidSSCStrategy.cpp | 11 +- .../Strategy/RaidSSCStrategy.h | 0 .../Trigger/RaidSSCTriggers.cpp | 68 +++- .../Trigger/RaidSSCTriggers.h | 8 + 12 files changed, 281 insertions(+), 279 deletions(-) rename src/{Scenario/RaidAi => Ai/Raid}/SerpentshrineCavern/Action/RaidSSCActions.cpp (91%) rename src/{Scenario/RaidAi => Ai/Raid}/SerpentshrineCavern/Action/RaidSSCActions.h (97%) rename src/{Scenario/RaidAi => Ai/Raid}/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp (95%) rename src/{Scenario/RaidAi => Ai/Raid}/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.h (100%) rename src/{Scenario/RaidAi => Ai/Raid}/SerpentshrineCavern/RaidSSCActionContext.h (97%) rename src/{Scenario/RaidAi/SerpentshrineCavern/Util => Ai/Raid/SerpentshrineCavern}/RaidSSCHelpers.cpp (97%) rename src/{Scenario/RaidAi/SerpentshrineCavern/Util => Ai/Raid/SerpentshrineCavern}/RaidSSCHelpers.h (98%) rename src/{Scenario/RaidAi => Ai/Raid}/SerpentshrineCavern/RaidSSCTriggerContext.h (97%) rename src/{Scenario/RaidAi => Ai/Raid}/SerpentshrineCavern/Strategy/RaidSSCStrategy.cpp (97%) rename src/{Scenario/RaidAi => Ai/Raid}/SerpentshrineCavern/Strategy/RaidSSCStrategy.h (100%) rename src/{Scenario/RaidAi => Ai/Raid}/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp (93%) rename src/{Scenario/RaidAi => Ai/Raid}/SerpentshrineCavern/Trigger/RaidSSCTriggers.h (97%) diff --git a/src/Scenario/RaidAi/SerpentshrineCavern/Action/RaidSSCActions.cpp b/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp similarity index 91% rename from src/Scenario/RaidAi/SerpentshrineCavern/Action/RaidSSCActions.cpp rename to src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp index c22ab54dce..75ccb1f97e 100644 --- a/src/Scenario/RaidAi/SerpentshrineCavern/Action/RaidSSCActions.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp @@ -14,47 +14,6 @@ using namespace SerpentShrineCavernHelpers; bool SerpentShrineCavernEraseTimersAndTrackersAction::Execute(Event event) { - if (botAI->IsHeal(bot, true)) - { - if (bot->getClass() == CLASS_DRUID) - { - if (botAI->HasStrategy("caster", BotState::BOT_STATE_COMBAT)) - { - botAI->ChangeStrategy("-caster", BotState::BOT_STATE_COMBAT); - botAI->ChangeStrategy("+heal", BotState::BOT_STATE_COMBAT); - } - } - if (bot->getClass() == CLASS_PALADIN) - { - if (botAI->HasStrategy("dps", BotState::BOT_STATE_COMBAT)) - { - botAI->ChangeStrategy("-dps", BotState::BOT_STATE_COMBAT); - botAI->ChangeStrategy("+heal", BotState::BOT_STATE_COMBAT); - } - } - if (bot->getClass() == CLASS_PRIEST) - { - if (botAI->HasStrategy("holy dps", BotState::BOT_STATE_COMBAT)) - { - botAI->ChangeStrategy("-holy dps", BotState::BOT_STATE_COMBAT); - - uint8 tab = AiFactory::GetPlayerSpecTab(bot); - if (tab == PRIEST_TAB_DISCIPLINE) - botAI->ChangeStrategy("+heal", BotState::BOT_STATE_COMBAT); - else - botAI->ChangeStrategy("+holy heal", BotState::BOT_STATE_COMBAT); - } - } - if (bot->getClass() == CLASS_SHAMAN) - { - if (botAI->HasStrategy("ele", BotState::BOT_STATE_COMBAT)) - { - botAI->ChangeStrategy("-ele", BotState::BOT_STATE_COMBAT); - botAI->ChangeStrategy("+resto", BotState::BOT_STATE_COMBAT); - } - } - } - const uint32 instanceId = bot->GetMap()->GetInstanceId(); const ObjectGuid guid = bot->GetGUID(); @@ -373,9 +332,10 @@ bool HydrossTheUnstableFrostPhaseSpreadOutAction::Execute(Event event) if (!member || member == bot || !member->IsAlive()) continue; + const float safeDistance = 6.0f; const uint32 minInterval = 1000; - if (bot->GetExactDist2d(member) < 6.0f) - return FleePosition(member->GetPosition(), 8.0f, minInterval); + if (bot->GetExactDist2d(member) < safeDistance) + return FleePosition(member->GetPosition(), safeDistance, minInterval); } } @@ -778,7 +738,7 @@ bool LeotherasTheBlindDemonFormTankAttackBossAction::Execute(Event event) if (bot->GetTarget() != leotherasDemon->GetGUID()) return Attack(leotherasDemon); } - else if (!GetLeotherasHuman(botAI)) + else if (GetLeotherasHuman(botAI)) { if (botAI->HasStrategy("tank", BotState::BOT_STATE_COMBAT)) botAI->ChangeStrategy("-tank", BotState::BOT_STATE_COMBAT); @@ -787,16 +747,26 @@ bool LeotherasTheBlindDemonFormTankAttackBossAction::Execute(Event event) return false; } +// Stop melee tanks from attacking upon transformation so they don't take aggro +// Applies only if there is a Warlock tank present +bool LeotherasTheBlindMeleeTanksDontAttackDemonFormAction::Execute(Event event) +{ + bot->AttackStop(); + botAI->Reset(); + return true; +} + // Intent is to keep enough distance from Leotheras and spread to prepare for Whirlwind // And stay away from the Warlock tank to avoid Chaos Blasts bool LeotherasTheBlindPositionRangedAction::Execute(Event event) { + const float safeDistFromBoss = 15.0f; Unit* leotherasHuman = GetLeotherasHuman(botAI); - if (leotherasHuman && bot->GetExactDist2d(leotherasHuman) < 10.0f && - leotherasHuman->GetVictim() != bot && !bot->HasAura(SPELL_INSIDIOUS_WHISPER)) + if (leotherasHuman && bot->GetExactDist2d(leotherasHuman) < safeDistFromBoss && + leotherasHuman->GetVictim() != bot) { const uint32 minInterval = 500; - return FleePosition(leotherasHuman->GetPosition(), 12.0f, minInterval); + return FleePosition(leotherasHuman->GetPosition(), safeDistFromBoss, minInterval); } Group* group = bot->GetGroup(); @@ -814,11 +784,16 @@ bool LeotherasTheBlindPositionRangedAction::Execute(Event event) const uint32 minInterval = 0; if (GetLeotherasDemonFormTank(botAI, bot) == member) { - if (bot->GetExactDist2d(member) < 10.0f) - return FleePosition(member->GetPosition(), 12.0f, minInterval); + const float safeDistFromTank = 10.0f; + if (bot->GetExactDist2d(member) < safeDistFromTank) + return FleePosition(member->GetPosition(), safeDistFromTank, minInterval); + } + else + { + const float safeDistFromMember = 6.0f; + if (bot->GetExactDist2d(member) < safeDistFromMember) + return FleePosition(member->GetPosition(), safeDistFromMember, minInterval); } - else if (!bot->HasAura(SPELL_INSIDIOUS_WHISPER) && bot->GetExactDist2d(member) < 5.0f) - return FleePosition(member->GetPosition(), 6.0f, minInterval); } } @@ -830,11 +805,11 @@ bool LeotherasTheBlindRunAwayFromWhirlwindAction::Execute(Event event) if (Unit* leotherasHuman = GetLeotherasHuman(botAI)) { float currentDistance = bot->GetExactDist2d(leotherasHuman); - const float safeDistance = 20.0f; + const float safeDistance = 25.0f; if (currentDistance < safeDistance) { botAI->Reset(); - return MoveAway(leotherasHuman, safeDistance - currentDistance + 5.0f); + return MoveAway(leotherasHuman, safeDistance - currentDistance); } } @@ -845,6 +820,9 @@ bool LeotherasTheBlindRunAwayFromWhirlwindAction::Execute(Event event) // If a melee tank is used, other melee needs to run away after too many Chaos Blast stacks bool LeotherasTheBlindMeleeDpsRunAwayFromBossAction::Execute(Event event) { + if (botAI->CanCastSpell("cloak of shadows", bot)) + return botAI->CastSpell("cloak of shadows", bot); + Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI); if (!leotherasPhase2Demon) return false; @@ -859,102 +837,15 @@ bool LeotherasTheBlindMeleeDpsRunAwayFromBossAction::Execute(Event event) { botAI->Reset(); if (demonVictim != bot) - return MoveAway(demonVictim, safeDistance - currentDistance + 1.0f); + return MoveAway(demonVictim, safeDistance - currentDistance); } - else if (!bot->HasAura(SPELL_INSIDIOUS_WHISPER)) - return true; return false; } -// (1) When leotheras is in phase 2, bots with a tank strategy will switch to a dps strategy -// (2) When leotheras is not in phase 2, bots with a tank spec will switch to a tank strategy -// (3) When an inner demon is found that is associated with a bot's guid, bots with a healing strategy -// will switch to a dps strategy, and all bots will attack their associated inner demon -// (4) When an inner demon is not found that is associated with a bot's guid, -// bots with a healing spec will switch to a healing strategy -// Note: Second parameter of role checks is true if determined by strategy, false if determined by spec +// Hardcoded actions for healers and bear tanks to kill Inner Demons bool LeotherasTheBlindDestroyInnerDemonAction::Execute(Event event) { - if (GetPhase2LeotherasDemon(botAI)) - { - if (botAI->IsTank(bot, false)) - { - if (bot->getClass() == CLASS_DEATH_KNIGHT) - { - if (botAI->HasStrategy("blood", BotState::BOT_STATE_COMBAT)) - { - botAI->ChangeStrategy("-blood", BotState::BOT_STATE_COMBAT); - botAI->ChangeStrategy("+frost", BotState::BOT_STATE_COMBAT); - } - } - if (bot->getClass() == CLASS_DRUID) - { - if (botAI->HasStrategy("bear", BotState::BOT_STATE_COMBAT)) - { - botAI->ChangeStrategy("-bear", BotState::BOT_STATE_COMBAT); - botAI->ChangeStrategy("+cat", BotState::BOT_STATE_COMBAT); - } - if (bot->HasAura(SPELL_DIRE_BEAR_FORM)) - bot->RemoveAura(SPELL_DIRE_BEAR_FORM); - } - if (bot->getClass() == CLASS_PALADIN) - { - if (botAI->HasStrategy("tank", BotState::BOT_STATE_COMBAT)) - { - botAI->ChangeStrategy("-tank", BotState::BOT_STATE_COMBAT); - botAI->ChangeStrategy("+dps", BotState::BOT_STATE_COMBAT); - } - } - if (bot->getClass() == CLASS_WARRIOR) - { - if (botAI->HasStrategy("tank", BotState::BOT_STATE_COMBAT)) - { - botAI->ChangeStrategy("-tank", BotState::BOT_STATE_COMBAT); - botAI->ChangeStrategy("+arms", BotState::BOT_STATE_COMBAT); - } - } - } - } - else - { - if (botAI->IsTank(bot, true)) - { - if (bot->getClass() == CLASS_DEATH_KNIGHT) - { - if (botAI->HasStrategy("frost", BotState::BOT_STATE_COMBAT)) - { - botAI->ChangeStrategy("-frost", BotState::BOT_STATE_COMBAT); - botAI->ChangeStrategy("+blood", BotState::BOT_STATE_COMBAT); - } - } - if (bot->getClass() == CLASS_DRUID) - { - if (botAI->HasStrategy("cat", BotState::BOT_STATE_COMBAT)) - { - botAI->ChangeStrategy("-cat", BotState::BOT_STATE_COMBAT); - botAI->ChangeStrategy("+bear", BotState::BOT_STATE_COMBAT); - } - } - if (bot->getClass() == CLASS_PALADIN) - { - if (botAI->HasStrategy("dps", BotState::BOT_STATE_COMBAT)) - { - botAI->ChangeStrategy("-dps", BotState::BOT_STATE_COMBAT); - botAI->ChangeStrategy("+tank", BotState::BOT_STATE_COMBAT); - } - } - if (bot->getClass() == CLASS_WARRIOR) - { - if (botAI->HasStrategy("arms", BotState::BOT_STATE_COMBAT)) - { - botAI->ChangeStrategy("-arms", BotState::BOT_STATE_COMBAT); - botAI->ChangeStrategy("+tank", BotState::BOT_STATE_COMBAT); - } - } - } - } - Unit* innerDemon = nullptr; auto const& npcs = botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); @@ -972,92 +863,110 @@ bool LeotherasTheBlindDestroyInnerDemonAction::Execute(Event event) if (innerDemon) { - // False = determine role by current strategy - if (botAI->IsHeal(bot, false)) - { - if (bot->getClass() == CLASS_DRUID) - { - if (botAI->HasStrategy("heal", BotState::BOT_STATE_COMBAT)) - { - botAI->ChangeStrategy("-heal", BotState::BOT_STATE_COMBAT); - botAI->ChangeStrategy("+caster", BotState::BOT_STATE_COMBAT); - } - if (bot->HasAura(SPELL_TREE_OF_LIFE)) - bot->RemoveAura(SPELL_TREE_OF_LIFE); - } - if (bot->getClass() == CLASS_PALADIN) - { - if (botAI->HasStrategy("heal", BotState::BOT_STATE_COMBAT)) - { - botAI->ChangeStrategy("-heal", BotState::BOT_STATE_COMBAT); - botAI->ChangeStrategy("+dps", BotState::BOT_STATE_COMBAT); - } - } - if (bot->getClass() == CLASS_PRIEST) - { - if (botAI->HasStrategy("heal", BotState::BOT_STATE_COMBAT) || - botAI->HasStrategy("holy heal", BotState::BOT_STATE_COMBAT)) - { - botAI->ChangeStrategy("-heal", BotState::BOT_STATE_COMBAT); - botAI->ChangeStrategy("-holy heal", BotState::BOT_STATE_COMBAT); - botAI->ChangeStrategy("+holy dps", BotState::BOT_STATE_COMBAT); - } - } - if (bot->getClass() == CLASS_SHAMAN) - { - if (botAI->HasStrategy("resto", BotState::BOT_STATE_COMBAT)) - { - botAI->ChangeStrategy("-resto", BotState::BOT_STATE_COMBAT); - botAI->ChangeStrategy("+ele", BotState::BOT_STATE_COMBAT); - } - } - } - + // Everybody needs to focus on their Inner Demons; dps assist is disabled + // via multipliers if (bot->GetTarget() != innerDemon->GetGUID()) return Attack(innerDemon); + + if (botAI->IsTank(bot) && bot->getClass() == CLASS_DRUID) + return HandleFeralTankStrategy(innerDemon); + + if (botAI->IsHeal(bot)) + return HandleHealerStrategy(innerDemon); } - else + + return false; +} + +// At 50% nerfed damage, bears have trouble killing their Inner Demons without a specific strategy +// Warrior and Paladin tanks have no trouble in my experience (Prot Warriors have high DPS, and +// Prot Paladins have an advantage in that Inner Demons are weak to Holy) +bool LeotherasTheBlindDestroyInnerDemonAction::HandleFeralTankStrategy(Unit* innerDemon) +{ + if (bot->HasAura(SPELL_DIRE_BEAR_FORM)) + bot->RemoveAura(SPELL_DIRE_BEAR_FORM); + + if (bot->HasAura(SPELL_BEAR_FORM)) + bot->RemoveAura(SPELL_BEAR_FORM); + + bool casted = false; + + if (!bot->HasAura(SPELL_CAT_FORM) && botAI->CanCastSpell("cat form", bot)) + casted |= botAI->CastSpell("cat form", bot); + + if (bot->GetComboPoints() >= 4 && botAI->CanCastSpell("ferocious bite", innerDemon)) + casted |= botAI->CastSpell("ferocious bite", innerDemon); + + if (bot->GetComboPoints() == 0 && innerDemon->GetHealthPct() > 20.0f && + botAI->CanCastSpell("rake", innerDemon)) + casted |= botAI->CastSpell("rake", innerDemon); + + if (botAI->CanCastSpell("mangle (cat)", innerDemon)) + casted |= botAI->CastSpell("mangle (cat)", innerDemon); + + return casted; +} + +bool LeotherasTheBlindDestroyInnerDemonAction::HandleHealerStrategy(Unit* innerDemon) +{ + if (bot->getClass() == CLASS_DRUID) { - if (botAI->IsHeal(bot, true)) - { - if (bot->getClass() == CLASS_DRUID) - { - if (botAI->HasStrategy("caster", BotState::BOT_STATE_COMBAT)) - { - botAI->ChangeStrategy("-caster", BotState::BOT_STATE_COMBAT); - botAI->ChangeStrategy("+heal", BotState::BOT_STATE_COMBAT); - } - } - if (bot->getClass() == CLASS_PALADIN) - { - if (botAI->HasStrategy("dps", BotState::BOT_STATE_COMBAT)) - { - botAI->ChangeStrategy("-dps", BotState::BOT_STATE_COMBAT); - botAI->ChangeStrategy("+heal", BotState::BOT_STATE_COMBAT); - } - } - if (bot->getClass() == CLASS_PRIEST) - { - if (botAI->HasStrategy("holy dps", BotState::BOT_STATE_COMBAT)) - { - botAI->ChangeStrategy("-holy dps", BotState::BOT_STATE_COMBAT); + if (bot->HasAura(SPELL_TREE_OF_LIFE)) + bot->RemoveAura(SPELL_TREE_OF_LIFE); - uint8 tab = AiFactory::GetPlayerSpecTab(bot); - if (tab == PRIEST_TAB_DISCIPLINE) - botAI->ChangeStrategy("+heal", BotState::BOT_STATE_COMBAT); - else - botAI->ChangeStrategy("+holy heal", BotState::BOT_STATE_COMBAT); - } - } - if (bot->getClass() == CLASS_SHAMAN) - { - if (botAI->HasStrategy("ele", BotState::BOT_STATE_COMBAT)) - { - botAI->ChangeStrategy("-ele", BotState::BOT_STATE_COMBAT); - botAI->ChangeStrategy("+resto", BotState::BOT_STATE_COMBAT); - } - } - } + bool casted = false; + + if (botAI->CanCastSpell("barkskin", bot)) + casted |= botAI->CastSpell("barkskin", bot); + + if (botAI->CanCastSpell("wrath", innerDemon)) + casted |= botAI->CastSpell("wrath", innerDemon); + + return casted; + } + else if (bot->getClass() == CLASS_PALADIN) + { + bool casted = false; + + if (botAI->CanCastSpell("avenging wrath", bot)) + casted |= botAI->CastSpell("avenging wrath", bot); + + if (botAI->CanCastSpell("consecration", bot)) + casted |= botAI->CastSpell("consecration", bot); + + if (botAI->CanCastSpell("exorcism", innerDemon)) + casted |= botAI->CastSpell("exorcism", innerDemon); + + if (botAI->CanCastSpell("hammer of wrath", innerDemon)) + casted |= botAI->CastSpell("hammer of wrath", innerDemon); + + if (botAI->CanCastSpell("holy shock", innerDemon)) + casted |= botAI->CastSpell("holy shock", innerDemon); + + if (botAI->CanCastSpell("judgment of light", innerDemon)) + casted |= botAI->CastSpell("judgment of light", innerDemon); + + return casted; + } + else if (bot->getClass() == CLASS_PRIEST) + { + if (botAI->CanCastSpell("smite", innerDemon)) + return botAI->CastSpell("smite", innerDemon); + } + else if (bot->getClass() == CLASS_SHAMAN) + { + bool casted = false; + + if (botAI->CanCastSpell("earth shock", innerDemon)) + casted |= botAI->CastSpell("earth shock", innerDemon); + + if (botAI->CanCastSpell("chain lightning", innerDemon)) + casted |= botAI->CastSpell("chain lightning", innerDemon); + + if (botAI->CanCastSpell("lightning bolt", innerDemon)) + casted |= botAI->CastSpell("lightning bolt", innerDemon); + + return casted; } return false; @@ -1098,6 +1007,7 @@ bool LeotherasTheBlindFinalPhaseAssignDpsPriorityAction::Execute(Event event) return false; } +// Misdirect to Warlock tank or to main tank if there is no Warlock tank bool LeotherasTheBlindMisdirectBossToDemonFormTankAction::Execute(Event event) { Unit* leotherasDemon = GetActiveLeotherasDemon(botAI); @@ -1105,11 +1015,29 @@ bool LeotherasTheBlindMisdirectBossToDemonFormTankAction::Execute(Event event) return false; Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); - if (!demonFormTank) + Player* targetTank = demonFormTank; + + if (!targetTank) + { + if (Group* group = bot->GetGroup()) + { + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->IsAlive() && botAI->IsMainTank(member)) + { + targetTank = member; + break; + } + } + } + } + + if (!targetTank) return false; - if (botAI->CanCastSpell("misdirection", demonFormTank)) - return botAI->CastSpell("misdirection", demonFormTank); + if (botAI->CanCastSpell("misdirection", targetTank)) + return botAI->CastSpell("misdirection", targetTank); if (bot->HasAura(SPELL_MISDIRECTION) && botAI->CanCastSpell("steady shot", leotherasDemon)) return botAI->CastSpell("steady shot", leotherasDemon); @@ -2230,7 +2158,7 @@ bool LadyVashjTankAttackAndMoveAwayStriderAction::Execute(Event event) return botAI->CastSpell("heavy netherweave net", strider); } - if (!strider->HasAura(SPELL_FROST_SHOCK) && bot->getClass() == CLASS_SHAMAN && + if (!botAI->HasAura("frost shock", strider) && bot->getClass() == CLASS_SHAMAN && botAI->CanCastSpell("frost shock", strider)) return botAI->CastSpell("frost shock", strider); diff --git a/src/Scenario/RaidAi/SerpentshrineCavern/Action/RaidSSCActions.h b/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.h similarity index 97% rename from src/Scenario/RaidAi/SerpentshrineCavern/Action/RaidSSCActions.h rename to src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.h index 5c3a368298..7503b6e071 100644 --- a/src/Scenario/RaidAi/SerpentshrineCavern/Action/RaidSSCActions.h +++ b/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.h @@ -163,6 +163,14 @@ class LeotherasTheBlindDemonFormTankAttackBossAction : public AttackAction bool Execute(Event event) override; }; +class LeotherasTheBlindMeleeTanksDontAttackDemonFormAction : public Action +{ +public: + LeotherasTheBlindMeleeTanksDontAttackDemonFormAction( + PlayerbotAI* botAI, std::string const name = "leotheras the blind melee tanks don't attack demon form") : Action(botAI, name) {} + bool Execute(Event event) override; +}; + class LeotherasTheBlindRunAwayFromWhirlwindAction : public MovementAction { public: @@ -185,6 +193,10 @@ class LeotherasTheBlindDestroyInnerDemonAction : public AttackAction LeotherasTheBlindDestroyInnerDemonAction( PlayerbotAI* botAI, std::string const name = "leotheras the blind destroy inner demon") : AttackAction(botAI, name) {} bool Execute(Event event) override; + +private: + bool HandleFeralTankStrategy(Unit* innerDemon); + bool HandleHealerStrategy(Unit* innerDemon); }; class LeotherasTheBlindFinalPhaseAssignDpsPriorityAction : public AttackAction diff --git a/src/Scenario/RaidAi/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp b/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp similarity index 95% rename from src/Scenario/RaidAi/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp rename to src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp index 2ec6a5494d..30fcfc8aea 100644 --- a/src/Scenario/RaidAi/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp @@ -6,6 +6,7 @@ #include "DKActions.h" #include "DruidActions.h" #include "DruidBearActions.h" +#include "DruidShapeshiftActions.h" #include "FollowActions.h" #include "GenericSpellActions.h" #include "HunterActions.h" @@ -248,31 +249,26 @@ float LeotherasTheBlindAvoidWhirlwindMultiplier::GetValue(Action* action) return 1.0f; } -// Applies only if there is a Warlock tank float LeotherasTheBlindDisableTankActionsMultiplier::GetValue(Action* action) { if (!GetActiveLeotherasDemon(botAI)) return 1.0f; - Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); - if (!demonFormTank) - return 1.0f; + if (GetLeotherasDemonFormTank(botAI, bot) == bot) + { + // (1) Warlock tank will not use Shadow Ward + // Shadow Ward is coded into the Warlock tank strategy (for Twin Emps) but is useless here + if (dynamic_cast(action)) + return 0.0f; - // (1) Warlock tank will not use Shadow Ward - // Shadow Ward is coded into the Warlock tank strategy (for Twin Emps) but is useless here - if (dynamic_cast(action)) - return 0.0f; + return 1.0f; + } - // (2) Phase 2 only: disable tank assist for non-demon form tanks - // This is a fallback since there is an action that switches them to dps strategy - if (botAI->IsTank(bot) && bot != demonFormTank && GetPhase2LeotherasDemon(botAI)) + // (2) Phase 2 only: non-Warlock tanks should not attack Leotheras + if (botAI->IsTank(bot) && GetPhase2LeotherasDemon(botAI) && + !bot->HasAura(SPELL_INSIDIOUS_WHISPER)) { - /* if ((dynamic_cast(action) && - !dynamic_cast(action)) || - dynamic_cast(action)) - return 0.0f; */ - - if (dynamic_cast(action)) + if (dynamic_cast(action)) return 0.0f; } @@ -290,7 +286,10 @@ float LeotherasTheBlindFocusOnInnerDemonMultiplier::GetValue(Action* action) dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action)) + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) return 0.0f; } @@ -580,10 +579,12 @@ float MorogrimTidewalkerMaintainPhase2StackingMultiplier::GetValue(Action* actio // Don't use other major cooldowns in Phase 1, either float LadyVashjDelayCooldownsMultiplier::GetValue(Action* action) { + if (!AI_VALUE2(Unit*, "find target", "lady vashj")) + return 1.0f; + if (bot->getClass() == CLASS_SHAMAN) { - if (!AI_VALUE2(Unit*, "find target", "lady vashj") || - IsLadyVashjInPhase3(botAI)) + if (IsLadyVashjInPhase3(botAI)) return 1.0f; if (dynamic_cast(action) || @@ -591,7 +592,7 @@ float LadyVashjDelayCooldownsMultiplier::GetValue(Action* action) return 0.0f; } - if (botAI->IsDps(bot) && !IsLadyVashjInPhase1(botAI)) + if (botAI->IsDps(bot) && IsLadyVashjInPhase1(botAI)) { if (dynamic_cast(action) || dynamic_cast(action) || diff --git a/src/Scenario/RaidAi/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.h b/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.h similarity index 100% rename from src/Scenario/RaidAi/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.h rename to src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.h diff --git a/src/Scenario/RaidAi/SerpentshrineCavern/RaidSSCActionContext.h b/src/Ai/Raid/SerpentshrineCavern/RaidSSCActionContext.h similarity index 97% rename from src/Scenario/RaidAi/SerpentshrineCavern/RaidSSCActionContext.h rename to src/Ai/Raid/SerpentshrineCavern/RaidSSCActionContext.h index dde6205201..e6dce16949 100644 --- a/src/Scenario/RaidAi/SerpentshrineCavern/RaidSSCActionContext.h +++ b/src/Ai/Raid/SerpentshrineCavern/RaidSSCActionContext.h @@ -65,6 +65,9 @@ class RaidSSCActionContext : public NamedObjectContext creators["leotheras the blind demon form tank attack boss"] = &RaidSSCActionContext::leotheras_the_blind_demon_form_tank_attack_boss; + creators["leotheras the blind melee tanks don't attack demon form"] = + &RaidSSCActionContext::leotheras_the_blind_melee_tanks_dont_attack_demon_form; + creators["leotheras the blind position ranged"] = &RaidSSCActionContext::leotheras_the_blind_position_ranged; @@ -225,6 +228,9 @@ class RaidSSCActionContext : public NamedObjectContext static Action* leotheras_the_blind_demon_form_tank_attack_boss( PlayerbotAI* botAI) { return new LeotherasTheBlindDemonFormTankAttackBossAction(botAI); } + static Action* leotheras_the_blind_melee_tanks_dont_attack_demon_form( + PlayerbotAI* botAI) { return new LeotherasTheBlindMeleeTanksDontAttackDemonFormAction(botAI); } + static Action* leotheras_the_blind_position_ranged( PlayerbotAI* botAI) { return new LeotherasTheBlindPositionRangedAction(botAI); } diff --git a/src/Scenario/RaidAi/SerpentshrineCavern/Util/RaidSSCHelpers.cpp b/src/Ai/Raid/SerpentshrineCavern/RaidSSCHelpers.cpp similarity index 97% rename from src/Scenario/RaidAi/SerpentshrineCavern/Util/RaidSSCHelpers.cpp rename to src/Ai/Raid/SerpentshrineCavern/RaidSSCHelpers.cpp index e0868153f9..2cc991cc29 100644 --- a/src/Scenario/RaidAi/SerpentshrineCavern/Util/RaidSSCHelpers.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/RaidSSCHelpers.cpp @@ -246,20 +246,27 @@ namespace SerpentShrineCavernHelpers return member; } - // (2) Second loop: Return the first Warlock bot with the tank strategy + // (2) Fall back to bot Warlock with highest HP + Player* highestHpWarlock = nullptr; + uint32 highestHp = 0; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (!member || !member->IsAlive() || member->getClass() != CLASS_WARLOCK) + if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) || + member->getClass() != CLASS_WARLOCK) continue; - PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); - if (memberAI && memberAI->HasStrategy("tank", BotState::BOT_STATE_COMBAT)) - return member; + uint32 hp = member->GetMaxHealth(); + if (!highestHpWarlock || hp > highestHp) + { + highestHpWarlock = member; + highestHp = hp; + } } - // (3) If no assistant or tank Warlock found, return nullptr - return nullptr; + // (3) Return the found Warlock tank, or nullptr if none found + return highestHpWarlock; } // Fathom-Lord Karathress diff --git a/src/Scenario/RaidAi/SerpentshrineCavern/Util/RaidSSCHelpers.h b/src/Ai/Raid/SerpentshrineCavern/RaidSSCHelpers.h similarity index 98% rename from src/Scenario/RaidAi/SerpentshrineCavern/Util/RaidSSCHelpers.h rename to src/Ai/Raid/SerpentshrineCavern/RaidSSCHelpers.h index d1d907c515..b2a980f046 100644 --- a/src/Scenario/RaidAi/SerpentshrineCavern/Util/RaidSSCHelpers.h +++ b/src/Ai/Raid/SerpentshrineCavern/RaidSSCHelpers.h @@ -48,6 +48,8 @@ namespace SerpentShrineCavernHelpers SPELL_ENTANGLE = 38316, // Druid + SPELL_CAT_FORM = 768, + SPELL_BEAR_FORM = 5487, SPELL_DIRE_BEAR_FORM = 9634, SPELL_TREE_OF_LIFE = 33891, @@ -59,7 +61,6 @@ namespace SerpentShrineCavernHelpers // Shaman SPELL_GROUNDING_TOTEM_EFFECT = 8178, - SPELL_FROST_SHOCK = 25464, // Warlock SPELL_CURSE_OF_EXHAUSTION = 18223, diff --git a/src/Scenario/RaidAi/SerpentshrineCavern/RaidSSCTriggerContext.h b/src/Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h similarity index 97% rename from src/Scenario/RaidAi/SerpentshrineCavern/RaidSSCTriggerContext.h rename to src/Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h index 3fd19e524a..13135bf3e5 100644 --- a/src/Scenario/RaidAi/SerpentshrineCavern/RaidSSCTriggerContext.h +++ b/src/Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h @@ -65,6 +65,9 @@ class RaidSSCTriggerContext : public NamedObjectContext creators["leotheras the blind boss transformed into demon form"] = &RaidSSCTriggerContext::leotheras_the_blind_boss_transformed_into_demon_form; + creators["leotheras the blind only warlock should tank demon form"] = + &RaidSSCTriggerContext::leotheras_the_blind_only_warlock_should_tank_demon_form; + creators["leotheras the blind boss engaged by ranged"] = &RaidSSCTriggerContext::leotheras_the_blind_boss_engaged_by_ranged; @@ -219,6 +222,9 @@ class RaidSSCTriggerContext : public NamedObjectContext static Trigger* leotheras_the_blind_boss_transformed_into_demon_form( PlayerbotAI* botAI) { return new LeotherasTheBlindBossTransformedIntoDemonFormTrigger(botAI); } + static Trigger* leotheras_the_blind_only_warlock_should_tank_demon_form( + PlayerbotAI* botAI) { return new LeotherasTheBlindOnlyWarlockShouldTankDemonFormTrigger(botAI); } + static Trigger* leotheras_the_blind_boss_engaged_by_ranged( PlayerbotAI* botAI) { return new LeotherasTheBlindBossEngagedByRangedTrigger(botAI); } diff --git a/src/Scenario/RaidAi/SerpentshrineCavern/Strategy/RaidSSCStrategy.cpp b/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.cpp similarity index 97% rename from src/Scenario/RaidAi/SerpentshrineCavern/Strategy/RaidSSCStrategy.cpp rename to src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.cpp index 62b4e22a52..63886f039d 100644 --- a/src/Scenario/RaidAi/SerpentshrineCavern/Strategy/RaidSSCStrategy.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.cpp @@ -59,6 +59,9 @@ void RaidSSCStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("leotheras the blind boss transformed into demon form", { NextAction("leotheras the blind demon form tank attack boss", ACTION_EMERGENCY + 6) })); + triggers.push_back(new TriggerNode("leotheras the blind only warlock should tank demon form", { + NextAction("leotheras the blind melee tanks don't attack demon form", ACTION_RAID + 1) })); + triggers.push_back(new TriggerNode("leotheras the blind boss engaged by ranged", { NextAction("leotheras the blind position ranged", ACTION_RAID + 1) })); @@ -66,16 +69,16 @@ void RaidSSCStrategy::InitTriggers(std::vector& triggers) NextAction("leotheras the blind run away from whirlwind", ACTION_EMERGENCY + 1) })); triggers.push_back(new TriggerNode("leotheras the blind bot has too many chaos blast stacks", { - NextAction("leotheras the blind melee dps run away from boss", ACTION_EMERGENCY + 7) })); + NextAction("leotheras the blind melee dps run away from boss", ACTION_EMERGENCY + 6) })); triggers.push_back(new TriggerNode("leotheras the blind inner demon has awakened", { - NextAction("leotheras the blind destroy inner demon", ACTION_EMERGENCY + 6) })); + NextAction("leotheras the blind destroy inner demon", ACTION_EMERGENCY + 7) })); triggers.push_back(new TriggerNode("leotheras the blind entered final phase", { - NextAction("leotheras the blind final phase assign dps priority", ACTION_RAID + 2) })); + NextAction("leotheras the blind final phase assign dps priority", ACTION_RAID + 1) })); triggers.push_back(new TriggerNode("leotheras the blind demon form tank needs aggro", { - NextAction("leotheras the blind misdirect boss to demon form tank", ACTION_RAID + 3) })); + NextAction("leotheras the blind misdirect boss to demon form tank", ACTION_RAID + 2) })); triggers.push_back(new TriggerNode("leotheras the blind boss wipes aggro upon phase change", { NextAction("leotheras the blind manage dps wait timers", ACTION_EMERGENCY + 10) })); diff --git a/src/Scenario/RaidAi/SerpentshrineCavern/Strategy/RaidSSCStrategy.h b/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.h similarity index 100% rename from src/Scenario/RaidAi/SerpentshrineCavern/Strategy/RaidSSCStrategy.h rename to src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.h diff --git a/src/Scenario/RaidAi/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp b/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp similarity index 93% rename from src/Scenario/RaidAi/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp rename to src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp index aebe50736c..d524e967b2 100644 --- a/src/Scenario/RaidAi/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp @@ -195,26 +195,50 @@ bool LeotherasTheBlindBossTransformedIntoDemonFormTrigger::IsActive() GetLeotherasDemonFormTank(botAI, bot) == bot; } -bool LeotherasTheBlindBossEngagedByRangedTrigger::IsActive() +bool LeotherasTheBlindOnlyWarlockShouldTankDemonFormTrigger::IsActive() { - if (!botAI->IsRanged(bot)) + if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) return false; + if (!botAI->IsTank(bot)) + return false; + + if (!GetPhase2LeotherasDemon(botAI)) + return false; + + Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST); + if (chaosBlast && chaosBlast->GetStackAmount() >= 5) + return false; + + if (!GetLeotherasDemonFormTank(botAI, bot)) + return false; + return true; +} + +bool LeotherasTheBlindBossEngagedByRangedTrigger::IsActive() +{ + if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) + return false; + + if (!botAI->IsRanged(bot)) + return false; Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); - if (!leotheras || - leotheras->HasAura(SPELL_LEOTHERAS_BANISHED) || - leotheras->HasAura(SPELL_WHIRLWIND) || - leotheras->HasAura(SPELL_WHIRLWIND_CHANNEL)) + if (!leotheras) return false; - return GetLeotherasDemonFormTank(botAI, bot) != bot; + return !leotheras->HasAura(SPELL_LEOTHERAS_BANISHED) && + !leotheras->HasAura(SPELL_WHIRLWIND) && + !leotheras->HasAura(SPELL_WHIRLWIND_CHANNEL); } bool LeotherasTheBlindBossChannelingWhirlwindTrigger::IsActive() { - if (botAI->IsTank(bot, true) && botAI->IsMelee(bot)) + if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) + return false; + + if (botAI->IsTank(bot) && botAI->IsMelee(bot)) return false; Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); @@ -227,23 +251,29 @@ bool LeotherasTheBlindBossChannelingWhirlwindTrigger::IsActive() bool LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger::IsActive() { - if (botAI->IsRanged(bot) || botAI->IsTank(bot, true)) + if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) + return false; + + if (botAI->IsRanged(bot)) return false; Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST); - if (!chaosBlast || chaosBlast->GetStackAmount() < 4) + if (!chaosBlast || chaosBlast->GetStackAmount() < 5) + return false; + + if (!GetPhase2LeotherasDemon(botAI)) + return false; + + Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); + if (!demonFormTank && botAI->IsMainTank(bot)) return false; - return GetPhase2LeotherasDemon(botAI); + return true; } bool LeotherasTheBlindInnerDemonHasAwakenedTrigger::IsActive() { - /* if (!botAI->HasCheat(BotCheatMask::raid)) - return false; - - return bot->HasAura(SPELL_INSIDIOUS_WHISPER); */ - if (!AI_VALUE2(Unit*, "find target", "leotheras the blind")) + if (!bot->HasAura(SPELL_INSIDIOUS_WHISPER)) return false; return GetLeotherasDemonFormTank(botAI, bot) != bot; @@ -254,7 +284,7 @@ bool LeotherasTheBlindEnteredFinalPhaseTrigger::IsActive() if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) return false; - if (botAI->IsHeal(bot, true)) + if (botAI->IsHeal(bot)) return false; if (GetLeotherasDemonFormTank(botAI, bot) == bot) @@ -266,10 +296,10 @@ bool LeotherasTheBlindEnteredFinalPhaseTrigger::IsActive() bool LeotherasTheBlindDemonFormTankNeedsAggro::IsActive() { - if (bot->getClass() != CLASS_HUNTER) + if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) return false; - if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) + if (bot->getClass() != CLASS_HUNTER) return false; return AI_VALUE2(Unit*, "find target", "leotheras the blind"); diff --git a/src/Scenario/RaidAi/SerpentshrineCavern/Trigger/RaidSSCTriggers.h b/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.h similarity index 97% rename from src/Scenario/RaidAi/SerpentshrineCavern/Trigger/RaidSSCTriggers.h rename to src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.h index 6e79eaa0ef..6a82853ef1 100644 --- a/src/Scenario/RaidAi/SerpentshrineCavern/Trigger/RaidSSCTriggers.h +++ b/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.h @@ -149,6 +149,14 @@ class LeotherasTheBlindHumanFormEngagedByMainTankTrigger : public Trigger bool IsActive() override; }; +class LeotherasTheBlindOnlyWarlockShouldTankDemonFormTrigger : public Trigger +{ +public: + LeotherasTheBlindOnlyWarlockShouldTankDemonFormTrigger( + PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind only warlock should tank demon form") {} + bool IsActive() override; +}; + class LeotherasTheBlindBossTransformedIntoDemonFormTrigger : public Trigger { public: From 3a2d5ad6d3c729316c82fc953e4c6b534ee5a89e Mon Sep 17 00:00:00 2001 From: crow Date: Sat, 24 Jan 2026 17:39:01 -0600 Subject: [PATCH 21/25] Update for assistant function commit --- .../Action/RaidSSCActions.cpp | 216 ++++++++++++------ .../Multiplier/RaidSSCMultipliers.cpp | 15 +- .../SerpentshrineCavern/RaidSSCHelpers.cpp | 8 +- .../Trigger/RaidSSCTriggers.cpp | 88 +++---- 4 files changed, 194 insertions(+), 133 deletions(-) diff --git a/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp b/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp index 75ccb1f97e..106e2904ad 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp @@ -20,29 +20,40 @@ bool SerpentShrineCavernEraseTimersAndTrackersAction::Execute(Event event) bool erased = false; if (!AI_VALUE2(Unit*, "find target", "hydross the unstable")) { - erased |= hydrossChangeToNaturePhaseTimer.erase(instanceId) > 0; - erased |= hydrossChangeToFrostPhaseTimer.erase(instanceId) > 0; - erased |= hydrossNatureDpsWaitTimer.erase(instanceId) > 0; - erased |= hydrossFrostDpsWaitTimer.erase(instanceId) > 0; + if (hydrossChangeToNaturePhaseTimer.erase(instanceId) > 0) + erased = true; + if (hydrossChangeToFrostPhaseTimer.erase(instanceId) > 0) + erased = true; + if (hydrossNatureDpsWaitTimer.erase(instanceId) > 0) + erased = true; + if (hydrossFrostDpsWaitTimer.erase(instanceId) > 0) + erased = true; } if (!AI_VALUE2(Unit*, "find target", "the lurker below")) { - erased |= lurkerRangedPositions.erase(guid) > 0; - erased |= lurkerSpoutTimer.erase(instanceId) > 0; + if (lurkerRangedPositions.erase(guid) > 0) + erased = true; + if (lurkerSpoutTimer.erase(instanceId) > 0) + erased = true; } if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) { - erased |= karathressDpsWaitTimer.erase(instanceId) > 0; + if (karathressDpsWaitTimer.erase(instanceId) > 0) + erased = true; } if (!AI_VALUE2(Unit*, "find target", "morogrim tidewalker")) { - erased |= tidewalkerTankStep.erase(guid) > 0; - erased |= tidewalkerRangedStep.erase(guid) > 0; + if (tidewalkerTankStep.erase(guid) > 0) + erased = true; + if (tidewalkerRangedStep.erase(guid) > 0) + erased = true; } if (!AI_VALUE2(Unit*, "find target", "lady vashj")) { - erased |= vashjRangedPositions.erase(guid) > 0; - erased |= hasReachedVashjRangedPosition.erase(guid) > 0; + if (vashjRangedPositions.erase(guid) > 0) + erased = true; + if (hasReachedVashjRangedPosition.erase(guid) > 0) + erased = true; } return erased; @@ -470,20 +481,31 @@ bool HydrossTheUnstableManageTimersAction::Execute(Event event) bool changed = false; if (!hydross->HasAura(SPELL_CORRUPTION)) { - changed |= hydrossFrostDpsWaitTimer.try_emplace(instanceId, now).second; - changed |= hydrossNatureDpsWaitTimer.erase(instanceId) > 0; - changed |= hydrossChangeToFrostPhaseTimer.erase(instanceId) > 0; - + if (hydrossFrostDpsWaitTimer.try_emplace(instanceId, now).second) + changed = true; + if (hydrossNatureDpsWaitTimer.erase(instanceId) > 0) + changed = true; + if (hydrossChangeToFrostPhaseTimer.erase(instanceId) > 0) + changed = true; if (HasMarkOfHydrossAt100Percent(bot)) - changed |= hydrossChangeToNaturePhaseTimer.try_emplace(instanceId, now).second; + { + if (hydrossChangeToNaturePhaseTimer.try_emplace(instanceId, now).second) + changed = true; + } } else { - changed |= hydrossNatureDpsWaitTimer.try_emplace(instanceId, now).second; - changed |= hydrossFrostDpsWaitTimer.erase(instanceId) > 0; - changed |= hydrossChangeToNaturePhaseTimer.erase(instanceId) > 0; + if (hydrossNatureDpsWaitTimer.try_emplace(instanceId, now).second) + changed = true; + if (hydrossFrostDpsWaitTimer.erase(instanceId) > 0) + changed = true; + if (hydrossChangeToNaturePhaseTimer.erase(instanceId) > 0) + changed = true; if (HasMarkOfCorruptionAt100Percent(bot)) - changed |= hydrossChangeToFrostPhaseTimer.try_emplace(instanceId, now).second; + { + if (hydrossChangeToFrostPhaseTimer.try_emplace(instanceId, now).second) + changed = true; + } } return changed; @@ -863,16 +885,16 @@ bool LeotherasTheBlindDestroyInnerDemonAction::Execute(Event event) if (innerDemon) { - // Everybody needs to focus on their Inner Demons; dps assist is disabled - // via multipliers - if (bot->GetTarget() != innerDemon->GetGUID()) - return Attack(innerDemon); - if (botAI->IsTank(bot) && bot->getClass() == CLASS_DRUID) return HandleFeralTankStrategy(innerDemon); if (botAI->IsHeal(bot)) return HandleHealerStrategy(innerDemon); + + // Roles without a strategy need to affirmatively attack their Inner Demons + // Because DPS assist is disabled via multipliers + if (bot->GetTarget() != innerDemon->GetGUID()) + return Attack(innerDemon); } return false; @@ -890,19 +912,37 @@ bool LeotherasTheBlindDestroyInnerDemonAction::HandleFeralTankStrategy(Unit* inn bot->RemoveAura(SPELL_BEAR_FORM); bool casted = false; - if (!bot->HasAura(SPELL_CAT_FORM) && botAI->CanCastSpell("cat form", bot)) - casted |= botAI->CastSpell("cat form", bot); - + { + if (botAI->CastSpell("cat form", bot)) + casted = true; + } + if (botAI->CanCastSpell("berserk", bot)) + { + if (botAI->CastSpell("berserk", bot)) + casted = true; + } + if (bot->GetPower(POWER_ENERGY) < 30 && botAI->CanCastSpell("tiger's fury", bot)) + { + if (botAI->CastSpell("tiger's fury", bot)) + casted = true; + } if (bot->GetComboPoints() >= 4 && botAI->CanCastSpell("ferocious bite", innerDemon)) - casted |= botAI->CastSpell("ferocious bite", innerDemon); - - if (bot->GetComboPoints() == 0 && innerDemon->GetHealthPct() > 20.0f && + { + if (botAI->CastSpell("ferocious bite", innerDemon)) + casted = true; + } + if (bot->GetComboPoints() == 0 && innerDemon->GetHealthPct() > 25.0f && botAI->CanCastSpell("rake", innerDemon)) - casted |= botAI->CastSpell("rake", innerDemon); - + { + if (botAI->CastSpell("rake", innerDemon)) + casted = true; + } if (botAI->CanCastSpell("mangle (cat)", innerDemon)) - casted |= botAI->CastSpell("mangle (cat)", innerDemon); + { + if (botAI->CastSpell("mangle (cat)", innerDemon)) + casted = true; + } return casted; } @@ -915,36 +955,52 @@ bool LeotherasTheBlindDestroyInnerDemonAction::HandleHealerStrategy(Unit* innerD bot->RemoveAura(SPELL_TREE_OF_LIFE); bool casted = false; - if (botAI->CanCastSpell("barkskin", bot)) - casted |= botAI->CastSpell("barkskin", bot); - + { + if (botAI->CastSpell("barkskin", bot)) + casted = true; + } if (botAI->CanCastSpell("wrath", innerDemon)) - casted |= botAI->CastSpell("wrath", innerDemon); + { + if (botAI->CastSpell("wrath", innerDemon)) + casted = true; + } return casted; } else if (bot->getClass() == CLASS_PALADIN) { bool casted = false; - if (botAI->CanCastSpell("avenging wrath", bot)) - casted |= botAI->CastSpell("avenging wrath", bot); - + { + if (botAI->CastSpell("avenging wrath", bot)) + casted = true; + } if (botAI->CanCastSpell("consecration", bot)) - casted |= botAI->CastSpell("consecration", bot); - + { + if (botAI->CastSpell("consecration", bot)) + casted = true; + } if (botAI->CanCastSpell("exorcism", innerDemon)) - casted |= botAI->CastSpell("exorcism", innerDemon); - + { + if (botAI->CastSpell("exorcism", innerDemon)) + casted = true; + } if (botAI->CanCastSpell("hammer of wrath", innerDemon)) - casted |= botAI->CastSpell("hammer of wrath", innerDemon); - + { + if (botAI->CastSpell("hammer of wrath", innerDemon)) + casted = true; + } if (botAI->CanCastSpell("holy shock", innerDemon)) - casted |= botAI->CastSpell("holy shock", innerDemon); - + { + if (botAI->CastSpell("holy shock", innerDemon)) + casted = true; + } if (botAI->CanCastSpell("judgment of light", innerDemon)) - casted |= botAI->CastSpell("judgment of light", innerDemon); + { + if (botAI->CastSpell("judgment of light", innerDemon)) + casted = true; + } return casted; } @@ -956,15 +1012,21 @@ bool LeotherasTheBlindDestroyInnerDemonAction::HandleHealerStrategy(Unit* innerD else if (bot->getClass() == CLASS_SHAMAN) { bool casted = false; - if (botAI->CanCastSpell("earth shock", innerDemon)) - casted |= botAI->CastSpell("earth shock", innerDemon); - + { + if (botAI->CastSpell("earth shock", innerDemon)) + casted = true; + } if (botAI->CanCastSpell("chain lightning", innerDemon)) - casted |= botAI->CastSpell("chain lightning", innerDemon); - + { + if (botAI->CastSpell("chain lightning", innerDemon)) + casted = true; + } if (botAI->CanCastSpell("lightning bolt", innerDemon)) - casted |= botAI->CastSpell("lightning bolt", innerDemon); + { + if (botAI->CastSpell("lightning bolt", innerDemon)) + casted = true; + } return casted; } @@ -1059,9 +1121,12 @@ bool LeotherasTheBlindManageDpsWaitTimersAction::Execute(Event event) // Encounter start/reset: clear all timers if (leotheras->HasAura(SPELL_LEOTHERAS_BANISHED)) { - changed |= leotherasHumanFormDpsWaitTimer.erase(instanceId) > 0; - changed |= leotherasDemonFormDpsWaitTimer.erase(instanceId) > 0; - changed |= leotherasFinalPhaseDpsWaitTimer.erase(instanceId) > 0; + if (leotherasHumanFormDpsWaitTimer.erase(instanceId) > 0) + changed = true; + if (leotherasDemonFormDpsWaitTimer.erase(instanceId) > 0) + changed = true; + if (leotherasFinalPhaseDpsWaitTimer.erase(instanceId) > 0) + changed = true; } // Human Phase @@ -1069,21 +1134,28 @@ bool LeotherasTheBlindManageDpsWaitTimersAction::Execute(Event event) Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI); if (leotherasHuman && !leotherasPhase3Demon) { - changed |= leotherasHumanFormDpsWaitTimer.try_emplace(instanceId, now).second; - changed |= leotherasDemonFormDpsWaitTimer.erase(instanceId) > 0; + if (leotherasHumanFormDpsWaitTimer.try_emplace(instanceId, now).second) + changed = true; + if (leotherasDemonFormDpsWaitTimer.erase(instanceId) > 0) + changed = true; } // Demon Phase else if (Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI)) { - changed |= leotherasDemonFormDpsWaitTimer.try_emplace(instanceId, now).second; - changed |= leotherasHumanFormDpsWaitTimer.erase(instanceId) > 0; + if (leotherasDemonFormDpsWaitTimer.try_emplace(instanceId, now).second) + changed = true; + if (leotherasHumanFormDpsWaitTimer.erase(instanceId) > 0) + changed = true; } // Final Phase (<15% HP) else if (leotherasHuman && leotherasPhase3Demon) { - changed |= leotherasFinalPhaseDpsWaitTimer.try_emplace(instanceId, now).second; - changed |= leotherasHumanFormDpsWaitTimer.erase(instanceId) > 0; - changed |= leotherasDemonFormDpsWaitTimer.erase(instanceId) > 0; + if (leotherasFinalPhaseDpsWaitTimer.try_emplace(instanceId, now).second) + changed = true; + if (leotherasHumanFormDpsWaitTimer.erase(instanceId) > 0) + changed = true; + if (leotherasDemonFormDpsWaitTimer.erase(instanceId) > 0) + changed = true; } return changed; @@ -2834,10 +2906,14 @@ bool LadyVashjEraseCorePassingTrackersAction::Execute(Event event) const uint32 instanceId = vashj->GetMap()->GetInstanceId(); bool erased = false; - erased |= nearestTriggerGuid.erase(instanceId) > 0; - erased |= lastImbueAttempt.erase(instanceId) > 0; - erased |= lastCoreInInventoryTime.erase(instanceId) > 0; - erased |= intendedLineup.erase(bot->GetGUID()) > 0; + if (nearestTriggerGuid.erase(instanceId) > 0) + erased = true; + if (lastImbueAttempt.erase(instanceId) > 0) + erased = true; + if (lastCoreInInventoryTime.erase(instanceId)) + erased = true; + if (intendedLineup.erase(bot->GetGUID()) > 0) + erased = true; return erased; } diff --git a/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp b/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp index 30fcfc8aea..5f5f2462bc 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp @@ -6,6 +6,7 @@ #include "DKActions.h" #include "DruidActions.h" #include "DruidBearActions.h" +#include "DruidCatActions.h" #include "DruidShapeshiftActions.h" #include "FollowActions.h" #include "GenericSpellActions.h" @@ -256,8 +257,8 @@ float LeotherasTheBlindDisableTankActionsMultiplier::GetValue(Action* action) if (GetLeotherasDemonFormTank(botAI, bot) == bot) { - // (1) Warlock tank will not use Shadow Ward - // Shadow Ward is coded into the Warlock tank strategy (for Twin Emps) but is useless here + // (1) Warlock tank will not use Shadow Ward, which is coded into the + // Warlock tank strategy (for Twin Emps) but is useless here if (dynamic_cast(action)) return 0.0f; @@ -265,10 +266,12 @@ float LeotherasTheBlindDisableTankActionsMultiplier::GetValue(Action* action) } // (2) Phase 2 only: non-Warlock tanks should not attack Leotheras - if (botAI->IsTank(bot) && GetPhase2LeotherasDemon(botAI) && - !bot->HasAura(SPELL_INSIDIOUS_WHISPER)) + if (botAI->IsTank(bot) && !bot->HasAura(SPELL_INSIDIOUS_WHISPER)) { - if (dynamic_cast(action)) + if (GetPhase2LeotherasDemon(botAI) && dynamic_cast(action)) + return 0.0f; + + if (!GetPhase3LeotherasDemon(botAI) && dynamic_cast(action)) return 0.0f; } @@ -508,7 +511,7 @@ float FathomLordKarathressWaitForDpsMultiplier::GetValue(Action* action) float FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier::GetValue(Action* action) { - if (!botAI->IsHealAssistantOfIndex(bot, 0)) + if (!botAI->IsAssistHealOfIndex(bot, 0, true)) return 1.0f; if (AI_VALUE2(Unit*, "find target", "fathom-guard caribdis")) diff --git a/src/Ai/Raid/SerpentshrineCavern/RaidSSCHelpers.cpp b/src/Ai/Raid/SerpentshrineCavern/RaidSSCHelpers.cpp index 2cc991cc29..8d8e307797 100644 --- a/src/Ai/Raid/SerpentshrineCavern/RaidSSCHelpers.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/RaidSSCHelpers.cpp @@ -468,7 +468,7 @@ namespace SerpentShrineCavernHelpers if (!memberAI) continue; - if (memberAI->IsHealAssistantOfIndex(member, 0)) + if (memberAI->IsAssistHealOfIndex(member, 0, true)) return member; } @@ -503,7 +503,7 @@ namespace SerpentShrineCavernHelpers if (!memberAI) continue; - if (memberAI->IsHealAssistantOfIndex(member, 1)) + if (memberAI->IsAssistHealOfIndex(member, 1, true)) return member; } @@ -540,7 +540,7 @@ namespace SerpentShrineCavernHelpers if (!memberAI) continue; - if (memberAI->IsHealAssistantOfIndex(member, 2)) + if (memberAI->IsAssistHealOfIndex(member, 2, true)) return member; } @@ -579,7 +579,7 @@ namespace SerpentShrineCavernHelpers if (!memberAI) continue; - if (memberAI->IsRangedDpsAssistantOfIndex(member, 0)) + if (memberAI->IsAssistRangedDpsOfIndex(member, 0, true)) return member; } diff --git a/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp b/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp index d524e967b2..f59a3db5d4 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp @@ -24,10 +24,8 @@ bool UnderbogColossusSpawnedToxicPoolAfterDeathTrigger::IsActive() bool GreyheartTidecallerWaterElementalTotemSpawnedTrigger::IsActive() { - if (!botAI->IsDps(bot)) - return false; - - return GetFirstAliveUnitByEntry(botAI, NPC_WATER_ELEMENTAL_TOTEM); + return botAI->IsDps(bot) && + GetFirstAliveUnitByEntry(botAI, NPC_WATER_ELEMENTAL_TOTEM); } // Hydross the Unstable @@ -60,18 +58,14 @@ bool HydrossTheUnstableElementalsSpawnedTrigger::IsActive() bool HydrossTheUnstableDangerFromWaterTombsTrigger::IsActive() { - if (!botAI->IsRanged(bot)) - return false; - - return AI_VALUE2(Unit*, "find target", "hydross the unstable"); + return botAI->IsRanged(bot) && + AI_VALUE2(Unit*, "find target", "hydross the unstable"); } bool HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger::IsActive() { - if (bot->getClass() != CLASS_HUNTER) - return false; - - return AI_VALUE2(Unit*, "find target", "hydross the unstable"); + return bot->getClass() == CLASS_HUNTER && + AI_VALUE2(Unit*, "find target", "hydross the unstable"); } bool HydrossTheUnstableAggroResetsUponPhaseChangeTrigger::IsActive() @@ -203,17 +197,15 @@ bool LeotherasTheBlindOnlyWarlockShouldTankDemonFormTrigger::IsActive() if (!botAI->IsTank(bot)) return false; - if (!GetPhase2LeotherasDemon(botAI)) - return false; - Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST); if (chaosBlast && chaosBlast->GetStackAmount() >= 5) return false; - if (!GetLeotherasDemonFormTank(botAI, bot)) + if (!GetPhase2LeotherasDemon(botAI)) return false; - return true; + return GetLeotherasDemonFormTank(botAI, bot) && + GetLeotherasDemonFormTank(botAI, bot) != bot; } bool LeotherasTheBlindBossEngagedByRangedTrigger::IsActive() @@ -273,10 +265,8 @@ bool LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger::IsActive() bool LeotherasTheBlindInnerDemonHasAwakenedTrigger::IsActive() { - if (!bot->HasAura(SPELL_INSIDIOUS_WHISPER)) - return false; - - return GetLeotherasDemonFormTank(botAI, bot) != bot; + return bot->HasAura(SPELL_INSIDIOUS_WHISPER) && + GetLeotherasDemonFormTank(botAI, bot) != bot; } bool LeotherasTheBlindEnteredFinalPhaseTrigger::IsActive() @@ -307,10 +297,8 @@ bool LeotherasTheBlindDemonFormTankNeedsAggro::IsActive() bool LeotherasTheBlindBossWipesAggroUponPhaseChangeTrigger::IsActive() { - if (!AI_VALUE2(Unit*, "find target", "leotheras the blind")) - return false; - - return IsInstanceTimerManager(botAI, bot); + return AI_VALUE2(Unit*, "find target", "leotheras the blind") && + IsInstanceTimerManager(botAI, bot); } // Fathom-Lord Karathress @@ -345,7 +333,7 @@ bool FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger::IsActive() if (!caribdis) return false; - if (!botAI->IsHealAssistantOfIndex(bot, 0)) + if (!botAI->IsAssistHealOfIndex(bot, 0, true)) return false; Player* firstAssistTank = nullptr; @@ -382,30 +370,25 @@ bool FathomLordKarathressDeterminingKillOrderTrigger::IsActive() if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) return false; - if (botAI->IsDps(bot)) - return true; - - if (botAI->IsAssistTankOfIndex(bot, 0, false) && - !AI_VALUE2(Unit*, "find target", "fathom-guard caribdis")) - return true; - - if (botAI->IsAssistTankOfIndex(bot, 1, false) && - !AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis")) - return true; + if (botAI->IsHeal(bot)) + return false; - if (botAI->IsAssistTankOfIndex(bot, 2, false) && - !AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess")) + if (botAI->IsDps(bot)) return true; - - return false; + else if (botAI->IsAssistTankOfIndex(bot, 0, false)) + return AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); + else if (botAI->IsAssistTankOfIndex(bot, 1, false)) + return AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis"); + else if (botAI->IsAssistTankOfIndex(bot, 2, false)) + return AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess"); + else + return false; } bool FathomLordKarathressTanksNeedToEstablishAggroTrigger::IsActive() { - if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) - return false; - - return IsInstanceTimerManager(botAI, bot); + return IsInstanceTimerManager(botAI, bot) && + AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); } // Morogrim Tidewalker @@ -444,10 +427,7 @@ bool LadyVashjBossEngagedByMainTankTrigger::IsActive() bool LadyVashjBossEngagedByRangedInPhase1Trigger::IsActive() { - if (!botAI->IsRanged(bot)) - return false; - - return IsLadyVashjInPhase1(botAI); + return botAI->IsRanged(bot) && IsLadyVashjInPhase1(botAI); } bool LadyVashjCastsShockBlastOnHighestAggroTrigger::IsActive() @@ -489,9 +469,12 @@ bool LadyVashjPullingBossInPhase1AndPhase3Trigger::IsActive() return false; Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); - return vashj && ((vashj->GetHealthPct() <= 100.0f && vashj->GetHealthPct() > 90.0f) || - (!vashj->HasUnitState(UNIT_STATE_ROOT) && vashj->GetHealthPct() <= 50.0f && - vashj->GetHealthPct() > 40.0f)); + if (!vashj) + return false; + + return (vashj->GetHealthPct() <= 100.0f && vashj->GetHealthPct() > 90.0f) || + (!vashj->HasUnitState(UNIT_STATE_ROOT) && vashj->GetHealthPct() <= 50.0f && + vashj->GetHealthPct() > 40.0f); } bool LadyVashjAddsSpawnInPhase2AndPhase3Trigger::IsActive() @@ -554,8 +537,7 @@ bool LadyVashjTaintedElementalCheatTrigger::IsActive() bool LadyVashjTaintedCoreWasLootedTrigger::IsActive() { - if (!AI_VALUE2(Unit*, "find target", "lady vashj") || - !IsLadyVashjInPhase2(botAI)) + if (!AI_VALUE2(Unit*, "find target", "lady vashj") || !IsLadyVashjInPhase2(botAI)) return false; Group* group = bot->GetGroup(); From 87026cb784045ae05dc78592f6b912589053d696 Mon Sep 17 00:00:00 2001 From: Crow Date: Sat, 24 Jan 2026 21:26:21 -0600 Subject: [PATCH 22/25] delete blank line --- .../Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp b/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp index 5f5f2462bc..2baf656d03 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp @@ -115,7 +115,6 @@ float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action) bool aboutToChange = (itPhase != hydrossChangeToNaturePhaseTimer.end() && (now - itPhase->second) > phaseChangeWaitSeconds); - if (justChanged || aboutToChange) { if (dynamic_cast(action) || From 8bd0ec5cffad140813931a8f47b1073c3e97860f Mon Sep 17 00:00:00 2001 From: crow Date: Fri, 6 Feb 2026 18:42:06 -0600 Subject: [PATCH 23/25] some significant updates to several bosses --- .../Action/RaidSSCActions.cpp | 376 ++++++++---------- .../Action/RaidSSCActions.h | 4 +- .../Multiplier/RaidSSCMultipliers.cpp | 97 ++--- .../Strategy/RaidSSCStrategy.cpp | 4 +- .../Trigger/RaidSSCTriggers.cpp | 54 +-- .../Trigger/RaidSSCTriggers.h | 8 - .../{ => Util}/RaidSSCHelpers.cpp | 108 +---- .../{ => Util}/RaidSSCHelpers.h | 16 +- 8 files changed, 257 insertions(+), 410 deletions(-) rename src/Ai/Raid/SerpentshrineCavern/{ => Util}/RaidSSCHelpers.cpp (85%) rename src/Ai/Raid/SerpentshrineCavern/{ => Util}/RaidSSCHelpers.h (90%) diff --git a/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp b/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp index 106e2904ad..fedc7e5f87 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp @@ -6,6 +6,7 @@ #include "LootObjectStack.h" #include "ObjectAccessor.h" #include "Playerbots.h" +#include "RaidBossHelpers.h" #include "RtiTargetValue.h" using namespace SerpentShrineCavernHelpers; @@ -61,7 +62,7 @@ bool SerpentShrineCavernEraseTimersAndTrackersAction::Execute(Event event) // Trash Mobs -// Non-combat method (some colossi leave a toxic pool upon death) +// Move out of toxic pool left behind by some colossi upon death bool UnderbogColossusEscapeToxicPoolAction::Execute(Event event) { Aura* aura = bot->GetAura(SPELL_TOXIC_POOL); @@ -95,8 +96,8 @@ bool UnderbogColossusEscapeToxicPoolAction::Execute(Event event) if (radius <= 0.0f) return false; - const float bufferDist = 3.0f; - const float centerThreshold = 1.0f; + constexpr float bufferDist = 3.0f; + constexpr float centerThreshold = 1.0f; float dx = bot->GetPositionX() - dynObj->GetPositionX(); float dy = bot->GetPositionY() - dynObj->GetPositionY(); @@ -307,7 +308,7 @@ bool HydrossTheUnstablePrioritizeElementalAddsAction::Execute(Event event) Unit* waterElemental = GetFirstAliveUnitByEntry(botAI, NPC_PURE_SPAWN_OF_HYDROSS); if (waterElemental) { - if (IsInstanceTimerManager(botAI, bot)) + if (IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr)) MarkTargetWithSkull(bot, waterElemental); SetRtiTarget(botAI, "skull", waterElemental); @@ -317,7 +318,7 @@ bool HydrossTheUnstablePrioritizeElementalAddsAction::Execute(Event event) } else if (Unit* natureElemental = GetFirstAliveUnitByEntry(botAI, NPC_TAINTED_SPAWN_OF_HYDROSS)) { - if (IsInstanceTimerManager(botAI, bot)) + if (IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr)) MarkTargetWithSkull(bot, natureElemental); SetRtiTarget(botAI, "skull", natureElemental); @@ -343,8 +344,8 @@ bool HydrossTheUnstableFrostPhaseSpreadOutAction::Execute(Event event) if (!member || member == bot || !member->IsAlive()) continue; - const float safeDistance = 6.0f; - const uint32 minInterval = 1000; + constexpr float safeDistance = 6.0f; + constexpr uint32 minInterval = 1000; if (bot->GetExactDist2d(member) < safeDistance) return FleePosition(member->GetPosition(), safeDistance, minInterval); } @@ -431,12 +432,12 @@ bool HydrossTheUnstableStopDpsUponPhaseChangeAction::Execute(Event event) const uint32 instanceId = hydross->GetMap()->GetInstanceId(); const time_t now = std::time(nullptr); - const int phaseStartStopSeconds = 5; - const int phaseEndStopSeconds = 1; + constexpr uint8 phaseStartStopSeconds = 5; + constexpr uint8 phaseEndStopSeconds = 1; bool shouldStopDps = false; - // 1 second after 100% Mark of Hydross, stop DPS until transition into nature phase + // 1 second after 100% Mark of Hydross, stop DPS auto itNature = hydrossChangeToNaturePhaseTimer.find(instanceId); if (itNature != hydrossChangeToNaturePhaseTimer.end() && (now - itNature->second) >= phaseEndStopSeconds) @@ -448,7 +449,7 @@ bool HydrossTheUnstableStopDpsUponPhaseChangeAction::Execute(Event event) (now - itNatureDps->second) < phaseStartStopSeconds) shouldStopDps = true; - // 1 second after 100% Mark of Corruption, stop DPS until transition into frost phase + // 1 second after 100% Mark of Corruption, stop DPS auto itFrost = hydrossChangeToFrostPhaseTimer.find(instanceId); if (itFrost != hydrossChangeToFrostPhaseTimer.end() && (now - itFrost->second) >= phaseEndStopSeconds) @@ -513,30 +514,40 @@ bool HydrossTheUnstableManageTimersAction::Execute(Event event) // The Lurker Below -// Run around behind Lurker during Spout, maintaining distance of 20-24 yards -// Stay within 90-degree arc behind Lurker +// Run around behind Lurker during Spout bool TheLurkerBelowRunAroundBehindBossAction::Execute(Event event) { Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); if (!lurker) return false; - if (bot->HasAura(SPELL_TREE_OF_LIFE)) - bot->RemoveAura(SPELL_TREE_OF_LIFE); + float radius = frand(20.0f, 21.0f); + float botAngle = std::atan2( + bot->GetPositionY() - lurker->GetPositionY(), bot->GetPositionX() - lurker->GetPositionX()); + float relativeAngle = Position::NormalizeOrientation(botAngle - lurker->GetOrientation()); + constexpr float safeArc = M_PI / 2.0f; - float bossFacing = lurker->GetOrientation(); - float behindAngle = bossFacing + M_PI + frand(-0.5f, 0.5f) * (M_PI / 2.0f); - float radius = frand(20.0f, 24.0f); - - float targetX = lurker->GetPositionX() + radius * std::cos(behindAngle); - float targetY = lurker->GetPositionY() + radius * std::sin(behindAngle); - - if (bot->GetExactDist2d(targetX, targetY) > 1.0f) + if (std::fabs(Position::NormalizeOrientation(relativeAngle - M_PI)) > safeArc / 2.0f) { + float tangentAngle = botAngle + (relativeAngle > M_PI ? -0.1f : 0.1f); + float moveX = lurker->GetPositionX() + radius * std::cos(tangentAngle); + float moveY = lurker->GetPositionY() + radius * std::sin(tangentAngle); botAI->Reset(); - return MoveTo(SSC_MAP_ID, targetX, targetY, lurker->GetPositionZ(), false, false, + return MoveTo(SSC_MAP_ID, moveX, moveY, lurker->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_FORCED, true, false); } + else + { + float behindAngle = lurker->GetOrientation() + M_PI + frand(-0.5f, 0.5f) * safeArc; + float targetX = lurker->GetPositionX() + radius * std::cos(behindAngle); + float targetY = lurker->GetPositionY() + radius * std::sin(behindAngle); + if (bot->GetExactDist2d(targetX, targetY) > 2.0f) + { + botAI->Reset(); + return MoveTo(SSC_MAP_ID, targetX, targetY, lurker->GetPositionZ(), false, false, + false, false, MovementPriority::MOVEMENT_FORCED, true, false); + } + } return false; } @@ -594,9 +605,9 @@ bool TheLurkerBelowSpreadRangedInArcAction::Execute(Event event) size_t botIndex = (findIt != rangedMembers.end()) ? std::distance(rangedMembers.begin(), findIt) : 0; - const float arcSpan = 2.0f * M_PI / 3.0f; - const float arcCenter = 2.262f; - const float arcStart = arcCenter - arcSpan / 2.0f; + constexpr float arcSpan = 2.0f * M_PI / 3.0f; + constexpr float arcCenter = 2.262f; + constexpr float arcStart = arcCenter - arcSpan / 2.0f; float angle = (count == 1) ? arcCenter : (arcStart + arcSpan * static_cast(botIndex) / static_cast(count - 1)); @@ -742,28 +753,15 @@ bool LeotherasTheBlindDemonFormTankAttackBossAction::Execute(Event event) } if (innerDemon) - { - if (botAI->HasStrategy("tank", BotState::BOT_STATE_COMBAT)) - botAI->ChangeStrategy("-tank", BotState::BOT_STATE_COMBAT); + return false; - if (bot->GetTarget() != innerDemon->GetGUID()) - return Attack(innerDemon); - } - else if (Unit* leotherasDemon = GetActiveLeotherasDemon(botAI)) + if (Unit* leotherasDemon = GetActiveLeotherasDemon(botAI)) { - if (!botAI->HasStrategy("tank", BotState::BOT_STATE_COMBAT)) - botAI->ChangeStrategy("+tank", BotState::BOT_STATE_COMBAT); - MarkTargetWithSquare(bot, leotherasDemon); SetRtiTarget(botAI, "square", leotherasDemon); - if (bot->GetTarget() != leotherasDemon->GetGUID()) - return Attack(leotherasDemon); - } - else if (GetLeotherasHuman(botAI)) - { - if (botAI->HasStrategy("tank", BotState::BOT_STATE_COMBAT)) - botAI->ChangeStrategy("-tank", BotState::BOT_STATE_COMBAT); + if (botAI->CanCastSpell("searing pain", leotherasDemon)) + return botAI->CastSpell("searing pain", leotherasDemon); } return false; @@ -782,12 +780,12 @@ bool LeotherasTheBlindMeleeTanksDontAttackDemonFormAction::Execute(Event event) // And stay away from the Warlock tank to avoid Chaos Blasts bool LeotherasTheBlindPositionRangedAction::Execute(Event event) { - const float safeDistFromBoss = 15.0f; + constexpr float safeDistFromBoss = 15.0f; Unit* leotherasHuman = GetLeotherasHuman(botAI); if (leotherasHuman && bot->GetExactDist2d(leotherasHuman) < safeDistFromBoss && leotherasHuman->GetVictim() != bot) { - const uint32 minInterval = 500; + constexpr uint32 minInterval = 500; return FleePosition(leotherasHuman->GetPosition(), safeDistFromBoss, minInterval); } @@ -803,16 +801,16 @@ bool LeotherasTheBlindPositionRangedAction::Execute(Event event) if (!member || member == bot || !member->IsAlive()) continue; - const uint32 minInterval = 0; + constexpr uint32 minInterval = 0; if (GetLeotherasDemonFormTank(botAI, bot) == member) { - const float safeDistFromTank = 10.0f; + constexpr float safeDistFromTank = 10.0f; if (bot->GetExactDist2d(member) < safeDistFromTank) return FleePosition(member->GetPosition(), safeDistFromTank, minInterval); } else { - const float safeDistFromMember = 6.0f; + constexpr float safeDistFromMember = 6.0f; if (bot->GetExactDist2d(member) < safeDistFromMember) return FleePosition(member->GetPosition(), safeDistFromMember, minInterval); } @@ -827,7 +825,7 @@ bool LeotherasTheBlindRunAwayFromWhirlwindAction::Execute(Event event) if (Unit* leotherasHuman = GetLeotherasHuman(botAI)) { float currentDistance = bot->GetExactDist2d(leotherasHuman); - const float safeDistance = 25.0f; + constexpr float safeDistance = 25.0f; if (currentDistance < safeDistance) { botAI->Reset(); @@ -845,16 +843,16 @@ bool LeotherasTheBlindMeleeDpsRunAwayFromBossAction::Execute(Event event) if (botAI->CanCastSpell("cloak of shadows", bot)) return botAI->CastSpell("cloak of shadows", bot); - Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI); - if (!leotherasPhase2Demon) + Unit* leotheras = GetPhase2LeotherasDemon(botAI); + if (!leotheras) return false; - Unit* demonVictim = leotherasPhase2Demon->GetVictim(); + Unit* demonVictim = leotheras->GetVictim(); if (!demonVictim) return false; float currentDistance = bot->GetExactDist2d(demonVictim); - const float safeDistance = 10.0f; + constexpr float safeDistance = 10.0f; if (currentDistance < safeDistance) { botAI->Reset(); @@ -1307,7 +1305,7 @@ bool FathomLordKarathressThirdAssistTankPositionTidalvessAction::Execute(Event e } // Caribdis's tank spot is far away so a dedicated healer is needed -// Use the assistant flag to select the healer (Paladin recommended) +// Use the assistant flag to select the healer bool FathomLordKarathressPositionCaribdisTankHealerAction::Execute(Event event) { Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); @@ -1763,9 +1761,9 @@ bool LadyVashjMainTankPositionBossAction::Execute(Event event) if (enchanted) { float currentDistance = bot->GetExactDist2d(enchanted); - const float safeDistance = 10.0f; + constexpr float safeDistance = 10.0f; if (currentDistance < safeDistance) - return MoveAway(enchanted, safeDistance - currentDistance + 5.0f); + return MoveAway(enchanted, safeDistance - currentDistance); } } } @@ -1804,12 +1802,12 @@ bool LadyVashjPhase1SpreadRangedInArcAction::Execute(Event event) return false; const Position& center = VASHJ_PLATFORM_CENTER_POSITION; - const float minRadius = 20.0f; - const float maxRadius = 30.0f; + constexpr float minRadius = 20.0f; + constexpr float maxRadius = 30.0f; - const float arcCenter = M_PI / 2.0f; // North - const float arcSpan = M_PI; // 180° - const float arcStart = arcCenter - arcSpan / 2.0f; + constexpr float arcCenter = M_PI / 2.0f; // North + constexpr float arcSpan = M_PI; // 180° + constexpr float arcStart = arcCenter - arcSpan / 2.0f; float angle; if (count == 1) @@ -1936,9 +1934,9 @@ bool LadyVashjStaticChargeMoveAwayFromGroupAction::Execute(Event event) if (mainTank && bot != mainTank) { float currentDistance = bot->GetExactDist2d(mainTank); - const float safeDistance = 10.0f; + constexpr float safeDistance = 11.0f; if (currentDistance < safeDistance) - return MoveAway(mainTank, safeDistance - currentDistance + 0.5f); + return MoveAway(mainTank, safeDistance - currentDistance); } // If any other bot has Static Charge, it should move away from other group members @@ -1951,9 +1949,9 @@ bool LadyVashjStaticChargeMoveAwayFromGroupAction::Execute(Event event) continue; float currentDistance = bot->GetExactDist2d(member); - const float safeDistance = 10.0f; + constexpr float safeDistance = 11.0f; if (currentDistance < safeDistance) - return MoveFromGroup(safeDistance + 0.5f); + return MoveFromGroup(safeDistance); } } @@ -1988,7 +1986,7 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event event) Unit* strider = nullptr; Unit* sporebat = nullptr; - // Search and attack radius are intended to keep bots on the platform (not go down the stairs) + // Search and attack radius are intended to keep bots from going down the stairs const float maxSearchRange = botAI->IsRanged(bot) ? 60.0f : 55.0f; const float maxPursueRange = maxSearchRange - 5.0f; @@ -2201,7 +2199,7 @@ bool LadyVashjTankAttackAndMoveAwayStriderAction::Execute(Event event) if (strider->GetVictim() == bot) { float currentDistance = bot->GetExactDist2d(vashj); - const float safeDistance = 25.0f; + constexpr float safeDistance = 28.0f; if (currentDistance < safeDistance) return MoveAway(vashj, safeDistance - currentDistance); @@ -2214,9 +2212,9 @@ bool LadyVashjTankAttackAndMoveAwayStriderAction::Execute(Event event) if (!botAI->HasCheat(BotCheatMask::raid) || !botAI->IsTank(bot)) { float currentDistance = bot->GetExactDist2d(strider); - const float safeDistance = 15.0f; + constexpr float safeDistance = 20.0f; if (currentDistance < safeDistance) - return MoveAway(strider, safeDistance - currentDistance + 5.0f); + return MoveAway(strider, safeDistance - currentDistance); } // Try to root/slow the Strider if it is not tankable (poor man's kiting strategy) @@ -2285,7 +2283,7 @@ bool LadyVashjLootTaintedCoreAction::Execute(Event) return false; auto const& corpses = context->GetValue("nearest corpses")->Get(); - const float maxLootRange = sPlayerbotAIConfig->lootDistance; + const float maxLootRange = sPlayerbotAIConfig.lootDistance; for (auto const& guid : corpses) { @@ -2323,7 +2321,7 @@ bool LadyVashjLootTaintedCoreAction::Execute(Event) const ObjectGuid botGuid = bot->GetGUID(); const ObjectGuid corpseGuid = guid; - const uint8 coreIndex = 0; + constexpr uint8 coreIndex = 0; botAI->AddTimedEvent([botGuid, corpseGuid, coreIndex, vashj]() { @@ -2347,7 +2345,9 @@ bool LadyVashjLootTaintedCoreAction::Execute(Event) *packet << coreIndex; receiver->GetSession()->QueuePacket(packet); - lastCoreInInventoryTime[vashj->GetMap()->GetInstanceId()] = std::time(nullptr); + const uint32 instanceId = vashj->GetMap()->GetInstanceId(); + const time_t now = std::time(nullptr); + lastCoreInInventoryTime.insert_or_assign(instanceId, now); }, 600); return true; @@ -2441,23 +2441,14 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) designatedLooter, firstCorePasser, closestTrigger)) { const time_t now = std::time(nullptr); - - // Track lastImbueAttempt is to prevent repeated throwing animations - // from multiple imbue attempts - auto [it, inserted] = lastImbueAttempt.try_emplace(instanceId, now); - if (inserted) - { - lastCoreInInventoryTime[instanceId] = now; - botAI->ImbueItem(item, firstCorePasser); - ScheduleStoreCoreAfterImbue(botAI, bot, firstCorePasser); - return true; - } - if ((now - it->second) >= 2) + auto it = lastImbueAttempt.find(instanceId); + if (it == lastImbueAttempt.end() || (now - it->second) >= 2) { - it->second = now; - lastCoreInInventoryTime[instanceId] = now; + lastImbueAttempt.insert_or_assign(instanceId, now); + lastCoreInInventoryTime.insert_or_assign(instanceId, now); botAI->ImbueItem(item, firstCorePasser); - ScheduleStoreCoreAfterImbue(botAI, bot, firstCorePasser); + intendedLineup.erase(bot->GetGUID()); + ScheduleTransferCoreAfterImbue(botAI, bot, firstCorePasser); return true; } } @@ -2466,29 +2457,16 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) // pass to second core passer else if (bot == firstCorePasser) { - if (IsSecondCorePasserInIntendedPosition( - firstCorePasser, secondCorePasser, closestTrigger)) + const time_t now = std::time(nullptr); + auto it = lastImbueAttempt.find(instanceId); + if (it == lastImbueAttempt.end() || (now - it->second) >= 2) { - const time_t now = std::time(nullptr); - - auto [it, inserted] = lastImbueAttempt.try_emplace(instanceId, now); - if (inserted) - { - lastCoreInInventoryTime[instanceId] = now; - botAI->ImbueItem(item, secondCorePasser); - intendedLineup.erase(bot->GetGUID()); - ScheduleStoreCoreAfterImbue(botAI, bot, secondCorePasser); - return true; - } - if ((now - it->second) >= 2) - { - it->second = now; - lastCoreInInventoryTime[instanceId] = now; - botAI->ImbueItem(item, secondCorePasser); - intendedLineup.erase(bot->GetGUID()); - ScheduleStoreCoreAfterImbue(botAI, bot, secondCorePasser); - return true; - } + lastImbueAttempt.insert_or_assign(instanceId, now); + lastCoreInInventoryTime.insert_or_assign(instanceId, now); + botAI->ImbueItem(item, secondCorePasser); + intendedLineup.erase(bot->GetGUID()); + ScheduleTransferCoreAfterImbue(botAI, bot, secondCorePasser); + return true; } } // Second core passer: if closest usable generator is within passing distance @@ -2496,29 +2474,20 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) // possible to the generator while staying in passing range else if (bot == secondCorePasser) { - if (!UseCoreOnNearestGenerator()) + if (!UseCoreOnNearestGenerator(instanceId)) { if (IsThirdCorePasserInIntendedPosition( secondCorePasser, thirdCorePasser, closestTrigger)) { const time_t now = std::time(nullptr); - - auto [it, inserted] = lastImbueAttempt.try_emplace(instanceId, now); - if (inserted) - { - lastCoreInInventoryTime[instanceId] = now; - botAI->ImbueItem(item, thirdCorePasser); - intendedLineup.erase(bot->GetGUID()); - ScheduleStoreCoreAfterImbue(botAI, bot, thirdCorePasser); - return true; - } - if ((now - it->second) >= 2) + auto it = lastImbueAttempt.find(instanceId); + if (it == lastImbueAttempt.end() || (now - it->second) >= 2) { - it->second = now; - lastCoreInInventoryTime[instanceId] = now; + lastImbueAttempt.insert_or_assign(instanceId, now); + lastCoreInInventoryTime.insert_or_assign(instanceId, now); botAI->ImbueItem(item, thirdCorePasser); intendedLineup.erase(bot->GetGUID()); - ScheduleStoreCoreAfterImbue(botAI, bot, thirdCorePasser); + ScheduleTransferCoreAfterImbue(botAI, bot, thirdCorePasser); return true; } } @@ -2529,29 +2498,20 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) // possible to the generator while staying in passing range else if (bot == thirdCorePasser) { - if (!UseCoreOnNearestGenerator()) + if (!UseCoreOnNearestGenerator(instanceId)) { if (IsFourthCorePasserInIntendedPosition( thirdCorePasser, fourthCorePasser, closestTrigger)) { const time_t now = std::time(nullptr); - - auto [it, inserted] = lastImbueAttempt.try_emplace(instanceId, now); - if (inserted) - { - lastCoreInInventoryTime[instanceId] = now; - botAI->ImbueItem(item, fourthCorePasser); - intendedLineup.erase(bot->GetGUID()); - ScheduleStoreCoreAfterImbue(botAI, bot, fourthCorePasser); - return true; - } - if ((now - it->second) >= 2) + auto it = lastImbueAttempt.find(instanceId); + if (it == lastImbueAttempt.end() || (now - it->second) >= 2) { - it->second = now; - lastCoreInInventoryTime[instanceId] = now; + lastImbueAttempt.insert_or_assign(instanceId, now); + lastCoreInInventoryTime.insert_or_assign(instanceId, now); botAI->ImbueItem(item, fourthCorePasser); intendedLineup.erase(bot->GetGUID()); - ScheduleStoreCoreAfterImbue(botAI, bot, fourthCorePasser); + ScheduleTransferCoreAfterImbue(botAI, bot, fourthCorePasser); return true; } } @@ -2560,7 +2520,7 @@ bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) // Fourth core passer: the fourth passer is rarely needed and no more than // four ever should be, so it should use the Core on the nearest generator else if (bot == fourthCorePasser) - UseCoreOnNearestGenerator(); + UseCoreOnNearestGenerator(instanceId); } return false; @@ -2571,7 +2531,7 @@ bool LadyVashjPassTheTaintedCoreAction::LineUpFirstCorePasser( { const float centerX = VASHJ_PLATFORM_CENTER_POSITION.GetPositionX(); const float centerY = VASHJ_PLATFORM_CENTER_POSITION.GetPositionY(); - const float radius = 57.5f; + constexpr float radius = 57.5f; float mx = designatedLooter->GetPositionX(); float my = designatedLooter->GetPositionY(); @@ -2579,7 +2539,7 @@ bool LadyVashjPassTheTaintedCoreAction::LineUpFirstCorePasser( float targetX = centerX + radius * std::cos(angle); float targetY = centerY + radius * std::sin(angle); - const float targetZ = 41.097f; + constexpr float targetZ = 41.097f; intendedLineup.insert_or_assign(bot->GetGUID(), Position(targetX, targetY, targetZ)); @@ -2608,31 +2568,29 @@ bool LadyVashjPassTheTaintedCoreAction::LineUpSecondCorePasser( float targetX, targetY, targetZ; // If firstCorePasser is within thresholdDist of closestTrigger, // go to nearTriggerDist short of closestTrigger - const float thresholdDist = 40.0f; - const float nearTriggerDist = 1.5f; + constexpr float thresholdDist = 40.0f; + constexpr float nearTriggerDist = 1.5f; // If firstCorePasser is not thresholdDist yards from closestTrigger, // go to farDistance from firstCorePasser - const float farDistance = 38.0f; + constexpr float farDistance = 38.0f; if (distToTrigger <= thresholdDist) { float moveDist = std::max(distToTrigger - nearTriggerDist, 0.0f); targetX = fx + dx * moveDist; targetY = fy + dy * moveDist; - targetZ = 42.985f; } else { targetX = fx + dx * farDistance; targetY = fy + dy * farDistance; - targetZ = 42.985f; } - intendedLineup.insert_or_assign(bot->GetGUID(), Position(targetX, targetY, targetZ)); + intendedLineup.insert_or_assign(bot->GetGUID(), Position(targetX, targetY, VASHJ_PLATFORM_Z)); bot->AttackStop(); bot->InterruptNonMeleeSpells(true); - return MoveTo(SSC_MAP_ID, targetX, targetY, targetZ, false, false, false, true, + return MoveTo(SSC_MAP_ID, targetX, targetY, VASHJ_PLATFORM_Z, false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); } @@ -2662,29 +2620,27 @@ bool LadyVashjPassTheTaintedCoreAction::LineUpThirdCorePasser( dx /= distToTrigger; dy /= distToTrigger; float targetX, targetY, targetZ; - const float thresholdDist = 40.0f; - const float nearTriggerDist = 1.5f; - const float farDistance = 38.0f; + constexpr float thresholdDist = 40.0f; + constexpr float nearTriggerDist = 1.5f; + constexpr float farDistance = 38.0f; if (distToTrigger <= thresholdDist) { float moveDist = std::max(distToTrigger - nearTriggerDist, 0.0f); targetX = sx + dx * moveDist; targetY = sy + dy * moveDist; - targetZ = 42.985f; } else { targetX = sx + dx * farDistance; targetY = sy + dy * farDistance; - targetZ = 42.985f; } - intendedLineup.insert_or_assign(bot->GetGUID(), Position(targetX, targetY, targetZ)); + intendedLineup.insert_or_assign(bot->GetGUID(), Position(targetX, targetY, VASHJ_PLATFORM_Z)); bot->AttackStop(); bot->InterruptNonMeleeSpells(true); - return MoveTo(SSC_MAP_ID, targetX, targetY, targetZ, false, false, false, true, + return MoveTo(SSC_MAP_ID, targetX, targetY, VASHJ_PLATFORM_Z, false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); return false; @@ -2718,16 +2674,15 @@ bool LadyVashjPassTheTaintedCoreAction::LineUpFourthCorePasser( dx /= distToTrigger; dy /= distToTrigger; - const float nearTriggerDist = 1.5f; + constexpr float nearTriggerDist = 1.5f; float targetX = tx - dx * nearTriggerDist; float targetY = ty - dy * nearTriggerDist; - const float targetZ = 42.985f; - intendedLineup.insert_or_assign(bot->GetGUID(), Position(targetX, targetY, targetZ)); + intendedLineup.insert_or_assign(bot->GetGUID(), Position(targetX, targetY, VASHJ_PLATFORM_Z)); bot->AttackStop(); bot->InterruptNonMeleeSpells(true); - return MoveTo(SSC_MAP_ID, targetX, targetY, targetZ, false, false, false, true, + return MoveTo(SSC_MAP_ID, targetX, targetY, VASHJ_PLATFORM_Z, false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); } @@ -2789,42 +2744,53 @@ bool LadyVashjPassTheTaintedCoreAction::IsFourthCorePasserInIntendedPosition( return false; } -// ImbueItem() is inconsistent in causing the receiving bot to receive the core -// So ScheduleStoreCoreAfterImbue() simulates the passing mechanic by creating the core -// on the receiver (note that ImbueItem() does always take away the core from the passer) -void LadyVashjPassTheTaintedCoreAction::ScheduleStoreCoreAfterImbue( +// ImbueItem() is inconsistent in causing the receiver bot to receive the core and the giver +// bot to remove the core, so ScheduleTransferCoreAfterImbue() creates the core on the receiver +// and removes it from the giver, with ImbueItem() called primarily for the throwing animation +void LadyVashjPassTheTaintedCoreAction::ScheduleTransferCoreAfterImbue( PlayerbotAI* botAI, Player* giver, Player* receiver) { - if (!receiver) + if (!receiver || !giver) return; - const uint32 delayMs = 1500; + constexpr uint32 delayMs = 1500; const ObjectGuid receiverGuid = receiver->GetGUID(); + const ObjectGuid giverGuid = giver->GetGUID(); - botAI->AddTimedEvent([receiverGuid]() + botAI->AddTimedEvent([receiverGuid, giverGuid]() { Player* receiverPlayer = receiverGuid.IsEmpty() ? nullptr : ObjectAccessor::FindPlayer(receiverGuid); + Player* giverPlayer = + giverGuid.IsEmpty() ? nullptr : ObjectAccessor::FindPlayer(giverGuid); + if (!receiverPlayer) return; - if (receiverPlayer->HasItemCount(ITEM_TAINTED_CORE, 1, false)) - return; + if (!receiverPlayer->HasItemCount(ITEM_TAINTED_CORE, 1, false)) + { + ItemPosCountVec dest; + uint32 count = 1; + int canStore = + receiverPlayer->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, ITEM_TAINTED_CORE, count); - ItemPosCountVec dest; - uint32 count = 1; - int canStore = - receiverPlayer->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, ITEM_TAINTED_CORE, count); + if (canStore == EQUIP_ERR_OK) + { + receiverPlayer->StoreNewItem(dest, ITEM_TAINTED_CORE, true, + Item::GenerateItemRandomPropertyId(ITEM_TAINTED_CORE)); + } + } - if (canStore == EQUIP_ERR_OK) + if (giverPlayer) { - receiverPlayer->StoreNewItem(dest, ITEM_TAINTED_CORE, true, - Item::GenerateItemRandomPropertyId(ITEM_TAINTED_CORE)); + Item* item = giverPlayer->GetItemByEntry(ITEM_TAINTED_CORE); + if (item && giverPlayer->HasItemCount(ITEM_TAINTED_CORE, 1, false)) + giverPlayer->DestroyItem(item->GetBagSlot(), item->GetSlot(), true); } }, delayMs); } -bool LadyVashjPassTheTaintedCoreAction::UseCoreOnNearestGenerator() +bool LadyVashjPassTheTaintedCoreAction::UseCoreOnNearestGenerator(const uint32 instanceId) { auto const& generators = GetAllGeneratorInfosByDbGuids(bot->GetMap(), SHIELD_GENERATOR_DB_GUIDS); @@ -2851,7 +2817,7 @@ bool LadyVashjPassTheTaintedCoreAction::UseCoreOnNearestGenerator() const uint8 bagIndex = core->GetBagSlot(); const uint8 slot = core->GetSlot(); - const uint8 cast_count = 0; + constexpr uint8 cast_count = 0; uint32 spellId = 0; for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) @@ -2864,8 +2830,8 @@ bool LadyVashjPassTheTaintedCoreAction::UseCoreOnNearestGenerator() } const ObjectGuid item_guid = core->GetGUID(); - const uint32 glyphIndex = 0; - const uint8 castFlags = 0; + constexpr uint32 glyphIndex = 0; + constexpr uint8 castFlags = 0; WorldPacket packet(CMSG_USE_ITEM); packet << bagIndex; @@ -2879,11 +2845,14 @@ bool LadyVashjPassTheTaintedCoreAction::UseCoreOnNearestGenerator() packet << generator->GetGUID().WriteAsPacked(); bot->GetSession()->HandleUseItemOpcode(packet); + nearestTriggerGuid.erase(instanceId); + lastImbueAttempt.erase(instanceId); + lastCoreInInventoryTime.erase(instanceId); return true; } -// For dead bots to destroy their cores so the logic can reset for the next attempt -// Or residual cores to be destroyed in Phase 3 +// Fallback for residual cores to be destroyed in Phase 3 in case +// ScheduleTransferCoreAfterImbue() fails to remove the core from the giver bool LadyVashjDestroyTaintedCoreAction::Execute(Event event) { if (Item* core = bot->GetItemByEntry(ITEM_TAINTED_CORE)) @@ -2895,8 +2864,8 @@ bool LadyVashjDestroyTaintedCoreAction::Execute(Event event) return false; } -// This needs to be separate from the general map erasing logic because somehow -// Bots tend to end up out of combat during the Vashj encounter +// This needs to be separate from the general map erasing logic because +// Bots may end up out of combat during the Vashj encounter bool LadyVashjEraseCorePassingTrackersAction::Execute(Event event) { Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); @@ -2910,7 +2879,7 @@ bool LadyVashjEraseCorePassingTrackersAction::Execute(Event event) erased = true; if (lastImbueAttempt.erase(instanceId) > 0) erased = true; - if (lastCoreInInventoryTime.erase(instanceId)) + if (lastCoreInInventoryTime.erase(instanceId) > 0) erased = true; if (intendedLineup.erase(bot->GetGUID()) > 0) erased = true; @@ -2927,7 +2896,7 @@ bool LadyVashjAvoidToxicSporesAction::Execute(Event event) if (spores.empty()) return false; - const float hazardRadius = 7.0f; + constexpr float hazardRadius = 7.0f; bool inDanger = false; for (Unit* spore : spores) { @@ -2942,7 +2911,7 @@ bool LadyVashjAvoidToxicSporesAction::Execute(Event event) return false; const Position& vashjCenter = VASHJ_PLATFORM_CENTER_POSITION; - const float maxRadius = 60.0f; + constexpr float maxRadius = 60.0f; Position safestPos = FindSafestNearbyPosition(spores, vashjCenter, maxRadius, hazardRadius); @@ -2957,10 +2926,10 @@ Position LadyVashjAvoidToxicSporesAction::FindSafestNearbyPosition( const std::vector& spores, const Position& vashjCenter, float maxRadius, float hazardRadius) { - const float searchStep = M_PI / 8.0f; - const float minDistance = 2.0f; - const float maxDistance = 40.0f; - const float distanceStep = 1.0f; + constexpr float searchStep = M_PI / 8.0f; + constexpr float minDistance = 2.0f; + constexpr float maxDistance = 40.0f; + constexpr float distanceStep = 1.0f; Position bestPos; float minMoveDistance = std::numeric_limits::max(); @@ -3023,7 +2992,7 @@ Position LadyVashjAvoidToxicSporesAction::FindSafestNearbyPosition( bool LadyVashjAvoidToxicSporesAction::IsPathSafeFromSpores(const Position& start, const Position& end, const std::vector& spores, float hazardRadius) { - const uint8 numChecks = 10; + constexpr uint8 numChecks = 10; float dx = end.GetPositionX() - start.GetPositionX(); float dy = end.GetPositionY() - start.GetPositionY(); @@ -3054,7 +3023,7 @@ std::vector LadyVashjAvoidToxicSporesAction::GetAllSporeDropTriggers( botAI->GetAiObjectContext()->GetValue("nearest npcs")->Get(); for (auto const& npcGuid : npcs) { - const float maxSearchRadius = 40.0f; + constexpr float maxSearchRadius = 40.0f; Unit* unit = botAI->GetUnit(npcGuid); if (unit && unit->GetEntry() == NPC_SPORE_DROP_TRIGGER && bot->GetExactDist2d(unit) < maxSearchRadius) @@ -3072,7 +3041,7 @@ bool LadyVashjUseFreeActionAbilitiesAction::Execute(Event event) auto const& spores = LadyVashjAvoidToxicSporesAction::GetAllSporeDropTriggers(botAI, bot); - const float toxicSporeRadius = 6.0f; + constexpr float toxicSporeRadius = 6.0f; // If Rogues are Entangled and either have Static Charge or // are near a spore, use Cloak of Shadows @@ -3087,9 +3056,11 @@ bool LadyVashjUseFreeActionAbilitiesAction::Execute(Event event) break; } } - if ((bot->HasAura(SPELL_STATIC_CHARGE) || nearSpore) && - botAI->CanCastSpell("cloak of shadows", bot)) - return botAI->CastSpell("cloak of shadows", bot); + if (bot->HasAura(SPELL_STATIC_CHARGE) || nearSpore) + { + if (botAI->CanCastSpell("cloak of shadows", bot)) + return botAI->CastSpell("cloak of shadows", bot); + } } // The remainder of the logic is for Paladins to use Hand of Freedom @@ -3101,13 +3072,8 @@ bool LadyVashjUseFreeActionAbilitiesAction::Execute(Event event) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (!member || !member->IsAlive()) - continue; - - if (!member->HasAura(SPELL_ENTANGLE)) - continue; - - if (!botAI->IsMelee(member)) + if (!member || !member->IsAlive() || !member->HasAura(SPELL_ENTANGLE) || + !botAI->IsMelee(member)) continue; bool nearToxicSpore = false; diff --git a/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.h b/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.h index 7503b6e071..cbd2374021 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.h +++ b/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.h @@ -417,8 +417,8 @@ class LadyVashjPassTheTaintedCoreAction : public MovementAction bool IsSecondCorePasserInIntendedPosition(Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger); bool IsThirdCorePasserInIntendedPosition(Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger); bool IsFourthCorePasserInIntendedPosition(Player* thirdCorePasser, Player* fourthCorePasser, Unit* closestTrigger); - void ScheduleStoreCoreAfterImbue(PlayerbotAI* botAI, Player* giver, Player* receiver); - bool UseCoreOnNearestGenerator(); + void ScheduleTransferCoreAfterImbue(PlayerbotAI* botAI, Player* giver, Player* receiver); + bool UseCoreOnNearestGenerator(const uint32 instanceId); }; class LadyVashjDestroyTaintedCoreAction : public Action diff --git a/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp b/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp index 2baf656d03..d43e6a52f3 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp @@ -56,7 +56,8 @@ float HydrossTheUnstableDisableTankActionsMultiplier::GetValue(Action* action) if (dynamic_cast(action) || dynamic_cast(action) || (dynamic_cast(action) && - !dynamic_cast(action))) + !dynamic_cast(action) && + !dynamic_cast(action))) { if ((botAI->IsMainTank(bot) && hydross->HasAura(SPELL_CORRUPTION)) || (botAI->IsAssistTankOfIndex(bot, 0, true) && !hydross->HasAura(SPELL_CORRUPTION))) @@ -83,8 +84,8 @@ float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action) const uint32 instanceId = hydross->GetMap()->GetInstanceId(); const time_t now = std::time(nullptr); - const uint8 phaseChangeWaitSeconds = 1; - const uint8 dpsWaitSeconds = 5; + constexpr uint8 phaseChangeWaitSeconds = 1; + constexpr uint8 dpsWaitSeconds = 5; if (!hydross->HasAura(SPELL_CORRUPTION) && !botAI->IsMainTank(bot)) { @@ -157,11 +158,12 @@ float TheLurkerBelowStayAwayFromSpoutMultiplier::GetValue(Action* action) if (dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action)) + dynamic_cast(action)) + return 0.0f; + + if (dynamic_cast(action) && + !dynamic_cast(action) && + !dynamic_cast(action)) return 0.0f; } @@ -191,6 +193,9 @@ float TheLurkerBelowDisableTankAssistMultiplier::GetValue(Action* action) if (!botAI->IsTank(bot)) return 1.0f; + if (bot->GetVictim() == nullptr) + return 1.0f; + Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); if (!lurker || lurker->getStandState() != UNIT_STAND_STATE_SUBMERGED) return 1.0f; @@ -212,7 +217,7 @@ float TheLurkerBelowDisableTankAssistMultiplier::GetValue(Action* action) if (tankCount >= 3) { - if (!bot->GetVictim() && dynamic_cast(action)) + if (dynamic_cast(action)) return 0.0f; } @@ -251,28 +256,17 @@ float LeotherasTheBlindAvoidWhirlwindMultiplier::GetValue(Action* action) float LeotherasTheBlindDisableTankActionsMultiplier::GetValue(Action* action) { - if (!GetActiveLeotherasDemon(botAI)) + if (!botAI->IsTank(bot) || bot->HasAura(SPELL_INSIDIOUS_WHISPER)) return 1.0f; - if (GetLeotherasDemonFormTank(botAI, bot) == bot) - { - // (1) Warlock tank will not use Shadow Ward, which is coded into the - // Warlock tank strategy (for Twin Emps) but is useless here - if (dynamic_cast(action)) - return 0.0f; - + if (!AI_VALUE2(Unit*, "find target", "leotheras the blind")) return 1.0f; - } - // (2) Phase 2 only: non-Warlock tanks should not attack Leotheras - if (botAI->IsTank(bot) && !bot->HasAura(SPELL_INSIDIOUS_WHISPER)) - { - if (GetPhase2LeotherasDemon(botAI) && dynamic_cast(action)) - return 0.0f; + if (GetPhase2LeotherasDemon(botAI) && dynamic_cast(action)) + return 0.0f; - if (!GetPhase3LeotherasDemon(botAI) && dynamic_cast(action)) - return 0.0f; - } + if (!GetPhase3LeotherasDemon(botAI) && dynamic_cast(action)) + return 0.0f; return 1.0f; } @@ -335,7 +329,7 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) const uint32 instanceId = leotheras->GetMap()->GetInstanceId(); const time_t now = std::time(nullptr); - const uint8 dpsWaitSecondsPhase1 = 5; + constexpr uint8 dpsWaitSecondsPhase1 = 5; Unit* leotherasHuman = GetLeotherasHuman(botAI); Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI); if (leotherasHuman && !leotherasHuman->HasAura(SPELL_LEOTHERAS_BANISHED) && @@ -355,7 +349,7 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) } } - const uint8 dpsWaitSecondsPhase2 = 12; + constexpr uint8 dpsWaitSecondsPhase2 = 12; Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI); Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); if (leotherasPhase2Demon) @@ -377,7 +371,7 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) } } - const uint8 dpsWaitSecondsPhase3 = 8; + constexpr uint8 dpsWaitSecondsPhase3 = 8; if (leotherasPhase3Demon) { if ((demonFormTank && demonFormTank == bot) || botAI->IsTank(bot)) @@ -397,16 +391,14 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) return 1.0f; } -// Wait until the final phase to use Bloodlust/Heroism +// Don't use Bloodlust/Heroism during the Channeler phase float LeotherasTheBlindDelayBloodlustAndHeroismMultiplier::GetValue(Action* action) { if (bot->getClass() != CLASS_SHAMAN) return 1.0f; - if (!AI_VALUE2(Unit*, "find target", "leotheras the blind")) - return 1.0f; - - if (!GetPhase3LeotherasDemon(botAI)) + Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); + if (leotheras && leotheras->HasAura(SPELL_LEOTHERAS_BANISHED)) { if (dynamic_cast(action) || dynamic_cast(action)) @@ -494,7 +486,7 @@ float FathomLordKarathressWaitForDpsMultiplier::GetValue(Action* action) return 1.0f; const time_t now = std::time(nullptr); - const uint8 dpsWaitSeconds = 12; + constexpr uint8 dpsWaitSeconds = 12; auto it = karathressDpsWaitTimer.find(karathress->GetMap()->GetInstanceId()); if (it == karathressDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds) @@ -523,6 +515,8 @@ float FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier::GetValue return 1.0f; } +// Morogrim Tidewalker + // Use Bloodlust/Heroism after the first Murloc spawn float MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier::GetValue(Action* action) { @@ -577,6 +571,8 @@ float MorogrimTidewalkerMaintainPhase2StackingMultiplier::GetValue(Action* actio return 1.0f; } +// Lady Vashj + // Wait until phase 3 to use Bloodlust/Heroism // Don't use other major cooldowns in Phase 1, either float LadyVashjDelayCooldownsMultiplier::GetValue(Action* action) @@ -702,6 +698,12 @@ float LadyVashjCorePassersPrioritizePositioningMultiplier::GetValue(Action* acti return player && player->HasItemCount(ITEM_TAINTED_CORE, 1, false); }; + if (hasCore(bot)) + { + if (!dynamic_cast(action)) + return 0.0f; + } + if (bot == designatedLooter) { if (!hasCore(bot)) @@ -775,26 +777,27 @@ float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *ac if (IsLadyVashjInPhase3(botAI)) { - if (dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action) || - dynamic_cast(action)) + if (dynamic_cast(action)) return 0.0f; Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental"); - if (enchanted && bot->GetVictim() == enchanted) - { - if (dynamic_cast(action)) - return 0.0f; - } - Unit* strider = AI_VALUE2(Unit*, "find target", "coilfang strider"); Unit* elite = AI_VALUE2(Unit*, "find target", "coilfang elite"); - if (!strider && !elite) + if (enchanted || strider || elite) { - if (dynamic_cast(action)) + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action)) return 0.0f; + + if (enchanted && bot->GetVictim() == enchanted) + { + if (dynamic_cast(action)) + return 0.0f; + } } + else if (dynamic_cast(action)) + return 0.0f; } return 1.0f; diff --git a/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.cpp b/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.cpp index 63886f039d..139667dc66 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Strategy/RaidSSCStrategy.cpp @@ -57,7 +57,7 @@ void RaidSSCStrategy::InitTriggers(std::vector& triggers) NextAction("leotheras the blind target spellbinders", ACTION_RAID + 1) })); triggers.push_back(new TriggerNode("leotheras the blind boss transformed into demon form", { - NextAction("leotheras the blind demon form tank attack boss", ACTION_EMERGENCY + 6) })); + NextAction("leotheras the blind demon form tank attack boss", ACTION_RAID + 1) })); triggers.push_back(new TriggerNode("leotheras the blind only warlock should tank demon form", { NextAction("leotheras the blind melee tanks don't attack demon form", ACTION_RAID + 1) })); @@ -132,7 +132,7 @@ void RaidSSCStrategy::InitTriggers(std::vector& triggers) NextAction("lady vashj static charge move away from group", ACTION_EMERGENCY + 7) })); triggers.push_back(new TriggerNode("lady vashj pulling boss in phase 1 and phase 3", { - NextAction("lady vashj misdirect boss to main tank", ACTION_EMERGENCY + 1) })); + NextAction("lady vashj misdirect boss to main tank", ACTION_RAID + 2) })); triggers.push_back(new TriggerNode("lady vashj tainted elemental cheat", { NextAction("lady vashj teleport to tainted elemental", ACTION_EMERGENCY + 10), diff --git a/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp b/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp index f59a3db5d4..1039b1e318 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp @@ -6,6 +6,7 @@ #include "LootObjectStack.h" #include "ObjectAccessor.h" #include "Playerbots.h" +#include "RaidBossHelpers.h" using namespace SerpentShrineCavernHelpers; @@ -82,7 +83,7 @@ bool HydrossTheUnstableAggroResetsUponPhaseChangeTrigger::IsActive() bool HydrossTheUnstableNeedToManageTimersTrigger::IsActive() { return AI_VALUE2(Unit*, "find target", "hydross the unstable") && - IsInstanceTimerManager(botAI, bot); + IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr); } // The Lurker Below @@ -173,7 +174,7 @@ bool TheLurkerBelowBossIsSubmergedTrigger::IsActive() bool TheLurkerBelowNeedToPrepareTimerForSpoutTrigger::IsActive() { return AI_VALUE2(Unit*, "find target", "the lurker below") && - IsInstanceTimerManager(botAI, bot); + IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr); } // Leotheras the Blind @@ -185,27 +186,30 @@ bool LeotherasTheBlindBossIsInactiveTrigger::IsActive() bool LeotherasTheBlindBossTransformedIntoDemonFormTrigger::IsActive() { - return AI_VALUE2(Unit*, "find target", "leotheras the blind") && - GetLeotherasDemonFormTank(botAI, bot) == bot; + if (!AI_VALUE2(Unit*, "find target", "leotheras the blind")) + return false; + + if (GetLeotherasDemonFormTank(botAI, bot) != bot) + return false; + + return GetActiveLeotherasDemon(botAI); } bool LeotherasTheBlindOnlyWarlockShouldTankDemonFormTrigger::IsActive() { - if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) + if (botAI->IsRanged(bot) || !botAI->IsTank(bot)) return false; - if (!botAI->IsTank(bot)) + if (!AI_VALUE2(Unit*, "find target", "leotheras the blind")) return false; - Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST); - if (chaosBlast && chaosBlast->GetStackAmount() >= 5) + if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) return false; - if (!GetPhase2LeotherasDemon(botAI)) + if (!GetLeotherasDemonFormTank(botAI, bot)) return false; - return GetLeotherasDemonFormTank(botAI, bot) && - GetLeotherasDemonFormTank(botAI, bot) != bot; + return GetPhase2LeotherasDemon(botAI); } bool LeotherasTheBlindBossEngagedByRangedTrigger::IsActive() @@ -253,14 +257,10 @@ bool LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger::IsActive() if (!chaosBlast || chaosBlast->GetStackAmount() < 5) return false; - if (!GetPhase2LeotherasDemon(botAI)) + if (!GetLeotherasDemonFormTank(botAI, bot) && botAI->IsMainTank(bot)) return false; - Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); - if (!demonFormTank && botAI->IsMainTank(bot)) - return false; - - return true; + return GetPhase2LeotherasDemon(botAI); } bool LeotherasTheBlindInnerDemonHasAwakenedTrigger::IsActive() @@ -298,7 +298,7 @@ bool LeotherasTheBlindDemonFormTankNeedsAggro::IsActive() bool LeotherasTheBlindBossWipesAggroUponPhaseChangeTrigger::IsActive() { return AI_VALUE2(Unit*, "find target", "leotheras the blind") && - IsInstanceTimerManager(botAI, bot); + IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr); } // Fathom-Lord Karathress @@ -376,19 +376,19 @@ bool FathomLordKarathressDeterminingKillOrderTrigger::IsActive() if (botAI->IsDps(bot)) return true; else if (botAI->IsAssistTankOfIndex(bot, 0, false)) - return AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); + return !AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); else if (botAI->IsAssistTankOfIndex(bot, 1, false)) - return AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis"); + return !AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis"); else if (botAI->IsAssistTankOfIndex(bot, 2, false)) - return AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess"); + return !AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess"); else return false; } bool FathomLordKarathressTanksNeedToEstablishAggroTrigger::IsActive() { - return IsInstanceTimerManager(botAI, bot) && - AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); + return AI_VALUE2(Unit*, "find target", "fathom-lord karathress") && + IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr); } // Morogrim Tidewalker @@ -596,6 +596,9 @@ bool LadyVashjTaintedCoreIsUnusableTrigger::IsActive() if (!vashj) return false; + if (!IsLadyVashjInPhase2(botAI)) + return bot->HasItemCount(ITEM_TAINTED_CORE, 1, false); + Group* group = bot->GetGroup(); if (!group) return false; @@ -619,9 +622,6 @@ bool LadyVashjTaintedCoreIsUnusableTrigger::IsActive() return true; } - if (!IsLadyVashjInPhase2(botAI)) - return bot->HasItemCount(ITEM_TAINTED_CORE, 1, false); - return false; } @@ -635,7 +635,7 @@ bool LadyVashjNeedToResetCorePassingTrackersTrigger::IsActive() if (!group) return false; - return IsInstanceTimerManager(botAI, bot) || + return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr) || GetDesignatedCoreLooter(group, botAI) == bot || GetFirstTaintedCorePasser(group, botAI) == bot || GetSecondTaintedCorePasser(group, botAI) == bot || diff --git a/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.h b/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.h index 6a82853ef1..e106b58f3e 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.h +++ b/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.h @@ -141,14 +141,6 @@ class LeotherasTheBlindBossIsInactiveTrigger : public Trigger bool IsActive() override; }; -class LeotherasTheBlindHumanFormEngagedByMainTankTrigger : public Trigger -{ -public: - LeotherasTheBlindHumanFormEngagedByMainTankTrigger( - PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind human form engaged by main tank") {} - bool IsActive() override; -}; - class LeotherasTheBlindOnlyWarlockShouldTankDemonFormTrigger : public Trigger { public: diff --git a/src/Ai/Raid/SerpentshrineCavern/RaidSSCHelpers.cpp b/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.cpp similarity index 85% rename from src/Ai/Raid/SerpentshrineCavern/RaidSSCHelpers.cpp rename to src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.cpp index 8d8e307797..6087217168 100644 --- a/src/Ai/Raid/SerpentshrineCavern/RaidSSCHelpers.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.cpp @@ -3,111 +3,10 @@ #include "Creature.h" #include "ObjectAccessor.h" #include "Playerbots.h" -#include "RtiTargetValue.h" +#include "RaidBossHelpers.h" namespace SerpentShrineCavernHelpers { - // General Helpers - - void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId) - { - if (!target) - return; - - if (Group* group = bot->GetGroup()) - { - ObjectGuid currentGuid = group->GetTargetIcon(iconId); - if (currentGuid != target->GetGUID()) - group->SetTargetIcon(iconId, bot->GetGUID(), target->GetGUID()); - } - } - - void MarkTargetWithSkull(Player* bot, Unit* target) - { - MarkTargetWithIcon(bot, target, RtiTargetValue::skullIndex); - } - - void MarkTargetWithSquare(Player* bot, Unit* target) - { - MarkTargetWithIcon(bot, target, RtiTargetValue::squareIndex); - } - - void MarkTargetWithStar(Player* bot, Unit* target) - { - MarkTargetWithIcon(bot, target, RtiTargetValue::starIndex); - } - - void MarkTargetWithCircle(Player* bot, Unit* target) - { - MarkTargetWithIcon(bot, target, RtiTargetValue::circleIndex); - } - - void MarkTargetWithDiamond(Player* bot, Unit* target) - { - MarkTargetWithIcon(bot, target, RtiTargetValue::diamondIndex); - } - - void MarkTargetWithTriangle(Player* bot, Unit* target) - { - MarkTargetWithIcon(bot, target, RtiTargetValue::triangleIndex); - } - - void MarkTargetWithCross(Player* bot, Unit* target) - { - MarkTargetWithIcon(bot, target, RtiTargetValue::crossIndex); - } - - void MarkTargetWithMoon(Player* bot, Unit* target) - { - MarkTargetWithIcon(bot, target, RtiTargetValue::moonIndex); - } - - void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target) - { - if (!target) - return; - - std::string currentRti = botAI->GetAiObjectContext()->GetValue("rti")->Get(); - Unit* currentTarget = botAI->GetAiObjectContext()->GetValue("rti target")->Get(); - - if (currentRti != rtiName || currentTarget != target) - { - botAI->GetAiObjectContext()->GetValue("rti")->Set(rtiName); - botAI->GetAiObjectContext()->GetValue("rti target")->Set(target); - } - } - - // Dps bot selected for marking and managing timers and trackers - bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot) - { - if (Group* group = bot->GetGroup()) - { - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (member && member->IsAlive() && botAI->IsDps(member) && - GET_PLAYERBOT_AI(member) && !member->HasAura(SPELL_INSIDIOUS_WHISPER)) - return member == bot; - } - } - - return false; - } - - Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry) - { - auto const& npcs = - botAI->GetAiObjectContext()->GetValue("nearest hostile npcs")->Get(); - for (auto const& npcGuid : npcs) - { - Unit* unit = botAI->GetUnit(npcGuid); - if (unit && unit->IsAlive() && unit->GetEntry() == entry) - return unit; - } - - return nullptr; - } - // Hydross the Unstable const Position HYDROSS_FROST_TANK_POSITION = { -236.669f, -358.352f, -0.828f }; @@ -144,8 +43,7 @@ namespace SerpentShrineCavernHelpers bool HasNoMarkOfCorruption(Player* bot) { - return - !bot->HasAura(SPELL_MARK_OF_CORRUPTION_10) && + return !bot->HasAura(SPELL_MARK_OF_CORRUPTION_10) && !bot->HasAura(SPELL_MARK_OF_CORRUPTION_25) && !bot->HasAura(SPELL_MARK_OF_CORRUPTION_50) && !bot->HasAura(SPELL_MARK_OF_CORRUPTION_100) && @@ -646,7 +544,7 @@ namespace SerpentShrineCavernHelpers return nullptr; std::list triggers; - const float searchRange = 150.0f; + constexpr float searchRange = 150.0f; reference->GetCreatureListWithEntryInGrid( triggers, NPC_WORLD_INVISIBLE_TRIGGER, searchRange); diff --git a/src/Ai/Raid/SerpentshrineCavern/RaidSSCHelpers.h b/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.h similarity index 90% rename from src/Ai/Raid/SerpentshrineCavern/RaidSSCHelpers.h rename to src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.h index b2a980f046..dbc5628a2a 100644 --- a/src/Ai/Raid/SerpentshrineCavern/RaidSSCHelpers.h +++ b/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.h @@ -110,20 +110,7 @@ namespace SerpentShrineCavernHelpers ITEM_HEAVY_NETHERWEAVE_NET = 24269, }; - // General - const uint32 SSC_MAP_ID = 548; - void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId); - void MarkTargetWithSkull(Player* bot, Unit* target); - void MarkTargetWithSquare(Player* bot, Unit* target); - void MarkTargetWithStar(Player* bot, Unit* target); - void MarkTargetWithCircle(Player* bot, Unit* target); - void MarkTargetWithDiamond(Player* bot, Unit* target); - void MarkTargetWithTriangle(Player* bot, Unit* target); - void MarkTargetWithCross(Player* bot, Unit* target); - void MarkTargetWithMoon(Player* bot, Unit* target); - void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target); - bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot); - Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry); + constexpr uint32 SSC_MAP_ID = 548; // Hydross the Unstable extern const Position HYDROSS_FROST_TANK_POSITION; @@ -171,6 +158,7 @@ namespace SerpentShrineCavernHelpers extern std::unordered_map tidewalkerRangedStep; // Lady Vashj + constexpr float VASHJ_PLATFORM_Z = 42.985f; extern const Position VASHJ_PLATFORM_CENTER_POSITION; extern std::unordered_map vashjRangedPositions; extern std::unordered_map hasReachedVashjRangedPosition; From 0348c6aa3a3df63b00a79a86ceaa59c48850f0c3 Mon Sep 17 00:00:00 2001 From: Crow Date: Fri, 6 Feb 2026 21:47:02 -0600 Subject: [PATCH 24/25] delete duplicate condition for multiplier --- .../Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp b/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp index d43e6a52f3..d66c67d007 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp @@ -645,8 +645,7 @@ float LadyVashjStaticChargeStayAwayFromGroupMultiplier::GetValue(Action* action) if (!AI_VALUE2(Unit*, "find target", "lady vashj")) return 1.0f; - if (dynamic_cast(action) || - dynamic_cast(action) || + if (dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || dynamic_cast(action) || From 3163b4a5840269ce4dbcbbc832da2b50e920f434 Mon Sep 17 00:00:00 2001 From: crow Date: Sun, 8 Feb 2026 02:49:34 -0600 Subject: [PATCH 25/25] comment out unused params and other fixes --- .../Action/RaidSSCActions.cpp | 104 +++++++++--------- .../Multiplier/RaidSSCMultipliers.cpp | 8 +- .../Trigger/RaidSSCTriggers.cpp | 10 +- .../Util/RaidSSCHelpers.cpp | 18 +-- .../SerpentshrineCavern/Util/RaidSSCHelpers.h | 2 +- 5 files changed, 65 insertions(+), 77 deletions(-) diff --git a/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp b/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp index fedc7e5f87..7aa3eda35b 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Action/RaidSSCActions.cpp @@ -13,7 +13,7 @@ using namespace SerpentShrineCavernHelpers; // General -bool SerpentShrineCavernEraseTimersAndTrackersAction::Execute(Event event) +bool SerpentShrineCavernEraseTimersAndTrackersAction::Execute(Event /*event*/) { const uint32 instanceId = bot->GetMap()->GetInstanceId(); const ObjectGuid guid = bot->GetGUID(); @@ -63,7 +63,7 @@ bool SerpentShrineCavernEraseTimersAndTrackersAction::Execute(Event event) // Trash Mobs // Move out of toxic pool left behind by some colossi upon death -bool UnderbogColossusEscapeToxicPoolAction::Execute(Event event) +bool UnderbogColossusEscapeToxicPoolAction::Execute(Event /*event*/) { Aura* aura = bot->GetAura(SPELL_TOXIC_POOL); if (!aura) @@ -129,7 +129,7 @@ bool UnderbogColossusEscapeToxicPoolAction::Execute(Event event) true, MovementPriority::MOVEMENT_FORCED, true, false); } -bool GreyheartTidecallerMarkWaterElementalTotemAction::Execute(Event event) +bool GreyheartTidecallerMarkWaterElementalTotemAction::Execute(Event /*event*/) { if (Unit* totem = GetFirstAliveUnitByEntry(botAI, NPC_WATER_ELEMENTAL_TOTEM)) MarkTargetWithSkull(bot, totem); @@ -142,7 +142,7 @@ bool GreyheartTidecallerMarkWaterElementalTotemAction::Execute(Event event) // (1) When tanking, move to designated tanking spot on frost side // (2) 1 second after 100% Mark of Hydross, move to nature tank's spot to hand off boss // (3) When Hydross is in nature form, move back to frost tank spot and wait for transition -bool HydrossTheUnstablePositionFrostTankAction::Execute(Event event) +bool HydrossTheUnstablePositionFrostTankAction::Execute(Event /*event*/) { Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); if (!hydross) @@ -224,7 +224,7 @@ bool HydrossTheUnstablePositionFrostTankAction::Execute(Event event) // (1) When tanking, move to designated tanking spot on nature side // (2) 1 second after 100% Mark of Corruption, move to frost tank's spot to hand off boss // (3) When Hydross is in frost form, move back to nature tank spot and wait for transition -bool HydrossTheUnstablePositionNatureTankAction::Execute(Event event) +bool HydrossTheUnstablePositionNatureTankAction::Execute(Event /*event*/) { Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); if (!hydross) @@ -303,7 +303,7 @@ bool HydrossTheUnstablePositionNatureTankAction::Execute(Event event) return false; } -bool HydrossTheUnstablePrioritizeElementalAddsAction::Execute(Event event) +bool HydrossTheUnstablePrioritizeElementalAddsAction::Execute(Event /*event*/) { Unit* waterElemental = GetFirstAliveUnitByEntry(botAI, NPC_PURE_SPAWN_OF_HYDROSS); if (waterElemental) @@ -331,7 +331,7 @@ bool HydrossTheUnstablePrioritizeElementalAddsAction::Execute(Event event) } // To mitigate the effect of Water Tomb -bool HydrossTheUnstableFrostPhaseSpreadOutAction::Execute(Event event) +bool HydrossTheUnstableFrostPhaseSpreadOutAction::Execute(Event /*event*/) { if (!AI_VALUE2(Unit*, "find target", "hydross the unstable")) return false; @@ -354,7 +354,7 @@ bool HydrossTheUnstableFrostPhaseSpreadOutAction::Execute(Event event) return false; } -bool HydrossTheUnstableMisdirectBossToTankAction::Execute(Event event) +bool HydrossTheUnstableMisdirectBossToTankAction::Execute(Event /*event*/) { Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); if (!hydross) @@ -424,7 +424,7 @@ bool HydrossTheUnstableMisdirectBossToTankAction::TryMisdirectToNatureTank( return false; } -bool HydrossTheUnstableStopDpsUponPhaseChangeAction::Execute(Event event) +bool HydrossTheUnstableStopDpsUponPhaseChangeAction::Execute(Event /*event*/) { Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); if (!hydross) @@ -470,7 +470,7 @@ bool HydrossTheUnstableStopDpsUponPhaseChangeAction::Execute(Event event) return false; } -bool HydrossTheUnstableManageTimersAction::Execute(Event event) +bool HydrossTheUnstableManageTimersAction::Execute(Event /*event*/) { Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); if (!hydross) @@ -515,7 +515,7 @@ bool HydrossTheUnstableManageTimersAction::Execute(Event event) // The Lurker Below // Run around behind Lurker during Spout -bool TheLurkerBelowRunAroundBehindBossAction::Execute(Event event) +bool TheLurkerBelowRunAroundBehindBossAction::Execute(Event /*event*/) { Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); if (!lurker) @@ -552,7 +552,7 @@ bool TheLurkerBelowRunAroundBehindBossAction::Execute(Event event) return false; } -bool TheLurkerBelowPositionMainTankAction::Execute(Event event) +bool TheLurkerBelowPositionMainTankAction::Execute(Event /*event*/) { Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); if (!lurker) @@ -573,7 +573,7 @@ bool TheLurkerBelowPositionMainTankAction::Execute(Event event) } // Assign ranged positions within a 120-degree arc behind Lurker -bool TheLurkerBelowSpreadRangedInArcAction::Execute(Event event) +bool TheLurkerBelowSpreadRangedInArcAction::Execute(Event /*event*/) { Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); if (!lurker) @@ -636,7 +636,7 @@ bool TheLurkerBelowSpreadRangedInArcAction::Execute(Event event) // During the submerge phase, if there are >= 3 tanks in the raid, // the first 3 will each pick up 1 Guardian -bool TheLurkerBelowTanksPickUpAddsAction::Execute(Event event) +bool TheLurkerBelowTanksPickUpAddsAction::Execute(Event /*event*/) { Player* mainTank = nullptr; Player* firstAssistTank = nullptr; @@ -700,7 +700,7 @@ bool TheLurkerBelowTanksPickUpAddsAction::Execute(Event event) return false; } -bool TheLurkerBelowManageSpoutTimerAction::Execute(Event event) +bool TheLurkerBelowManageSpoutTimerAction::Execute(Event /*event*/) { Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); if (!lurker) @@ -725,7 +725,7 @@ bool TheLurkerBelowManageSpoutTimerAction::Execute(Event event) // Leotheras the Blind -bool LeotherasTheBlindTargetSpellbindersAction::Execute(Event event) +bool LeotherasTheBlindTargetSpellbindersAction::Execute(Event /*event*/) { if (Unit* spellbinder = GetFirstAliveUnitByEntry(botAI, NPC_GREYHEART_SPELLBINDER)) MarkTargetWithSkull(bot, spellbinder); @@ -735,7 +735,7 @@ bool LeotherasTheBlindTargetSpellbindersAction::Execute(Event event) // Warlock tank action--see GetLeotherasDemonFormTank in RaidSSCHelpers.cpp // Use tank strategy for Demon Form and DPS strategy for Human Form -bool LeotherasTheBlindDemonFormTankAttackBossAction::Execute(Event event) +bool LeotherasTheBlindDemonFormTankAttackBossAction::Execute(Event /*event*/) { Unit* innerDemon = nullptr; auto const& npcs = @@ -769,7 +769,7 @@ bool LeotherasTheBlindDemonFormTankAttackBossAction::Execute(Event event) // Stop melee tanks from attacking upon transformation so they don't take aggro // Applies only if there is a Warlock tank present -bool LeotherasTheBlindMeleeTanksDontAttackDemonFormAction::Execute(Event event) +bool LeotherasTheBlindMeleeTanksDontAttackDemonFormAction::Execute(Event /*event*/) { bot->AttackStop(); botAI->Reset(); @@ -778,7 +778,7 @@ bool LeotherasTheBlindMeleeTanksDontAttackDemonFormAction::Execute(Event event) // Intent is to keep enough distance from Leotheras and spread to prepare for Whirlwind // And stay away from the Warlock tank to avoid Chaos Blasts -bool LeotherasTheBlindPositionRangedAction::Execute(Event event) +bool LeotherasTheBlindPositionRangedAction::Execute(Event /*event*/) { constexpr float safeDistFromBoss = 15.0f; Unit* leotherasHuman = GetLeotherasHuman(botAI); @@ -802,7 +802,7 @@ bool LeotherasTheBlindPositionRangedAction::Execute(Event event) continue; constexpr uint32 minInterval = 0; - if (GetLeotherasDemonFormTank(botAI, bot) == member) + if (GetLeotherasDemonFormTank(bot) == member) { constexpr float safeDistFromTank = 10.0f; if (bot->GetExactDist2d(member) < safeDistFromTank) @@ -820,7 +820,7 @@ bool LeotherasTheBlindPositionRangedAction::Execute(Event event) return false; } -bool LeotherasTheBlindRunAwayFromWhirlwindAction::Execute(Event event) +bool LeotherasTheBlindRunAwayFromWhirlwindAction::Execute(Event /*event*/) { if (Unit* leotherasHuman = GetLeotherasHuman(botAI)) { @@ -838,7 +838,7 @@ bool LeotherasTheBlindRunAwayFromWhirlwindAction::Execute(Event event) // This method is likely unnecessary unless the player does not use a Warlock tank // If a melee tank is used, other melee needs to run away after too many Chaos Blast stacks -bool LeotherasTheBlindMeleeDpsRunAwayFromBossAction::Execute(Event event) +bool LeotherasTheBlindMeleeDpsRunAwayFromBossAction::Execute(Event /*event*/) { if (botAI->CanCastSpell("cloak of shadows", bot)) return botAI->CastSpell("cloak of shadows", bot); @@ -864,7 +864,7 @@ bool LeotherasTheBlindMeleeDpsRunAwayFromBossAction::Execute(Event event) } // Hardcoded actions for healers and bear tanks to kill Inner Demons -bool LeotherasTheBlindDestroyInnerDemonAction::Execute(Event event) +bool LeotherasTheBlindDestroyInnerDemonAction::Execute(Event /*event*/) { Unit* innerDemon = nullptr; auto const& npcs = @@ -1033,7 +1033,7 @@ bool LeotherasTheBlindDestroyInnerDemonAction::HandleHealerStrategy(Unit* innerD } // Everybody except the Warlock tank should focus on Leotheras in Phase 3 -bool LeotherasTheBlindFinalPhaseAssignDpsPriorityAction::Execute(Event event) +bool LeotherasTheBlindFinalPhaseAssignDpsPriorityAction::Execute(Event /*event*/) { Unit* leotherasHuman = GetLeotherasHuman(botAI); if (!leotherasHuman) @@ -1068,13 +1068,13 @@ bool LeotherasTheBlindFinalPhaseAssignDpsPriorityAction::Execute(Event event) } // Misdirect to Warlock tank or to main tank if there is no Warlock tank -bool LeotherasTheBlindMisdirectBossToDemonFormTankAction::Execute(Event event) +bool LeotherasTheBlindMisdirectBossToDemonFormTankAction::Execute(Event /*event*/) { Unit* leotherasDemon = GetActiveLeotherasDemon(botAI); if (!leotherasDemon) return false; - Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); + Player* demonFormTank = GetLeotherasDemonFormTank(bot); Player* targetTank = demonFormTank; if (!targetTank) @@ -1106,7 +1106,7 @@ bool LeotherasTheBlindMisdirectBossToDemonFormTankAction::Execute(Event event) } // This does not pause DPS after a Whirlwind, which is also an aggro wipe -bool LeotherasTheBlindManageDpsWaitTimersAction::Execute(Event event) +bool LeotherasTheBlindManageDpsWaitTimersAction::Execute(Event /*event*/) { Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); if (!leotheras) @@ -1164,7 +1164,7 @@ bool LeotherasTheBlindManageDpsWaitTimersAction::Execute(Event event) // is crucial to separate Caribdis from the others // Karathress is tanked near his starting position -bool FathomLordKarathressMainTankPositionBossAction::Execute(Event event) +bool FathomLordKarathressMainTankPositionBossAction::Execute(Event /*event*/) { Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); if (!karathress) @@ -1200,7 +1200,7 @@ bool FathomLordKarathressMainTankPositionBossAction::Execute(Event event) // Caribdis is pulled far to the West in the corner // Best to use a Warrior or Druid tank for interrupts -bool FathomLordKarathressFirstAssistTankPositionCaribdisAction::Execute(Event event) +bool FathomLordKarathressFirstAssistTankPositionCaribdisAction::Execute(Event /*event*/) { Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); if (!caribdis) @@ -1235,7 +1235,7 @@ bool FathomLordKarathressFirstAssistTankPositionCaribdisAction::Execute(Event ev } // Sharkkis is pulled North to the other side of the ramp -bool FathomLordKarathressSecondAssistTankPositionSharkkisAction::Execute(Event event) +bool FathomLordKarathressSecondAssistTankPositionSharkkisAction::Execute(Event /*event*/) { Unit* sharkkis = AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis"); if (!sharkkis) @@ -1270,7 +1270,7 @@ bool FathomLordKarathressSecondAssistTankPositionSharkkisAction::Execute(Event e } // Tidalvess is pulled Northwest near the pillar -bool FathomLordKarathressThirdAssistTankPositionTidalvessAction::Execute(Event event) +bool FathomLordKarathressThirdAssistTankPositionTidalvessAction::Execute(Event /*event*/) { Unit* tidalvess = AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess"); if (!tidalvess) @@ -1306,7 +1306,7 @@ bool FathomLordKarathressThirdAssistTankPositionTidalvessAction::Execute(Event e // Caribdis's tank spot is far away so a dedicated healer is needed // Use the assistant flag to select the healer -bool FathomLordKarathressPositionCaribdisTankHealerAction::Execute(Event event) +bool FathomLordKarathressPositionCaribdisTankHealerAction::Execute(Event /*event*/) { Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); if (!caribdis) @@ -1332,7 +1332,7 @@ bool FathomLordKarathressPositionCaribdisTankHealerAction::Execute(Event event) } // Misdirect priority: (1) Caribdis tank, (2) Tidalvess tank, (3) Sharkkis tank -bool FathomLordKarathressMisdirectBossesToTanksAction::Execute(Event event) +bool FathomLordKarathressMisdirectBossesToTanksAction::Execute(Event /*event*/) { Group* group = bot->GetGroup(); if (!group) @@ -1418,7 +1418,7 @@ bool FathomLordKarathressMisdirectBossesToTanksAction::Execute(Event event) // Kill order is non-standard because bots handle Cyclones poorly and need more time // to get her down than real players (standard is ranged DPS help with Sharkkis first) -bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) +bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event /*event*/) { // Target priority 1: Spitfire Totems for melee dps Unit* totem = GetFirstAliveUnitByEntry(botAI, NPC_SPITFIRE_TOTEM); @@ -1526,7 +1526,7 @@ bool FathomLordKarathressAssignDpsPriorityAction::Execute(Event event) return false; } -bool FathomLordKarathressManageDpsTimerAction::Execute(Event event) +bool FathomLordKarathressManageDpsTimerAction::Execute(Event /*event*/) { Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); if (!karathress) @@ -1540,7 +1540,7 @@ bool FathomLordKarathressManageDpsTimerAction::Execute(Event event) // Morogrim Tidewalker -bool MorogrimTidewalkerMisdirectBossToMainTankAction::Execute(Event event) +bool MorogrimTidewalkerMisdirectBossToMainTankAction::Execute(Event /*event*/) { Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); if (!tidewalker) @@ -1574,7 +1574,7 @@ bool MorogrimTidewalkerMisdirectBossToMainTankAction::Execute(Event event) // Separate tanking positions are used for phase 1 and phase 2 to address the // Water Globule mechanic in phase 2 -bool MorogrimTidewalkerMoveBossToTankPositionAction::Execute(Event event) +bool MorogrimTidewalkerMoveBossToTankPositionAction::Execute(Event /*event*/) { Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); if (!tidewalker) @@ -1666,7 +1666,7 @@ bool MorogrimTidewalkerMoveBossToTankPositionAction::MoveToPhase2TankPosition(Un // Ranged stack behind the boss in the Northeast corner in phase 2 // No corresponding method for melee since they will do so anyway -bool MorogrimTidewalkerPhase2RepositionRangedAction::Execute(Event event) +bool MorogrimTidewalkerPhase2RepositionRangedAction::Execute(Event /*event*/) { Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); if (!tidewalker) @@ -1724,7 +1724,7 @@ bool MorogrimTidewalkerPhase2RepositionRangedAction::Execute(Event event) // Lady Vashj -bool LadyVashjMainTankPositionBossAction::Execute(Event event) +bool LadyVashjMainTankPositionBossAction::Execute(Event /*event*/) { Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); if (!vashj) @@ -1772,7 +1772,7 @@ bool LadyVashjMainTankPositionBossAction::Execute(Event event) } // Semicircle around center of the room (to allow escape paths by Static Charged bots) -bool LadyVashjPhase1SpreadRangedInArcAction::Execute(Event event) +bool LadyVashjPhase1SpreadRangedInArcAction::Execute(Event /*event*/) { std::vector spreadMembers; if (Group* group = bot->GetGroup()) @@ -1845,7 +1845,7 @@ bool LadyVashjPhase1SpreadRangedInArcAction::Execute(Event event) } // For absorbing Shock Burst -bool LadyVashjSetGroundingTotemInMainTankGroupAction::Execute(Event event) +bool LadyVashjSetGroundingTotemInMainTankGroupAction::Execute(Event /*event*/) { Player* mainTank = nullptr; if (Group* group = bot->GetGroup()) @@ -1880,7 +1880,7 @@ bool LadyVashjSetGroundingTotemInMainTankGroupAction::Execute(Event event) return false; } -bool LadyVashjMisdirectBossToMainTankAction::Execute(Event event) +bool LadyVashjMisdirectBossToMainTankAction::Execute(Event /*event*/) { Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); if (!vashj) @@ -1912,7 +1912,7 @@ bool LadyVashjMisdirectBossToMainTankAction::Execute(Event event) return false; } -bool LadyVashjStaticChargeMoveAwayFromGroupAction::Execute(Event event) +bool LadyVashjStaticChargeMoveAwayFromGroupAction::Execute(Event /*event*/) { Group* group = bot->GetGroup(); if (!group) @@ -1958,7 +1958,7 @@ bool LadyVashjStaticChargeMoveAwayFromGroupAction::Execute(Event event) return false; } -bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event event) +bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event /*event*/) { Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); if (!vashj) @@ -2134,7 +2134,7 @@ bool LadyVashjAssignPhase2AndPhase3DpsPriorityAction::Execute(Event event) return false; } -bool LadyVashjMisdirectStriderToFirstAssistTankAction::Execute(Event event) +bool LadyVashjMisdirectStriderToFirstAssistTankAction::Execute(Event /*event*/) { // Striders are not tankable without a cheat to block Fear so there is // no point in misdirecting if raid cheats are not enabled @@ -2174,7 +2174,7 @@ bool LadyVashjMisdirectStriderToFirstAssistTankAction::Execute(Event event) return false; } -bool LadyVashjTankAttackAndMoveAwayStriderAction::Execute(Event event) +bool LadyVashjTankAttackAndMoveAwayStriderAction::Execute(Event /*event*/) { Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); if (!vashj) @@ -2246,7 +2246,7 @@ bool LadyVashjTankAttackAndMoveAwayStriderAction::Execute(Event event) // If cheats are enabled, the first returned melee DPS bot will teleport to Tainted Elementals // Such bot will recover HP and remove the Poison Bolt debuff while attacking the elemental -bool LadyVashjTeleportToTaintedElementalAction::Execute(Event event) +bool LadyVashjTeleportToTaintedElementalAction::Execute(Event /*event*/) { Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental"); if (!tainted) @@ -2356,7 +2356,7 @@ bool LadyVashjLootTaintedCoreAction::Execute(Event) return false; } -bool LadyVashjPassTheTaintedCoreAction::Execute(Event event) +bool LadyVashjPassTheTaintedCoreAction::Execute(Event /*event*/) { Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); if (!vashj) @@ -2853,7 +2853,7 @@ bool LadyVashjPassTheTaintedCoreAction::UseCoreOnNearestGenerator(const uint32 i // Fallback for residual cores to be destroyed in Phase 3 in case // ScheduleTransferCoreAfterImbue() fails to remove the core from the giver -bool LadyVashjDestroyTaintedCoreAction::Execute(Event event) +bool LadyVashjDestroyTaintedCoreAction::Execute(Event /*event*/) { if (Item* core = bot->GetItemByEntry(ITEM_TAINTED_CORE)) { @@ -2866,7 +2866,7 @@ bool LadyVashjDestroyTaintedCoreAction::Execute(Event event) // This needs to be separate from the general map erasing logic because // Bots may end up out of combat during the Vashj encounter -bool LadyVashjEraseCorePassingTrackersAction::Execute(Event event) +bool LadyVashjEraseCorePassingTrackersAction::Execute(Event /*event*/) { Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); if (!vashj) @@ -2890,7 +2890,7 @@ bool LadyVashjEraseCorePassingTrackersAction::Execute(Event event) // The standard "avoid aoe" strategy does work for Toxic Spores, but this method // provides more buffer distance and limits the area in which bots can move // so that they do not go down the stairs -bool LadyVashjAvoidToxicSporesAction::Execute(Event event) +bool LadyVashjAvoidToxicSporesAction::Execute(Event /*event*/) { auto const& spores = GetAllSporeDropTriggers(botAI, bot); if (spores.empty()) @@ -3033,7 +3033,7 @@ std::vector LadyVashjAvoidToxicSporesAction::GetAllSporeDropTriggers( return sporeDropTriggers; } -bool LadyVashjUseFreeActionAbilitiesAction::Execute(Event event) +bool LadyVashjUseFreeActionAbilitiesAction::Execute(Event /*event*/) { Group* group = bot->GetGroup(); if (!group) diff --git a/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp b/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp index d66c67d007..c99cafa3c7 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Multiplier/RaidSSCMultipliers.cpp @@ -351,7 +351,7 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action) constexpr uint8 dpsWaitSecondsPhase2 = 12; Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI); - Player* demonFormTank = GetLeotherasDemonFormTank(botAI, bot); + Player* demonFormTank = GetLeotherasDemonFormTank(bot); if (leotherasPhase2Demon) { if (demonFormTank && demonFormTank == bot) @@ -655,13 +655,9 @@ float LadyVashjStaticChargeStayAwayFromGroupMultiplier::GetValue(Action* action) return 1.0f; } -// If raid cheat (which enables bot looting of the core) is not enabled -// Bots should not loot the core +// Bots should not loot the core with normal looting logic float LadyVashjDoNotLootTheTaintedCoreMultiplier::GetValue(Action* action) { - if (!botAI->HasCheat(BotCheatMask::raid)) - return 1.0f; - if (AI_VALUE2(Unit*, "find target", "lady vashj")) { if (dynamic_cast(action)) diff --git a/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp b/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp index 1039b1e318..e77e63642e 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Trigger/RaidSSCTriggers.cpp @@ -189,7 +189,7 @@ bool LeotherasTheBlindBossTransformedIntoDemonFormTrigger::IsActive() if (!AI_VALUE2(Unit*, "find target", "leotheras the blind")) return false; - if (GetLeotherasDemonFormTank(botAI, bot) != bot) + if (GetLeotherasDemonFormTank(bot) != bot) return false; return GetActiveLeotherasDemon(botAI); @@ -206,7 +206,7 @@ bool LeotherasTheBlindOnlyWarlockShouldTankDemonFormTrigger::IsActive() if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) return false; - if (!GetLeotherasDemonFormTank(botAI, bot)) + if (!GetLeotherasDemonFormTank(bot)) return false; return GetPhase2LeotherasDemon(botAI); @@ -257,7 +257,7 @@ bool LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger::IsActive() if (!chaosBlast || chaosBlast->GetStackAmount() < 5) return false; - if (!GetLeotherasDemonFormTank(botAI, bot) && botAI->IsMainTank(bot)) + if (!GetLeotherasDemonFormTank(bot) && botAI->IsMainTank(bot)) return false; return GetPhase2LeotherasDemon(botAI); @@ -266,7 +266,7 @@ bool LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger::IsActive() bool LeotherasTheBlindInnerDemonHasAwakenedTrigger::IsActive() { return bot->HasAura(SPELL_INSIDIOUS_WHISPER) && - GetLeotherasDemonFormTank(botAI, bot) != bot; + GetLeotherasDemonFormTank(bot) != bot; } bool LeotherasTheBlindEnteredFinalPhaseTrigger::IsActive() @@ -277,7 +277,7 @@ bool LeotherasTheBlindEnteredFinalPhaseTrigger::IsActive() if (botAI->IsHeal(bot)) return false; - if (GetLeotherasDemonFormTank(botAI, bot) == bot) + if (GetLeotherasDemonFormTank(bot) == bot) return false; return GetPhase3LeotherasDemon(botAI) && diff --git a/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.cpp b/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.cpp index 6087217168..7bda085be1 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.cpp +++ b/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.cpp @@ -127,7 +127,7 @@ namespace SerpentShrineCavernHelpers return phase2 ? phase2 : phase3; } - Player* GetLeotherasDemonFormTank(PlayerbotAI* botAI, Player* bot) + Player* GetLeotherasDemonFormTank(Player* bot) { Group* group = bot->GetGroup(); if (!group) @@ -144,10 +144,7 @@ namespace SerpentShrineCavernHelpers return member; } - // (2) Fall back to bot Warlock with highest HP - Player* highestHpWarlock = nullptr; - uint32 highestHp = 0; - + // (2) Fall back to first found bot Warlock for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); @@ -155,16 +152,11 @@ namespace SerpentShrineCavernHelpers member->getClass() != CLASS_WARLOCK) continue; - uint32 hp = member->GetMaxHealth(); - if (!highestHpWarlock || hp > highestHp) - { - highestHpWarlock = member; - highestHp = hp; - } + return member; } - // (3) Return the found Warlock tank, or nullptr if none found - return highestHpWarlock; + // (3) Return nullptr if none found + return nullptr; } // Fathom-Lord Karathress diff --git a/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.h b/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.h index dbc5628a2a..a725e28fcd 100644 --- a/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.h +++ b/src/Ai/Raid/SerpentshrineCavern/Util/RaidSSCHelpers.h @@ -138,7 +138,7 @@ namespace SerpentShrineCavernHelpers Unit* GetPhase2LeotherasDemon(PlayerbotAI* botAI); Unit* GetPhase3LeotherasDemon(PlayerbotAI* botAI); Unit* GetActiveLeotherasDemon(PlayerbotAI* botAI); - Player* GetLeotherasDemonFormTank(PlayerbotAI* botAI, Player* bot); + Player* GetLeotherasDemonFormTank(Player* bot); // Fathom-Lord Karathress extern const Position KARATHRESS_TANK_POSITION;