diff --git a/src/Ai/Base/Actions/ChatShortcutActions.cpp b/src/Ai/Base/Actions/ChatShortcutActions.cpp index 84ed3584..0caeb5e0 100644 --- a/src/Ai/Base/Actions/ChatShortcutActions.cpp +++ b/src/Ai/Base/Actions/ChatShortcutActions.cpp @@ -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(); diff --git a/src/Ai/Base/Actions/ChatShortcutActions.h b/src/Ai/Base/Actions/ChatShortcutActions.h index b7e4a9e9..fa941a4c 100644 --- a/src/Ai/Base/Actions/ChatShortcutActions.h +++ b/src/Ai/Base/Actions/ChatShortcutActions.h @@ -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: diff --git a/src/Ai/Base/ChatActionContext.h b/src/Ai/Base/ChatActionContext.h index 4bfdcb04..edd111bb 100644 --- a/src/Ai/Base/ChatActionContext.h +++ b/src/Ai/Base/ChatActionContext.h @@ -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); } diff --git a/src/Ai/Base/ChatTriggerContext.h b/src/Ai/Base/ChatTriggerContext.h index 3e71839b..54ae236b 100644 --- a/src/Ai/Base/ChatTriggerContext.h +++ b/src/Ai/Base/ChatTriggerContext.h @@ -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"); } diff --git a/src/Ai/Base/Strategy/ChatCommandHandlerStrategy.cpp b/src/Ai/Base/Strategy/ChatCommandHandlerStrategy.cpp index f67ae49c..7bc1d539 100644 --- a/src/Ai/Base/Strategy/ChatCommandHandlerStrategy.cpp +++ b/src/Ai/Base/Strategy/ChatCommandHandlerStrategy.cpp @@ -83,6 +83,8 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector& 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( diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions.h b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions.h new file mode 100644 index 00000000..d5bfe0bc --- /dev/null +++ b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions.h @@ -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> waypoints; +// std::pair 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 diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Anubrekhan.cpp b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Anubrekhan.cpp new file mode 100644 index 00000000..7ff77ead --- /dev/null +++ b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Anubrekhan.cpp @@ -0,0 +1,81 @@ +#include "RaidNaxxActions.h" + +#include "ObjectGuid.h" +#include "Playerbots.h" + +bool AnubrekhanChooseTargetAction::Execute(Event /*event*/) +{ + GuidVector attackers = context->GetValue("attackers")->Get(); + Unit* target = nullptr; + Unit* target_boss = nullptr; + std::vector 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("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; +} diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Faerlina.cpp b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Faerlina.cpp new file mode 100644 index 00000000..0c52be78 --- /dev/null +++ b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Faerlina.cpp @@ -0,0 +1,3 @@ +#include "RaidNaxxActions.h" + +// Reserved for Faerlina-specific actions. diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_FourHorseman.cpp b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_FourHorseman.cpp new file mode 100644 index 00000000..56736388 --- /dev/null +++ b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_FourHorseman.cpp @@ -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("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 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("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; +} diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Gluth.cpp b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Gluth.cpp new file mode 100644 index 00000000..f95f17ff --- /dev/null +++ b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Gluth.cpp @@ -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("possible targets")->Get(); + Unit* target = nullptr; + Unit* target_boss = nullptr; + std::vector 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("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; +} diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Gothik.cpp b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Gothik.cpp new file mode 100644 index 00000000..a33fef30 --- /dev/null +++ b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Gothik.cpp @@ -0,0 +1,3 @@ +#include "RaidNaxxActions.h" + +// Reserved for Gothik-specific actions. diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Grobbulus.cpp b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Grobbulus.cpp new file mode 100644 index 00000000..1917c3e1 --- /dev/null +++ b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Grobbulus.cpp @@ -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; +} diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Heigan.cpp b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Heigan.cpp new file mode 100644 index 00000000..9e622195 --- /dev/null +++ b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Heigan.cpp @@ -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); +//} diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Kelthuzad.cpp b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Kelthuzad.cpp new file mode 100644 index 00000000..5f774117 --- /dev/null +++ b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Kelthuzad.cpp @@ -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("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 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("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; +} diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Loatheb.cpp b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Loatheb.cpp new file mode 100644 index 00000000..df43cabe --- /dev/null +++ b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Loatheb.cpp @@ -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("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("current target")->Get() == target) + return false; + + return Attack(target); +} diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Maexxna.cpp b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Maexxna.cpp new file mode 100644 index 00000000..cfb7d27d --- /dev/null +++ b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Maexxna.cpp @@ -0,0 +1,3 @@ +#include "RaidNaxxActions.h" + +// Reserved for Maexxna-specific actions. diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Noth.cpp b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Noth.cpp new file mode 100644 index 00000000..4f8fe24a --- /dev/null +++ b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Noth.cpp @@ -0,0 +1,3 @@ +#include "RaidNaxxActions.h" + +// Reserved for Noth-specific actions. diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Patchwerk.cpp b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Patchwerk.cpp new file mode 100644 index 00000000..e0e2ee3e --- /dev/null +++ b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Patchwerk.cpp @@ -0,0 +1,31 @@ +#include "RaidNaxxActions.h" + +#include +#include + +//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); +//} diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Razuvious.cpp b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Razuvious.cpp new file mode 100644 index 00000000..ce0ca6ca --- /dev/null +++ b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Razuvious.cpp @@ -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("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); +} diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Sapphiron.cpp b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Sapphiron.cpp new file mode 100644 index 00000000..af906da8 --- /dev/null +++ b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Sapphiron.cpp @@ -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 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 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; +} diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Shared.cpp b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Shared.cpp new file mode 100644 index 00000000..50b3bc1d --- /dev/null +++ b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Shared.cpp @@ -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; +} diff --git a/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Thaddius.cpp b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Thaddius.cpp new file mode 100644 index 00000000..0935f054 --- /dev/null +++ b/src/Ai/Raid/Naxxramas/Action/RaidNaxxActions_Thaddius.cpp @@ -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 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 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> 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> 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); +} diff --git a/src/Ai/Raid/Naxxramas/Multiplier/RaidNaxxMultipliers.cpp b/src/Ai/Raid/Naxxramas/Multiplier/RaidNaxxMultipliers.cpp new file mode 100644 index 00000000..d34b48e6 --- /dev/null +++ b/src/Ai/Raid/Naxxramas/Multiplier/RaidNaxxMultipliers.cpp @@ -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(action)) + return botAI->IsMainTank(bot) ? 0.0f : 1.0f; + + if (dynamic_cast(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(action) || +// dynamic_cast(action) || +// dynamic_cast(action) ) +// { +// return 0.0f; +// } +// if (!platform_phase && !eruption_casting) +// { +// return 1.0f; +// } +// if (dynamic_cast(action) || dynamic_cast(action)) +// { +// return 1.0f; +// } +// if (dynamic_cast(action) && !dynamic_cast(action)) +// { +// CastSpellAction* spellAction = dynamic_cast(action); +// uint32 spellId = AI_VALUE2(uint32, "spell id", spellAction->getSpell()); +// SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); +// if (!spellInfo) +// { +// return 0.0f; +// } +// uint32 castTime = spellInfo->CalcCastTime(); +// if (castTime == 0 && !spellInfo->IsChanneled()) +// { +// return 1.0f; +// } +// } +// return 0.0f; +//} + +float LoathebGenericMultiplier::GetValue(Action* action) +{ + Unit* boss = AI_VALUE2(Unit*, "find target", "loatheb"); + if (!boss) + return 1.0f; + + context->GetValue("neglect threat")->Set(true); + if (botAI->GetState() == BOT_STATE_COMBAT && + (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action))) + { + return 0.0f; + } + if (!dynamic_cast(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(action)) + return 0.0f; + // pet phase + if (helper.IsPhasePet() && + (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action))) + { + return 0.0f; + } + // die at the same time + Unit* target = AI_VALUE(Unit*, "current target"); + Unit* feugen = AI_VALUE2(Unit*, "find target", "feugen"); + Unit* stalagg = AI_VALUE2(Unit*, "find target", "stalagg"); + if (helper.IsPhasePet() && target && feugen && stalagg && target->GetHealthPct() <= 40 && + (feugen->GetHealthPct() >= target->GetHealthPct() + 3 || stalagg->GetHealthPct() >= target->GetHealthPct() + 3)) + { + if (dynamic_cast(action) && !dynamic_cast(action)) + return 0.0f; + } + // magnetic pull + // uint32 curr_timer = eventMap->GetTimer(); + // // if (curr_phase == 2 && bot->GetPositionZ() > 312.5f && dynamic_cast(action)) + // { + // if (curr_phase == 2 && (curr_timer % 20000 >= 18000 || curr_timer % 20000 <= 2000) && + // dynamic_cast(action)) + // { + // // MotionMaster *mm = bot->GetMotionMaster(); + // // mm->Clear(); + // return 0.0f; + // } + // thaddius phase + // if (curr_phase == 8 && dynamic_cast(action)) + // { + // return 0.0f; + // } + return 1.0f; +} + +float SapphironGenericMultiplier::GetValue(Action* action) +{ + if (!helper.UpdateBossAI()) + return 1.0f; + + if (dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + +float InstructorRazuviousGenericMultiplier::GetValue(Action* action) +{ + if (!helper.UpdateBossAI()) + return 1.0f; + + context->GetValue("neglect threat")->Set(true); + if (botAI->GetState() == BOT_STATE_COMBAT && + (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action))) + { + return 0.0f; + } + return 1.0f; +} + +float KelthuzadGenericMultiplier::GetValue(Action* action) +{ + if (!helper.UpdateBossAI()) + return 1.0f; + + if ((dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action))) + { + return 0.0f; + } + if (helper.IsPhaseOne()) + { + if (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action)) + { + return 0.0f; + } + } + if (helper.IsPhaseTwo()) + { + if (dynamic_cast(action) || dynamic_cast(action)) + return 0.0f; + + } + return 1.0f; +} + +float 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(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("neglect threat")->Set(true); + if ((dynamic_cast(action) || dynamic_cast(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(boss->GetAI()); +// EventMap* eventMap = boss_botAI->GetEvents(); +// uint32 curr_phase = eventMap->GetPhaseMask(); +// if (curr_phase == 1 && (dynamic_cast(action))) +// { +// return 0.0f; +// } +// if (curr_phase == 1 && (dynamic_cast(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(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(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(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action)) + { + return 0.0f; + } + } + } + if (dynamic_cast(action)) + { + Unit* target = AI_VALUE(Unit*, "current target"); + if (helper.IsZombieChow(target)) + return 0.0f; + } + return 1.0f; +} diff --git a/src/Ai/Raid/Naxxramas/Multiplier/RaidNaxxMultipliers.h b/src/Ai/Raid/Naxxramas/Multiplier/RaidNaxxMultipliers.h new file mode 100644 index 00000000..af702f60 --- /dev/null +++ b/src/Ai/Raid/Naxxramas/Multiplier/RaidNaxxMultipliers.h @@ -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 diff --git a/src/Ai/Raid/Naxxramas/RaidNaxxActionContext.h b/src/Ai/Raid/Naxxramas/RaidNaxxActionContext.h new file mode 100644 index 00000000..45941d67 --- /dev/null +++ b/src/Ai/Raid/Naxxramas/RaidNaxxActionContext.h @@ -0,0 +1,95 @@ +// /* +// * Copyright (C) 2016+ AzerothCore , 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 +{ +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 diff --git a/src/Ai/Raid/Naxxramas/RaidNaxxTriggerContext.h b/src/Ai/Raid/Naxxramas/RaidNaxxTriggerContext.h new file mode 100644 index 00000000..d980b476 --- /dev/null +++ b/src/Ai/Raid/Naxxramas/RaidNaxxTriggerContext.h @@ -0,0 +1,86 @@ +// /* +// * Copyright (C) 2016+ AzerothCore , 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 +{ +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 diff --git a/src/Ai/Raid/Naxxramas/Strategy/RaidNaxxStrategy.cpp b/src/Ai/Raid/Naxxramas/Strategy/RaidNaxxStrategy.cpp new file mode 100644 index 00000000..e0c3421d --- /dev/null +++ b/src/Ai/Raid/Naxxramas/Strategy/RaidNaxxStrategy.cpp @@ -0,0 +1,156 @@ +#include "RaidNaxxStrategy.h" + +#include "RaidNaxxMultipliers.h" + +void RaidNaxxStrategy::InitTriggers(std::vector& 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& 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)); +} diff --git a/src/Ai/Raid/Naxxramas/Strategy/RaidNaxxStrategy.h b/src/Ai/Raid/Naxxramas/Strategy/RaidNaxxStrategy.h new file mode 100644 index 00000000..4b8a9a7c --- /dev/null +++ b/src/Ai/Raid/Naxxramas/Strategy/RaidNaxxStrategy.h @@ -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& triggers) override; + virtual void InitMultipliers(std::vector& multipliers) override; +}; + +#endif diff --git a/src/Ai/Raid/Naxxramas/Trigger/RaidNaxxTriggers.cpp b/src/Ai/Raid/Naxxramas/Trigger/RaidNaxxTriggers.cpp new file mode 100644 index 00000000..f4fb38e1 --- /dev/null +++ b/src/Ai/Raid/Naxxramas/Trigger/RaidNaxxTriggers.cpp @@ -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(); +} diff --git a/src/Ai/Raid/Naxxramas/Trigger/RaidNaxxTriggers.h b/src/Ai/Raid/Naxxramas/Trigger/RaidNaxxTriggers.h new file mode 100644 index 00000000..6f7727cf --- /dev/null +++ b/src/Ai/Raid/Naxxramas/Trigger/RaidNaxxTriggers.h @@ -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 diff --git a/src/Ai/Raid/Naxxramas/Util/RaidNaxxBossHelper.h b/src/Ai/Raid/Naxxramas/Util/RaidNaxxBossHelper.h new file mode 100644 index 00000000..9b87bb58 --- /dev/null +++ b/src/Ai/Raid/Naxxramas/Util/RaidNaxxBossHelper.h @@ -0,0 +1,533 @@ +#ifndef _PLAYERBOT_RAIDNAXXBOSSHELPER_H +#define _PLAYERBOT_RAIDNAXXBOSSHELPER_H + +#include + +#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 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(_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 center = {3716.19f, -5106.58f}; + const std::pair tank_pos = {3709.19f, -5104.86f}; + const std::pair assist_tank_pos = {3746.05f, -5112.74f}; + bool 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("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 mainTankPos = {3512.07f, -5274.06f}; + const std::pair 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& 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 mainTankPos25 = {3331.48f, -3109.06f}; + const std::pair mainTankPos10 = {3278.29f, -3162.06f}; + const std::pair beforeDecimatePos = {3267.34f, -3175.68f}; + const std::pair leftSlowDownPos = {3290.68f, -3141.65f}; + const std::pair rightSlowDownPos = {3300.78f, -3151.98f}; + const std::pair rangedPos = {3301.45f, -3139.29f}; + const std::pair healPos = {3303.09f, -3135.24f}; + + const float decimatedZombiePct = 10.0f; + 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 mainTankPos = {2877.57f, -3967.00f}; + const std::pair 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 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 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 tankPosFeugen = {3522.94f, -3002.60f}; + const std::pair tankPosStalagg = {3436.14f, -2919.98f}; + const std::pair rangedPosFeugen = {3500.45f, -2997.92f}; + const std::pair rangedPosStalagg = {3441.01f, -2942.04f}; + const float tankPosZ = 312.61f; + 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 PetPhaseGetPosForTank() + { + if (GetNearestPet() == feugen) + return tankPosFeugen; + + return tankPosStalagg; + } + std::pair 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 diff --git a/src/Ai/Raid/Naxxramas/Util/RaidNaxxSpellIds.h b/src/Ai/Raid/Naxxramas/Util/RaidNaxxSpellIds.h new file mode 100644 index 00000000..53299e7e --- /dev/null +++ b/src/Ai/Raid/Naxxramas/Util/RaidNaxxSpellIds.h @@ -0,0 +1,165 @@ +#ifndef _PLAYERBOT_RAIDNAXXSPELLIDS_H +#define _PLAYERBOT_RAIDNAXXSPELLIDS_H + +#include + +#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 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 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 spellIds) + { + if (!info) + return false; + + for (uint32 spellId : spellIds) + { + if (info->Id == spellId) + return true; + } + return false; + } +} // namespace NaxxSpellIds + +#endif diff --git a/src/Ai/Raid/RaidStrategyContext.h b/src/Ai/Raid/RaidStrategyContext.h index 4d87c5ac..d117c459 100644 --- a/src/Ai/Raid/RaidStrategyContext.h +++ b/src/Ai/Raid/RaidStrategyContext.h @@ -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); } diff --git a/src/Bot/Engine/AiObjectContext.cpp b/src/Bot/Engine/AiObjectContext.cpp index ff54b971..e6333708 100644 --- a/src/Bot/Engine/AiObjectContext.cpp +++ b/src/Bot/Engine/AiObjectContext.cpp @@ -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