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
<!-- 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?
- [ ] No, not at all
- [ ] Minimal impact (**explain below**)
- [ ] Moderate impact (**explain below**)
- - [ ] No, not at all
- - [ ] Minimal impact (**explain below**)
- - [ ] Moderate impact (**explain below**)
- Does this change modify default bot behavior?
- [ ] No
- [ ] Yes (**explain why**)
- - [ ] No
- - [ ] Yes (**explain why**)
- Does this change add new decision branches or increase maintenance complexity?
- [ ] No
- [ ] Yes (**explain below**)
- - [ ] No
- - [ ] 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.
-->
Does this change add bot messages to translate?
- [ ] No
- [ ] Yes (**list messages in the table**)
- - [ ] No
- - [ ] Yes (**list messages in the table**)
| 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.
-->
Was AI assistance used while working on this change?
- [ ] No
- [ ] Yes (**explain below**)
- - [ ] No
- - [ ] Yes (**explain below**)
<!--
If yes, please specify:
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code generation).
@ -94,10 +94,10 @@ If yes, please specify:
## Final Checklist
- [ ] Stability is not compromised.
- [ ] Performance impact is understood, tested, and acceptable.
- [ ] Added logic complexity is justified and explained.
- [ ] Documentation updated if needed (Conf comments, WiKi commands).
- - [ ] Stability is not compromised.
- - [ ] Performance impact is understood, tested, and acceptable.
- - [ ] Added logic complexity is justified and explained.
- - [ ] Documentation updated if needed (Conf comments, WiKi commands).
## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request. -->

View File

@ -5,18 +5,211 @@
#include "FollowActions.h"
#include <algorithm>
#include <cmath>
#include <array>
#include "Event.h"
#include "Formations.h"
#include "LastMovementValue.h"
#include "MotionMaster.h"
#include "PlayerbotAI.h"
#include "Playerbots.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*/)
{
Formation* formation = AI_VALUE(Formation*, "formation");
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;
if (!target.empty())
{

View File

@ -37,10 +37,10 @@ public:
uint32 GetCurrWaypoint() override;
};
class GrobblulusMoveCenterAction : public MoveInsideAction
class GrobbulusMoveCenterAction : public MoveInsideAction
{
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
@ -173,26 +173,26 @@ private:
RazuviousBossHelper helper;
};
class HorsemanAttractAlternativelyAction : public AttackAction
class FourHorsemenAttractAlternativelyAction : public AttackAction
{
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;
protected:
FourhorsemanBossHelper helper;
FourHorsemenBossHelper helper;
};
class HorsemanAttactInOrderAction : public AttackAction
class FourHorsemenAttackInOrderAction : public AttackAction
{
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;
protected:
FourhorsemanBossHelper helper;
FourHorsemenBossHelper helper;
};
// class SapphironGroundMainTankPositionAction : public MovementAction

View File

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

View File

@ -2,7 +2,7 @@
#include "Playerbots.h"
bool HorsemanAttractAlternativelyAction::Execute(Event /*event*/)
bool FourHorsemenAttractAlternativelyAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
@ -13,13 +13,13 @@ bool HorsemanAttractAlternativelyAction::Execute(Event /*event*/)
return true;
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 false;
}
bool HorsemanAttactInOrderAction::Execute(Event /*event*/)
bool FourHorsemenAttackInOrderAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -30,8 +30,8 @@ public:
creators["razuvious tank"] = &RaidNaxxTriggerContext::razuvious_tank;
creators["razuvious nontank"] = &RaidNaxxTriggerContext::razuvious_nontank;
creators["horseman attractors"] = &RaidNaxxTriggerContext::horseman_attractors;
creators["horseman except attractors"] = &RaidNaxxTriggerContext::horseman_except_attractors;
creators["four horsemen attractors"] = &RaidNaxxTriggerContext::four_horsemen_attractors;
creators["four horsemen except attractors"] = &RaidNaxxTriggerContext::four_horsemen_except_attractors;
creators["sapphiron ground"] = &RaidNaxxTriggerContext::sapphiron_ground;
creators["sapphiron flight"] = &RaidNaxxTriggerContext::sapphiron_flight;
@ -66,8 +66,8 @@ private:
static Trigger* razuvious_tank(PlayerbotAI* ai) { return new RazuviousTankTrigger(ai); }
static Trigger* razuvious_nontank(PlayerbotAI* ai) { return new RazuviousNontankTrigger(ai); }
static Trigger* horseman_attractors(PlayerbotAI* ai) { return new HorsemanAttractorsTrigger(ai); }
static Trigger* horseman_except_attractors(PlayerbotAI* ai) { return new HorsemanExceptAttractorsTrigger(ai); }
static Trigger* four_horsemen_attractors(PlayerbotAI* ai) { return new FourHorsemenAttractorsTrigger(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_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) }
));
// four horseman
triggers.push_back(new TriggerNode("horseman attractors",
{ NextAction("horseman attract alternatively", ACTION_RAID + 1) }
// four horsemen
triggers.push_back(new TriggerNode("four horsemen attractors",
{ NextAction("four horsemen attract alternatively", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("horseman except attractors",
{ NextAction("horseman attack in order", ACTION_RAID + 1) }
triggers.push_back(new TriggerNode("four horsemen except attractors",
{ NextAction("four horsemen attack in order", ACTION_RAID + 1) }
));
// sapphiron
@ -150,7 +150,7 @@ void RaidNaxxStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
multipliers.push_back(new InstructorRazuviousGenericMultiplier(botAI));
multipliers.push_back(new KelthuzadGenericMultiplier(botAI));
multipliers.push_back(new AnubrekhanGenericMultiplier(botAI));
multipliers.push_back(new FourhorsemanGenericMultiplier(botAI));
multipliers.push_back(new FourHorsemenGenericMultiplier(botAI));
// multipliers.push_back(new GothikGenericMultiplier(botAI));
multipliers.push_back(new GluthGenericMultiplier(botAI));
}

View File

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

View File

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

View File

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

View File

@ -58,6 +58,7 @@ namespace NaxxSpellIds
// Sapphiron
static constexpr uint32 Icebolt10 = 28522;
static constexpr uint32 Icebolt25 = 28526;
static constexpr uint32 Chill10 = 28547;
static constexpr uint32 Chill25 = 55699;
/*
// 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
#define _PLAYERBOT_RAIDSSCACTIONS_H
@ -75,8 +80,8 @@ public:
bool Execute(Event event) override;
private:
bool TryMisdirectToFrostTank(Unit* hydross, Group* group);
bool TryMisdirectToNatureTank(Unit* hydross, Group* group);
bool TryMisdirectToFrostTank(Unit* hydross);
bool TryMisdirectToNatureTank(Unit* hydross);
};
class HydrossTheUnstableStopDpsUponPhaseChangeAction : public Action
@ -413,10 +418,10 @@ private:
bool LineUpSecondCorePasser(Player* firstCorePasser, Unit* closestTrigger);
bool LineUpThirdCorePasser(Player* designatedLooter, Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger);
bool LineUpFourthCorePasser(Player* firstCorePasser, Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger);
bool IsFirstCorePasserInIntendedPosition(Player* designatedLooter, Player* firstCorePasser, Unit* closestTrigger);
bool IsSecondCorePasserInIntendedPosition(Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger);
bool IsThirdCorePasserInIntendedPosition(Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger);
bool IsFourthCorePasserInIntendedPosition(Player* thirdCorePasser, Player* fourthCorePasser, Unit* closestTrigger);
bool IsFirstCorePasserInPosition(Player* designatedLooter, Player* firstCorePasser, Unit* closestTrigger);
bool IsSecondCorePasserInPosition(Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger);
bool IsThirdCorePasserInPosition(Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger);
bool IsFourthCorePasserInPosition(Player* thirdCorePasser, Player* fourthCorePasser, Unit* closestTrigger);
void ScheduleTransferCoreAfterImbue(PlayerbotAI* botAI, Player* giver, Player* receiver);
bool UseCoreOnNearestGenerator(const uint32 instanceId);
};
@ -428,19 +433,12 @@ public:
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
{
public:
LadyVashjAvoidToxicSporesAction(PlayerbotAI* botAI, std::string const name = "lady vashj avoid toxic spores") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
static std::vector<Unit*> GetAllSporeDropTriggers(PlayerbotAI* botAI, Player* bot);
static std::vector<Unit*> GetAllSporeDropTriggers(Player* bot);
private:
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 "RaidSSCActions.h"
#include "RaidSSCHelpers.h"
@ -28,12 +33,10 @@ using namespace SerpentShrineCavernHelpers;
float UnderbogColossusEscapeToxicPoolMultiplier::GetValue(Action* action)
{
if (bot->HasAura(SPELL_TOXIC_POOL))
{
if (dynamic_cast<MovementAction*>(action) &&
if (bot->HasAura(SPELL_TOXIC_POOL) &&
dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<UnderbogColossusEscapeToxicPoolAction*>(action))
return 0.0f;
}
return 1.0f;
}
@ -53,16 +56,16 @@ float HydrossTheUnstableDisableTankActionsMultiplier::GetValue(Action* action)
dynamic_cast<CombatFormationMoveAction*>(action))
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) ||
dynamic_cast<ReachTargetAction*>(action) ||
(dynamic_cast<AttackAction*>(action) &&
!dynamic_cast<HydrossTheUnstablePositionFrostTankAction*>(action) &&
!dynamic_cast<HydrossTheUnstablePositionNatureTankAction*>(action)))
{
if ((botAI->IsMainTank(bot) && hydross->HasAura(SPELL_CORRUPTION)) ||
(botAI->IsAssistTankOfIndex(bot, 0, true) && !hydross->HasAura(SPELL_CORRUPTION)))
return 0.0f;
}
return 1.0f;
}
@ -97,14 +100,14 @@ float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action)
bool aboutToChange = (itPhase != hydrossChangeToFrostPhaseTimer.end() &&
(now - itPhase->second) > phaseChangeWaitSeconds);
if (justChanged || aboutToChange)
{
if (!justChanged && !aboutToChange)
return 1.0f;
if (dynamic_cast<AttackAction*>(action) ||
(dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
}
}
if (hydross->HasAura(SPELL_CORRUPTION) && !botAI->IsAssistTankOfIndex(bot, 0, true))
{
@ -116,14 +119,14 @@ float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action)
bool aboutToChange = (itPhase != hydrossChangeToNaturePhaseTimer.end() &&
(now - itPhase->second) > phaseChangeWaitSeconds);
if (justChanged || aboutToChange)
{
if (!justChanged && !aboutToChange)
return 1.0f;
if (dynamic_cast<AttackAction*>(action) ||
(dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
}
}
return 1.0f;
}
@ -133,11 +136,9 @@ float HydrossTheUnstableControlMisdirectionMultiplier::GetValue(Action* action)
if (bot->getClass() != CLASS_HUNTER)
return 1.0f;
if (AI_VALUE2(Unit*, "find target", "hydross the unstable"))
{
if (dynamic_cast<CastMisdirectionOnMainTankAction*>(action))
if (AI_VALUE2(Unit*, "find target", "hydross the unstable") &&
dynamic_cast<CastMisdirectionOnMainTankAction*>(action))
return 0.0f;
}
return 1.0f;
}
@ -175,14 +176,14 @@ float TheLurkerBelowMaintainRangedSpreadMultiplier::GetValue(Action* action)
if (!botAI->IsRanged(bot))
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) ||
dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action))
return 0.0f;
}
return 1.0f;
}
@ -215,11 +216,11 @@ float TheLurkerBelowDisableTankAssistMultiplier::GetValue(Action* action)
++tankCount;
}
if (tankCount >= 3)
{
if (tankCount < 3)
return 1.0f;
if (dynamic_cast<TankAssistAction*>(action))
return 0.0f;
}
return 1.0f;
}
@ -234,14 +235,11 @@ float LeotherasTheBlindAvoidWhirlwindMultiplier::GetValue(Action* action)
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return 1.0f;
Unit* leotherasHuman = GetLeotherasHuman(botAI);
if (!leotherasHuman)
Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind");
if (!leotheras || (!leotheras->HasAura(SPELL_WHIRLWIND) &&
!leotheras->HasAura(SPELL_WHIRLWIND_CHANNEL)))
return 1.0f;
if (!leotherasHuman->HasAura(SPELL_LEOTHERAS_BANISHED) &&
(leotherasHuman->HasAura(SPELL_WHIRLWIND) ||
leotherasHuman->HasAura(SPELL_WHIRLWIND_CHANNEL)))
{
if (dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
@ -249,7 +247,6 @@ float LeotherasTheBlindAvoidWhirlwindMultiplier::GetValue(Action* action)
!dynamic_cast<AttackAction*>(action) &&
!dynamic_cast<LeotherasTheBlindRunAwayFromWhirlwindAction*>(action))
return 0.0f;
}
return 1.0f;
}
@ -262,10 +259,10 @@ float LeotherasTheBlindDisableTankActionsMultiplier::GetValue(Action* action)
if (!AI_VALUE2(Unit*, "find target", "leotheras the blind"))
return 1.0f;
if (GetPhase2LeotherasDemon(botAI) && dynamic_cast<AttackAction*>(action))
if (GetPhase2LeotherasDemon(bot) && dynamic_cast<AttackAction*>(action))
return 0.0f;
if (!GetPhase3LeotherasDemon(botAI) && dynamic_cast<CastBerserkAction*>(action))
if (!GetPhase3LeotherasDemon(bot) && dynamic_cast<CastBerserkAction*>(action))
return 0.0f;
return 1.0f;
@ -273,8 +270,9 @@ float LeotherasTheBlindDisableTankActionsMultiplier::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) ||
dynamic_cast<CastHealingSpellAction*>(action) ||
@ -287,7 +285,6 @@ float LeotherasTheBlindFocusOnInnerDemonMultiplier::GetValue(Action* action)
dynamic_cast<CastDireBearFormAction*>(action) ||
dynamic_cast<CastTreeFormAction*>(action))
return 0.0f;
}
return 1.0f;
}
@ -297,19 +294,19 @@ float LeotherasTheBlindMeleeDpsAvoidChaosBlastMultiplier::GetValue(Action* actio
if (botAI->IsRanged(bot) || botAI->IsTank(bot))
return 1.0f;
if (!GetPhase2LeotherasDemon(botAI))
if (!GetPhase2LeotherasDemon(bot))
return 1.0f;
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) ||
dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<CastReachTargetSpellAction*>(action) ||
dynamic_cast<CastKillingSpreeAction*>(action))
return 0.0f;
}
return 1.0f;
}
@ -330,8 +327,8 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action)
const time_t now = std::time(nullptr);
constexpr uint8 dpsWaitSecondsPhase1 = 5;
Unit* leotherasHuman = GetLeotherasHuman(botAI);
Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI);
Unit* leotherasHuman = GetLeotherasHuman(bot);
Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(bot);
if (leotherasHuman && !leotherasHuman->HasAura(SPELL_LEOTHERAS_BANISHED) &&
!leotherasPhase3Demon)
{
@ -350,7 +347,7 @@ float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action)
}
constexpr uint8 dpsWaitSecondsPhase2 = 12;
Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI);
Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(bot);
Player* demonFormTank = GetLeotherasDemonFormTank(bot);
if (leotherasPhase2Demon)
{
@ -398,12 +395,12 @@ float LeotherasTheBlindDelayBloodlustAndHeroismMultiplier::GetValue(Action* acti
return 1.0f;
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))
return 0.0f;
}
return 1.0f;
}
@ -447,14 +444,12 @@ float FathomLordKarathressDisableAoeMultiplier::GetValue(Action* action)
if (!botAI->IsDps(bot))
return 1.0f;
if (AI_VALUE2(Unit*, "find target", "fathom-lord karathress"))
{
if (auto castSpellAction = dynamic_cast<CastSpellAction*>(action))
{
if (castSpellAction->getThreatType() == Action::ActionThreatType::Aoe)
if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress"))
return 1.0f;
auto castSpellAction = dynamic_cast<CastSpellAction*>(action);
if (castSpellAction && castSpellAction->getThreatType() == Action::ActionThreatType::Aoe)
return 0.0f;
}
}
return 1.0f;
}
@ -464,11 +459,11 @@ float FathomLordKarathressControlMisdirectionMultiplier::GetValue(Action* action
if (bot->getClass() != CLASS_HUNTER)
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;
}
return 1.0f;
}
@ -505,12 +500,12 @@ float FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier::GetValue
if (!botAI->IsAssistHealOfIndex(bot, 0, true))
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))
return 0.0f;
}
return 1.0f;
}
@ -526,12 +521,12 @@ float MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier::GetValue(Action* act
if (!AI_VALUE2(Unit*, "find target", "morogrim tidewalker"))
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))
return 0.0f;
}
return 1.0f;
}
@ -541,11 +536,11 @@ float MorogrimTidewalkerDisableTankActionsMultiplier::GetValue(Action* action)
if (!botAI->IsMainTank(bot))
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;
}
return 1.0f;
}
@ -556,17 +551,14 @@ float MorogrimTidewalkerMaintainPhase2StackingMultiplier::GetValue(Action* actio
return 1.0f;
Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker");
if (!tidewalker)
if (!tidewalker || tidewalker->GetHealthPct() > 25.0f)
return 1.0f;
if (tidewalker->GetHealthPct() < 25.0f)
{
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action))
return 0.0f;
}
return 1.0f;
}
@ -580,18 +572,15 @@ float LadyVashjDelayCooldownsMultiplier::GetValue(Action* action)
if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
return 1.0f;
if (bot->getClass() == CLASS_SHAMAN)
{
if (IsLadyVashjInPhase3(botAI))
if (bot->getClass() == CLASS_SHAMAN &&
!IsLadyVashjInPhase3(botAI) &&
(dynamic_cast<CastBloodlustAction*>(action) ||
dynamic_cast<CastHeroismAction*>(action)))
return 0.0f;
if (!botAI->IsDps(bot) || !IsLadyVashjInPhase1(botAI))
return 1.0f;
if (dynamic_cast<CastBloodlustAction*>(action) ||
dynamic_cast<CastHeroismAction*>(action))
return 0.0f;
}
if (botAI->IsDps(bot) && IsLadyVashjInPhase1(botAI))
{
if (dynamic_cast<CastMetamorphosisAction*>(action) ||
dynamic_cast<CastAdrenalineRushAction*>(action) ||
dynamic_cast<CastBladeFlurryAction*>(action) ||
@ -614,8 +603,29 @@ float LadyVashjDelayCooldownsMultiplier::GetValue(Action* action)
dynamic_cast<CastBloodFuryAction*>(action) ||
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;
}
@ -624,15 +634,15 @@ float LadyVashjMaintainPhase1RangedSpreadMultiplier::GetValue(Action* action)
if (!botAI->IsRanged(bot))
return 1.0f;
if (AI_VALUE2(Unit*, "find target", "lady vashj") &&
IsLadyVashjInPhase1(botAI))
{
if (!AI_VALUE2(Unit*, "find target", "lady vashj") ||
!IsLadyVashjInPhase1(botAI))
return 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action))
return 0.0f;
}
return 1.0f;
}
@ -658,19 +668,18 @@ float LadyVashjStaticChargeStayAwayFromGroupMultiplier::GetValue(Action* action)
// Bots should not loot the core with normal looting logic
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;
}
return 1.0f;
}
float LadyVashjCorePassersPrioritizePositioningMultiplier::GetValue(Action* action)
{
if (!AI_VALUE2(Unit*, "find target", "lady vashj") ||
!IsLadyVashjInPhase2(botAI))
if (!AI_VALUE2(Unit*, "find target", "lady vashj") || !IsLadyVashjInPhase2(botAI))
return 1.0f;
if (dynamic_cast<WipeAction*>(action) ||
@ -678,65 +687,43 @@ float LadyVashjCorePassersPrioritizePositioningMultiplier::GetValue(Action* acti
dynamic_cast<LadyVashjDestroyTaintedCoreAction*>(action))
return 1.0f;
Group* group = bot->GetGroup();
if (!group)
return 1.0f;
auto coreHandlers = GetCoreHandlers(botAI, bot);
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);
bool isCoreHandler = false;
int myIndex = -1;
for (int i = 0; i < static_cast<int>(coreHandlers.size()); ++i)
{
if (coreHandlers[i] && coreHandlers[i] == bot)
{
isCoreHandler = true;
myIndex = i;
}
}
if (!isCoreHandler)
return 1.0f;
auto hasCore = [](Player* player)
{
return player && player->HasItemCount(ITEM_TAINTED_CORE, 1, false);
};
if (hasCore(bot))
{
if (!dynamic_cast<LadyVashjPassTheTaintedCoreAction*>(action))
// If the bot actually has the core, only allow core handling
if (hasCore(bot) && !dynamic_cast<LadyVashjPassTheTaintedCoreAction*>(action))
return 0.0f;
}
if (bot == designatedLooter)
{
if (!hasCore(bot))
return 1.0f;
}
else if (bot == firstCorePasser)
{
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;
// First and second passers block movement when the looter teleports to the elemental
Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental");
if (tainted && coreHandlers[0]->GetExactDist2d(tainted) < 5.0f &&
(bot == coreHandlers[1] || bot == coreHandlers[2]) &&
(dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<LadyVashjPassTheTaintedCoreAction*>(action)))
return 0.0f;
if (AI_VALUE2(Unit*, "find target", "tainted elemental") &&
(bot == firstCorePasser || bot == secondCorePasser))
{
if (dynamic_cast<MovementAction*>(action) &&
// If any prior handler (including self) recently had the core, block other movement
if (AnyRecentCoreInInventory(botAI, bot) &&
dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<LadyVashjPassTheTaintedCoreAction*>(action))
return 0.0f;
}
if (AnyRecentCoreInInventory(group, botAI))
{
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<LadyVashjPassTheTaintedCoreAction*>(action))
return 0.0f;
}
return 1.0f;
}
@ -745,7 +732,8 @@ float LadyVashjCorePassersPrioritizePositioningMultiplier::GetValue(Action* acti
// So the standard target selection system must be disabled
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;
if (dynamic_cast<AvoidAoeAction*>(action))
@ -755,24 +743,26 @@ float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *ac
{
if (dynamic_cast<DpsAssistAction*>(action) ||
dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<FollowAction*>(action) ||
dynamic_cast<FleeAction*>(action))
return 0.0f;
if (bot->GetExactDist2d(vashj) < 60.0f &&
dynamic_cast<FollowAction*>(action))
return 0.0f;
if (!botAI->IsHeal(bot) && dynamic_cast<CastHealingSpellAction*>(action))
return 0.0f;
Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental");
if (enchanted && bot->GetVictim() == enchanted)
{
if (dynamic_cast<CastDebuffSpellOnAttackerAction*>(action))
if (enchanted && bot->GetVictim() == enchanted &&
dynamic_cast<CastDebuffSpellOnAttackerAction*>(action))
return 0.0f;
}
}
if (IsLadyVashjInPhase3(botAI))
{
if (dynamic_cast<DpsAssistAction*>(action))
if (dynamic_cast<DpsAssistAction*>(action) ||
dynamic_cast<TankAssistAction*>(action))
return 0.0f;
Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental");
@ -780,17 +770,14 @@ float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *ac
Unit* elite = AI_VALUE2(Unit*, "find target", "coilfang elite");
if (enchanted || strider || elite)
{
if (dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<FollowAction*>(action) ||
if (dynamic_cast<FollowAction*>(action) ||
dynamic_cast<FleeAction*>(action))
return 0.0f;
if (enchanted && bot->GetVictim() == enchanted)
{
if (dynamic_cast<CastDebuffSpellOnAttackerAction*>(action))
if (enchanted && bot->GetVictim() == enchanted &&
dynamic_cast<CastDebuffSpellOnAttackerAction*>(action))
return 0.0f;
}
}
else if (dynamic_cast<CombatFormationMoveAction*>(action))
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
#define _PLAYERBOT_RAIDSSCMULTIPLIERS_H
@ -193,6 +198,14 @@ public:
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
{
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
#define _PLAYERBOT_RAIDSSCACTIONCONTEXT_H
@ -161,9 +166,6 @@ public:
creators["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"] =
&RaidSSCActionContext::lady_vashj_avoid_toxic_spores;
@ -324,9 +326,6 @@ private:
static Action* lady_vashj_destroy_tainted_core(
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(
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
#define _PLAYERBOT_RAIDSSCTRIGGERCONTEXT_H
@ -155,9 +160,6 @@ public:
creators["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"] =
&RaidSSCTriggerContext::lady_vashj_toxic_sporebats_are_spewing_poison_clouds;
@ -312,9 +314,6 @@ private:
static Trigger* lady_vashj_tainted_core_is_unusable(
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(
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 "RaidSSCMultipliers.h"
@ -144,9 +149,6 @@ void RaidSSCStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode("lady vashj tainted core is unusable", {
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", {
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>
multipliers.push_back(new LadyVashjDelayCooldownsMultiplier(botAI));
multipliers.push_back(new LadyVashjMainTankGroupShamanUseGroundingTotemMultiplier(botAI));
multipliers.push_back(new LadyVashjMaintainPhase1RangedSpreadMultiplier(botAI));
multipliers.push_back(new LadyVashjStaticChargeStayAwayFromGroupMultiplier(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_
#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 "RaidSSCHelpers.h"
#include "RaidSSCActions.h"
@ -26,35 +31,37 @@ bool UnderbogColossusSpawnedToxicPoolAfterDeathTrigger::IsActive()
bool GreyheartTidecallerWaterElementalTotemSpawnedTrigger::IsActive()
{
return botAI->IsDps(bot) &&
GetFirstAliveUnitByEntry(botAI, NPC_WATER_ELEMENTAL_TOTEM);
AI_VALUE2(Unit*, "find target", "greyheart tidecaller");
}
// Hydross the Unstable <Duke of Currents>
bool HydrossTheUnstableBotIsFrostTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "hydross the unstable") &&
botAI->IsMainTank(bot);
return botAI->IsMainTank(bot) &&
AI_VALUE2(Unit*, "find target", "hydross the unstable");
}
bool HydrossTheUnstableBotIsNatureTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "hydross the unstable") &&
botAI->IsAssistTankOfIndex(bot, 0, true);
return botAI->IsAssistTankOfIndex(bot, 0, true) &&
AI_VALUE2(Unit*, "find target", "hydross the unstable");
}
bool HydrossTheUnstableElementalsSpawnedTrigger::IsActive()
{
if (botAI->IsHeal(bot))
return false;
Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable");
if (hydross && hydross->GetHealthPct() < 10.0f)
if (!hydross || hydross->GetHealthPct() < 10.0f)
return false;
if (!AI_VALUE2(Unit*, "find target", "pure spawn of hydross") &&
!AI_VALUE2(Unit*, "find target", "tainted spawn of hydross"))
if (botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0, true))
return false;
return !botAI->IsHeal(bot) && !botAI->IsMainTank(bot) &&
!botAI->IsAssistTankOfIndex(bot, 0, true);
return AI_VALUE2(Unit*, "find target", "pure spawn of hydross") ||
AI_VALUE2(Unit*, "find target", "tainted spawn of hydross");
}
bool HydrossTheUnstableDangerFromWaterTombsTrigger::IsActive()
@ -71,19 +78,19 @@ bool HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger::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 bot->getClass() != CLASS_HUNTER &&
!botAI->IsHeal(bot) &&
!botAI->IsMainTank(bot) &&
!botAI->IsAssistTankOfIndex(bot, 0, true);
return AI_VALUE2(Unit*, "find target", "hydross the unstable");
}
bool HydrossTheUnstableNeedToManageTimersTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "hydross the unstable") &&
IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr);
return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) &&
AI_VALUE2(Unit*, "find target", "hydross the unstable");
}
// The Lurker Below
@ -102,11 +109,11 @@ bool TheLurkerBelowSpoutIsActiveTrigger::IsActive()
bool TheLurkerBelowBossIsActiveForMainTankTrigger::IsActive()
{
Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below");
if (!lurker)
if (!botAI->IsMainTank(bot))
return false;
if (!botAI->IsMainTank(bot))
Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below");
if (!lurker)
return false;
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
bool TheLurkerBelowBossIsSubmergedTrigger::IsActive()
{
if (!botAI->IsTank(bot))
return false;
Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below");
if (!lurker || lurker->getStandState() != UNIT_STAND_STATE_SUBMERGED)
return false;
Player* mainTank = nullptr;
Player* firstAssistTank = nullptr;
Player* secondAssistTank = nullptr;
Group* group = bot->GetGroup();
if (!group)
return false;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive())
continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (!memberAI)
continue;
if (!mainTank && memberAI->IsMainTank(member))
mainTank = member;
else if (!firstAssistTank && memberAI->IsAssistTankOfIndex(member, 0, true))
firstAssistTank = member;
else if (!secondAssistTank && memberAI->IsAssistTankOfIndex(member, 1, true))
secondAssistTank = member;
}
Player* mainTank = GetGroupMainTank(botAI, bot);
Player* firstAssistTank = GetGroupAssistTank(botAI, bot, 0);
Player* secondAssistTank = GetGroupAssistTank(botAI, bot, 1);
if (!mainTank || !firstAssistTank || !secondAssistTank)
return false;
@ -173,51 +161,55 @@ bool TheLurkerBelowBossIsSubmergedTrigger::IsActive()
bool TheLurkerBelowNeedToPrepareTimerForSpoutTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "the lurker below") &&
IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr);
return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) &&
AI_VALUE2(Unit*, "find target", "the lurker below");
}
// Leotheras the Blind
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()
{
if (bot->getClass() != CLASS_WARLOCK)
return false;
if (!AI_VALUE2(Unit*, "find target", "leotheras the blind"))
return false;
if (GetLeotherasDemonFormTank(bot) != bot)
return false;
return GetActiveLeotherasDemon(botAI);
return GetActiveLeotherasDemon(bot);
}
bool LeotherasTheBlindOnlyWarlockShouldTankDemonFormTrigger::IsActive()
{
if (botAI->IsRanged(bot) || !botAI->IsTank(bot))
if (!botAI->IsTank(bot))
return false;
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
if (!AI_VALUE2(Unit*, "find target", "leotheras the blind"))
return false;
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
if (!GetLeotherasDemonFormTank(bot))
return false;
return GetPhase2LeotherasDemon(botAI);
return GetPhase2LeotherasDemon(bot);
}
bool LeotherasTheBlindBossEngagedByRangedTrigger::IsActive()
{
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
if (!botAI->IsRanged(bot))
return false;
if (!botAI->IsRanged(bot))
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind");
@ -231,14 +223,14 @@ bool LeotherasTheBlindBossEngagedByRangedTrigger::IsActive()
bool LeotherasTheBlindBossChannelingWhirlwindTrigger::IsActive()
{
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
if (botAI->IsTank(bot) && botAI->IsMelee(bot))
if (botAI->IsTank(bot))
return false;
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 leotheras->HasAura(SPELL_WHIRLWIND) ||
@ -247,10 +239,13 @@ bool LeotherasTheBlindBossChannelingWhirlwindTrigger::IsActive()
bool LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger::IsActive()
{
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
if (botAI->IsRanged(bot))
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;
Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST);
@ -260,7 +255,7 @@ bool LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger::IsActive()
if (!GetLeotherasDemonFormTank(bot) && botAI->IsMainTank(bot))
return false;
return GetPhase2LeotherasDemon(botAI);
return GetPhase2LeotherasDemon(bot);
}
bool LeotherasTheBlindInnerDemonHasAwakenedTrigger::IsActive()
@ -271,89 +266,68 @@ bool LeotherasTheBlindInnerDemonHasAwakenedTrigger::IsActive()
bool LeotherasTheBlindEnteredFinalPhaseTrigger::IsActive()
{
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
if (botAI->IsHeal(bot))
return false;
if (GetLeotherasDemonFormTank(bot) == bot)
if (!AI_VALUE2(Unit*, "find target", "leotheras the blind"))
return false;
return GetPhase3LeotherasDemon(botAI) &&
GetLeotherasHuman(botAI);
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
if (bot->getClass() == CLASS_WARLOCK && GetLeotherasDemonFormTank(bot) == bot)
return false;
return GetPhase3LeotherasDemon(bot);
}
bool LeotherasTheBlindDemonFormTankNeedsAggro::IsActive()
{
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
if (bot->getClass() != CLASS_HUNTER)
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()
{
return AI_VALUE2(Unit*, "find target", "leotheras the blind") &&
IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr);
return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) &&
AI_VALUE2(Unit*, "find target", "leotheras the blind");
}
// Fathom-Lord Karathress
bool FathomLordKarathressBossEngagedByMainTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "fathom-lord karathress") &&
botAI->IsMainTank(bot);
return botAI->IsMainTank(bot) &&
AI_VALUE2(Unit*, "find target", "fathom-lord karathress");
}
bool FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "fathom-guard caribdis") &&
botAI->IsAssistTankOfIndex(bot, 0, false);
return botAI->IsAssistTankOfIndex(bot, 0, false) &&
AI_VALUE2(Unit*, "find target", "fathom-guard caribdis");
}
bool FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis") &&
botAI->IsAssistTankOfIndex(bot, 1, false);
return botAI->IsAssistTankOfIndex(bot, 1, false) &&
AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis");
}
bool FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess") &&
botAI->IsAssistTankOfIndex(bot, 2, false);
return botAI->IsAssistTankOfIndex(bot, 2, false) &&
AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess");
}
bool FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger::IsActive()
{
Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis");
if (!caribdis)
return false;
if (!botAI->IsAssistHealOfIndex(bot, 0, true))
return false;
Player* firstAssistTank = nullptr;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive())
continue;
if (botAI->IsAssistTankOfIndex(member, 0, false))
{
firstAssistTank = member;
break;
}
}
}
return firstAssistTank;
return botAI->IsAssistHealOfIndex(bot, 0, true) &&
AI_VALUE2(Unit*, "find target", "fathom-guard caribdis");
}
bool FathomLordKarathressPullingBossesTrigger::IsActive()
@ -367,10 +341,10 @@ bool FathomLordKarathressPullingBossesTrigger::IsActive()
bool FathomLordKarathressDeterminingKillOrderTrigger::IsActive()
{
if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress"))
if (botAI->IsHeal(bot))
return false;
if (botAI->IsHeal(bot))
if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress"))
return false;
if (botAI->IsDps(bot))
@ -387,8 +361,8 @@ bool FathomLordKarathressDeterminingKillOrderTrigger::IsActive()
bool FathomLordKarathressTanksNeedToEstablishAggroTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "fathom-lord karathress") &&
IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr);
return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID) &&
AI_VALUE2(Unit*, "find target", "fathom-lord karathress");
}
// Morogrim Tidewalker
@ -404,8 +378,8 @@ bool MorogrimTidewalkerPullingBossTrigger::IsActive()
bool MorogrimTidewalkerBossEngagedByMainTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "morogrim tidewalker") &&
botAI->IsMainTank(bot);
return botAI->IsMainTank(bot) &&
AI_VALUE2(Unit*, "find target", "morogrim tidewalker");
}
bool MorogrimTidewalkerWaterGlobulesAreIncomingTrigger::IsActive()
@ -421,8 +395,11 @@ bool MorogrimTidewalkerWaterGlobulesAreIncomingTrigger::IsActive()
bool LadyVashjBossEngagedByMainTankTrigger::IsActive()
{
if (!botAI->IsMainTank(bot))
return false;
return AI_VALUE2(Unit*, "find target", "lady vashj") &&
!IsLadyVashjInPhase2(botAI) && botAI->IsMainTank(bot);
!IsLadyVashjInPhase2(botAI);
}
bool LadyVashjBossEngagedByRangedInPhase1Trigger::IsActive()
@ -439,10 +416,7 @@ bool LadyVashjCastsShockBlastOnHighestAggroTrigger::IsActive()
IsLadyVashjInPhase2(botAI))
return false;
if (!IsMainTankInSameSubgroup(bot))
return false;
return true;
return IsMainTankInSameSubgroup(botAI, bot);
}
bool LadyVashjBotHasStaticChargeTrigger::IsActive()
@ -450,15 +424,16 @@ bool LadyVashjBotHasStaticChargeTrigger::IsActive()
if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
return false;
if (Group* group = bot->GetGroup())
{
Group* group = bot->GetGroup();
if (!group)
return false;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->HasAura(SPELL_STATIC_CHARGE))
return true;
}
}
return false;
}
@ -500,9 +475,10 @@ bool LadyVashjTaintedElementalCheatTrigger::IsActive()
return false;
bool taintedPresent = false;
Unit* taintedUnit = AI_VALUE2(Unit*, "find target", "tainted elemental");
if (taintedUnit)
if (AI_VALUE2(Unit*, "find target", "tainted elemental"))
{
taintedPresent = true;
}
else
{
GuidVector corpses = AI_VALUE(GuidVector, "nearest corpses");
@ -513,26 +489,20 @@ bool LadyVashjTaintedElementalCheatTrigger::IsActive()
if (!object)
continue;
if (Creature* creature = object->ToCreature())
{
if (creature->GetEntry() == NPC_TAINTED_ELEMENTAL && !creature->IsAlive())
if (Creature* creature = object->ToCreature();
creature->GetEntry() == NPC_TAINTED_ELEMENTAL && !creature->IsAlive())
{
taintedPresent = true;
break;
}
}
}
}
if (!taintedPresent)
return false;
Group* group = bot->GetGroup();
if (!group)
return false;
return (GetDesignatedCoreLooter(group, botAI) == bot &&
!bot->HasItemCount(ITEM_TAINTED_CORE, 1, false));
return GetDesignatedCoreLooter(botAI, bot) == bot &&
!bot->HasItemCount(ITEM_TAINTED_CORE, 1, false);
}
bool LadyVashjTaintedCoreWasLootedTrigger::IsActive()
@ -540,54 +510,24 @@ bool LadyVashjTaintedCoreWasLootedTrigger::IsActive()
if (!AI_VALUE2(Unit*, "find target", "lady vashj") || !IsLadyVashjInPhase2(botAI))
return false;
Group* group = bot->GetGroup();
if (!group)
return false;
auto coreHandlers = GetCoreHandlers(botAI, bot);
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);
bool isCoreHandler = false;
for (Player* handler : coreHandlers)
if (handler == bot)
isCoreHandler = true;
auto hasCore = [](Player* player) -> bool
{
return player && player->HasItemCount(ITEM_TAINTED_CORE, 1, false);
};
if (bot == designatedLooter)
{
if (!hasCore(bot))
if (!isCoreHandler)
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
if (AI_VALUE2(Unit*, "find target", "tainted elemental") &&
(bot == firstCorePasser || bot == secondCorePasser))
Unit* tainted = AI_VALUE2(Unit*, "find target", "tainted elemental");
if (tainted && coreHandlers[0]->GetExactDist2d(tainted) < 5.0f &&
(bot == coreHandlers[1] || bot == coreHandlers[2]))
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()
@ -599,18 +539,7 @@ bool LadyVashjTaintedCoreIsUnusableTrigger::IsActive()
if (!IsLadyVashjInPhase2(botAI))
return bot->HasItemCount(ITEM_TAINTED_CORE, 1, false);
Group* group = bot->GetGroup();
if (!group)
return false;
Player* coreHandlers[] =
{
GetDesignatedCoreLooter(group, botAI),
GetFirstTaintedCorePasser(group, botAI),
GetSecondTaintedCorePasser(group, botAI),
GetThirdTaintedCorePasser(group, botAI),
GetFourthTaintedCorePasser(group, botAI)
};
auto coreHandlers = GetCoreHandlers(botAI, bot);
if (bot->HasItemCount(ITEM_TAINTED_CORE, 1, false))
{
@ -625,24 +554,6 @@ bool LadyVashjTaintedCoreIsUnusableTrigger::IsActive()
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()
{
return IsLadyVashjInPhase3(botAI);
@ -653,8 +564,10 @@ bool LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger::IsActive()
if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
return false;
if (Group* group = bot->GetGroup())
{
Group* group = bot->GetGroup();
if (!group)
return false;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
@ -664,7 +577,6 @@ bool LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger::IsActive()
if (botAI->IsMelee(member))
return true;
}
}
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
#define _PLAYERBOT_RAIDSSCTRIGGERS_H
@ -387,14 +392,6 @@ public:
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
{
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 "AiFactory.h"
#include "Creature.h"
@ -79,61 +84,54 @@ namespace SerpentShrineCavernHelpers
std::unordered_map<uint32, time_t> leotherasDemonFormDpsWaitTimer;
std::unordered_map<uint32, time_t> leotherasFinalPhaseDpsWaitTimer;
Unit* GetLeotherasHuman(PlayerbotAI* botAI)
Unit* GetLeotherasHuman(Player* bot)
{
auto const& npcs =
botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest hostile npcs")->Get();
for (auto const& guid : npcs)
{
Unit* unit = botAI->GetUnit(guid);
if (unit && unit->GetEntry() == NPC_LEOTHERAS_THE_BLIND &&
unit->IsInCombat() && !unit->HasAura(SPELL_METAMORPHOSIS))
return unit;
}
constexpr float searchRadius = 100.0f;
Creature* leotheras =
bot->FindNearestCreature(NPC_LEOTHERAS_THE_BLIND, searchRadius, true);
if (leotheras && leotheras->IsInCombat() &&
!leotheras->HasAura(SPELL_METAMORPHOSIS))
return leotheras;
return nullptr;
}
Unit* GetPhase2LeotherasDemon(PlayerbotAI* botAI)
Unit* GetPhase2LeotherasDemon(Player* bot)
{
auto const& npcs =
botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest hostile npcs")->Get();
for (auto const& guid : npcs)
{
Unit* unit = botAI->GetUnit(guid);
if (unit && unit->GetEntry() == NPC_LEOTHERAS_THE_BLIND &&
unit->HasAura(SPELL_METAMORPHOSIS))
return unit;
}
constexpr float searchRadius = 100.0f;
Creature* leotheras =
bot->FindNearestCreature(NPC_LEOTHERAS_THE_BLIND, searchRadius, true);
if (leotheras && leotheras->HasAura(SPELL_METAMORPHOSIS))
return leotheras;
return nullptr;
}
Unit* GetPhase3LeotherasDemon(PlayerbotAI* botAI)
Unit* GetPhase3LeotherasDemon(Player* bot)
{
auto const& npcs =
botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest hostile npcs")->Get();
for (auto const& guid : npcs)
{
Unit* unit = botAI->GetUnit(guid);
if (unit && unit->GetEntry() == NPC_SHADOW_OF_LEOTHERAS)
return unit;
}
return nullptr;
constexpr float searchRadius = 100.0f;
return bot->FindNearestCreature(NPC_SHADOW_OF_LEOTHERAS, searchRadius, true);
}
Unit* GetActiveLeotherasDemon(PlayerbotAI* botAI)
Unit* GetActiveLeotherasDemon(Player* bot)
{
Unit* phase2 = GetPhase2LeotherasDemon(botAI);
Unit* phase3 = GetPhase3LeotherasDemon(botAI);
Unit* phase2 = GetPhase2LeotherasDemon(bot);
Unit* phase3 = GetPhase3LeotherasDemon(bot);
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)
{
Group* group = bot->GetGroup();
if (!group)
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())
{
Player* member = ref->GetSource();
@ -142,21 +140,12 @@ namespace SerpentShrineCavernHelpers
if (group->IsAssistant(member->GetGUID()))
return member;
if (!fallbackWarlock && GET_PLAYERBOT_AI(member))
fallbackWarlock = member;
}
// (2) Fall back to first found bot Warlock
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
member->getClass() != CLASS_WARLOCK)
continue;
return member;
}
// (3) Return nullptr if none found
return nullptr;
return fallbackWarlock;
}
// Fathom-Lord Karathress
@ -182,16 +171,15 @@ namespace SerpentShrineCavernHelpers
// 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<uint32, ObjectGuid> nearestTriggerGuid;
std::unordered_map<ObjectGuid, Position> intendedLineup;
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();
if (!group || !group->isRaidGroup())
@ -210,12 +198,9 @@ namespace SerpentShrineCavernHelpers
if (group->GetMemberGroup(member->GetGUID()) != botSubGroup)
continue;
if (PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member))
{
if (memberAI->IsMainTank(member))
if (botAI->IsMainTank(member))
return true;
}
}
return false;
}
@ -277,38 +262,9 @@ namespace SerpentShrineCavernHelpers
return false;
}
bool AnyRecentCoreInInventory(Group* group, PlayerbotAI* botAI, uint32 graceSeconds)
{
Unit* vashj =
botAI->GetAiObjectContext()->GetValue<Unit*>("find target", "lady vashj")->Get();
if (!vashj)
return false;
if (group)
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->HasItemCount(ITEM_TAINTED_CORE, 1, false))
return true;
}
}
const uint32 instanceId = vashj->GetMap()->GetInstanceId();
const time_t now = std::time(nullptr);
auto it = lastCoreInInventoryTime.find(instanceId);
if (it != lastCoreInInventoryTime.end())
{
if ((now - it->second) <= static_cast<time_t>(graceSeconds))
return true;
}
return false;
}
Player* GetDesignatedCoreLooter(Group* group, PlayerbotAI* botAI)
Player* GetDesignatedCoreLooter(PlayerbotAI* botAI, Player* bot)
{
Group* group = bot->GetGroup();
if (!group)
return nullptr;
@ -317,10 +273,15 @@ namespace SerpentShrineCavernHelpers
if (!leaderGuid.IsEmpty())
leader = ObjectAccessor::FindPlayer(leaderGuid);
// If cheats are disabled, the group leader will be the designated looter
if (!botAI->HasCheat(BotCheatMask::raid))
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())
{
Player* member = ref->GetSource();
@ -331,22 +292,36 @@ namespace SerpentShrineCavernHelpers
if (!memberAI)
continue;
if (memberAI->IsMelee(member) && memberAI->IsDps(member))
return member;
if (!fallback && memberAI->IsRangedDps(member))
fallback = member;
}
return fallback ? fallback : leader;
}
Player* GetFirstTaintedCorePasser(Group* group, PlayerbotAI* botAI)
if (!meleeDpsAssistant && memberAI->IsMelee(member) &&
memberAI->IsDps(member) && group->IsAssistant(member->GetGUID()))
{
meleeDpsAssistant = member;
break;
}
if (!meleeDps && memberAI->IsMelee(member) && memberAI->IsDps(member))
meleeDps = member;
if (!rangedDps && memberAI->IsRangedDps(member))
rangedDps = member;
}
if (meleeDpsAssistant)
return meleeDpsAssistant;
if (meleeDps)
return meleeDps;
if (rangedDps)
return rangedDps;
return leader;
}
Player* GetFirstTaintedCorePasser(PlayerbotAI* botAI, Player* bot)
{
Group* group = bot->GetGroup();
if (!group)
return nullptr;
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
Player* designatedLooter = GetDesignatedCoreLooter(botAI, bot);
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
@ -355,32 +330,29 @@ namespace SerpentShrineCavernHelpers
continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (!memberAI)
continue;
if (memberAI->IsAssistHealOfIndex(member, 0, true))
if (memberAI && memberAI->IsAssistHealOfIndex(member, 0, true))
return member;
}
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
botAI->IsTank(member) || member == designatedLooter)
continue;
if (member && member->IsAlive() && GET_PLAYERBOT_AI(member) &&
!botAI->IsTank(member) && member != designatedLooter)
return member;
}
return nullptr;
}
Player* GetSecondTaintedCorePasser(Group* group, PlayerbotAI* botAI)
Player* GetSecondTaintedCorePasser(PlayerbotAI* botAI, Player* bot)
{
Group* group = bot->GetGroup();
if (!group)
return nullptr;
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI);
Player* designatedLooter = GetDesignatedCoreLooter(botAI, bot);
Player* firstCorePasser = GetFirstTaintedCorePasser(botAI, bot);
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
@ -390,34 +362,31 @@ namespace SerpentShrineCavernHelpers
continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (!memberAI)
continue;
if (memberAI->IsAssistHealOfIndex(member, 1, true))
if (memberAI && memberAI->IsAssistHealOfIndex(member, 1, true))
return member;
}
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
botAI->IsTank(member) || member == designatedLooter ||
member == firstCorePasser)
continue;
if (member && member->IsAlive() && GET_PLAYERBOT_AI(member) &&
!botAI->IsTank(member) && member != designatedLooter &&
member != firstCorePasser)
return member;
}
return nullptr;
}
Player* GetThirdTaintedCorePasser(Group* group, PlayerbotAI* botAI)
Player* GetThirdTaintedCorePasser(PlayerbotAI* botAI, Player* bot)
{
Group* group = bot->GetGroup();
if (!group)
return nullptr;
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI);
Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI);
Player* designatedLooter = GetDesignatedCoreLooter(botAI, bot);
Player* firstCorePasser = GetFirstTaintedCorePasser(botAI, bot);
Player* secondCorePasser = GetSecondTaintedCorePasser(botAI, bot);
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
@ -427,35 +396,32 @@ namespace SerpentShrineCavernHelpers
continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (!memberAI)
continue;
if (memberAI->IsAssistHealOfIndex(member, 2, true))
if (memberAI && memberAI->IsAssistHealOfIndex(member, 2, true))
return member;
}
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
botAI->IsTank(member) || member == designatedLooter ||
member == firstCorePasser || member == secondCorePasser)
continue;
if (member && member->IsAlive() && GET_PLAYERBOT_AI(member) &&
!botAI->IsTank(member) && member != designatedLooter &&
member != firstCorePasser && member != secondCorePasser)
return member;
}
return nullptr;
}
Player* GetFourthTaintedCorePasser(Group* group, PlayerbotAI* botAI)
Player* GetFourthTaintedCorePasser(PlayerbotAI* botAI, Player* bot)
{
Group* group = bot->GetGroup();
if (!group)
return nullptr;
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI);
Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI);
Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI);
Player* designatedLooter = GetDesignatedCoreLooter(botAI, bot);
Player* firstCorePasser = GetFirstTaintedCorePasser(botAI, bot);
Player* secondCorePasser = GetSecondTaintedCorePasser(botAI, bot);
Player* thirdCorePasser = GetThirdTaintedCorePasser(botAI, bot);
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
@ -466,27 +432,75 @@ namespace SerpentShrineCavernHelpers
continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (!memberAI)
continue;
if (memberAI->IsAssistRangedDpsOfIndex(member, 0, true))
if (memberAI && memberAI->IsAssistRangedDpsOfIndex(member, 0, true))
return member;
}
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
botAI->IsTank(member) || member == designatedLooter ||
member == firstCorePasser || member == secondCorePasser ||
member == thirdCorePasser)
continue;
if (member && member->IsAlive() && GET_PLAYERBOT_AI(member) &&
!botAI->IsTank(member) && member != designatedLooter &&
member != firstCorePasser && member != secondCorePasser &&
member != thirdCorePasser)
return member;
}
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 =
{
47482, // NW
@ -510,10 +524,7 @@ namespace SerpentShrineCavernHelpers
continue;
GameObject* go = bounds.first->second;
if (!go)
continue;
if (go->GetGoState() != GO_STATE_READY)
if (!go || go->GetGoState() != GO_STATE_READY)
continue;
GeneratorInfo info;
@ -529,7 +540,7 @@ namespace SerpentShrineCavernHelpers
// Returns the nearest active Shield Generator to the bot
// Active generators are powered by NPC_WORLD_INVISIBLE_TRIGGER creatures,
// which depawn after use
// which despawn after use
Unit* GetNearestActiveShieldGeneratorTriggerByEntry(Unit* 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_
#define _PLAYERBOT_RAIDSSCHELPERS_H_
@ -64,9 +69,6 @@ namespace SerpentShrineCavernHelpers
// Warlock
SPELL_CURSE_OF_EXHAUSTION = 18223,
// Item
SPELL_HEAVY_NETHERWEAVE_NET = 31368,
};
enum SerpentShrineCavernNPCs
@ -105,9 +107,6 @@ namespace SerpentShrineCavernHelpers
{
// Lady Vashj <Coilfang Matron>
ITEM_TAINTED_CORE = 31088,
// Tailoring
ITEM_HEAVY_NETHERWEAVE_NET = 24269,
};
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> leotherasDemonFormDpsWaitTimer;
extern std::unordered_map<uint32, time_t> leotherasFinalPhaseDpsWaitTimer;
Unit* GetLeotherasHuman(PlayerbotAI* botAI);
Unit* GetPhase2LeotherasDemon(PlayerbotAI* botAI);
Unit* GetPhase3LeotherasDemon(PlayerbotAI* botAI);
Unit* GetActiveLeotherasDemon(PlayerbotAI* botAI);
Unit* GetLeotherasHuman(Player* bot);
Unit* GetPhase2LeotherasDemon(Player* bot);
Unit* GetPhase3LeotherasDemon(Player* bot);
Unit* GetActiveLeotherasDemon(Player* bot);
Player* GetLeotherasDemonFormTank(Player* bot);
// Fathom-Lord Karathress
@ -158,25 +157,26 @@ namespace SerpentShrineCavernHelpers
extern std::unordered_map<ObjectGuid, uint8> tidewalkerRangedStep;
// 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 std::unordered_map<ObjectGuid, Position> vashjRangedPositions;
extern std::unordered_map<ObjectGuid, bool> hasReachedVashjRangedPosition;
extern std::unordered_map<uint32, ObjectGuid> nearestTriggerGuid;
extern std::unordered_map<ObjectGuid, Position> intendedLineup;
extern std::unordered_map<uint32, time_t> lastImbueAttempt;
extern std::unordered_map<uint32, time_t> lastCoreInInventoryTime;
bool IsMainTankInSameSubgroup(Player* bot);
extern std::unordered_map<ObjectGuid, time_t> lastCoreInInventoryTime;
bool IsMainTankInSameSubgroup(PlayerbotAI* botAI, Player* bot);
bool IsLadyVashjInPhase1(PlayerbotAI* botAI);
bool IsLadyVashjInPhase2(PlayerbotAI* botAI);
bool IsLadyVashjInPhase3(PlayerbotAI* botAI);
bool IsValidLadyVashjCombatNpc(Unit* unit, PlayerbotAI* botAI);
bool AnyRecentCoreInInventory(Group* group, PlayerbotAI* botAI, uint32 graceSeconds = 3);
Player* GetDesignatedCoreLooter(Group* group, PlayerbotAI* botAI);
Player* GetFirstTaintedCorePasser(Group* group, PlayerbotAI* botAI);
Player* GetSecondTaintedCorePasser(Group* group, PlayerbotAI* botAI);
Player* GetThirdTaintedCorePasser(Group* group, PlayerbotAI* botAI);
Player* GetFourthTaintedCorePasser(Group* group, PlayerbotAI* botAI);
Player* GetDesignatedCoreLooter(PlayerbotAI* botAI, Player* bot);
Player* GetFirstTaintedCorePasser(PlayerbotAI* botAI, Player* bot);
Player* GetSecondTaintedCorePasser(PlayerbotAI* botAI, Player* bot);
Player* GetThirdTaintedCorePasser(PlayerbotAI* botAI, Player* bot);
Player* GetFourthTaintedCorePasser(PlayerbotAI* botAI, Player* bot);
std::array<Player*, 5> GetCoreHandlers(PlayerbotAI* botAI, Player* bot);
bool AnyRecentCoreInInventory(PlayerbotAI* botAI, Player* bot);
struct GeneratorInfo { ObjectGuid guid; float x, y, z; };
extern const std::vector<uint32> SHIELD_GENERATOR_DB_GUIDS;
std::vector<GeneratorInfo> GetAllGeneratorInfosByDbGuids(

View File

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

View File

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

View File

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

View File

@ -52,15 +52,14 @@ public:
PlayerbotsSecureLoginServerScript()
: 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)
return true;
auto const oldPos = packet.rpos();
WorldPacket pkt(packet);
ObjectGuid loginGuid;
packet >> loginGuid;
packet.rpos(oldPos);
pkt >> loginGuid;
if (!loginGuid)
return true;