Compare commits

..

No commits in common. "a695ac77fa3658081a86e1542e25571fe7e52609" and "2925f248a6832c8ce054eabd4a7fb40a798f8378" have entirely different histories.

30 changed files with 1772 additions and 1518 deletions

View File

@ -5,211 +5,18 @@
#include "FollowActions.h" #include "FollowActions.h"
#include <algorithm>
#include <cmath>
#include <array>
#include "Event.h" #include "Event.h"
#include "Formations.h" #include "Formations.h"
#include "LastMovementValue.h" #include "LastMovementValue.h"
#include "MotionMaster.h"
#include "PlayerbotAI.h" #include "PlayerbotAI.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "ServerFacade.h" #include "ServerFacade.h"
#include "Transport.h"
#include "Map.h"
namespace
{
Transport* GetTransportForPosTolerant(Map* map, WorldObject* ref, uint32 phaseMask, float x, float y, float z)
{
if (!map || !ref)
return nullptr;
std::array<float, 4> const probes = { z, z + 0.5f, z + 1.5f, z - 0.5f };
for (float const pz : probes)
{
if (Transport* t = map->GetTransportForPos(phaseMask, x, y, pz, ref))
return t;
}
return nullptr;
}
// Attempts to find a point on the leader's transport that is closer to the bot,
// by probing along the segment from master -> bot and returning the last point
// that is still detected as being on the expected transport.
bool FindBoardingPointOnTransport(Map* map, Transport* expectedTransport, WorldObject* ref,
float masterX, float masterY, float masterZ,
float botX, float botY, float botZ,
float& outX, float& outY, float& outZ)
{
if (!map || !expectedTransport || !ref)
return false;
uint32 const phaseMask = ref->GetPhaseMask();
// Ensure master is actually detected on that transport (tolerant).
if (GetTransportForPosTolerant(map, ref, phaseMask, masterX, masterY, masterZ) != expectedTransport)
return false;
// The raycast in GetTransportForPos starts at (z + 2). Probe with a safe Z.
float const probeZ = std::max(masterZ, botZ);
// Adaptive step count: small platforms need tighter sampling.
float const dx2 = botX - masterX;
float const dy2 = botY - masterY;
float const dist2d = std::sqrt(dx2 * dx2 + dy2 * dy2);
int32 const steps = std::clamp(static_cast<int32>(dist2d / 0.75f), 10, 28);
float const dx = (botX - masterX) / static_cast<float>(steps);
float const dy = (botY - masterY) / static_cast<float>(steps);
// Master must actually be on the expected transport for this to work.
if (map->GetTransportForPos(ref->GetPhaseMask(), masterX, masterY, probeZ, ref) != expectedTransport)
return false;
float lastX = masterX;
float lastY = masterY;
bool found = false;
for (int32 i = 1; i <= steps; ++i)
{
float const px = masterX + dx * i;
float const py = masterY + dy * i;
Transport* const t = GetTransportForPosTolerant(map, ref, phaseMask, px, py, probeZ);
if (t != expectedTransport)
break;
lastX = px;
lastY = py;
found = true;
}
if (!found)
return false;
outX = lastX;
outY = lastY;
outZ = masterZ; // keep deck-level Z to encourage stepping onto the platform/boat
return true;
}
}
bool FollowAction::Execute(Event /*event*/) bool FollowAction::Execute(Event /*event*/)
{ {
Formation* formation = AI_VALUE(Formation*, "formation"); Formation* formation = AI_VALUE(Formation*, "formation");
std::string const target = formation->GetTargetName(); std::string const target = formation->GetTargetName();
// Transport handling for moving transports only (boats/zeppelins).
Player* master = botAI->GetMaster();
if (master && master->IsInWorld() && bot->IsInWorld() && bot->GetMapId() == master->GetMapId())
{
Map* map = master->GetMap();
uint32 const mapId = bot->GetMapId();
Transport* transport = nullptr;
bool masterOnTransport = false;
if (master->GetTransport())
{
transport = master->GetTransport();
masterOnTransport = true;
}
else if (map)
{
transport = GetTransportForPosTolerant(map, master, master->GetPhaseMask(),
master->GetPositionX(), master->GetPositionY(), master->GetPositionZ());
masterOnTransport = (transport != nullptr);
}
// Ignore static transports (elevators/trams): only keep boats/zeppelins here.
if (transport && transport->IsStaticTransport())
transport = nullptr;
if (transport && map && bot->GetTransport() != transport)
{
float const botProbeZ = std::max(bot->GetPositionZ(), transport->GetPositionZ());
Transport* botSurfaceTransport = GetTransportForPosTolerant(map, bot, bot->GetPhaseMask(),
bot->GetPositionX(), bot->GetPositionY(), botProbeZ);
if (botSurfaceTransport == transport)
{
transport->AddPassenger(bot, true);
bot->StopMovingOnCurrentPos();
return true;
}
float const boardingAssistDistance = 60.0f;
float const dist2d = ServerFacade::instance().GetDistance2d(bot, master);
bool const inAssist = ServerFacade::instance().IsDistanceLessOrEqualThan(dist2d, boardingAssistDistance);
if (inAssist)
{
float destX = masterOnTransport ? master->GetPositionX() : transport->GetPositionX();
float destY = masterOnTransport ? master->GetPositionY() : transport->GetPositionY();
float destZ = masterOnTransport ? master->GetPositionZ() : transport->GetPositionZ();
float edgeX = 0.0f;
float edgeY = 0.0f;
float edgeZ = 0.0f;
if (masterOnTransport &&
FindBoardingPointOnTransport(map, transport, master,
master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(),
bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
edgeX, edgeY, edgeZ))
{
destX = edgeX;
destY = edgeY;
destZ = edgeZ;
}
MovementPriority const priority = botAI->GetState() == BOT_STATE_COMBAT
? MovementPriority::MOVEMENT_COMBAT
: MovementPriority::MOVEMENT_NORMAL;
bool const movingAllowed = IsMovingAllowed(mapId, destX, destY, destZ);
bool const dupMove = IsDuplicateMove(mapId, destX, destY, destZ);
bool const waiting = IsWaitingForLastMove(priority);
if (movingAllowed && !dupMove && !waiting)
{
if (bot->IsSitState())
bot->SetStandState(UNIT_STAND_STATE_STAND);
if (bot->IsNonMeleeSpellCast(true))
{
bot->CastStop();
botAI->InterruptSpell();
}
if (MotionMaster* mm = bot->GetMotionMaster())
{
mm->MovePoint(
/*id*/ 0,
/*coords*/ destX, destY, destZ,
/*forcedMovement*/ FORCED_MOVEMENT_NONE,
/*speed*/ 0.0f,
/*orientation*/ 0.0f,
/*generatePath*/ false,
/*forceDestination*/ false);
}
else
return false;
float delay = 1000.0f * MoveDelay(bot->GetExactDist(destX, destY, destZ));
delay = std::clamp(delay, 0.0f, static_cast<float>(sPlayerbotAIConfig.maxWaitForMove));
AI_VALUE(LastMovement&, "last movement")
.Set(mapId, destX, destY, destZ, bot->GetOrientation(), delay, priority);
ClearIdleState();
return true;
}
}
}
}
// end unified transport handling
bool moved = false; bool moved = false;
if (!target.empty()) if (!target.empty())
{ {

View File

@ -37,10 +37,10 @@ public:
uint32 GetCurrWaypoint() override; uint32 GetCurrWaypoint() override;
}; };
class GrobbulusMoveCenterAction : public MoveInsideAction class GrobblulusMoveCenterAction : public MoveInsideAction
{ {
public: public:
GrobbulusMoveCenterAction(PlayerbotAI* ai) : MoveInsideAction(ai, 3281.23f, -3310.38f, 5.0f) {} GrobblulusMoveCenterAction(PlayerbotAI* ai) : MoveInsideAction(ai, 3281.23f, -3310.38f, 5.0f) {}
}; };
class GrobbulusMoveAwayAction : public MovementAction class GrobbulusMoveAwayAction : public MovementAction
@ -173,26 +173,26 @@ private:
RazuviousBossHelper helper; RazuviousBossHelper helper;
}; };
class FourHorsemenAttractAlternativelyAction : public AttackAction class HorsemanAttractAlternativelyAction : public AttackAction
{ {
public: public:
FourHorsemenAttractAlternativelyAction(PlayerbotAI* ai) : AttackAction(ai, "four horsemen attract alternatively"), helper(ai) HorsemanAttractAlternativelyAction(PlayerbotAI* ai) : AttackAction(ai, "horseman attract alternatively"), helper(ai)
{ {
} }
bool Execute(Event event) override; bool Execute(Event event) override;
protected: protected:
FourHorsemenBossHelper helper; FourhorsemanBossHelper helper;
}; };
class FourHorsemenAttackInOrderAction : public AttackAction class HorsemanAttactInOrderAction : public AttackAction
{ {
public: public:
FourHorsemenAttackInOrderAction(PlayerbotAI* ai) : AttackAction(ai, "four horsemen attack in order"), helper(ai) {} HorsemanAttactInOrderAction(PlayerbotAI* ai) : AttackAction(ai, "horseman attact in order"), helper(ai) {}
bool Execute(Event event) override; bool Execute(Event event) override;
protected: protected:
FourHorsemenBossHelper helper; FourhorsemanBossHelper helper;
}; };
// class SapphironGroundMainTankPositionAction : public MovementAction // class SapphironGroundMainTankPositionAction : public MovementAction

View File

@ -1,6 +1,7 @@
#include "RaidNaxxActions.h"
#include "ObjectGuid.h" #include "ObjectGuid.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "RaidNaxxActions.h"
bool AnubrekhanChooseTargetAction::Execute(Event /*event*/) bool AnubrekhanChooseTargetAction::Execute(Event /*event*/)
{ {
@ -65,10 +66,13 @@ bool AnubrekhanPositionAction::Execute(Event /*event*/)
{ {
uint32 nearest = FindNearestWaypoint(); uint32 nearest = FindNearestWaypoint();
uint32 next_point; uint32 next_point;
if (inPhase)
next_point = (nearest + 1) % intervals; next_point = (nearest + 1) % intervals;
else
next_point = nearest;
return MoveTo(bot->GetMapId(), waypoints[next_point].first, waypoints[next_point].second, return MoveTo(bot->GetMapId(), waypoints[next_point].first, waypoints[next_point].second, bot->GetPositionZ(), false, false,
bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT); false, false, MovementPriority::MOVEMENT_COMBAT);
} }
else else
return MoveInside(533, 3272.49f, -3476.27f, bot->GetPositionZ(), 3.0f, MovementPriority::MOVEMENT_COMBAT); return MoveInside(533, 3272.49f, -3476.27f, bot->GetPositionZ(), 3.0f, MovementPriority::MOVEMENT_COMBAT);

View File

@ -2,7 +2,7 @@
#include "Playerbots.h" #include "Playerbots.h"
bool FourHorsemenAttractAlternativelyAction::Execute(Event /*event*/) bool HorsemanAttractAlternativelyAction::Execute(Event /*event*/)
{ {
if (!helper.UpdateBossAI()) if (!helper.UpdateBossAI())
return false; return false;
@ -13,13 +13,13 @@ bool FourHorsemenAttractAlternativelyAction::Execute(Event /*event*/)
return true; return true;
Unit* attackTarget = helper.CurrentAttackTarget(); Unit* attackTarget = helper.CurrentAttackTarget();
if (attackTarget && context->GetValue<Unit*>("current target")->Get() != attackTarget) if (context->GetValue<Unit*>("current target")->Get() != attackTarget)
return Attack(attackTarget); return Attack(attackTarget);
return false; return false;
} }
bool FourHorsemenAttackInOrderAction::Execute(Event /*event*/) bool HorsemanAttactInOrderAction::Execute(Event /*event*/)
{ {
if (!helper.UpdateBossAI()) if (!helper.UpdateBossAI())
return false; return false;

View File

@ -70,6 +70,7 @@ bool SapphironFlightPositionAction::MoveToNearestIcebolt()
if (!group) if (!group)
return false; return false;
Group::MemberSlotList const& slots = group->GetMemberSlots();
Player* playerWithIcebolt = nullptr; Player* playerWithIcebolt = nullptr;
float minDistance; float minDistance;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())

View File

@ -13,7 +13,7 @@ bool ThaddiusAttackNearestPetAction::isUseful()
return false; return false;
Unit* target = helper.GetNearestPet(); Unit* target = helper.GetNearestPet();
if (!target || !bot->IsWithinDistInMap(target, 50.0f)) if (!bot->IsWithinDistInMap(target, 50.0f))
return false; return false;
return true; return true;
@ -22,7 +22,7 @@ bool ThaddiusAttackNearestPetAction::isUseful()
bool ThaddiusAttackNearestPetAction::Execute(Event /*event*/) bool ThaddiusAttackNearestPetAction::Execute(Event /*event*/)
{ {
Unit* target = helper.GetNearestPet(); Unit* target = helper.GetNearestPet();
if (!target || !bot->IsWithinLOSInMap(target)) if (!bot->IsWithinLOSInMap(target))
return MoveTo(target, 0, MovementPriority::MOVEMENT_COMBAT); return MoveTo(target, 0, MovementPriority::MOVEMENT_COMBAT);
if (AI_VALUE(Unit*, "current target") != target) if (AI_VALUE(Unit*, "current target") != target)

View File

@ -245,7 +245,7 @@ float AnubrekhanGenericMultiplier::GetValue(Action* action)
return 1.0f; return 1.0f;
} }
float FourHorsemenGenericMultiplier::GetValue(Action* action) float FourhorsemanGenericMultiplier::GetValue(Action* action)
{ {
Unit* boss = AI_VALUE2(Unit*, "find target", "sir zeliek"); Unit* boss = AI_VALUE2(Unit*, "find target", "sir zeliek");
if (!boss) if (!boss)

View File

@ -1,6 +1,6 @@
#ifndef _PLAYERBOT_RAIDNAXXMULTIPLIERS_H #ifndef _PLAYERRBOT_RAIDNAXXMULTIPLIERS_H
#define _PLAYERBOT_RAIDNAXXMULTIPLIERS_H #define _PLAYERRBOT_RAIDNAXXMULTIPLIERS_H
#include "Multiplier.h" #include "Multiplier.h"
#include "RaidNaxxBossHelper.h" #include "RaidNaxxBossHelper.h"
@ -84,10 +84,10 @@ public:
virtual float GetValue(Action* action); virtual float GetValue(Action* action);
}; };
class FourHorsemenGenericMultiplier : public Multiplier class FourhorsemanGenericMultiplier : public Multiplier
{ {
public: public:
FourHorsemenGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "four horsemen generic") {} FourhorsemanGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "fourhorseman generic") {}
public: public:
virtual float GetValue(Action* action); virtual float GetValue(Action* action);

View File

@ -31,8 +31,8 @@ public:
creators["razuvious use obedience crystal"] = &RaidNaxxActionContext::razuvious_use_obedience_crystal; creators["razuvious use obedience crystal"] = &RaidNaxxActionContext::razuvious_use_obedience_crystal;
creators["razuvious target"] = &RaidNaxxActionContext::razuvious_target; creators["razuvious target"] = &RaidNaxxActionContext::razuvious_target;
creators["four horsemen attract alternatively"] = &RaidNaxxActionContext::four_horsemen_attract_alternatively; creators["horseman attract alternatively"] = &RaidNaxxActionContext::horseman_attract_alternatively;
creators["four horsemen attack in order"] = &RaidNaxxActionContext::four_horsemen_attack_in_order; creators["horseman attack in order"] = &RaidNaxxActionContext::horseman_attack_in_order;
creators["sapphiron ground position"] = &RaidNaxxActionContext::sapphiron_ground_position; creators["sapphiron ground position"] = &RaidNaxxActionContext::sapphiron_ground_position;
creators["sapphiron flight position"] = &RaidNaxxActionContext::sapphiron_flight_position; creators["sapphiron flight position"] = &RaidNaxxActionContext::sapphiron_flight_position;
@ -56,7 +56,7 @@ public:
private: private:
static Action* go_behind_the_boss(PlayerbotAI* ai) { return new GrobbulusGoBehindAction(ai); } static Action* go_behind_the_boss(PlayerbotAI* ai) { return new GrobbulusGoBehindAction(ai); }
static Action* rotate_grobbulus(PlayerbotAI* ai) { return new GrobbulusRotateAction(ai); } static Action* rotate_grobbulus(PlayerbotAI* ai) { return new GrobbulusRotateAction(ai); }
static Action* grobbulus_move_center(PlayerbotAI* ai) { return new GrobbulusMoveCenterAction(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* grobbulus_move_away(PlayerbotAI* ai) { return new GrobbulusMoveAwayAction(ai); }
//static Action* heigan_dance_melee(PlayerbotAI* ai) { return new HeiganDanceMeleeAction(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* heigan_dance_ranged(PlayerbotAI* ai) { return new HeiganDanceRangedAction(ai); }
@ -70,8 +70,11 @@ private:
{ {
return new RazuviousUseObedienceCrystalAction(ai); return new RazuviousUseObedienceCrystalAction(ai);
} }
static Action* four_horsemen_attract_alternatively(PlayerbotAI* ai) { return new FourHorsemenAttractAlternativelyAction(ai); } static Action* horseman_attract_alternatively(PlayerbotAI* ai)
static Action* four_horsemen_attack_in_order(PlayerbotAI* ai) { return new FourHorsemenAttackInOrderAction(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 // static Action* sapphiron_ground_main_tank_position(PlayerbotAI* ai) { return new
// SapphironGroundMainTankPositionAction(ai); } // SapphironGroundMainTankPositionAction(ai); }
static Action* sapphiron_ground_position(PlayerbotAI* ai) { return new SapphironGroundPositionAction(ai); } static Action* sapphiron_ground_position(PlayerbotAI* ai) { return new SapphironGroundPositionAction(ai); }

View File

@ -30,8 +30,8 @@ public:
creators["razuvious tank"] = &RaidNaxxTriggerContext::razuvious_tank; creators["razuvious tank"] = &RaidNaxxTriggerContext::razuvious_tank;
creators["razuvious nontank"] = &RaidNaxxTriggerContext::razuvious_nontank; creators["razuvious nontank"] = &RaidNaxxTriggerContext::razuvious_nontank;
creators["four horsemen attractors"] = &RaidNaxxTriggerContext::four_horsemen_attractors; creators["horseman attractors"] = &RaidNaxxTriggerContext::horseman_attractors;
creators["four horsemen except attractors"] = &RaidNaxxTriggerContext::four_horsemen_except_attractors; creators["horseman except attractors"] = &RaidNaxxTriggerContext::horseman_except_attractors;
creators["sapphiron ground"] = &RaidNaxxTriggerContext::sapphiron_ground; creators["sapphiron ground"] = &RaidNaxxTriggerContext::sapphiron_ground;
creators["sapphiron flight"] = &RaidNaxxTriggerContext::sapphiron_flight; creators["sapphiron flight"] = &RaidNaxxTriggerContext::sapphiron_flight;
@ -66,8 +66,8 @@ private:
static Trigger* razuvious_tank(PlayerbotAI* ai) { return new RazuviousTankTrigger(ai); } static Trigger* razuvious_tank(PlayerbotAI* ai) { return new RazuviousTankTrigger(ai); }
static Trigger* razuvious_nontank(PlayerbotAI* ai) { return new RazuviousNontankTrigger(ai); } static Trigger* razuvious_nontank(PlayerbotAI* ai) { return new RazuviousNontankTrigger(ai); }
static Trigger* four_horsemen_attractors(PlayerbotAI* ai) { return new FourHorsemenAttractorsTrigger(ai); } static Trigger* horseman_attractors(PlayerbotAI* ai) { return new HorsemanAttractorsTrigger(ai); }
static Trigger* four_horsemen_except_attractors(PlayerbotAI* ai) { return new FourHorsemenExceptAttractorsTrigger(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_ground(PlayerbotAI* ai) { return new SapphironGroundTrigger(ai); }
static Trigger* sapphiron_flight(PlayerbotAI* ai) { return new SapphironFlightTrigger(ai); } static Trigger* sapphiron_flight(PlayerbotAI* ai) { return new SapphironFlightTrigger(ai); }

View File

@ -97,13 +97,13 @@ void RaidNaxxStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ NextAction("razuvious target", ACTION_RAID + 1) } { NextAction("razuvious target", ACTION_RAID + 1) }
)); ));
// four horsemen // four horseman
triggers.push_back(new TriggerNode("four horsemen attractors", triggers.push_back(new TriggerNode("horseman attractors",
{ NextAction("four horsemen attract alternatively", ACTION_RAID + 1) } { NextAction("horseman attract alternatively", ACTION_RAID + 1) }
)); ));
triggers.push_back(new TriggerNode("four horsemen except attractors", triggers.push_back(new TriggerNode("horseman except attractors",
{ NextAction("four horsemen attack in order", ACTION_RAID + 1) } { NextAction("horseman attack in order", ACTION_RAID + 1) }
)); ));
// sapphiron // sapphiron
@ -150,7 +150,7 @@ void RaidNaxxStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
multipliers.push_back(new InstructorRazuviousGenericMultiplier(botAI)); multipliers.push_back(new InstructorRazuviousGenericMultiplier(botAI));
multipliers.push_back(new KelthuzadGenericMultiplier(botAI)); multipliers.push_back(new KelthuzadGenericMultiplier(botAI));
multipliers.push_back(new AnubrekhanGenericMultiplier(botAI)); multipliers.push_back(new AnubrekhanGenericMultiplier(botAI));
multipliers.push_back(new FourHorsemenGenericMultiplier(botAI)); multipliers.push_back(new FourhorsemanGenericMultiplier(botAI));
// multipliers.push_back(new GothikGenericMultiplier(botAI)); // multipliers.push_back(new GothikGenericMultiplier(botAI));
multipliers.push_back(new GluthGenericMultiplier(botAI)); multipliers.push_back(new GluthGenericMultiplier(botAI));
} }

View File

@ -114,7 +114,7 @@ bool RazuviousNontankTrigger::IsActive()
return helper.UpdateBossAI() && !(bot->getClass() == CLASS_PRIEST); return helper.UpdateBossAI() && !(bot->getClass() == CLASS_PRIEST);
} }
bool FourHorsemenAttractorsTrigger::IsActive() bool HorsemanAttractorsTrigger::IsActive()
{ {
if (!helper.UpdateBossAI()) if (!helper.UpdateBossAI())
return false; return false;
@ -122,7 +122,7 @@ bool FourHorsemenAttractorsTrigger::IsActive()
return helper.IsAttracter(bot); return helper.IsAttracter(bot);
} }
bool FourHorsemenExceptAttractorsTrigger::IsActive() bool HorsemanExceptAttractorsTrigger::IsActive()
{ {
if (!helper.UpdateBossAI()) if (!helper.UpdateBossAI())
return false; return false;

View File

@ -186,24 +186,24 @@ private:
ThaddiusBossHelper helper; ThaddiusBossHelper helper;
}; };
class FourHorsemenAttractorsTrigger : public Trigger class HorsemanAttractorsTrigger : public Trigger
{ {
public: public:
FourHorsemenAttractorsTrigger(PlayerbotAI* ai) : Trigger(ai, "four horsemen attractors"), helper(ai) {} HorsemanAttractorsTrigger(PlayerbotAI* ai) : Trigger(ai, "fourhorsemen attractors"), helper(ai) {}
bool IsActive() override; bool IsActive() override;
private: private:
FourHorsemenBossHelper helper; FourhorsemanBossHelper helper;
}; };
class FourHorsemenExceptAttractorsTrigger : public Trigger class HorsemanExceptAttractorsTrigger : public Trigger
{ {
public: public:
FourHorsemenExceptAttractorsTrigger(PlayerbotAI* ai) : Trigger(ai, "four horsemen except attractors"), helper(ai) {} HorsemanExceptAttractorsTrigger(PlayerbotAI* ai) : Trigger(ai, "fourhorsemen except attractors"), helper(ai) {}
bool IsActive() override; bool IsActive() override;
private: private:
FourHorsemenBossHelper helper; FourhorsemanBossHelper helper;
}; };
class SapphironGroundTrigger : public Trigger class SapphironGroundTrigger : public Trigger

View File

@ -202,7 +202,7 @@ public:
} }
bool FindPosToAvoidChill(std::vector<float>& dest) bool FindPosToAvoidChill(std::vector<float>& dest)
{ {
Aura* aura = NaxxSpellIds::GetAnyAura(bot, {NaxxSpellIds::Chill10, NaxxSpellIds::Chill25}); Aura* aura = NaxxSpellIds::GetAnyAura(bot, {NaxxSpellIds::Chill25});
if (!aura) if (!aura)
{ {
// Fallback to name for custom spell data. // Fallback to name for custom spell data.
@ -363,13 +363,13 @@ private:
Unit* _unit = nullptr; Unit* _unit = nullptr;
}; };
class FourHorsemenBossHelper : public AiObject class FourhorsemanBossHelper : public AiObject
{ {
public: public:
const float posZ = 241.27f; const float posZ = 241.27f;
const std::pair<float, float> attractPos[2] = {{2502.03f, -2910.90f}, const std::pair<float, float> attractPos[2] = {{2502.03f, -2910.90f},
{2484.61f, -2947.07f}}; // left (sir zeliek), right (lady blaumeux) {2484.61f, -2947.07f}}; // left (sir zeliek), right (lady blaumeux)
FourHorsemenBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {} FourhorsemanBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {}
bool UpdateBossAI() bool UpdateBossAI()
{ {
if (!bot->IsInCombat()) if (!bot->IsInCombat())
@ -497,8 +497,7 @@ public:
if (feugen && feugen->IsAlive()) if (feugen && feugen->IsAlive())
unit = feugen; unit = feugen;
if (stalagg && stalagg->IsAlive() && if (stalagg && stalagg->IsAlive() && (!feugen || bot->GetDistance(stalagg) < bot->GetDistance(feugen)))
(!feugen || !feugen->IsAlive() || bot->GetDistance(stalagg) < bot->GetDistance(feugen)))
unit = stalagg; unit = stalagg;
return unit; return unit;

View File

@ -58,7 +58,6 @@ namespace NaxxSpellIds
// Sapphiron // Sapphiron
static constexpr uint32 Icebolt10 = 28522; static constexpr uint32 Icebolt10 = 28522;
static constexpr uint32 Icebolt25 = 28526; static constexpr uint32 Icebolt25 = 28526;
static constexpr uint32 Chill10 = 28547;
static constexpr uint32 Chill25 = 55699; static constexpr uint32 Chill25 = 55699;
/* /*
// Fight // Fight

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,3 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_RAIDSSCACTIONS_H #ifndef _PLAYERBOT_RAIDSSCACTIONS_H
#define _PLAYERBOT_RAIDSSCACTIONS_H #define _PLAYERBOT_RAIDSSCACTIONS_H
@ -80,8 +75,8 @@ public:
bool Execute(Event event) override; bool Execute(Event event) override;
private: private:
bool TryMisdirectToFrostTank(Unit* hydross); bool TryMisdirectToFrostTank(Unit* hydross, Group* group);
bool TryMisdirectToNatureTank(Unit* hydross); bool TryMisdirectToNatureTank(Unit* hydross, Group* group);
}; };
class HydrossTheUnstableStopDpsUponPhaseChangeAction : public Action class HydrossTheUnstableStopDpsUponPhaseChangeAction : public Action
@ -418,10 +413,10 @@ private:
bool LineUpSecondCorePasser(Player* firstCorePasser, Unit* closestTrigger); bool LineUpSecondCorePasser(Player* firstCorePasser, Unit* closestTrigger);
bool LineUpThirdCorePasser(Player* designatedLooter, Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger); bool LineUpThirdCorePasser(Player* designatedLooter, Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger);
bool LineUpFourthCorePasser(Player* firstCorePasser, Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger); bool LineUpFourthCorePasser(Player* firstCorePasser, Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger);
bool IsFirstCorePasserInPosition(Player* designatedLooter, Player* firstCorePasser, Unit* closestTrigger); bool IsFirstCorePasserInIntendedPosition(Player* designatedLooter, Player* firstCorePasser, Unit* closestTrigger);
bool IsSecondCorePasserInPosition(Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger); bool IsSecondCorePasserInIntendedPosition(Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger);
bool IsThirdCorePasserInPosition(Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger); bool IsThirdCorePasserInIntendedPosition(Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger);
bool IsFourthCorePasserInPosition(Player* thirdCorePasser, Player* fourthCorePasser, Unit* closestTrigger); bool IsFourthCorePasserInIntendedPosition(Player* thirdCorePasser, Player* fourthCorePasser, Unit* closestTrigger);
void ScheduleTransferCoreAfterImbue(PlayerbotAI* botAI, Player* giver, Player* receiver); void ScheduleTransferCoreAfterImbue(PlayerbotAI* botAI, Player* giver, Player* receiver);
bool UseCoreOnNearestGenerator(const uint32 instanceId); bool UseCoreOnNearestGenerator(const uint32 instanceId);
}; };
@ -433,12 +428,19 @@ public:
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
class LadyVashjEraseCorePassingTrackersAction : public Action
{
public:
LadyVashjEraseCorePassingTrackersAction(PlayerbotAI* botAI, std::string const name = "lady vashj erase core passing trackers") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class LadyVashjAvoidToxicSporesAction : public MovementAction class LadyVashjAvoidToxicSporesAction : public MovementAction
{ {
public: public:
LadyVashjAvoidToxicSporesAction(PlayerbotAI* botAI, std::string const name = "lady vashj avoid toxic spores") : MovementAction(botAI, name) {} LadyVashjAvoidToxicSporesAction(PlayerbotAI* botAI, std::string const name = "lady vashj avoid toxic spores") : MovementAction(botAI, name) {}
bool Execute(Event event) override; bool Execute(Event event) override;
static std::vector<Unit*> GetAllSporeDropTriggers(Player* bot); static std::vector<Unit*> GetAllSporeDropTriggers(PlayerbotAI* botAI, Player* bot);
private: private:
Position FindSafestNearbyPosition(const std::vector<Unit*>& spores, const Position& position, float maxRadius, float hazardRadius); Position FindSafestNearbyPosition(const std::vector<Unit*>& spores, const Position& position, float maxRadius, float hazardRadius);

View File

@ -1,8 +1,3 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "RaidSSCMultipliers.h" #include "RaidSSCMultipliers.h"
#include "RaidSSCActions.h" #include "RaidSSCActions.h"
#include "RaidSSCHelpers.h" #include "RaidSSCHelpers.h"
@ -33,10 +28,12 @@ using namespace SerpentShrineCavernHelpers;
float UnderbogColossusEscapeToxicPoolMultiplier::GetValue(Action* action) float UnderbogColossusEscapeToxicPoolMultiplier::GetValue(Action* action)
{ {
if (bot->HasAura(SPELL_TOXIC_POOL) && if (bot->HasAura(SPELL_TOXIC_POOL))
dynamic_cast<MovementAction*>(action) && {
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<UnderbogColossusEscapeToxicPoolAction*>(action)) !dynamic_cast<UnderbogColossusEscapeToxicPoolAction*>(action))
return 0.0f; return 0.0f;
}
return 1.0f; return 1.0f;
} }
@ -56,16 +53,16 @@ float HydrossTheUnstableDisableTankActionsMultiplier::GetValue(Action* action)
dynamic_cast<CombatFormationMoveAction*>(action)) dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f; return 0.0f;
if ((botAI->IsMainTank(bot) && !hydross->HasAura(SPELL_CORRUPTION)) ||
(botAI->IsAssistTankOfIndex(bot, 0, true) && hydross->HasAura(SPELL_CORRUPTION)))
return 1.0f;
if (dynamic_cast<CastReachTargetSpellAction*>(action) || if (dynamic_cast<CastReachTargetSpellAction*>(action) ||
dynamic_cast<ReachTargetAction*>(action) || dynamic_cast<ReachTargetAction*>(action) ||
(dynamic_cast<AttackAction*>(action) && (dynamic_cast<AttackAction*>(action) &&
!dynamic_cast<HydrossTheUnstablePositionFrostTankAction*>(action) && !dynamic_cast<HydrossTheUnstablePositionFrostTankAction*>(action) &&
!dynamic_cast<HydrossTheUnstablePositionNatureTankAction*>(action))) !dynamic_cast<HydrossTheUnstablePositionNatureTankAction*>(action)))
{
if ((botAI->IsMainTank(bot) && hydross->HasAura(SPELL_CORRUPTION)) ||
(botAI->IsAssistTankOfIndex(bot, 0, true) && !hydross->HasAura(SPELL_CORRUPTION)))
return 0.0f; return 0.0f;
}
return 1.0f; return 1.0f;
} }
@ -100,14 +97,14 @@ float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action)
bool aboutToChange = (itPhase != hydrossChangeToFrostPhaseTimer.end() && bool aboutToChange = (itPhase != hydrossChangeToFrostPhaseTimer.end() &&
(now - itPhase->second) > phaseChangeWaitSeconds); (now - itPhase->second) > phaseChangeWaitSeconds);
if (!justChanged && !aboutToChange) if (justChanged || aboutToChange)
return 1.0f; {
if (dynamic_cast<AttackAction*>(action) || if (dynamic_cast<AttackAction*>(action) ||
(dynamic_cast<CastSpellAction*>(action) && (dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action))) !dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f; return 0.0f;
} }
}
if (hydross->HasAura(SPELL_CORRUPTION) && !botAI->IsAssistTankOfIndex(bot, 0, true)) if (hydross->HasAura(SPELL_CORRUPTION) && !botAI->IsAssistTankOfIndex(bot, 0, true))
{ {
@ -119,14 +116,14 @@ float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action)
bool aboutToChange = (itPhase != hydrossChangeToNaturePhaseTimer.end() && bool aboutToChange = (itPhase != hydrossChangeToNaturePhaseTimer.end() &&
(now - itPhase->second) > phaseChangeWaitSeconds); (now - itPhase->second) > phaseChangeWaitSeconds);
if (!justChanged && !aboutToChange) if (justChanged || aboutToChange)
return 1.0f; {
if (dynamic_cast<AttackAction*>(action) || if (dynamic_cast<AttackAction*>(action) ||
(dynamic_cast<CastSpellAction*>(action) && (dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action))) !dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f; return 0.0f;
} }
}
return 1.0f; return 1.0f;
} }
@ -136,9 +133,11 @@ float HydrossTheUnstableControlMisdirectionMultiplier::GetValue(Action* action)
if (bot->getClass() != CLASS_HUNTER) if (bot->getClass() != CLASS_HUNTER)
return 1.0f; return 1.0f;
if (AI_VALUE2(Unit*, "find target", "hydross the unstable") && if (AI_VALUE2(Unit*, "find target", "hydross the unstable"))
dynamic_cast<CastMisdirectionOnMainTankAction*>(action)) {
if (dynamic_cast<CastMisdirectionOnMainTankAction*>(action))
return 0.0f; return 0.0f;
}
return 1.0f; return 1.0f;
} }
@ -176,14 +175,14 @@ float TheLurkerBelowMaintainRangedSpreadMultiplier::GetValue(Action* action)
if (!botAI->IsRanged(bot)) if (!botAI->IsRanged(bot))
return 1.0f; return 1.0f;
if (!AI_VALUE2(Unit*, "find target", "the lurker below")) if (AI_VALUE2(Unit*, "find target", "the lurker below"))
return 1.0f; {
if (dynamic_cast<CombatFormationMoveAction*>(action) || if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<FleeAction*>(action) || dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) || dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action)) dynamic_cast<CastBlinkBackAction*>(action))
return 0.0f; return 0.0f;
}
return 1.0f; return 1.0f;
} }
@ -216,11 +215,11 @@ float TheLurkerBelowDisableTankAssistMultiplier::GetValue(Action* action)
++tankCount; ++tankCount;
} }
if (tankCount < 3) if (tankCount >= 3)
return 1.0f; {
if (dynamic_cast<TankAssistAction*>(action)) if (dynamic_cast<TankAssistAction*>(action))
return 0.0f; return 0.0f;
}
return 1.0f; return 1.0f;
} }
@ -235,11 +234,14 @@ float LeotherasTheBlindAvoidWhirlwindMultiplier::GetValue(Action* action)
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return 1.0f; return 1.0f;
Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); Unit* leotherasHuman = GetLeotherasHuman(botAI);
if (!leotheras || (!leotheras->HasAura(SPELL_WHIRLWIND) && if (!leotherasHuman)
!leotheras->HasAura(SPELL_WHIRLWIND_CHANNEL)))
return 1.0f; return 1.0f;
if (!leotherasHuman->HasAura(SPELL_LEOTHERAS_BANISHED) &&
(leotherasHuman->HasAura(SPELL_WHIRLWIND) ||
leotherasHuman->HasAura(SPELL_WHIRLWIND_CHANNEL)))
{
if (dynamic_cast<CastReachTargetSpellAction*>(action)) if (dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f; return 0.0f;
@ -247,6 +249,7 @@ float LeotherasTheBlindAvoidWhirlwindMultiplier::GetValue(Action* action)
!dynamic_cast<AttackAction*>(action) && !dynamic_cast<AttackAction*>(action) &&
!dynamic_cast<LeotherasTheBlindRunAwayFromWhirlwindAction*>(action)) !dynamic_cast<LeotherasTheBlindRunAwayFromWhirlwindAction*>(action))
return 0.0f; return 0.0f;
}
return 1.0f; return 1.0f;
} }
@ -259,10 +262,10 @@ float LeotherasTheBlindDisableTankActionsMultiplier::GetValue(Action* action)
if (!AI_VALUE2(Unit*, "find target", "leotheras the blind")) if (!AI_VALUE2(Unit*, "find target", "leotheras the blind"))
return 1.0f; return 1.0f;
if (GetPhase2LeotherasDemon(bot) && dynamic_cast<AttackAction*>(action)) if (GetPhase2LeotherasDemon(botAI) && dynamic_cast<AttackAction*>(action))
return 0.0f; return 0.0f;
if (!GetPhase3LeotherasDemon(bot) && dynamic_cast<CastBerserkAction*>(action)) if (!GetPhase3LeotherasDemon(botAI) && dynamic_cast<CastBerserkAction*>(action))
return 0.0f; return 0.0f;
return 1.0f; return 1.0f;
@ -270,9 +273,8 @@ float LeotherasTheBlindDisableTankActionsMultiplier::GetValue(Action* action)
float LeotherasTheBlindFocusOnInnerDemonMultiplier::GetValue(Action* action) float LeotherasTheBlindFocusOnInnerDemonMultiplier::GetValue(Action* action)
{ {
if (!bot->HasAura(SPELL_INSIDIOUS_WHISPER)) if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return 1.0f; {
if (dynamic_cast<TankAssistAction*>(action) || if (dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<DpsAssistAction*>(action) || dynamic_cast<DpsAssistAction*>(action) ||
dynamic_cast<CastHealingSpellAction*>(action) || dynamic_cast<CastHealingSpellAction*>(action) ||
@ -285,6 +287,7 @@ float LeotherasTheBlindFocusOnInnerDemonMultiplier::GetValue(Action* action)
dynamic_cast<CastDireBearFormAction*>(action) || dynamic_cast<CastDireBearFormAction*>(action) ||
dynamic_cast<CastTreeFormAction*>(action)) dynamic_cast<CastTreeFormAction*>(action))
return 0.0f; return 0.0f;
}
return 1.0f; return 1.0f;
} }
@ -294,19 +297,19 @@ float LeotherasTheBlindMeleeDpsAvoidChaosBlastMultiplier::GetValue(Action* actio
if (botAI->IsRanged(bot) || botAI->IsTank(bot)) if (botAI->IsRanged(bot) || botAI->IsTank(bot))
return 1.0f; return 1.0f;
if (!GetPhase2LeotherasDemon(bot)) if (!GetPhase2LeotherasDemon(botAI))
return 1.0f; return 1.0f;
Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST); Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST);
if (!chaosBlast || chaosBlast->GetStackAmount() < 5) if (chaosBlast && chaosBlast->GetStackAmount() >= 5)
return 1.0f; {
if (dynamic_cast<AttackAction*>(action) || if (dynamic_cast<AttackAction*>(action) ||
dynamic_cast<ReachTargetAction*>(action) || dynamic_cast<ReachTargetAction*>(action) ||
dynamic_cast<CombatFormationMoveAction*>(action) || dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<CastReachTargetSpellAction*>(action) || dynamic_cast<CastReachTargetSpellAction*>(action) ||
dynamic_cast<CastKillingSpreeAction*>(action)) dynamic_cast<CastKillingSpreeAction*>(action))
return 0.0f; return 0.0f;
}
return 1.0f; return 1.0f;
} }
@ -327,8 +330,8 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action)
const time_t now = std::time(nullptr); const time_t now = std::time(nullptr);
constexpr uint8 dpsWaitSecondsPhase1 = 5; constexpr uint8 dpsWaitSecondsPhase1 = 5;
Unit* leotherasHuman = GetLeotherasHuman(bot); Unit* leotherasHuman = GetLeotherasHuman(botAI);
Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(bot); Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI);
if (leotherasHuman && !leotherasHuman->HasAura(SPELL_LEOTHERAS_BANISHED) && if (leotherasHuman && !leotherasHuman->HasAura(SPELL_LEOTHERAS_BANISHED) &&
!leotherasPhase3Demon) !leotherasPhase3Demon)
{ {
@ -347,7 +350,7 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action)
} }
constexpr uint8 dpsWaitSecondsPhase2 = 12; constexpr uint8 dpsWaitSecondsPhase2 = 12;
Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(bot); Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI);
Player* demonFormTank = GetLeotherasDemonFormTank(bot); Player* demonFormTank = GetLeotherasDemonFormTank(bot);
if (leotherasPhase2Demon) if (leotherasPhase2Demon)
{ {
@ -395,12 +398,12 @@ float LeotherasTheBlindDelayBloodlustAndHeroismMultiplier::GetValue(Action* acti
return 1.0f; return 1.0f;
Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind");
if (!leotheras || !leotheras->HasAura(SPELL_LEOTHERAS_BANISHED)) if (leotheras && leotheras->HasAura(SPELL_LEOTHERAS_BANISHED))
return 1.0f; {
if (dynamic_cast<CastHeroismAction*>(action) || if (dynamic_cast<CastHeroismAction*>(action) ||
dynamic_cast<CastBloodlustAction*>(action)) dynamic_cast<CastBloodlustAction*>(action))
return 0.0f; return 0.0f;
}
return 1.0f; return 1.0f;
} }
@ -444,12 +447,14 @@ float FathomLordKarathressDisableAoeMultiplier::GetValue(Action* action)
if (!botAI->IsDps(bot)) if (!botAI->IsDps(bot))
return 1.0f; return 1.0f;
if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) if (AI_VALUE2(Unit*, "find target", "fathom-lord karathress"))
return 1.0f; {
if (auto castSpellAction = dynamic_cast<CastSpellAction*>(action))
auto castSpellAction = dynamic_cast<CastSpellAction*>(action); {
if (castSpellAction && castSpellAction->getThreatType() == Action::ActionThreatType::Aoe) if (castSpellAction->getThreatType() == Action::ActionThreatType::Aoe)
return 0.0f; return 0.0f;
}
}
return 1.0f; return 1.0f;
} }
@ -459,11 +464,11 @@ float FathomLordKarathressControlMisdirectionMultiplier::GetValue(Action* action
if (bot->getClass() != CLASS_HUNTER) if (bot->getClass() != CLASS_HUNTER)
return 1.0f; return 1.0f;
if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) if (AI_VALUE2(Unit*, "find target", "fathom-lord karathress"))
return 1.0f; {
if (dynamic_cast<CastMisdirectionOnMainTankAction*>(action)) if (dynamic_cast<CastMisdirectionOnMainTankAction*>(action))
return 0.0f; return 0.0f;
}
return 1.0f; return 1.0f;
} }
@ -500,12 +505,12 @@ float FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier::GetValue
if (!botAI->IsAssistHealOfIndex(bot, 0, true)) if (!botAI->IsAssistHealOfIndex(bot, 0, true))
return 1.0f; return 1.0f;
if (!AI_VALUE2(Unit*, "find target", "fathom-guard caribdis")) if (AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"))
return 1.0f; {
if (dynamic_cast<FleeAction*>(action) || if (dynamic_cast<FleeAction*>(action) ||
dynamic_cast<FollowAction*>(action)) dynamic_cast<FollowAction*>(action))
return 0.0f; return 0.0f;
}
return 1.0f; return 1.0f;
} }
@ -521,12 +526,12 @@ float MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier::GetValue(Action* act
if (!AI_VALUE2(Unit*, "find target", "morogrim tidewalker")) if (!AI_VALUE2(Unit*, "find target", "morogrim tidewalker"))
return 1.0f; return 1.0f;
if (AI_VALUE2(Unit*, "find target", "tidewalker lurker")) if (!AI_VALUE2(Unit*, "find target", "tidewalker lurker"))
return 1.0f; {
if (dynamic_cast<CastHeroismAction*>(action) || if (dynamic_cast<CastHeroismAction*>(action) ||
dynamic_cast<CastBloodlustAction*>(action)) dynamic_cast<CastBloodlustAction*>(action))
return 0.0f; return 0.0f;
}
return 1.0f; return 1.0f;
} }
@ -536,11 +541,11 @@ float MorogrimTidewalkerDisableTankActionsMultiplier::GetValue(Action* action)
if (!botAI->IsMainTank(bot)) if (!botAI->IsMainTank(bot))
return 1.0f; return 1.0f;
if (!AI_VALUE2(Unit*, "find target", "morogrim tidewalker")) if (AI_VALUE2(Unit*, "find target", "morogrim tidewalker"))
return 1.0f; {
if (dynamic_cast<CombatFormationMoveAction*>(action)) if (dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f; return 0.0f;
}
return 1.0f; return 1.0f;
} }
@ -551,14 +556,17 @@ float MorogrimTidewalkerMaintainPhase2StackingMultiplier::GetValue(Action* actio
return 1.0f; return 1.0f;
Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker");
if (!tidewalker || tidewalker->GetHealthPct() > 25.0f) if (!tidewalker)
return 1.0f; return 1.0f;
if (tidewalker->GetHealthPct() < 25.0f)
{
if (dynamic_cast<CombatFormationMoveAction*>(action) || if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<FleeAction*>(action) || dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) || dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action)) dynamic_cast<CastBlinkBackAction*>(action))
return 0.0f; return 0.0f;
}
return 1.0f; return 1.0f;
} }
@ -572,15 +580,18 @@ float LadyVashjDelayCooldownsMultiplier::GetValue(Action* action)
if (!AI_VALUE2(Unit*, "find target", "lady vashj")) if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
return 1.0f; return 1.0f;
if (bot->getClass() == CLASS_SHAMAN && if (bot->getClass() == CLASS_SHAMAN)
!IsLadyVashjInPhase3(botAI) && {
(dynamic_cast<CastBloodlustAction*>(action) || if (IsLadyVashjInPhase3(botAI))
dynamic_cast<CastHeroismAction*>(action)))
return 0.0f;
if (!botAI->IsDps(bot) || !IsLadyVashjInPhase1(botAI))
return 1.0f; return 1.0f;
if (dynamic_cast<CastBloodlustAction*>(action) ||
dynamic_cast<CastHeroismAction*>(action))
return 0.0f;
}
if (botAI->IsDps(bot) && IsLadyVashjInPhase1(botAI))
{
if (dynamic_cast<CastMetamorphosisAction*>(action) || if (dynamic_cast<CastMetamorphosisAction*>(action) ||
dynamic_cast<CastAdrenalineRushAction*>(action) || dynamic_cast<CastAdrenalineRushAction*>(action) ||
dynamic_cast<CastBladeFlurryAction*>(action) || dynamic_cast<CastBladeFlurryAction*>(action) ||
@ -603,29 +614,8 @@ float LadyVashjDelayCooldownsMultiplier::GetValue(Action* action)
dynamic_cast<CastBloodFuryAction*>(action) || dynamic_cast<CastBloodFuryAction*>(action) ||
dynamic_cast<UseTrinketAction*>(action)) dynamic_cast<UseTrinketAction*>(action))
return 0.0f; return 0.0f;
return 1.0f;
} }
float LadyVashjMainTankGroupShamanUseGroundingTotemMultiplier::GetValue(Action* action)
{
if (bot->getClass() != CLASS_SHAMAN)
return 1.0f;
if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
return 1.0f;
if (!IsMainTankInSameSubgroup(botAI, bot))
return 1.0f;
if (dynamic_cast<CastWindfuryTotemAction*>(action) ||
dynamic_cast<SetWindfuryTotemAction*>(action) ||
dynamic_cast<CastWrathOfAirTotemAction*>(action) ||
dynamic_cast<SetWrathOfAirTotemAction*>(action) ||
dynamic_cast<CastNatureResistanceTotemAction*>(action) ||
dynamic_cast<SetNatureResistanceTotemAction*>(action))
return 0.0f;
return 1.0f; return 1.0f;
} }
@ -634,15 +624,15 @@ float LadyVashjMaintainPhase1RangedSpreadMultiplier::GetValue(Action* action)
if (!botAI->IsRanged(bot)) if (!botAI->IsRanged(bot))
return 1.0f; return 1.0f;
if (!AI_VALUE2(Unit*, "find target", "lady vashj") || if (AI_VALUE2(Unit*, "find target", "lady vashj") &&
!IsLadyVashjInPhase1(botAI)) IsLadyVashjInPhase1(botAI))
return 1.0f; {
if (dynamic_cast<CombatFormationMoveAction*>(action) || if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<FleeAction*>(action) || dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) || dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action)) dynamic_cast<CastBlinkBackAction*>(action))
return 0.0f; return 0.0f;
}
return 1.0f; return 1.0f;
} }
@ -668,18 +658,19 @@ float LadyVashjStaticChargeStayAwayFromGroupMultiplier::GetValue(Action* action)
// Bots should not loot the core with normal looting logic // Bots should not loot the core with normal looting logic
float LadyVashjDoNotLootTheTaintedCoreMultiplier::GetValue(Action* action) float LadyVashjDoNotLootTheTaintedCoreMultiplier::GetValue(Action* action)
{ {
if (!AI_VALUE2(Unit*, "find target", "lady vashj")) if (AI_VALUE2(Unit*, "find target", "lady vashj"))
return 1.0f; {
if (dynamic_cast<LootAction*>(action)) if (dynamic_cast<LootAction*>(action))
return 0.0f; return 0.0f;
}
return 1.0f; return 1.0f;
} }
float LadyVashjCorePassersPrioritizePositioningMultiplier::GetValue(Action* action) float LadyVashjCorePassersPrioritizePositioningMultiplier::GetValue(Action* action)
{ {
if (!AI_VALUE2(Unit*, "find target", "lady vashj") || !IsLadyVashjInPhase2(botAI)) if (!AI_VALUE2(Unit*, "find target", "lady vashj") ||
!IsLadyVashjInPhase2(botAI))
return 1.0f; return 1.0f;
if (dynamic_cast<WipeAction*>(action) || if (dynamic_cast<WipeAction*>(action) ||
@ -687,43 +678,65 @@ float LadyVashjCorePassersPrioritizePositioningMultiplier::GetValue(Action* acti
dynamic_cast<LadyVashjDestroyTaintedCoreAction*>(action)) dynamic_cast<LadyVashjDestroyTaintedCoreAction*>(action))
return 1.0f; return 1.0f;
auto coreHandlers = GetCoreHandlers(botAI, bot); Group* group = bot->GetGroup();
if (!group)
bool isCoreHandler = false;
int myIndex = -1;
for (int i = 0; i < static_cast<int>(coreHandlers.size()); ++i)
{
if (coreHandlers[i] && coreHandlers[i] == bot)
{
isCoreHandler = true;
myIndex = i;
}
}
if (!isCoreHandler)
return 1.0f; return 1.0f;
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI);
Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI);
Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI);
Player* fourthCorePasser = GetFourthTaintedCorePasser(group, botAI);
auto hasCore = [](Player* player) auto hasCore = [](Player* player)
{ {
return player && player->HasItemCount(ITEM_TAINTED_CORE, 1, false); return player && player->HasItemCount(ITEM_TAINTED_CORE, 1, false);
}; };
// If the bot actually has the core, only allow core handling if (hasCore(bot))
if (hasCore(bot) && !dynamic_cast<LadyVashjPassTheTaintedCoreAction*>(action)) {
if (!dynamic_cast<LadyVashjPassTheTaintedCoreAction*>(action))
return 0.0f; return 0.0f;
}
// First and second passers block movement when the looter teleports to the elemental if (bot == designatedLooter)
Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental"); {
if (tainted && coreHandlers[0]->GetExactDist2d(tainted) < 5.0f && if (!hasCore(bot))
(bot == coreHandlers[1] || bot == coreHandlers[2]) && return 1.0f;
(dynamic_cast<MovementAction*>(action) && }
!dynamic_cast<LadyVashjPassTheTaintedCoreAction*>(action))) else if (bot == firstCorePasser)
return 0.0f; {
if (hasCore(secondCorePasser) || hasCore(thirdCorePasser) ||
hasCore(fourthCorePasser))
return 1.0f;
}
else if (bot == secondCorePasser)
{
if (hasCore(thirdCorePasser) || hasCore(fourthCorePasser))
return 1.0f;
}
else if (bot == thirdCorePasser)
{
if (hasCore(fourthCorePasser))
return 1.0f;
}
else if (bot != fourthCorePasser)
return 1.0f;
// If any prior handler (including self) recently had the core, block other movement if (AI_VALUE2(Unit*, "find target", "tainted elemental") &&
if (AnyRecentCoreInInventory(botAI, bot) && (bot == firstCorePasser || bot == secondCorePasser))
dynamic_cast<MovementAction*>(action) && {
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<LadyVashjPassTheTaintedCoreAction*>(action)) !dynamic_cast<LadyVashjPassTheTaintedCoreAction*>(action))
return 0.0f; return 0.0f;
}
if (AnyRecentCoreInInventory(group, botAI))
{
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<LadyVashjPassTheTaintedCoreAction*>(action))
return 0.0f;
}
return 1.0f; return 1.0f;
} }
@ -732,8 +745,7 @@ float LadyVashjCorePassersPrioritizePositioningMultiplier::GetValue(Action* acti
// So the standard target selection system must be disabled // So the standard target selection system must be disabled
float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *action) float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *action)
{ {
Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj"); if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
if (!vashj)
return 1.0f; return 1.0f;
if (dynamic_cast<AvoidAoeAction*>(action)) if (dynamic_cast<AvoidAoeAction*>(action))
@ -743,26 +755,24 @@ float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *ac
{ {
if (dynamic_cast<DpsAssistAction*>(action) || if (dynamic_cast<DpsAssistAction*>(action) ||
dynamic_cast<TankAssistAction*>(action) || dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<FollowAction*>(action) ||
dynamic_cast<FleeAction*>(action)) dynamic_cast<FleeAction*>(action))
return 0.0f; return 0.0f;
if (bot->GetExactDist2d(vashj) < 60.0f &&
dynamic_cast<FollowAction*>(action))
return 0.0f;
if (!botAI->IsHeal(bot) && dynamic_cast<CastHealingSpellAction*>(action)) if (!botAI->IsHeal(bot) && dynamic_cast<CastHealingSpellAction*>(action))
return 0.0f; return 0.0f;
Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental"); Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental");
if (enchanted && bot->GetVictim() == enchanted && if (enchanted && bot->GetVictim() == enchanted)
dynamic_cast<CastDebuffSpellOnAttackerAction*>(action)) {
if (dynamic_cast<CastDebuffSpellOnAttackerAction*>(action))
return 0.0f; return 0.0f;
} }
}
if (IsLadyVashjInPhase3(botAI)) if (IsLadyVashjInPhase3(botAI))
{ {
if (dynamic_cast<DpsAssistAction*>(action) || if (dynamic_cast<DpsAssistAction*>(action))
dynamic_cast<TankAssistAction*>(action))
return 0.0f; return 0.0f;
Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental"); Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental");
@ -770,14 +780,17 @@ float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *ac
Unit* elite = AI_VALUE2(Unit*, "find target", "coilfang elite"); Unit* elite = AI_VALUE2(Unit*, "find target", "coilfang elite");
if (enchanted || strider || elite) if (enchanted || strider || elite)
{ {
if (dynamic_cast<FollowAction*>(action) || if (dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<FollowAction*>(action) ||
dynamic_cast<FleeAction*>(action)) dynamic_cast<FleeAction*>(action))
return 0.0f; return 0.0f;
if (enchanted && bot->GetVictim() == enchanted && if (enchanted && bot->GetVictim() == enchanted)
dynamic_cast<CastDebuffSpellOnAttackerAction*>(action)) {
if (dynamic_cast<CastDebuffSpellOnAttackerAction*>(action))
return 0.0f; return 0.0f;
} }
}
else if (dynamic_cast<CombatFormationMoveAction*>(action)) else if (dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f; return 0.0f;
} }

View File

@ -1,8 +1,3 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_RAIDSSCMULTIPLIERS_H #ifndef _PLAYERBOT_RAIDSSCMULTIPLIERS_H
#define _PLAYERBOT_RAIDSSCMULTIPLIERS_H #define _PLAYERBOT_RAIDSSCMULTIPLIERS_H
@ -198,14 +193,6 @@ public:
virtual float GetValue(Action* action); virtual float GetValue(Action* action);
}; };
class LadyVashjMainTankGroupShamanUseGroundingTotemMultiplier : public Multiplier
{
public:
LadyVashjMainTankGroupShamanUseGroundingTotemMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj main tank group shaman use grounding totem") {}
virtual float GetValue(Action* action);
};
class LadyVashjMaintainPhase1RangedSpreadMultiplier : public Multiplier class LadyVashjMaintainPhase1RangedSpreadMultiplier : public Multiplier
{ {
public: public:

View File

@ -1,8 +1,3 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_RAIDSSCACTIONCONTEXT_H #ifndef _PLAYERBOT_RAIDSSCACTIONCONTEXT_H
#define _PLAYERBOT_RAIDSSCACTIONCONTEXT_H #define _PLAYERBOT_RAIDSSCACTIONCONTEXT_H
@ -166,6 +161,9 @@ public:
creators["lady vashj destroy tainted core"] = creators["lady vashj destroy tainted core"] =
&RaidSSCActionContext::lady_vashj_destroy_tainted_core; &RaidSSCActionContext::lady_vashj_destroy_tainted_core;
creators["lady vashj erase core passing trackers"] =
&RaidSSCActionContext::lady_vashj_erase_core_passing_trackers;
creators["lady vashj avoid toxic spores"] = creators["lady vashj avoid toxic spores"] =
&RaidSSCActionContext::lady_vashj_avoid_toxic_spores; &RaidSSCActionContext::lady_vashj_avoid_toxic_spores;
@ -326,6 +324,9 @@ private:
static Action* lady_vashj_destroy_tainted_core( static Action* lady_vashj_destroy_tainted_core(
PlayerbotAI* botAI) { return new LadyVashjDestroyTaintedCoreAction(botAI); } PlayerbotAI* botAI) { return new LadyVashjDestroyTaintedCoreAction(botAI); }
static Action* lady_vashj_erase_core_passing_trackers(
PlayerbotAI* botAI) { return new LadyVashjEraseCorePassingTrackersAction(botAI); }
static Action* lady_vashj_avoid_toxic_spores( static Action* lady_vashj_avoid_toxic_spores(
PlayerbotAI* botAI) { return new LadyVashjAvoidToxicSporesAction(botAI); } PlayerbotAI* botAI) { return new LadyVashjAvoidToxicSporesAction(botAI); }

View File

@ -1,8 +1,3 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_RAIDSSCTRIGGERCONTEXT_H #ifndef _PLAYERBOT_RAIDSSCTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDSSCTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDSSCTRIGGERCONTEXT_H
@ -160,6 +155,9 @@ public:
creators["lady vashj tainted core is unusable"] = creators["lady vashj tainted core is unusable"] =
&RaidSSCTriggerContext::lady_vashj_tainted_core_is_unusable; &RaidSSCTriggerContext::lady_vashj_tainted_core_is_unusable;
creators["lady vashj need to reset core passing trackers"] =
&RaidSSCTriggerContext::lady_vashj_need_to_reset_core_passing_trackers;
creators["lady vashj toxic sporebats are spewing poison clouds"] = creators["lady vashj toxic sporebats are spewing poison clouds"] =
&RaidSSCTriggerContext::lady_vashj_toxic_sporebats_are_spewing_poison_clouds; &RaidSSCTriggerContext::lady_vashj_toxic_sporebats_are_spewing_poison_clouds;
@ -314,6 +312,9 @@ private:
static Trigger* lady_vashj_tainted_core_is_unusable( static Trigger* lady_vashj_tainted_core_is_unusable(
PlayerbotAI* botAI) { return new LadyVashjTaintedCoreIsUnusableTrigger(botAI); } PlayerbotAI* botAI) { return new LadyVashjTaintedCoreIsUnusableTrigger(botAI); }
static Trigger* lady_vashj_need_to_reset_core_passing_trackers(
PlayerbotAI* botAI) { return new LadyVashjNeedToResetCorePassingTrackersTrigger(botAI); }
static Trigger* lady_vashj_toxic_sporebats_are_spewing_poison_clouds( static Trigger* lady_vashj_toxic_sporebats_are_spewing_poison_clouds(
PlayerbotAI* botAI) { return new LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger(botAI); } PlayerbotAI* botAI) { return new LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger(botAI); }

View File

@ -1,8 +1,3 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "RaidSSCStrategy.h" #include "RaidSSCStrategy.h"
#include "RaidSSCMultipliers.h" #include "RaidSSCMultipliers.h"
@ -149,6 +144,9 @@ void RaidSSCStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode("lady vashj tainted core is unusable", { triggers.push_back(new TriggerNode("lady vashj tainted core is unusable", {
NextAction("lady vashj destroy tainted core", ACTION_EMERGENCY + 1) })); NextAction("lady vashj destroy tainted core", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode("lady vashj need to reset core passing trackers", {
NextAction("lady vashj erase core passing trackers", ACTION_EMERGENCY + 10) }));
triggers.push_back(new TriggerNode("lady vashj adds spawn in phase 2 and phase 3", { triggers.push_back(new TriggerNode("lady vashj adds spawn in phase 2 and phase 3", {
NextAction("lady vashj assign phase 2 and phase 3 dps priority", ACTION_RAID + 1) })); NextAction("lady vashj assign phase 2 and phase 3 dps priority", ACTION_RAID + 1) }));
@ -200,7 +198,6 @@ void RaidSSCStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
// Lady Vashj <Coilfang Matron> // Lady Vashj <Coilfang Matron>
multipliers.push_back(new LadyVashjDelayCooldownsMultiplier(botAI)); multipliers.push_back(new LadyVashjDelayCooldownsMultiplier(botAI));
multipliers.push_back(new LadyVashjMainTankGroupShamanUseGroundingTotemMultiplier(botAI));
multipliers.push_back(new LadyVashjMaintainPhase1RangedSpreadMultiplier(botAI)); multipliers.push_back(new LadyVashjMaintainPhase1RangedSpreadMultiplier(botAI));
multipliers.push_back(new LadyVashjStaticChargeStayAwayFromGroupMultiplier(botAI)); multipliers.push_back(new LadyVashjStaticChargeStayAwayFromGroupMultiplier(botAI));
multipliers.push_back(new LadyVashjDoNotLootTheTaintedCoreMultiplier(botAI)); multipliers.push_back(new LadyVashjDoNotLootTheTaintedCoreMultiplier(botAI));

View File

@ -1,8 +1,3 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_RAIDSSCSTRATEGY_H_ #ifndef _PLAYERBOT_RAIDSSCSTRATEGY_H_
#define _PLAYERBOT_RAIDSSCSTRATEGY_H_ #define _PLAYERBOT_RAIDSSCSTRATEGY_H_

View File

@ -1,8 +1,3 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "RaidSSCTriggers.h" #include "RaidSSCTriggers.h"
#include "RaidSSCHelpers.h" #include "RaidSSCHelpers.h"
#include "RaidSSCActions.h" #include "RaidSSCActions.h"
@ -31,37 +26,35 @@ bool UnderbogColossusSpawnedToxicPoolAfterDeathTrigger::IsActive()
bool GreyheartTidecallerWaterElementalTotemSpawnedTrigger::IsActive() bool GreyheartTidecallerWaterElementalTotemSpawnedTrigger::IsActive()
{ {
return botAI->IsDps(bot) && return botAI->IsDps(bot) &&
AI_VALUE2(Unit*, "find target", "greyheart tidecaller"); GetFirstAliveUnitByEntry(botAI, NPC_WATER_ELEMENTAL_TOTEM);
} }
// Hydross the Unstable <Duke of Currents> // Hydross the Unstable <Duke of Currents>
bool HydrossTheUnstableBotIsFrostTankTrigger::IsActive() bool HydrossTheUnstableBotIsFrostTankTrigger::IsActive()
{ {
return botAI->IsMainTank(bot) && return AI_VALUE2(Unit*, "find target", "hydross the unstable") &&
AI_VALUE2(Unit*, "find target", "hydross the unstable"); botAI->IsMainTank(bot);
} }
bool HydrossTheUnstableBotIsNatureTankTrigger::IsActive() bool HydrossTheUnstableBotIsNatureTankTrigger::IsActive()
{ {
return botAI->IsAssistTankOfIndex(bot, 0, true) && return AI_VALUE2(Unit*, "find target", "hydross the unstable") &&
AI_VALUE2(Unit*, "find target", "hydross the unstable"); botAI->IsAssistTankOfIndex(bot, 0, true);
} }
bool HydrossTheUnstableElementalsSpawnedTrigger::IsActive() bool HydrossTheUnstableElementalsSpawnedTrigger::IsActive()
{ {
if (botAI->IsHeal(bot))
return false;
Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable"); Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable");
if (!hydross || hydross->GetHealthPct() < 10.0f) if (hydross && hydross->GetHealthPct() < 10.0f)
return false; return false;
if (botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0, true)) if (!AI_VALUE2(Unit*, "find target", "pure spawn of hydross") &&
!AI_VALUE2(Unit*, "find target", "tainted spawn of hydross"))
return false; return false;
return AI_VALUE2(Unit*, "find target", "pure spawn of hydross") || return !botAI->IsHeal(bot) && !botAI->IsMainTank(bot) &&
AI_VALUE2(Unit*, "find target", "tainted spawn of hydross"); !botAI->IsAssistTankOfIndex(bot, 0, true);
} }
bool HydrossTheUnstableDangerFromWaterTombsTrigger::IsActive() bool HydrossTheUnstableDangerFromWaterTombsTrigger::IsActive()
@ -78,19 +71,19 @@ bool HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger::IsActive()
bool HydrossTheUnstableAggroResetsUponPhaseChangeTrigger::IsActive() bool HydrossTheUnstableAggroResetsUponPhaseChangeTrigger::IsActive()
{ {
if (bot->getClass() == CLASS_HUNTER || if (!AI_VALUE2(Unit*, "find target", "hydross the unstable"))
botAI->IsHeal(bot) ||
botAI->IsMainTank(bot) ||
botAI->IsAssistTankOfIndex(bot, 0, true))
return false; return false;
return AI_VALUE2(Unit*, "find target", "hydross the unstable"); return bot->getClass() != CLASS_HUNTER &&
!botAI->IsHeal(bot) &&
!botAI->IsMainTank(bot) &&
!botAI->IsAssistTankOfIndex(bot, 0, true);
} }
bool HydrossTheUnstableNeedToManageTimersTrigger::IsActive() bool HydrossTheUnstableNeedToManageTimersTrigger::IsActive()
{ {
return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) && return AI_VALUE2(Unit*, "find target", "hydross the unstable") &&
AI_VALUE2(Unit*, "find target", "hydross the unstable"); IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr);
} }
// The Lurker Below // The Lurker Below
@ -109,13 +102,13 @@ bool TheLurkerBelowSpoutIsActiveTrigger::IsActive()
bool TheLurkerBelowBossIsActiveForMainTankTrigger::IsActive() bool TheLurkerBelowBossIsActiveForMainTankTrigger::IsActive()
{ {
if (!botAI->IsMainTank(bot))
return false;
Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below");
if (!lurker) if (!lurker)
return false; return false;
if (!botAI->IsMainTank(bot))
return false;
const time_t now = std::time(nullptr); const time_t now = std::time(nullptr);
auto it = lurkerSpoutTimer.find(lurker->GetMap()->GetInstanceId()); auto it = lurkerSpoutTimer.find(lurker->GetMap()->GetInstanceId());
@ -142,16 +135,35 @@ bool TheLurkerBelowBossCastsGeyserTrigger::IsActive()
// Trigger will be active only if there are at least 3 tanks in the raid // Trigger will be active only if there are at least 3 tanks in the raid
bool TheLurkerBelowBossIsSubmergedTrigger::IsActive() bool TheLurkerBelowBossIsSubmergedTrigger::IsActive()
{ {
if (!botAI->IsTank(bot))
return false;
Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below");
if (!lurker || lurker->getStandState() != UNIT_STAND_STATE_SUBMERGED) if (!lurker || lurker->getStandState() != UNIT_STAND_STATE_SUBMERGED)
return false; return false;
Player* mainTank = GetGroupMainTank(botAI, bot); Player* mainTank = nullptr;
Player* firstAssistTank = GetGroupAssistTank(botAI, bot, 0); Player* firstAssistTank = nullptr;
Player* secondAssistTank = GetGroupAssistTank(botAI, bot, 1); Player* secondAssistTank = nullptr;
Group* group = bot->GetGroup();
if (!group)
return false;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive())
continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (!memberAI)
continue;
if (!mainTank && memberAI->IsMainTank(member))
mainTank = member;
else if (!firstAssistTank && memberAI->IsAssistTankOfIndex(member, 0, true))
firstAssistTank = member;
else if (!secondAssistTank && memberAI->IsAssistTankOfIndex(member, 1, true))
secondAssistTank = member;
}
if (!mainTank || !firstAssistTank || !secondAssistTank) if (!mainTank || !firstAssistTank || !secondAssistTank)
return false; return false;
@ -161,55 +173,51 @@ bool TheLurkerBelowBossIsSubmergedTrigger::IsActive()
bool TheLurkerBelowNeedToPrepareTimerForSpoutTrigger::IsActive() bool TheLurkerBelowNeedToPrepareTimerForSpoutTrigger::IsActive()
{ {
return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) && return AI_VALUE2(Unit*, "find target", "the lurker below") &&
AI_VALUE2(Unit*, "find target", "the lurker below"); IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr);
} }
// Leotheras the Blind // Leotheras the Blind
bool LeotherasTheBlindBossIsInactiveTrigger::IsActive() bool LeotherasTheBlindBossIsInactiveTrigger::IsActive()
{ {
return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) && return AI_VALUE2(Unit*, "find target", "greyheart spellbinder");
AI_VALUE2(Unit*, "find target", "greyheart spellbinder");
} }
bool LeotherasTheBlindBossTransformedIntoDemonFormTrigger::IsActive() bool LeotherasTheBlindBossTransformedIntoDemonFormTrigger::IsActive()
{ {
if (bot->getClass() != CLASS_WARLOCK)
return false;
if (!AI_VALUE2(Unit*, "find target", "leotheras the blind")) if (!AI_VALUE2(Unit*, "find target", "leotheras the blind"))
return false; return false;
if (GetLeotherasDemonFormTank(bot) != bot) if (GetLeotherasDemonFormTank(bot) != bot)
return false; return false;
return GetActiveLeotherasDemon(bot); return GetActiveLeotherasDemon(botAI);
} }
bool LeotherasTheBlindOnlyWarlockShouldTankDemonFormTrigger::IsActive() bool LeotherasTheBlindOnlyWarlockShouldTankDemonFormTrigger::IsActive()
{ {
if (!botAI->IsTank(bot)) if (botAI->IsRanged(bot) || !botAI->IsTank(bot))
return false;
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false; return false;
if (!AI_VALUE2(Unit*, "find target", "leotheras the blind")) if (!AI_VALUE2(Unit*, "find target", "leotheras the blind"))
return false; return false;
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
if (!GetLeotherasDemonFormTank(bot)) if (!GetLeotherasDemonFormTank(bot))
return false; return false;
return GetPhase2LeotherasDemon(bot); return GetPhase2LeotherasDemon(botAI);
} }
bool LeotherasTheBlindBossEngagedByRangedTrigger::IsActive() bool LeotherasTheBlindBossEngagedByRangedTrigger::IsActive()
{ {
if (!botAI->IsRanged(bot)) if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false; return false;
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) if (!botAI->IsRanged(bot))
return false; return false;
Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind");
@ -223,14 +231,14 @@ bool LeotherasTheBlindBossEngagedByRangedTrigger::IsActive()
bool LeotherasTheBlindBossChannelingWhirlwindTrigger::IsActive() bool LeotherasTheBlindBossChannelingWhirlwindTrigger::IsActive()
{ {
if (botAI->IsTank(bot)) if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
if (botAI->IsTank(bot) && botAI->IsMelee(bot))
return false; return false;
Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind");
if (!leotheras) if (!leotheras || leotheras->HasAura(SPELL_LEOTHERAS_BANISHED))
return false;
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false; return false;
return leotheras->HasAura(SPELL_WHIRLWIND) || return leotheras->HasAura(SPELL_WHIRLWIND) ||
@ -239,15 +247,12 @@ bool LeotherasTheBlindBossChannelingWhirlwindTrigger::IsActive()
bool LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger::IsActive() bool LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger::IsActive()
{ {
if (botAI->IsRanged(bot))
return false;
if (!AI_VALUE2(Unit*, "find target", "leotheras the blind"))
return false;
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false; return false;
if (botAI->IsRanged(bot))
return false;
Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST); Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST);
if (!chaosBlast || chaosBlast->GetStackAmount() < 5) if (!chaosBlast || chaosBlast->GetStackAmount() < 5)
return false; return false;
@ -255,7 +260,7 @@ bool LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger::IsActive()
if (!GetLeotherasDemonFormTank(bot) && botAI->IsMainTank(bot)) if (!GetLeotherasDemonFormTank(bot) && botAI->IsMainTank(bot))
return false; return false;
return GetPhase2LeotherasDemon(bot); return GetPhase2LeotherasDemon(botAI);
} }
bool LeotherasTheBlindInnerDemonHasAwakenedTrigger::IsActive() bool LeotherasTheBlindInnerDemonHasAwakenedTrigger::IsActive()
@ -266,68 +271,89 @@ bool LeotherasTheBlindInnerDemonHasAwakenedTrigger::IsActive()
bool LeotherasTheBlindEnteredFinalPhaseTrigger::IsActive() bool LeotherasTheBlindEnteredFinalPhaseTrigger::IsActive()
{ {
if (botAI->IsHeal(bot))
return false;
if (!AI_VALUE2(Unit*, "find target", "leotheras the blind"))
return false;
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false; return false;
if (bot->getClass() == CLASS_WARLOCK && GetLeotherasDemonFormTank(bot) == bot) if (botAI->IsHeal(bot))
return false; return false;
return GetPhase3LeotherasDemon(bot); if (GetLeotherasDemonFormTank(bot) == bot)
return false;
return GetPhase3LeotherasDemon(botAI) &&
GetLeotherasHuman(botAI);
} }
bool LeotherasTheBlindDemonFormTankNeedsAggro::IsActive() bool LeotherasTheBlindDemonFormTankNeedsAggro::IsActive()
{ {
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
if (bot->getClass() != CLASS_HUNTER) if (bot->getClass() != CLASS_HUNTER)
return false; return false;
if (!AI_VALUE2(Unit*, "find target", "leotheras the blind")) return AI_VALUE2(Unit*, "find target", "leotheras the blind");
return false;
return !bot->HasAura(SPELL_INSIDIOUS_WHISPER);
} }
bool LeotherasTheBlindBossWipesAggroUponPhaseChangeTrigger::IsActive() bool LeotherasTheBlindBossWipesAggroUponPhaseChangeTrigger::IsActive()
{ {
return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) && return AI_VALUE2(Unit*, "find target", "leotheras the blind") &&
AI_VALUE2(Unit*, "find target", "leotheras the blind"); IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr);
} }
// Fathom-Lord Karathress // Fathom-Lord Karathress
bool FathomLordKarathressBossEngagedByMainTankTrigger::IsActive() bool FathomLordKarathressBossEngagedByMainTankTrigger::IsActive()
{ {
return botAI->IsMainTank(bot) && return AI_VALUE2(Unit*, "find target", "fathom-lord karathress") &&
AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); botAI->IsMainTank(bot);
} }
bool FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger::IsActive() bool FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger::IsActive()
{ {
return botAI->IsAssistTankOfIndex(bot, 0, false) && return AI_VALUE2(Unit*, "find target", "fathom-guard caribdis") &&
AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); botAI->IsAssistTankOfIndex(bot, 0, false);
} }
bool FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger::IsActive() bool FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger::IsActive()
{ {
return botAI->IsAssistTankOfIndex(bot, 1, false) && return AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis") &&
AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis"); botAI->IsAssistTankOfIndex(bot, 1, false);
} }
bool FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger::IsActive() bool FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger::IsActive()
{ {
return botAI->IsAssistTankOfIndex(bot, 2, false) && return AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess") &&
AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess"); botAI->IsAssistTankOfIndex(bot, 2, false);
} }
bool FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger::IsActive() bool FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger::IsActive()
{ {
return botAI->IsAssistHealOfIndex(bot, 0, true) && Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis");
AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); if (!caribdis)
return false;
if (!botAI->IsAssistHealOfIndex(bot, 0, true))
return false;
Player* firstAssistTank = nullptr;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive())
continue;
if (botAI->IsAssistTankOfIndex(member, 0, false))
{
firstAssistTank = member;
break;
}
}
}
return firstAssistTank;
} }
bool FathomLordKarathressPullingBossesTrigger::IsActive() bool FathomLordKarathressPullingBossesTrigger::IsActive()
@ -341,10 +367,10 @@ bool FathomLordKarathressPullingBossesTrigger::IsActive()
bool FathomLordKarathressDeterminingKillOrderTrigger::IsActive() bool FathomLordKarathressDeterminingKillOrderTrigger::IsActive()
{ {
if (botAI->IsHeal(bot)) if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress"))
return false; return false;
if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) if (botAI->IsHeal(bot))
return false; return false;
if (botAI->IsDps(bot)) if (botAI->IsDps(bot))
@ -361,8 +387,8 @@ bool FathomLordKarathressDeterminingKillOrderTrigger::IsActive()
bool FathomLordKarathressTanksNeedToEstablishAggroTrigger::IsActive() bool FathomLordKarathressTanksNeedToEstablishAggroTrigger::IsActive()
{ {
return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) && return AI_VALUE2(Unit*, "find target", "fathom-lord karathress") &&
AI_VALUE2(Unit*, "find target", "fathom-lord karathress"); IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr);
} }
// Morogrim Tidewalker // Morogrim Tidewalker
@ -378,8 +404,8 @@ bool MorogrimTidewalkerPullingBossTrigger::IsActive()
bool MorogrimTidewalkerBossEngagedByMainTankTrigger::IsActive() bool MorogrimTidewalkerBossEngagedByMainTankTrigger::IsActive()
{ {
return botAI->IsMainTank(bot) && return AI_VALUE2(Unit*, "find target", "morogrim tidewalker") &&
AI_VALUE2(Unit*, "find target", "morogrim tidewalker"); botAI->IsMainTank(bot);
} }
bool MorogrimTidewalkerWaterGlobulesAreIncomingTrigger::IsActive() bool MorogrimTidewalkerWaterGlobulesAreIncomingTrigger::IsActive()
@ -395,11 +421,8 @@ bool MorogrimTidewalkerWaterGlobulesAreIncomingTrigger::IsActive()
bool LadyVashjBossEngagedByMainTankTrigger::IsActive() bool LadyVashjBossEngagedByMainTankTrigger::IsActive()
{ {
if (!botAI->IsMainTank(bot))
return false;
return AI_VALUE2(Unit*, "find target", "lady vashj") && return AI_VALUE2(Unit*, "find target", "lady vashj") &&
!IsLadyVashjInPhase2(botAI); !IsLadyVashjInPhase2(botAI) && botAI->IsMainTank(bot);
} }
bool LadyVashjBossEngagedByRangedInPhase1Trigger::IsActive() bool LadyVashjBossEngagedByRangedInPhase1Trigger::IsActive()
@ -416,7 +439,10 @@ bool LadyVashjCastsShockBlastOnHighestAggroTrigger::IsActive()
IsLadyVashjInPhase2(botAI)) IsLadyVashjInPhase2(botAI))
return false; return false;
return IsMainTankInSameSubgroup(botAI, bot); if (!IsMainTankInSameSubgroup(bot))
return false;
return true;
} }
bool LadyVashjBotHasStaticChargeTrigger::IsActive() bool LadyVashjBotHasStaticChargeTrigger::IsActive()
@ -424,16 +450,15 @@ bool LadyVashjBotHasStaticChargeTrigger::IsActive()
if (!AI_VALUE2(Unit*, "find target", "lady vashj")) if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
return false; return false;
Group* group = bot->GetGroup(); if (Group* group = bot->GetGroup())
if (!group) {
return false;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
Player* member = ref->GetSource(); Player* member = ref->GetSource();
if (member && member->HasAura(SPELL_STATIC_CHARGE)) if (member && member->HasAura(SPELL_STATIC_CHARGE))
return true; return true;
} }
}
return false; return false;
} }
@ -475,10 +500,9 @@ bool LadyVashjTaintedElementalCheatTrigger::IsActive()
return false; return false;
bool taintedPresent = false; bool taintedPresent = false;
if (AI_VALUE2(Unit*, "find target", "tainted elemental")) Unit* taintedUnit = AI_VALUE2(Unit*, "find target", "tainted elemental");
{ if (taintedUnit)
taintedPresent = true; taintedPresent = true;
}
else else
{ {
GuidVector corpses = AI_VALUE(GuidVector, "nearest corpses"); GuidVector corpses = AI_VALUE(GuidVector, "nearest corpses");
@ -489,20 +513,26 @@ bool LadyVashjTaintedElementalCheatTrigger::IsActive()
if (!object) if (!object)
continue; continue;
if (Creature* creature = object->ToCreature(); if (Creature* creature = object->ToCreature())
creature->GetEntry() == NPC_TAINTED_ELEMENTAL && !creature->IsAlive()) {
if (creature->GetEntry() == NPC_TAINTED_ELEMENTAL && !creature->IsAlive())
{ {
taintedPresent = true; taintedPresent = true;
break; break;
} }
} }
} }
}
if (!taintedPresent) if (!taintedPresent)
return false; return false;
return GetDesignatedCoreLooter(botAI, bot) == bot && Group* group = bot->GetGroup();
!bot->HasItemCount(ITEM_TAINTED_CORE, 1, false); if (!group)
return false;
return (GetDesignatedCoreLooter(group, botAI) == bot &&
!bot->HasItemCount(ITEM_TAINTED_CORE, 1, false));
} }
bool LadyVashjTaintedCoreWasLootedTrigger::IsActive() bool LadyVashjTaintedCoreWasLootedTrigger::IsActive()
@ -510,24 +540,54 @@ bool LadyVashjTaintedCoreWasLootedTrigger::IsActive()
if (!AI_VALUE2(Unit*, "find target", "lady vashj") || !IsLadyVashjInPhase2(botAI)) if (!AI_VALUE2(Unit*, "find target", "lady vashj") || !IsLadyVashjInPhase2(botAI))
return false; return false;
auto coreHandlers = GetCoreHandlers(botAI, bot); Group* group = bot->GetGroup();
if (!group)
bool isCoreHandler = false;
for (Player* handler : coreHandlers)
if (handler == bot)
isCoreHandler = true;
if (!isCoreHandler)
return false; return false;
// First and second passers move to positions as soon as the elemental appears Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental"); Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI);
if (tainted && coreHandlers[0]->GetExactDist2d(tainted) < 5.0f && Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI);
(bot == coreHandlers[1] || bot == coreHandlers[2])) Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI);
Player* fourthCorePasser = GetFourthTaintedCorePasser(group, botAI);
auto hasCore = [](Player* player) -> bool
{
return player && player->HasItemCount(ITEM_TAINTED_CORE, 1, false);
};
if (bot == designatedLooter)
{
if (!hasCore(bot))
return false;
}
else if (bot == firstCorePasser)
{
if (hasCore(secondCorePasser) || hasCore(thirdCorePasser) ||
hasCore(fourthCorePasser))
return false;
}
else if (bot == secondCorePasser)
{
if (hasCore(thirdCorePasser) || hasCore(fourthCorePasser))
return false;
}
else if (bot == thirdCorePasser)
{
if (hasCore(fourthCorePasser))
return false;
}
else if (bot != fourthCorePasser)
return false;
if (AnyRecentCoreInInventory(group, botAI))
return true; return true;
// Main logic: run if core is in play for this bot or a prior handler // First and second passers move to positions as soon as the elemental appears
return AnyRecentCoreInInventory(botAI, bot); if (AI_VALUE2(Unit*, "find target", "tainted elemental") &&
(bot == firstCorePasser || bot == secondCorePasser))
return true;
return false;
} }
bool LadyVashjTaintedCoreIsUnusableTrigger::IsActive() bool LadyVashjTaintedCoreIsUnusableTrigger::IsActive()
@ -539,7 +599,18 @@ bool LadyVashjTaintedCoreIsUnusableTrigger::IsActive()
if (!IsLadyVashjInPhase2(botAI)) if (!IsLadyVashjInPhase2(botAI))
return bot->HasItemCount(ITEM_TAINTED_CORE, 1, false); return bot->HasItemCount(ITEM_TAINTED_CORE, 1, false);
auto coreHandlers = GetCoreHandlers(botAI, bot); Group* group = bot->GetGroup();
if (!group)
return false;
Player* coreHandlers[] =
{
GetDesignatedCoreLooter(group, botAI),
GetFirstTaintedCorePasser(group, botAI),
GetSecondTaintedCorePasser(group, botAI),
GetThirdTaintedCorePasser(group, botAI),
GetFourthTaintedCorePasser(group, botAI)
};
if (bot->HasItemCount(ITEM_TAINTED_CORE, 1, false)) if (bot->HasItemCount(ITEM_TAINTED_CORE, 1, false))
{ {
@ -554,6 +625,24 @@ bool LadyVashjTaintedCoreIsUnusableTrigger::IsActive()
return false; return false;
} }
bool LadyVashjNeedToResetCorePassingTrackersTrigger::IsActive()
{
Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj");
if (!vashj || IsLadyVashjInPhase2(botAI))
return false;
Group* group = bot->GetGroup();
if (!group)
return false;
return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr) ||
GetDesignatedCoreLooter(group, botAI) == bot ||
GetFirstTaintedCorePasser(group, botAI) == bot ||
GetSecondTaintedCorePasser(group, botAI) == bot ||
GetThirdTaintedCorePasser(group, botAI) == bot ||
GetFourthTaintedCorePasser(group, botAI) == bot;
}
bool LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger::IsActive() bool LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger::IsActive()
{ {
return IsLadyVashjInPhase3(botAI); return IsLadyVashjInPhase3(botAI);
@ -564,10 +653,8 @@ bool LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger::IsActive()
if (!AI_VALUE2(Unit*, "find target", "lady vashj")) if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
return false; return false;
Group* group = bot->GetGroup(); if (Group* group = bot->GetGroup())
if (!group) {
return false;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
Player* member = ref->GetSource(); Player* member = ref->GetSource();
@ -577,6 +664,7 @@ bool LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger::IsActive()
if (botAI->IsMelee(member)) if (botAI->IsMelee(member))
return true; return true;
} }
}
return false; return false;
} }

View File

@ -1,8 +1,3 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_RAIDSSCTRIGGERS_H #ifndef _PLAYERBOT_RAIDSSCTRIGGERS_H
#define _PLAYERBOT_RAIDSSCTRIGGERS_H #define _PLAYERBOT_RAIDSSCTRIGGERS_H
@ -392,6 +387,14 @@ public:
bool IsActive() override; bool IsActive() override;
}; };
class LadyVashjNeedToResetCorePassingTrackersTrigger : public Trigger
{
public:
LadyVashjNeedToResetCorePassingTrackersTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "lady vashj need to reset core passing trackers") {}
bool IsActive() override;
};
class LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger : public Trigger class LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger : public Trigger
{ {
public: public:

View File

@ -1,8 +1,3 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "RaidSSCHelpers.h" #include "RaidSSCHelpers.h"
#include "AiFactory.h" #include "AiFactory.h"
#include "Creature.h" #include "Creature.h"
@ -84,54 +79,61 @@ namespace SerpentShrineCavernHelpers
std::unordered_map<uint32, time_t> leotherasDemonFormDpsWaitTimer; std::unordered_map<uint32, time_t> leotherasDemonFormDpsWaitTimer;
std::unordered_map<uint32, time_t> leotherasFinalPhaseDpsWaitTimer; std::unordered_map<uint32, time_t> leotherasFinalPhaseDpsWaitTimer;
Unit* GetLeotherasHuman(Player* bot) Unit* GetLeotherasHuman(PlayerbotAI* botAI)
{ {
constexpr float searchRadius = 100.0f; auto const& npcs =
Creature* leotheras = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest hostile npcs")->Get();
bot->FindNearestCreature(NPC_LEOTHERAS_THE_BLIND, searchRadius, true); for (auto const& guid : npcs)
{
if (leotheras && leotheras->IsInCombat() && Unit* unit = botAI->GetUnit(guid);
!leotheras->HasAura(SPELL_METAMORPHOSIS)) if (unit && unit->GetEntry() == NPC_LEOTHERAS_THE_BLIND &&
return leotheras; unit->IsInCombat() && !unit->HasAura(SPELL_METAMORPHOSIS))
return unit;
}
return nullptr; return nullptr;
} }
Unit* GetPhase2LeotherasDemon(Player* bot) Unit* GetPhase2LeotherasDemon(PlayerbotAI* botAI)
{ {
constexpr float searchRadius = 100.0f; auto const& npcs =
Creature* leotheras = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest hostile npcs")->Get();
bot->FindNearestCreature(NPC_LEOTHERAS_THE_BLIND, searchRadius, true); for (auto const& guid : npcs)
{
if (leotheras && leotheras->HasAura(SPELL_METAMORPHOSIS)) Unit* unit = botAI->GetUnit(guid);
return leotheras; if (unit && unit->GetEntry() == NPC_LEOTHERAS_THE_BLIND &&
unit->HasAura(SPELL_METAMORPHOSIS))
return unit;
}
return nullptr; return nullptr;
} }
Unit* GetPhase3LeotherasDemon(Player* bot) Unit* GetPhase3LeotherasDemon(PlayerbotAI* botAI)
{ {
constexpr float searchRadius = 100.0f; auto const& npcs =
return bot->FindNearestCreature(NPC_SHADOW_OF_LEOTHERAS, searchRadius, true); botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest hostile npcs")->Get();
for (auto const& guid : npcs)
{
Unit* unit = botAI->GetUnit(guid);
if (unit && unit->GetEntry() == NPC_SHADOW_OF_LEOTHERAS)
return unit;
}
return nullptr;
} }
Unit* GetActiveLeotherasDemon(Player* bot) Unit* GetActiveLeotherasDemon(PlayerbotAI* botAI)
{ {
Unit* phase2 = GetPhase2LeotherasDemon(bot); Unit* phase2 = GetPhase2LeotherasDemon(botAI);
Unit* phase3 = GetPhase3LeotherasDemon(bot); Unit* phase3 = GetPhase3LeotherasDemon(botAI);
return phase2 ? phase2 : phase3; return phase2 ? phase2 : phase3;
} }
// (1) First priority is an assistant Warlock (real player or bot)
// (2) If no assistant Warlock, then look for any Warlock bot
Player* GetLeotherasDemonFormTank(Player* bot) Player* GetLeotherasDemonFormTank(Player* bot)
{ {
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
if (!group) if (!group)
return nullptr; return nullptr;
Player* fallbackWarlock = nullptr; // (1) First loop: Return the first assistant Warlock (real player or bot)
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
Player* member = ref->GetSource(); Player* member = ref->GetSource();
@ -140,12 +142,21 @@ namespace SerpentShrineCavernHelpers
if (group->IsAssistant(member->GetGUID())) if (group->IsAssistant(member->GetGUID()))
return member; return member;
if (!fallbackWarlock && GET_PLAYERBOT_AI(member))
fallbackWarlock = member;
} }
return fallbackWarlock; // (2) Fall back to first found bot Warlock
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
member->getClass() != CLASS_WARLOCK)
continue;
return member;
}
// (3) Return nullptr if none found
return nullptr;
} }
// Fathom-Lord Karathress // Fathom-Lord Karathress
@ -171,15 +182,16 @@ namespace SerpentShrineCavernHelpers
// Lady Vashj <Coilfang Matron> // Lady Vashj <Coilfang Matron>
const Position VASHJ_PLATFORM_CENTER_POSITION = { 29.634f, -923.541f, 42.902f }; const Position VASHJ_PLATFORM_CENTER_POSITION = { 29.634f, -923.541f, 42.985f };
std::unordered_map<ObjectGuid, Position> vashjRangedPositions;
std::unordered_map<ObjectGuid, bool> hasReachedVashjRangedPosition; std::unordered_map<ObjectGuid, bool> hasReachedVashjRangedPosition;
std::unordered_map<uint32, ObjectGuid> nearestTriggerGuid; std::unordered_map<uint32, ObjectGuid> nearestTriggerGuid;
std::unordered_map<ObjectGuid, Position> intendedLineup; std::unordered_map<ObjectGuid, Position> intendedLineup;
std::unordered_map<uint32, time_t> lastImbueAttempt; std::unordered_map<uint32, time_t> lastImbueAttempt;
std::unordered_map<ObjectGuid, time_t> lastCoreInInventoryTime; std::unordered_map<uint32, time_t> lastCoreInInventoryTime;
bool IsMainTankInSameSubgroup(PlayerbotAI* botAI, Player* bot) bool IsMainTankInSameSubgroup(Player* bot)
{ {
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
if (!group || !group->isRaidGroup()) if (!group || !group->isRaidGroup())
@ -198,9 +210,12 @@ namespace SerpentShrineCavernHelpers
if (group->GetMemberGroup(member->GetGUID()) != botSubGroup) if (group->GetMemberGroup(member->GetGUID()) != botSubGroup)
continue; continue;
if (botAI->IsMainTank(member)) if (PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member))
{
if (memberAI->IsMainTank(member))
return true; return true;
} }
}
return false; return false;
} }
@ -262,9 +277,38 @@ namespace SerpentShrineCavernHelpers
return false; return false;
} }
Player* GetDesignatedCoreLooter(PlayerbotAI* botAI, Player* bot) bool AnyRecentCoreInInventory(Group* group, PlayerbotAI* botAI, uint32 graceSeconds)
{
Unit* vashj =
botAI->GetAiObjectContext()->GetValue<Unit*>("find target", "lady vashj")->Get();
if (!vashj)
return false;
if (group)
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->HasItemCount(ITEM_TAINTED_CORE, 1, false))
return true;
}
}
const uint32 instanceId = vashj->GetMap()->GetInstanceId();
const time_t now = std::time(nullptr);
auto it = lastCoreInInventoryTime.find(instanceId);
if (it != lastCoreInInventoryTime.end())
{
if ((now - it->second) <= static_cast<time_t>(graceSeconds))
return true;
}
return false;
}
Player* GetDesignatedCoreLooter(Group* group, PlayerbotAI* botAI)
{ {
Group* group = bot->GetGroup();
if (!group) if (!group)
return nullptr; return nullptr;
@ -273,15 +317,10 @@ namespace SerpentShrineCavernHelpers
if (!leaderGuid.IsEmpty()) if (!leaderGuid.IsEmpty())
leader = ObjectAccessor::FindPlayer(leaderGuid); leader = ObjectAccessor::FindPlayer(leaderGuid);
// If cheats are disabled, the group leader will be the designated looter
if (!botAI->HasCheat(BotCheatMask::raid)) if (!botAI->HasCheat(BotCheatMask::raid))
return leader; return leader;
// Priority: (1) assistant melee DPS, (2) other melee DPS, (3) any ranged DPS Player* fallback = leader;
Player* meleeDpsAssistant = nullptr;
Player* meleeDps = nullptr;
Player* rangedDps = nullptr;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
Player* member = ref->GetSource(); Player* member = ref->GetSource();
@ -292,36 +331,22 @@ namespace SerpentShrineCavernHelpers
if (!memberAI) if (!memberAI)
continue; continue;
if (!meleeDpsAssistant && memberAI->IsMelee(member) && if (memberAI->IsMelee(member) && memberAI->IsDps(member))
memberAI->IsDps(member) && group->IsAssistant(member->GetGUID())) return member;
if (!fallback && memberAI->IsRangedDps(member))
fallback = member;
}
return fallback ? fallback : leader;
}
Player* GetFirstTaintedCorePasser(Group* group, PlayerbotAI* botAI)
{ {
meleeDpsAssistant = member;
break;
}
if (!meleeDps && memberAI->IsMelee(member) && memberAI->IsDps(member))
meleeDps = member;
if (!rangedDps && memberAI->IsRangedDps(member))
rangedDps = member;
}
if (meleeDpsAssistant)
return meleeDpsAssistant;
if (meleeDps)
return meleeDps;
if (rangedDps)
return rangedDps;
return leader;
}
Player* GetFirstTaintedCorePasser(PlayerbotAI* botAI, Player* bot)
{
Group* group = bot->GetGroup();
if (!group) if (!group)
return nullptr; return nullptr;
Player* designatedLooter = GetDesignatedCoreLooter(botAI, bot); Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
@ -330,29 +355,32 @@ namespace SerpentShrineCavernHelpers
continue; continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (memberAI && memberAI->IsAssistHealOfIndex(member, 0, true)) if (!memberAI)
continue;
if (memberAI->IsAssistHealOfIndex(member, 0, true))
return member; return member;
} }
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
Player* member = ref->GetSource(); Player* member = ref->GetSource();
if (member && member->IsAlive() && GET_PLAYERBOT_AI(member) && if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
!botAI->IsTank(member) && member != designatedLooter) botAI->IsTank(member) || member == designatedLooter)
continue;
return member; return member;
} }
return nullptr; return nullptr;
} }
Player* GetSecondTaintedCorePasser(PlayerbotAI* botAI, Player* bot) Player* GetSecondTaintedCorePasser(Group* group, PlayerbotAI* botAI)
{ {
Group* group = bot->GetGroup();
if (!group) if (!group)
return nullptr; return nullptr;
Player* designatedLooter = GetDesignatedCoreLooter(botAI, bot); Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
Player* firstCorePasser = GetFirstTaintedCorePasser(botAI, bot); Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI);
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
@ -362,31 +390,34 @@ namespace SerpentShrineCavernHelpers
continue; continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (memberAI && memberAI->IsAssistHealOfIndex(member, 1, true)) if (!memberAI)
continue;
if (memberAI->IsAssistHealOfIndex(member, 1, true))
return member; return member;
} }
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
Player* member = ref->GetSource(); Player* member = ref->GetSource();
if (member && member->IsAlive() && GET_PLAYERBOT_AI(member) && if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
!botAI->IsTank(member) && member != designatedLooter && botAI->IsTank(member) || member == designatedLooter ||
member != firstCorePasser) member == firstCorePasser)
continue;
return member; return member;
} }
return nullptr; return nullptr;
} }
Player* GetThirdTaintedCorePasser(PlayerbotAI* botAI, Player* bot) Player* GetThirdTaintedCorePasser(Group* group, PlayerbotAI* botAI)
{ {
Group* group = bot->GetGroup();
if (!group) if (!group)
return nullptr; return nullptr;
Player* designatedLooter = GetDesignatedCoreLooter(botAI, bot); Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
Player* firstCorePasser = GetFirstTaintedCorePasser(botAI, bot); Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI);
Player* secondCorePasser = GetSecondTaintedCorePasser(botAI, bot); Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI);
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
@ -396,32 +427,35 @@ namespace SerpentShrineCavernHelpers
continue; continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (memberAI && memberAI->IsAssistHealOfIndex(member, 2, true)) if (!memberAI)
continue;
if (memberAI->IsAssistHealOfIndex(member, 2, true))
return member; return member;
} }
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
Player* member = ref->GetSource(); Player* member = ref->GetSource();
if (member && member->IsAlive() && GET_PLAYERBOT_AI(member) && if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
!botAI->IsTank(member) && member != designatedLooter && botAI->IsTank(member) || member == designatedLooter ||
member != firstCorePasser && member != secondCorePasser) member == firstCorePasser || member == secondCorePasser)
continue;
return member; return member;
} }
return nullptr; return nullptr;
} }
Player* GetFourthTaintedCorePasser(PlayerbotAI* botAI, Player* bot) Player* GetFourthTaintedCorePasser(Group* group, PlayerbotAI* botAI)
{ {
Group* group = bot->GetGroup();
if (!group) if (!group)
return nullptr; return nullptr;
Player* designatedLooter = GetDesignatedCoreLooter(botAI, bot); Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
Player* firstCorePasser = GetFirstTaintedCorePasser(botAI, bot); Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI);
Player* secondCorePasser = GetSecondTaintedCorePasser(botAI, bot); Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI);
Player* thirdCorePasser = GetThirdTaintedCorePasser(botAI, bot); Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI);
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
@ -432,75 +466,27 @@ namespace SerpentShrineCavernHelpers
continue; continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (memberAI && memberAI->IsAssistRangedDpsOfIndex(member, 0, true)) if (!memberAI)
continue;
if (memberAI->IsAssistRangedDpsOfIndex(member, 0, true))
return member; return member;
} }
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
Player* member = ref->GetSource(); Player* member = ref->GetSource();
if (member && member->IsAlive() && GET_PLAYERBOT_AI(member) && if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
!botAI->IsTank(member) && member != designatedLooter && botAI->IsTank(member) || member == designatedLooter ||
member != firstCorePasser && member != secondCorePasser && member == firstCorePasser || member == secondCorePasser ||
member != thirdCorePasser) member == thirdCorePasser)
continue;
return member; return member;
} }
return nullptr; return nullptr;
} }
std::array<Player*, 5> GetCoreHandlers(PlayerbotAI* botAI, Player* bot)
{
return
{
GetDesignatedCoreLooter(botAI, bot),
GetFirstTaintedCorePasser(botAI, bot),
GetSecondTaintedCorePasser(botAI, bot),
GetThirdTaintedCorePasser(botAI, bot),
GetFourthTaintedCorePasser(botAI, bot)
};
}
// Checks if any bot from earlier in the passing sequence has the Tainted Core or
// had it within the prior 3 seconds so the chain is not broken when the Core is in transit
bool AnyRecentCoreInInventory(PlayerbotAI* botAI, Player* bot)
{
Unit* vashj =
botAI->GetAiObjectContext()->GetValue<Unit*>("find target", "lady vashj")->Get();
if (!vashj)
return false;
auto coreHandlers = GetCoreHandlers(botAI, bot);
int8 myIndex = -1;
for (int8 i = 0; i < 5; ++i)
if (coreHandlers[i] && coreHandlers[i] == bot)
myIndex = i;
if (myIndex == -1)
return false;
const time_t now = std::time(nullptr);
constexpr uint8 lookbackSeconds = 3;
for (int8 i = 0; i <= myIndex; ++i)
{
Player* handler = coreHandlers[i];
if (!handler)
continue;
if (handler->HasItemCount(ITEM_TAINTED_CORE, 1, false))
return true;
auto it = lastCoreInInventoryTime.find(handler->GetGUID());
if (it != lastCoreInInventoryTime.end() &&
(now - it->second) <= static_cast<time_t>(lookbackSeconds))
return true;
}
return false;
}
const std::vector<uint32> SHIELD_GENERATOR_DB_GUIDS = const std::vector<uint32> SHIELD_GENERATOR_DB_GUIDS =
{ {
47482, // NW 47482, // NW
@ -524,7 +510,10 @@ namespace SerpentShrineCavernHelpers
continue; continue;
GameObject* go = bounds.first->second; GameObject* go = bounds.first->second;
if (!go || go->GetGoState() != GO_STATE_READY) if (!go)
continue;
if (go->GetGoState() != GO_STATE_READY)
continue; continue;
GeneratorInfo info; GeneratorInfo info;
@ -540,7 +529,7 @@ namespace SerpentShrineCavernHelpers
// Returns the nearest active Shield Generator to the bot // Returns the nearest active Shield Generator to the bot
// Active generators are powered by NPC_WORLD_INVISIBLE_TRIGGER creatures, // Active generators are powered by NPC_WORLD_INVISIBLE_TRIGGER creatures,
// which despawn after use // which depawn after use
Unit* GetNearestActiveShieldGeneratorTriggerByEntry(Unit* reference) Unit* GetNearestActiveShieldGeneratorTriggerByEntry(Unit* reference)
{ {
if (!reference) if (!reference)

View File

@ -1,8 +1,3 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_RAIDSSCHELPERS_H_ #ifndef _PLAYERBOT_RAIDSSCHELPERS_H_
#define _PLAYERBOT_RAIDSSCHELPERS_H_ #define _PLAYERBOT_RAIDSSCHELPERS_H_
@ -69,6 +64,9 @@ namespace SerpentShrineCavernHelpers
// Warlock // Warlock
SPELL_CURSE_OF_EXHAUSTION = 18223, SPELL_CURSE_OF_EXHAUSTION = 18223,
// Item
SPELL_HEAVY_NETHERWEAVE_NET = 31368,
}; };
enum SerpentShrineCavernNPCs enum SerpentShrineCavernNPCs
@ -107,6 +105,9 @@ namespace SerpentShrineCavernHelpers
{ {
// Lady Vashj <Coilfang Matron> // Lady Vashj <Coilfang Matron>
ITEM_TAINTED_CORE = 31088, ITEM_TAINTED_CORE = 31088,
// Tailoring
ITEM_HEAVY_NETHERWEAVE_NET = 24269,
}; };
constexpr uint32 SSC_MAP_ID = 548; constexpr uint32 SSC_MAP_ID = 548;
@ -133,10 +134,10 @@ namespace SerpentShrineCavernHelpers
extern std::unordered_map<uint32, time_t> leotherasHumanFormDpsWaitTimer; extern std::unordered_map<uint32, time_t> leotherasHumanFormDpsWaitTimer;
extern std::unordered_map<uint32, time_t> leotherasDemonFormDpsWaitTimer; extern std::unordered_map<uint32, time_t> leotherasDemonFormDpsWaitTimer;
extern std::unordered_map<uint32, time_t> leotherasFinalPhaseDpsWaitTimer; extern std::unordered_map<uint32, time_t> leotherasFinalPhaseDpsWaitTimer;
Unit* GetLeotherasHuman(Player* bot); Unit* GetLeotherasHuman(PlayerbotAI* botAI);
Unit* GetPhase2LeotherasDemon(Player* bot); Unit* GetPhase2LeotherasDemon(PlayerbotAI* botAI);
Unit* GetPhase3LeotherasDemon(Player* bot); Unit* GetPhase3LeotherasDemon(PlayerbotAI* botAI);
Unit* GetActiveLeotherasDemon(Player* bot); Unit* GetActiveLeotherasDemon(PlayerbotAI* botAI);
Player* GetLeotherasDemonFormTank(Player* bot); Player* GetLeotherasDemonFormTank(Player* bot);
// Fathom-Lord Karathress // Fathom-Lord Karathress
@ -157,26 +158,25 @@ namespace SerpentShrineCavernHelpers
extern std::unordered_map<ObjectGuid, uint8> tidewalkerRangedStep; extern std::unordered_map<ObjectGuid, uint8> tidewalkerRangedStep;
// Lady Vashj <Coilfang Matron> // Lady Vashj <Coilfang Matron>
constexpr float VASHJ_PLATFORM_CENTER_Z = 42.902f; constexpr float VASHJ_PLATFORM_Z = 42.985f;
constexpr float VASHJ_PLATFORM_EDGE_Z = 41.097f;
extern const Position VASHJ_PLATFORM_CENTER_POSITION; extern const Position VASHJ_PLATFORM_CENTER_POSITION;
extern std::unordered_map<ObjectGuid, Position> vashjRangedPositions;
extern std::unordered_map<ObjectGuid, bool> hasReachedVashjRangedPosition; extern std::unordered_map<ObjectGuid, bool> hasReachedVashjRangedPosition;
extern std::unordered_map<uint32, ObjectGuid> nearestTriggerGuid; extern std::unordered_map<uint32, ObjectGuid> nearestTriggerGuid;
extern std::unordered_map<ObjectGuid, Position> intendedLineup; extern std::unordered_map<ObjectGuid, Position> intendedLineup;
extern std::unordered_map<uint32, time_t> lastImbueAttempt; extern std::unordered_map<uint32, time_t> lastImbueAttempt;
extern std::unordered_map<ObjectGuid, time_t> lastCoreInInventoryTime; extern std::unordered_map<uint32, time_t> lastCoreInInventoryTime;
bool IsMainTankInSameSubgroup(PlayerbotAI* botAI, Player* bot); bool IsMainTankInSameSubgroup(Player* bot);
bool IsLadyVashjInPhase1(PlayerbotAI* botAI); bool IsLadyVashjInPhase1(PlayerbotAI* botAI);
bool IsLadyVashjInPhase2(PlayerbotAI* botAI); bool IsLadyVashjInPhase2(PlayerbotAI* botAI);
bool IsLadyVashjInPhase3(PlayerbotAI* botAI); bool IsLadyVashjInPhase3(PlayerbotAI* botAI);
bool IsValidLadyVashjCombatNpc(Unit* unit, PlayerbotAI* botAI); bool IsValidLadyVashjCombatNpc(Unit* unit, PlayerbotAI* botAI);
Player* GetDesignatedCoreLooter(PlayerbotAI* botAI, Player* bot); bool AnyRecentCoreInInventory(Group* group, PlayerbotAI* botAI, uint32 graceSeconds = 3);
Player* GetFirstTaintedCorePasser(PlayerbotAI* botAI, Player* bot); Player* GetDesignatedCoreLooter(Group* group, PlayerbotAI* botAI);
Player* GetSecondTaintedCorePasser(PlayerbotAI* botAI, Player* bot); Player* GetFirstTaintedCorePasser(Group* group, PlayerbotAI* botAI);
Player* GetThirdTaintedCorePasser(PlayerbotAI* botAI, Player* bot); Player* GetSecondTaintedCorePasser(Group* group, PlayerbotAI* botAI);
Player* GetFourthTaintedCorePasser(PlayerbotAI* botAI, Player* bot); Player* GetThirdTaintedCorePasser(Group* group, PlayerbotAI* botAI);
std::array<Player*, 5> GetCoreHandlers(PlayerbotAI* botAI, Player* bot); Player* GetFourthTaintedCorePasser(Group* group, PlayerbotAI* botAI);
bool AnyRecentCoreInInventory(PlayerbotAI* botAI, Player* bot);
struct GeneratorInfo { ObjectGuid guid; float x, y, z; }; struct GeneratorInfo { ObjectGuid guid; float x, y, z; };
extern const std::vector<uint32> SHIELD_GENERATOR_DB_GUIDS; extern const std::vector<uint32> SHIELD_GENERATOR_DB_GUIDS;
std::vector<GeneratorInfo> GetAllGeneratorInfosByDbGuids( std::vector<GeneratorInfo> GetAllGeneratorInfosByDbGuids(

View File

@ -1796,7 +1796,7 @@ PlayerbotAI* PlayerbotsMgr::GetPlayerbotAI(Player* player)
if (itr != _playerbotsAIMap.end()) if (itr != _playerbotsAIMap.end())
{ {
if (itr->second->IsBotAI()) if (itr->second->IsBotAI())
return dynamic_cast<PlayerbotAI*>(itr->second); return reinterpret_cast<PlayerbotAI*>(itr->second);
} }
return nullptr; return nullptr;
@ -1812,7 +1812,7 @@ PlayerbotMgr* PlayerbotsMgr::GetPlayerbotMgr(Player* player)
if (itr != _playerbotsMgrMap.end()) if (itr != _playerbotsMgrMap.end())
{ {
if (!itr->second->IsBotAI()) if (!itr->second->IsBotAI())
return dynamic_cast<PlayerbotMgr*>(itr->second); return reinterpret_cast<PlayerbotMgr*>(itr->second);
} }
return nullptr; return nullptr;

View File

@ -1070,7 +1070,7 @@ void GuildTaskMgr::CheckKillTaskInternal(Player* player, Unit* victim)
if (!victim->IsCreature()) if (!victim->IsCreature())
return; return;
Creature* creature = dynamic_cast<Creature*>(victim); Creature* creature = reinterpret_cast<Creature*>(victim);
if (!creature) if (!creature)
return; return;

View File

@ -1753,8 +1753,9 @@ void TravelNodeMap::generateTransportNodes()
for (auto const& itr : *sObjectMgr->GetGameObjectTemplates()) for (auto const& itr : *sObjectMgr->GetGameObjectTemplates())
{ {
GameObjectTemplate const* data = &itr.second; GameObjectTemplate const* data = &itr.second;
if (!data || (data->type != GAMEOBJECT_TYPE_TRANSPORT && data->type != GAMEOBJECT_TYPE_MO_TRANSPORT)) if (data && (data->type == GAMEOBJECT_TYPE_TRANSPORT || data->type == GAMEOBJECT_TYPE_MO_TRANSPORT))
continue; {
TransportAnimation const* animation = sTransportMgr->GetTransportAnimInfo(itr.first);
uint32 pathId = data->moTransport.taxiPathId; uint32 pathId = data->moTransport.taxiPathId;
float moveSpeed = data->moTransport.moveSpeed; float moveSpeed = data->moTransport.moveSpeed;
@ -1763,20 +1764,125 @@ void TravelNodeMap::generateTransportNodes()
TaxiPathNodeList const& path = sTaxiPathNodesByPath[pathId]; TaxiPathNodeList const& path = sTaxiPathNodesByPath[pathId];
// Keep only transports with taxi paths (boats/zeppelins).
if (path.empty())
continue;
std::vector<WorldPosition> ppath; std::vector<WorldPosition> ppath;
TravelNode* prevNode = nullptr; TravelNode* prevNode = nullptr;
// Elevators/Trams
if (path.empty())
{
if (animation)
{
TransportPathContainer aPath = animation->Path;
float timeStart;
for (auto& transport : WorldPosition().getGameObjectsNear(0, itr.first))
{
prevNode = nullptr;
WorldPosition basePos(transport->mapid, transport->posX, transport->posY, transport->posZ,
transport->orientation);
WorldPosition lPos = WorldPosition();
for (auto& p : aPath)
{
float dx = -1 * p.second->X;
float dy = -1 * p.second->Y;
WorldPosition pos =
WorldPosition(basePos.GetMapId(), basePos.GetPositionX() + dx,
basePos.GetPositionY() + dy, basePos.GetPositionZ() + p.second->Z,
basePos.GetOrientation());
if (prevNode)
{
ppath.push_back(pos);
}
if (pos.distance(&lPos) == 0)
{
TravelNode* node =
TravelNodeMap::instance().addNode(pos, data->name, true, true, true, itr.first);
if (!prevNode)
{
ppath.push_back(pos);
timeStart = p.second->TimeSeg;
}
else
{
float totalTime = (p.second->TimeSeg - timeStart) / 1000.0f;
TravelNodePath travelPath(0.1f, totalTime, (uint8)TravelNodePathType::transport,
itr.first, true);
node->setPathTo(prevNode, travelPath);
ppath.clear();
ppath.push_back(pos);
timeStart = p.second->TimeSeg;
}
prevNode = node;
}
lPos = pos;
}
if (prevNode)
{
for (auto& p : aPath)
{
float dx = -1 * p.second->X;
float dy = -1 * p.second->Y;
WorldPosition pos =
WorldPosition(basePos.GetMapId(), basePos.GetPositionX() + dx,
basePos.GetPositionY() + dy, basePos.GetPositionZ() + p.second->Z,
basePos.GetOrientation());
ppath.push_back(pos);
if (pos.distance(&lPos) == 0)
{
TravelNode* node =
TravelNodeMap::instance().addNode(pos, data->name, true, true, true, itr.first);
if (node != prevNode)
{
if (p.second->TimeSeg < timeStart)
timeStart = 0;
float totalTime = (p.second->TimeSeg - timeStart) / 1000.0f;
TravelNodePath travelPath(0.1f, totalTime, (uint8)TravelNodePathType::transport,
itr.first, true);
travelPath.setPath(ppath);
node->setPathTo(prevNode, travelPath);
ppath.clear();
ppath.push_back(pos);
timeStart = p.second->TimeSeg;
}
}
lPos = pos;
}
}
ppath.clear();
}
}
}
else // Boats/Zepelins
{
// Loop over the path and connect stop locations. // Loop over the path and connect stop locations.
for (auto& p : path) for (auto& p : path)
{ {
WorldPosition pos = WorldPosition(p->mapid, p->x, p->y, p->z, 0); WorldPosition pos = WorldPosition(p->mapid, p->x, p->y, p->z, 0);
// if (data->displayId == 3015)
// pos.setZ(pos.getZ() + 6.0f);
// else if (data->displayId == 3031)
// pos.setZ(pos.getZ() - 17.0f);
if (prevNode) if (prevNode)
{
ppath.push_back(pos); ppath.push_back(pos);
}
if (p->delay > 0) if (p->delay > 0)
{ {
@ -1799,13 +1905,18 @@ void TravelNodeMap::generateTransportNodes()
} }
} }
if (!prevNode) if (prevNode)
continue; {
// Continue from start until first stop and connect to end. // Continue from start until first stop and connect to end.
for (auto& p : path) for (auto& p : path)
{ {
WorldPosition pos = WorldPosition(p->mapid, p->x, p->y, p->z, 0); WorldPosition pos = WorldPosition(p->mapid, p->x, p->y, p->z, 0);
// if (data->displayId == 3015)
// pos.setZ(pos.getZ() + 6.0f);
// else if (data->displayId == 3031)
// pos.setZ(pos.getZ() - 17.0f);
ppath.push_back(pos); ppath.push_back(pos);
if (p->delay > 0) if (p->delay > 0)
@ -1814,16 +1925,20 @@ void TravelNodeMap::generateTransportNodes()
if (node != prevNode) if (node != prevNode)
{ {
TravelNodePath travelPath(0.1f, 0.0, (uint8)TravelNodePathType::transport, itr.first, true); TravelNodePath travelPath(0.1f, 0.0, (uint8)TravelNodePathType::transport, itr.first,
true);
travelPath.setPathAndCost(ppath, moveSpeed); travelPath.setPathAndCost(ppath, moveSpeed);
node->setPathTo(prevNode, travelPath); node->setPathTo(prevNode, travelPath);
} }
} }
} }
}
ppath.clear(); ppath.clear();
} }
} }
}
}
void TravelNodeMap::generateZoneMeanNodes() void TravelNodeMap::generateZoneMeanNodes()
{ {