diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 07b07293..848ee41e 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -1507,6 +1507,9 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster) case 409: strategyName = "mc"; break; + case 509: + strategyName = "aq20"; + break; default: break; } diff --git a/src/strategy/AiObjectContext.cpp b/src/strategy/AiObjectContext.cpp index 9052b61b..1735d56d 100644 --- a/src/strategy/AiObjectContext.cpp +++ b/src/strategy/AiObjectContext.cpp @@ -24,6 +24,8 @@ #include "raids/naxxramas/RaidNaxxTriggerContext.h" #include "raids/moltencore/RaidMcActionContext.h" #include "raids/moltencore/RaidMcTriggerContext.h" +#include "raids/aq20/RaidAq20ActionContext.h" +#include "raids/aq20/RaidAq20TriggerContext.h" AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI) { @@ -40,6 +42,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI) actionContexts.Add(new RaidNaxxActionContext()); actionContexts.Add(new RaidUlduarActionContext()); actionContexts.Add(new RaidMcActionContext()); + actionContexts.Add(new RaidAq20ActionContext()); triggerContexts.Add(new TriggerContext()); triggerContexts.Add(new ChatTriggerContext()); @@ -48,6 +51,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI) triggerContexts.Add(new RaidNaxxTriggerContext()); triggerContexts.Add(new RaidUlduarTriggerContext()); triggerContexts.Add(new RaidMcTriggerContext()); + triggerContexts.Add(new RaidAq20TriggerContext()); valueContexts.Add(new ValueContext()); diff --git a/src/strategy/actions/MovementActions.cpp b/src/strategy/actions/MovementActions.cpp index 7f47f9e3..57eb4255 100644 --- a/src/strategy/actions/MovementActions.cpp +++ b/src/strategy/actions/MovementActions.cpp @@ -762,7 +762,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle, // return true; } -bool MovementAction::MoveTo(Unit* target, float distance, MovementPriority priority) +bool MovementAction::MoveTo(WorldObject* target, float distance, MovementPriority priority) { if (!IsMovingAllowed(target)) return false; @@ -874,7 +874,7 @@ float MovementAction::GetFollowAngle() return 0; } -bool MovementAction::IsMovingAllowed(Unit* target) +bool MovementAction::IsMovingAllowed(WorldObject* target) { if (!target) return false; @@ -1272,6 +1272,8 @@ bool MovementAction::ChaseTo(WorldObject* obj, float distance, float angle) // bot->GetMotionMaster()->Clear(); bot->GetMotionMaster()->MoveChase((Unit*)obj, distance); + + // TODO shouldnt this use "last movement" value? WaitForReach(bot->GetExactDist2d(obj) - distance); return true; } @@ -1295,6 +1297,7 @@ float MovementAction::MoveDelay(float distance) return delay; } +// TODO should this be removed? (or modified to use "last movement" value?) void MovementAction::WaitForReach(float distance) { float delay = 1000.0f * MoveDelay(distance); @@ -1313,6 +1316,15 @@ void MovementAction::WaitForReach(float distance) botAI->SetNextCheckDelay((uint32)delay); } +// similiar to botAI->SetNextCheckDelay() but only stops movement +void MovementAction::SetNextMovementDelay(float delayMillis) +{ + AI_VALUE(LastMovement&, "last movement") + .Set(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetOrientation(), + delayMillis, + MovementPriority::MOVEMENT_FORCED); +} + bool MovementAction::Flee(Unit* target) { Player* master = GetMaster(); diff --git a/src/strategy/actions/MovementActions.h b/src/strategy/actions/MovementActions.h index 6ef93475..7edf119b 100644 --- a/src/strategy/actions/MovementActions.h +++ b/src/strategy/actions/MovementActions.h @@ -30,7 +30,7 @@ protected: bool MoveToLOS(WorldObject* target, bool ranged = false); bool MoveTo(uint32 mapId, float x, float y, float z, bool idle = false, bool react = false, bool normal_only = false, bool exact_waypoint = false, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); - bool MoveTo(Unit* target, float distance = 0.0f, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); + bool MoveTo(WorldObject* target, float distance = 0.0f, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); bool MoveNear(WorldObject* target, float distance = sPlayerbotAIConfig->contactDistance, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); float GetFollowAngle(); bool Follow(Unit* target, float distance = sPlayerbotAIConfig->followDistance); @@ -39,7 +39,8 @@ protected: bool ReachCombatTo(Unit* target, float distance = 0.0f); float MoveDelay(float distance); void WaitForReach(float distance); - bool IsMovingAllowed(Unit* target); + void SetNextMovementDelay(float delayMillis); + bool IsMovingAllowed(WorldObject* target); bool IsMovingAllowed(uint32 mapId, float x, float y, float z); bool IsDuplicateMove(uint32 mapId, float x, float y, float z); bool IsWaitingForLastMove(MovementPriority priority); diff --git a/src/strategy/raids/RaidStrategyContext.h b/src/strategy/raids/RaidStrategyContext.h index 29e997d2..a60ed12d 100644 --- a/src/strategy/raids/RaidStrategyContext.h +++ b/src/strategy/raids/RaidStrategyContext.h @@ -6,6 +6,7 @@ #include "RaidBwlStrategy.h" #include "RaidNaxxStrategy.h" #include "RaidMcStrategy.h" +#include "RaidAq20Strategy.h" class RaidStrategyContext : public NamedObjectContext { @@ -19,6 +20,7 @@ public: creators["bwl"] = &RaidStrategyContext::bwl; creators["uld"] = &RaidStrategyContext::uld; creators["mc"] = &RaidStrategyContext::mc; + creators["aq20"] = &RaidStrategyContext::aq20; } private: @@ -26,6 +28,7 @@ private: static Strategy* bwl(PlayerbotAI* botAI) { return new RaidBwlStrategy(botAI); } static Strategy* uld(PlayerbotAI* botAI) { return new RaidUlduarStrategy(botAI); } static Strategy* mc(PlayerbotAI* botAI) { return new RaidMcStrategy(botAI); } + static Strategy* aq20(PlayerbotAI* botAI) { return new RaidAq20Strategy(botAI); } }; -#endif \ No newline at end of file +#endif diff --git a/src/strategy/raids/aq20/RaidAq20ActionContext.h b/src/strategy/raids/aq20/RaidAq20ActionContext.h new file mode 100644 index 00000000..ea3afcf4 --- /dev/null +++ b/src/strategy/raids/aq20/RaidAq20ActionContext.h @@ -0,0 +1,20 @@ +#ifndef _PLAYERBOT_RAIDAQ20ACTIONCONTEXT_H +#define _PLAYERBOT_RAIDAQ20ACTIONCONTEXT_H + +#include "Action.h" +#include "NamedObjectContext.h" +#include "RaidAq20Actions.h" + +class RaidAq20ActionContext : public NamedObjectContext +{ +public: + RaidAq20ActionContext() + { + creators["aq20 use crystal"] = &RaidAq20ActionContext::use_crystal; + } + +private: + static Action* use_crystal(PlayerbotAI* ai) { return new Aq20UseCrystalAction(ai); } +}; + +#endif diff --git a/src/strategy/raids/aq20/RaidAq20Actions.cpp b/src/strategy/raids/aq20/RaidAq20Actions.cpp new file mode 100644 index 00000000..9b7e4065 --- /dev/null +++ b/src/strategy/raids/aq20/RaidAq20Actions.cpp @@ -0,0 +1,49 @@ +#include "RaidAq20Actions.h" + +#include "Playerbots.h" +#include "RaidAq20Utils.h" + + +bool Aq20UseCrystalAction::Execute(Event event) +{ + if (Unit* boss = AI_VALUE2(Unit*, "find target", "ossirian the unscarred")) + { + if (GameObject* crystal = RaidAq20Utils::GetNearestCrystal(boss)) + { + float botDist = bot->GetDistance(crystal); + if (botDist > INTERACTION_DISTANCE) + return MoveTo(bot->GetMapId(), + crystal->GetPositionX() + frand(-3.5f, 3.5f), + crystal->GetPositionY() + frand(-3.5f, 3.5f), + crystal->GetPositionZ()); + + // if we're already in range just wait here until it's time to activate crystal + SetNextMovementDelay(500); + + // don't activate crystal if boss too far or its already been activated + if (boss->GetDistance(crystal) > 25.0f || + crystal->HasGameObjectFlag(GO_FLAG_IN_USE)) + return false; + + // don't activate crystal if boss doesn't have buff yet AND isn't going to have it soon + // (though ideally bot should activate it ~5 seconds early due to time it takes for + // crystal to activate and remove buff) + if (!RaidAq20Utils::IsOssirianBuffActive(boss) && + RaidAq20Utils::GetOssirianDebuffTimeRemaining(boss) > 5000) + return false; + + // this makes crystal do its animation (then disappear after) + WorldPacket data1(CMSG_GAMEOBJ_USE); + data1 << crystal->GetGUID(); + bot->GetSession()->HandleGameObjectUseOpcode(data1); + + // this makes crystal actually remove the buff and put on debuff (took a while to figure that out) + WorldPacket data2(CMSG_GAMEOBJ_USE); + data2 << crystal->GetGUID(); + bot->GetSession()->HandleGameobjectReportUse(data2); + + return true; + } + } + return false; +} diff --git a/src/strategy/raids/aq20/RaidAq20Actions.h b/src/strategy/raids/aq20/RaidAq20Actions.h new file mode 100644 index 00000000..59a9b437 --- /dev/null +++ b/src/strategy/raids/aq20/RaidAq20Actions.h @@ -0,0 +1,14 @@ +#ifndef _PLAYERBOT_RAIDAQ20ACTIONS_H +#define _PLAYERBOT_RAIDAQ20ACTIONS_H + +#include "MovementActions.h" +#include "PlayerbotAI.h" +#include "Playerbots.h" + +class Aq20UseCrystalAction : public MovementAction +{ +public: + Aq20UseCrystalAction(PlayerbotAI* botAI, std::string const name = "aq20 use crystal") : MovementAction(botAI, name) {} + bool Execute(Event event) override; +}; +#endif diff --git a/src/strategy/raids/aq20/RaidAq20Strategy.cpp b/src/strategy/raids/aq20/RaidAq20Strategy.cpp new file mode 100644 index 00000000..2b8cbe8c --- /dev/null +++ b/src/strategy/raids/aq20/RaidAq20Strategy.cpp @@ -0,0 +1,11 @@ +#include "RaidAq20Strategy.h" + +#include "Strategy.h" + +void RaidAq20Strategy::InitTriggers(std::vector& triggers) +{ + triggers.push_back( + new TriggerNode("aq20 move to crystal", + NextAction::array(0, new NextAction("aq20 use crystal", ACTION_RAID), nullptr))); + +} diff --git a/src/strategy/raids/aq20/RaidAq20Strategy.h b/src/strategy/raids/aq20/RaidAq20Strategy.h new file mode 100644 index 00000000..97ff7453 --- /dev/null +++ b/src/strategy/raids/aq20/RaidAq20Strategy.h @@ -0,0 +1,17 @@ +#ifndef _PLAYERBOT_RAIDAQ20STRATEGY_H +#define _PLAYERBOT_RAIDAQ20STRATEGY_H + +#include "AiObjectContext.h" +#include "Multiplier.h" +#include "Strategy.h" + +class RaidAq20Strategy : public Strategy +{ +public: + RaidAq20Strategy(PlayerbotAI* ai) : Strategy(ai) {} + virtual std::string const getName() override { return "aq20"; } + virtual void InitTriggers(std::vector& triggers) override; + // virtual void InitMultipliers(std::vector &multipliers) override; +}; + +#endif diff --git a/src/strategy/raids/aq20/RaidAq20TriggerContext.h b/src/strategy/raids/aq20/RaidAq20TriggerContext.h new file mode 100644 index 00000000..b49ae1c6 --- /dev/null +++ b/src/strategy/raids/aq20/RaidAq20TriggerContext.h @@ -0,0 +1,20 @@ +#ifndef _PLAYERBOT_RAIDAQ20TRIGGERCONTEXT_H +#define _PLAYERBOT_RAIDAQ20TRIGGERCONTEXT_H + +#include "AiObjectContext.h" +#include "NamedObjectContext.h" +#include "RaidAq20Triggers.h" + +class RaidAq20TriggerContext : public NamedObjectContext +{ +public: + RaidAq20TriggerContext() + { + creators["aq20 move to crystal"] = &RaidAq20TriggerContext::move_to_crystal; + } + +private: + static Trigger* move_to_crystal(PlayerbotAI* ai) { return new Aq20MoveToCrystalTrigger(ai); } +}; + +#endif diff --git a/src/strategy/raids/aq20/RaidAq20Triggers.cpp b/src/strategy/raids/aq20/RaidAq20Triggers.cpp new file mode 100644 index 00000000..fbda881a --- /dev/null +++ b/src/strategy/raids/aq20/RaidAq20Triggers.cpp @@ -0,0 +1,37 @@ +#include "RaidAq20Triggers.h" + +#include "SharedDefines.h" +#include "RaidAq20Utils.h" + + +bool Aq20MoveToCrystalTrigger::IsActive() +{ + if (Unit* boss = AI_VALUE2(Unit*, "find target", "ossirian the unscarred")) + { + if (boss->IsInCombat()) + { + // if buff is active move to crystal + if (RaidAq20Utils::IsOssirianBuffActive(boss)) + return true; + + // if buff is not active a debuff will be, buff becomes active once debuff expires + // so move to crystal when debuff almost done, or based debuff time left and + // distance bot is from crystal (ie: start moving early enough to make it) + int32 debuffTimeRemaining = RaidAq20Utils::GetOssirianDebuffTimeRemaining(boss); + if (debuffTimeRemaining < 5000) + return true; + if (debuffTimeRemaining < 30000) + { + if (GameObject* crystal = RaidAq20Utils::GetNearestCrystal(boss)) + { + float botDist = bot->GetDistance(crystal); + float timeToReach = botDist / bot->GetSpeed(MOVE_RUN); + // bot should ideally activate crystal a ~5 seconds early (due to time it takes for crystal + // to activate) so aim to get there in time to do so + return debuffTimeRemaining - 5000 < timeToReach * 1000.0f; + } + } + } + } + return false; +} diff --git a/src/strategy/raids/aq20/RaidAq20Triggers.h b/src/strategy/raids/aq20/RaidAq20Triggers.h new file mode 100644 index 00000000..219de8f4 --- /dev/null +++ b/src/strategy/raids/aq20/RaidAq20Triggers.h @@ -0,0 +1,14 @@ +#ifndef _PLAYERBOT_RAIDAQ20TRIGGERS_H +#define _PLAYERBOT_RAIDAQ20TRIGGERS_H + +#include "PlayerbotAI.h" +#include "Playerbots.h" +#include "Trigger.h" + +class Aq20MoveToCrystalTrigger : public Trigger +{ +public: + Aq20MoveToCrystalTrigger(PlayerbotAI* botAI) : Trigger(botAI, "aq20 move to crystal") {} + bool IsActive() override; +}; +#endif diff --git a/src/strategy/raids/aq20/RaidAq20Utils.cpp b/src/strategy/raids/aq20/RaidAq20Utils.cpp new file mode 100644 index 00000000..41637af7 --- /dev/null +++ b/src/strategy/raids/aq20/RaidAq20Utils.cpp @@ -0,0 +1,38 @@ +#include "RaidAq20Utils.h" + +#include "SpellAuras.h" + +uint32 const OSSIRIAN_BUFF = 25176; +uint32 const OSSIRIAN_DEBUFFS[] = {25177, 25178, 25180, 25181, 25183}; +uint32 const OSSIRIAN_CRYSTAL_GO_ENTRY = 180619; + +bool RaidAq20Utils::IsOssirianBuffActive(Unit* ossirian) +{ + return ossirian && ossirian->HasAura(OSSIRIAN_BUFF); +} + +int32 RaidAq20Utils::GetOssirianDebuffTimeRemaining(Unit* ossirian) +{ + int32 retVal = 0xffffff; + if (ossirian) + { + for (uint32 debuff : OSSIRIAN_DEBUFFS) + { + if (AuraApplication* auraApplication = ossirian->GetAuraApplication(debuff)) + { + if (Aura* aura = auraApplication->GetBase()) + { + int32 duration = aura->GetDuration(); + if (retVal > duration) + retVal = duration; + } + } + } + } + return retVal; +} + +GameObject* RaidAq20Utils::GetNearestCrystal(Unit* ossirian) +{ + return ossirian ? ossirian->FindNearestGameObject(OSSIRIAN_CRYSTAL_GO_ENTRY, 200.0f) : nullptr; +} diff --git a/src/strategy/raids/aq20/RaidAq20Utils.h b/src/strategy/raids/aq20/RaidAq20Utils.h new file mode 100644 index 00000000..b78340d0 --- /dev/null +++ b/src/strategy/raids/aq20/RaidAq20Utils.h @@ -0,0 +1,15 @@ +#ifndef _PLAYERBOT_RAIDAQ20UTILS_H +#define _PLAYERBOT_RAIDAQ20UTILS_H + +#include "GameObject.h" +#include "Unit.h" + +class RaidAq20Utils +{ +public: + static bool IsOssirianBuffActive(Unit* ossirian); + static int32 GetOssirianDebuffTimeRemaining(Unit* ossirian); + static GameObject* GetNearestCrystal(Unit* ossirian); +}; + +#endif