Restore Naxx Strategies without core dependencies (#2031)

### Summary
This PR restores the Naxxramas raid strategies that were removed in
commit 686fe513b2 .
The reintroduced logic is core‑friendly (no AzerothCore script headers
or internal boss AI/EventMap dependencies), and the Naxxramas actions
have been refactored into per‑boss files for better maintainability.

### Motivation
The previous removal was meant to avoid core modifications and unblock
upstreaming.
This PR brings the strategies back while adhering to that requirement,
using only observable state and mod‑playerbots helpers.

### What’s included

- Re‑enabled the Naxxramas strategies previously removed.
- Replaced core script header dependencies with observable checks
(auras, casts, unit flags, flight state, etc.).
- Split the Naxxramas action logic into per‑boss source files to avoid a
“god file” and ease future maintenance.
- Minor, non‑intrusive behavior improvements aligned with existing
helpers.

### Future work
Some strategies may still require refinement or more advanced handling
later.
This PR focuses on restoring the baseline logic without core
dependencies, while keeping changes minimal and safe.

**Any contributions are welcome to further improve and fine‑tune the
Naxxramas strategies.**

### Testing
Tested in some Naxx boxx.
No server crash and boss killed :D

Note: I'll make another PR with revised scripts when this one are merged

---------

Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com>
Co-authored-by: bash <hermensb@gmail.com>
Co-authored-by: Revision <tkn963@gmail.com>
Co-authored-by: kadeshar <kadeshar@gmail.com>
This commit is contained in:
Alex Dcnh
2026-03-06 16:57:21 +01:00
committed by GitHub
parent 28a888b6e0
commit 18bd655869
35 changed files with 3486 additions and 2 deletions

View File

@@ -237,6 +237,20 @@ bool MaxDpsChatShortcutAction::Execute(Event /*event*/)
return true;
}
bool NaxxChatShortcutAction::Execute(Event /*event*/)
{
Player* master = GetMaster();
if (!master)
return false;
botAI->Reset();
botAI->ChangeStrategy("+naxx", BOT_STATE_NON_COMBAT);
botAI->ChangeStrategy("+naxx", BOT_STATE_COMBAT);
botAI->TellMasterNoFacing("Add Naxx Strategies!");
// bot->Say("Add Naxx Strategies!", LANG_UNIVERSAL);
return true;
}
bool BwlChatShortcutAction::Execute(Event /*event*/)
{
Player* master = GetMaster();

View File

@@ -85,6 +85,13 @@ public:
bool Execute(Event event) override;
};
class NaxxChatShortcutAction : public Action
{
public:
NaxxChatShortcutAction(PlayerbotAI* ai) : Action(ai, "naxx chat shortcut") {}
virtual bool Execute(Event event);
};
class BwlChatShortcutAction : public Action
{
public:

View File

@@ -187,6 +187,7 @@ public:
creators["guild leave"] = &ChatActionContext::guild_leave;
creators["rtsc"] = &ChatActionContext::rtsc;
creators["bwl chat shortcut"] = &ChatActionContext::bwl_chat_shortcut;
creators["naxx chat shortcut"] = &ChatActionContext::naxx_chat_shortcut;
creators["tell estimated dps"] = &ChatActionContext::tell_estimated_dps;
creators["join"] = &ChatActionContext::join;
creators["lfg"] = &ChatActionContext::lfg;
@@ -298,6 +299,7 @@ private:
static Action* guild_remove(PlayerbotAI* botAI) { return new GuildRemoveAction(botAI); }
static Action* guild_leave(PlayerbotAI* botAI) { return new GuildLeaveAction(botAI); }
static Action* rtsc(PlayerbotAI* botAI) { return new RTSCAction(botAI); }
static Action* naxx_chat_shortcut(PlayerbotAI* ai) { return new NaxxChatShortcutAction(ai); }
static Action* bwl_chat_shortcut(PlayerbotAI* ai) { return new BwlChatShortcutAction(ai); }
static Action* tell_estimated_dps(PlayerbotAI* ai) { return new TellEstimatedDpsAction(ai); }
static Action* join(PlayerbotAI* ai) { return new JoinGroupAction(ai); }

View File

@@ -127,7 +127,6 @@ public:
creators["guild leave"] = &ChatTriggerContext::guild_leave;
creators["rtsc"] = &ChatTriggerContext::rtsc;
creators["drink"] = &ChatTriggerContext::drink;
// creators["bwl"] = &ChatTriggerContext::bwl;
creators["dps"] = &ChatTriggerContext::dps;
creators["disperse"] = &ChatTriggerContext::disperse;
creators["calc"] = &ChatTriggerContext::calc;
@@ -245,7 +244,6 @@ private:
static Trigger* guild_leave(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "guild leave"); }
static Trigger* rtsc(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "rtsc"); }
static Trigger* drink(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "drink"); }
// static Trigger* bwl(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "bwl"); }
static Trigger* dps(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "dps"); }
static Trigger* disperse(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "disperse"); }
static Trigger* calc(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "calc"); }

View File

@@ -83,6 +83,8 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
new TriggerNode("target", { NextAction("tell target", relevance) }));
triggers.push_back(
new TriggerNode("ready", { NextAction("ready check", relevance) }));
triggers.push_back(
new TriggerNode("naxx", {NextAction("naxx chat shortcut", relevance)}));
triggers.push_back(
new TriggerNode("bwl", { NextAction("bwl chat shortcut", relevance) }));
triggers.push_back(

View File

@@ -0,0 +1,326 @@
#ifndef _PLAYERBOT_RAIDNAXXACTIONS_H
#define _PLAYERBOT_RAIDNAXXACTIONS_H
#include "Action.h"
#include "AttackAction.h"
#include "GenericActions.h"
#include "MovementActions.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "RaidNaxxBossHelper.h"
class GrobbulusGoBehindAction : public MovementAction
{
public:
GrobbulusGoBehindAction(PlayerbotAI* ai, float distance = 24.0f, float delta_angle = M_PI / 8)
: MovementAction(ai, "grobbulus go behind")
{
this->distance = distance;
this->delta_angle = delta_angle;
}
virtual bool Execute(Event event);
protected:
float distance, delta_angle;
};
class GrobbulusRotateAction : public RotateAroundTheCenterPointAction
{
public:
GrobbulusRotateAction(PlayerbotAI* botAI)
: RotateAroundTheCenterPointAction(botAI, "rotate grobbulus", 3281.23f, -3310.38f, 35.0f, 8, true, M_PI) {}
virtual bool isUseful() override
{
return RotateAroundTheCenterPointAction::isUseful() && botAI->IsMainTank(bot) &&
AI_VALUE2(bool, "has aggro", "boss target");
}
uint32 GetCurrWaypoint() override;
};
class GrobblulusMoveCenterAction : public MoveInsideAction
{
public:
GrobblulusMoveCenterAction(PlayerbotAI* ai) : MoveInsideAction(ai, 3281.23f, -3310.38f, 5.0f) {}
};
class GrobbulusMoveAwayAction : public MovementAction
{
public:
GrobbulusMoveAwayAction(PlayerbotAI* ai, float distance = 18.0f)
: MovementAction(ai, "grobbulus move away"), distance(distance)
{
}
bool Execute(Event event) override;
private:
float distance;
};
//class HeiganDanceAction : public MovementAction
//{
//public:
// HeiganDanceAction(PlayerbotAI* ai) : MovementAction(ai, "heigan dance")
// {
// this->last_eruption_ms = 0;
// this->platform_phase = false;
// ResetSafe();
// waypoints.push_back(std::make_pair(2794.88f, -3668.12f));
// waypoints.push_back(std::make_pair(2775.49f, -3674.43f));
// waypoints.push_back(std::make_pair(2762.30f, -3684.59f));
// waypoints.push_back(std::make_pair(2755.99f, -3703.96f));
// platform = std::make_pair(2794.26f, -3706.67f);
// }
//
//protected:
// bool CalculateSafe();
// void ResetSafe()
// {
// curr_safe = 0;
// curr_dir = 1;
// }
// void NextSafe()
// {
// curr_safe += curr_dir;
// if (curr_safe == 3 || curr_safe == 0)
// {
// curr_dir = -curr_dir;
// }
// }
// uint32 last_eruption_ms;
// bool platform_phase;
// uint32 curr_safe, curr_dir;
// std::vector<std::pair<float, float>> waypoints;
// std::pair<float, float> platform;
//};
//
//class HeiganDanceMeleeAction : public HeiganDanceAction
//{
//public:
// HeiganDanceMeleeAction(PlayerbotAI* ai) : HeiganDanceAction(ai) {}
// virtual bool Execute(Event event);
//};
//
//class HeiganDanceRangedAction : public HeiganDanceAction
//{
//public:
// HeiganDanceRangedAction(PlayerbotAI* ai) : HeiganDanceAction(ai) {}
// virtual bool Execute(Event event);
//};
class ThaddiusAttackNearestPetAction : public AttackAction
{
public:
ThaddiusAttackNearestPetAction(PlayerbotAI* ai) : AttackAction(ai, "thaddius attack nearest pet"), helper(ai) {}
virtual bool Execute(Event event);
virtual bool isUseful();
private:
ThaddiusBossHelper helper;
};
// class ThaddiusMeleeToPlaceAction : public MovementAction
// {
// public:
// ThaddiusMeleeToPlaceAction(PlayerbotAI* ai) : MovementAction(ai, "thaddius melee to place") {}
// virtual bool Execute(Event event);
// virtual bool isUseful();
// };
// class ThaddiusRangedToPlaceAction : public MovementAction
// {
// public:
// ThaddiusRangedToPlaceAction(PlayerbotAI* ai) : MovementAction(ai, "thaddius ranged to place") {}
// virtual bool Execute(Event event);
// virtual bool isUseful();
// };
class ThaddiusMoveToPlatformAction : public MovementAction
{
public:
ThaddiusMoveToPlatformAction(PlayerbotAI* ai) : MovementAction(ai, "thaddius move to platform") {}
virtual bool Execute(Event event);
virtual bool isUseful();
};
class ThaddiusMovePolarityAction : public MovementAction
{
public:
ThaddiusMovePolarityAction(PlayerbotAI* ai) : MovementAction(ai, "thaddius move polarity") {}
virtual bool Execute(Event event);
virtual bool isUseful();
};
class RazuviousUseObedienceCrystalAction : public MovementAction
{
public:
RazuviousUseObedienceCrystalAction(PlayerbotAI* ai)
: MovementAction(ai, "razuvious use obedience crystal"), helper(ai)
{
}
bool Execute(Event event) override;
private:
RazuviousBossHelper helper;
};
class RazuviousTargetAction : public AttackAction
{
public:
RazuviousTargetAction(PlayerbotAI* ai) : AttackAction(ai, "razuvious target"), helper(ai) {}
bool Execute(Event event) override;
private:
RazuviousBossHelper helper;
};
class HorsemanAttractAlternativelyAction : public AttackAction
{
public:
HorsemanAttractAlternativelyAction(PlayerbotAI* ai) : AttackAction(ai, "horseman attract alternatively"), helper(ai)
{
}
bool Execute(Event event) override;
protected:
FourhorsemanBossHelper helper;
};
class HorsemanAttactInOrderAction : public AttackAction
{
public:
HorsemanAttactInOrderAction(PlayerbotAI* ai) : AttackAction(ai, "horseman attact in order"), helper(ai) {}
bool Execute(Event event) override;
protected:
FourhorsemanBossHelper helper;
};
// class SapphironGroundMainTankPositionAction : public MovementAction
// {
// public:
// SapphironGroundMainTankPositionAction(PlayerbotAI* ai) : MovementAction(ai, "sapphiron ground main tank
// position") {} virtual bool Execute(Event event);
// };
class SapphironGroundPositionAction : public MovementAction
{
public:
SapphironGroundPositionAction(PlayerbotAI* ai) : MovementAction(ai, "sapphiron ground position"), helper(ai) {}
bool Execute(Event event) override;
protected:
SapphironBossHelper helper;
};
class SapphironFlightPositionAction : public MovementAction
{
public:
SapphironFlightPositionAction(PlayerbotAI* ai) : MovementAction(ai, "sapphiron flight position"), helper(ai) {}
bool Execute(Event event) override;
protected:
SapphironBossHelper helper;
bool MoveToNearestIcebolt();
};
// class SapphironAvoidChillAction : public MovementAction
// {
// public:
// SapphironAvoidChillAction(PlayerbotAI* ai) : MovementAction(ai, "sapphiron avoid chill") {}
// virtual bool Execute(Event event);
// };
class KelthuzadChooseTargetAction : public AttackAction
{
public:
KelthuzadChooseTargetAction(PlayerbotAI* ai) : AttackAction(ai, "kel'thuzad choose target"), helper(ai) {}
virtual bool Execute(Event event);
private:
KelthuzadBossHelper helper;
};
class KelthuzadPositionAction : public MovementAction
{
public:
KelthuzadPositionAction(PlayerbotAI* ai) : MovementAction(ai, "kel'thuzad position"), helper(ai) {}
virtual bool Execute(Event event);
private:
KelthuzadBossHelper helper;
};
class AnubrekhanChooseTargetAction : public AttackAction
{
public:
AnubrekhanChooseTargetAction(PlayerbotAI* ai) : AttackAction(ai, "anub'rekhan choose target") {}
bool Execute(Event event) override;
};
class AnubrekhanPositionAction : public RotateAroundTheCenterPointAction
{
public:
AnubrekhanPositionAction(PlayerbotAI* ai)
: RotateAroundTheCenterPointAction(ai, "anub'rekhan position", 3272.49f, -3476.27f, 45.0f, 16) {}
bool Execute(Event event) override;
};
class GluthChooseTargetAction : public AttackAction
{
public:
GluthChooseTargetAction(PlayerbotAI* ai) : AttackAction(ai, "gluth choose target"), helper(ai) {}
bool Execute(Event event) override;
private:
GluthBossHelper helper;
};
class GluthPositionAction : public RotateAroundTheCenterPointAction
{
public:
GluthPositionAction(PlayerbotAI* ai)
: RotateAroundTheCenterPointAction(ai, "gluth position", 3293.61f, -3149.01f, 12.0f, 12), helper(ai) {}
bool Execute(Event event) override;
private:
GluthBossHelper helper;
};
class GluthSlowdownAction : public Action
{
public:
GluthSlowdownAction(PlayerbotAI* ai) : Action(ai, "gluth slowdown"), helper(ai) {}
bool Execute(Event event) override;
private:
GluthBossHelper helper;
};
class LoathebPositionAction : public MovementAction
{
public:
LoathebPositionAction(PlayerbotAI* ai) : MovementAction(ai, "loatheb position"), helper(ai) {}
virtual bool Execute(Event event);
private:
LoathebBossHelper helper;
};
class LoathebChooseTargetAction : public AttackAction
{
public:
LoathebChooseTargetAction(PlayerbotAI* ai) : AttackAction(ai, "loatheb choose target"), helper(ai) {}
virtual bool Execute(Event event);
private:
LoathebBossHelper helper;
};
//class PatchwerkRangedPositionAction : public MovementAction
//{
//public:
// PatchwerkRangedPositionAction(PlayerbotAI* ai) : MovementAction(ai, "patchwerk ranged position") {}
// bool Execute(Event event) override;
//};
#endif

View File

@@ -0,0 +1,81 @@
#include "RaidNaxxActions.h"
#include "ObjectGuid.h"
#include "Playerbots.h"
bool AnubrekhanChooseTargetAction::Execute(Event /*event*/)
{
GuidVector attackers = context->GetValue<GuidVector>("attackers")->Get();
Unit* target = nullptr;
Unit* target_boss = nullptr;
std::vector<Unit*> target_guards;
for (ObjectGuid const guid : attackers)
{
Unit* unit = botAI->GetUnit(guid);
if (!unit)
continue;
if (botAI->EqualLowercaseName(unit->GetName(), "crypt guard"))
target_guards.push_back(unit);
if (botAI->EqualLowercaseName(unit->GetName(), "anub'rekhan"))
target_boss = unit;
}
if (botAI->IsMainTank(bot))
target = target_boss;
else
{
if (target_guards.size() == 0)
target = target_boss;
else
{
if (botAI->IsAssistTank(bot))
{
for (Unit* t : target_guards)
{
if (target == nullptr || (target->GetVictim() && target->GetVictim()->ToPlayer() &&
botAI->IsTank(target->GetVictim()->ToPlayer())))
target = t;
}
}
else
{
for (Unit* t : target_guards)
{
if (target == nullptr || target->GetHealthPct() > t->GetHealthPct())
target = t;
}
}
}
}
if (context->GetValue<Unit*>("current target")->Get() == target)
return false;
return Attack(target);
}
bool AnubrekhanPositionAction::Execute(Event /*event*/)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "anub'rekhan");
if (!boss)
return false;
bool inPhase = botAI->HasAura("locust swarm", boss) || boss->GetCurrentSpell(CURRENT_GENERIC_SPELL);
if (inPhase)
{
if (botAI->IsMainTank(bot))
{
uint32 nearest = FindNearestWaypoint();
uint32 next_point;
if (inPhase)
next_point = (nearest + 1) % intervals;
else
next_point = nearest;
return MoveTo(bot->GetMapId(), waypoints[next_point].first, waypoints[next_point].second, bot->GetPositionZ(), false, false,
false, false, MovementPriority::MOVEMENT_COMBAT);
}
else
return MoveInside(533, 3272.49f, -3476.27f, bot->GetPositionZ(), 3.0f, MovementPriority::MOVEMENT_COMBAT);
}
return false;
}

View File

@@ -0,0 +1,3 @@
#include "RaidNaxxActions.h"
// Reserved for Faerlina-specific actions.

View File

@@ -0,0 +1,59 @@
#include "RaidNaxxActions.h"
#include "Playerbots.h"
bool HorsemanAttractAlternativelyAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
helper.CalculatePosToGo(bot);
auto [posX, posY] = helper.CurrentAttractPos();
if (MoveTo(bot->GetMapId(), posX, posY, helper.posZ, false, false, false, false, MovementPriority::MOVEMENT_COMBAT))
return true;
Unit* attackTarget = helper.CurrentAttackTarget();
if (context->GetValue<Unit*>("current target")->Get() != attackTarget)
return Attack(attackTarget);
return false;
}
bool HorsemanAttactInOrderAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
Unit* target = nullptr;
Unit* thane = AI_VALUE2(Unit*, "find target", "thane korth'azz");
Unit* lady = AI_VALUE2(Unit*, "find target", "lady blaumeux");
Unit* sir = AI_VALUE2(Unit*, "find target", "sir zeliek");
Unit* fourth = AI_VALUE2(Unit*, "find target", "baron rivendare");
if (!fourth)
fourth = AI_VALUE2(Unit*, "find target", "highlord mograine");
std::vector<Unit*> attack_order;
if (botAI->IsAssistTank(bot))
attack_order = {fourth, thane, lady, sir};
else
attack_order = {thane, fourth, lady, sir};
for (Unit* t : attack_order)
{
if (t && t->IsAlive())
{
target = t;
break;
}
}
if (target)
{
if (context->GetValue<Unit*>("current target")->Get() == target && botAI->GetState() == BOT_STATE_COMBAT)
return false;
if (!bot->IsWithinLOSInMap(target))
return MoveNear(target, 22.0f, MovementPriority::MOVEMENT_COMBAT);
return Attack(target);
}
return false;
}

View File

@@ -0,0 +1,178 @@
#include "RaidNaxxActions.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
#include "SharedDefines.h"
bool GluthChooseTargetAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
GuidVector attackers = context->GetValue<GuidVector>("possible targets")->Get();
Unit* target = nullptr;
Unit* target_boss = nullptr;
std::vector<Unit*> target_zombies;
for (GuidVector::iterator i = attackers.begin(); i != attackers.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (!unit)
continue;
if (!unit->IsAlive())
continue;
if (botAI->EqualLowercaseName(unit->GetName(), "zombie chow"))
target_zombies.push_back(unit);
if (botAI->EqualLowercaseName(unit->GetName(), "gluth"))
target_boss = unit;
}
if (botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0))
target = target_boss;
else if (botAI->IsAssistTankOfIndex(bot, 1))
{
for (Unit* t : target_zombies)
{
if (t->GetHealthPct() > helper.decimatedZombiePct && t->GetVictim() != bot && t->GetDistance2d(bot) <= 10.0f)
{
if (!target || t->GetDistance2d(bot) < target->GetDistance2d(bot))
target = t;
}
}
}
else if (botAI->GetClassIndex(bot, CLASS_HUNTER) == 0 || botAI->GetClassIndex(bot, CLASS_HUNTER) == 1)
{
// prevent zombie go straight to gluth
for (Unit* t : target_zombies)
{
if (t->GetHealthPct() > helper.decimatedZombiePct && t->GetVictim() == target_boss &&
t->GetDistance2d(bot) <= sPlayerbotAIConfig.spellDistance)
{
if (!target || t->GetDistance2d(bot) < target->GetDistance2d(bot))
target = t;
}
}
if (!target)
target = target_boss;
}
else
{
for (Unit* t : target_zombies)
{
if (t->GetHealthPct() <= helper.decimatedZombiePct)
{
if (target == nullptr ||
target->GetDistance2d(helper.mainTankPos25.first, helper.mainTankPos25.second) >
t->GetDistance2d(helper.mainTankPos25.first, helper.mainTankPos25.second))
target = t;
}
}
if (target == nullptr)
target = target_boss;
}
if (!target || context->GetValue<Unit*>("current target")->Get() == target)
return false;
if (target_boss && target == target_boss)
return Attack(target, true);
return Attack(target, false);
// return Attack(target);
}
bool GluthPositionAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
bool raid25 = bot->GetRaidDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL;
if (botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0))
{
if (AI_VALUE2(bool, "has aggro", "boss target"))
{
if (raid25)
{
if (MoveTo(NAXX_MAP_ID, helper.mainTankPos25.first, helper.mainTankPos25.second, bot->GetPositionZ(), false, false, false,
false, MovementPriority::MOVEMENT_COMBAT))
return true;
return MoveInside(NAXX_MAP_ID, helper.mainTankPos25.first, helper.mainTankPos25.second, bot->GetPositionZ(), 2.0f,
MovementPriority::MOVEMENT_COMBAT);
}
else
{
if (MoveTo(NAXX_MAP_ID, helper.mainTankPos10.first, helper.mainTankPos10.second, bot->GetPositionZ(), false, false, false,
false, MovementPriority::MOVEMENT_COMBAT))
return true;
return MoveInside(NAXX_MAP_ID, helper.mainTankPos10.first, helper.mainTankPos10.second, bot->GetPositionZ(), 2.0f,
MovementPriority::MOVEMENT_COMBAT);
}
}
}
else if (botAI->IsAssistTankOfIndex(bot, 1))
{
if (helper.BeforeDecimate())
{
if (MoveTo(bot->GetMapId(), helper.beforeDecimatePos.first, helper.beforeDecimatePos.second, bot->GetPositionZ(), false, false,
false, false, MovementPriority::MOVEMENT_COMBAT))
return true;
return MoveInside(bot->GetMapId(), helper.beforeDecimatePos.first, helper.beforeDecimatePos.second, bot->GetPositionZ(), 2.0f,
MovementPriority::MOVEMENT_COMBAT);
}
else
{
if (AI_VALUE2(bool, "has aggro", "current target"))
{
uint32 nearest = FindNearestWaypoint();
uint32 next_point = (nearest + 1) % intervals;
return MoveTo(bot->GetMapId(), waypoints[next_point].first, waypoints[next_point].second, bot->GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
}
else if (botAI->IsRangedDps(bot))
{
if (raid25)
{
if (botAI->GetClassIndex(bot, CLASS_HUNTER) == 0)
return MoveInside(NAXX_MAP_ID, helper.leftSlowDownPos.first, helper.leftSlowDownPos.second, bot->GetPositionZ(), 0.0f,
MovementPriority::MOVEMENT_COMBAT);
if (botAI->GetClassIndex(bot, CLASS_HUNTER) == 1)
return MoveInside(NAXX_MAP_ID, helper.rightSlowDownPos.first, helper.rightSlowDownPos.second, bot->GetPositionZ(), 0.0f,
MovementPriority::MOVEMENT_COMBAT);
}
return MoveInside(NAXX_MAP_ID, helper.rangedPos.first, helper.rangedPos.second, bot->GetPositionZ(), 3.0f,
MovementPriority::MOVEMENT_COMBAT);
}
else if (botAI->IsHeal(bot))
return MoveInside(NAXX_MAP_ID, helper.healPos.first, helper.healPos.second, bot->GetPositionZ(), 0.0f,
MovementPriority::MOVEMENT_COMBAT);
return false;
}
bool GluthSlowdownAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
bool raid25 = bot->GetRaidDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL;
if (!raid25)
return false;
if (helper.JustStartCombat())
return false;
switch (bot->getClass())
{
case CLASS_HUNTER:
return botAI->CastSpell("frost trap", bot);
break;
default:
break;
}
return false;
}

View File

@@ -0,0 +1,3 @@
#include "RaidNaxxActions.h"
// Reserved for Gothik-specific actions.

View File

@@ -0,0 +1,46 @@
#include "RaidNaxxActions.h"
#include "Playerbots.h"
bool GrobbulusGoBehindAction::Execute(Event /*event*/)
{
Unit* boss = AI_VALUE(Unit*, "boss target");
if (!boss)
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);
}
bool GrobbulusMoveAwayAction::Execute(Event /*event*/)
{
Unit* boss = AI_VALUE(Unit*, "boss target");
if (!boss)
return false;
const float currentDistance = bot->GetExactDist2d(boss);
if (currentDistance >= distance)
return false;
const float angle = boss->GetAngle(bot);
const float x = boss->GetPositionX() + cos(angle) * distance;
const float y = boss->GetPositionY() + sin(angle) * distance;
const float z = bot->GetPositionZ();
return MoveTo(bot->GetMapId(), x, y, z, false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
uint32 GrobbulusRotateAction::GetCurrWaypoint()
{
uint32 current = FindNearestWaypoint();
if (clockwise)
return (current + 1) % intervals;
return (current + intervals - 1) % intervals;
}

View File

@@ -0,0 +1,77 @@
#include "Playerbots.h"
#include "RaidNaxxActions.h"
#include "RaidNaxxSpellIds.h"
#include "Spell.h"
#include "Timer.h"
//bool HeiganDanceAction::CalculateSafe()
//{
// Unit* boss = AI_VALUE2(Unit*, "find target", "heigan the unclean");
// if (!boss)
// {
// return false;
// }
// uint32 now = getMSTime();
// platform_phase = boss->IsWithinDist2d(platform.first, platform.second, 10.0f);
// if (last_eruption_ms != 0 && now - last_eruption_ms > 15000)
// {
// ResetSafe();
// }
// if (boss->HasUnitState(UNIT_STATE_CASTING))
// {
// Spell* spell = boss->GetCurrentSpell(CURRENT_GENERIC_SPELL);
// if (!spell)
// {
// spell = boss->GetCurrentSpell(CURRENT_CHANNELED_SPELL);
// }
// if (spell)
// {
// SpellInfo const* info = spell->GetSpellInfo();
// bool isEruption = NaxxSpellIds::MatchesAnySpellId(info, {NaxxSpellIds::Eruption10});
// if (!isEruption && info && info->SpellName[LOCALE_enUS])
// {
// // Fallback to name for custom spell data.
// isEruption = botAI->EqualLowercaseName(info->SpellName[LOCALE_enUS], "eruption");
// }
// if (isEruption)
// {
// if (last_eruption_ms == 0 || now - last_eruption_ms > 500)
// {
// NextSafe();
// }
// last_eruption_ms = now;
// }
// }
// }
// return true;
//}
//
//bool HeiganDanceMeleeAction::Execute(Event event)
//{
// CalculateSafe();
// if (!platform_phase && botAI->IsMainTank(bot) && !AI_VALUE2(bool, "has aggro", "boss target"))
// {
// return false;
// }
// assert(curr_safe >= 0 && curr_safe <= 3);
// return MoveInside(bot->GetMapId(), waypoints[curr_safe].first, waypoints[curr_safe].second, bot->GetPositionZ(),
// botAI->IsMainTank(bot) ? 0 : 0, MovementPriority::MOVEMENT_COMBAT);
//}
//
//bool HeiganDanceRangedAction::Execute(Event event)
//{
// CalculateSafe();
// if (!platform_phase)
// {
// if (MoveTo(bot->GetMapId(), platform.first, platform.second, 276.54f, false, false, false, false,
// MovementPriority::MOVEMENT_COMBAT))
// {
// return true;
// }
// return MoveInside(bot->GetMapId(), platform.first, platform.second, 276.54f, 2.0f,
// MovementPriority::MOVEMENT_COMBAT);
// }
// botAI->InterruptSpell();
// return MoveInside(bot->GetMapId(), waypoints[curr_safe].first, waypoints[curr_safe].second, bot->GetPositionZ(), 0,
// MovementPriority::MOVEMENT_COMBAT);
//}

View File

@@ -0,0 +1,177 @@
#include "RaidNaxxActions.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
bool KelthuzadChooseTargetAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
GuidVector attackers = context->GetValue<GuidVector>("attackers")->Get();
Unit* target = nullptr;
Unit *target_soldier = nullptr, *target_weaver = nullptr, *target_abomination = nullptr, *target_kelthuzad = nullptr,
*target_guardian = nullptr;
for (auto i = attackers.begin(); i != attackers.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (!unit)
continue;
if (botAI->EqualLowercaseName(unit->GetName(), "guardian of icecrown"))
{
if (!target_guardian)
target_guardian = unit;
else if (unit->GetVictim() && target_guardian->GetVictim() && unit->GetVictim()->ToPlayer() &&
target_guardian->GetVictim()->ToPlayer() && !botAI->IsAssistTank(unit->GetVictim()->ToPlayer()) &&
botAI->IsAssistTank(target_guardian->GetVictim()->ToPlayer()))
{
target_guardian = unit;
}
else if (unit->GetVictim() && target_guardian->GetVictim() && unit->GetVictim()->ToPlayer() &&
target_guardian->GetVictim()->ToPlayer() && !botAI->IsAssistTank(unit->GetVictim()->ToPlayer()) &&
!botAI->IsAssistTank(target_guardian->GetVictim()->ToPlayer()) &&
target_guardian->GetDistance2d(helper.center.first, helper.center.second) >
bot->GetDistance2d(unit))
{
target_guardian = unit;
}
}
if (unit->GetDistance2d(helper.center.first, helper.center.second) > 30.0f)
continue;
if (bot->GetDistance2d(unit) > sPlayerbotAIConfig.spellDistance)
continue;
if (botAI->EqualLowercaseName(unit->GetName(), "unstoppable abomination"))
{
if (target_abomination == nullptr ||
target_abomination->GetDistance2d(helper.center.first, helper.center.second) >
unit->GetDistance2d(helper.center.first, helper.center.second))
{
target_abomination = unit;
}
}
if (botAI->EqualLowercaseName(unit->GetName(), "soldier of the frozen wastes"))
{
if (target_soldier == nullptr ||
target_soldier->GetDistance2d(helper.center.first, helper.center.second) >
unit->GetDistance2d(helper.center.first, helper.center.second))
{
target_soldier = unit;
}
}
if (botAI->EqualLowercaseName(unit->GetName(), "soul weaver"))
{
if (target_weaver == nullptr || target_weaver->GetDistance2d(helper.center.first, helper.center.second) >
unit->GetDistance2d(helper.center.first, helper.center.second))
target_weaver = unit;
}
if (botAI->EqualLowercaseName(unit->GetName(), "kel'thuzad"))
target_kelthuzad = unit;
}
std::vector<Unit*> targets;
if (botAI->IsRanged(bot))
{
if (botAI->GetRangedDpsIndex(bot) <= 1)
targets = {target_soldier, target_weaver, target_abomination, target_kelthuzad};
else
targets = {target_weaver, target_soldier, target_abomination, target_kelthuzad};
}
else if (botAI->IsAssistTank(bot))
targets = {target_abomination, target_guardian, target_kelthuzad};
else
targets = {target_abomination, target_kelthuzad};
for (Unit* t : targets)
{
if (t)
{
target = t;
break;
}
}
if (context->GetValue<Unit*>("current target")->Get() == target)
return false;
if (target_kelthuzad && target == target_kelthuzad)
return Attack(target, true);
return Attack(target, false);
}
bool KelthuzadPositionAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
if (helper.IsPhaseOne())
{
if (AI_VALUE(Unit*, "current target") == nullptr)
return MoveInside(NAXX_MAP_ID, helper.center.first, helper.center.second, bot->GetPositionZ(), 3.0f,
MovementPriority::MOVEMENT_COMBAT);
}
else if (helper.IsPhaseTwo())
{
Unit* shadow_fissure = helper.GetAnyShadowFissure();
if (!shadow_fissure || !bot->IsWithinDistInMap(shadow_fissure, 10.0f))
{
float distance, angle;
if (botAI->IsMainTank(bot))
{
if (AI_VALUE2(bool, "has aggro", "current target"))
return MoveTo(NAXX_MAP_ID, helper.tank_pos.first, helper.tank_pos.second, bot->GetPositionZ(), false, false, false,
false, MovementPriority::MOVEMENT_COMBAT);
else
return false;
}
else if (botAI->IsRanged(bot))
{
uint32 index = botAI->GetRangedIndex(bot);
if (index < 8)
{
distance = 20.0f;
angle = index * M_PI / 4;
}
else
{
distance = 32.0f;
angle = (index - 8) * M_PI / 4;
}
float dx, dy;
dx = helper.center.first + cos(angle) * distance;
dy = helper.center.second + sin(angle) * distance;
return MoveTo(NAXX_MAP_ID, dx, dy, bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
else if (botAI->IsTank(bot))
{
Unit* cur_tar = AI_VALUE(Unit*, "current target");
if (cur_tar && cur_tar->GetVictim() && cur_tar->GetVictim()->ToPlayer() &&
botAI->EqualLowercaseName(cur_tar->GetName(), "guardian of icecrown") &&
botAI->IsAssistTank(cur_tar->GetVictim()->ToPlayer()))
{
return MoveTo(NAXX_MAP_ID, helper.assist_tank_pos.first, helper.assist_tank_pos.second, bot->GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
else
return false;
}
}
else
{
float dx, dy;
float angle;
if (!botAI->IsRanged(bot))
angle = shadow_fissure->GetAngle(helper.center.first, helper.center.second);
else
angle = bot->GetAngle(shadow_fissure) + M_PI;
dx = shadow_fissure->GetPositionX() + cos(angle) * 10.0f;
dy = shadow_fissure->GetPositionY() + sin(angle) * 10.0f;
return MoveTo(NAXX_MAP_ID, dx, dy, bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
return false;
}

View File

@@ -0,0 +1,55 @@
#include "RaidNaxxActions.h"
#include "Playerbots.h"
bool LoathebPositionAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
if (botAI->IsTank(bot))
{
if (AI_VALUE2(bool, "has aggro", "boss target"))
return MoveTo(533, helper.mainTankPos.first, helper.mainTankPos.second, bot->GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT);
}
else if (botAI->IsRanged(bot))
return MoveInside(533, helper.rangePos.first, helper.rangePos.second, bot->GetPositionZ(), 1.0f,
MovementPriority::MOVEMENT_COMBAT);
return false;
}
bool LoathebChooseTargetAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
GuidVector attackers = context->GetValue<GuidVector>("attackers")->Get();
Unit* target = nullptr;
Unit* target_boss = nullptr;
Unit* target_spore = nullptr;
for (auto i = attackers.begin(); i != attackers.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (!unit)
continue;
if (!unit->IsAlive())
continue;
if (botAI->EqualLowercaseName(unit->GetName(), "spore"))
target_spore = unit;
if (botAI->EqualLowercaseName(unit->GetName(), "loatheb"))
target_boss = unit;
}
if (target_spore && bot->GetDistance2d(target_spore) <= 1.0f)
target = target_spore;
else
target = target_boss;
if (!target || context->GetValue<Unit*>("current target")->Get() == target)
return false;
return Attack(target);
}

View File

@@ -0,0 +1,3 @@
#include "RaidNaxxActions.h"
// Reserved for Maexxna-specific actions.

View File

@@ -0,0 +1,3 @@
#include "RaidNaxxActions.h"
// Reserved for Noth-specific actions.

View File

@@ -0,0 +1,31 @@
#include "RaidNaxxActions.h"
#include <algorithm>
#include <cmath>
//bool PatchwerkRangedPositionAction::Execute(Event event)
//{
// Unit* boss = AI_VALUE2(Unit*, "find target", "patchwerk");
// if (!boss)
// return false;
//
// constexpr float minDistance = 12.0f;
// constexpr float maxDistance = 15.0f;
// const float distance = bot->GetExactDist2d(boss);
//
// if (distance >= minDistance && distance <= maxDistance)
// return false;
//
// const float desiredDistance = std::clamp(distance, minDistance, maxDistance);
// float angle = boss->GetAngle(bot);
//
// if (distance < 0.1f)
// angle = boss->GetOrientation();
//
// const float x = boss->GetPositionX() + std::cos(angle) * desiredDistance;
// const float y = boss->GetPositionY() + std::sin(angle) * desiredDistance;
// const float z = bot->GetPositionZ();
//
// return MoveTo(boss->GetMapId(), x, y, z, false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true,
// false);
//}

View File

@@ -0,0 +1,147 @@
#include "RaidNaxxActions.h"
#include "ObjectGuid.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
#include "SharedDefines.h"
bool RazuviousUseObedienceCrystalAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
// bot->GetCharm
if (Unit* charm = bot->GetCharm())
{
Unit* target = AI_VALUE2(Unit*, "find target", "instructor razuvious");
if (!target)
return false;
if (charm->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_ACTIVE) == NULL_MOTION_TYPE)
{
charm->GetMotionMaster()->Clear();
charm->GetMotionMaster()->MoveChase(target);
charm->GetAI()->AttackStart(target);
}
Aura* forceObedience = botAI->GetAura("force obedience", charm);
uint32 duration_time;
if (!forceObedience)
{
forceObedience = botAI->GetAura("mind control", charm);
duration_time = 60000;
}
else
duration_time = 90000;
if (!forceObedience)
return false;
if (charm->GetDistance(target) <= 0.51f)
{
// taunt
bool tauntUseful = true;
if (forceObedience->GetDuration() <= (duration_time - 5000))
{
if (target->GetVictim() && botAI->HasAura(29061, target->GetVictim()))
tauntUseful = false;
if (forceObedience->GetDuration() <= 3000)
tauntUseful = false;
}
if (forceObedience->GetDuration() >= (duration_time - 500))
tauntUseful = false;
if (tauntUseful && !charm->HasSpellCooldown(29060))
{
// shield
if (!charm->HasSpellCooldown(29061))
{
charm->CastSpell(charm, 29061, true);
charm->AddSpellCooldown(29061, 0, 30 * 1000);
}
charm->CastSpell(target, 29060, true);
charm->AddSpellCooldown(29060, 0, 20 * 1000);
}
// strike
if (!charm->HasSpellCooldown(61696))
{
charm->CastSpell(target, 61696, true);
charm->AddSpellCooldown(61696, 0, 4 * 1000);
}
}
}
else
{
Difficulty diff = bot->GetRaidDifficulty();
if (diff == RAID_DIFFICULTY_10MAN_NORMAL)
{
GuidVector npcs = AI_VALUE(GuidVector, "nearest npcs");
for (auto i = npcs.begin(); i != npcs.end(); i++)
{
Creature* unit = botAI->GetCreature(*i);
if (!unit)
continue;
if (botAI->IsMainTank(bot) && unit->GetSpawnId() != 128352)
continue;
if (!botAI->IsMainTank(bot) && unit->GetSpawnId() != 128353)
continue;
if (MoveTo(unit, 0.0f, MovementPriority::MOVEMENT_COMBAT))
return true;
Creature* creature = bot->GetNPCIfCanInteractWith(*i, UNIT_NPC_FLAG_SPELLCLICK);
if (!creature)
continue;
creature->HandleSpellClick(bot);
return true;
}
}
else
{
GuidVector attackers = context->GetValue<GuidVector>("attackers")->Get();
Unit* target = nullptr;
for (auto i = attackers.begin(); i != attackers.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (!unit)
continue;
if (botAI->EqualLowercaseName(unit->GetName(), "death knight understudy"))
{
target = unit;
break;
}
}
if (target)
{
if (bot->GetDistance2d(target) > sPlayerbotAIConfig.spellDistance)
return MoveNear(target, sPlayerbotAIConfig.spellDistance, MovementPriority::MOVEMENT_COMBAT);
else
return botAI->CastSpell("mind control", target);
}
}
}
return false;
}
bool RazuviousTargetAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
Unit* razuvious = AI_VALUE2(Unit*, "find target", "instructor razuvious");
Unit* understudy = AI_VALUE2(Unit*, "find target", "death knight understudy");
Unit* target = nullptr;
if (botAI->IsTank(bot))
target = understudy;
else
target = razuvious;
if (AI_VALUE(Unit*, "current target") == target)
return false;
return Attack(target);
}

View File

@@ -0,0 +1,104 @@
#include "RaidNaxxActions.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
#include "RaidNaxxBossHelper.h"
#include "RaidNaxxSpellIds.h"
bool SapphironGroundPositionAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
if (botAI->IsMainTank(bot))
{
if (AI_VALUE2(bool, "has aggro", "current target"))
return MoveTo(NAXX_MAP_ID, helper.mainTankPos.first, helper.mainTankPos.second, helper.GENERIC_HEIGHT, false, false, false,
false, MovementPriority::MOVEMENT_COMBAT);
return false;
}
if (helper.JustLanded())
{
uint32 index = botAI->GetGroupSlotIndex(bot);
float start_angle = 0.85 * M_PI;
float offset_angle = M_PI * 0.02 * index;
float angle = start_angle + offset_angle;
float distance;
if (botAI->IsRanged(bot))
distance = 35.0f;
else if (botAI->IsHeal(bot))
distance = 30.0f;
else
distance = 5.0f;
float posX = helper.center.first + cos(angle) * distance;
float posY = helper.center.second + sin(angle) * distance;
if (MoveTo(NAXX_MAP_ID, posX, posY, helper.GENERIC_HEIGHT, false, false, false, false, MovementPriority::MOVEMENT_COMBAT))
return true;
return MoveInside(NAXX_MAP_ID, posX, posY, helper.GENERIC_HEIGHT, 2.0f, MovementPriority::MOVEMENT_COMBAT);
}
else
{
std::vector<float> dest;
if (helper.FindPosToAvoidChill(dest))
return MoveTo(NAXX_MAP_ID, dest[0], dest[1], dest[2], false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
return false;
}
bool SapphironFlightPositionAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
if (helper.WaitForExplosion())
return MoveToNearestIcebolt();
else
{
std::vector<float> dest;
if (helper.FindPosToAvoidChill(dest))
return MoveTo(NAXX_MAP_ID, dest[0], dest[1], dest[2], false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
return false;
}
bool SapphironFlightPositionAction::MoveToNearestIcebolt()
{
Group* group = bot->GetGroup();
if (!group)
return false;
Group::MemberSlotList const& slots = group->GetMemberSlots();
Player* playerWithIcebolt = nullptr;
float minDistance;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (NaxxSpellIds::HasAnyAura(botAI, member, {NaxxSpellIds::Icebolt10, NaxxSpellIds::Icebolt25}) ||
botAI->HasAura("icebolt", member, false, false, -1, true))
{
if (!playerWithIcebolt || minDistance > bot->GetDistance(member))
{
playerWithIcebolt = member;
minDistance = bot->GetDistance(member);
}
}
}
if (playerWithIcebolt)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "sapphiron");
if (boss)
{
float angle = boss->GetAngle(playerWithIcebolt);
float posX = playerWithIcebolt->GetPositionX() + cos(angle) * 3.0f;
float posY = playerWithIcebolt->GetPositionY() + sin(angle) * 3.0f;
if (MoveTo(NAXX_MAP_ID, posX, posY, helper.GENERIC_HEIGHT, false, false, false, false, MovementPriority::MOVEMENT_COMBAT))
return true;
return MoveNear(playerWithIcebolt, 3.0f, MovementPriority::MOVEMENT_COMBAT);
}
}
return false;
}

View File

@@ -0,0 +1,18 @@
#include "RaidNaxxActions.h"
uint32 RotateAroundTheCenterPointAction::FindNearestWaypoint()
{
float minDistance = 0;
int ret = -1;
for (int i = 0; i < intervals; i++)
{
float w_x = waypoints[i].first, w_y = waypoints[i].second;
float dis = bot->GetDistance2d(w_x, w_y);
if (ret == -1 || dis < minDistance)
{
ret = i;
minDistance = dis;
}
}
return ret;
}

View File

@@ -0,0 +1,134 @@
#include "RaidNaxxActions.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
#include "RaidNaxxSpellIds.h"
bool ThaddiusAttackNearestPetAction::isUseful()
{
if (!helper.UpdateBossAI())
return false;
if (!helper.IsPhasePet())
return false;
Unit* target = helper.GetNearestPet();
if (!bot->IsWithinDistInMap(target, 50.0f))
return false;
return true;
}
bool ThaddiusAttackNearestPetAction::Execute(Event /*event*/)
{
Unit* target = helper.GetNearestPet();
if (!bot->IsWithinLOSInMap(target))
return MoveTo(target, 0, MovementPriority::MOVEMENT_COMBAT);
if (AI_VALUE(Unit*, "current target") != target)
return Attack(target);
if (botAI->IsTank(bot) && AI_VALUE2(bool, "has aggro", "current target"))
{
std::pair<float, float> posForTank = helper.PetPhaseGetPosForTank();
return MoveTo(533, posForTank.first, posForTank.second, helper.tankPosZ, false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
if (botAI->IsRanged(bot))
{
std::pair<float, float> posForRanged = helper.PetPhaseGetPosForRanged();
return MoveTo(533, posForRanged.first, posForRanged.second, helper.tankPosZ, false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
return false;
}
bool ThaddiusMoveToPlatformAction::isUseful() { return true; }
bool ThaddiusMoveToPlatformAction::Execute(Event /*event*/)
{
std::vector<std::pair<float, float>> position = {
// high left
{3462.99f, -2918.90f},
// high right
{3520.65f, -2976.51f},
// low left
{3471.36f, -2910.65f},
// low right
{3528.80f, -2967.04f},
// center
{3512.19f, -2928.58f},
};
float high_z = 312.00f, low_z = 304.02f;
bool is_left = bot->GetDistance2d(position[0].first, position[0].second) <
bot->GetDistance2d(position[1].first, position[1].second);
if (bot->GetPositionZ() >= (high_z - 3.0f))
{
if (is_left)
{
if (!MoveTo(bot->GetMapId(), position[0].first, position[0].second, high_z, false, false, false, false, MovementPriority::MOVEMENT_COMBAT))
{
float distance = bot->GetExactDist2d(position[0].first, position[0].second);
if (distance < sPlayerbotAIConfig.contactDistance)
JumpTo(bot->GetMapId(), position[2].first, position[2].second, low_z, MovementPriority::MOVEMENT_COMBAT);
// bot->TeleportTo(bot->GetMapId(), position[2].first, position[2].second, low_z, bot->GetOrientation());
}
}
else
{
if (!MoveTo(bot->GetMapId(), position[1].first, position[1].second, high_z, false, false, false, false, MovementPriority::MOVEMENT_COMBAT))
{
float distance = bot->GetExactDist2d(position[1].first, position[1].second);
if (distance < sPlayerbotAIConfig.contactDistance)
JumpTo(bot->GetMapId(), position[3].first, position[3].second, low_z, MovementPriority::MOVEMENT_COMBAT);
// bot->TeleportTo(bot->GetMapId(), position[3].first, position[3].second, low_z, bot->GetOrientation());
}
}
}
else
return MoveTo(bot->GetMapId(), position[4].first, position[4].second, low_z, false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
return true;
}
bool ThaddiusMovePolarityAction::isUseful()
{
return !botAI->IsMainTank(bot) || AI_VALUE2(bool, "has aggro", "current target");
}
bool ThaddiusMovePolarityAction::Execute(Event /*event*/)
{
std::vector<std::pair<float, float>> position = {
// left melee
{3508.29f, -2920.12f},
// left ranged
{3501.72f, -2913.36f},
// right melee
{3519.74f, -2931.69f},
// right ranged
{3524.32f, -2936.26f},
// center melee
{3512.19f, -2928.58f},
// center ranged
{3504.68f, -2936.68f},
};
uint32 idx;
if (NaxxSpellIds::HasAnyAura(
botAI, bot,
{NaxxSpellIds::NegativeCharge10, NaxxSpellIds::NegativeCharge25, NaxxSpellIds::NegativeChargeStack}) ||
botAI->HasAura("negative charge", bot, false, false, -1, true))
{
idx = 0;
}
else if (NaxxSpellIds::HasAnyAura(
botAI, bot,
{NaxxSpellIds::PositiveCharge10, NaxxSpellIds::PositiveCharge25, NaxxSpellIds::PositiveChargeStack}) ||
botAI->HasAura("positive charge", bot, false, false, -1, true))
{
idx = 1;
}
else
{
idx = 2;
}
idx = idx * 2 + botAI->IsRanged(bot);
return MoveTo(bot->GetMapId(), position[idx].first, position[idx].second, bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}

View File

@@ -0,0 +1,322 @@
#include "RaidNaxxMultipliers.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 "RaidNaxxSpellIds.h"
#include "ReachTargetActions.h"
#include "RogueActions.h"
#include "ScriptedCreature.h"
#include "ShamanActions.h"
#include "Spell.h"
#include "UseMeetingStoneAction.h"
#include "WarriorActions.h"
float GrobbulusMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "grobbulus");
if (!boss)
return 1.0f;
if (dynamic_cast<AvoidAoeAction*>(action))
return botAI->IsMainTank(bot) ? 0.0f : 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
return 1.0f;
}
//float HeiganDanceMultiplier::GetValue(Action* action)
//{
// Unit* boss = AI_VALUE2(Unit*, "find target", "heigan the unclean");
// if (!boss)
// {
// return 1.0f;
// }
// bool platform_phase = boss->IsWithinDist2d(2794.26f, -3706.67f, 10.0f);
// bool eruption_casting = false;
// if (boss->HasUnitState(UNIT_STATE_CASTING))
// {
// Spell* spell = boss->GetCurrentSpell(CURRENT_GENERIC_SPELL);
// if (!spell)
// {
// spell = boss->GetCurrentSpell(CURRENT_CHANNELED_SPELL);
// }
// if (spell)
// {
// SpellInfo const* info = spell->GetSpellInfo();
// bool isEruption = NaxxSpellIds::MatchesAnySpellId(info, {NaxxSpellIds::Eruption10});
// if (!isEruption && info && info->SpellName[LOCALE_enUS])
// {
// // Fallback to name for custom spell data.
// isEruption = botAI->EqualLowercaseName(info->SpellName[LOCALE_enUS], "eruption");
// }
// if (isEruption)
// {
// eruption_casting = true;
// }
// }
// }
// if (dynamic_cast<CombatFormationMoveAction*>(action) ||
// dynamic_cast<CastDisengageAction*>(action) ||
// dynamic_cast<CastBlinkBackAction*>(action) )
// {
// return 0.0f;
// }
// if (!platform_phase && !eruption_casting)
// {
// 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 LoathebGenericMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "loatheb");
if (!boss)
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<CastDebuffSpellOnAttackerAction*>(action) || dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CombatFormationMoveAction*>(action)))
{
return 0.0f;
}
if (!dynamic_cast<CastHealingSpellAction*>(action))
return 1.0f;
Aura* aura = NaxxSpellIds::GetAnyAura(bot, {NaxxSpellIds::NecroticAura10});
if (!aura)
{
// Fallback to name for custom spell data.
aura = botAI->GetAura("necrotic aura", bot);
}
if (!aura || aura->GetDuration() <= 1500)
return 1.0f;
return 0.0f;
}
float ThaddiusGenericMultiplier::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 SapphironGenericMultiplier::GetValue(Action* action)
{
if (!helper.UpdateBossAI())
return 1.0f;
if (dynamic_cast<CastDeathGripAction*>(action) || dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
return 1.0f;
}
float InstructorRazuviousGenericMultiplier::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 KelthuzadGenericMultiplier::GetValue(Action* action)
{
if (!helper.UpdateBossAI())
return 1.0f;
if ((dynamic_cast<DpsAssistAction*>(action) || dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<CastDebuffSpellOnAttackerAction*>(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 AnubrekhanGenericMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "anub'rekhan");
if (!boss)
return 1.0f;
if (NaxxSpellIds::HasAnyAura(
botAI, boss, {NaxxSpellIds::LocustSwarm10, NaxxSpellIds::LocustSwarm10Alt, NaxxSpellIds::LocustSwarm25}) ||
botAI->HasAura("locust swarm", boss))
{
if (dynamic_cast<FleeAction*>(action))
return 0.0f;
}
return 1.0f;
}
float FourhorsemanGenericMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "sir zeliek");
if (!boss)
return 1.0f;
context->GetValue<bool>("neglect threat")->Set(true);
if ((dynamic_cast<DpsAssistAction*>(action) || dynamic_cast<TankAssistAction*>(action)))
return 0.0f;
return 1.0f;
}
// float GothikGenericMultiplier::GetValue(Action* action)
// {
// Unit* boss = AI_VALUE2(Unit*, "find target", "gothik the harvester");
// if (!boss)
// {
// return 1.0f;
// }
// BossAI* boss_ai = dynamic_cast<BossAI*>(boss->GetAI());
// EventMap* eventMap = boss_botAI->GetEvents();
// uint32 curr_phase = eventMap->GetPhaseMask();
// if (curr_phase == 1 && (dynamic_cast<FollowAction*>(action)))
// {
// return 0.0f;
// }
// if (curr_phase == 1 && (dynamic_cast<AttackAction*>(action)))
// {
// Unit* target = action->GetTarget();
// if (target == boss)
// {
// return 0.0f;
// }
// }
// return 1.0f;
// }
float GluthGenericMultiplier::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 = NaxxSpellIds::GetAnyAura(bot, {NaxxSpellIds::MortalWound10, NaxxSpellIds::MortalWound25});
if (!aura)
{
// Fallback to name for custom spell data.
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 (helper.IsZombieChow(target))
return 0.0f;
}
return 1.0f;
}

View File

@@ -0,0 +1,115 @@
#ifndef _PLAYERRBOT_RAIDNAXXMULTIPLIERS_H
#define _PLAYERRBOT_RAIDNAXXMULTIPLIERS_H
#include "Multiplier.h"
#include "RaidNaxxBossHelper.h"
class GrobbulusMultiplier : public Multiplier
{
public:
GrobbulusMultiplier(PlayerbotAI* ai) : Multiplier(ai, "grobbulus") {}
public:
virtual float GetValue(Action* action);
};
//class HeiganDanceMultiplier : public Multiplier
//{
//public:
// HeiganDanceMultiplier(PlayerbotAI* ai) : Multiplier(ai, "helgan dance") {}
//
//public:
// virtual float GetValue(Action* action);
//};
class LoathebGenericMultiplier : public Multiplier
{
public:
LoathebGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "loatheb generic") {}
public:
virtual float GetValue(Action* action);
};
class ThaddiusGenericMultiplier : public Multiplier
{
public:
ThaddiusGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "thaddius generic"), helper(ai) {}
public:
virtual float GetValue(Action* action);
private:
ThaddiusBossHelper helper;
};
class SapphironGenericMultiplier : public Multiplier
{
public:
SapphironGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "sapphiron generic"), helper(ai) {}
virtual float GetValue(Action* action);
private:
SapphironBossHelper helper;
};
class InstructorRazuviousGenericMultiplier : public Multiplier
{
public:
InstructorRazuviousGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "instructor razuvious generic"), helper(ai) {}
virtual float GetValue(Action* action);
private:
RazuviousBossHelper helper;
};
class KelthuzadGenericMultiplier : public Multiplier
{
public:
KelthuzadGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "kelthuzad generic"), helper(ai) {}
virtual float GetValue(Action* action);
private:
KelthuzadBossHelper helper;
};
class AnubrekhanGenericMultiplier : public Multiplier
{
public:
AnubrekhanGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "anubrekhan generic") {}
public:
virtual float GetValue(Action* action);
};
class FourhorsemanGenericMultiplier : public Multiplier
{
public:
FourhorsemanGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "fourhorseman generic") {}
public:
virtual float GetValue(Action* action);
};
// class GothikGenericMultiplier : public Multiplier
// {
// public:
// GothikGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "gothik generic") {}
// public:
// virtual float GetValue(Action* action);
// };
class GluthGenericMultiplier : public Multiplier
{
public:
GluthGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "gluth generic"), helper(ai) {}
float GetValue(Action* action) override;
private:
GluthBossHelper helper;
};
#endif

View File

@@ -0,0 +1,95 @@
// /*
// * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
// and/or modify it under version 3 of the License, or (at your option), any later version.
// */
#ifndef _PLAYERBOT_RAIDNAXXACTIONCONTEXT_H
#define _PLAYERBOT_RAIDNAXXACTIONCONTEXT_H
#include "Action.h"
#include "NamedObjectContext.h"
#include "RaidNaxxActions.h"
class RaidNaxxActionContext : public NamedObjectContext<Action>
{
public:
RaidNaxxActionContext()
{
creators["grobbulus go behind the boss"] = &RaidNaxxActionContext::go_behind_the_boss;
creators["rotate grobbulus"] = &RaidNaxxActionContext::rotate_grobbulus;
creators["grobbulus move center"] = &RaidNaxxActionContext::grobbulus_move_center;
creators["grobbulus move away"] = &RaidNaxxActionContext::grobbulus_move_away;
//creators["heigan dance melee"] = &RaidNaxxActionContext::heigan_dance_melee;
//creators["heigan dance ranged"] = &RaidNaxxActionContext::heigan_dance_ranged;
creators["thaddius attack nearest pet"] = &RaidNaxxActionContext::thaddius_attack_nearest_pet;
// creators["thaddius melee to place"] = &RaidNaxxActionContext::thaddius_tank_to_place;
// creators["thaddius ranged to place"] = &RaidNaxxActionContext::thaddius_ranged_to_place;
creators["thaddius move to platform"] = &RaidNaxxActionContext::thaddius_move_to_platform;
creators["thaddius move polarity"] = &RaidNaxxActionContext::thaddius_move_polarity;
creators["razuvious use obedience crystal"] = &RaidNaxxActionContext::razuvious_use_obedience_crystal;
creators["razuvious target"] = &RaidNaxxActionContext::razuvious_target;
creators["horseman attract alternatively"] = &RaidNaxxActionContext::horseman_attract_alternatively;
creators["horseman attack in order"] = &RaidNaxxActionContext::horseman_attack_in_order;
creators["sapphiron ground position"] = &RaidNaxxActionContext::sapphiron_ground_position;
creators["sapphiron flight position"] = &RaidNaxxActionContext::sapphiron_flight_position;
creators["kel'thuzad choose target"] = &RaidNaxxActionContext::kelthuzad_choose_target;
creators["kel'thuzad position"] = &RaidNaxxActionContext::kelthuzad_position;
creators["anub'rekhan choose target"] = &RaidNaxxActionContext::anubrekhan_choose_target;
creators["anub'rekhan position"] = &RaidNaxxActionContext::anubrekhan_position;
creators["gluth choose target"] = &RaidNaxxActionContext::gluth_choose_target;
creators["gluth position"] = &RaidNaxxActionContext::gluth_position;
creators["gluth slowdown"] = &RaidNaxxActionContext::gluth_slowdown;
//creators["patchwerk ranged position"] = &RaidNaxxActionContext::patchwerk_ranged_position;
creators["loatheb position"] = &RaidNaxxActionContext::loatheb_position;
creators["loatheb choose target"] = &RaidNaxxActionContext::loatheb_choose_target;
}
private:
static Action* go_behind_the_boss(PlayerbotAI* ai) { return new GrobbulusGoBehindAction(ai); }
static Action* rotate_grobbulus(PlayerbotAI* ai) { return new GrobbulusRotateAction(ai); }
static Action* grobbulus_move_center(PlayerbotAI* ai) { return new GrobblulusMoveCenterAction(ai); }
static Action* grobbulus_move_away(PlayerbotAI* ai) { return new GrobbulusMoveAwayAction(ai); }
//static Action* heigan_dance_melee(PlayerbotAI* ai) { return new HeiganDanceMeleeAction(ai); }
//static Action* heigan_dance_ranged(PlayerbotAI* ai) { return new HeiganDanceRangedAction(ai); }
static Action* thaddius_attack_nearest_pet(PlayerbotAI* ai) { return new ThaddiusAttackNearestPetAction(ai); }
// static Action* thaddius_tank_to_place(PlayerbotAI* ai) { return new ThaddiusMeleeToPlaceAction(ai); }
// static Action* thaddius_ranged_to_place(PlayerbotAI* ai) { return new ThaddiusRangedToPlaceAction(ai); }
static Action* thaddius_move_to_platform(PlayerbotAI* ai) { return new ThaddiusMoveToPlatformAction(ai); }
static Action* thaddius_move_polarity(PlayerbotAI* ai) { return new ThaddiusMovePolarityAction(ai); }
static Action* razuvious_target(PlayerbotAI* ai) { return new RazuviousTargetAction(ai); }
static Action* razuvious_use_obedience_crystal(PlayerbotAI* ai)
{
return new RazuviousUseObedienceCrystalAction(ai);
}
static Action* horseman_attract_alternatively(PlayerbotAI* ai)
{
return new HorsemanAttractAlternativelyAction(ai);
}
static Action* horseman_attack_in_order(PlayerbotAI* ai) { return new HorsemanAttactInOrderAction(ai); }
// static Action* sapphiron_ground_main_tank_position(PlayerbotAI* ai) { return new
// SapphironGroundMainTankPositionAction(ai); }
static Action* sapphiron_ground_position(PlayerbotAI* ai) { return new SapphironGroundPositionAction(ai); }
static Action* sapphiron_flight_position(PlayerbotAI* ai) { return new SapphironFlightPositionAction(ai); }
// static Action* sapphiron_avoid_chill(PlayerbotAI* ai) { return new SapphironAvoidChillAction(ai); }
static Action* kelthuzad_choose_target(PlayerbotAI* ai) { return new KelthuzadChooseTargetAction(ai); }
static Action* kelthuzad_position(PlayerbotAI* ai) { return new KelthuzadPositionAction(ai); }
static Action* anubrekhan_choose_target(PlayerbotAI* ai) { return new AnubrekhanChooseTargetAction(ai); }
static Action* anubrekhan_position(PlayerbotAI* ai) { return new AnubrekhanPositionAction(ai); }
static Action* gluth_choose_target(PlayerbotAI* ai) { return new GluthChooseTargetAction(ai); }
static Action* gluth_position(PlayerbotAI* ai) { return new GluthPositionAction(ai); }
static Action* gluth_slowdown(PlayerbotAI* ai) { return new GluthSlowdownAction(ai); }
//static Action* patchwerk_ranged_position(PlayerbotAI* ai) { return new PatchwerkRangedPositionAction(ai); }
static Action* loatheb_position(PlayerbotAI* ai) { return new LoathebPositionAction(ai); }
static Action* loatheb_choose_target(PlayerbotAI* ai) { return new LoathebChooseTargetAction(ai); }
};
#endif

View File

@@ -0,0 +1,86 @@
// /*
// * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
// and/or modify it under version 3 of the License, or (at your option), any later version.
// */
#ifndef _PLAYERBOT_RAIDNAXXTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDNAXXTRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h"
#include "RaidNaxxTriggers.h"
class RaidNaxxTriggerContext : public NamedObjectContext<Trigger>
{
public:
RaidNaxxTriggerContext()
{
creators["mutating injection melee"] = &RaidNaxxTriggerContext::mutating_injection_melee;
creators["mutating injection ranged"] = &RaidNaxxTriggerContext::mutating_injection_ranged;
creators["mutating injection removed"] = &RaidNaxxTriggerContext::mutating_injection_removed;
creators["grobbulus cloud"] = &RaidNaxxTriggerContext::grobbulus_cloud;
//creators["heigan melee"] = &RaidNaxxTriggerContext::heigan_melee;
//creators["heigan ranged"] = &RaidNaxxTriggerContext::heigan_ranged;
creators["thaddius phase pet"] = &RaidNaxxTriggerContext::thaddius_phase_pet;
creators["thaddius phase pet lose aggro"] = &RaidNaxxTriggerContext::thaddius_phase_pet_lose_aggro;
creators["thaddius phase transition"] = &RaidNaxxTriggerContext::thaddius_phase_transition;
creators["thaddius phase thaddius"] = &RaidNaxxTriggerContext::thaddius_phase_thaddius;
creators["razuvious tank"] = &RaidNaxxTriggerContext::razuvious_tank;
creators["razuvious nontank"] = &RaidNaxxTriggerContext::razuvious_nontank;
creators["horseman attractors"] = &RaidNaxxTriggerContext::horseman_attractors;
creators["horseman except attractors"] = &RaidNaxxTriggerContext::horseman_except_attractors;
creators["sapphiron ground"] = &RaidNaxxTriggerContext::sapphiron_ground;
creators["sapphiron flight"] = &RaidNaxxTriggerContext::sapphiron_flight;
creators["kel'thuzad"] = &RaidNaxxTriggerContext::kelthuzad;
creators["anub'rekhan"] = &RaidNaxxTriggerContext::anubrekhan;
creators["faerlina"] = &RaidNaxxTriggerContext::faerlina;
creators["maexxna"] = &RaidNaxxTriggerContext::maexxna;
//creators["patchwerk tank"] = &RaidNaxxTriggerContext::patchwerk_tank;
//creators["patchwerk non-tank"] = &RaidNaxxTriggerContext::patchwerk_non_tank;
//creators["patchwerk ranged"] = &RaidNaxxTriggerContext::patchwerk_ranged;
creators["gluth"] = &RaidNaxxTriggerContext::gluth;
creators["gluth main tank mortal wound"] = &RaidNaxxTriggerContext::gluth_main_tank_mortal_wound;
creators["loatheb"] = &RaidNaxxTriggerContext::loatheb;
}
private:
static Trigger* mutating_injection_melee(PlayerbotAI* ai) { return new MutatingInjectionMeleeTrigger(ai); }
static Trigger* mutating_injection_ranged(PlayerbotAI* ai) { return new MutatingInjectionRangedTrigger(ai); }
static Trigger* mutating_injection_removed(PlayerbotAI* ai) { return new MutatingInjectionRemovedTrigger(ai); }
static Trigger* grobbulus_cloud(PlayerbotAI* ai) { return new GrobbulusCloudTrigger(ai); }
//static Trigger* heigan_melee(PlayerbotAI* ai) { return new HeiganMeleeTrigger(ai); }
//static Trigger* heigan_ranged(PlayerbotAI* ai) { return new HeiganRangedTrigger(ai); }
static Trigger* thaddius_phase_pet(PlayerbotAI* ai) { return new ThaddiusPhasePetTrigger(ai); }
static Trigger* thaddius_phase_pet_lose_aggro(PlayerbotAI* ai) { return new ThaddiusPhasePetLoseAggroTrigger(ai); }
static Trigger* thaddius_phase_transition(PlayerbotAI* ai) { return new ThaddiusPhaseTransitionTrigger(ai); }
static Trigger* thaddius_phase_thaddius(PlayerbotAI* ai) { return new ThaddiusPhaseThaddiusTrigger(ai); }
static Trigger* razuvious_tank(PlayerbotAI* ai) { return new RazuviousTankTrigger(ai); }
static Trigger* razuvious_nontank(PlayerbotAI* ai) { return new RazuviousNontankTrigger(ai); }
static Trigger* horseman_attractors(PlayerbotAI* ai) { return new HorsemanAttractorsTrigger(ai); }
static Trigger* horseman_except_attractors(PlayerbotAI* ai) { return new HorsemanExceptAttractorsTrigger(ai); }
static Trigger* sapphiron_ground(PlayerbotAI* ai) { return new SapphironGroundTrigger(ai); }
static Trigger* sapphiron_flight(PlayerbotAI* ai) { return new SapphironFlightTrigger(ai); }
static Trigger* kelthuzad(PlayerbotAI* ai) { return new KelthuzadTrigger(ai); }
static Trigger* anubrekhan(PlayerbotAI* ai) { return new AnubrekhanTrigger(ai); }
static Trigger* faerlina(PlayerbotAI* ai) { return new FaerlinaTrigger(ai); }
static Trigger* maexxna(PlayerbotAI* ai) { return new MaexxnaTrigger(ai); }
//static Trigger* patchwerk_tank(PlayerbotAI* ai) { return new PatchwerkTankTrigger(ai); }
//static Trigger* patchwerk_non_tank(PlayerbotAI* ai) { return new PatchwerkNonTankTrigger(ai); }
//static Trigger* patchwerk_ranged(PlayerbotAI* ai) { return new PatchwerkRangedTrigger(ai); }
static Trigger* gluth(PlayerbotAI* ai) { return new GluthTrigger(ai); }
static Trigger* gluth_main_tank_mortal_wound(PlayerbotAI* ai) { return new GluthMainTankMortalWoundTrigger(ai); }
static Trigger* loatheb(PlayerbotAI* ai) { return new LoathebTrigger(ai); }
};
#endif

View File

@@ -0,0 +1,156 @@
#include "RaidNaxxStrategy.h"
#include "RaidNaxxMultipliers.h"
void RaidNaxxStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
// Grobbulus
triggers.push_back(new TriggerNode("mutating injection melee",
{ NextAction("grobbulus move away", ACTION_RAID + 2) }
));
triggers.push_back(new TriggerNode("mutating injection ranged",
{ NextAction("grobbulus go behind the boss", ACTION_RAID + 2) }
));
triggers.push_back(new TriggerNode("mutating injection removed",
{ NextAction("grobbulus move center", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("grobbulus cloud",
{ NextAction("rotate grobbulus", ACTION_RAID + 1) }
));
// Heigan the Unclean
//triggers.push_back(new TriggerNode("heigan melee",
// { NextAction("heigan dance melee", ACTION_RAID + 1) }
//));
//triggers.push_back(new TriggerNode("heigan ranged",
// { NextAction("heigan dance ranged", ACTION_RAID + 1) }
//));
// Kel'Thuzad
triggers.push_back(
new TriggerNode("kel'thuzad",
{
NextAction("kel'thuzad position", ACTION_RAID + 2),
NextAction("kel'thuzad choose target", ACTION_RAID + 1)
})
);
// Anub'Rekhan
triggers.push_back(new TriggerNode("anub'rekhan",
{ NextAction("anub'rekhan position", ACTION_RAID + 1) }
));
// Grand Widow Faerlina
triggers.push_back(new TriggerNode("faerlina",
{ NextAction("avoid aoe", ACTION_RAID + 1) }
));
// Maexxna
triggers.push_back(
new TriggerNode("maexxna",
{
NextAction("rear flank", ACTION_RAID + 1),
NextAction("avoid aoe", ACTION_RAID + 1)
})
);
// Patchwerk
//triggers.push_back(new TriggerNode("patchwerk tank",
// { NextAction("tank face", ACTION_RAID + 2) }
//));
//triggers.push_back(new TriggerNode("patchwerk ranged",
// { NextAction("patchwerk ranged position", ACTION_RAID + 2) }
//));
//triggers.push_back(new TriggerNode("patchwerk non-tank",
// { NextAction("rear flank", ACTION_RAID + 1) }
//));
// Thaddius
triggers.push_back(new TriggerNode("thaddius phase pet",
{ NextAction("thaddius attack nearest pet", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("thaddius phase pet lose aggro",
{ NextAction("taunt spell", ACTION_RAID + 2) }
));
triggers.push_back(new TriggerNode("thaddius phase transition",
{ NextAction("thaddius move to platform", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("thaddius phase thaddius",
{ NextAction("thaddius move polarity", ACTION_RAID + 1) }
));
// Instructor Razuvious
triggers.push_back(new TriggerNode("razuvious tank",
{ NextAction("razuvious use obedience crystal", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("razuvious nontank",
{ NextAction("razuvious target", ACTION_RAID + 1) }
));
// four horseman
triggers.push_back(new TriggerNode("horseman attractors",
{ NextAction("horseman attract alternatively", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("horseman except attractors",
{ NextAction("horseman attack in order", ACTION_RAID + 1) }
));
// sapphiron
triggers.push_back(new TriggerNode("sapphiron ground",
{ NextAction("sapphiron ground position", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("sapphiron flight",
{ NextAction("sapphiron flight position", ACTION_RAID + 1) }
));
// Gluth
triggers.push_back(
new TriggerNode("gluth",
{
NextAction("gluth choose target", ACTION_RAID + 1),
NextAction("gluth position", ACTION_RAID + 1),
NextAction("gluth slowdown", ACTION_RAID)
})
);
triggers.push_back(new TriggerNode("gluth main tank mortal wound",
{ NextAction("taunt spell", ACTION_RAID + 1) }
));
// Loatheb
triggers.push_back(
new TriggerNode("loatheb",
{
NextAction("loatheb position", ACTION_RAID + 1),
NextAction("loatheb choose target", ACTION_RAID + 1)
})
);
}
void RaidNaxxStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{
multipliers.push_back(new GrobbulusMultiplier(botAI));
//multipliers.push_back(new HeiganDanceMultiplier(botAI));
multipliers.push_back(new LoathebGenericMultiplier(botAI));
multipliers.push_back(new ThaddiusGenericMultiplier(botAI));
multipliers.push_back(new SapphironGenericMultiplier(botAI));
multipliers.push_back(new InstructorRazuviousGenericMultiplier(botAI));
multipliers.push_back(new KelthuzadGenericMultiplier(botAI));
multipliers.push_back(new AnubrekhanGenericMultiplier(botAI));
multipliers.push_back(new FourhorsemanGenericMultiplier(botAI));
// multipliers.push_back(new GothikGenericMultiplier(botAI));
multipliers.push_back(new GluthGenericMultiplier(botAI));
}

View File

@@ -0,0 +1,18 @@
#ifndef _PLAYERBOT_RAIDNAXXSTRATEGY_H
#define _PLAYERBOT_RAIDNAXXSTRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h"
class RaidNaxxStrategy : public Strategy
{
public:
RaidNaxxStrategy(PlayerbotAI* ai) : Strategy(ai) {}
virtual std::string const getName() override { return "naxx"; }
virtual void InitTriggers(std::vector<TriggerNode*>& triggers) override;
virtual void InitMultipliers(std::vector<Multiplier*>& multipliers) override;
};
#endif

View File

@@ -0,0 +1,257 @@
#include "RaidNaxxTriggers.h"
#include "Playerbots.h"
#include "RaidNaxxSpellIds.h"
#include "Timer.h"
#include "Trigger.h"
bool MutatingInjectionMeleeTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "grobbulus");
if (!boss)
return false;
return MutatingInjectionTrigger::IsActive() && !botAI->IsRanged(bot);
}
bool MutatingInjectionRangedTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "grobbulus");
if (!boss)
return false;
return MutatingInjectionTrigger::IsActive() && botAI->IsRanged(bot);
}
bool AuraRemovedTrigger::IsActive()
{
bool check = botAI->HasAura(name, bot, false, false, -1, true);
bool ret = false;
if (prev_check && !check)
ret = true;
prev_check = check;
return ret;
}
bool MutatingInjectionRemovedTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "grobbulus");
if (!boss)
return false;
return HasNoAuraTrigger::IsActive() && botAI->GetState() == BOT_STATE_COMBAT && botAI->IsRanged(bot);
}
bool GrobbulusCloudTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "grobbulus");
if (!boss)
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);
if (!AI_VALUE2(bool, "has aggro", "boss target"))
return false;
uint32 now = getMSTime();
bool poison_cloud_casting = false;
if (boss->HasUnitState(UNIT_STATE_CASTING))
{
Spell* spell = boss->GetCurrentSpell(CURRENT_GENERIC_SPELL);
if (!spell)
spell = boss->GetCurrentSpell(CURRENT_CHANNELED_SPELL);
if (spell)
poison_cloud_casting = NaxxSpellIds::MatchesAnySpellId(spell->GetSpellInfo(), {NaxxSpellIds::PoisonCloud});
}
if (!poison_cloud_casting && last_cloud_ms != 0 && now - last_cloud_ms < CloudRotationDelayMs)
return false;
last_cloud_ms = now;
return true;
}
//bool HeiganMeleeTrigger::IsActive()
//{
// Unit* heigan = AI_VALUE2(Unit*, "find target", "heigan the unclean");
// if (!heigan)
// {
// return false;
// }
// return !botAI->IsRanged(bot);
//}
//
//bool HeiganRangedTrigger::IsActive()
//{
// Unit* heigan = AI_VALUE2(Unit*, "find target", "heigan the unclean");
// if (!heigan)
// {
// return false;
// }
// return botAI->IsRanged(bot);
//}
bool RazuviousTankTrigger::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 RazuviousNontankTrigger::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 HorsemanAttractorsTrigger::IsActive()
{
if (!helper.UpdateBossAI())
return false;
return helper.IsAttracter(bot);
}
bool HorsemanExceptAttractorsTrigger::IsActive()
{
if (!helper.UpdateBossAI())
return false;
return !helper.IsAttracter(bot);
}
bool SapphironGroundTrigger::IsActive()
{
if (!helper.UpdateBossAI())
return false;
return helper.IsPhaseGround();
}
bool SapphironFlightTrigger::IsActive()
{
if (!helper.UpdateBossAI())
return false;
return helper.IsPhaseFlight();
}
bool GluthTrigger::IsActive() { return helper.UpdateBossAI(); }
bool GluthMainTankMortalWoundTrigger::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 = NaxxSpellIds::GetAnyAura(mt, {NaxxSpellIds::MortalWound10, NaxxSpellIds::MortalWound25});
if (!aura)
{
// Fallback to name for custom spell data.
aura = botAI->GetAura("mortal wound", mt, false, true);
}
if (!aura || aura->GetStackAmount() < 5)
return false;
return true;
}
bool KelthuzadTrigger::IsActive() { return helper.UpdateBossAI(); }
bool AnubrekhanTrigger::IsActive() {
Unit* boss = AI_VALUE2(Unit*, "find target", "anub'rekhan");
if (!boss)
return false;
return true;
}
bool FaerlinaTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "grand widow faerlina");
if (!boss)
return false;
return true;
}
bool MaexxnaTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "maexxna");
if (!boss)
return false;
return !botAI->IsTank(bot);
}
//bool PatchwerkTankTrigger::IsActive()
//{
// Unit* boss = AI_VALUE2(Unit*, "find target", "patchwerk");
// if (!boss)
// {
// return false;
// }
// return !botAI->IsTank(bot) && !botAI->IsRanged(bot);
//}
//
//bool PatchwerkRangedTrigger::IsActive()
//{
// Unit* boss = AI_VALUE2(Unit*, "find target", "patchwerk");
// if (!boss)
// {
// return false;
// }
// return !botAI->IsTank(bot) && botAI->IsRanged(bot);
//}
//
//bool PatchwerkNonTankTrigger::IsActive()
//{
// Unit* boss = AI_VALUE2(Unit*, "find target", "patchwerk");
// if (!boss)
// {
// return false;
// }
// return !botAI->IsTank(bot);
//}
bool LoathebTrigger::IsActive() { return helper.UpdateBossAI(); }
bool ThaddiusPhasePetTrigger::IsActive()
{
if (!helper.UpdateBossAI())
return false;
return helper.IsPhasePet();
}
bool ThaddiusPhaseTransitionTrigger::IsActive()
{
if (!helper.UpdateBossAI())
return false;
return helper.IsPhaseTransition();
}
bool ThaddiusPhaseThaddiusTrigger::IsActive()
{
if (!helper.UpdateBossAI())
return false;
return helper.IsPhaseThaddius();
}

View File

@@ -0,0 +1,259 @@
#ifndef _PLAYERBOT_RAIDNAXXTRIGGERS_H
#define _PLAYERBOT_RAIDNAXXTRIGGERS_H
#include "EventMap.h"
#include "GenericTriggers.h"
#include "PlayerbotAIConfig.h"
#include "RaidNaxxBossHelper.h"
#include "Trigger.h"
class MutatingInjectionTrigger : public HasAuraTrigger
{
public:
MutatingInjectionTrigger(PlayerbotAI* ai) : HasAuraTrigger(ai, "mutating injection", 1) {}
};
class MutatingInjectionMeleeTrigger : public MutatingInjectionTrigger
{
public:
MutatingInjectionMeleeTrigger(PlayerbotAI* ai) : MutatingInjectionTrigger(ai) {}
bool IsActive() override;
};
class MutatingInjectionRangedTrigger : public MutatingInjectionTrigger
{
public:
MutatingInjectionRangedTrigger(PlayerbotAI* ai) : MutatingInjectionTrigger(ai) {}
bool IsActive() override;
};
class AuraRemovedTrigger : public Trigger
{
public:
AuraRemovedTrigger(PlayerbotAI* botAI, std::string name) : Trigger(botAI, name, 1)
{
this->prev_check = false;
}
virtual bool IsActive() override;
protected:
bool prev_check;
};
class MutatingInjectionRemovedTrigger : public HasNoAuraTrigger
{
public:
MutatingInjectionRemovedTrigger(PlayerbotAI* ai) : HasNoAuraTrigger(ai, "mutating injection") {}
virtual bool IsActive();
};
class GrobbulusCloudTrigger : public Trigger
{
public:
GrobbulusCloudTrigger(PlayerbotAI* ai) : Trigger(ai, "grobbulus cloud event"), last_cloud_ms(0) {}
bool IsActive() override;
private:
uint32 last_cloud_ms;
static constexpr uint32 CloudRotationDelayMs = 15000;
};
//class HeiganMeleeTrigger : public Trigger
//{
//public:
// HeiganMeleeTrigger(PlayerbotAI* ai) : Trigger(ai, "heigan melee") {}
// virtual bool IsActive();
//};
//
//class HeiganRangedTrigger : public Trigger
//{
//public:
// HeiganRangedTrigger(PlayerbotAI* ai) : Trigger(ai, "heigan ranged") {}
// bool IsActive() override;
//};
class RazuviousTankTrigger : public Trigger
{
public:
RazuviousTankTrigger(PlayerbotAI* ai) : Trigger(ai, "instructor razuvious tank"), helper(ai) {}
bool IsActive() override;
private:
RazuviousBossHelper helper;
};
class RazuviousNontankTrigger : public Trigger
{
public:
RazuviousNontankTrigger(PlayerbotAI* ai) : Trigger(ai, "instructor razuvious non-tank"), helper(ai) {}
bool IsActive() override;
private:
RazuviousBossHelper helper;
};
class KelthuzadTrigger : public Trigger
{
public:
KelthuzadTrigger(PlayerbotAI* ai) : Trigger(ai, "kel'thuzad trigger"), helper(ai) {}
bool IsActive() override;
private:
KelthuzadBossHelper helper;
};
class AnubrekhanTrigger : public Trigger
{
public:
AnubrekhanTrigger(PlayerbotAI* ai) : Trigger(ai, "anub'rekhan") {}
bool IsActive() override;
};
class FaerlinaTrigger : public Trigger
{
public:
FaerlinaTrigger(PlayerbotAI* ai) : Trigger(ai, "faerlina") {}
bool IsActive() override;
};
class MaexxnaTrigger : public Trigger
{
public:
MaexxnaTrigger(PlayerbotAI* ai) : Trigger(ai, "maexxna") {}
bool IsActive() override;
};
//class PatchwerkTankTrigger : public Trigger
//{
//public:
// PatchwerkTankTrigger(PlayerbotAI* ai) : Trigger(ai, "patchwerk tank") {}
// bool IsActive() override;
//};
//
//class PatchwerkNonTankTrigger : public Trigger
//{
//public:
// PatchwerkNonTankTrigger(PlayerbotAI* ai) : Trigger(ai, "patchwerk non-tank") {}
// bool IsActive() override;
//};
//
//class PatchwerkRangedTrigger : public Trigger
//{
//public:
// PatchwerkRangedTrigger(PlayerbotAI* ai) : Trigger(ai, "patchwerk ranged") {}
// bool IsActive() override;
//};
class ThaddiusPhasePetTrigger : public Trigger
{
public:
ThaddiusPhasePetTrigger(PlayerbotAI* ai) : Trigger(ai, "thaddius phase pet"), helper(ai) {}
bool IsActive() override;
private:
ThaddiusBossHelper helper;
};
class ThaddiusPhasePetLoseAggroTrigger : public ThaddiusPhasePetTrigger
{
public:
ThaddiusPhasePetLoseAggroTrigger(PlayerbotAI* ai) : ThaddiusPhasePetTrigger(ai) {}
virtual bool IsActive()
{
Unit* target = AI_VALUE(Unit*, "current target");
return ThaddiusPhasePetTrigger::IsActive() && botAI->IsTank(bot) && target && target->GetVictim() != bot;
}
};
class ThaddiusPhaseTransitionTrigger : public Trigger
{
public:
ThaddiusPhaseTransitionTrigger(PlayerbotAI* ai) : Trigger(ai, "thaddius phase transition"), helper(ai) {}
bool IsActive() override;
private:
ThaddiusBossHelper helper;
};
class ThaddiusPhaseThaddiusTrigger : public Trigger
{
public:
ThaddiusPhaseThaddiusTrigger(PlayerbotAI* ai) : Trigger(ai, "thaddius phase thaddius"), helper(ai) {}
bool IsActive() override;
private:
ThaddiusBossHelper helper;
};
class HorsemanAttractorsTrigger : public Trigger
{
public:
HorsemanAttractorsTrigger(PlayerbotAI* ai) : Trigger(ai, "fourhorsemen attractors"), helper(ai) {}
bool IsActive() override;
private:
FourhorsemanBossHelper helper;
};
class HorsemanExceptAttractorsTrigger : public Trigger
{
public:
HorsemanExceptAttractorsTrigger(PlayerbotAI* ai) : Trigger(ai, "fourhorsemen except attractors"), helper(ai) {}
bool IsActive() override;
private:
FourhorsemanBossHelper helper;
};
class SapphironGroundTrigger : public Trigger
{
public:
SapphironGroundTrigger(PlayerbotAI* ai) : Trigger(ai, "sapphiron ground"), helper(ai) {}
bool IsActive() override;
private:
SapphironBossHelper helper;
};
class SapphironFlightTrigger : public Trigger
{
public:
SapphironFlightTrigger(PlayerbotAI* ai) : Trigger(ai, "sapphiron flight"), helper(ai) {}
bool IsActive() override;
private:
SapphironBossHelper helper;
};
class GluthTrigger : public Trigger
{
public:
GluthTrigger(PlayerbotAI* ai) : Trigger(ai, "gluth trigger"), helper(ai) {}
bool IsActive() override;
private:
GluthBossHelper helper;
};
class GluthMainTankMortalWoundTrigger : public Trigger
{
public:
GluthMainTankMortalWoundTrigger(PlayerbotAI* ai) : Trigger(ai, "gluth main tank mortal wound trigger"), helper(ai) {}
bool IsActive() override;
private:
GluthBossHelper helper;
};
class LoathebTrigger : public Trigger
{
public:
LoathebTrigger(PlayerbotAI* ai) : Trigger(ai, "loatheb"), helper(ai) {}
bool IsActive() override;
private:
LoathebBossHelper helper;
};
#endif

View File

@@ -0,0 +1,533 @@
#ifndef _PLAYERBOT_RAIDNAXXBOSSHELPER_H
#define _PLAYERBOT_RAIDNAXXBOSSHELPER_H
#include <string>
#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 "ScriptedCreature.h"
#include "SharedDefines.h"
#include "Spell.h"
#include "Timer.h"
#include "RaidNaxxSpellIds.h"
const uint32 NAXX_MAP_ID = 533;
template <class BossAiType>
class GenericBossHelper : public AiObject
{
public:
GenericBossHelper(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;
_ai = dynamic_cast<BossAiType*>(_target->GetAI());
if (!_ai)
return false;
_event_map = &_ai->events;
if (!_event_map)
return false;
}
if (!_event_map)
return false;
_timer = getMSTime();
return true;
}
virtual void Reset()
{
_unit = nullptr;
_target = nullptr;
_ai = nullptr;
_event_map = nullptr;
_timer = 0;
}
protected:
std::string _name;
Unit* _unit = nullptr;
Creature* _target = nullptr;
BossAiType* _ai = nullptr;
EventMap* _event_map = nullptr;
uint32 _timer = 0;
};
class KelthuzadBossHelper : public AiObject
{
public:
KelthuzadBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {}
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 UpdateBossAI()
{
if (!bot->IsInCombat())
Reset();
if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive()))
Reset();
if (!_unit)
_unit = AI_VALUE2(Unit*, "find target", "kel'thuzad");
return _unit != nullptr;
}
bool IsPhaseOne() { return _unit && _unit->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE); }
bool IsPhaseTwo() { return _unit && !_unit->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE); }
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;
}
private:
void Reset() { _unit = nullptr; }
Unit* _unit = nullptr;
};
class RazuviousBossHelper : public AiObject
{
public:
RazuviousBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {}
bool UpdateBossAI()
{
if (!bot->IsInCombat())
Reset();
if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive()))
Reset();
if (!_unit)
_unit = AI_VALUE2(Unit*, "find target", "instructor razuvious");
return _unit != nullptr;
}
private:
void Reset() { _unit = nullptr; }
Unit* _unit = nullptr;
};
class SapphironBossHelper : public AiObject
{
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;
SapphironBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {}
bool UpdateBossAI()
{
if (!bot->IsInCombat())
Reset();
if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive()))
Reset();
if (!_unit)
{
_unit = AI_VALUE2(Unit*, "find target", "sapphiron");
if (!_unit)
return false;
}
bool now_flying = _unit->IsFlying();
if (_was_flying && !now_flying)
_last_land_ms = getMSTime();
_was_flying = now_flying;
return true;
}
bool IsPhaseGround() { return _unit && !_unit->IsFlying(); }
bool IsPhaseFlight() { return _unit && _unit->IsFlying(); }
bool JustLanded()
{
if (!_last_land_ms)
return false;
return getMSTime() - _last_land_ms <= POSITION_TIME_AFTER_LANDED;
}
bool WaitForExplosion()
{
if (!IsPhaseFlight())
return false;
Group* group = bot->GetGroup();
if (!group)
return false;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member &&
(NaxxSpellIds::HasAnyAura(botAI, member, {NaxxSpellIds::Icebolt10, NaxxSpellIds::Icebolt25}) ||
botAI->HasAura("icebolt", member, false, false, -1, true)))
{
return true;
}
}
return false;
}
bool FindPosToAvoidChill(std::vector<float>& dest)
{
Aura* aura = NaxxSpellIds::GetAnyAura(bot, {NaxxSpellIds::Chill25});
if (!aura)
{
// Fallback to name for custom spell data.
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:
void Reset()
{
_unit = nullptr;
_was_flying = false;
_last_land_ms = 0;
}
const uint32 POSITION_TIME_AFTER_LANDED = 5000;
Unit* _unit = nullptr;
bool _was_flying = false;
uint32 _last_land_ms = 0;
};
class GluthBossHelper : public AiObject
{
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;
GluthBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {}
bool UpdateBossAI()
{
if (!bot->IsInCombat())
Reset();
if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive()))
Reset();
if (!_unit)
{
_unit = AI_VALUE2(Unit*, "find target", "gluth");
if (!_unit)
return false;
}
if (_unit->IsInCombat())
{
if (_combat_start_ms == 0)
_combat_start_ms = getMSTime();
}
else
_combat_start_ms = 0;
return true;
}
bool BeforeDecimate()
{
if (!_unit || !_unit->HasUnitState(UNIT_STATE_CASTING))
return false;
Spell* spell = _unit->GetCurrentSpell(CURRENT_GENERIC_SPELL);
if (!spell)
spell = _unit->GetCurrentSpell(CURRENT_CHANNELED_SPELL);
if (!spell)
return false;
SpellInfo const* info = spell->GetSpellInfo();
if (!info)
return false;
if (NaxxSpellIds::MatchesAnySpellId(
info, {NaxxSpellIds::Decimate10, NaxxSpellIds::Decimate25, NaxxSpellIds::Decimate25Alt}))
return true;
// Fallback to name for custom spell data.
return info->SpellName[LOCALE_enUS] && botAI->EqualLowercaseName(info->SpellName[LOCALE_enUS], "decimate");
}
bool JustStartCombat() const { return _combat_start_ms != 0 && getMSTime() - _combat_start_ms < 10000; }
bool IsZombieChow(Unit* unit) const { return unit && botAI->EqualLowercaseName(unit->GetName(), "zombie chow"); }
private:
void Reset()
{
_unit = nullptr;
_combat_start_ms = 0;
}
Unit* _unit = nullptr;
uint32 _combat_start_ms = 0;
};
class LoathebBossHelper : public AiObject
{
public:
const std::pair<float, float> mainTankPos = {2877.57f, -3967.00f};
const std::pair<float, float> rangePos = {2896.96f, -3980.61f};
LoathebBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {}
bool UpdateBossAI()
{
if (!bot->IsInCombat())
Reset();
if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive()))
Reset();
if (!_unit)
_unit = AI_VALUE2(Unit*, "find target", "loatheb");
return _unit != nullptr;
}
private:
void Reset() { _unit = nullptr; }
Unit* _unit = nullptr;
};
class FourhorsemanBossHelper : public AiObject
{
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)
FourhorsemanBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {}
bool UpdateBossAI()
{
if (!bot->IsInCombat())
Reset();
else if (_combat_start_ms == 0)
_combat_start_ms = getMSTime();
if (_sir && (!_sir->IsInWorld() || !_sir->IsAlive()))
Reset();
if (!_sir)
{
_sir = AI_VALUE2(Unit*, "find target", "sir zeliek");
if (!_sir)
return false;
}
_lady = AI_VALUE2(Unit*, "find target", "lady blaumeux");
return true;
}
void Reset()
{
_sir = nullptr;
_lady = nullptr;
_combat_start_ms = 0;
posToGo = 0;
}
bool IsAttracter(Player* bot)
{
Difficulty diff = bot->GetRaidDifficulty();
if (diff == RAID_DIFFICULTY_25MAN_NORMAL)
{
return botAI->IsAssistRangedDpsOfIndex(bot, 0) || botAI->IsAssistHealOfIndex(bot, 0) ||
botAI->IsAssistHealOfIndex(bot, 1) || botAI->IsAssistHealOfIndex(bot, 2);
}
return botAI->IsAssistRangedDpsOfIndex(bot, 0) || botAI->IsAssistHealOfIndex(bot, 0);
}
void CalculatePosToGo(Player* bot)
{
bool raid25 = bot->GetRaidDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL;
Unit* lady = _lady;
if (!lady)
posToGo = 0;
else
{
uint32 elapsed_ms = _combat_start_ms ? getMSTime() - _combat_start_ms : 0;
// Interval: 24s - 15s - 15s - ...
posToGo = !(elapsed_ms <= 9000 || ((elapsed_ms - 9000) / 67500) % 2 == 0);
if (botAI->IsAssistRangedDpsOfIndex(bot, 0) || (raid25 && botAI->IsAssistHealOfIndex(bot, 1)))
posToGo = 1 - posToGo;
}
}
std::pair<float, float> CurrentAttractPos()
{
bool raid25 = bot->GetRaidDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL;
float posX = attractPos[posToGo].first, posY = attractPos[posToGo].second;
if (posToGo == 1)
{
float offset_x = 0.0f;
float offset_y = 0.0f;
float bias = 4.5f;
if (raid25)
{
offset_x = -bias;
offset_y = bias;
}
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;
uint32 _combat_start_ms = 0;
int posToGo = 0;
};
class ThaddiusBossHelper : public AiObject
{
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;
ThaddiusBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {}
bool UpdateBossAI()
{
if (!bot->IsInCombat())
Reset();
if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive()))
Reset();
if (!_unit)
{
_unit = AI_VALUE2(Unit*, "find target", "thaddius");
if (!_unit)
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 && _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:
void Reset()
{
_unit = nullptr;
feugen = nullptr;
stalagg = nullptr;
}
Unit* _unit = nullptr;
Unit* feugen = nullptr;
Unit* stalagg = nullptr;
};
#endif

View File

@@ -0,0 +1,165 @@
#ifndef _PLAYERBOT_RAIDNAXXSPELLIDS_H
#define _PLAYERBOT_RAIDNAXXSPELLIDS_H
#include <initializer_list>
#include "PlayerbotAI.h"
// use src/server/scripts/Northrend/Naxxramas/naxxramas.h for CreatureId, NaxxramasSay, NaxxramasEvent, NaxxramasMisc
namespace NaxxSpellIds
{
// Heigan
static constexpr uint32 Eruption10 = 29371;
/*
SPELL_SPELL_DISRUPTION = 29310,
SPELL_DECREPIT_FEVER = 29998,
SPELL_PLAGUE_CLOUD = 29350,
SPELL_TELEPORT_SELF = 30211
*/
// Grobbulus
static constexpr uint32 PoisonCloud = 28240;
// Thaddius polarity
static constexpr uint32 PositiveCharge10 = 28059;
static constexpr uint32 PositiveCharge25 = 28062;
static constexpr uint32 PositiveChargeStack = 29659;
static constexpr uint32 NegativeCharge10 = 28084;
static constexpr uint32 NegativeCharge25 = 28085;
static constexpr uint32 NegativeChargeStack = 29660;
/*
SPELL_MAGNETIC_PULL = 28337,
SPELL_TESLA_SHOCK = 28099,
SPELL_SHOCK_VISUAL = 28159,
// Stalagg
SPELL_POWER_SURGE = 54529,
SPELL_STALAGG_CHAIN = 28096,
// Feugen
SPELL_STATIC_FIELD = 28135,
SPELL_FEUGEN_CHAIN = 28111,
// Thaddius
SPELL_POLARITY_SHIFT = 28089,
SPELL_BALL_LIGHTNING = 28299,
SPELL_CHAIN_LIGHTNING = 28167,
SPELL_BERSERK = 27680,
SPELL_THADDIUS_VISUAL_LIGHTNING = 28136,
SPELL_THADDIUS_SPAWN_STUN = 28160,
SPELL_POSITIVE_CHARGE = 28062,
SPELL_POSITIVE_CHARGE_STACK = 29659,
SPELL_NEGATIVE_CHARGE = 28085,
SPELL_NEGATIVE_CHARGE_STACK = 29660,
SPELL_POSITIVE_POLARITY = 28059,
SPELL_NEGATIVE_POLARITY = 28084
*/
// Sapphiron
static constexpr uint32 Icebolt10 = 28522;
static constexpr uint32 Icebolt25 = 28526;
static constexpr uint32 Chill25 = 55699;
/*
// Fight
SPELL_FROST_AURA = 28531,
SPELL_CLEAVE = 19983,
SPELL_TAIL_SWEEP = 55697,
SPELL_SUMMON_BLIZZARD = 28560,
SPELL_LIFE_DRAIN = 28542,
SPELL_BERSERK = 26662,
// Ice block
SPELL_ICEBOLT_CAST = 28526,
SPELL_ICEBOLT_TRIGGER = 28522,
SPELL_FROST_MISSILE = 30101,
SPELL_FROST_EXPLOSION = 28524,
// Visuals
SPELL_SAPPHIRON_DIES = 29357
*/
// Gluth
static constexpr uint32 Decimate10 = 28374;
static constexpr uint32 Decimate25 = 54426;
static constexpr uint32 Decimate25Alt = 28375;
static constexpr uint32 MortalWound10 = 25646;
static constexpr uint32 MortalWound25 = 54378;
/*
SPELL_MORTAL_WOUND = 25646,
SPELL_ENRAGE = 28371,
SPELL_DECIMATE = 28374,
SPELL_DECIMATE_DAMAGE = 28375,
SPELL_BERSERK = 26662,
SPELL_INFECTED_WOUND = 29306,
SPELL_CHOW_SEARCHER = 28404
*/
// Anub'Rekhan
static constexpr uint32 LocustSwarm10 = 28785;
static constexpr uint32 LocustSwarm10Alt = 28786;
static constexpr uint32 LocustSwarm25 = 54021; // 25-man Locust Swarm
/*
SPELL_IMPALE = 28783,
SPELL_LOCUST_SWARM = 28785,
SPELL_SUMMON_CORPSE_SCARABS_5 = 29105,
SPELL_SUMMON_CORPSE_SCARABS_10 = 28864,
SPELL_BERSERK = 26662
ACHIEV_TIMED_START_EVENT = 9891,
EVENT_SPAWN_CRYPT_GUARDS_1 = 0,
EVENT_BERSERK = 1,
////
Position const cryptguardPositions[] = {
{ 3299.732f, -3502.489f, 287.077f, 2.378f },
{ 3299.086f, -3450.929f, 287.077f, 3.999f },
{ 3331.217f, -3476.607f, 287.074f, 3.269f }
};
*/
// Loatheb
static constexpr uint32 NecroticAura10 = 55593;
/*
SPELL_NECROTIC_AURA = 55593,
SPELL_SUMMON_SPORE = 29234,
SPELL_DEATHBLOOM = 29865,
SPELL_INEVITABLE_DOOM = 29204,
SPELL_BERSERK = 26662
*/
inline bool HasAnyAura(PlayerbotAI* botAI, Unit* unit, std::initializer_list<uint32> spellIds)
{
if (!botAI || !unit)
return false;
for (uint32 spellId : spellIds)
{
if (botAI->HasAura(spellId, unit))
return true;
}
return false;
}
inline Aura* GetAnyAura(Unit* unit, std::initializer_list<uint32> spellIds)
{
if (!unit)
return nullptr;
for (uint32 spellId : spellIds)
{
if (Aura* aura = unit->GetAura(spellId))
return aura;
}
return nullptr;
}
inline bool MatchesAnySpellId(SpellInfo const* info, std::initializer_list<uint32> spellIds)
{
if (!info)
return false;
for (uint32 spellId : spellIds)
{
if (info->Id == spellId)
return true;
}
return false;
}
} // namespace NaxxSpellIds
#endif

View File

@@ -8,6 +8,7 @@
#include "RaidKarazhanStrategy.h"
#include "RaidGruulsLairStrategy.h"
#include "RaidMagtheridonStrategy.h"
#include "RaidNaxxStrategy.h"
#include "RaidSSCStrategy.h"
#include "RaidTempestKeepStrategy.h"
#include "RaidOsStrategy.h"
@@ -28,6 +29,7 @@ public:
creators["karazhan"] = &RaidStrategyContext::karazhan;
creators["gruulslair"] = &RaidStrategyContext::gruulslair;
creators["magtheridon"] = &RaidStrategyContext::magtheridon;
creators["naxx"] = &RaidStrategyContext::naxx;
creators["ssc"] = &RaidStrategyContext::ssc;
creators["tempestkeep"] = &RaidStrategyContext::tempestkeep;
creators["wotlk-os"] = &RaidStrategyContext::wotlk_os;
@@ -45,6 +47,7 @@ private:
static Strategy* karazhan(PlayerbotAI* botAI) { return new RaidKarazhanStrategy(botAI); }
static Strategy* gruulslair(PlayerbotAI* botAI) { return new RaidGruulsLairStrategy(botAI); }
static Strategy* magtheridon(PlayerbotAI* botAI) { return new RaidMagtheridonStrategy(botAI); }
static Strategy* naxx(PlayerbotAI* botAI) { return new RaidNaxxStrategy(botAI); }
static Strategy* ssc(PlayerbotAI* botAI) { return new RaidSSCStrategy(botAI); }
static Strategy* tempestkeep(PlayerbotAI* botAI) { return new RaidTempestKeepStrategy(botAI); }
static Strategy* wotlk_os(PlayerbotAI* botAI) { return new RaidOsStrategy(botAI); }

View File

@@ -41,6 +41,8 @@
#include "Ai/Raid/GruulsLair/RaidGruulsLairTriggerContext.h"
#include "Ai/Raid/Magtheridon/RaidMagtheridonActionContext.h"
#include "Ai/Raid/Magtheridon/RaidMagtheridonTriggerContext.h"
#include "Ai/Raid/Naxxramas/RaidNaxxActionContext.h"
#include "Ai/Raid/Naxxramas/RaidNaxxTriggerContext.h"
#include "Ai/Raid/SerpentshrineCavern/RaidSSCActionContext.h"
#include "Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h"
#include "Ai/Raid/TempestKeep/RaidTempestKeepActionContext.h"
@@ -119,6 +121,7 @@ void AiObjectContext::BuildSharedActionContexts(SharedNamedObjectContextList<Act
actionContexts.Add(new RaidKarazhanActionContext());
actionContexts.Add(new RaidGruulsLairActionContext());
actionContexts.Add(new RaidMagtheridonActionContext());
actionContexts.Add(new RaidNaxxActionContext());
actionContexts.Add(new RaidSSCActionContext());
actionContexts.Add(new RaidTempestKeepActionContext());
actionContexts.Add(new RaidOsActionContext());
@@ -155,6 +158,7 @@ void AiObjectContext::BuildSharedTriggerContexts(SharedNamedObjectContextList<Tr
triggerContexts.Add(new RaidKarazhanTriggerContext());
triggerContexts.Add(new RaidGruulsLairTriggerContext());
triggerContexts.Add(new RaidMagtheridonTriggerContext());
triggerContexts.Add(new RaidNaxxTriggerContext());
triggerContexts.Add(new RaidSSCTriggerContext());
triggerContexts.Add(new RaidTempestKeepTriggerContext());
triggerContexts.Add(new RaidOsTriggerContext());

View File

@@ -1550,6 +1550,9 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
case 532:
strategyName = "karazhan"; // Karazhan
break;
case 533:
strategyName = "naxx"; // Naxxramas
break;
case 544:
strategyName = "magtheridon"; // Magtheridon's Lair
break;