Feat. Playerbots naxx 40 Integration (#749)

Override default Naxx playerbots strategy to include Naxx40 bosses.
This commit is contained in:
Keleborn
2025-10-24 13:15:14 -07:00
committed by GitHub
parent 0801ffdd91
commit 6c8923eeac
12 changed files with 1525 additions and 0 deletions

View File

@@ -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

View File

@@ -0,0 +1,69 @@
// /*
// * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, 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

View File

@@ -0,0 +1,22 @@
#ifdef MOD_PLAYERBOTS
#include "IndividualProgressionStrategyOverride.h"
#include "IndividualProgressionNaxxMultipliers.h"
#include "RaidNaxxMultipliers.h"
void IPRaidNaxxStrategy::InitMultipliers(std::vector<Multiplier*>& 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

View File

@@ -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<Multiplier*>& multipliers) override;
};
#endif
#endif

View File

@@ -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<BossAiGrobbulus40*>(ai);
if (!boss_ai->events.Empty())
eventMap = &boss_ai->events;
}
else
{
auto* boss_ai = dynamic_cast<Grobbulus::boss_grobbulus::boss_grobbulusAI*>(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<BossAiHeigan40*>(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<Heigan::boss_heigan::boss_heiganAI*>(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

View File

@@ -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

View File

@@ -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 <list>
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

View File

@@ -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 BossAiType, class Boss40AiType>
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<Boss40AiType*>(ai);
if (boss_ai)
{
if (!boss_ai->events.Empty())
_event_map = &boss_ai->events;
}
else
{
return false;
}
}
else
{
auto* boss_ai = dynamic_cast<BossAiType*>(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<Kelthuzad::boss_kelthuzad::boss_kelthuzadAI, BossAiKelthuzad40>
{
public:
IPKelthuzadBossHelper(PlayerbotAI* botAI) : IPGenericBossHelper(botAI, "kel'thuzad") {}
const std::pair<float, float> center = {3716.19f, -5106.58f};
const std::pair<float, float> tank_pos = {3709.19f, -5104.86f};
const std::pair<float, float> 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<GuidVector>("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<Razuvious::boss_razuvious::boss_razuviousAI, BossAiRazuvious40>
{
public:
IPRazuviousBossHelper(PlayerbotAI* botAI) : IPGenericBossHelper(botAI, "instructor razuvious") {}
};
class IPSapphironBossHelper : public IPGenericBossHelper<Sapphiron::boss_sapphiron::boss_sapphironAI, BossAiSapphiron40>
{
public:
const std::pair<float, float> mainTankPos = {3512.07f, -5274.06f};
const std::pair<float, float> 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<float>& 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<Gluth::boss_gluth::boss_gluthAI, BossAiGluth40>
{
public:
const std::pair<float, float> mainTankPos25 = {3331.48f, -3109.06f};
const std::pair<float, float> mainTankPos10 = {3278.29f, -3162.06f};
const std::pair<float, float> beforeDecimatePos = {3267.34f, -3175.68f};
const std::pair<float, float> leftSlowDownPos = {3290.68f, -3141.65f};
const std::pair<float, float> rightSlowDownPos = {3300.78f, -3151.98f};
const std::pair<float, float> rangedPos = {3301.45f, -3139.29f};
const std::pair<float, float> 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<Loatheb::boss_loatheb::boss_loathebAI, BossAiLoatheb40>
{
public:
const std::pair<float, float> mainTankPos = {2877.57f, -3967.00f};
const std::pair<float, float> rangePos = {2896.96f, -3980.61f};
IPLoathebBossHelper(PlayerbotAI* botAI) : IPGenericBossHelper(botAI, "loatheb") {}
};
class IPFourhorsemanBossHelper : public IPGenericBossHelper<FourHorsemen::boss_four_horsemen::boss_four_horsemenAI, BossAiFourhorsemen40>
{
public:
const float posZ = 241.27f;
const std::pair<float, float> 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<BossAiFourhorsemen40*>(lady->GetAI());
if (ladyRealAi)
{
if (!ladyRealAi->events.Empty())
ladyEvent = &ladyRealAi->events;
}
else
{
return true;
}
}
else
{
auto* ladyRealAi = dynamic_cast<FourHorsemen::boss_four_horsemen::boss_four_horsemenAI*>(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<float, float> 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<Thaddius::boss_thaddius::boss_thaddiusAI, BossAiThaddius40>
{
public:
const std::pair<float, float> tankPosFeugen = {3522.94f, -3002.60f};
const std::pair<float, float> tankPosStalagg = {3436.14f, -2919.98f};
const std::pair<float, float> rangedPosFeugen = {3500.45f, -2997.92f};
const std::pair<float, float> 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<float, float> PetPhaseGetPosForTank()
{
if (GetNearestPet() == feugen)
{
return tankPosFeugen;
}
return tankPosStalagg;
}
std::pair<float, float> PetPhaseGetPosForRanged()
{
if (GetNearestPet() == feugen)
{
return rangedPosFeugen;
}
return rangedPosStalagg;
}
protected:
Unit* feugen = nullptr;
Unit* stalagg = nullptr;
};
#endif

View File

@@ -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<BossAiHeigan40*>(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<Heigan::boss_heigan::boss_heiganAI*>(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<CombatFormationMoveAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action) )
{
return 0.0f;
}
if (curr_phase != 1 && (int32)curr_dance - curr_timer >= 3000)
{
return 1.0f;
}
if (dynamic_cast<HeiganDanceAction*>(action) || dynamic_cast<CurePartyMemberAction*>(action))
{
return 1.0f;
}
if (dynamic_cast<CastSpellAction*>(action) && !dynamic_cast<CastMeleeSpellAction*>(action))
{
CastSpellAction* spellAction = dynamic_cast<CastSpellAction*>(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<CombatFormationMoveAction*>(action))
return 0.0f;
// pet phase
if (helper.IsPhasePet() &&
(dynamic_cast<DpsAssistAction*>(action) || dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<CastDebuffSpellOnAttackerAction*>(action) ||
dynamic_cast<ReachPartyMemberToHealAction*>(action) || dynamic_cast<BuffOnMainTankAction*>(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<CastSpellAction*>(action) && !dynamic_cast<CastHealingSpellAction*>(action))
{
return 0.0f;
}
}
// magnetic pull
// uint32 curr_timer = eventMap->GetTimer();
// // if (curr_phase == 2 && bot->GetPositionZ() > 312.5f && dynamic_cast<MovementAction*>(action)) {
// if (curr_phase == 2 && (curr_timer % 20000 >= 18000 || curr_timer % 20000 <= 2000) &&
// dynamic_cast<MovementAction*>(action)) {
// // MotionMaster *mm = bot->GetMotionMaster();
// // mm->Clear();
// return 0.0f;
// }
// thaddius phase
// if (curr_phase == 8 && dynamic_cast<FleeAction*>(action)) {
// return 0.0f;
// }
return 1.0f;
}
float IPSapphironGenericMultiplier::GetValue(Action* action)
{
if (!helper.UpdateBossAI())
{
return 1.0f;
}
if (dynamic_cast<FollowAction*>(action) || dynamic_cast<CastDeathGripAction*>(action) ||
dynamic_cast<CombatFormationMoveAction*>(action))
{
return 0.0f;
}
return 1.0f;
}
float IPInstructorRazuviousGenericMultiplier::GetValue(Action* action)
{
if (!helper.UpdateBossAI())
{
return 1.0f;
}
context->GetValue<bool>("neglect threat")->Set(true);
if (botAI->GetState() == BOT_STATE_COMBAT &&
(dynamic_cast<DpsAssistAction*>(action) || dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<CastTauntAction*>(action) || dynamic_cast<CastDarkCommandAction*>(action) ||
dynamic_cast<CastHandOfReckoningAction*>(action) || dynamic_cast<CastGrowlAction*>(action)))
{
return 0.0f;
}
return 1.0f;
}
float IPKelthuzadGenericMultiplier::GetValue(Action* action)
{
if (!helper.UpdateBossAI())
{
return 1.0f;
}
if ((dynamic_cast<DpsAssistAction*>(action) || dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<CastDebuffSpellOnAttackerAction*>(action) || dynamic_cast<FollowAction*>(action) ||
dynamic_cast<FleeAction*>(action)))
{
return 0.0f;
}
if (helper.IsPhaseOne())
{
if (dynamic_cast<CastTotemAction*>(action) || dynamic_cast<CastShadowfiendAction*>(action) ||
dynamic_cast<CastRaiseDeadAction*>(action) || dynamic_cast<CastFeignDeathAction*>(action) ||
dynamic_cast<CastInvisibilityAction*>(action) || dynamic_cast<CastVanishAction*>(action) ||
dynamic_cast<PetAttackAction*>(action))
{
return 0.0f;
}
}
if (helper.IsPhaseTwo())
{
if (dynamic_cast<CastBlizzardAction*>(action) || dynamic_cast<CastFrostNovaAction*>(action))
{
return 0.0f;
}
}
return 1.0f;
}
float IPGluthGenericMultiplier::GetValue(Action* action)
{
if (!helper.UpdateBossAI())
{
return 1.0f;
}
if ((dynamic_cast<DpsAssistAction*>(action) || dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<FleeAction*>(action) || dynamic_cast<CastDebuffSpellOnAttackerAction*>(action) ||
dynamic_cast<CastStarfallAction*>(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<CastTauntAction*>(action) || dynamic_cast<CastDarkCommandAction*>(action) ||
dynamic_cast<CastHandOfReckoningAction*>(action) || dynamic_cast<CastGrowlAction*>(action))
{
return 0.0f;
}
}
}
if (dynamic_cast<PetAttackAction*>(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

View File

@@ -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

View File

@@ -0,0 +1,156 @@
#ifdef MOD_PLAYERBOTS
#include "IndividualProgressionNaxxTriggers.h"
#include "EventMap.h"
#include "Playerbots.h"
#include "ScriptedCreature.h"
#include "Trigger.h"
template <class T>
bool BossEventTrigger<T>::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<T*>(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<Grobbulus::boss_grobbulus::boss_grobbulusAI>::IsActive();
#endif

View File

@@ -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 T>
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<Grobbulus::boss_grobbulus::boss_grobbulusAI>
{
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