From 6c8923eeac3c3fe7a08a9e1233a7283d65c4dd23 Mon Sep 17 00:00:00 2001 From: Keleborn <22352763+Celandriel@users.noreply.github.com> Date: Fri, 24 Oct 2025 13:15:14 -0700 Subject: [PATCH] Feat. Playerbots naxx 40 Integration (#749) Override default Naxx playerbots strategy to include Naxx40 bosses. --- ...idualProgressionPlayerbotsActionContext.h} | 18 + ...idualProgressionPlayerbotsTriggerContext.h | 69 +++ .../IndividualProgressionStrategyOverride.cpp | 22 + .../IndividualProgressionStrategyOverride.h | 17 + .../IndividualProgressionNaxxActions.cpp | 143 ++++++ .../Naxx40/IndividualProgressionNaxxActions.h | 40 ++ .../Naxx40/IndividualProgressionNaxxAi40.h | 81 +++ .../IndividualProgressionNaxxBossHelper.h | 470 ++++++++++++++++++ .../IndividualProgressionNaxxMultipliers.cpp | 255 ++++++++++ .../IndividualProgressionNaxxMultipliers.h | 71 +++ .../IndividualProgressionNaxxTriggers.cpp | 156 ++++++ .../IndividualProgressionNaxxTriggers.h | 183 +++++++ 12 files changed, 1525 insertions(+) rename src/playerbots/{InidivualProgressionPlayerbotsActionContext.h => IndividualProgressionPlayerbotsActionContext.h} (51%) create mode 100644 src/playerbots/IndividualProgressionPlayerbotsTriggerContext.h create mode 100644 src/playerbots/IndividualProgressionStrategyOverride.cpp create mode 100644 src/playerbots/IndividualProgressionStrategyOverride.h create mode 100644 src/playerbots/raids/Naxx40/IndividualProgressionNaxxActions.cpp create mode 100644 src/playerbots/raids/Naxx40/IndividualProgressionNaxxActions.h create mode 100644 src/playerbots/raids/Naxx40/IndividualProgressionNaxxAi40.h create mode 100644 src/playerbots/raids/Naxx40/IndividualProgressionNaxxBossHelper.h create mode 100644 src/playerbots/raids/Naxx40/IndividualProgressionNaxxMultipliers.cpp create mode 100644 src/playerbots/raids/Naxx40/IndividualProgressionNaxxMultipliers.h create mode 100644 src/playerbots/raids/Naxx40/IndividualProgressionNaxxTriggers.cpp create mode 100644 src/playerbots/raids/Naxx40/IndividualProgressionNaxxTriggers.h diff --git a/src/playerbots/InidivualProgressionPlayerbotsActionContext.h b/src/playerbots/IndividualProgressionPlayerbotsActionContext.h similarity index 51% rename from src/playerbots/InidivualProgressionPlayerbotsActionContext.h rename to src/playerbots/IndividualProgressionPlayerbotsActionContext.h index c12509d..c14541c 100644 --- a/src/playerbots/InidivualProgressionPlayerbotsActionContext.h +++ b/src/playerbots/IndividualProgressionPlayerbotsActionContext.h @@ -6,6 +6,9 @@ #include "IndividualProgressionOnyxiaActions.h" #include "RaidOnyxiaActionContext.h" +#include "IndividualProgressionNaxxActions.h" +#include "RaidNaxxActionContext.h" + class IPOnyxiaActionContext : public RaidOnyxiaActionContext { public: @@ -18,5 +21,20 @@ private: }; +class IPNaxxActionContext : public RaidNaxxActionContext +{ +public: + IPNaxxActionContext : public RaidNaxxActionContext() + { + creators["grobbulus go behind"] = &IPNaxxActionContext::go_behind_the_boss; + creators["rotate grobbulus"] = &IPNaxxActionContext::rotate_grobbulus; + } + +private: + static Action* go_behind_the_boss(PlayerbotAI* ai) { return new IPGrobbulusGoBehindAction(ai); } + static Action* rotate_grobbulus(PlayerbotAI* ai) { return new IPGrobbulusRotateAction(ai); } +}; + + #endif #endif diff --git a/src/playerbots/IndividualProgressionPlayerbotsTriggerContext.h b/src/playerbots/IndividualProgressionPlayerbotsTriggerContext.h new file mode 100644 index 0000000..54e3fc3 --- /dev/null +++ b/src/playerbots/IndividualProgressionPlayerbotsTriggerContext.h @@ -0,0 +1,69 @@ +// /* +// * Copyright (C) 2016+ AzerothCore , released under GNU GPL v2 license, you may redistribute it +// and/or modify it under version 2 of the License, or (at your option), any later version. +// */ +#ifdef MOD_PLAYERBOTS +#ifndef MOD_INDIVIDUAL_PROGRESSION_TRIGGER_CONTEXT_H +#define MOD_INDIVIDUAL_PROGRESSION_TRIGGER_CONTEXT_H + +#include "AiObjectContext.h" +#include "NamedObjectContext.h" +#include "RaidNaxxTriggers.h" +#include "IndividualProgressionNaxxTriggers.h" + + +class IPRaidNaxxTriggerContext : public RaidNaxxTriggerContext +{ +public: + IPRaidNaxxTriggerContext : RaidNaxxTriggerContext() + { + creators["grobbulus cloud"] = &IPRaidNaxxTriggerContext::grobbulus_cloud; + creators["heigan melee"] = &IPRaidNaxxTriggerContext::heigan_melee; + creators["heigan ranged"] = &IPRaidNaxxTriggerContext::heigan_ranged; + + creators["thaddius phase pet"] = &IPRaidNaxxTriggerContext::thaddius_phase_pet; + creators["thaddius phase transition"] = &IPRaidNaxxTriggerContext::thaddius_phase_transition; + creators["thaddius phase thaddius"] = &IPRaidNaxxTriggerContext::thaddius_phase_thaddius; + + creators["razuvious tank"] = &IPRaidNaxxTriggerContext::razuvious_tank; + creators["razuvious nontank"] = &IPRaidNaxxTriggerContext::razuvious_nontank; + + creators["horseman attractors"] = &IPRaidNaxxTriggerContext::horseman_attractors; + creators["horseman except attractors"] = &IPRaidNaxxTriggerContext::horseman_except_attractors; + + creators["sapphiron ground"] = &IPRaidNaxxTriggerContext::sapphiron_ground; + creators["sapphiron flight"] = &IPRaidNaxxTriggerContext::sapphiron_flight; + + creators["kel'thuzad"] = &IPRaidNaxxTriggerContext::kelthuzad; + + creators["gluth"] = &IPRaidNaxxTriggerContext::gluth; + creators["gluth main tank mortal wound"] = &IPRaidNaxxTriggerContext::gluth_main_tank_mortal_wound; + + creators["loatheb"] = &IPRaidNaxxTriggerContext::loatheb; + } + +private: + static Trigger* grobbulus_cloud(PlayerbotAI* ai) { return new IPGrobbulusCloudTrigger(ai); } + static Trigger* heigan_melee(PlayerbotAI* ai) { return new IPHeiganMeleeTrigger(ai); } + static Trigger* heigan_ranged(PlayerbotAI* ai) { return new IPHeiganRangedTrigger(ai); } + + static Trigger* thaddius_phase_pet(PlayerbotAI* ai) { return new IPThaddiusPhasePetTrigger(ai); } + static Trigger* thaddius_phase_transition(PlayerbotAI* ai) { return new IPThaddiusPhaseTransitionTrigger(ai); } + static Trigger* thaddius_phase_thaddius(PlayerbotAI* ai) { return new IPThaddiusPhaseThaddiusTrigger(ai); } + static Trigger* razuvious_tank(PlayerbotAI* ai) { return new IPRazuviousTankTrigger(ai); } + static Trigger* razuvious_nontank(PlayerbotAI* ai) { return new IPRazuviousNontankTrigger(ai); } + + static Trigger* horseman_attractors(PlayerbotAI* ai) { return new IPHorsemanAttractorsTrigger(ai); } + static Trigger* horseman_except_attractors(PlayerbotAI* ai) { return new IPHorsemanExceptAttractorsTrigger(ai); } + + static Trigger* sapphiron_ground(PlayerbotAI* ai) { return new IPSapphironGroundTrigger(ai); } + static Trigger* sapphiron_flight(PlayerbotAI* ai) { return new IPSapphironFlightTrigger(ai); } + static Trigger* kelthuzad(PlayerbotAI* ai) { return new IPKelthuzadTrigger(ai); } + static Trigger* anubrekhan(PlayerbotAI* ai) { return new IPAnubrekhanTrigger(ai); } + static Trigger* gluth(PlayerbotAI* ai) { return new IPGluthTrigger(ai); } + static Trigger* gluth_main_tank_mortal_wound(PlayerbotAI* ai) { return new IPGluthMainTankMortalWoundTrigger(ai); } + static Trigger* loatheb(PlayerbotAI* ai) { return new IPLoathebTrigger(ai); } +}; + +#endif +#endif \ No newline at end of file diff --git a/src/playerbots/IndividualProgressionStrategyOverride.cpp b/src/playerbots/IndividualProgressionStrategyOverride.cpp new file mode 100644 index 0000000..3a13701 --- /dev/null +++ b/src/playerbots/IndividualProgressionStrategyOverride.cpp @@ -0,0 +1,22 @@ +#ifdef MOD_PLAYERBOTS + +#include "IndividualProgressionStrategyOverride.h" +#include "IndividualProgressionNaxxMultipliers.h" +#include "RaidNaxxMultipliers.h" + +void IPRaidNaxxStrategy::InitMultipliers(std::vector& multipliers) +{ + multipliers.push_back(new GrobbulusMultiplier(botAI)); + multipliers.push_back(new IPHeiganDanceMultiplier(botAI)); + multipliers.push_back(new LoathebGenericMultiplier(botAI)); + multipliers.push_back(new IPThaddiusGenericMultiplier(botAI)); + multipliers.push_back(new IPSapphironGenericMultiplier(botAI)); + multipliers.push_back(new IPInstructorRazuviousGenericMultiplier(botAI)); + multipliers.push_back(new IPKelthuzadGenericMultiplier(botAI)); + multipliers.push_back(new AnubrekhanGenericMultiplier(botAI)); + multipliers.push_back(new FourhorsemanGenericMultiplier(botAI)); + // multipliers.push_back(new GothikGenericMultiplier(botAI)); + multipliers.push_back(new IPGluthGenericMultiplier(botAI)); +} + +#endif diff --git a/src/playerbots/IndividualProgressionStrategyOverride.h b/src/playerbots/IndividualProgressionStrategyOverride.h new file mode 100644 index 0000000..d553b74 --- /dev/null +++ b/src/playerbots/IndividualProgressionStrategyOverride.h @@ -0,0 +1,17 @@ +#ifdef MOD_PLAYERBOTS +#ifndef MOD_INDIVIDUAL_PROGRESSION_STRATEGY_OVERRIDE_H +#define MOD_INDIVIDUAL_PROGRESSION_STRATEGY_OVERRIDE_H + +#include "RaidNaxxStrategy.h" +#include "Multiplier.h" + +class IPRaidNaxxStrategy : public RaidNaxxStrategy +{ +public: + IPRaidNaxxStrategy(PlayerbotAI* ai) : RaidNaxxStrategy(ai) {} + virtual void InitMultipliers(std::vector& multipliers) override; +}; + + +#endif +#endif diff --git a/src/playerbots/raids/Naxx40/IndividualProgressionNaxxActions.cpp b/src/playerbots/raids/Naxx40/IndividualProgressionNaxxActions.cpp new file mode 100644 index 0000000..8b7a3fb --- /dev/null +++ b/src/playerbots/raids/Naxx40/IndividualProgressionNaxxActions.cpp @@ -0,0 +1,143 @@ +#ifdef MOD_PLAYERBOTS + +#include "IndividualProgressionNaxxActions.h" +#include "RaidNaxxActions.h" +#include "IndividualProgressionNaxxAi40.h" + +#include "LastMovementValue.h" +#include "ObjectGuid.h" +#include "PlayerbotAIConfig.h" +#include "Playerbots.h" +#include "RaidNaxxBossHelper.h" +#include "RaidNaxxStrategy.h" +#include "ScriptedCreature.h" +#include "SharedDefines.h" + +bool IPGrobbulusGoBehindAction::Execute(Event event) +{ + Unit* boss = AI_VALUE(Unit*, "boss target"); + if (!boss + || (boss->GetEntry() != 15931 // Default Azerothcore Grobbulus + && boss->GetEntry() != 351003)) // mod-individual-progression Grobbulus + { + return false; + } + // Position* pos = boss->GetPosition(); + float orientation = boss->GetOrientation() + M_PI + delta_angle; + float x = boss->GetPositionX(); + float y = boss->GetPositionY(); + float z = boss->GetPositionZ(); + float rx = x + cos(orientation) * distance; + float ry = y + sin(orientation) * distance; + return MoveTo(bot->GetMapId(), rx, ry, z, false, false, false, false, MovementPriority::MOVEMENT_COMBAT); +} + +uint32 IPGrobbulusRotateAction::GetCurrWaypoint() +{ + Unit* boss = AI_VALUE(Unit*, "boss target"); + if (!boss + || (boss->GetEntry() != 15931 // Default AzerothCore Grobbulus + && boss->GetEntry() != 351003)) // mod-individual-progression Grobbulus + { + return false; + } + auto* ai = boss->GetAI(); + if (!ai) + { + return false; + } + EventMap* eventMap = nullptr; + const char* typeName = typeid(*ai).name(); + if (std::string(typeName).find("boss_grobbulus_40") != std::string::npos) + { + auto* boss_ai = reinterpret_cast(ai); + if (!boss_ai->events.Empty()) + eventMap = &boss_ai->events; + } + else + { + auto* boss_ai = dynamic_cast(ai); + if (!boss_ai || boss_ai->events.Empty()) + { + return false; + } + eventMap = &boss_ai->events; + } + if (!eventMap || eventMap->Empty()) + { + return false; + } + if (eventMap->GetTimer() > 1000000) + { + return false; + } + const uint32 event_time = eventMap->GetNextEventTime(2); // EVENT_DECEPIT_FEVER + return (event_time / 15000) % intervals; +} + + +bool IPHeiganDanceAction::CalculateSafe() +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "heigan the unclean"); + if (!boss || boss->isDead()) + { + return false; + } + auto* ai = boss->GetAI(); + if (!ai) + { + return false; + } + EventMap* eventMap = nullptr; + uint32 curr_phase = 0; + const char* typeName = typeid(*ai).name(); + if (std::string(typeName).find("boss_heigan_40") != std::string::npos) + { + auto* boss_ai = reinterpret_cast(ai); + if (boss_ai) + { + if (!boss_ai->events.Empty()) + eventMap = &boss_ai->events; + if (boss_ai->currentPhase) + curr_phase = boss_ai->currentPhase; + } + else + { + return false; + } + } + else + { + auto* boss_ai = dynamic_cast(ai); + if (!boss_ai || boss_ai->events.Empty()) + { + return false; + } + eventMap = &boss_ai->events; + curr_phase = boss_ai->currentPhase; + } + if (!eventMap || eventMap->Empty()) + { + return false; + } + uint32 curr_timer = eventMap->GetTimer(); + if (curr_timer > 1000000) + { + return false; + } + uint32 curr_erupt = eventMap->GetNextEventTime(3); // EVENT_ERUPT_SECTION + uint32 curr_dance = eventMap->GetNextEventTime(4); // EVENT_SWITCH_PHASE + if ((curr_phase == 0 && curr_dance - curr_timer >= 85000) || (curr_phase == 1 && curr_dance - curr_timer >= 40000)) + { + ResetSafe(); + } + else if (curr_erupt != prev_erupt) + { + NextSafe(); + } + prev_phase = curr_phase; + prev_erupt = curr_erupt; + return true; +} + +#endif diff --git a/src/playerbots/raids/Naxx40/IndividualProgressionNaxxActions.h b/src/playerbots/raids/Naxx40/IndividualProgressionNaxxActions.h new file mode 100644 index 0000000..4a22067 --- /dev/null +++ b/src/playerbots/raids/Naxx40/IndividualProgressionNaxxActions.h @@ -0,0 +1,40 @@ +#ifdef MOD_PLAYERBOTS +#ifndef MOD_INDIVIDUAL_PROGRESSION_NAXXACTIONS_H +#define MOD_INDIVIDUAL_PROGRESSION_NAXXACTIONS_H + +#include "RaidNaxxActions.h" + +class PlayerbotAI; + +class IPGrobbulusGoBehindAction : public GrobbulusGoBehindAction +{ +public: + IPGrobbulusGoBehindAction(PlayerbotAI* ai, float distance = 24.0f, float delta_angle = M_PI / 8) + : GrobbulusGoBehindAction(ai,distance, delta_angle) {} + virtual bool Execute(Event event) override; +}; + + +class IPGrobbulusRotateAction : public GrobbulusRotateAction +{ +public: + IPGrobbulusRotateAction(PlayerbotAI* botAI) + : GrobbulusRotateAction(botAI) + {} + + uint32 GetCurrWaypoint() override; +}; + +class IPHeiganDanceAction : public HeiganDanceAction +{ +public: + IPHeiganDanceAction(PlayerbotAI* ai) : HeiganDanceAction(ai) + {} + +protected: + bool CalculateSafe(); +}; + + +#endif +#endif diff --git a/src/playerbots/raids/Naxx40/IndividualProgressionNaxxAi40.h b/src/playerbots/raids/Naxx40/IndividualProgressionNaxxAi40.h new file mode 100644 index 0000000..2def5e3 --- /dev/null +++ b/src/playerbots/raids/Naxx40/IndividualProgressionNaxxAi40.h @@ -0,0 +1,81 @@ +#ifdef MOD_PLAYERBOTS +#ifndef MOD_INDIVIDUAL_PROGRESSION_NAXXAI_H +#define MOD_INDIVIDUAL_PROGRESSION_NAXXAI_H + +#include "EventMap.h" +#include "CreatureAIImpl.h" +#include "ObjectGuid.h" +#include "ScriptMgr.h" +#include "ScriptedCreature.h" +#include "SpellInfo.h" +#include + +struct BossAiHeigan40 : BossAI +{ + EventMap events; + uint8 currentPhase{}; + uint8 currentSection{}; + bool moveRight{}; + GuidList portedPlayersThisPhase; +}; + +struct BossAiGrobbulus40 : BossAI +{ + EventMap events; + SummonList summons; + uint32 dropSludgeTimer{}; +}; + +struct BossAiGluth40 : BossAI +{ + EventMap events; + SummonList summons; +}; + +struct BossAiThaddius40 : BossAI +{ + EventMap events; + SummonList summons; + uint32 summonTimer{}; + uint32 reviveTimer{}; + uint32 resetTimer{}; + bool ballLightningEnabled; +}; + +struct BossAiRazuvious40 : BossAI +{ + EventMap events; + SummonList summons; +}; + +struct BossAiFourhorsemen40 : BossAI +{ + EventMap events; + uint8 horsemanId; + bool doneFirstShieldWall; +}; + +struct BossAiLoatheb40 : BossAI +{ + uint8 doomCounter; + EventMap events; + SummonList summons; +}; + +struct BossAiSapphiron40 : BossAI +{ + EventMap events; + uint8 iceboltCount{}; + uint32 spawnTimer{}; + GuidList blockList; + ObjectGuid currentTarget; +}; + +struct BossAiKelthuzad40 : BossAI +{ + EventMap events; + SummonList summons; +}; + +#endif +#endif diff --git a/src/playerbots/raids/Naxx40/IndividualProgressionNaxxBossHelper.h b/src/playerbots/raids/Naxx40/IndividualProgressionNaxxBossHelper.h new file mode 100644 index 0000000..a731b29 --- /dev/null +++ b/src/playerbots/raids/Naxx40/IndividualProgressionNaxxBossHelper.h @@ -0,0 +1,470 @@ +#ifdef MOD_PLAYERBOTS + +#include "AiObject.h" +#include "AiObjectContext.h" +#include "EventMap.h" +#include "Log.h" +#include "NamedObjectContext.h" +#include "ObjectGuid.h" +#include "Player.h" +#include "PlayerbotAI.h" +#include "Playerbots.h" +#include "RaidNaxxScripts.h" +#include "ScriptedCreature.h" +#include "SharedDefines.h" + +#include "RaidNaxxBossHelper.h" +#include "IndividualProgressionNaxxAi40.h" + + +template +class IPGenericBossHelper : public AiObject +{ +public: + IPGenericBossHelper(PlayerbotAI* botAI, std::string name) : AiObject(botAI), _name(name) {} + virtual bool UpdateBossAI() + { + if (!bot->IsInCombat()) + { + _unit = nullptr; + } + if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive())) + { + _unit = nullptr; + } + if (!_unit) + { + _unit = AI_VALUE2(Unit*, "find target", _name); + if (!_unit) + { + return false; + } + _target = _unit->ToCreature(); + if (!_target) + { + return false; + } + auto* ai = _target->GetAI(); + if (!ai) + { + return false; + } + const char* typeName = typeid(*ai).name(); + if (std::string(typeName).find("_40") != std::string::npos) + { + auto* boss_ai = reinterpret_cast(ai); + if (boss_ai) + { + if (!boss_ai->events.Empty()) + _event_map = &boss_ai->events; + } + else + { + return false; + } + } + else + { + auto* boss_ai = dynamic_cast(ai); + if (!boss_ai || boss_ai->events.Empty()) + { + return false; + } + _event_map = &boss_ai->events; + } + if (!_event_map) + { + return false; + } + } + if (!_event_map || _event_map->Empty()) + { + return false; + } + _timer = _event_map->GetTimer(); + if (_timer > 1000000) + { + return false; + } + return true; + } + virtual void Reset() + { + _unit = nullptr; + _target = nullptr; + _event_map = nullptr; + _timer = 0; + } + +protected: + std::string _name; + Unit* _unit = nullptr; + Creature* _target = nullptr; + EventMap* _event_map = nullptr; + uint32 _timer = 0; +}; + +class IPKelthuzadBossHelper : public IPGenericBossHelper +{ +public: + IPKelthuzadBossHelper(PlayerbotAI* botAI) : IPGenericBossHelper(botAI, "kel'thuzad") {} + const std::pair center = {3716.19f, -5106.58f}; + const std::pair tank_pos = {3709.19f, -5104.86f}; + const std::pair assist_tank_pos = {3746.05f, -5112.74f}; + bool IsPhaseOne() { return _event_map->GetNextEventTime(4) != 0; } // EVENT_PHASE_2 + bool IsPhaseTwo() { return !IsPhaseOne(); } + Unit* GetAnyShadowFissure() + { + Unit* shadow_fissure = nullptr; + GuidVector units = *context->GetValue("nearest triggers"); + for (auto i = units.begin(); i != units.end(); i++) + { + Unit* unit = botAI->GetUnit(*i); + if (!unit) + continue; + if (botAI->EqualLowercaseName(unit->GetName(), "shadow fissure")) + { + shadow_fissure = unit; + } + } + return shadow_fissure; + } +}; + +class IPRazuviousBossHelper : public IPGenericBossHelper +{ +public: + IPRazuviousBossHelper(PlayerbotAI* botAI) : IPGenericBossHelper(botAI, "instructor razuvious") {} +}; + +class IPSapphironBossHelper : public IPGenericBossHelper +{ +public: + const std::pair mainTankPos = {3512.07f, -5274.06f}; + const std::pair center = {3517.31f, -5253.74f}; + const float GENERIC_HEIGHT = 137.29f; + IPSapphironBossHelper(PlayerbotAI* botAI) : IPGenericBossHelper(botAI, "sapphiron") {} + bool UpdateBossAI() override + { + if (!IPGenericBossHelper::UpdateBossAI()) + { + return false; + } + uint32 nextEventGround = _event_map->GetNextEventTime(13); // EVENT_GROUND + if (nextEventGround && nextEventGround != lastEventGround) + lastEventGround = nextEventGround; + return true; + } + bool IsPhaseGround() { return _target->GetReactState() == REACT_AGGRESSIVE; } + bool IsPhaseFlight() { return !IsPhaseGround(); } + bool JustLanded() + { + return (_event_map->GetNextEventTime(6) - _timer) >= EVENT_FLIGHT_INTERVAL - POSITION_TIME_AFTER_LANDED; // EVENT_FLIGHT_START + } + bool WaitForExplosion() { return _event_map->GetNextEventTime(10); } // EVENT_FLIGHT_SPELL_EXPLOSION + bool FindPosToAvoidChill(std::vector& dest) + { + Aura* aura = botAI->GetAura("chill", bot); + if (!aura) + { + return false; + } + DynamicObject* dyn_obj = aura->GetDynobjOwner(); + if (!dyn_obj) + { + return false; + } + Unit* currentTarget = AI_VALUE(Unit*, "current target"); + float angle = 0; + uint32 index = botAI->GetGroupSlotIndex(bot); + if (currentTarget) + { + if (botAI->IsRanged(bot)) + { + if (bot->GetExactDist2d(currentTarget) <= 45.0f) + { + angle = bot->GetAngle(dyn_obj) - M_PI + (rand_norm() - 0.5) * M_PI / 2; + } + else + { + if (index % 2 == 0) + { + angle = bot->GetAngle(currentTarget) + M_PI / 2; + } + else + { + angle = bot->GetAngle(currentTarget) - M_PI / 2; + } + } + } + else + { + if (index % 3 == 0) + { + angle = bot->GetAngle(currentTarget); + } + else if (index % 3 == 1) + { + angle = bot->GetAngle(currentTarget) + M_PI / 2; + } + else + { + angle = bot->GetAngle(currentTarget) - M_PI / 2; + } + } + } + else + { + angle = bot->GetAngle(dyn_obj) - M_PI + (rand_norm() - 0.5) * M_PI / 2; + } + dest = {bot->GetPositionX() + cos(angle) * 5.0f, bot->GetPositionY() + sin(angle) * 5.0f, bot->GetPositionZ()}; + return true; + } + +private: + const uint32 POSITION_TIME_AFTER_LANDED = 5000; + const uint32 EVENT_FLIGHT_INTERVAL = 45000; + uint32 lastEventGround = 0; +}; + +class IPGluthBossHelper : public IPGenericBossHelper +{ +public: + const std::pair mainTankPos25 = {3331.48f, -3109.06f}; + const std::pair mainTankPos10 = {3278.29f, -3162.06f}; + const std::pair beforeDecimatePos = {3267.34f, -3175.68f}; + const std::pair leftSlowDownPos = {3290.68f, -3141.65f}; + const std::pair rightSlowDownPos = {3300.78f, -3151.98f}; + const std::pair rangedPos = {3301.45f, -3139.29f}; + const std::pair healPos = {3303.09f, -3135.24f}; + + const float decimatedZombiePct = 10.0f; + IPGluthBossHelper(PlayerbotAI* botAI) : IPGenericBossHelper(botAI, "gluth") {} + bool BeforeDecimate() + { + uint32 decimate = _event_map->GetNextEventTime(3); // EVENT_DECIMATE + return decimate && decimate - _timer <= 3000; + } + bool JustStartCombat() { return _timer < 10000; } +}; + +class IPLoathebBossHelper : public IPGenericBossHelper +{ +public: + const std::pair mainTankPos = {2877.57f, -3967.00f}; + const std::pair rangePos = {2896.96f, -3980.61f}; + IPLoathebBossHelper(PlayerbotAI* botAI) : IPGenericBossHelper(botAI, "loatheb") {} +}; + +class IPFourhorsemanBossHelper : public IPGenericBossHelper +{ +public: + const float posZ = 241.27f; + const std::pair attractPos[2] = {{2502.03f, -2910.90f}, + {2484.61f, -2947.07f}}; // left (sir zeliek), right (lady blaumeux) + IPFourhorsemanBossHelper(PlayerbotAI* botAI) : IPGenericBossHelper(botAI, "sir zeliek") {} + bool UpdateBossAI() override + { + if (!IPGenericBossHelper::UpdateBossAI()) + { + return false; + } + if (!bot->IsInCombat()) + { + Reset(); + } + sir = _unit; + lady = AI_VALUE2(Unit*, "find target", "lady blaumeux"); + if (!lady) + { + return true; + } + auto* ladyBossAi = lady->GetAI(); + if (!ladyBossAi) + { + return true; + } + const char* typeName = typeid(*ladyBossAi).name(); + if (std::string(typeName).find("boss_four_horsemen_40") != std::string::npos) + { + auto* ladyRealAi = reinterpret_cast(lady->GetAI()); + if (ladyRealAi) + { + if (!ladyRealAi->events.Empty()) + ladyEvent = &ladyRealAi->events; + } + else + { + return true; + } + } + else + { + auto* ladyRealAi = dynamic_cast(lady->GetAI()); + if (!ladyRealAi || ladyRealAi->events.Empty()) + { + return true; + } + ladyEvent = &ladyRealAi->events; + } + if (!ladyEvent || ladyEvent->Empty()) + { + return true; + } + if (ladyEvent->GetTimer() > 1000000) + { + return true; + } + const uint32 voidZone = ladyEvent->GetNextEventTime(3); // EVENT_SECONDARY_SPELL + if (voidZone && lastEventVoidZone != voidZone) + { + voidZoneCounter++; + voidZoneCounter %= 8; + lastEventVoidZone = voidZone; + } + return true; + } + void Reset() override + { + IPGenericBossHelper::Reset(); + sir = nullptr; + lady = nullptr; + ladyEvent = nullptr; + lastEventVoidZone = 0; + voidZoneCounter = 0; + posToGo = 0; + } + bool IsAttracter(Player* bot) + { + Difficulty diff = bot->GetRaidDifficulty(); + if (diff == RAID_DIFFICULTY_25MAN_NORMAL) + { + return botAI->IsRangedDpsAssistantOfIndex(bot, 0) || botAI->IsHealAssistantOfIndex(bot, 0) || + botAI->IsHealAssistantOfIndex(bot, 1) || botAI->IsHealAssistantOfIndex(bot, 2); + } + return botAI->IsRangedDpsAssistantOfIndex(bot, 0) || botAI->IsHealAssistantOfIndex(bot, 0); + } + void CalculatePosToGo(Player* bot) + { + bool raid25 = bot->GetRaidDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL; + if (!lady) + { + posToGo = 0; + } + else + { + // Interval: 24s - 15s - 15s - ... + posToGo = !(_timer <= 9000 || ((_timer - 9000) / 67500) % 2 == 0); + if (botAI->IsRangedDpsAssistantOfIndex(bot, 0) || (raid25 && botAI->IsHealAssistantOfIndex(bot, 1))) + { + posToGo = 1 - posToGo; + } + } + } + std::pair CurrentAttractPos() + { + float posX = attractPos[posToGo].first, posY = attractPos[posToGo].second; + if (posToGo == 1) + { + float offset_x; + float offset_y; + if (voidZoneCounter < 4) + { + offset_x = voidZoneCounter * (-4.5f); + offset_y = voidZoneCounter * (4.5f); + } + if (voidZoneCounter >= 4) + { + offset_x = (7 - voidZoneCounter) * (-4.5f); + offset_y = (7 - voidZoneCounter) * (4.5f); + offset_x += 4.5f; + offset_y += 4.5f; + } + posX += offset_x; + posY += offset_y; + } + return {posX, posY}; + } + Unit* CurrentAttackTarget() + { + if (posToGo == 0) + { + return sir; + } + return lady; + } + +protected: + Unit* sir = nullptr; + Unit* lady = nullptr; + EventMap* ladyEvent = nullptr; + uint32 lastEventVoidZone = 0; + uint32 voidZoneCounter = 0; + int posToGo = 0; +}; + +class IPThaddiusBossHelper : public IPGenericBossHelper +{ +public: + const std::pair tankPosFeugen = {3522.94f, -3002.60f}; + const std::pair tankPosStalagg = {3436.14f, -2919.98f}; + const std::pair rangedPosFeugen = {3500.45f, -2997.92f}; + const std::pair rangedPosStalagg = {3441.01f, -2942.04f}; + const float tankPosZ = 312.61f; + IPThaddiusBossHelper(PlayerbotAI* botAI) : IPGenericBossHelper(botAI, "thaddius") {} + bool UpdateBossAI() override + { + if (!IPGenericBossHelper::UpdateBossAI()) + { + return false; + } + feugen = AI_VALUE2(Unit*, "find target", "feugen"); + stalagg = AI_VALUE2(Unit*, "find target", "stalagg"); + return true; + } + bool IsPhasePet() { return (feugen && feugen->IsAlive()) || (stalagg && stalagg->IsAlive()); } + bool IsPhaseTransition() + { + if (IsPhasePet()) + { + return false; + } + return _unit->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE); + } + bool IsPhaseThaddius() { return !IsPhasePet() && !IsPhaseTransition(); } + Unit* GetNearestPet() + { + Unit* unit = nullptr; + if (feugen && feugen->IsAlive()) + { + unit = feugen; + } + if (stalagg && stalagg->IsAlive() && (!feugen || bot->GetDistance(stalagg) < bot->GetDistance(feugen))) + { + unit = stalagg; + } + return unit; + } + std::pair PetPhaseGetPosForTank() + { + if (GetNearestPet() == feugen) + { + return tankPosFeugen; + } + return tankPosStalagg; + } + std::pair PetPhaseGetPosForRanged() + { + if (GetNearestPet() == feugen) + { + return rangedPosFeugen; + } + return rangedPosStalagg; + } + +protected: + Unit* feugen = nullptr; + Unit* stalagg = nullptr; +}; + +#endif diff --git a/src/playerbots/raids/Naxx40/IndividualProgressionNaxxMultipliers.cpp b/src/playerbots/raids/Naxx40/IndividualProgressionNaxxMultipliers.cpp new file mode 100644 index 0000000..7d56a92 --- /dev/null +++ b/src/playerbots/raids/Naxx40/IndividualProgressionNaxxMultipliers.cpp @@ -0,0 +1,255 @@ +#ifdef MOD_PLAYERBOTS + +#include "IndividualProgressionNaxxMultipliers.h" +#include "IndividualProgressionNaxxAi40.h" +#include "ChooseTargetActions.h" +#include "DKActions.h" +#include "DruidActions.h" +#include "DruidBearActions.h" +#include "FollowActions.h" +#include "GenericActions.h" +#include "GenericSpellActions.h" +#include "HunterActions.h" +#include "MageActions.h" +#include "MovementActions.h" +#include "PaladinActions.h" +#include "PriestActions.h" +#include "RaidNaxxActions.h" +#include "ReachTargetActions.h" +#include "RogueActions.h" +#include "ScriptedCreature.h" +#include "ShamanActions.h" +#include "UseMeetingStoneAction.h" +#include "WarriorActions.h" +#include "PlayerbotAI.h" + + +float IPHeiganDanceMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "heigan the unclean"); + if (!boss || boss->isDead()) + { + return 1.0f; + } + auto* ai = boss->GetAI(); + if (!ai) + { + return 1.0f; + } + EventMap* eventMap = nullptr; + uint32 curr_phase = 0; + const char* typeName = typeid(*ai).name(); + if (std::string(typeName).find("boss_heigan_40") != std::string::npos) + { + auto* boss_ai = reinterpret_cast(ai); + if (boss_ai) + { + if (!boss_ai->events.Empty()) + eventMap = &boss_ai->events; + if (boss_ai->currentPhase) + curr_phase = boss_ai->currentPhase; + } + else + { + return false; + } + } + else + { + auto* boss_ai = dynamic_cast(ai); + if (!boss_ai || boss_ai->events.Empty()) + { + return 1.0f; + } + eventMap = &boss_ai->events; + curr_phase = boss_ai->currentPhase; + } + if (!eventMap || eventMap->Empty()) + { + return 1.0f; + } + uint32 curr_timer = eventMap->GetTimer(); + if (curr_timer > 1000000) + { + return 1.0f; + } + uint32 curr_dance = eventMap->GetNextEventTime(4); // EVENT_SWITCH_PHASE + uint32 curr_erupt = eventMap->GetNextEventTime(3); // EVENT_ERUPT_SECTION + if (dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) ) + { + return 0.0f; + } + if (curr_phase != 1 && (int32)curr_dance - curr_timer >= 3000) + { + return 1.0f; + } + if (dynamic_cast(action) || dynamic_cast(action)) + { + return 1.0f; + } + if (dynamic_cast(action) && !dynamic_cast(action)) + { + CastSpellAction* spellAction = dynamic_cast(action); + uint32 spellId = AI_VALUE2(uint32, "spell id", spellAction->getSpell()); + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); + if (!spellInfo) + { + return 0.0f; + } + uint32 castTime = spellInfo->CalcCastTime(); + if (castTime == 0 && !spellInfo->IsChanneled()) + { + return 1.0f; + } + } + return 0.0f; +} + +float IPThaddiusGenericMultiplier::GetValue(Action* action) +{ + if (!helper.UpdateBossAI()) + { + return 1.0f; + } + if (dynamic_cast(action)) + return 0.0f; + // pet phase + if (helper.IsPhasePet() && + (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action))) + { + return 0.0f; + } + // die at the same time + Unit* target = AI_VALUE(Unit*, "current target"); + Unit* feugen = AI_VALUE2(Unit*, "find target", "feugen"); + Unit* stalagg = AI_VALUE2(Unit*, "find target", "stalagg"); + if (helper.IsPhasePet() && target && feugen && stalagg && target->GetHealthPct() <= 40 && + (feugen->GetHealthPct() >= target->GetHealthPct() + 3 || stalagg->GetHealthPct() >= target->GetHealthPct() + 3)) + { + if (dynamic_cast(action) && !dynamic_cast(action)) + { + return 0.0f; + } + } + // magnetic pull + // uint32 curr_timer = eventMap->GetTimer(); + // // if (curr_phase == 2 && bot->GetPositionZ() > 312.5f && dynamic_cast(action)) { + // if (curr_phase == 2 && (curr_timer % 20000 >= 18000 || curr_timer % 20000 <= 2000) && + // dynamic_cast(action)) { + // // MotionMaster *mm = bot->GetMotionMaster(); + // // mm->Clear(); + // return 0.0f; + // } + // thaddius phase + // if (curr_phase == 8 && dynamic_cast(action)) { + // return 0.0f; + // } + return 1.0f; +} + +float IPSapphironGenericMultiplier::GetValue(Action* action) +{ + if (!helper.UpdateBossAI()) + { + return 1.0f; + } + if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action)) + { + return 0.0f; + } + return 1.0f; +} + +float IPInstructorRazuviousGenericMultiplier::GetValue(Action* action) +{ + if (!helper.UpdateBossAI()) + { + return 1.0f; + } + context->GetValue("neglect threat")->Set(true); + if (botAI->GetState() == BOT_STATE_COMBAT && + (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action))) + { + return 0.0f; + } + return 1.0f; +} + +float IPKelthuzadGenericMultiplier::GetValue(Action* action) +{ + if (!helper.UpdateBossAI()) + { + return 1.0f; + } + if ((dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action))) + { + return 0.0f; + } + if (helper.IsPhaseOne()) + { + if (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 (helper.IsPhaseTwo()) + { + if (dynamic_cast(action) || dynamic_cast(action)) + { + return 0.0f; + } + } + return 1.0f; +} + +float IPGluthGenericMultiplier::GetValue(Action* action) +{ + if (!helper.UpdateBossAI()) + { + return 1.0f; + } + if ((dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action))) + { + return 0.0f; + } + + if (botAI->IsMainTank(bot)) + { + Aura* aura = botAI->GetAura("mortal wound", bot, false, true); + if (aura && aura->GetStackAmount() >= 5) + { + if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action)) + { + return 0.0f; + } + } + } + if (dynamic_cast(action)) + { + Unit* target = AI_VALUE(Unit*, "current target"); + if (target && !target->isDead() + && (target->GetEntry() == 16360 // Default Azerothcore Zombie Chow + || target->GetEntry() == 351069)) // mod-individual-progression Zombie Chow + { + return 0.0f; + } + } + return 1.0f; +} + +#endif diff --git a/src/playerbots/raids/Naxx40/IndividualProgressionNaxxMultipliers.h b/src/playerbots/raids/Naxx40/IndividualProgressionNaxxMultipliers.h new file mode 100644 index 0000000..58b278a --- /dev/null +++ b/src/playerbots/raids/Naxx40/IndividualProgressionNaxxMultipliers.h @@ -0,0 +1,71 @@ + +#ifdef MOD_PLAYERBOTS +#ifndef MOD_INDIVIDUAL_PROGRESSION_NAXXMULTIPLIERS_H +#define MOD_INDIVIDUAL_PROGRESSION_NAXXMULTIPLIERS_H + +#include "Multiplier.h" +#include "IndividualProgressionNaxxBossHelper.h" + +class IPHeiganDanceMultiplier : public Multiplier +{ +public: + IPHeiganDanceMultiplier(PlayerbotAI* ai) : Multiplier(ai, "helgan dance") {} + + virtual float GetValue(Action* action); +}; + +class IPThaddiusGenericMultiplier : public Multiplier +{ +public: + IPThaddiusGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "thaddius generic"), helper(ai) {} + + virtual float GetValue(Action* action); + +private: + IPThaddiusBossHelper helper; +}; + +class IPSapphironGenericMultiplier : public Multiplier +{ +public: + IPSapphironGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "sapphiron generic"), helper(ai) {} + + virtual float GetValue(Action* action); + +private: + IPSapphironBossHelper helper; +}; + +class IPInstructorRazuviousGenericMultiplier : public Multiplier +{ +public: + IPInstructorRazuviousGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "instructor razuvious generic"), helper(ai) {} + + virtual float GetValue(Action* action); + +private: + IPRazuviousBossHelper helper; +}; + +class IPKelthuzadGenericMultiplier : public Multiplier +{ +public: + IPKelthuzadGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "kelthuzad generic"), helper(ai) {} + virtual float GetValue(Action* action); + +private: + IPKelthuzadBossHelper helper; +}; + +class IPGluthGenericMultiplier : public Multiplier +{ +public: + IPGluthGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "gluth generic"), helper(ai) {} + float GetValue(Action* action) override; + +private: + IPGluthBossHelper helper; +}; + +#endif +#endif diff --git a/src/playerbots/raids/Naxx40/IndividualProgressionNaxxTriggers.cpp b/src/playerbots/raids/Naxx40/IndividualProgressionNaxxTriggers.cpp new file mode 100644 index 0000000..8829cf5 --- /dev/null +++ b/src/playerbots/raids/Naxx40/IndividualProgressionNaxxTriggers.cpp @@ -0,0 +1,156 @@ +#ifdef MOD_PLAYERBOTS +#include "IndividualProgressionNaxxTriggers.h" + +#include "EventMap.h" +#include "Playerbots.h" +#include "ScriptedCreature.h" +#include "Trigger.h" + + +template +bool BossEventTrigger::IsActive() +{ + Unit* boss = AI_VALUE(Unit*, "boss target"); + if (!boss + || (boss->GetEntry() != boss_entry // Default Azerothcore from BossEventTrigger instanciation + && (boss_entry_secondary == 0 || boss->GetEntry() != boss_entry_secondary))) // If an other boss version exists from an external module + { + return false; + } + T* ai = dynamic_cast(boss->GetAI()); + if(!ai) + { + return false; + } + EventMap* eventMap = &ai->events; + if (!eventMap) + { + return false; + } + const uint32 event_time = eventMap->GetNextEventTime(event_id); + if (event_time != last_event_time) + { + last_event_time = event_time; + return true; + } + return false; +} + +bool IPGrobbulusCloudTrigger::IsActive() +{ + Unit* boss = AI_VALUE(Unit*, "boss target"); + if (!boss + || (boss->GetEntry() != boss_entry // Default Azerothcore Grobbulus (15931) + && boss->GetEntry() != boss_entry_secondary)) // mod-individual-progression Grobbulus (351003) + { + return false; + } + if (!botAI->IsMainTank(bot)) + { + return false; + } + // bot->Yell("has aggro on " + boss->GetName() + " : " + to_string(AI_VALUE2(bool, "has aggro", "boss target")), + // LANG_UNIVERSAL); + return AI_VALUE2(bool, "has aggro", "boss target"); +} + +bool IPHeiganMeleeTrigger::IsActive() +{ + Unit* heigan = AI_VALUE2(Unit*, "find target", "heigan the unclean"); + if (!heigan || heigan->isDead()) + { + return false; + } + return !botAI->IsRanged(bot); +} + +bool IPHeiganRangedTrigger::IsActive() +{ + Unit* heigan = AI_VALUE2(Unit*, "find target", "heigan the unclean"); + if (!heigan || heigan->isDead()) + { + return false; + } + return botAI->IsRanged(bot); +} + +bool IPRazuviousTankTrigger::IsActive() +{ + Difficulty diff = bot->GetRaidDifficulty(); + if (diff == RAID_DIFFICULTY_10MAN_NORMAL) + { + return helper.UpdateBossAI() && botAI->IsTank(bot); + } + return helper.UpdateBossAI() && bot->getClass() == CLASS_PRIEST; +} + +bool IPRazuviousNontankTrigger::IsActive() +{ + Difficulty diff = bot->GetRaidDifficulty(); + if (diff == RAID_DIFFICULTY_10MAN_NORMAL) + { + return helper.UpdateBossAI() && !(botAI->IsTank(bot)); + } + return helper.UpdateBossAI() && !(bot->getClass() == CLASS_PRIEST); +} + + +bool IPGluthTrigger::IsActive() { return helper.UpdateBossAI(); } + +bool IPGluthMainTankMortalWoundTrigger::IsActive() +{ + if (!helper.UpdateBossAI()) + { + return false; + } + if (!botAI->IsAssistTankOfIndex(bot, 0)) + { + return false; + } + Unit* mt = AI_VALUE(Unit*, "main tank"); + if (!mt) + { + return false; + } + Aura* aura = botAI->GetAura("mortal wound", mt, false, true); + if (!aura || aura->GetStackAmount() < 5) + { + return false; + } + return true; +} + +bool IPKelthuzadTrigger::IsActive() { return helper.UpdateBossAI(); } + +bool IPLoathebTrigger::IsActive() { return helper.UpdateBossAI(); } + +bool IPThaddiusPhasePetTrigger::IsActive() +{ + if (!helper.UpdateBossAI()) + { + return false; + } + return helper.IsPhasePet(); +} + +bool IPThaddiusPhaseTransitionTrigger::IsActive() +{ + if (!helper.UpdateBossAI()) + { + return false; + } + return helper.IsPhaseTransition(); +} + +bool IPThaddiusPhaseThaddiusTrigger::IsActive() +{ + if (!helper.UpdateBossAI()) + { + return false; + } + return helper.IsPhaseThaddius(); +} + +template bool BossEventTrigger::IsActive(); + +#endif diff --git a/src/playerbots/raids/Naxx40/IndividualProgressionNaxxTriggers.h b/src/playerbots/raids/Naxx40/IndividualProgressionNaxxTriggers.h new file mode 100644 index 0000000..0ae2229 --- /dev/null +++ b/src/playerbots/raids/Naxx40/IndividualProgressionNaxxTriggers.h @@ -0,0 +1,183 @@ +#ifdef MOD_PLAYERBOTS +#ifndef _PLAYERBOT_RAIDNAXXTRIGGERS_H +#define _PLAYERBOT_RAIDNAXXTRIGGERS_H + +#include "EventMap.h" +#include "GenericTriggers.h" +#include "PlayerbotAIConfig.h" +#include "IndividualProgressionNaxxBossHelper.h" +#include "RaidNaxxScripts.h" +#include "Trigger.h" + +template +class BossEventTrigger : public Trigger +{ +public: + BossEventTrigger(PlayerbotAI* ai, uint32 event_id, uint32 boss_entry, uint32 boss_entry_secondary = 0, std::string name = "boss event") + : Trigger(ai, name, 1) + { + this->event_id = event_id; + this->boss_entry = boss_entry; + this->boss_entry_secondary = boss_entry_secondary; + this->last_event_time = -1; + } + virtual bool IsActive(); + +protected: + uint32 event_id, boss_entry, boss_entry_secondary, last_event_time; +}; + +class IPGrobbulusCloudTrigger : public BossEventTrigger +{ +public: + IPGrobbulusCloudTrigger(PlayerbotAI* ai) : BossEventTrigger(ai, 2, 15931, 351003, "grobbulus cloud event") {} + virtual bool IsActive(); +}; + +class IPHeiganMeleeTrigger : public Trigger +{ +public: + IPHeiganMeleeTrigger(PlayerbotAI* ai) : Trigger(ai, "heigan melee") {} + virtual bool IsActive(); +}; + +class IPHeiganRangedTrigger : public Trigger +{ +public: + IPHeiganRangedTrigger(PlayerbotAI* ai) : Trigger(ai, "heigan ranged") {} + bool IsActive() override; +}; + +class IPRazuviousTankTrigger : public Trigger +{ +public: + IPRazuviousTankTrigger(PlayerbotAI* ai) : Trigger(ai, "instructor razuvious tank"), helper(ai) {} + bool IsActive() override; + +private: + IPRazuviousBossHelper helper; +}; + +class IPRazuviousNontankTrigger : public Trigger +{ +public: + IPRazuviousNontankTrigger(PlayerbotAI* ai) : Trigger(ai, "instructor razuvious non-tank"), helper(ai) {} + bool IsActive() override; + +private: + IPRazuviousBossHelper helper; +}; + +class IPKelthuzadTrigger : public Trigger +{ +public: + IPKelthuzadTrigger(PlayerbotAI* ai) : Trigger(ai, "kel'thuzad trigger"), helper(ai) {} + bool IsActive() override; + +private: + IPKelthuzadBossHelper helper; +}; + +class IPThaddiusPhasePetTrigger : public Trigger +{ +public: + IPThaddiusPhasePetTrigger(PlayerbotAI* ai) : Trigger(ai, "thaddius phase pet"), helper(ai) {} + bool IsActive() override; + +private: + IPThaddiusBossHelper helper; +}; + +class IPThaddiusPhaseTransitionTrigger : public Trigger +{ +public: + IPThaddiusPhaseTransitionTrigger(PlayerbotAI* ai) : Trigger(ai, "thaddius phase transition"), helper(ai) {} + bool IsActive() override; + +private: + IPThaddiusBossHelper helper; +}; + +class IPThaddiusPhaseThaddiusTrigger : public Trigger +{ +public: + IPThaddiusPhaseThaddiusTrigger(PlayerbotAI* ai) : Trigger(ai, "thaddius phase thaddius"), helper(ai) {} + bool IsActive() override; + +private: + IPThaddiusBossHelper helper; +}; + +class IPHorsemanAttractorsTrigger : public Trigger +{ +public: + IPHorsemanAttractorsTrigger(PlayerbotAI* ai) : Trigger(ai, "fourhorsemen attractors"), helper(ai) {} + bool IsActive() override; + +private: + IPFourhorsemanBossHelper helper; +}; + +class IPHorsemanExceptAttractorsTrigger : public Trigger +{ +public: + IPHorsemanExceptAttractorsTrigger(PlayerbotAI* ai) : Trigger(ai, "fourhorsemen except attractors"), helper(ai) {} + bool IsActive() override; + +private: + IPFourhorsemanBossHelper helper; +}; + +class IPSapphironGroundTrigger : public Trigger +{ +public: + IPSapphironGroundTrigger(PlayerbotAI* ai) : Trigger(ai, "sapphiron ground"), helper(ai) {} + bool IsActive() override; + +private: + IPSapphironBossHelper helper; +}; + + +class IPSapphironFlightTrigger : public Trigger +{ +public: + IPSapphironFlightTrigger(PlayerbotAI* ai) : Trigger(ai, "sapphiron flight"), helper(ai) {} + bool IsActive() override; + +private: + IPSapphironBossHelper helper; +}; + +class IPGluthTrigger : public Trigger +{ +public: + IPGluthTrigger(PlayerbotAI* ai) : Trigger(ai, "gluth trigger"), helper(ai) {} + bool IsActive() override; + +private: + IPGluthBossHelper helper; +}; + +class IPGluthMainTankMortalWoundTrigger : public Trigger +{ +public: + IPGluthMainTankMortalWoundTrigger(PlayerbotAI* ai) : Trigger(ai, "gluth main tank mortal wound trigger"), helper(ai) {} + bool IsActive() override; + +private: + IPGluthBossHelper helper; +}; + +class IPLoathebTrigger : public Trigger +{ +public: + IPLoathebTrigger(PlayerbotAI* ai) : Trigger(ai, "loatheb"), helper(ai) {} + bool IsActive() override; + +private: + IPLoathebBossHelper helper; +}; + +#endif +#endif