Merge pull request #504 from fuzzdeveloper/aq20-ossirian-strat

AQ20 Ossirian Strat
This commit is contained in:
fuzzdeveloper
2024-09-01 22:18:43 +10:00
committed by GitHub
15 changed files with 263 additions and 5 deletions

View File

@@ -1507,6 +1507,9 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
case 409: case 409:
strategyName = "mc"; strategyName = "mc";
break; break;
case 509:
strategyName = "aq20";
break;
default: default:
break; break;
} }

View File

@@ -24,6 +24,8 @@
#include "raids/naxxramas/RaidNaxxTriggerContext.h" #include "raids/naxxramas/RaidNaxxTriggerContext.h"
#include "raids/moltencore/RaidMcActionContext.h" #include "raids/moltencore/RaidMcActionContext.h"
#include "raids/moltencore/RaidMcTriggerContext.h" #include "raids/moltencore/RaidMcTriggerContext.h"
#include "raids/aq20/RaidAq20ActionContext.h"
#include "raids/aq20/RaidAq20TriggerContext.h"
AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI) AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
{ {
@@ -40,6 +42,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
actionContexts.Add(new RaidNaxxActionContext()); actionContexts.Add(new RaidNaxxActionContext());
actionContexts.Add(new RaidUlduarActionContext()); actionContexts.Add(new RaidUlduarActionContext());
actionContexts.Add(new RaidMcActionContext()); actionContexts.Add(new RaidMcActionContext());
actionContexts.Add(new RaidAq20ActionContext());
triggerContexts.Add(new TriggerContext()); triggerContexts.Add(new TriggerContext());
triggerContexts.Add(new ChatTriggerContext()); triggerContexts.Add(new ChatTriggerContext());
@@ -48,6 +51,7 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
triggerContexts.Add(new RaidNaxxTriggerContext()); triggerContexts.Add(new RaidNaxxTriggerContext());
triggerContexts.Add(new RaidUlduarTriggerContext()); triggerContexts.Add(new RaidUlduarTriggerContext());
triggerContexts.Add(new RaidMcTriggerContext()); triggerContexts.Add(new RaidMcTriggerContext());
triggerContexts.Add(new RaidAq20TriggerContext());
valueContexts.Add(new ValueContext()); valueContexts.Add(new ValueContext());

View File

@@ -762,7 +762,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
// return true; // return true;
} }
bool MovementAction::MoveTo(Unit* target, float distance, MovementPriority priority) bool MovementAction::MoveTo(WorldObject* target, float distance, MovementPriority priority)
{ {
if (!IsMovingAllowed(target)) if (!IsMovingAllowed(target))
return false; return false;
@@ -874,7 +874,7 @@ float MovementAction::GetFollowAngle()
return 0; return 0;
} }
bool MovementAction::IsMovingAllowed(Unit* target) bool MovementAction::IsMovingAllowed(WorldObject* target)
{ {
if (!target) if (!target)
return false; return false;
@@ -1272,6 +1272,8 @@ bool MovementAction::ChaseTo(WorldObject* obj, float distance, float angle)
// bot->GetMotionMaster()->Clear(); // bot->GetMotionMaster()->Clear();
bot->GetMotionMaster()->MoveChase((Unit*)obj, distance); bot->GetMotionMaster()->MoveChase((Unit*)obj, distance);
// TODO shouldnt this use "last movement" value?
WaitForReach(bot->GetExactDist2d(obj) - distance); WaitForReach(bot->GetExactDist2d(obj) - distance);
return true; return true;
} }
@@ -1295,6 +1297,7 @@ float MovementAction::MoveDelay(float distance)
return delay; return delay;
} }
// TODO should this be removed? (or modified to use "last movement" value?)
void MovementAction::WaitForReach(float distance) void MovementAction::WaitForReach(float distance)
{ {
float delay = 1000.0f * MoveDelay(distance); float delay = 1000.0f * MoveDelay(distance);
@@ -1313,6 +1316,15 @@ void MovementAction::WaitForReach(float distance)
botAI->SetNextCheckDelay((uint32)delay); 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) bool MovementAction::Flee(Unit* target)
{ {
Player* master = GetMaster(); Player* master = GetMaster();

View File

@@ -30,7 +30,7 @@ protected:
bool MoveToLOS(WorldObject* target, bool ranged = false); bool MoveToLOS(WorldObject* target, bool ranged = false);
bool MoveTo(uint32 mapId, float x, float y, float z, bool idle = false, bool react = 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 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); bool MoveNear(WorldObject* target, float distance = sPlayerbotAIConfig->contactDistance, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
float GetFollowAngle(); float GetFollowAngle();
bool Follow(Unit* target, float distance = sPlayerbotAIConfig->followDistance); bool Follow(Unit* target, float distance = sPlayerbotAIConfig->followDistance);
@@ -39,7 +39,8 @@ protected:
bool ReachCombatTo(Unit* target, float distance = 0.0f); bool ReachCombatTo(Unit* target, float distance = 0.0f);
float MoveDelay(float distance); float MoveDelay(float distance);
void WaitForReach(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 IsMovingAllowed(uint32 mapId, float x, float y, float z);
bool IsDuplicateMove(uint32 mapId, float x, float y, float z); bool IsDuplicateMove(uint32 mapId, float x, float y, float z);
bool IsWaitingForLastMove(MovementPriority priority); bool IsWaitingForLastMove(MovementPriority priority);

View File

@@ -6,6 +6,7 @@
#include "RaidBwlStrategy.h" #include "RaidBwlStrategy.h"
#include "RaidNaxxStrategy.h" #include "RaidNaxxStrategy.h"
#include "RaidMcStrategy.h" #include "RaidMcStrategy.h"
#include "RaidAq20Strategy.h"
class RaidStrategyContext : public NamedObjectContext<Strategy> class RaidStrategyContext : public NamedObjectContext<Strategy>
{ {
@@ -19,6 +20,7 @@ public:
creators["bwl"] = &RaidStrategyContext::bwl; creators["bwl"] = &RaidStrategyContext::bwl;
creators["uld"] = &RaidStrategyContext::uld; creators["uld"] = &RaidStrategyContext::uld;
creators["mc"] = &RaidStrategyContext::mc; creators["mc"] = &RaidStrategyContext::mc;
creators["aq20"] = &RaidStrategyContext::aq20;
} }
private: private:
@@ -26,6 +28,7 @@ private:
static Strategy* bwl(PlayerbotAI* botAI) { return new RaidBwlStrategy(botAI); } static Strategy* bwl(PlayerbotAI* botAI) { return new RaidBwlStrategy(botAI); }
static Strategy* uld(PlayerbotAI* botAI) { return new RaidUlduarStrategy(botAI); } static Strategy* uld(PlayerbotAI* botAI) { return new RaidUlduarStrategy(botAI); }
static Strategy* mc(PlayerbotAI* botAI) { return new RaidMcStrategy(botAI); } static Strategy* mc(PlayerbotAI* botAI) { return new RaidMcStrategy(botAI); }
static Strategy* aq20(PlayerbotAI* botAI) { return new RaidAq20Strategy(botAI); }
}; };
#endif #endif

View File

@@ -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<Action>
{
public:
RaidAq20ActionContext()
{
creators["aq20 use crystal"] = &RaidAq20ActionContext::use_crystal;
}
private:
static Action* use_crystal(PlayerbotAI* ai) { return new Aq20UseCrystalAction(ai); }
};
#endif

View File

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

View File

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

View File

@@ -0,0 +1,11 @@
#include "RaidAq20Strategy.h"
#include "Strategy.h"
void RaidAq20Strategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(
new TriggerNode("aq20 move to crystal",
NextAction::array(0, new NextAction("aq20 use crystal", ACTION_RAID), nullptr)));
}

View File

@@ -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<TriggerNode*>& triggers) override;
// virtual void InitMultipliers(std::vector<Multiplier*> &multipliers) override;
};
#endif

View File

@@ -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<Trigger>
{
public:
RaidAq20TriggerContext()
{
creators["aq20 move to crystal"] = &RaidAq20TriggerContext::move_to_crystal;
}
private:
static Trigger* move_to_crystal(PlayerbotAI* ai) { return new Aq20MoveToCrystalTrigger(ai); }
};
#endif

View File

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

View File

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

View File

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

View File

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