Merge pull request #2205 from mod-playerbots/test-staging

Test staging to master
This commit is contained in:
kadeshar 2026-03-20 20:36:12 +01:00 committed by GitHub
commit 4b7b0958dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 1544 additions and 1799 deletions

View File

@ -43,21 +43,21 @@ any impact on performance, you may skip these question. If necessary, a maintain
## Impact Assessment ## Impact Assessment
<!-- As a generic test, before and after measure of pmon (playerbot pmon tick) can help you here. --> <!-- As a generic test, before and after measure of pmon (playerbot pmon tick) can help you here. -->
- Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots? - Does this change increase per-bot/per-tick processing or risk scaling poorly with thousands of bots?
- [ ] No, not at all - - [ ] No, not at all
- [ ] Minimal impact (**explain below**) - - [ ] Minimal impact (**explain below**)
- [ ] Moderate impact (**explain below**) - - [ ] Moderate impact (**explain below**)
- Does this change modify default bot behavior? - Does this change modify default bot behavior?
- [ ] No - - [ ] No
- [ ] Yes (**explain why**) - - [ ] Yes (**explain why**)
- Does this change add new decision branches or increase maintenance complexity? - Does this change add new decision branches or increase maintenance complexity?
- [ ] No - - [ ] No
- [ ] Yes (**explain below**) - - [ ] Yes (**explain below**)
@ -68,8 +68,8 @@ the message is in a translatable format, and list in the table the message_key a
Search for GetBotTextOrDefault in the codebase for examples. Search for GetBotTextOrDefault in the codebase for examples.
--> -->
Does this change add bot messages to translate? Does this change add bot messages to translate?
- [ ] No - - [ ] No
- [ ] Yes (**list messages in the table**) - - [ ] Yes (**list messages in the table**)
| Message key | Default message | | Message key | Default message |
| --------------- | ------------------ | | --------------- | ------------------ |
@ -82,8 +82,8 @@ AI assistance is allowed, but all submitted code must be fully understood, revie
We expect contributors to be honest about what they do and do not understand. We expect contributors to be honest about what they do and do not understand.
--> -->
Was AI assistance used while working on this change? Was AI assistance used while working on this change?
- [ ] No - - [ ] No
- [ ] Yes (**explain below**) - - [ ] Yes (**explain below**)
<!-- <!--
If yes, please specify: If yes, please specify:
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code generation). - Purpose of usage (e.g. brainstorming, refactoring, documentation, code generation).
@ -94,10 +94,10 @@ If yes, please specify:
## Final Checklist ## Final Checklist
- [ ] Stability is not compromised. - - [ ] Stability is not compromised.
- [ ] Performance impact is understood, tested, and acceptable. - - [ ] Performance impact is understood, tested, and acceptable.
- [ ] Added logic complexity is justified and explained. - - [ ] Added logic complexity is justified and explained.
- [ ] Documentation updated if needed (Conf comments, WiKi commands). - - [ ] Documentation updated if needed (Conf comments, WiKi commands).
## Notes for Reviewers ## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request. --> <!-- Anything else that's helpful to review or test your pull request. -->

View File

@ -5,18 +5,211 @@
#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 GrobblulusMoveCenterAction : public MoveInsideAction class GrobbulusMoveCenterAction : public MoveInsideAction
{ {
public: public:
GrobblulusMoveCenterAction(PlayerbotAI* ai) : MoveInsideAction(ai, 3281.23f, -3310.38f, 5.0f) {} GrobbulusMoveCenterAction(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 HorsemanAttractAlternativelyAction : public AttackAction class FourHorsemenAttractAlternativelyAction : public AttackAction
{ {
public: public:
HorsemanAttractAlternativelyAction(PlayerbotAI* ai) : AttackAction(ai, "horseman attract alternatively"), helper(ai) FourHorsemenAttractAlternativelyAction(PlayerbotAI* ai) : AttackAction(ai, "four horsemen attract alternatively"), helper(ai)
{ {
} }
bool Execute(Event event) override; bool Execute(Event event) override;
protected: protected:
FourhorsemanBossHelper helper; FourHorsemenBossHelper helper;
}; };
class HorsemanAttactInOrderAction : public AttackAction class FourHorsemenAttackInOrderAction : public AttackAction
{ {
public: public:
HorsemanAttactInOrderAction(PlayerbotAI* ai) : AttackAction(ai, "horseman attact in order"), helper(ai) {} FourHorsemenAttackInOrderAction(PlayerbotAI* ai) : AttackAction(ai, "four horsemen attack in order"), helper(ai) {}
bool Execute(Event event) override; bool Execute(Event event) override;
protected: protected:
FourhorsemanBossHelper helper; FourHorsemenBossHelper helper;
}; };
// class SapphironGroundMainTankPositionAction : public MovementAction // class SapphironGroundMainTankPositionAction : public MovementAction

View File

@ -1,7 +1,6 @@
#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*/)
{ {
@ -66,13 +65,10 @@ 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, bot->GetPositionZ(), false, false, return MoveTo(bot->GetMapId(), waypoints[next_point].first, waypoints[next_point].second,
false, false, MovementPriority::MOVEMENT_COMBAT); bot->GetPositionZ(), false, false, 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 HorsemanAttractAlternativelyAction::Execute(Event /*event*/) bool FourHorsemenAttractAlternativelyAction::Execute(Event /*event*/)
{ {
if (!helper.UpdateBossAI()) if (!helper.UpdateBossAI())
return false; return false;
@ -13,13 +13,13 @@ bool HorsemanAttractAlternativelyAction::Execute(Event /*event*/)
return true; return true;
Unit* attackTarget = helper.CurrentAttackTarget(); Unit* attackTarget = helper.CurrentAttackTarget();
if (context->GetValue<Unit*>("current target")->Get() != attackTarget) if (attackTarget && context->GetValue<Unit*>("current target")->Get() != attackTarget)
return Attack(attackTarget); return Attack(attackTarget);
return false; return false;
} }
bool HorsemanAttactInOrderAction::Execute(Event /*event*/) bool FourHorsemenAttackInOrderAction::Execute(Event /*event*/)
{ {
if (!helper.UpdateBossAI()) if (!helper.UpdateBossAI())
return false; return false;

View File

@ -70,7 +70,6 @@ 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 (!bot->IsWithinDistInMap(target, 50.0f)) if (!target || !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 (!bot->IsWithinLOSInMap(target)) if (!target || !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 FourhorsemanGenericMultiplier::GetValue(Action* action) float FourHorsemenGenericMultiplier::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 _PLAYERRBOT_RAIDNAXXMULTIPLIERS_H #ifndef _PLAYERBOT_RAIDNAXXMULTIPLIERS_H
#define _PLAYERRBOT_RAIDNAXXMULTIPLIERS_H #define _PLAYERBOT_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 FourhorsemanGenericMultiplier : public Multiplier class FourHorsemenGenericMultiplier : public Multiplier
{ {
public: public:
FourhorsemanGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "fourhorseman generic") {} FourHorsemenGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "four horsemen 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["horseman attract alternatively"] = &RaidNaxxActionContext::horseman_attract_alternatively; creators["four horsemen attract alternatively"] = &RaidNaxxActionContext::four_horsemen_attract_alternatively;
creators["horseman attack in order"] = &RaidNaxxActionContext::horseman_attack_in_order; creators["four horsemen attack in order"] = &RaidNaxxActionContext::four_horsemen_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 GrobblulusMoveCenterAction(ai); } static Action* grobbulus_move_center(PlayerbotAI* ai) { return new GrobbulusMoveCenterAction(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,11 +70,8 @@ private:
{ {
return new RazuviousUseObedienceCrystalAction(ai); return new RazuviousUseObedienceCrystalAction(ai);
} }
static Action* horseman_attract_alternatively(PlayerbotAI* ai) static Action* four_horsemen_attract_alternatively(PlayerbotAI* ai) { return new FourHorsemenAttractAlternativelyAction(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["horseman attractors"] = &RaidNaxxTriggerContext::horseman_attractors; creators["four horsemen attractors"] = &RaidNaxxTriggerContext::four_horsemen_attractors;
creators["horseman except attractors"] = &RaidNaxxTriggerContext::horseman_except_attractors; creators["four horsemen except attractors"] = &RaidNaxxTriggerContext::four_horsemen_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* horseman_attractors(PlayerbotAI* ai) { return new HorsemanAttractorsTrigger(ai); } static Trigger* four_horsemen_attractors(PlayerbotAI* ai) { return new FourHorsemenAttractorsTrigger(ai); }
static Trigger* horseman_except_attractors(PlayerbotAI* ai) { return new HorsemanExceptAttractorsTrigger(ai); } static Trigger* four_horsemen_except_attractors(PlayerbotAI* ai) { return new FourHorsemenExceptAttractorsTrigger(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 horseman // four horsemen
triggers.push_back(new TriggerNode("horseman attractors", triggers.push_back(new TriggerNode("four horsemen attractors",
{ NextAction("horseman attract alternatively", ACTION_RAID + 1) } { NextAction("four horsemen attract alternatively", ACTION_RAID + 1) }
)); ));
triggers.push_back(new TriggerNode("horseman except attractors", triggers.push_back(new TriggerNode("four horsemen except attractors",
{ NextAction("horseman attack in order", ACTION_RAID + 1) } { NextAction("four horsemen 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 FourhorsemanGenericMultiplier(botAI)); multipliers.push_back(new FourHorsemenGenericMultiplier(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 HorsemanAttractorsTrigger::IsActive() bool FourHorsemenAttractorsTrigger::IsActive()
{ {
if (!helper.UpdateBossAI()) if (!helper.UpdateBossAI())
return false; return false;
@ -122,7 +122,7 @@ bool HorsemanAttractorsTrigger::IsActive()
return helper.IsAttracter(bot); return helper.IsAttracter(bot);
} }
bool HorsemanExceptAttractorsTrigger::IsActive() bool FourHorsemenExceptAttractorsTrigger::IsActive()
{ {
if (!helper.UpdateBossAI()) if (!helper.UpdateBossAI())
return false; return false;

View File

@ -186,24 +186,24 @@ private:
ThaddiusBossHelper helper; ThaddiusBossHelper helper;
}; };
class HorsemanAttractorsTrigger : public Trigger class FourHorsemenAttractorsTrigger : public Trigger
{ {
public: public:
HorsemanAttractorsTrigger(PlayerbotAI* ai) : Trigger(ai, "fourhorsemen attractors"), helper(ai) {} FourHorsemenAttractorsTrigger(PlayerbotAI* ai) : Trigger(ai, "four horsemen attractors"), helper(ai) {}
bool IsActive() override; bool IsActive() override;
private: private:
FourhorsemanBossHelper helper; FourHorsemenBossHelper helper;
}; };
class HorsemanExceptAttractorsTrigger : public Trigger class FourHorsemenExceptAttractorsTrigger : public Trigger
{ {
public: public:
HorsemanExceptAttractorsTrigger(PlayerbotAI* ai) : Trigger(ai, "fourhorsemen except attractors"), helper(ai) {} FourHorsemenExceptAttractorsTrigger(PlayerbotAI* ai) : Trigger(ai, "four horsemen except attractors"), helper(ai) {}
bool IsActive() override; bool IsActive() override;
private: private:
FourhorsemanBossHelper helper; FourHorsemenBossHelper 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::Chill25}); Aura* aura = NaxxSpellIds::GetAnyAura(bot, {NaxxSpellIds::Chill10, 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 FourhorsemanBossHelper : public AiObject class FourHorsemenBossHelper : 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)
FourhorsemanBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {} FourHorsemenBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {}
bool UpdateBossAI() bool UpdateBossAI()
{ {
if (!bot->IsInCombat()) if (!bot->IsInCombat())
@ -497,7 +497,8 @@ public:
if (feugen && feugen->IsAlive()) if (feugen && feugen->IsAlive())
unit = feugen; unit = feugen;
if (stalagg && stalagg->IsAlive() && (!feugen || bot->GetDistance(stalagg) < bot->GetDistance(feugen))) if (stalagg && stalagg->IsAlive() &&
(!feugen || !feugen->IsAlive() || bot->GetDistance(stalagg) < bot->GetDistance(feugen)))
unit = stalagg; unit = stalagg;
return unit; return unit;

View File

@ -58,6 +58,7 @@ 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,3 +1,8 @@
/*
* 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
@ -75,8 +80,8 @@ public:
bool Execute(Event event) override; bool Execute(Event event) override;
private: private:
bool TryMisdirectToFrostTank(Unit* hydross, Group* group); bool TryMisdirectToFrostTank(Unit* hydross);
bool TryMisdirectToNatureTank(Unit* hydross, Group* group); bool TryMisdirectToNatureTank(Unit* hydross);
}; };
class HydrossTheUnstableStopDpsUponPhaseChangeAction : public Action class HydrossTheUnstableStopDpsUponPhaseChangeAction : public Action
@ -413,10 +418,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 IsFirstCorePasserInIntendedPosition(Player* designatedLooter, Player* firstCorePasser, Unit* closestTrigger); bool IsFirstCorePasserInPosition(Player* designatedLooter, Player* firstCorePasser, Unit* closestTrigger);
bool IsSecondCorePasserInIntendedPosition(Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger); bool IsSecondCorePasserInPosition(Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger);
bool IsThirdCorePasserInIntendedPosition(Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger); bool IsThirdCorePasserInPosition(Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger);
bool IsFourthCorePasserInIntendedPosition(Player* thirdCorePasser, Player* fourthCorePasser, Unit* closestTrigger); bool IsFourthCorePasserInPosition(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);
}; };
@ -428,19 +433,12 @@ 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(PlayerbotAI* botAI, Player* bot); static std::vector<Unit*> GetAllSporeDropTriggers(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,3 +1,8 @@
/*
* 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"
@ -28,12 +33,10 @@ 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;
} }
@ -53,16 +56,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)))
{ return 0.0f;
if ((botAI->IsMainTank(bot) && hydross->HasAura(SPELL_CORRUPTION)) ||
(botAI->IsAssistTankOfIndex(bot, 0, true) && !hydross->HasAura(SPELL_CORRUPTION)))
return 0.0f;
}
return 1.0f; return 1.0f;
} }
@ -97,13 +100,13 @@ 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) ||
(dynamic_cast<CastSpellAction*>(action) && if (dynamic_cast<AttackAction*>(action) ||
!dynamic_cast<CastHealingSpellAction*>(action))) (dynamic_cast<CastSpellAction*>(action) &&
return 0.0f; !dynamic_cast<CastHealingSpellAction*>(action)))
} return 0.0f;
} }
if (hydross->HasAura(SPELL_CORRUPTION) && !botAI->IsAssistTankOfIndex(bot, 0, true)) if (hydross->HasAura(SPELL_CORRUPTION) && !botAI->IsAssistTankOfIndex(bot, 0, true))
@ -116,13 +119,13 @@ 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) ||
(dynamic_cast<CastSpellAction*>(action) && if (dynamic_cast<AttackAction*>(action) ||
!dynamic_cast<CastHealingSpellAction*>(action))) (dynamic_cast<CastSpellAction*>(action) &&
return 0.0f; !dynamic_cast<CastHealingSpellAction*>(action)))
} return 0.0f;
} }
return 1.0f; return 1.0f;
@ -133,11 +136,9 @@ 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;
} }
@ -175,14 +176,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) ||
dynamic_cast<FleeAction*>(action) || if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) || dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action)) dynamic_cast<CastDisengageAction*>(action) ||
return 0.0f; dynamic_cast<CastBlinkBackAction*>(action))
} return 0.0f;
return 1.0f; return 1.0f;
} }
@ -215,11 +216,11 @@ float TheLurkerBelowDisableTankAssistMultiplier::GetValue(Action* action)
++tankCount; ++tankCount;
} }
if (tankCount >= 3) if (tankCount < 3)
{ return 1.0f;
if (dynamic_cast<TankAssistAction*>(action))
return 0.0f; if (dynamic_cast<TankAssistAction*>(action))
} return 0.0f;
return 1.0f; return 1.0f;
} }
@ -234,22 +235,18 @@ float LeotherasTheBlindAvoidWhirlwindMultiplier::GetValue(Action* action)
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return 1.0f; return 1.0f;
Unit* leotherasHuman = GetLeotherasHuman(botAI); Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind");
if (!leotherasHuman) if (!leotheras || (!leotheras->HasAura(SPELL_WHIRLWIND) &&
!leotheras->HasAura(SPELL_WHIRLWIND_CHANNEL)))
return 1.0f; return 1.0f;
if (!leotherasHuman->HasAura(SPELL_LEOTHERAS_BANISHED) && if (dynamic_cast<CastReachTargetSpellAction*>(action))
(leotherasHuman->HasAura(SPELL_WHIRLWIND) || return 0.0f;
leotherasHuman->HasAura(SPELL_WHIRLWIND_CHANNEL)))
{
if (dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
if (dynamic_cast<MovementAction*>(action) && if (dynamic_cast<MovementAction*>(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;
} }
@ -262,10 +259,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(botAI) && dynamic_cast<AttackAction*>(action)) if (GetPhase2LeotherasDemon(bot) && dynamic_cast<AttackAction*>(action))
return 0.0f; return 0.0f;
if (!GetPhase3LeotherasDemon(botAI) && dynamic_cast<CastBerserkAction*>(action)) if (!GetPhase3LeotherasDemon(bot) && dynamic_cast<CastBerserkAction*>(action))
return 0.0f; return 0.0f;
return 1.0f; return 1.0f;
@ -273,21 +270,21 @@ 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) ||
dynamic_cast<DpsAssistAction*>(action) || if (dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<CastHealingSpellAction*>(action) || dynamic_cast<DpsAssistAction*>(action) ||
dynamic_cast<CastCureSpellAction*>(action) || dynamic_cast<CastHealingSpellAction*>(action) ||
dynamic_cast<CurePartyMemberAction*>(action) || dynamic_cast<CastCureSpellAction*>(action) ||
dynamic_cast<CastBuffSpellAction*>(action) || dynamic_cast<CurePartyMemberAction*>(action) ||
dynamic_cast<ResurrectPartyMemberAction*>(action) || dynamic_cast<CastBuffSpellAction*>(action) ||
dynamic_cast<PartyMemberActionNameSupport*>(action) || dynamic_cast<ResurrectPartyMemberAction*>(action) ||
dynamic_cast<CastBearFormAction*>(action) || dynamic_cast<PartyMemberActionNameSupport*>(action) ||
dynamic_cast<CastDireBearFormAction*>(action) || dynamic_cast<CastBearFormAction*>(action) ||
dynamic_cast<CastTreeFormAction*>(action)) dynamic_cast<CastDireBearFormAction*>(action) ||
return 0.0f; dynamic_cast<CastTreeFormAction*>(action))
} return 0.0f;
return 1.0f; return 1.0f;
} }
@ -297,19 +294,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(botAI)) if (!GetPhase2LeotherasDemon(bot))
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) ||
dynamic_cast<ReachTargetAction*>(action) || if (dynamic_cast<AttackAction*>(action) ||
dynamic_cast<CombatFormationMoveAction*>(action) || dynamic_cast<ReachTargetAction*>(action) ||
dynamic_cast<CastReachTargetSpellAction*>(action) || dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<CastKillingSpreeAction*>(action)) dynamic_cast<CastReachTargetSpellAction*>(action) ||
return 0.0f; dynamic_cast<CastKillingSpreeAction*>(action))
} return 0.0f;
return 1.0f; return 1.0f;
} }
@ -330,8 +327,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(botAI); Unit* leotherasHuman = GetLeotherasHuman(bot);
Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI); Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(bot);
if (leotherasHuman && !leotherasHuman->HasAura(SPELL_LEOTHERAS_BANISHED) && if (leotherasHuman && !leotherasHuman->HasAura(SPELL_LEOTHERAS_BANISHED) &&
!leotherasPhase3Demon) !leotherasPhase3Demon)
{ {
@ -345,12 +342,12 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action)
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;
} }
} }
constexpr uint8 dpsWaitSecondsPhase2 = 12; constexpr uint8 dpsWaitSecondsPhase2 = 12;
Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI); Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(bot);
Player* demonFormTank = GetLeotherasDemonFormTank(bot); Player* demonFormTank = GetLeotherasDemonFormTank(bot);
if (leotherasPhase2Demon) if (leotherasPhase2Demon)
{ {
@ -367,7 +364,7 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action)
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;
} }
} }
@ -384,7 +381,7 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action)
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;
} }
} }
@ -398,12 +395,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) ||
dynamic_cast<CastBloodlustAction*>(action)) if (dynamic_cast<CastHeroismAction*>(action) ||
return 0.0f; dynamic_cast<CastBloodlustAction*>(action))
} return 0.0f;
return 1.0f; return 1.0f;
} }
@ -447,14 +444,12 @@ 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->getThreatType() == Action::ActionThreatType::Aoe) if (castSpellAction && castSpellAction->getThreatType() == Action::ActionThreatType::Aoe)
return 0.0f; return 0.0f;
}
}
return 1.0f; return 1.0f;
} }
@ -464,11 +459,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))
return 0.0f; if (dynamic_cast<CastMisdirectionOnMainTankAction*>(action))
} return 0.0f;
return 1.0f; return 1.0f;
} }
@ -494,7 +489,7 @@ float FathomLordKarathressWaitForDpsMultiplier::GetValue(Action* action)
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;
@ -505,12 +500,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) ||
dynamic_cast<FollowAction*>(action)) if (dynamic_cast<FleeAction*>(action) ||
return 0.0f; dynamic_cast<FollowAction*>(action))
} return 0.0f;
return 1.0f; return 1.0f;
} }
@ -526,12 +521,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) ||
dynamic_cast<CastBloodlustAction*>(action)) if (dynamic_cast<CastHeroismAction*>(action) ||
return 0.0f; dynamic_cast<CastBloodlustAction*>(action))
} return 0.0f;
return 1.0f; return 1.0f;
} }
@ -541,11 +536,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))
return 0.0f; if (dynamic_cast<CombatFormationMoveAction*>(action))
} return 0.0f;
return 1.0f; return 1.0f;
} }
@ -556,17 +551,14 @@ 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) if (!tidewalker || tidewalker->GetHealthPct() > 25.0f)
return 1.0f; return 1.0f;
if (tidewalker->GetHealthPct() < 25.0f) if (dynamic_cast<CombatFormationMoveAction*>(action) ||
{ dynamic_cast<FleeAction*>(action) ||
if (dynamic_cast<CombatFormationMoveAction*>(action) || dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<FleeAction*>(action) || dynamic_cast<CastBlinkBackAction*>(action))
dynamic_cast<CastDisengageAction*>(action) || return 0.0f;
dynamic_cast<CastBlinkBackAction*>(action))
return 0.0f;
}
return 1.0f; return 1.0f;
} }
@ -580,41 +572,59 @@ 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) &&
if (IsLadyVashjInPhase3(botAI)) (dynamic_cast<CastBloodlustAction*>(action) ||
return 1.0f; dynamic_cast<CastHeroismAction*>(action)))
return 0.0f;
if (dynamic_cast<CastBloodlustAction*>(action) || if (!botAI->IsDps(bot) || !IsLadyVashjInPhase1(botAI))
dynamic_cast<CastHeroismAction*>(action)) return 1.0f;
return 0.0f;
}
if (botAI->IsDps(bot) && IsLadyVashjInPhase1(botAI)) if (dynamic_cast<CastMetamorphosisAction*>(action) ||
{ dynamic_cast<CastAdrenalineRushAction*>(action) ||
if (dynamic_cast<CastMetamorphosisAction*>(action) || dynamic_cast<CastBladeFlurryAction*>(action) ||
dynamic_cast<CastAdrenalineRushAction*>(action) || dynamic_cast<CastIcyVeinsAction*>(action) ||
dynamic_cast<CastBladeFlurryAction*>(action) || dynamic_cast<CastColdSnapAction*>(action) ||
dynamic_cast<CastIcyVeinsAction*>(action) || dynamic_cast<CastArcanePowerAction*>(action) ||
dynamic_cast<CastColdSnapAction*>(action) || dynamic_cast<CastPresenceOfMindAction*>(action) ||
dynamic_cast<CastArcanePowerAction*>(action) || dynamic_cast<CastCombustionAction*>(action) ||
dynamic_cast<CastPresenceOfMindAction*>(action) || dynamic_cast<CastRapidFireAction*>(action) ||
dynamic_cast<CastCombustionAction*>(action) || dynamic_cast<CastReadinessAction*>(action) ||
dynamic_cast<CastRapidFireAction*>(action) || dynamic_cast<CastAvengingWrathAction*>(action) ||
dynamic_cast<CastReadinessAction*>(action) || dynamic_cast<CastElementalMasteryAction*>(action) ||
dynamic_cast<CastAvengingWrathAction*>(action) || dynamic_cast<CastFeralSpiritAction*>(action) ||
dynamic_cast<CastElementalMasteryAction*>(action) || dynamic_cast<CastFireElementalTotemAction*>(action) ||
dynamic_cast<CastFeralSpiritAction*>(action) || dynamic_cast<CastFireElementalTotemMeleeAction*>(action) ||
dynamic_cast<CastFireElementalTotemAction*>(action) || dynamic_cast<CastForceOfNatureAction*>(action) ||
dynamic_cast<CastFireElementalTotemMeleeAction*>(action) || dynamic_cast<CastArmyOfTheDeadAction*>(action) ||
dynamic_cast<CastForceOfNatureAction*>(action) || dynamic_cast<CastSummonGargoyleAction*>(action) ||
dynamic_cast<CastArmyOfTheDeadAction*>(action) || dynamic_cast<CastBerserkingAction*>(action) ||
dynamic_cast<CastSummonGargoyleAction*>(action) || dynamic_cast<CastBloodFuryAction*>(action) ||
dynamic_cast<CastBerserkingAction*>(action) || dynamic_cast<UseTrinketAction*>(action))
dynamic_cast<CastBloodFuryAction*>(action) || return 0.0f;
dynamic_cast<UseTrinketAction*>(action))
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;
} }
@ -624,15 +634,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) ||
dynamic_cast<FleeAction*>(action) || if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) || dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action)) dynamic_cast<CastDisengageAction*>(action) ||
return 0.0f; dynamic_cast<CastBlinkBackAction*>(action))
} return 0.0f;
return 1.0f; return 1.0f;
} }
@ -658,19 +668,18 @@ 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))
return 0.0f; if (dynamic_cast<LootAction*>(action))
} 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") || if (!AI_VALUE2(Unit*, "find target", "lady vashj") || !IsLadyVashjInPhase2(botAI))
!IsLadyVashjInPhase2(botAI))
return 1.0f; return 1.0f;
if (dynamic_cast<WipeAction*>(action) || if (dynamic_cast<WipeAction*>(action) ||
@ -678,65 +687,43 @@ float LadyVashjCorePassersPrioritizePositioningMultiplier::GetValue(Action* acti
dynamic_cast<LadyVashjDestroyTaintedCoreAction*>(action)) dynamic_cast<LadyVashjDestroyTaintedCoreAction*>(action))
return 1.0f; return 1.0f;
Group* group = bot->GetGroup(); auto coreHandlers = GetCoreHandlers(botAI, bot);
if (!group)
return 1.0f;
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI); bool isCoreHandler = false;
Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI); int myIndex = -1;
Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI); for (int i = 0; i < static_cast<int>(coreHandlers.size()); ++i)
Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI); {
Player* fourthCorePasser = GetFourthTaintedCorePasser(group, botAI); if (coreHandlers[i] && coreHandlers[i] == bot)
{
isCoreHandler = true;
myIndex = i;
}
}
if (!isCoreHandler)
return 1.0f;
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 (hasCore(bot)) // If the bot actually has the core, only allow core handling
{ if (hasCore(bot) && !dynamic_cast<LadyVashjPassTheTaintedCoreAction*>(action))
if (!dynamic_cast<LadyVashjPassTheTaintedCoreAction*>(action)) return 0.0f;
return 0.0f;
}
if (bot == designatedLooter) // First and second passers block movement when the looter teleports to the elemental
{ Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental");
if (!hasCore(bot)) if (tainted && coreHandlers[0]->GetExactDist2d(tainted) < 5.0f &&
return 1.0f; (bot == coreHandlers[1] || bot == coreHandlers[2]) &&
} (dynamic_cast<MovementAction*>(action) &&
else if (bot == firstCorePasser) !dynamic_cast<LadyVashjPassTheTaintedCoreAction*>(action)))
{ 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 (AI_VALUE2(Unit*, "find target", "tainted elemental") && // If any prior handler (including self) recently had the core, block other movement
(bot == firstCorePasser || bot == secondCorePasser)) if (AnyRecentCoreInInventory(botAI, bot) &&
{ 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;
} }
@ -745,7 +732,8 @@ 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)
{ {
if (!AI_VALUE2(Unit*, "find target", "lady vashj")) Unit* vashj = 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))
@ -755,24 +743,26 @@ 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");
@ -780,16 +770,13 @@ 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<TankAssistAction*>(action) || if (dynamic_cast<FollowAction*>(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,3 +1,8 @@
/*
* 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
@ -193,6 +198,14 @@ 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,3 +1,8 @@
/*
* 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
@ -161,9 +166,6 @@ 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;
@ -324,9 +326,6 @@ 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,3 +1,8 @@
/*
* 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
@ -155,9 +160,6 @@ 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;
@ -312,9 +314,6 @@ 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,3 +1,8 @@
/*
* 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"
@ -144,9 +149,6 @@ 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) }));
@ -198,6 +200,7 @@ 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,3 +1,8 @@
/*
* 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,3 +1,8 @@
/*
* 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"
@ -26,35 +31,37 @@ bool UnderbogColossusSpawnedToxicPoolAfterDeathTrigger::IsActive()
bool GreyheartTidecallerWaterElementalTotemSpawnedTrigger::IsActive() bool GreyheartTidecallerWaterElementalTotemSpawnedTrigger::IsActive()
{ {
return botAI->IsDps(bot) && return botAI->IsDps(bot) &&
GetFirstAliveUnitByEntry(botAI, NPC_WATER_ELEMENTAL_TOTEM); AI_VALUE2(Unit*, "find target", "greyheart tidecaller");
} }
// Hydross the Unstable <Duke of Currents> // Hydross the Unstable <Duke of Currents>
bool HydrossTheUnstableBotIsFrostTankTrigger::IsActive() bool HydrossTheUnstableBotIsFrostTankTrigger::IsActive()
{ {
return AI_VALUE2(Unit*, "find target", "hydross the unstable") && return botAI->IsMainTank(bot) &&
botAI->IsMainTank(bot); AI_VALUE2(Unit*, "find target", "hydross the unstable");
} }
bool HydrossTheUnstableBotIsNatureTankTrigger::IsActive() bool HydrossTheUnstableBotIsNatureTankTrigger::IsActive()
{ {
return AI_VALUE2(Unit*, "find target", "hydross the unstable") && return botAI->IsAssistTankOfIndex(bot, 0, true) &&
botAI->IsAssistTankOfIndex(bot, 0, true); AI_VALUE2(Unit*, "find target", "hydross the unstable");
} }
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 (!AI_VALUE2(Unit*, "find target", "pure spawn of hydross") && if (botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0, true))
!AI_VALUE2(Unit*, "find target", "tainted spawn of hydross"))
return false; return false;
return !botAI->IsHeal(bot) && !botAI->IsMainTank(bot) && return AI_VALUE2(Unit*, "find target", "pure spawn of hydross") ||
!botAI->IsAssistTankOfIndex(bot, 0, true); AI_VALUE2(Unit*, "find target", "tainted spawn of hydross");
} }
bool HydrossTheUnstableDangerFromWaterTombsTrigger::IsActive() bool HydrossTheUnstableDangerFromWaterTombsTrigger::IsActive()
@ -71,19 +78,19 @@ bool HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger::IsActive()
bool HydrossTheUnstableAggroResetsUponPhaseChangeTrigger::IsActive() bool HydrossTheUnstableAggroResetsUponPhaseChangeTrigger::IsActive()
{ {
if (!AI_VALUE2(Unit*, "find target", "hydross the unstable")) if (bot->getClass() == CLASS_HUNTER ||
botAI->IsHeal(bot) ||
botAI->IsMainTank(bot) ||
botAI->IsAssistTankOfIndex(bot, 0, true))
return false; return false;
return bot->getClass() != CLASS_HUNTER && return AI_VALUE2(Unit*, "find target", "hydross the unstable");
!botAI->IsHeal(bot) &&
!botAI->IsMainTank(bot) &&
!botAI->IsAssistTankOfIndex(bot, 0, true);
} }
bool HydrossTheUnstableNeedToManageTimersTrigger::IsActive() bool HydrossTheUnstableNeedToManageTimersTrigger::IsActive()
{ {
return AI_VALUE2(Unit*, "find target", "hydross the unstable") && return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) &&
IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr); AI_VALUE2(Unit*, "find target", "hydross the unstable");
} }
// The Lurker Below // The Lurker Below
@ -102,11 +109,11 @@ bool TheLurkerBelowSpoutIsActiveTrigger::IsActive()
bool TheLurkerBelowBossIsActiveForMainTankTrigger::IsActive() bool TheLurkerBelowBossIsActiveForMainTankTrigger::IsActive()
{ {
Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below"); if (!botAI->IsMainTank(bot))
if (!lurker)
return false; return false;
if (!botAI->IsMainTank(bot)) Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below");
if (!lurker)
return false; return false;
const time_t now = std::time(nullptr); const time_t now = std::time(nullptr);
@ -135,35 +142,16 @@ 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 = nullptr; Player* mainTank = GetGroupMainTank(botAI, bot);
Player* firstAssistTank = nullptr; Player* firstAssistTank = GetGroupAssistTank(botAI, bot, 0);
Player* secondAssistTank = nullptr; Player* secondAssistTank = GetGroupAssistTank(botAI, bot, 1);
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;
@ -173,51 +161,55 @@ bool TheLurkerBelowBossIsSubmergedTrigger::IsActive()
bool TheLurkerBelowNeedToPrepareTimerForSpoutTrigger::IsActive() bool TheLurkerBelowNeedToPrepareTimerForSpoutTrigger::IsActive()
{ {
return AI_VALUE2(Unit*, "find target", "the lurker below") && return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) &&
IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr); AI_VALUE2(Unit*, "find target", "the lurker below");
} }
// Leotheras the Blind // Leotheras the Blind
bool LeotherasTheBlindBossIsInactiveTrigger::IsActive() bool LeotherasTheBlindBossIsInactiveTrigger::IsActive()
{ {
return AI_VALUE2(Unit*, "find target", "greyheart spellbinder"); return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) &&
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(botAI); return GetActiveLeotherasDemon(bot);
} }
bool LeotherasTheBlindOnlyWarlockShouldTankDemonFormTrigger::IsActive() bool LeotherasTheBlindOnlyWarlockShouldTankDemonFormTrigger::IsActive()
{ {
if (botAI->IsRanged(bot) || !botAI->IsTank(bot)) if (!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(botAI); return GetPhase2LeotherasDemon(bot);
} }
bool LeotherasTheBlindBossEngagedByRangedTrigger::IsActive() bool LeotherasTheBlindBossEngagedByRangedTrigger::IsActive()
{ {
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) if (!botAI->IsRanged(bot))
return false; return false;
if (!botAI->IsRanged(bot)) if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false; return false;
Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind"); Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind");
@ -231,14 +223,14 @@ bool LeotherasTheBlindBossEngagedByRangedTrigger::IsActive()
bool LeotherasTheBlindBossChannelingWhirlwindTrigger::IsActive() bool LeotherasTheBlindBossChannelingWhirlwindTrigger::IsActive()
{ {
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) if (botAI->IsTank(bot))
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 || leotheras->HasAura(SPELL_LEOTHERAS_BANISHED)) if (!leotheras)
return false;
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false; return false;
return leotheras->HasAura(SPELL_WHIRLWIND) || return leotheras->HasAura(SPELL_WHIRLWIND) ||
@ -247,10 +239,13 @@ bool LeotherasTheBlindBossChannelingWhirlwindTrigger::IsActive()
bool LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger::IsActive() bool LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger::IsActive()
{ {
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER)) if (botAI->IsRanged(bot))
return false; return false;
if (botAI->IsRanged(bot)) if (!AI_VALUE2(Unit*, "find target", "leotheras the blind"))
return false;
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false; return false;
Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST); Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST);
@ -260,7 +255,7 @@ bool LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger::IsActive()
if (!GetLeotherasDemonFormTank(bot) && botAI->IsMainTank(bot)) if (!GetLeotherasDemonFormTank(bot) && botAI->IsMainTank(bot))
return false; return false;
return GetPhase2LeotherasDemon(botAI); return GetPhase2LeotherasDemon(bot);
} }
bool LeotherasTheBlindInnerDemonHasAwakenedTrigger::IsActive() bool LeotherasTheBlindInnerDemonHasAwakenedTrigger::IsActive()
@ -271,89 +266,68 @@ bool LeotherasTheBlindInnerDemonHasAwakenedTrigger::IsActive()
bool LeotherasTheBlindEnteredFinalPhaseTrigger::IsActive() bool LeotherasTheBlindEnteredFinalPhaseTrigger::IsActive()
{ {
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
if (botAI->IsHeal(bot)) if (botAI->IsHeal(bot))
return false; return false;
if (GetLeotherasDemonFormTank(bot) == bot) if (!AI_VALUE2(Unit*, "find target", "leotheras the blind"))
return false; return false;
return GetPhase3LeotherasDemon(botAI) && if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
GetLeotherasHuman(botAI); return false;
if (bot->getClass() == CLASS_WARLOCK && GetLeotherasDemonFormTank(bot) == bot)
return false;
return GetPhase3LeotherasDemon(bot);
} }
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;
return AI_VALUE2(Unit*, "find target", "leotheras the blind"); if (!AI_VALUE2(Unit*, "find target", "leotheras the blind"))
return false;
return !bot->HasAura(SPELL_INSIDIOUS_WHISPER);
} }
bool LeotherasTheBlindBossWipesAggroUponPhaseChangeTrigger::IsActive() bool LeotherasTheBlindBossWipesAggroUponPhaseChangeTrigger::IsActive()
{ {
return AI_VALUE2(Unit*, "find target", "leotheras the blind") && return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) &&
IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr); AI_VALUE2(Unit*, "find target", "leotheras the blind");
} }
// Fathom-Lord Karathress // Fathom-Lord Karathress
bool FathomLordKarathressBossEngagedByMainTankTrigger::IsActive() bool FathomLordKarathressBossEngagedByMainTankTrigger::IsActive()
{ {
return AI_VALUE2(Unit*, "find target", "fathom-lord karathress") && return botAI->IsMainTank(bot) &&
botAI->IsMainTank(bot); AI_VALUE2(Unit*, "find target", "fathom-lord karathress");
} }
bool FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger::IsActive() bool FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger::IsActive()
{ {
return AI_VALUE2(Unit*, "find target", "fathom-guard caribdis") && return botAI->IsAssistTankOfIndex(bot, 0, false) &&
botAI->IsAssistTankOfIndex(bot, 0, false); AI_VALUE2(Unit*, "find target", "fathom-guard caribdis");
} }
bool FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger::IsActive() bool FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger::IsActive()
{ {
return AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis") && return botAI->IsAssistTankOfIndex(bot, 1, false) &&
botAI->IsAssistTankOfIndex(bot, 1, false); AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis");
} }
bool FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger::IsActive() bool FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger::IsActive()
{ {
return AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess") && return botAI->IsAssistTankOfIndex(bot, 2, false) &&
botAI->IsAssistTankOfIndex(bot, 2, false); AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess");
} }
bool FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger::IsActive() bool FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger::IsActive()
{ {
Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"); return botAI->IsAssistHealOfIndex(bot, 0, true) &&
if (!caribdis) AI_VALUE2(Unit*, "find target", "fathom-guard 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()
@ -367,10 +341,10 @@ bool FathomLordKarathressPullingBossesTrigger::IsActive()
bool FathomLordKarathressDeterminingKillOrderTrigger::IsActive() bool FathomLordKarathressDeterminingKillOrderTrigger::IsActive()
{ {
if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress")) if (botAI->IsHeal(bot))
return false; return false;
if (botAI->IsHeal(bot)) if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress"))
return false; return false;
if (botAI->IsDps(bot)) if (botAI->IsDps(bot))
@ -387,8 +361,8 @@ bool FathomLordKarathressDeterminingKillOrderTrigger::IsActive()
bool FathomLordKarathressTanksNeedToEstablishAggroTrigger::IsActive() bool FathomLordKarathressTanksNeedToEstablishAggroTrigger::IsActive()
{ {
return AI_VALUE2(Unit*, "find target", "fathom-lord karathress") && return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) &&
IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr); AI_VALUE2(Unit*, "find target", "fathom-lord karathress");
} }
// Morogrim Tidewalker // Morogrim Tidewalker
@ -404,8 +378,8 @@ bool MorogrimTidewalkerPullingBossTrigger::IsActive()
bool MorogrimTidewalkerBossEngagedByMainTankTrigger::IsActive() bool MorogrimTidewalkerBossEngagedByMainTankTrigger::IsActive()
{ {
return AI_VALUE2(Unit*, "find target", "morogrim tidewalker") && return botAI->IsMainTank(bot) &&
botAI->IsMainTank(bot); AI_VALUE2(Unit*, "find target", "morogrim tidewalker");
} }
bool MorogrimTidewalkerWaterGlobulesAreIncomingTrigger::IsActive() bool MorogrimTidewalkerWaterGlobulesAreIncomingTrigger::IsActive()
@ -421,8 +395,11 @@ 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) && botAI->IsMainTank(bot); !IsLadyVashjInPhase2(botAI);
} }
bool LadyVashjBossEngagedByRangedInPhase1Trigger::IsActive() bool LadyVashjBossEngagedByRangedInPhase1Trigger::IsActive()
@ -439,10 +416,7 @@ bool LadyVashjCastsShockBlastOnHighestAggroTrigger::IsActive()
IsLadyVashjInPhase2(botAI)) IsLadyVashjInPhase2(botAI))
return false; return false;
if (!IsMainTankInSameSubgroup(bot)) return IsMainTankInSameSubgroup(botAI, bot);
return false;
return true;
} }
bool LadyVashjBotHasStaticChargeTrigger::IsActive() bool LadyVashjBotHasStaticChargeTrigger::IsActive()
@ -450,14 +424,15 @@ bool LadyVashjBotHasStaticChargeTrigger::IsActive()
if (!AI_VALUE2(Unit*, "find target", "lady vashj")) if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
return false; return false;
if (Group* group = bot->GetGroup()) 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();
{ if (member && member->HasAura(SPELL_STATIC_CHARGE))
Player* member = ref->GetSource(); return true;
if (member && member->HasAura(SPELL_STATIC_CHARGE))
return true;
}
} }
return false; return false;
@ -500,9 +475,10 @@ bool LadyVashjTaintedElementalCheatTrigger::IsActive()
return false; return false;
bool taintedPresent = false; bool taintedPresent = false;
Unit* taintedUnit = AI_VALUE2(Unit*, "find target", "tainted elemental"); if (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");
@ -513,13 +489,11 @@ 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;
{ break;
taintedPresent = true;
break;
}
} }
} }
} }
@ -527,12 +501,8 @@ bool LadyVashjTaintedElementalCheatTrigger::IsActive()
if (!taintedPresent) if (!taintedPresent)
return false; return false;
Group* group = bot->GetGroup(); return GetDesignatedCoreLooter(botAI, bot) == bot &&
if (!group) !bot->HasItemCount(ITEM_TAINTED_CORE, 1, false);
return false;
return (GetDesignatedCoreLooter(group, botAI) == bot &&
!bot->HasItemCount(ITEM_TAINTED_CORE, 1, false));
} }
bool LadyVashjTaintedCoreWasLootedTrigger::IsActive() bool LadyVashjTaintedCoreWasLootedTrigger::IsActive()
@ -540,54 +510,24 @@ 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;
Group* group = bot->GetGroup(); auto coreHandlers = GetCoreHandlers(botAI, bot);
if (!group)
bool isCoreHandler = false;
for (Player* handler : coreHandlers)
if (handler == bot)
isCoreHandler = true;
if (!isCoreHandler)
return false; return false;
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) -> 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;
// First and second passers move to positions as soon as the elemental appears // First and second passers move to positions as soon as the elemental appears
if (AI_VALUE2(Unit*, "find target", "tainted elemental") && Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental");
(bot == firstCorePasser || bot == secondCorePasser)) if (tainted && coreHandlers[0]->GetExactDist2d(tainted) < 5.0f &&
(bot == coreHandlers[1] || bot == coreHandlers[2]))
return true; return true;
return false; // Main logic: run if core is in play for this bot or a prior handler
return AnyRecentCoreInInventory(botAI, bot);
} }
bool LadyVashjTaintedCoreIsUnusableTrigger::IsActive() bool LadyVashjTaintedCoreIsUnusableTrigger::IsActive()
@ -599,18 +539,7 @@ 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);
Group* group = bot->GetGroup(); auto coreHandlers = GetCoreHandlers(botAI, bot);
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))
{ {
@ -625,24 +554,6 @@ 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);
@ -653,17 +564,18 @@ bool LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger::IsActive()
if (!AI_VALUE2(Unit*, "find target", "lady vashj")) if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
return false; return false;
if (Group* group = bot->GetGroup()) Group* group = bot->GetGroup();
{ if (!group)
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) return false;
{
Player* member = ref->GetSource();
if (!member || !member->HasAura(SPELL_ENTANGLE))
continue;
if (botAI->IsMelee(member)) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
return true; {
} Player* member = ref->GetSource();
if (!member || !member->HasAura(SPELL_ENTANGLE))
continue;
if (botAI->IsMelee(member))
return true;
} }
return false; return false;

View File

@ -1,3 +1,8 @@
/*
* 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
@ -387,14 +392,6 @@ 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,3 +1,8 @@
/*
* 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"
@ -79,61 +84,54 @@ 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(PlayerbotAI* botAI) Unit* GetLeotherasHuman(Player* bot)
{ {
auto const& npcs = constexpr float searchRadius = 100.0f;
botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest hostile npcs")->Get(); Creature* leotheras =
for (auto const& guid : npcs) bot->FindNearestCreature(NPC_LEOTHERAS_THE_BLIND, searchRadius, true);
{
Unit* unit = botAI->GetUnit(guid); if (leotheras && leotheras->IsInCombat() &&
if (unit && unit->GetEntry() == NPC_LEOTHERAS_THE_BLIND && !leotheras->HasAura(SPELL_METAMORPHOSIS))
unit->IsInCombat() && !unit->HasAura(SPELL_METAMORPHOSIS)) return leotheras;
return unit;
}
return nullptr; return nullptr;
} }
Unit* GetPhase2LeotherasDemon(PlayerbotAI* botAI) Unit* GetPhase2LeotherasDemon(Player* bot)
{ {
auto const& npcs = constexpr float searchRadius = 100.0f;
botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest hostile npcs")->Get(); Creature* leotheras =
for (auto const& guid : npcs) bot->FindNearestCreature(NPC_LEOTHERAS_THE_BLIND, searchRadius, true);
{
Unit* unit = botAI->GetUnit(guid); if (leotheras && leotheras->HasAura(SPELL_METAMORPHOSIS))
if (unit && unit->GetEntry() == NPC_LEOTHERAS_THE_BLIND && return leotheras;
unit->HasAura(SPELL_METAMORPHOSIS))
return unit;
}
return nullptr; return nullptr;
} }
Unit* GetPhase3LeotherasDemon(PlayerbotAI* botAI) Unit* GetPhase3LeotherasDemon(Player* bot)
{ {
auto const& npcs = constexpr float searchRadius = 100.0f;
botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest hostile npcs")->Get(); return bot->FindNearestCreature(NPC_SHADOW_OF_LEOTHERAS, searchRadius, true);
for (auto const& guid : npcs)
{
Unit* unit = botAI->GetUnit(guid);
if (unit && unit->GetEntry() == NPC_SHADOW_OF_LEOTHERAS)
return unit;
}
return nullptr;
} }
Unit* GetActiveLeotherasDemon(PlayerbotAI* botAI) Unit* GetActiveLeotherasDemon(Player* bot)
{ {
Unit* phase2 = GetPhase2LeotherasDemon(botAI); Unit* phase2 = GetPhase2LeotherasDemon(bot);
Unit* phase3 = GetPhase3LeotherasDemon(botAI); Unit* phase3 = GetPhase3LeotherasDemon(bot);
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;
// (1) First loop: Return the first assistant Warlock (real player or bot) Player* fallbackWarlock = 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();
@ -142,21 +140,12 @@ namespace SerpentShrineCavernHelpers
if (group->IsAssistant(member->GetGUID())) if (group->IsAssistant(member->GetGUID()))
return member; return member;
if (!fallbackWarlock && GET_PLAYERBOT_AI(member))
fallbackWarlock = member;
} }
// (2) Fall back to first found bot Warlock return fallbackWarlock;
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
@ -182,16 +171,15 @@ namespace SerpentShrineCavernHelpers
// Lady Vashj <Coilfang Matron> // Lady Vashj <Coilfang Matron>
const Position VASHJ_PLATFORM_CENTER_POSITION = { 29.634f, -923.541f, 42.985f }; const Position VASHJ_PLATFORM_CENTER_POSITION = { 29.634f, -923.541f, 42.902f };
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<uint32, time_t> lastCoreInInventoryTime; std::unordered_map<ObjectGuid, time_t> lastCoreInInventoryTime;
bool IsMainTankInSameSubgroup(Player* bot) bool IsMainTankInSameSubgroup(PlayerbotAI* botAI, Player* bot)
{ {
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
if (!group || !group->isRaidGroup()) if (!group || !group->isRaidGroup())
@ -210,11 +198,8 @@ namespace SerpentShrineCavernHelpers
if (group->GetMemberGroup(member->GetGUID()) != botSubGroup) if (group->GetMemberGroup(member->GetGUID()) != botSubGroup)
continue; continue;
if (PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member)) if (botAI->IsMainTank(member))
{ return true;
if (memberAI->IsMainTank(member))
return true;
}
} }
return false; return false;
@ -277,38 +262,9 @@ namespace SerpentShrineCavernHelpers
return false; return false;
} }
bool AnyRecentCoreInInventory(Group* group, PlayerbotAI* botAI, uint32 graceSeconds) Player* GetDesignatedCoreLooter(PlayerbotAI* botAI, Player* bot)
{
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;
@ -317,10 +273,15 @@ 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;
Player* fallback = leader; // Priority: (1) assistant melee DPS, (2) other melee DPS, (3) any ranged DPS
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();
@ -331,22 +292,36 @@ namespace SerpentShrineCavernHelpers
if (!memberAI) if (!memberAI)
continue; continue;
if (memberAI->IsMelee(member) && memberAI->IsDps(member)) if (!meleeDpsAssistant && memberAI->IsMelee(member) &&
return member; memberAI->IsDps(member) && group->IsAssistant(member->GetGUID()))
{
meleeDpsAssistant = member;
break;
}
if (!fallback && memberAI->IsRangedDps(member)) if (!meleeDps && memberAI->IsMelee(member) && memberAI->IsDps(member))
fallback = member; meleeDps = member;
if (!rangedDps && memberAI->IsRangedDps(member))
rangedDps = member;
} }
return fallback ? fallback : leader; if (meleeDpsAssistant)
return meleeDpsAssistant;
if (meleeDps)
return meleeDps;
if (rangedDps)
return rangedDps;
return leader;
} }
Player* GetFirstTaintedCorePasser(Group* group, PlayerbotAI* botAI) Player* GetFirstTaintedCorePasser(PlayerbotAI* botAI, Player* bot)
{ {
Group* group = bot->GetGroup();
if (!group) if (!group)
return nullptr; return nullptr;
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI); Player* designatedLooter = GetDesignatedCoreLooter(botAI, bot);
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
@ -355,32 +330,29 @@ namespace SerpentShrineCavernHelpers
continue; continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (!memberAI) if (memberAI && memberAI->IsAssistHealOfIndex(member, 0, true))
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(Group* group, PlayerbotAI* botAI) Player* GetSecondTaintedCorePasser(PlayerbotAI* botAI, Player* bot)
{ {
Group* group = bot->GetGroup();
if (!group) if (!group)
return nullptr; return nullptr;
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI); Player* designatedLooter = GetDesignatedCoreLooter(botAI, bot);
Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI); Player* firstCorePasser = GetFirstTaintedCorePasser(botAI, bot);
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
@ -390,34 +362,31 @@ namespace SerpentShrineCavernHelpers
continue; continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (!memberAI) if (memberAI && memberAI->IsAssistHealOfIndex(member, 1, true))
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(Group* group, PlayerbotAI* botAI) Player* GetThirdTaintedCorePasser(PlayerbotAI* botAI, Player* bot)
{ {
Group* group = bot->GetGroup();
if (!group) if (!group)
return nullptr; return nullptr;
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI); Player* designatedLooter = GetDesignatedCoreLooter(botAI, bot);
Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI); Player* firstCorePasser = GetFirstTaintedCorePasser(botAI, bot);
Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI); Player* secondCorePasser = GetSecondTaintedCorePasser(botAI, bot);
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
@ -427,35 +396,32 @@ namespace SerpentShrineCavernHelpers
continue; continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (!memberAI) if (memberAI && memberAI->IsAssistHealOfIndex(member, 2, true))
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(Group* group, PlayerbotAI* botAI) Player* GetFourthTaintedCorePasser(PlayerbotAI* botAI, Player* bot)
{ {
Group* group = bot->GetGroup();
if (!group) if (!group)
return nullptr; return nullptr;
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI); Player* designatedLooter = GetDesignatedCoreLooter(botAI, bot);
Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI); Player* firstCorePasser = GetFirstTaintedCorePasser(botAI, bot);
Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI); Player* secondCorePasser = GetSecondTaintedCorePasser(botAI, bot);
Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI); Player* thirdCorePasser = GetThirdTaintedCorePasser(botAI, bot);
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
@ -466,27 +432,75 @@ namespace SerpentShrineCavernHelpers
continue; continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (!memberAI) if (memberAI && memberAI->IsAssistRangedDpsOfIndex(member, 0, true))
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
@ -510,10 +524,7 @@ namespace SerpentShrineCavernHelpers
continue; continue;
GameObject* go = bounds.first->second; GameObject* go = bounds.first->second;
if (!go) if (!go || go->GetGoState() != GO_STATE_READY)
continue;
if (go->GetGoState() != GO_STATE_READY)
continue; continue;
GeneratorInfo info; GeneratorInfo info;
@ -529,7 +540,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 depawn after use // which despawn after use
Unit* GetNearestActiveShieldGeneratorTriggerByEntry(Unit* reference) Unit* GetNearestActiveShieldGeneratorTriggerByEntry(Unit* reference)
{ {
if (!reference) if (!reference)

View File

@ -1,3 +1,8 @@
/*
* 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_
@ -64,9 +69,6 @@ namespace SerpentShrineCavernHelpers
// Warlock // Warlock
SPELL_CURSE_OF_EXHAUSTION = 18223, SPELL_CURSE_OF_EXHAUSTION = 18223,
// Item
SPELL_HEAVY_NETHERWEAVE_NET = 31368,
}; };
enum SerpentShrineCavernNPCs enum SerpentShrineCavernNPCs
@ -105,9 +107,6 @@ 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;
@ -134,10 +133,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(PlayerbotAI* botAI); Unit* GetLeotherasHuman(Player* bot);
Unit* GetPhase2LeotherasDemon(PlayerbotAI* botAI); Unit* GetPhase2LeotherasDemon(Player* bot);
Unit* GetPhase3LeotherasDemon(PlayerbotAI* botAI); Unit* GetPhase3LeotherasDemon(Player* bot);
Unit* GetActiveLeotherasDemon(PlayerbotAI* botAI); Unit* GetActiveLeotherasDemon(Player* bot);
Player* GetLeotherasDemonFormTank(Player* bot); Player* GetLeotherasDemonFormTank(Player* bot);
// Fathom-Lord Karathress // Fathom-Lord Karathress
@ -158,25 +157,26 @@ 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_Z = 42.985f; constexpr float VASHJ_PLATFORM_CENTER_Z = 42.902f;
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<uint32, time_t> lastCoreInInventoryTime; extern std::unordered_map<ObjectGuid, time_t> lastCoreInInventoryTime;
bool IsMainTankInSameSubgroup(Player* bot); bool IsMainTankInSameSubgroup(PlayerbotAI* botAI, 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);
bool AnyRecentCoreInInventory(Group* group, PlayerbotAI* botAI, uint32 graceSeconds = 3); Player* GetDesignatedCoreLooter(PlayerbotAI* botAI, Player* bot);
Player* GetDesignatedCoreLooter(Group* group, PlayerbotAI* botAI); Player* GetFirstTaintedCorePasser(PlayerbotAI* botAI, Player* bot);
Player* GetFirstTaintedCorePasser(Group* group, PlayerbotAI* botAI); Player* GetSecondTaintedCorePasser(PlayerbotAI* botAI, Player* bot);
Player* GetSecondTaintedCorePasser(Group* group, PlayerbotAI* botAI); Player* GetThirdTaintedCorePasser(PlayerbotAI* botAI, Player* bot);
Player* GetThirdTaintedCorePasser(Group* group, PlayerbotAI* botAI); Player* GetFourthTaintedCorePasser(PlayerbotAI* botAI, Player* bot);
Player* GetFourthTaintedCorePasser(Group* group, PlayerbotAI* botAI); std::array<Player*, 5> GetCoreHandlers(PlayerbotAI* botAI, Player* bot);
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 reinterpret_cast<PlayerbotAI*>(itr->second); return dynamic_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 reinterpret_cast<PlayerbotMgr*>(itr->second); return dynamic_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 = reinterpret_cast<Creature*>(victim); Creature* creature = dynamic_cast<Creature*>(victim);
if (!creature) if (!creature)
return; return;

View File

@ -1753,190 +1753,75 @@ 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;
uint32 pathId = data->moTransport.taxiPathId;
float moveSpeed = data->moTransport.moveSpeed;
if (pathId >= sTaxiPathNodesByPath.size())
continue;
TaxiPathNodeList const& path = sTaxiPathNodesByPath[pathId];
// Keep only transports with taxi paths (boats/zeppelins).
if (path.empty())
continue;
std::vector<WorldPosition> ppath;
TravelNode* prevNode = nullptr;
// Loop over the path and connect stop locations.
for (auto& p : path)
{ {
TransportAnimation const* animation = sTransportMgr->GetTransportAnimInfo(itr.first); WorldPosition pos = WorldPosition(p->mapid, p->x, p->y, p->z, 0);
uint32 pathId = data->moTransport.taxiPathId; if (prevNode)
float moveSpeed = data->moTransport.moveSpeed; ppath.push_back(pos);
if (pathId >= sTaxiPathNodesByPath.size())
continue;
TaxiPathNodeList const& path = sTaxiPathNodesByPath[pathId]; if (p->delay > 0)
std::vector<WorldPosition> ppath;
TravelNode* prevNode = nullptr;
// Elevators/Trams
if (path.empty())
{ {
if (animation) TravelNode* node = TravelNodeMap::instance().addNode(pos, data->name, true, true, true, itr.first);
if (!prevNode)
{ {
TransportPathContainer aPath = animation->Path; ppath.push_back(pos);
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
else // Boats/Zepelins
{
// Loop over the path and connect stop locations.
for (auto& p : path)
{ {
WorldPosition pos = WorldPosition(p->mapid, p->x, p->y, p->z, 0); TravelNodePath travelPath(0.1f, 0.0, (uint8)TravelNodePathType::transport, itr.first, true);
travelPath.setPathAndCost(ppath, moveSpeed);
// if (data->displayId == 3015) node->setPathTo(prevNode, travelPath);
// pos.setZ(pos.getZ() + 6.0f); ppath.clear();
// else if (data->displayId == 3031) ppath.push_back(pos);
// pos.setZ(pos.getZ() - 17.0f);
if (prevNode)
{
ppath.push_back(pos);
}
if (p->delay > 0)
{
TravelNode* node = TravelNodeMap::instance().addNode(pos, data->name, true, true, true, itr.first);
if (!prevNode)
{
ppath.push_back(pos);
}
else
{
TravelNodePath travelPath(0.1f, 0.0, (uint8)TravelNodePathType::transport, itr.first, true);
travelPath.setPathAndCost(ppath, moveSpeed);
node->setPathTo(prevNode, travelPath);
ppath.clear();
ppath.push_back(pos);
}
prevNode = node;
}
} }
if (prevNode) prevNode = node;
{
// Continue from start until first stop and connect to end.
for (auto& p : path)
{
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);
if (p->delay > 0)
{
TravelNode* node = TravelNodeMap::instance().getNode(pos, nullptr, 5.0f);
if (node != prevNode)
{
TravelNodePath travelPath(0.1f, 0.0, (uint8)TravelNodePathType::transport, itr.first,
true);
travelPath.setPathAndCost(ppath, moveSpeed);
node->setPathTo(prevNode, travelPath);
}
}
}
}
ppath.clear();
} }
} }
if (!prevNode)
continue;
// Continue from start until first stop and connect to end.
for (auto& p : path)
{
WorldPosition pos = WorldPosition(p->mapid, p->x, p->y, p->z, 0);
ppath.push_back(pos);
if (p->delay > 0)
{
TravelNode* node = TravelNodeMap::instance().getNode(pos, nullptr, 5.0f);
if (node != prevNode)
{
TravelNodePath travelPath(0.1f, 0.0, (uint8)TravelNodePathType::transport, itr.first, true);
travelPath.setPathAndCost(ppath, moveSpeed);
node->setPathTo(prevNode, travelPath);
}
}
}
ppath.clear();
} }
} }

View File

@ -52,15 +52,14 @@ public:
PlayerbotsSecureLoginServerScript() PlayerbotsSecureLoginServerScript()
: ServerScript("PlayerbotsSecureLoginServerScript", { SERVERHOOK_CAN_PACKET_RECEIVE }) {} : ServerScript("PlayerbotsSecureLoginServerScript", { SERVERHOOK_CAN_PACKET_RECEIVE }) {}
bool CanPacketReceive(WorldSession* /*session*/, WorldPacket& packet) override bool CanPacketReceive(WorldSession* /*session*/, WorldPacket const& packet) override
{ {
if (packet.GetOpcode() != CMSG_PLAYER_LOGIN) if (packet.GetOpcode() != CMSG_PLAYER_LOGIN)
return true; return true;
auto const oldPos = packet.rpos(); WorldPacket pkt(packet);
ObjectGuid loginGuid; ObjectGuid loginGuid;
packet >> loginGuid; pkt >> loginGuid;
packet.rpos(oldPos);
if (!loginGuid) if (!loginGuid)
return true; return true;