Refactor raid strategy framework (#2069)

# Pull Request

The purposes of this PR are to (1) establish a general raid helper
framework for the benefit of future raid strategies and (2) make some
improvements to problematic areas of the raid strategy code.

List of changes:
1. Added new RaidBossHelpers.cpp and RaidBossHelpers.h files in the Raid
folder.

3. Moved reused helpers from Karazhan, Gruul, and Magtheridon strategies
to the new helper files.
4. Modified the prior function that assigned a DPS bot to store and
erase timers and trackers in associative containers--the function now
includes parameters for mapId (so a bot that is not in the instance will
not be assigned) and for the ability to exclude a bot (useful for
excluding particular important roles, such as a Warlock tank, so they
are not bogged down by these extra tasks at critical moments). I also
renamed it from IsInstanceTimerManager to IsMechanicTrackerBot.
5. Moved all helper files in raid strategies to Util folders (was needed
for ICC, MC, and Ulduar).
6. Renamed and reordered includes of Ulduar files in AiObjectContext.cpp
to match other raid strategies.
a. This initially caused compile errors which made me realize that the
existing code had several problems with missing includes and was
compiling only due to the prior ordering in AiObjectContext.cpp.
Therefore, I added the missing includes to Molten Core, Ulduar, and
Vault of Archavon strategies.
b. Ulduar and Old Kingdom were also using the same constant name for a
spell--the reordering caused a compile error here as well, which just
highlighted an existing problem that was being hidden. I renamed the
constant for Ulduar to fix this, but I think the better approach going
forward would be to use a namespace or enum class. But that is for
another time and probably another person.
7. Several changes with respect to Ulduar files:
a. The position constants and enums for spells and NPCs and such were in
the trigger header file. I did not think that made sense so moved them
to existing helper files.
b. Since the strategy does not use multipliers, I removed all files and
references to multipliers in it.
c. I removed some unneeded includes. I did not do a detailed review to
determine what else could be removed--I just took some out that I could
tell right away were not needed.
d. I renamed the ingame strategy name from "uld" to "ulduar," which I
think is clearer and is still plenty short.
8. Partial refactor of Gruul and Magtheridon strategies:
a. I did not due a full refactoring but made some quick changes to
things I did previously that were rather stupid like repeating
calculations, having useless logic like pointless IsAlive() checks for
creatures already on the hostile references list, and not using the
existing Position class for coordinates.
b. There were a few substantive changes, such as allowing players to
pick Maulgar mage and moonkin tanks with the assistant flag, but a
greater refactoring of the strategies themselves is beyond this PR.
c. I was clearing some containers used for Gruul and Magtheridon
strategies; the methods are now fixed to erase only the applicable keys
so that in the unlikely event that one server has multiple groups
running Gruul or Magtheridon at the same time, there won't be timer or
position tracker conflicts.

## How to Test the Changes

1. Enter any raid instance that has any code impacted by this PR
2. Engage bosses and observe if any strategies are now broken

I personally tested Maulgar, Gruul, and Magtheridon and confirmed that
they still work as intended.

## Complexity & Impact

I do not expect this PR to have any relevant changes to in-game
performance, but I will defer to those more knowledgeable than I if
there are concerns in this area. As I've mentioned before, you can
consider me to be like a person who has taken half an intro C++ course
at best.

## AI Assistance

None beyond autocomplete of repetitive changes.

---------

Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
This commit is contained in:
Crow
2026-02-06 13:55:43 -06:00
committed by GitHub
parent bebac60c51
commit b31bda85ee
43 changed files with 891 additions and 1081 deletions

View File

@@ -2,6 +2,7 @@
#include "RaidGruulsLairHelpers.h"
#include "CreatureAI.h"
#include "Playerbots.h"
#include "RaidBossHelpers.h"
#include "Unit.h"
using namespace GruulsLairHelpers;
@@ -12,6 +13,8 @@ using namespace GruulsLairHelpers;
bool HighKingMaulgarMainTankAttackMaulgarAction::Execute(Event event)
{
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
if (!maulgar)
return false;
MarkTargetWithSquare(bot, maulgar);
SetRtiTarget(botAI, "square", maulgar);
@@ -21,31 +24,20 @@ bool HighKingMaulgarMainTankAttackMaulgarAction::Execute(Event event)
if (maulgar->GetVictim() == bot)
{
const Location& tankPosition = GruulsLairLocations::MaulgarTankPosition;
const Position& position = MAULGAR_TANK_POSITION;
const float maxDistance = 3.0f;
float distanceToTankPosition = bot->GetExactDist2d(tankPosition.x, tankPosition.y);
float distanceToPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
if (distanceToTankPosition > maxDistance)
if (distanceToPosition > maxDistance)
{
float dX = tankPosition.x - bot->GetPositionX();
float dY = tankPosition.y - bot->GetPositionY();
float dist = sqrt(dX * dX + dY * dY);
float moveX = bot->GetPositionX() + (dX / dist) * maxDistance;
float moveY = bot->GetPositionY() + (dY / dist) * maxDistance;
return MoveTo(bot->GetMapId(), moveX, moveY, tankPosition.z, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
float dX = position.GetPositionX() - bot->GetPositionX();
float dY = position.GetPositionY() - bot->GetPositionY();
float moveX = bot->GetPositionX() + (dX / distanceToPosition) * maxDistance;
float moveY = bot->GetPositionY() + (dY / distanceToPosition) * maxDistance;
return MoveTo(GRUULS_LAIR_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, true);
}
float orientation = atan2(maulgar->GetPositionY() - bot->GetPositionY(),
maulgar->GetPositionX() - bot->GetPositionX());
bot->SetFacingTo(orientation);
}
else if (!bot->IsWithinMeleeRange(maulgar))
{
return MoveTo(maulgar->GetMapId(), maulgar->GetPositionX(), maulgar->GetPositionY(),
maulgar->GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
return false;
@@ -55,6 +47,8 @@ bool HighKingMaulgarMainTankAttackMaulgarAction::Execute(Event event)
bool HighKingMaulgarFirstAssistTankAttackOlmAction::Execute(Event event)
{
Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner");
if (!olm)
return false;
MarkTargetWithCircle(bot, olm);
SetRtiTarget(botAI, "circle", olm);
@@ -64,29 +58,22 @@ bool HighKingMaulgarFirstAssistTankAttackOlmAction::Execute(Event event)
if (olm->GetVictim() == bot)
{
const Location& tankPosition = GruulsLairLocations::OlmTankPosition;
const Position& position = OLM_TANK_POSITION;
const float maxDistance = 3.0f;
const float olmTankLeeway = 30.0f;
float distanceOlmToTankPosition = olm->GetExactDist2d(tankPosition.x, tankPosition.y);
float distanceOlmToPosition = olm->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
if (distanceOlmToTankPosition > olmTankLeeway)
if (distanceOlmToPosition > olmTankLeeway)
{
float dX = tankPosition.x - bot->GetPositionX();
float dY = tankPosition.y - bot->GetPositionY();
float dist = sqrt(dX * dX + dY * dY);
float moveX = bot->GetPositionX() + (dX / dist) * maxDistance;
float moveY = bot->GetPositionY() + (dY / dist) * maxDistance;
return MoveTo(bot->GetMapId(), moveX, moveY, tankPosition.z, false, false, false, false,
float dX = position.GetPositionX() - bot->GetPositionX();
float dY = position.GetPositionY() - bot->GetPositionY();
float moveX = bot->GetPositionX() + (dX / distanceOlmToPosition) * maxDistance;
float moveY = bot->GetPositionY() + (dY / distanceOlmToPosition) * maxDistance;
return MoveTo(GRUULS_LAIR_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
}
else if (!bot->IsWithinMeleeRange(olm))
{
return MoveTo(olm->GetMapId(), olm->GetPositionX(), olm->GetPositionY(),
olm->GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
return false;
}
@@ -95,6 +82,8 @@ bool HighKingMaulgarFirstAssistTankAttackOlmAction::Execute(Event event)
bool HighKingMaulgarSecondAssistTankAttackBlindeyeAction::Execute(Event event)
{
Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer");
if (!blindeye)
return false;
MarkTargetWithStar(bot, blindeye);
SetRtiTarget(botAI, "star", blindeye);
@@ -104,31 +93,20 @@ bool HighKingMaulgarSecondAssistTankAttackBlindeyeAction::Execute(Event event)
if (blindeye->GetVictim() == bot)
{
const Location& tankPosition = GruulsLairLocations::BlindeyeTankPosition;
const Position& position = BLINDEYE_TANK_POSITION;
const float maxDistance = 3.0f;
float distanceToTankPosition = bot->GetExactDist2d(tankPosition.x, tankPosition.y);
float distanceToPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
if (distanceToTankPosition > maxDistance)
if (distanceToPosition > maxDistance)
{
float dX = tankPosition.x - bot->GetPositionX();
float dY = tankPosition.y - bot->GetPositionY();
float dist = sqrt(dX * dX + dY * dY);
float moveX = bot->GetPositionX() + (dX / dist) * maxDistance;
float moveY = bot->GetPositionY() + (dY / dist) * maxDistance;
return MoveTo(bot->GetMapId(), moveX, moveY, tankPosition.z, false, false, false, false,
float dX = position.GetPositionX() - bot->GetPositionX();
float dY = position.GetPositionY() - bot->GetPositionY();
float moveX = bot->GetPositionX() + (dX / distanceToPosition) * maxDistance;
float moveY = bot->GetPositionY() + (dY / distanceToPosition) * maxDistance;
return MoveTo(GRUULS_LAIR_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
float orientation = atan2(blindeye->GetPositionY() - bot->GetPositionY(),
blindeye->GetPositionX() - bot->GetPositionX());
bot->SetFacingTo(orientation);
}
else if (!bot->IsWithinMeleeRange(blindeye))
{
return MoveTo(blindeye->GetMapId(), blindeye->GetPositionX(), blindeye->GetPositionY(),
blindeye->GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
return false;
@@ -138,6 +116,8 @@ bool HighKingMaulgarSecondAssistTankAttackBlindeyeAction::Execute(Event event)
bool HighKingMaulgarMageTankAttackKroshAction::Execute(Event event)
{
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand");
if (!krosh)
return false;
MarkTargetWithTriangle(bot, krosh);
SetRtiTarget(botAI, "triangle", krosh);
@@ -149,25 +129,22 @@ bool HighKingMaulgarMageTankAttackKroshAction::Execute(Event event)
return botAI->CastSpell("fire ward", bot);
if (bot->GetTarget() != krosh->GetGUID())
{
bot->SetSelection(krosh->GetGUID());
return true;
}
return Attack(krosh);
if (krosh->GetVictim() == bot)
{
const Location& tankPosition = GruulsLairLocations::KroshTankPosition;
float distanceToKrosh = krosh->GetExactDist2d(tankPosition.x, tankPosition.y);
const Position& position = KROSH_TANK_POSITION;
float distanceToKrosh = krosh->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
const float minDistance = 16.0f;
const float maxDistance = 29.0f;
const float tankPositionLeeway = 1.0f;
if (distanceToKrosh > minDistance && distanceToKrosh < maxDistance)
{
if (!bot->IsWithinDist2d(tankPosition.x, tankPosition.y, tankPositionLeeway))
if (!bot->IsWithinDist2d(position.GetPositionX(), position.GetPositionY(), tankPositionLeeway))
{
return MoveTo(bot->GetMapId(), tankPosition.x, tankPosition.y, tankPosition.z, false,
false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
return MoveTo(GRUULS_LAIR_MAP_ID, position.GetPositionX(), position.GetPositionY(), position.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
float orientation = atan2(krosh->GetPositionY() - bot->GetPositionY(),
@@ -179,7 +156,7 @@ bool HighKingMaulgarMageTankAttackKroshAction::Execute(Event event)
Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos))
{
return MoveTo(krosh->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ,
return MoveTo(GRUULS_LAIR_MAP_ID, safePos.GetPositionX(), safePos.GetPositionY(), safePos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
}
@@ -192,20 +169,19 @@ bool HighKingMaulgarMageTankAttackKroshAction::Execute(Event event)
bool HighKingMaulgarMoonkinTankAttackKigglerAction::Execute(Event event)
{
Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed");
if (!kiggler)
return false;
MarkTargetWithDiamond(bot, kiggler);
SetRtiTarget(botAI, "diamond", kiggler);
if (bot->GetTarget() != kiggler->GetGUID())
{
bot->SetSelection(kiggler->GetGUID());
return true;
}
return Attack(kiggler);
Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos))
{
return MoveTo(kiggler->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ,
return MoveTo(GRUULS_LAIR_MAP_ID, safePos.GetPositionX(), safePos.GetPositionY(), safePos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
@@ -216,120 +192,105 @@ bool HighKingMaulgarAssignDPSPriorityAction::Execute(Event event)
{
// Target priority 1: Blindeye
Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer");
if (blindeye && blindeye->IsAlive())
if (blindeye)
{
Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos))
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
return MoveTo(blindeye->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ,
return MoveTo(GRUULS_LAIR_MAP_ID, safePos.GetPositionX(), safePos.GetPositionY(), safePos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
SetRtiTarget(botAI, "star", blindeye);
if (bot->GetTarget() != blindeye->GetGUID())
{
bot->SetSelection(blindeye->GetGUID());
return Attack(blindeye);
}
return false;
}
// Target priority 2: Olm
Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner");
if (olm && olm->IsAlive())
if (olm)
{
Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos))
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
return MoveTo(olm->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ,
return MoveTo(GRUULS_LAIR_MAP_ID, safePos.GetPositionX(), safePos.GetPositionY(), safePos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
SetRtiTarget(botAI, "circle", olm);
if (bot->GetTarget() != olm->GetGUID())
{
bot->SetSelection(olm->GetGUID());
return Attack(olm);
}
return false;
}
// Target priority 3a: Krosh (ranged only)
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand");
if (krosh && krosh->IsAlive() && botAI->IsRanged(bot))
if (krosh && botAI->IsRanged(bot))
{
Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos))
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
return MoveTo(krosh->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ,
return MoveTo(GRUULS_LAIR_MAP_ID, safePos.GetPositionX(), safePos.GetPositionY(), safePos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
SetRtiTarget(botAI, "triangle", krosh);
if (bot->GetTarget() != krosh->GetGUID())
{
bot->SetSelection(krosh->GetGUID());
return Attack(krosh);
}
return false;
}
// Target priority 3b: Kiggler
Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed");
if (kiggler && kiggler->IsAlive())
if (kiggler)
{
Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos))
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
return MoveTo(kiggler->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ,
return MoveTo(GRUULS_LAIR_MAP_ID, safePos.GetPositionX(), safePos.GetPositionY(), safePos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
SetRtiTarget(botAI, "diamond", kiggler);
if (bot->GetTarget() != kiggler->GetGUID())
{
bot->SetSelection(kiggler->GetGUID());
return Attack(kiggler);
}
return false;
}
// Target priority 4: Maulgar
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
if (maulgar && maulgar->IsAlive())
if (maulgar)
{
Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos))
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
return MoveTo(maulgar->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ,
return MoveTo(GRUULS_LAIR_MAP_ID, safePos.GetPositionX(), safePos.GetPositionY(), safePos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
SetRtiTarget(botAI, "square", maulgar);
if (bot->GetTarget() != maulgar->GetGUID())
{
bot->SetSelection(maulgar->GetGUID());
return Attack(maulgar);
}
}
return false;
@@ -338,22 +299,22 @@ bool HighKingMaulgarAssignDPSPriorityAction::Execute(Event event)
// Avoid Whirlwind and Blast Wave and generally try to stay near the center of the room
bool HighKingMaulgarHealerFindSafePositionAction::Execute(Event event)
{
const Location& fightCenter = GruulsLairLocations::MaulgarRoomCenter;
const float maxDistanceFromFight = 50.0f;
float distToFight = bot->GetExactDist2d(fightCenter.x, fightCenter.y);
const Position& center = MAULGAR_ROOM_CENTER;
const float maxDistanceFromCenter = 50.0f;
float distToCenter = bot->GetExactDist2d(center.GetPositionX(), center.GetPositionY());
if (distToFight > maxDistanceFromFight)
if (distToCenter > maxDistanceFromCenter)
{
float angle = atan2(bot->GetPositionY() - fightCenter.y, bot->GetPositionX() - fightCenter.x);
float destX = fightCenter.x + 40.0f * cos(angle);
float destY = fightCenter.y + 40.0f * sin(angle);
float destZ = fightCenter.z;
float angle = atan2(bot->GetPositionY() - center.GetPositionY(), bot->GetPositionX() - center.GetPositionX());
float destX = center.GetPositionX() + 40.0f * cos(angle);
float destY = center.GetPositionY() + 40.0f * sin(angle);
float destZ = center.GetPositionZ();
if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
bot->GetPositionZ(), destX, destY, destZ))
return false;
return MoveTo(bot->GetMapId(), destX, destY, destZ, false, false, false, false,
return MoveTo(GRUULS_LAIR_MAP_ID, destX, destY, destZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
@@ -362,7 +323,7 @@ bool HighKingMaulgarHealerFindSafePositionAction::Execute(Event event)
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
return MoveTo(bot->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ,
return MoveTo(GRUULS_LAIR_MAP_ID, safePos.GetPositionX(), safePos.GetPositionY(), safePos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
@@ -373,6 +334,8 @@ bool HighKingMaulgarHealerFindSafePositionAction::Execute(Event event)
bool HighKingMaulgarRunAwayFromWhirlwindAction::Execute(Event event)
{
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
if (!maulgar)
return false;
const float safeDistance = 10.0f;
float distance = bot->GetExactDist2d(maulgar);
@@ -395,7 +358,7 @@ bool HighKingMaulgarRunAwayFromWhirlwindAction::Execute(Event event)
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(true);
return MoveTo(maulgar->GetMapId(), destX, destY, destZ, false, false, false, false,
return MoveTo(GRUULS_LAIR_MAP_ID, destX, destY, destZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
}
@@ -439,7 +402,7 @@ bool HighKingMaulgarBanishFelstalkerAction::Execute(Event event)
if (warlockIndex >= 0 && warlockIndex < felStalkers.size())
{
Unit* assignedFelStalker = felStalkers[warlockIndex];
if (!assignedFelStalker->HasAura(SPELL_BANISH) && botAI->CanCastSpell(SPELL_BANISH, assignedFelStalker, true))
if (!botAI->HasAura("banish", assignedFelStalker) && botAI->CanCastSpell("banish", assignedFelStalker))
return botAI->CastSpell("banish", assignedFelStalker);
}
@@ -528,40 +491,33 @@ bool HighKingMaulgarMisdirectOlmAndBlindeyeAction::Execute(Event event)
// Gruul the Dragonkiller Actions
// Position in center of the room
bool GruulTheDragonkillerMainTankPositionBossAction::Execute(Event event)
bool GruulTheDragonkillerTanksPositionBossAction::Execute(Event event)
{
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
if (!gruul)
return false;
if (bot->GetVictim() != gruul)
return Attack(gruul);
if (gruul->GetVictim() == bot)
{
const Location& tankPosition = GruulsLairLocations::GruulTankPosition;
const float maxDistance = 3.0f;
const Position& position = GRUUL_TANK_POSITION;
const float maxDistance = 5.0f;
float dX = tankPosition.x - bot->GetPositionX();
float dY = tankPosition.y - bot->GetPositionY();
float distanceToTankPosition = bot->GetExactDist2d(tankPosition.x, tankPosition.y);
float dX = position.GetPositionX() - bot->GetPositionX();
float dY = position.GetPositionY() - bot->GetPositionY();
float distanceToTankPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
if (distanceToTankPosition > maxDistance)
{
float step = std::min(maxDistance, distanceToTankPosition);
float moveX = bot->GetPositionX() + (dX / distanceToTankPosition) * maxDistance;
float moveY = bot->GetPositionY() + (dY / distanceToTankPosition) * maxDistance;
const float moveZ = tankPosition.z;
return MoveTo(bot->GetMapId(), moveX, moveY, moveZ, false, false, false, false,
const float moveZ = position.GetPositionZ();
return MoveTo(GRUULS_LAIR_MAP_ID, moveX, moveY, moveZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
float orientation = atan2(gruul->GetPositionY() - bot->GetPositionY(),
gruul->GetPositionX() - bot->GetPositionX());
bot->SetFacingTo(orientation);
}
else if (!bot->IsWithinMeleeRange(gruul))
{
return MoveTo(gruul->GetMapId(), gruul->GetPositionX(), gruul->GetPositionY(), gruul->GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
return false;
@@ -579,16 +535,16 @@ bool GruulTheDragonkillerSpreadRangedAction::Execute(Event event)
static std::unordered_map<ObjectGuid, bool> hasReachedInitialPosition;
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
if (gruul && gruul->IsAlive() && gruul->GetHealth() == gruul->GetMaxHealth())
if (gruul && gruul->GetHealth() == gruul->GetMaxHealth())
{
initialPositions.clear();
hasReachedInitialPosition.clear();
initialPositions.erase(bot->GetGUID());
hasReachedInitialPosition.erase(bot->GetGUID());
}
const Location& tankPosition = GruulsLairLocations::GruulTankPosition;
const float centerX = tankPosition.x;
const float centerY = tankPosition.y;
float centerZ = bot->GetPositionZ();
const Position& position = GRUUL_TANK_POSITION;
const float centerX = position.GetPositionX();
const float centerY = position.GetPositionY();
const float centerZ = position.GetPositionZ();
const float minRadius = 25.0f;
const float maxRadius = 40.0f;
@@ -642,7 +598,7 @@ bool GruulTheDragonkillerSpreadRangedAction::Execute(Event event)
bot->GetPositionY(), bot->GetPositionZ(), destX, destY, destZ))
return false;
return MoveTo(bot->GetMapId(), destX, destY, destZ, false, false, false, false,
return MoveTo(GRUULS_LAIR_MAP_ID, destX, destY, destZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}

View File

@@ -85,10 +85,10 @@ public:
bool Execute(Event event) override;
};
class GruulTheDragonkillerMainTankPositionBossAction : public AttackAction
class GruulTheDragonkillerTanksPositionBossAction : public AttackAction
{
public:
GruulTheDragonkillerMainTankPositionBossAction(PlayerbotAI* botAI, std::string const name = "gruul the dragonkiller main tank position boss") : AttackAction(botAI, name) {};
GruulTheDragonkillerTanksPositionBossAction(PlayerbotAI* botAI, std::string const name = "gruul the dragonkiller tanks position boss") : AttackAction(botAI, name) {};
bool Execute(Event event) override;
};

View File

@@ -8,18 +8,11 @@
#include "HunterActions.h"
#include "MageActions.h"
#include "Playerbots.h"
#include "ReachTargetActions.h"
#include "WarriorActions.h"
using namespace GruulsLairHelpers;
static bool IsChargeAction(Action* action)
{
return dynamic_cast<CastChargeAction*>(action) ||
dynamic_cast<CastInterceptAction*>(action) ||
dynamic_cast<CastFeralChargeBearAction*>(action) ||
dynamic_cast<CastFeralChargeCatAction*>(action);
}
float HighKingMaulgarDisableTankAssistMultiplier::GetValue(Action* action)
{
if (IsAnyOgreBossAlive(botAI) && dynamic_cast<TankAssistAction*>(action))
@@ -38,12 +31,10 @@ float HighKingMaulgarAvoidWhirlwindMultiplier::GetValue(Action* action)
Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer");
if (maulgar && maulgar->HasAura(SPELL_WHIRLWIND) &&
(!kiggler || !kiggler->IsAlive()) &&
(!krosh || !krosh->IsAlive()) &&
(!olm || !olm->IsAlive()) &&
(!blindeye || !blindeye->IsAlive()))
!kiggler && !krosh && !olm && !blindeye)
{
if (IsChargeAction(action) || (dynamic_cast<MovementAction*>(action) &&
if (dynamic_cast<CastReachTargetSpellAction*>(action) ||
(dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<HighKingMaulgarRunAwayFromWhirlwindAction*>(action)))
return 0.0f;
}
@@ -57,7 +48,8 @@ float HighKingMaulgarDisableArcaneShotOnKroshMultiplier::GetValue(Action* action
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand");
Unit* target = AI_VALUE(Unit*, "current target");
if (krosh && target && target->GetGUID() == krosh->GetGUID() && dynamic_cast<CastArcaneShotAction*>(action))
if (krosh && target && target->GetGUID() == krosh->GetGUID() &&
dynamic_cast<CastArcaneShotAction*>(action))
return 0.0f;
return 1.0f;
@@ -101,8 +93,9 @@ float GruulTheDragonkillerGroundSlamMultiplier::GetValue(Action* action)
if (bot->HasAura(SPELL_GROUND_SLAM_1) ||
bot->HasAura(SPELL_GROUND_SLAM_2))
{
if ((dynamic_cast<MovementAction*>(action) && !dynamic_cast<GruulTheDragonkillerShatterSpreadAction*>(action)) ||
IsChargeAction(action))
if ((dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<GruulTheDragonkillerShatterSpreadAction*>(action)) ||
dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
}

View File

@@ -22,7 +22,7 @@ public:
creators["high king maulgar misdirect olm and blindeye"] = &RaidGruulsLairActionContext::high_king_maulgar_misdirect_olm_and_blindeye;
// Gruul the Dragonkiller
creators["gruul the dragonkiller main tank position boss"] = &RaidGruulsLairActionContext::gruul_the_dragonkiller_main_tank_position_boss;
creators["gruul the dragonkiller tanks position boss"] = &RaidGruulsLairActionContext::gruul_the_dragonkiller_tanks_position_boss;
creators["gruul the dragonkiller spread ranged"] = &RaidGruulsLairActionContext::gruul_the_dragonkiller_spread_ranged;
creators["gruul the dragonkiller shatter spread"] = &RaidGruulsLairActionContext::gruul_the_dragonkiller_shatter_spread;
}
@@ -41,7 +41,7 @@ private:
static Action* high_king_maulgar_misdirect_olm_and_blindeye(PlayerbotAI* botAI) { return new HighKingMaulgarMisdirectOlmAndBlindeyeAction(botAI); }
// Gruul the Dragonkiller
static Action* gruul_the_dragonkiller_main_tank_position_boss(PlayerbotAI* botAI) { return new GruulTheDragonkillerMainTankPositionBossAction(botAI); }
static Action* gruul_the_dragonkiller_tanks_position_boss(PlayerbotAI* botAI) { return new GruulTheDragonkillerTanksPositionBossAction(botAI); }
static Action* gruul_the_dragonkiller_spread_ranged(PlayerbotAI* botAI) { return new GruulTheDragonkillerSpreadRangedAction(botAI); }
static Action* gruul_the_dragonkiller_shatter_spread(PlayerbotAI* botAI) { return new GruulTheDragonkillerShatterSpreadAction(botAI); }
};

View File

@@ -22,8 +22,8 @@ public:
creators["high king maulgar pulling olm and blindeye"] = &RaidGruulsLairTriggerContext::high_king_maulgar_pulling_olm_and_blindeye;
// Gruul the Dragonkiller
creators["gruul the dragonkiller boss engaged by main tank"] = &RaidGruulsLairTriggerContext::gruul_the_dragonkiller_boss_engaged_by_main_tank;
creators["gruul the dragonkiller boss engaged by range"] = &RaidGruulsLairTriggerContext::gruul_the_dragonkiller_boss_engaged_by_range;
creators["gruul the dragonkiller boss engaged by tanks"] = &RaidGruulsLairTriggerContext::gruul_the_dragonkiller_boss_engaged_by_tanks;
creators["gruul the dragonkiller boss engaged by ranged"] = &RaidGruulsLairTriggerContext::gruul_the_dragonkiller_boss_engaged_by_ranged;
creators["gruul the dragonkiller incoming shatter"] = &RaidGruulsLairTriggerContext::gruul_the_dragonkiller_incoming_shatter;
}
@@ -41,8 +41,8 @@ private:
static Trigger* high_king_maulgar_pulling_olm_and_blindeye(PlayerbotAI* botAI) { return new HighKingMaulgarPullingOlmAndBlindeyeTrigger(botAI); }
// Gruul the Dragonkiller
static Trigger* gruul_the_dragonkiller_boss_engaged_by_main_tank(PlayerbotAI* botAI) { return new GruulTheDragonkillerBossEngagedByMainTankTrigger(botAI); }
static Trigger* gruul_the_dragonkiller_boss_engaged_by_range(PlayerbotAI* botAI) { return new GruulTheDragonkillerBossEngagedByRangeTrigger(botAI); }
static Trigger* gruul_the_dragonkiller_boss_engaged_by_tanks(PlayerbotAI* botAI) { return new GruulTheDragonkillerBossEngagedByTanksTrigger(botAI); }
static Trigger* gruul_the_dragonkiller_boss_engaged_by_ranged(PlayerbotAI* botAI) { return new GruulTheDragonkillerBossEngagedByRangedTrigger(botAI); }
static Trigger* gruul_the_dragonkiller_incoming_shatter(PlayerbotAI* botAI) { return new GruulTheDragonkillerIncomingShatterTrigger(botAI); }
};

View File

@@ -35,10 +35,10 @@ void RaidGruulsLairStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
NextAction("high king maulgar misdirect olm and blindeye", ACTION_RAID + 2) }));
// Gruul the Dragonkiller
triggers.push_back(new TriggerNode("gruul the dragonkiller boss engaged by main tank", {
NextAction("gruul the dragonkiller main tank position boss", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("gruul the dragonkiller boss engaged by tanks", {
NextAction("gruul the dragonkiller tanks position boss", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("gruul the dragonkiller boss engaged by range", {
triggers.push_back(new TriggerNode("gruul the dragonkiller boss engaged by ranged", {
NextAction("gruul the dragonkiller spread ranged", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("gruul the dragonkiller incoming shatter", {

View File

@@ -10,35 +10,35 @@ bool HighKingMaulgarIsMainTankTrigger::IsActive()
{
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
return botAI->IsMainTank(bot) && maulgar && maulgar->IsAlive();
return botAI->IsMainTank(bot) && maulgar;
}
bool HighKingMaulgarIsFirstAssistTankTrigger::IsActive()
{
Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner");
return botAI->IsAssistTankOfIndex(bot, 0) && olm && olm->IsAlive();
return botAI->IsAssistTankOfIndex(bot, 0, false) && olm;
}
bool HighKingMaulgarIsSecondAssistTankTrigger::IsActive()
{
Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer");
return botAI->IsAssistTankOfIndex(bot, 1) && blindeye && blindeye->IsAlive();
return botAI->IsAssistTankOfIndex(bot, 1, false) && blindeye;
}
bool HighKingMaulgarIsMageTankTrigger::IsActive()
{
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand");
return IsKroshMageTank(botAI, bot) && krosh && krosh->IsAlive();
return IsKroshMageTank(botAI, bot) && krosh;
}
bool HighKingMaulgarIsMoonkinTankTrigger::IsActive()
{
Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed");
return IsKigglerMoonkinTank(botAI, bot) && kiggler && kiggler->IsAlive();
return IsKigglerMoonkinTank(botAI, bot) && kiggler;
}
bool HighKingMaulgarDeterminingKillOrderTrigger::IsActive()
@@ -50,11 +50,11 @@ bool HighKingMaulgarDeterminingKillOrderTrigger::IsActive()
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand");
return (botAI->IsDps(bot) || botAI->IsTank(bot)) &&
!(botAI->IsMainTank(bot) && maulgar && maulgar->IsAlive()) &&
!(botAI->IsAssistTankOfIndex(bot, 0) && olm && olm->IsAlive()) &&
!(botAI->IsAssistTankOfIndex(bot, 1) && blindeye && blindeye->IsAlive()) &&
!(IsKroshMageTank(botAI, bot) && krosh && krosh->IsAlive()) &&
!(IsKigglerMoonkinTank(botAI, bot) && kiggler && kiggler->IsAlive());
!(botAI->IsMainTank(bot) && maulgar) &&
!(botAI->IsAssistTankOfIndex(bot, 0, false) && olm) &&
!(botAI->IsAssistTankOfIndex(bot, 1, false) && blindeye) &&
!(IsKroshMageTank(botAI, bot) && krosh) &&
!(IsKigglerMoonkinTank(botAI, bot) && kiggler);
}
bool HighKingMaulgarHealerInDangerTrigger::IsActive()
@@ -66,7 +66,7 @@ bool HighKingMaulgarBossChannelingWhirlwindTrigger::IsActive()
{
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
return maulgar && maulgar->IsAlive() && maulgar->HasAura(SPELL_WHIRLWIND) &&
return maulgar && maulgar->HasAura(SPELL_WHIRLWIND) &&
!botAI->IsMainTank(bot);
}
@@ -74,7 +74,7 @@ bool HighKingMaulgarWildFelstalkerSpawnedTrigger::IsActive()
{
Unit* felStalker = AI_VALUE2(Unit*, "find target", "wild fel stalker");
return felStalker && felStalker->IsAlive() && bot->getClass() == CLASS_WARLOCK;
return felStalker && bot->getClass() == CLASS_WARLOCK;
}
bool HighKingMaulgarPullingOlmAndBlindeyeTrigger::IsActive()
@@ -120,12 +120,12 @@ bool HighKingMaulgarPullingOlmAndBlindeyeTrigger::IsActive()
switch (hunterIndex)
{
case 0:
return olm && olm->IsAlive() && olm->GetHealthPct() > 98.0f &&
olmTank && olmTank->IsAlive() && botAI->CanCastSpell("misdirection", olmTank);
return olm && olm->GetHealthPct() > 98.0f &&
olmTank && botAI->CanCastSpell("misdirection", olmTank);
case 1:
return blindeye && blindeye->IsAlive() && blindeye->GetHealthPct() > 90.0f &&
blindeyeTank && blindeyeTank->IsAlive() && botAI->CanCastSpell("misdirection", blindeyeTank);
return blindeye && blindeye->GetHealthPct() > 90.0f &&
blindeyeTank && botAI->CanCastSpell("misdirection", blindeyeTank);
default:
break;
@@ -136,25 +136,24 @@ bool HighKingMaulgarPullingOlmAndBlindeyeTrigger::IsActive()
// Gruul the Dragonkiller Triggers
bool GruulTheDragonkillerBossEngagedByMainTankTrigger::IsActive()
bool GruulTheDragonkillerBossEngagedByTanksTrigger::IsActive()
{
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
return gruul && gruul->IsAlive() && botAI->IsMainTank(bot);
return gruul && botAI->IsTank(bot);
}
bool GruulTheDragonkillerBossEngagedByRangeTrigger::IsActive()
bool GruulTheDragonkillerBossEngagedByRangedTrigger::IsActive()
{
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
return gruul && gruul->IsAlive() && botAI->IsRanged(bot);
return gruul && botAI->IsRanged(bot);
}
bool GruulTheDragonkillerIncomingShatterTrigger::IsActive()
{
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
return gruul && gruul->IsAlive() &&
(bot->HasAura(SPELL_GROUND_SLAM_1) ||
bot->HasAura(SPELL_GROUND_SLAM_2));
return gruul && (bot->HasAura(SPELL_GROUND_SLAM_1) ||
bot->HasAura(SPELL_GROUND_SLAM_2));
}

View File

@@ -73,17 +73,17 @@ public:
bool IsActive() override;
};
class GruulTheDragonkillerBossEngagedByMainTankTrigger : public Trigger
class GruulTheDragonkillerBossEngagedByTanksTrigger : public Trigger
{
public:
GruulTheDragonkillerBossEngagedByMainTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "gruul the dragonkiller boss engaged by main tank") {}
GruulTheDragonkillerBossEngagedByTanksTrigger(PlayerbotAI* botAI) : Trigger(botAI, "gruul the dragonkiller boss engaged by tanks") {}
bool IsActive() override;
};
class GruulTheDragonkillerBossEngagedByRangeTrigger : public Trigger
class GruulTheDragonkillerBossEngagedByRangedTrigger : public Trigger
{
public:
GruulTheDragonkillerBossEngagedByRangeTrigger(PlayerbotAI* botAI) : Trigger(botAI, "gruul the dragonkiller boss engaged by range") {}
GruulTheDragonkillerBossEngagedByRangedTrigger(PlayerbotAI* botAI) : Trigger(botAI, "gruul the dragonkiller boss engaged by ranged") {}
bool IsActive() override;
};

View File

@@ -6,19 +6,16 @@
namespace GruulsLairHelpers
{
namespace GruulsLairLocations
{
// Olm does not chase properly due to the Core's caster movement issues
// Thus, the below "OlmTankPosition" is beyond the actual desired tanking location
// It is the spot to which the OlmTank runs to to pull Olm to a decent tanking location
// "MaulgarRoomCenter" is to keep healers in a centralized location
const Location MaulgarTankPosition = { 90.686f, 167.047f, -13.234f };
const Location OlmTankPosition = { 87.485f, 234.942f, -3.635f };
const Location BlindeyeTankPosition = { 99.681f, 213.989f, -10.345f };
const Location KroshTankPosition = { 116.880f, 166.208f, -14.231f };
const Location MaulgarRoomCenter = { 88.754f, 150.759f, -11.569f };
const Location GruulTankPosition = { 241.238f, 365.025f, -4.220f };
}
// Olm does not chase properly due to the Core's caster movement issues
// Thus, the below "OlmTankPosition" is beyond the actual desired tanking location
// It is the spot to which the OlmTank runs to to pull Olm to a decent tanking location
// "MaulgarRoomCenter" is to keep healers in a centralized location
const Position MAULGAR_TANK_POSITION = { 90.686f, 167.047f, -13.234f };
const Position OLM_TANK_POSITION = { 87.485f, 234.942f, -3.635f };
const Position BLINDEYE_TANK_POSITION = { 99.681f, 213.989f, -10.345f };
const Position KROSH_TANK_POSITION = { 116.880f, 166.208f, -14.231f };
const Position MAULGAR_ROOM_CENTER = { 88.754f, 150.759f, -11.569f };
const Position GRUUL_TANK_POSITION = { 241.238f, 365.025f, -4.220f };
bool IsAnyOgreBossAlive(PlayerbotAI* botAI)
{
@@ -42,84 +39,43 @@ namespace GruulsLairHelpers
return false;
}
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId)
{
Group* group = bot->GetGroup();
if (!target || !group)
return;
ObjectGuid currentGuid = group->GetTargetIcon(iconId);
if (currentGuid != target->GetGUID())
{
group->SetTargetIcon(iconId, bot->GetGUID(), target->GetGUID());
}
}
void MarkTargetWithSquare(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::squareIndex);
}
void MarkTargetWithStar(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::starIndex);
}
void MarkTargetWithCircle(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::circleIndex);
}
void MarkTargetWithDiamond(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::diamondIndex);
}
void MarkTargetWithTriangle(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::triangleIndex);
}
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target)
{
if (!target)
return;
std::string currentRti = botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Get();
Unit* currentTarget = botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Get();
if (currentRti != rtiName || currentTarget != target)
{
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set(rtiName);
botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Set(target);
}
}
bool IsKroshMageTank(PlayerbotAI* botAI, Player* bot)
{
Group* group = bot->GetGroup();
if (!group)
return false;
Player* highestHpMage = nullptr;
uint32 highestHp = 0;
// (1) First loop: Return the first assistant Mage (real player or bot)
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
if (!member || !member->IsAlive() || member->getClass() != CLASS_MAGE)
continue;
if (member->getClass() == CLASS_MAGE)
if (group->IsAssistant(member->GetGUID()))
return member == bot;
}
// (2) Fall back to bot Mage with highest HP
Player* highestHpMage = nullptr;
uint32 highestHp = 0;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
member->getClass() != CLASS_MAGE)
continue;
uint32 hp = member->GetMaxHealth();
if (!highestHpMage || hp > highestHp)
{
uint32 hp = member->GetMaxHealth();
if (!highestHpMage || hp > highestHp)
{
highestHpMage = member;
highestHp = hp;
}
highestHpMage = member;
highestHp = hp;
}
}
// (3) Return the found Mage tank, or nullptr if none found
return highestHpMage == bot;
}
@@ -129,30 +85,37 @@ namespace GruulsLairHelpers
if (!group)
return false;
Player* highestHpMoonkin = nullptr;
uint32 highestHp = 0;
// (1) First loop: Return the first assistant Moonkin (real player or bot)
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
if (!member || !member->IsAlive() || member->getClass() != CLASS_DRUID)
continue;
if (member->getClass() == CLASS_DRUID)
if (group->IsAssistant(member->GetGUID()) &&
AiFactory::GetPlayerSpecTab(member) == DRUID_TAB_BALANCE)
return member == bot;
}
// (2) Fall back to bot Moonkin with highest HP
Player* highestHpMoonkin = nullptr;
uint32 highestHp = 0;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member->getClass() != CLASS_DRUID ||
!GET_PLAYERBOT_AI(member) || AiFactory::GetPlayerSpecTab(member) != DRUID_TAB_BALANCE)
continue;
uint32 hp = member->GetMaxHealth();
if (!highestHpMoonkin || hp > highestHp)
{
int tab = AiFactory::GetPlayerSpecTab(member);
if (tab == DRUID_TAB_BALANCE)
{
uint32 hp = member->GetMaxHealth();
if (!highestHpMoonkin || hp > highestHp)
{
highestHpMoonkin = member;
highestHp = hp;
}
}
highestHpMoonkin = member;
highestHp = hp;
}
}
// (3) Return the found Moonkin tank, or nullptr if none found
return highestHpMoonkin == bot;
}

View File

@@ -2,23 +2,19 @@
#define RAID_GRUULSLAIRHELPERS_H
#include "PlayerbotAI.h"
#include "RtiTargetValue.h"
namespace GruulsLairHelpers
{
enum GruulsLairSpells
{
// High King Maulgar
SPELL_WHIRLWIND = 33238,
SPELL_WHIRLWIND = 33238,
// Krosh Firehand
SPELL_SPELL_SHIELD = 33054,
SPELL_SPELL_SHIELD = 33054,
// Hunter
SPELL_MISDIRECTION = 35079,
// Warlock
SPELL_BANISH = 18647, // Rank 2
SPELL_MISDIRECTION = 35079,
// Gruul the Dragonkiller
SPELL_GROUND_SLAM_1 = 33525,
@@ -30,33 +26,20 @@ namespace GruulsLairHelpers
NPC_WILD_FEL_STALKER = 18847,
};
constexpr uint32 GRUULS_LAIR_MAP_ID = 565;
bool IsAnyOgreBossAlive(PlayerbotAI* botAI);
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId);
void MarkTargetWithSquare(Player* bot, Unit* target);
void MarkTargetWithStar(Player* bot, Unit* target);
void MarkTargetWithCircle(Player* bot, Unit* target);
void MarkTargetWithDiamond(Player* bot, Unit* target);
void MarkTargetWithTriangle(Player* bot, Unit* target);
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target);
bool IsKroshMageTank(PlayerbotAI* botAI, Player* bot);
bool IsKigglerMoonkinTank(PlayerbotAI* botAI, Player* bot);
bool IsPositionSafe(PlayerbotAI* botAI, Player* bot, Position pos);
bool TryGetNewSafePosition(PlayerbotAI* botAI, Player* bot, Position& outPos);
struct Location
{
float x, y, z;
};
namespace GruulsLairLocations
{
extern const Location MaulgarTankPosition;
extern const Location OlmTankPosition;
extern const Location BlindeyeTankPosition;
extern const Location KroshTankPosition;
extern const Location MaulgarRoomCenter;
extern const Location GruulTankPosition;
}
extern const Position MAULGAR_TANK_POSITION;
extern const Position OLM_TANK_POSITION;
extern const Position BLINDEYE_TANK_POSITION;
extern const Position KROSH_TANK_POSITION;
extern const Position MAULGAR_ROOM_CENTER;
extern const Position GRUUL_TANK_POSITION;
}
#endif

View File

@@ -2,6 +2,7 @@
#include "RaidKarazhanHelpers.h"
#include "Playerbots.h"
#include "PlayerbotTextMgr.h"
#include "RaidBossHelpers.h"
using namespace KarazhanHelpers;
@@ -44,7 +45,7 @@ bool AttumenTheHuntsmanMarkTargetAction::Execute(Event event)
Unit* attumenMounted = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED);
if (attumenMounted)
{
if (IsInstanceTimerManager(botAI, bot))
if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
MarkTargetWithStar(bot, attumenMounted);
SetRtiTarget(botAI, "star", attumenMounted);
@@ -57,7 +58,7 @@ bool AttumenTheHuntsmanMarkTargetAction::Execute(Event event)
}
else if (Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight"))
{
if (IsInstanceTimerManager(botAI, bot))
if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
MarkTargetWithStar(bot, midnight);
if (!botAI->IsAssistTankOfIndex(bot, 0))
@@ -180,7 +181,7 @@ bool MoroesMarkTargetAction::Execute(Event event)
if (target)
{
if (IsInstanceTimerManager(botAI, bot))
if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
MarkTargetWithSkull(bot, target);
SetRtiTarget(botAI, "skull", target);
@@ -405,7 +406,7 @@ bool TheCuratorMarkAstralFlareAction::Execute(Event event)
if (!flare)
return false;
if (IsInstanceTimerManager(botAI, bot))
if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
MarkTargetWithSkull(bot, flare);
SetRtiTarget(botAI, "skull", flare);
@@ -469,11 +470,11 @@ bool TheCuratorSpreadRangedAction::Execute(Event event)
// Prioritize (1) Demon Chains, (2) Kil'rek, (3) Illhoof
bool TerestianIllhoofMarkTargetAction::Execute(Event event)
{
Unit* demonChains = AI_VALUE2(Unit*, "find target", "demon chains");
Unit* kilrek = AI_VALUE2(Unit*, "find target", "kil'rek");
Unit* demonChains = GetFirstAliveUnitByEntry(botAI, NPC_DEMON_CHAINS);
Unit* kilrek = GetFirstAliveUnitByEntry(botAI, NPC_KILREK);
Unit* illhoof = AI_VALUE2(Unit*, "find target", "terestian illhoof");
Unit* target = GetFirstAliveUnit({demonChains, kilrek, illhoof});
Unit* target = GetFirstAliveUnit({demonChains, kilrek, illhoof});
if (target)
MarkTargetWithSkull(bot, target);
@@ -1007,7 +1008,7 @@ bool NetherspiteManageTimersAndTrackersAction::Execute(Event event)
if (netherspite->GetHealth() == netherspite->GetMaxHealth() &&
!netherspite->HasAura(SPELL_GREEN_BEAM_HEAL))
{
if (IsInstanceTimerManager(botAI, bot))
if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
netherspiteDpsWaitTimer.insert_or_assign(instanceId, now);
if (botAI->IsTank(bot) && !bot->HasAura(SPELL_RED_BEAM_DEBUFF))
@@ -1018,7 +1019,7 @@ bool NetherspiteManageTimersAndTrackersAction::Execute(Event event)
}
else if (netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
{
if (IsInstanceTimerManager(botAI, bot))
if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
netherspiteDpsWaitTimer.erase(instanceId);
if (botAI->IsTank(bot))
@@ -1029,7 +1030,7 @@ bool NetherspiteManageTimersAndTrackersAction::Execute(Event event)
}
else if (!netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
{
if (IsInstanceTimerManager(botAI, bot))
if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
netherspiteDpsWaitTimer.try_emplace(instanceId, now);
if (botAI->IsTank(bot) && bot->HasAura(SPELL_RED_BEAM_DEBUFF))
@@ -1458,7 +1459,7 @@ bool NightbaneManageTimersAndTrackersAction::Execute(Event event)
if (botAI->IsRanged(bot))
nightbaneRangedStep.erase(botGuid);
if (IsInstanceTimerManager(botAI, bot))
if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
nightbaneDpsWaitTimer.erase(instanceId);
}
// Erase flight phase timer and Rain of Bones tracker on ground phase and start DPS wait timer
@@ -1466,7 +1467,7 @@ bool NightbaneManageTimersAndTrackersAction::Execute(Event event)
{
nightbaneRainOfBonesHit.erase(botGuid);
if (IsInstanceTimerManager(botAI, bot))
if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
{
nightbaneFlightPhaseStartTimer.erase(instanceId);
nightbaneDpsWaitTimer.try_emplace(instanceId, now);
@@ -1482,7 +1483,7 @@ bool NightbaneManageTimersAndTrackersAction::Execute(Event event)
if (botAI->IsRanged(bot))
nightbaneRangedStep.erase(botGuid);
if (IsInstanceTimerManager(botAI, bot))
if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
{
nightbaneDpsWaitTimer.erase(instanceId);
nightbaneFlightPhaseStartTimer.try_emplace(instanceId, now);

View File

@@ -10,6 +10,7 @@
#include "MageActions.h"
#include "Playerbots.h"
#include "PriestActions.h"
#include "RaidBossHelpers.h"
#include "ReachTargetActions.h"
#include "RogueActions.h"
#include "ShamanActions.h"
@@ -242,6 +243,9 @@ float PrinceMalchezaarEnfeebleKeepDistanceMultiplier::GetValue(Action* action)
if (bot->HasAura(SPELL_ENFEEBLE))
{
if (dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<PrinceMalchezaarEnfeebledAvoidHazardAction*>(action))
return 0.0f;

View File

@@ -2,6 +2,7 @@
#include "RaidKarazhanHelpers.h"
#include "RaidKarazhanActions.h"
#include "Playerbots.h"
#include "RaidBossHelpers.h"
using namespace KarazhanHelpers;
@@ -40,7 +41,7 @@ bool AttumenTheHuntsmanAttumenIsMountedTrigger::IsActive()
bool AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger::IsActive()
{
if (!IsInstanceTimerManager(botAI, bot))
if (!IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
return false;
Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight");
@@ -110,7 +111,7 @@ bool BigBadWolfBossIsChasingLittleRedRidingHoodTrigger::IsActive()
bool RomuloAndJulianneBothBossesRevivedTrigger::IsActive()
{
if (!IsInstanceTimerManager(botAI, bot))
if (!IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
return false;
Unit* romulo = AI_VALUE2(Unit*, "find target", "romulo");
@@ -126,7 +127,7 @@ bool RomuloAndJulianneBothBossesRevivedTrigger::IsActive()
bool WizardOfOzNeedTargetPriorityTrigger::IsActive()
{
if (!IsInstanceTimerManager(botAI, bot))
if (!IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
return false;
Unit* dorothee = AI_VALUE2(Unit*, "find target", "dorothee");
@@ -178,7 +179,7 @@ bool TheCuratorBossAstralFlaresCastArcingSearTrigger::IsActive()
bool TerestianIllhoofNeedTargetPriorityTrigger::IsActive()
{
if (!IsInstanceTimerManager(botAI, bot))
if (!IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
return false;
Unit* illhoof = AI_VALUE2(Unit*, "find target", "terestian illhoof");
@@ -202,7 +203,7 @@ bool ShadeOfAranFlameWreathIsActiveTrigger::IsActive()
// Exclusion of Banish is so the player may Banish elementals if they wish
bool ShadeOfAranConjuredElementalsSummonedTrigger::IsActive()
{
if (!IsInstanceTimerManager(botAI, bot))
if (!IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
return false;
Unit* elemental = AI_VALUE2(Unit*, "find target", "conjured elemental");
@@ -279,7 +280,7 @@ bool NetherspiteBossIsBanishedTrigger::IsActive()
bool NetherspiteNeedToManageTimersAndTrackersTrigger::IsActive()
{
if (!botAI->IsTank(bot) && !IsInstanceTimerManager(botAI, bot))
if (!botAI->IsTank(bot) && !IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
return false;
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");

View File

@@ -1,7 +1,6 @@
#include "RaidKarazhanHelpers.h"
#include "RaidKarazhanActions.h"
#include "Playerbots.h"
#include "RtiTargetValue.h"
namespace KarazhanHelpers
{
@@ -52,75 +51,6 @@ namespace KarazhanHelpers
const Position NIGHTBANE_FLIGHT_STACK_POSITION = { -11159.555f, -1893.526f, 91.473f }; // Broken Barrel
const Position NIGHTBANE_RAIN_OF_BONES_POSITION = { -11165.233f, -1911.123f, 91.473f };
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId)
{
if (!target)
return;
if (Group* group = bot->GetGroup())
{
ObjectGuid currentGuid = group->GetTargetIcon(iconId);
if (currentGuid != target->GetGUID())
group->SetTargetIcon(iconId, bot->GetGUID(), target->GetGUID());
}
}
void MarkTargetWithSkull(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::skullIndex);
}
void MarkTargetWithSquare(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::squareIndex);
}
void MarkTargetWithStar(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::starIndex);
}
void MarkTargetWithCircle(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::circleIndex);
}
void MarkTargetWithMoon(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::moonIndex);
}
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target)
{
if (!target)
return;
std::string currentRti = botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Get();
Unit* currentTarget = botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Get();
if (currentRti != rtiName || currentTarget != target)
{
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set(rtiName);
botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Set(target);
}
}
// Only one bot is needed to set/reset instance-wide timers
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot)
{
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->IsAlive() && botAI->IsDps(member) && GET_PLAYERBOT_AI(member))
return member == bot;
}
}
return false;
}
Unit* GetFirstAliveUnit(const std::vector<Unit*>& units)
{
for (Unit* unit : units)
@@ -132,44 +62,6 @@ namespace KarazhanHelpers
return nullptr;
}
Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry)
{
const GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest hostile npcs")->Get();
for (auto const& npcGuid : npcs)
{
Unit* unit = botAI->GetUnit(npcGuid);
if (unit && unit->IsAlive() && unit->GetEntry() == entry)
return unit;
}
return nullptr;
}
Unit* GetNearestPlayerInRadius(Player* bot, float radius)
{
Unit* nearestPlayer = nullptr;
float nearestDistance = radius;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref != nullptr; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member == bot)
continue;
float distance = bot->GetExactDist2d(member);
if (distance < nearestDistance)
{
nearestDistance = distance;
nearestPlayer = member;
}
}
}
return nearestPlayer;
}
bool IsFlameWreathActive(PlayerbotAI* botAI, Player* bot)
{
Unit* aran = botAI->GetAiObjectContext()->GetValue<Unit*>("find target", "shade of aran")->Get();

View File

@@ -61,6 +61,11 @@ namespace KarazhanHelpers
NPC_ATTUMEN_THE_HUNTSMAN = 15550,
NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED = 16152,
// Terestian Illhoof
NPC_TERESTIAN_ILLHOOF = 15688,
NPC_DEMON_CHAINS = 17248,
NPC_KILREK = 17229,
// Shade of Aran
NPC_CONJURED_ELEMENTAL = 17167,
@@ -74,8 +79,8 @@ namespace KarazhanHelpers
NPC_NETHERSPITE_INFERNAL = 17646,
};
const uint32 KARAZHAN_MAP_ID = 532;
const float NIGHTBANE_FLIGHT_Z = 95.0f;
constexpr uint32 KARAZHAN_MAP_ID = 532;
constexpr float NIGHTBANE_FLIGHT_Z = 95.0f;
// Attumen the Huntsman
extern std::unordered_map<uint32, time_t> attumenDpsWaitTimer;
@@ -105,17 +110,7 @@ namespace KarazhanHelpers
extern const Position NIGHTBANE_FLIGHT_STACK_POSITION;
extern const Position NIGHTBANE_RAIN_OF_BONES_POSITION;
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId);
void MarkTargetWithSkull(Player* bot, Unit* target);
void MarkTargetWithSquare(Player* bot, Unit* target);
void MarkTargetWithStar(Player* bot, Unit* target);
void MarkTargetWithCircle(Player* bot, Unit* target);
void MarkTargetWithMoon(Player* bot, Unit* target);
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target);
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot);
Unit* GetFirstAliveUnit(const std::vector<Unit*>& units);
Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry);
Unit* GetNearestPlayerInRadius(Player* bot, float radius);
bool IsFlameWreathActive(PlayerbotAI* botAI, Player* bot);
std::vector<Player*> GetRedBlockers(PlayerbotAI* botAI, Player* bot);
std::vector<Player*> GetBlueBlockers(PlayerbotAI* botAI, Player* bot);

View File

@@ -4,6 +4,7 @@
#include "ObjectAccessor.h"
#include "ObjectGuid.h"
#include "Playerbots.h"
#include "RaidBossHelpers.h"
using namespace MagtheridonHelpers;
@@ -14,46 +15,45 @@ bool MagtheridonMainTankAttackFirstThreeChannelersAction::Execute(Event event)
return false;
Creature* channelerSquare = GetChanneler(bot, SOUTH_CHANNELER);
if (channelerSquare && channelerSquare->IsAlive())
if (channelerSquare)
MarkTargetWithSquare(bot, channelerSquare);
Creature* channelerStar = GetChanneler(bot, WEST_CHANNELER);
if (channelerStar && channelerStar->IsAlive())
if (channelerStar)
MarkTargetWithStar(bot, channelerStar);
Creature* channelerCircle = GetChanneler(bot, EAST_CHANNELER);
if (channelerCircle && channelerCircle->IsAlive())
if (channelerCircle)
MarkTargetWithCircle(bot, channelerCircle);
// After first three channelers are dead, wait for Magtheridon to activate
if ((!channelerSquare || !channelerSquare->IsAlive()) &&
(!channelerStar || !channelerStar->IsAlive()) &&
(!channelerCircle || !channelerCircle->IsAlive()))
if (!channelerSquare && !channelerStar && !channelerCircle)
{
const Location& position = MagtheridonsLairLocations::WaitingForMagtheridonPosition;
if (!bot->IsWithinDist2d(position.x, position.y, 2.0f))
const Position& position = WAITING_FOR_MAGTHERIDON_POSITION;
if (!bot->IsWithinDist2d(position.GetPositionX(), position.GetPositionY(), 2.0f))
{
return MoveTo(bot->GetMapId(), position.x, position.y, position.z, false, false, false, false,
return MoveTo(MAGTHERIDON_MAP_ID, position.GetPositionX(), position.GetPositionY(),
position.GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
bot->SetFacingTo(position.orientation);
bot->SetFacingTo(position.GetOrientation());
return true;
}
Creature* currentTarget = nullptr;
std::string rtiName;
if (channelerSquare && channelerSquare->IsAlive())
if (channelerSquare)
{
currentTarget = channelerSquare;
rtiName = "square";
}
else if (channelerStar && channelerStar->IsAlive())
else if (channelerStar)
{
currentTarget = channelerStar;
rtiName = "star";
}
else if (channelerCircle && channelerCircle->IsAlive())
else if (channelerCircle)
{
currentTarget = channelerCircle;
rtiName = "circle";
@@ -70,7 +70,7 @@ bool MagtheridonMainTankAttackFirstThreeChannelersAction::Execute(Event event)
bool MagtheridonFirstAssistTankAttackNWChannelerAction::Execute(Event event)
{
Creature* channelerDiamond = GetChanneler(bot, NORTHWEST_CHANNELER);
if (!channelerDiamond || !channelerDiamond->IsAlive())
if (!channelerDiamond)
return false;
MarkTargetWithDiamond(bot, channelerDiamond);
@@ -81,18 +81,18 @@ bool MagtheridonFirstAssistTankAttackNWChannelerAction::Execute(Event event)
if (channelerDiamond->GetVictim() == bot)
{
const Location& position = MagtheridonsLairLocations::NWChannelerTankPosition;
const Position& position = NW_CHANNELER_TANK_POSITION;
const float maxDistance = 3.0f;
float distanceToPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
if (bot->GetExactDist2d(position.x, position.y) > maxDistance)
if (distanceToPosition > maxDistance)
{
float dX = position.x - bot->GetPositionX();
float dY = position.y - bot->GetPositionY();
float dist = sqrt(dX * dX + dY * dY);
float moveX = bot->GetPositionX() + (dX / dist) * maxDistance;
float moveY = bot->GetPositionY() + (dY / dist) * maxDistance;
float dX = position.GetPositionX() - bot->GetPositionX();
float dY = position.GetPositionY() - bot->GetPositionY();
float moveX = bot->GetPositionX() + (dX / distanceToPosition) * maxDistance;
float moveY = bot->GetPositionY() + (dY / distanceToPosition) * maxDistance;
return MoveTo(bot->GetMapId(), moveX, moveY, position.z, false, false, false, false,
return MoveTo(MAGTHERIDON_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
}
@@ -103,7 +103,7 @@ bool MagtheridonFirstAssistTankAttackNWChannelerAction::Execute(Event event)
bool MagtheridonSecondAssistTankAttackNEChannelerAction::Execute(Event event)
{
Creature* channelerTriangle = GetChanneler(bot, NORTHEAST_CHANNELER);
if (!channelerTriangle || !channelerTriangle->IsAlive())
if (!channelerTriangle)
return false;
MarkTargetWithTriangle(bot, channelerTriangle);
@@ -114,18 +114,18 @@ bool MagtheridonSecondAssistTankAttackNEChannelerAction::Execute(Event event)
if (channelerTriangle->GetVictim() == bot)
{
const Location& position = MagtheridonsLairLocations::NEChannelerTankPosition;
const Position& position = NE_CHANNELER_TANK_POSITION;
const float maxDistance = 3.0f;
float distanceToPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
if (bot->GetExactDist2d(position.x, position.y) > maxDistance)
if (distanceToPosition > maxDistance)
{
float dX = position.x - bot->GetPositionX();
float dY = position.y - bot->GetPositionY();
float dist = sqrt(dX * dX + dY * dY);
float moveX = bot->GetPositionX() + (dX / dist) * maxDistance;
float moveY = bot->GetPositionY() + (dY / dist) * maxDistance;
float dX = position.GetPositionX() - bot->GetPositionX();
float dY = position.GetPositionY() - bot->GetPositionY();
float moveX = bot->GetPositionX() + (dX / distanceToPosition) * maxDistance;
float moveY = bot->GetPositionY() + (dY / distanceToPosition) * maxDistance;
return MoveTo(bot->GetMapId(), moveX, moveY, position.z, false, false, false, false,
return MoveTo(MAGTHERIDON_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
}
@@ -175,7 +175,7 @@ bool MagtheridonMisdirectHellfireChannelers::Execute(Event event)
switch (hunterIndex)
{
case 0:
if (mainTank && channelerStar && channelerStar->IsAlive() &&
if (mainTank && channelerStar &&
channelerStar->GetVictim() != mainTank)
{
if (botAI->CanCastSpell("misdirection", mainTank))
@@ -190,7 +190,7 @@ bool MagtheridonMisdirectHellfireChannelers::Execute(Event event)
break;
case 1:
if (mainTank && channelerCircle && channelerCircle->IsAlive() &&
if (mainTank && channelerCircle &&
channelerCircle->GetVictim() != mainTank)
{
if (botAI->CanCastSpell("misdirection", mainTank))
@@ -215,90 +215,69 @@ bool MagtheridonAssignDPSPriorityAction::Execute(Event event)
{
// Listed in order of priority
Creature* channelerSquare = GetChanneler(bot, SOUTH_CHANNELER);
if (channelerSquare && channelerSquare->IsAlive())
if (channelerSquare)
{
SetRtiTarget(botAI, "square", channelerSquare);
if (bot->GetTarget() != channelerSquare->GetGUID())
{
bot->SetSelection(channelerSquare->GetGUID());
return Attack(channelerSquare);
}
return false;
}
Creature* channelerStar = GetChanneler(bot, WEST_CHANNELER);
if (channelerStar && channelerStar->IsAlive())
if (channelerStar)
{
SetRtiTarget(botAI, "star", channelerStar);
if (bot->GetTarget() != channelerStar->GetGUID())
{
bot->SetSelection(channelerStar->GetGUID());
return Attack(channelerStar);
}
return false;
}
Creature* channelerCircle = GetChanneler(bot, EAST_CHANNELER);
if (channelerCircle && channelerCircle->IsAlive())
if (channelerCircle)
{
SetRtiTarget(botAI, "circle", channelerCircle);
if (bot->GetTarget() != channelerCircle->GetGUID())
{
bot->SetSelection(channelerCircle->GetGUID());
return Attack(channelerCircle);
}
return false;
}
Creature* channelerDiamond = GetChanneler(bot, NORTHWEST_CHANNELER);
if (channelerDiamond && channelerDiamond->IsAlive())
if (channelerDiamond)
{
SetRtiTarget(botAI, "diamond", channelerDiamond);
if (bot->GetTarget() != channelerDiamond->GetGUID())
{
bot->SetSelection(channelerDiamond->GetGUID());
return Attack(channelerDiamond);
}
return false;
}
Creature* channelerTriangle = GetChanneler(bot, NORTHEAST_CHANNELER);
if (channelerTriangle && channelerTriangle->IsAlive())
if (channelerTriangle)
{
SetRtiTarget(botAI, "triangle", channelerTriangle);
if (bot->GetTarget() != channelerTriangle->GetGUID())
{
bot->SetSelection(channelerTriangle->GetGUID());
return Attack(channelerTriangle);
}
return false;
}
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
if (magtheridon && !magtheridon->HasAura(SPELL_SHADOW_CAGE) &&
(!channelerSquare || !channelerSquare->IsAlive()) &&
(!channelerStar || !channelerStar->IsAlive()) &&
(!channelerCircle || !channelerCircle->IsAlive()) &&
(!channelerDiamond || !channelerDiamond->IsAlive()) &&
(!channelerTriangle || !channelerTriangle->IsAlive()))
!channelerSquare && !channelerStar && !channelerCircle &&
!channelerDiamond && !channelerTriangle)
{
SetRtiTarget(botAI, "cross", magtheridon);
if (bot->GetTarget() != magtheridon->GetGUID())
{
bot->SetSelection(magtheridon->GetGUID());
return Attack(magtheridon);
}
}
return false;
@@ -343,15 +322,15 @@ bool MagtheridonWarlockCCBurningAbyssalAction::Execute(Event event)
if (warlockIndex >= 0 && warlockIndex < abyssals.size())
{
Unit* assignedAbyssal = abyssals[warlockIndex];
if (!assignedAbyssal->HasAura(SPELL_BANISH) && botAI->CanCastSpell(SPELL_BANISH, assignedAbyssal, true))
if (!botAI->HasAura("banish", assignedAbyssal) && botAI->CanCastSpell("banish", assignedAbyssal))
return botAI->CastSpell("banish", assignedAbyssal);
}
for (size_t i = warlocks.size(); i < abyssals.size(); ++i)
{
Unit* excessAbyssal = abyssals[i];
if (!excessAbyssal->HasAura(SPELL_BANISH) && !excessAbyssal->HasAura(SPELL_FEAR) &&
botAI->CanCastSpell(SPELL_FEAR, excessAbyssal, true))
if (!botAI->HasAura("banish", excessAbyssal) && !botAI->HasAura("fear", excessAbyssal) &&
botAI->CanCastSpell("fear", excessAbyssal))
return botAI->CastSpell("fear", excessAbyssal);
}
@@ -373,22 +352,20 @@ bool MagtheridonMainTankPositionBossAction::Execute(Event event)
if (magtheridon->GetVictim() == bot)
{
const Location& position = MagtheridonsLairLocations::MagtheridonTankPosition;
const Position& position = MAGTHERIDON_TANK_POSITION;
const float maxDistance = 2.0f;
float distanceToPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
if (bot->GetExactDist2d(position.x, position.y) > maxDistance)
if (distanceToPosition > maxDistance)
{
float dX = position.x - bot->GetPositionX();
float dY = position.y - bot->GetPositionY();
float dist = sqrt(dX * dX + dY * dY);
float moveX = bot->GetPositionX() + (dX / dist) * maxDistance;
float moveY = bot->GetPositionY() + (dY / dist) * maxDistance;
float dX = position.GetPositionX() - bot->GetPositionX();
float dY = position.GetPositionY() - bot->GetPositionY();
float moveX = bot->GetPositionX() + (dX / distanceToPosition) * maxDistance;
float moveY = bot->GetPositionY() + (dY / distanceToPosition) * maxDistance;
return MoveTo(bot->GetMapId(), moveX, moveY, position.z, false, false, false, false,
return MoveTo(MAGTHERIDON_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, true);
}
bot->SetFacingTo(position.orientation);
}
return false;
@@ -440,13 +417,13 @@ bool MagtheridonSpreadRangedAction::Execute(Event event)
}
bool isHealer = botAI->IsHeal(bot);
const Location& center = isHealer
? MagtheridonsLairLocations::HealerSpreadPosition
: MagtheridonsLairLocations::RangedSpreadPosition;
const Position& center = isHealer
? HEALER_SPREAD_POSITION
: RANGED_SPREAD_POSITION;
float maxSpreadRadius = isHealer ? 15.0f : 20.0f;
float centerX = center.x;
float centerY = center.y;
float centerZ = bot->GetPositionZ();
float centerX = center.GetPositionX();
float centerY = center.GetPositionY();
float centerZ = center.GetPositionZ();
const float radiusBuffer = 3.0f;
if (!initialPositions.count(bot->GetGUID()))
@@ -479,7 +456,7 @@ bool MagtheridonSpreadRangedAction::Execute(Event event)
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
return MoveTo(bot->GetMapId(), destX, destY, destZ, false, false, false, false,
return MoveTo(MAGTHERIDON_MAP_ID, destX, destY, destZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
hasReachedInitialPosition[bot->GetGUID()] = true;
@@ -499,7 +476,7 @@ bool MagtheridonSpreadRangedAction::Execute(Event event)
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
return MoveTo(bot->GetMapId(), targetX, targetY, centerZ, false, false, false, false,
return MoveTo(MAGTHERIDON_MAP_ID, targetX, targetY, centerZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
}
@@ -593,7 +570,7 @@ bool MagtheridonUseManticronCubeAction::HandleWaitingPhase(const CubeInfo& cubeI
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(true);
return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, false,
return MoveTo(MAGTHERIDON_MAP_ID, targetX, targetY, targetZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
}
@@ -603,7 +580,7 @@ bool MagtheridonUseManticronCubeAction::HandleWaitingPhase(const CubeInfo& cubeI
float fallbackY = cubeInfo.y + sin(angle) * safeWaitDistance;
float fallbackZ = bot->GetPositionZ();
return MoveTo(bot->GetMapId(), fallbackX, fallbackY, fallbackZ, false, false, false, false,
return MoveTo(MAGTHERIDON_MAP_ID, fallbackX, fallbackY, fallbackZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
@@ -638,7 +615,7 @@ bool MagtheridonUseManticronCubeAction::HandleCubeInteraction(const CubeInfo& cu
bot->AttackStop();
bot->InterruptNonMeleeSpells(true);
return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, false,
return MoveTo(MAGTHERIDON_MAP_ID, targetX, targetY, targetZ, false, false, false, false,
MovementPriority::MOVEMENT_FORCED, true, false);
}
@@ -663,14 +640,14 @@ bool MagtheridonManageTimersAndAssignmentsAction::Execute(Event event)
magtheridon->FindCurrentSpellBySpellId(SPELL_BLAST_NOVA);
bool lastBlastNova = lastBlastNovaState[instanceId];
if (lastBlastNova && !blastNovaActive && IsInstanceTimerManager(botAI, bot))
if (lastBlastNova && !blastNovaActive && IsMechanicTrackerBot(botAI, bot, MAGTHERIDON_MAP_ID, nullptr))
blastNovaTimer[instanceId] = now;
lastBlastNovaState[instanceId] = blastNovaActive;
if (!magtheridon->HasAura(SPELL_SHADOW_CAGE))
{
if (IsInstanceTimerManager(botAI, bot))
if (IsMechanicTrackerBot(botAI, bot, MAGTHERIDON_MAP_ID, nullptr))
{
spreadWaitTimer.try_emplace(instanceId, now);
blastNovaTimer.try_emplace(instanceId, now);
@@ -679,11 +656,12 @@ bool MagtheridonManageTimersAndAssignmentsAction::Execute(Event event)
}
else
{
MagtheridonSpreadRangedAction::initialPositions.clear();
MagtheridonSpreadRangedAction::hasReachedInitialPosition.clear();
botToCubeAssignment.clear();
ObjectGuid guid = bot->GetGUID();
MagtheridonSpreadRangedAction::initialPositions.erase(guid);
MagtheridonSpreadRangedAction::hasReachedInitialPosition.erase(guid);
botToCubeAssignment.erase(guid);
if (IsInstanceTimerManager(botAI, bot))
if (IsMechanicTrackerBot(botAI, bot, MAGTHERIDON_MAP_ID, nullptr))
{
spreadWaitTimer.erase(instanceId);
blastNovaTimer.erase(instanceId);

View File

@@ -6,8 +6,6 @@
#include "AttackAction.h"
#include "MovementActions.h"
using namespace MagtheridonHelpers;
class MagtheridonMainTankAttackFirstThreeChannelersAction : public AttackAction
{
public:
@@ -85,8 +83,8 @@ public:
private:
bool HandleCubeRelease(Unit* magtheridon, GameObject* cube);
bool ShouldActivateCubeLogic(Unit* magtheridon);
bool HandleWaitingPhase(const CubeInfo& cubeInfo);
bool HandleCubeInteraction(const CubeInfo& cubeInfo, GameObject* cube);
bool HandleWaitingPhase(const MagtheridonHelpers::CubeInfo& cubeInfo);
bool HandleCubeInteraction(const MagtheridonHelpers::CubeInfo& cubeInfo, GameObject* cube);
};
class MagtheridonManageTimersAndAssignmentsAction : public Action

View File

@@ -8,6 +8,7 @@
#include "GenericSpellActions.h"
#include "Playerbots.h"
#include "WarlockActions.h"
#include "WipeAction.h"
using namespace MagtheridonHelpers;
@@ -24,10 +25,10 @@ float MagtheridonUseManticronCubeMultiplier::GetValue(Action* action)
auto it = botToCubeAssignment.find(bot->GetGUID());
if (it != botToCubeAssignment.end())
{
if (dynamic_cast<MagtheridonUseManticronCubeAction*>(action))
if (dynamic_cast<WipeAction*>(action))
return 1.0f;
return 0.0f;
else if (!dynamic_cast<MagtheridonUseManticronCubeAction*>(action))
return 0.0f;
}
}
@@ -41,28 +42,31 @@ float MagtheridonWaitToAttackMultiplier::GetValue(Action* action)
if (!magtheridon || magtheridon->HasAura(SPELL_SHADOW_CAGE))
return 1.0f;
if (botAI->IsMainTank(bot))
return 1.0f;
const uint8 dpsWaitSeconds = 6;
auto it = dpsWaitTimer.find(magtheridon->GetMap()->GetInstanceId());
if (it == dpsWaitTimer.end() ||
(time(nullptr) - it->second) < dpsWaitSeconds)
{
if (!botAI->IsMainTank(bot) && (dynamic_cast<AttackAction*>(action) ||
(!botAI->IsHeal(bot) && dynamic_cast<CastSpellAction*>(action))))
if (dynamic_cast<AttackAction*>(action) ||
(!botAI->IsHeal(bot) && dynamic_cast<CastSpellAction*>(action)))
return 0.0f;
}
return 1.0f;
}
// No tank assist for offtanks during the channeler phase
// So they don't try to pull channelers from each other or the main tank
float MagtheridonDisableOffTankAssistMultiplier::GetValue(Action* action)
{
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
Unit* channeler = AI_VALUE2(Unit*, "find target", "hellfire channeler");
if (!magtheridon)
return 1.0f;
if (bot->GetVictim() == nullptr)
return 1.0f;
if ((botAI->IsAssistTankOfIndex(bot, 0) || botAI->IsAssistTankOfIndex(bot, 1)) &&
dynamic_cast<TankAssistAction*>(action))
return 0.0f;

View File

@@ -18,7 +18,7 @@ bool MagtheridonNWChannelerEngagedByFirstAssistTankTrigger::IsActive()
Creature* channelerDiamond = GetChanneler(bot, NORTHWEST_CHANNELER);
return magtheridon && botAI->IsAssistTankOfIndex(bot, 0) &&
channelerDiamond && channelerDiamond->IsAlive();
channelerDiamond;
}
bool MagtheridonNEChannelerEngagedBySecondAssistTankTrigger::IsActive()
@@ -27,7 +27,7 @@ bool MagtheridonNEChannelerEngagedBySecondAssistTankTrigger::IsActive()
Creature* channelerTriangle = GetChanneler(bot, NORTHEAST_CHANNELER);
return magtheridon && botAI->IsAssistTankOfIndex(bot, 1) &&
channelerTriangle && channelerTriangle->IsAlive();
channelerTriangle;
}
bool MagtheridonPullingWestAndEastChannelersTrigger::IsActive()
@@ -38,8 +38,7 @@ bool MagtheridonPullingWestAndEastChannelersTrigger::IsActive()
Creature* channelerCircle = GetChanneler(bot, EAST_CHANNELER);
return magtheridon && bot->getClass() == CLASS_HUNTER &&
((channelerStar && channelerStar->IsAlive()) ||
(channelerCircle && channelerCircle->IsAlive()));
(channelerStar || channelerCircle);
}
bool MagtheridonDeterminingKillOrderTrigger::IsActive()
@@ -51,12 +50,11 @@ bool MagtheridonDeterminingKillOrderTrigger::IsActive()
Creature* channelerTriangle = GetChanneler(bot, NORTHEAST_CHANNELER);
if (!magtheridon || botAI->IsHeal(bot) || botAI->IsMainTank(bot) ||
(botAI->IsAssistTankOfIndex(bot, 0) && channelerDiamond && channelerDiamond->IsAlive()) ||
(botAI->IsAssistTankOfIndex(bot, 1) && channelerTriangle && channelerTriangle->IsAlive()))
(botAI->IsAssistTankOfIndex(bot, 0) && channelerDiamond) ||
(botAI->IsAssistTankOfIndex(bot, 1) && channelerTriangle))
return false;
return (channeler && channeler->IsAlive()) || (magtheridon &&
!magtheridon->HasAura(SPELL_SHADOW_CAGE));
return channeler || (magtheridon && !magtheridon->HasAura(SPELL_SHADOW_CAGE));
}
bool MagtheridonBurningAbyssalSpawnedTrigger::IsActive()
@@ -84,10 +82,8 @@ bool MagtheridonBossEngagedByMainTankTrigger::IsActive()
bool MagtheridonBossEngagedByRangedTrigger::IsActive()
{
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
Unit* channeler = AI_VALUE2(Unit*, "find target", "hellfire channeler");
return magtheridon && botAI->IsRanged(bot) &&
!(channeler && channeler->IsAlive());
return magtheridon && !magtheridon->HasAura(SPELL_SHADOW_CAGE) && botAI->IsRanged(bot);
}
bool MagtheridonIncomingBlastNovaTrigger::IsActive()
@@ -122,7 +118,5 @@ bool MagtheridonIncomingBlastNovaTrigger::IsActive()
bool MagtheridonNeedToManageTimersAndAssignmentsTrigger::IsActive()
{
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
return magtheridon;
return AI_VALUE2(Unit*, "find target", "magtheridon");
}

View File

@@ -1,22 +1,18 @@
#include "RaidMagtheridonHelpers.h"
#include "Creature.h"
#include "GameObject.h"
#include "GroupReference.h"
#include "Map.h"
#include "ObjectGuid.h"
#include "Playerbots.h"
namespace MagtheridonHelpers
{
namespace MagtheridonsLairLocations
{
const Location WaitingForMagtheridonPosition = { 1.359f, 2.048f, -0.406f, 3.135f };
const Location MagtheridonTankPosition = { 22.827f, 2.105f, -0.406f, 3.135f };
const Location NWChannelerTankPosition = { -11.764f, 30.818f, -0.411f, 0.0f };
const Location NEChannelerTankPosition = { -12.490f, -26.211f, -0.411f, 0.0f };
const Location RangedSpreadPosition = { -14.890f, 1.995f, -0.406f, 0.0f };
const Location HealerSpreadPosition = { -2.265f, 1.874f, -0.404f, 0.0f };
}
const Position WAITING_FOR_MAGTHERIDON_POSITION = { 1.359f, 2.048f, -0.406f, 3.135f };
const Position MAGTHERIDON_TANK_POSITION = { 22.827f, 2.105f, -0.406f, 3.135f };
const Position NW_CHANNELER_TANK_POSITION = { -11.764f, 30.818f, -0.411f, 0.0f };
const Position NE_CHANNELER_TANK_POSITION = { -12.490f, -26.211f, -0.411f, 0.0f };
const Position RANGED_SPREAD_POSITION = { -14.890f, 1.995f, -0.406f, 0.0f };
const Position HEALER_SPREAD_POSITION = { -2.265f, 1.874f, -0.404f, 0.0f };
// Identify channelers by their database GUIDs
Creature* GetChanneler(Player* bot, uint32 dbGuid)
@@ -29,63 +25,11 @@ namespace MagtheridonHelpers
if (it == map->GetCreatureBySpawnIdStore().end())
return nullptr;
return it->second;
}
Creature* channeler = it->second;
if (!channeler->IsAlive())
return nullptr;
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId)
{
Group* group = bot->GetGroup();
if (!target || !group)
return;
ObjectGuid currentGuid = group->GetTargetIcon(iconId);
if (currentGuid != target->GetGUID())
group->SetTargetIcon(iconId, bot->GetGUID(), target->GetGUID());
}
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target)
{
if (!target)
return;
std::string currentRti = botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Get();
Unit* currentTarget = botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Get();
if (currentRti != rtiName || currentTarget != target)
{
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set(rtiName);
botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Set(target);
}
}
void MarkTargetWithSquare(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::squareIndex);
}
void MarkTargetWithStar(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::starIndex);
}
void MarkTargetWithCircle(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::circleIndex);
}
void MarkTargetWithDiamond(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::diamondIndex);
}
void MarkTargetWithTriangle(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::triangleIndex);
}
void MarkTargetWithCross(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::crossIndex);
return channeler;
}
const std::vector<uint32> MANTICRON_CUBE_DB_GUIDS = { 43157, 43158, 43159, 43160, 43161 };
@@ -208,19 +152,4 @@ namespace MagtheridonHelpers
return true;
}
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot)
{
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->IsAlive() && botAI->IsDps(member) && GET_PLAYERBOT_AI(member))
return member == bot;
}
}
return true;
}
}

View File

@@ -8,7 +8,6 @@
#include "Group.h"
#include "ObjectGuid.h"
#include "PlayerbotAI.h"
#include "RtiTargetValue.h"
namespace MagtheridonHelpers
{
@@ -19,10 +18,6 @@ namespace MagtheridonHelpers
SPELL_BLAST_NOVA = 30616,
SPELL_SHADOW_GRASP = 30410,
// Warlock
SPELL_BANISH = 18647,
SPELL_FEAR = 6215,
// Hunter
SPELL_MISDIRECTION = 35079,
};
@@ -38,6 +33,7 @@ namespace MagtheridonHelpers
GO_BLAZE = 181832,
};
constexpr uint32 MAGTHERIDON_MAP_ID = 544;
constexpr uint32 SOUTH_CHANNELER = 90978;
constexpr uint32 WEST_CHANNELER = 90979;
constexpr uint32 NORTHWEST_CHANNELER = 90980;
@@ -45,31 +41,14 @@ namespace MagtheridonHelpers
constexpr uint32 NORTHEAST_CHANNELER = 90981;
Creature* GetChanneler(Player* bot, uint32 dbGuid);
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId);
void MarkTargetWithSquare(Player* bot, Unit* target);
void MarkTargetWithStar(Player* bot, Unit* target);
void MarkTargetWithCircle(Player* bot, Unit* target);
void MarkTargetWithDiamond(Player* bot, Unit* target);
void MarkTargetWithTriangle(Player* bot, Unit* target);
void MarkTargetWithCross(Player* bot, Unit* target);
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target);
bool IsSafeFromMagtheridonHazards(PlayerbotAI* botAI, Player* bot, float x, float y, float z);
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot);
struct Location
{
float x, y, z, orientation;
};
namespace MagtheridonsLairLocations
{
extern const Location WaitingForMagtheridonPosition;
extern const Location MagtheridonTankPosition;
extern const Location NWChannelerTankPosition;
extern const Location NEChannelerTankPosition;
extern const Location RangedSpreadPosition;
extern const Location HealerSpreadPosition;
}
extern const Position WAITING_FOR_MAGTHERIDON_POSITION;
extern const Position MAGTHERIDON_TANK_POSITION;
extern const Position NW_CHANNELER_TANK_POSITION;
extern const Position NE_CHANNELER_TANK_POSITION;
extern const Position RANGED_SPREAD_POSITION;
extern const Position HEALER_SPREAD_POSITION;
struct CubeInfo
{

View File

@@ -2,6 +2,7 @@
#define _PLAYERBOT_RAIDMCACTIONCONTEXT_H
#include "Action.h"
#include "BossAuraActions.h"
#include "NamedObjectContext.h"
#include "RaidMcActions.h"

View File

@@ -2,6 +2,7 @@
#define _PLAYERBOT_RAIDMCTRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "BossAuraTriggers.h"
#include "NamedObjectContext.h"
#include "RaidMcTriggers.h"

View File

@@ -0,0 +1,142 @@
#include "RaidBossHelpers.h"
#include "Playerbots.h"
#include "RtiTargetValue.h"
// Functions to mark targets with raid target icons
// Note that these functions do not allow the player to change the icon during the encounter
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId)
{
if (!target)
return;
if (Group* group = bot->GetGroup())
{
ObjectGuid currentGuid = group->GetTargetIcon(iconId);
if (currentGuid != target->GetGUID())
group->SetTargetIcon(iconId, bot->GetGUID(), target->GetGUID());
}
}
void MarkTargetWithSkull(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::skullIndex);
}
void MarkTargetWithSquare(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::squareIndex);
}
void MarkTargetWithStar(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::starIndex);
}
void MarkTargetWithCircle(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::circleIndex);
}
void MarkTargetWithDiamond(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::diamondIndex);
}
void MarkTargetWithTriangle(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::triangleIndex);
}
void MarkTargetWithCross(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::crossIndex);
}
void MarkTargetWithMoon(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::moonIndex);
}
// For bots to set their raid target icon to the specified icon on the specified target
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target)
{
if (!target)
return;
std::string currentRti = botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Get();
Unit* currentTarget = botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Get();
if (currentRti != rtiName || currentTarget != target)
{
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set(rtiName);
botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Set(target);
}
}
// Return the first alive DPS bot in the specified instance map, excluding any specified bot
// Intended for purposes of storing and erasing timers and trackers in associative containers
bool IsMechanicTrackerBot(PlayerbotAI* botAI, Player* bot, uint32 mapId, Player* exclude)
{
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member->GetMapId() != mapId ||
!GET_PLAYERBOT_AI(member) || !botAI->IsDps(member))
continue;
if (member != exclude)
return member == bot;
}
}
return false;
}
// Return the first matching alive unit from a cell search of nearby npcs
// More responsive than "find target," but performance cost is much higher
// Re: using the third parameter (false by default), some units are never considered
// to be in combat (e.g., totems)
Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry, bool requireInCombat)
{
auto const& npcs =
botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
for (auto const& npcGuid : npcs)
{
Unit* unit = botAI->GetUnit(npcGuid);
if (unit && unit->IsAlive() && unit->GetEntry() == entry)
{
if (!requireInCombat || unit->IsInCombat())
return unit;
}
}
return nullptr;
}
// Return the nearest alive player (human or bot) within the specified radius
Unit* GetNearestPlayerInRadius(Player* bot, float radius)
{
Unit* nearestPlayer = nullptr;
float nearestDistance = radius;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref != nullptr; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member == bot)
continue;
float distance = bot->GetExactDist2d(member);
if (distance < nearestDistance)
{
nearestDistance = distance;
nearestPlayer = member;
}
}
}
return nearestPlayer;
}

View File

@@ -0,0 +1,21 @@
#ifndef _PLAYERBOT_RAIDBOSSHELPERS_H_
#define _PLAYERBOT_RAIDBOSSHELPERS_H_
#include "AiObject.h"
#include "Unit.h"
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId);
void MarkTargetWithSkull(Player* bot, Unit* target);
void MarkTargetWithSquare(Player* bot, Unit* target);
void MarkTargetWithStar(Player* bot, Unit* target);
void MarkTargetWithCircle(Player* bot, Unit* target);
void MarkTargetWithDiamond(Player* bot, Unit* target);
void MarkTargetWithTriangle(Player* bot, Unit* target);
void MarkTargetWithCross(Player* bot, Unit* target);
void MarkTargetWithMoon(Player* bot, Unit* target);
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target);
bool IsMechanicTrackerBot(PlayerbotAI* botAI, Player* bot, uint32 mapId, Player* exclude = nullptr);
Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry, bool requireInCombat = false);
Unit* GetNearestPlayerInRadius(Player* bot, float radius);
#endif

View File

@@ -29,7 +29,7 @@ public:
creators["wotlk-os"] = &RaidStrategyContext::wotlk_os;
creators["wotlk-eoe"] = &RaidStrategyContext::wotlk_eoe;
creators["voa"] = &RaidStrategyContext::voa;
creators["uld"] = &RaidStrategyContext::uld;
creators["ulduar"] = &RaidStrategyContext::ulduar;
creators["onyxia"] = &RaidStrategyContext::onyxia;
creators["icc"] = &RaidStrategyContext::icc;
}
@@ -45,7 +45,7 @@ private:
static Strategy* wotlk_eoe(PlayerbotAI* botAI) { return new RaidEoEStrategy(botAI); }
static Strategy* voa(PlayerbotAI* botAI) { return new RaidVoAStrategy(botAI); }
static Strategy* onyxia(PlayerbotAI* botAI) { return new RaidOnyxiaStrategy(botAI); }
static Strategy* uld(PlayerbotAI* botAI) { return new RaidUlduarStrategy(botAI); }
static Strategy* ulduar(PlayerbotAI* botAI) { return new RaidUlduarStrategy(botAI); }
static Strategy* icc(PlayerbotAI* botAI) { return new RaidIccStrategy(botAI); }
};

View File

@@ -11,7 +11,6 @@
#include "GameObject.h"
#include "Group.h"
#include "LastMovementValue.h"
#include "ObjectDefines.h"
#include "ObjectGuid.h"
#include "PlayerbotAI.h"
#include "PlayerbotAIConfig.h"
@@ -19,11 +18,9 @@
#include "Position.h"
#include "RaidUlduarBossHelper.h"
#include "RaidUlduarScripts.h"
#include "RaidUlduarStrategy.h"
#include "RtiValue.h"
#include "ScriptedCreature.h"
#include "ServerFacade.h"
#include "SharedDefines.h"
#include "Unit.h"
#include "Vehicle.h"
#include <RtiTargetValue.h>

View File

@@ -1,28 +0,0 @@
#include "RaidUlduarMultipliers.h"
#include "ChooseTargetActions.h"
#include "DKActions.h"
#include "DruidActions.h"
#include "DruidBearActions.h"
#include "FollowActions.h"
#include "GenericActions.h"
#include "GenericSpellActions.h"
#include "HunterActions.h"
#include "MageActions.h"
#include "MovementActions.h"
#include "PaladinActions.h"
#include "PriestActions.h"
#include "RaidUlduarActions.h"
#include "ReachTargetActions.h"
#include "RogueActions.h"
#include "ScriptedCreature.h"
#include "ShamanActions.h"
#include "UseMeetingStoneAction.h"
#include "WarriorActions.h"
float FlameLeviathanMultiplier::GetValue(Action* action)
{
// if (dynamic_cast<FleeAction*>(action))
// return 0.0f;
return 1.0f;
}

View File

@@ -1,17 +0,0 @@
#ifndef _PLAYERRBOT_RAIDULDUARMULTIPLIERS_H_
#define _PLAYERRBOT_RAIDULDUARMULTIPLIERS_H_
#include "Multiplier.h"
#include "Ai/Raid/Ulduar/RaidUlduarBossHelper.h"
class FlameLeviathanMultiplier : public Multiplier
{
public:
FlameLeviathanMultiplier(PlayerbotAI* ai) : Multiplier(ai, "flame leviathan") {}
public:
virtual float GetValue(Action* action);
};
#endif

View File

@@ -1,169 +0,0 @@
#ifndef _PLAYERBOT_RAIDULDUARBOSSHELPER_H
#define _PLAYERBOT_RAIDULDUARBOSSHELPER_H
#include <string>
#include <unordered_map>
#include <vector>
#include <cmath>
#include <ctime>
#include <limits>
#include "AiObject.h"
#include "AiObjectContext.h"
#include "EventMap.h"
#include "Log.h"
#include "ObjectGuid.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "ScriptedCreature.h"
#include "SharedDefines.h"
const uint32 ULDUAR_MAP_ID = 603;
class RazorscaleBossHelper : public AiObject
{
public:
// Enums and constants specific to Razorscale
enum RazorscaleUnits : uint32
{
UNIT_RAZORSCALE = 33186,
UNIT_DARK_RUNE_SENTINEL = 33846,
UNIT_DARK_RUNE_WATCHER = 33453,
UNIT_DARK_RUNE_GUARDIAN = 33388,
UNIT_DEVOURING_FLAME = 34188,
};
enum RazorscaleGameObjects : uint32
{
GO_RAZORSCALE_HARPOON_1 = 194519,
GO_RAZORSCALE_HARPOON_2 = 194541,
GO_RAZORSCALE_HARPOON_3 = 194542,
GO_RAZORSCALE_HARPOON_4 = 194543,
};
enum RazorscaleSpells : uint32
{
SPELL_CHAIN_1 = 49679,
SPELL_CHAIN_2 = 49682,
SPELL_CHAIN_3 = 49683,
SPELL_CHAIN_4 = 49684,
SPELL_SENTINEL_WHIRLWIND = 63806,
SPELL_STUN_AURA = 62794,
SPELL_FUSEARMOR = 64771
};
static constexpr uint32 FUSEARMOR_THRESHOLD = 2;
// Constants for arena parameters
static constexpr float RAZORSCALE_FLYING_Z_THRESHOLD = 440.0f;
static constexpr float RAZORSCALE_ARENA_CENTER_X = 587.54f;
static constexpr float RAZORSCALE_ARENA_CENTER_Y = -175.04f;
static constexpr float RAZORSCALE_ARENA_RADIUS = 30.0f;
// Harpoon cooldown (seconds)
static constexpr time_t HARPOON_COOLDOWN_DURATION = 5;
// Structure for harpoon data
struct HarpoonData
{
uint32 gameObjectEntry;
uint32 chainSpellId;
};
explicit RazorscaleBossHelper(PlayerbotAI* botAI)
: AiObject(botAI), _boss(nullptr) {}
bool UpdateBossAI();
Unit* GetBoss() const;
bool IsGroundPhase() const;
bool IsFlyingPhase() const;
bool IsHarpoonFired(uint32 chainSpellId) const;
static bool IsHarpoonReady(GameObject* harpoonGO);
static void SetHarpoonOnCooldown(GameObject* harpoonGO);
GameObject* FindNearestHarpoon(float x, float y, float z) const;
static const std::vector<HarpoonData>& GetHarpoonData();
void AssignRolesBasedOnHealth();
bool AreRolesAssigned() const;
bool CanSwapRoles() const;
private:
Unit* _boss;
// A map to track the last role swap *per bot* by their GUID
static std::unordered_map<ObjectGuid, std::time_t> _lastRoleSwapTime;
// The cooldown that applies to every bot
static const std::time_t _roleSwapCooldown = 10;
static std::unordered_map<ObjectGuid, time_t> _harpoonCooldowns;
};
// template <class BossAiType>
// class GenericBossHelper : public AiObject
// {
// public:
// GenericBossHelper(PlayerbotAI* botAI, std::string name) : AiObject(botAI), _name(name) {}
// virtual bool UpdateBossAI()
// {
// if (!bot->IsInCombat())
// {
// _unit = nullptr;
// }
// if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive()))
// {
// _unit = nullptr;
// }
// if (!_unit)
// {
// _unit = AI_VALUE2(Unit*, "find target", _name);
// if (!_unit)
// {
// return false;
// }
// _target = _unit->ToCreature();
// if (!_target)
// {
// return false;
// }
// _ai = dynamic_cast<BossAiType*>(_target->GetAI());
// if (!_ai)
// {
// return false;
// }
// _event_map = &_ai->events;
// if (!_event_map)
// {
// return false;
// }
// }
// if (!_event_map)
// {
// return false;
// }
// _timer = _event_map->GetTimer();
// return true;
// }
// virtual void Reset()
// {
// _unit = nullptr;
// _target = nullptr;
// _ai = nullptr;
// _event_map = nullptr;
// _timer = 0;
// }
// protected:
// std::string _name;
// Unit* _unit = nullptr;
// Creature* _target = nullptr;
// BossAiType* _ai = nullptr;
// EventMap* _event_map = nullptr;
// uint32 _timer = 0;
// };
#endif

View File

@@ -1,7 +1,5 @@
#include "RaidUlduarStrategy.h"
#include "RaidUlduarMultipliers.h"
void RaidUlduarStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
//
@@ -316,8 +314,3 @@ void RaidUlduarStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
"yogg-saron phase 3 positioning trigger",
{ NextAction("yogg-saron phase 3 positioning action", ACTION_RAID) }));
}
void RaidUlduarStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{
multipliers.push_back(new FlameLeviathanMultiplier(botAI));
}

View File

@@ -3,16 +3,14 @@
#define _PLAYERBOT_RAIDULDUARSTRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h"
class RaidUlduarStrategy : public Strategy
{
public:
RaidUlduarStrategy(PlayerbotAI* ai) : Strategy(ai) {}
virtual std::string const getName() override { return "uld"; }
virtual std::string const getName() override { return "ulduar"; }
virtual void InitTriggers(std::vector<TriggerNode*>& triggers) override;
virtual void InitMultipliers(std::vector<Multiplier*>& multipliers) override;
};
#endif

View File

@@ -1634,7 +1634,7 @@ bool VezaxShadowCrashTrigger::IsActive()
return false;
}
return botAI->HasAura(SPELL_SHADOW_CRASH, bot);
return botAI->HasAura(SPELL_VEZAX_SHADOW_CRASH, bot);
}
bool VezaxMarkOfTheFacelessTrigger::IsActive()

View File

@@ -3,187 +3,9 @@
#include "EventMap.h"
#include "GenericTriggers.h"
#include "PlayerbotAIConfig.h"
#include "RaidUlduarBossHelper.h"
#include "Trigger.h"
enum UlduarIDs
{
// Iron Assembly
SPELL_LIGHTNING_TENDRILS_10_MAN = 61887,
SPELL_LIGHTNING_TENDRILS_25_MAN = 63486,
SPELL_OVERLOAD_10_MAN = 61869,
SPELL_OVERLOAD_25_MAN = 63481,
SPELL_OVERLOAD_10_MAN_2 = 63485,
SPELL_OVERLOAD_25_MAN_2 = 61886,
SPELL_RUNE_OF_POWER = 64320,
// Kologarn
NPC_RIGHT_ARM = 32934,
NPC_RUBBLE = 33768,
SPELL_CRUNCH_ARMOR = 64002,
SPELL_FOCUSED_EYEBEAM_10_2 = 63346,
SPELL_FOCUSED_EYEBEAM_10 = 63347,
SPELL_FOCUSED_EYEBEAM_25_2 = 63976,
SPELL_FOCUSED_EYEBEAM_25 = 63977,
// Hodir
NPC_SNOWPACKED_ICICLE = 33174,
NPC_TOASTY_FIRE = 33342,
SPELL_FLASH_FREEZE = 61968,
SPELL_BITING_COLD_PLAYER_AURA = 62039,
// Freya
NPC_SNAPLASHER = 32916,
NPC_STORM_LASHER = 32919,
NPC_DETONATING_LASHER = 32918,
NPC_ANCIENT_WATER_SPIRIT = 33202,
NPC_ANCIENT_CONSERVATOR = 33203,
NPC_HEALTHY_SPORE = 33215,
NPC_EONARS_GIFT = 33228,
GOBJECT_NATURE_BOMB = 194902,
// Thorim
NPC_DARK_RUNE_ACOLYTE_I = 32886,
NPC_CAPTURED_MERCENARY_SOLDIER_ALLY = 32885,
NPC_CAPTURED_MERCENARY_SOLDIER_HORDE = 32883,
NPC_CAPTURED_MERCENARY_CAPTAIN_ALLY = 32908,
NPC_CAPTURED_MERCENARY_CAPTAIN_HORDE = 32907,
NPC_JORMUNGAR_BEHEMOT = 32882,
NPC_DARK_RUNE_WARBRINGER = 32877,
NPC_DARK_RUNE_EVOKER = 32878,
NPC_DARK_RUNE_CHAMPION = 32876,
NPC_DARK_RUNE_COMMONER = 32904,
NPC_IRON_RING_GUARD = 32874,
NPC_RUNIC_COLOSSUS = 32872,
NPC_ANCIENT_RUNE_GIANT = 32873,
NPC_DARK_RUNE_ACOLYTE_G = 33110,
NPC_IRON_HONOR_GUARD = 32875,
SPELL_UNBALANCING_STRIKE = 62130,
// Mimiron
NPC_LEVIATHAN_MKII = 33432,
NPC_VX001 = 33651,
NPC_AERIAL_COMMAND_UNIT = 33670,
NPC_BOMB_BOT = 33836,
NPC_ROCKET_STRIKE_N = 34047,
NPC_ASSAULT_BOT = 34057,
NPC_PROXIMITY_MINE = 34362,
SPELL_P3WX2_LASER_BARRAGE_1 = 63293,
SPELL_P3WX2_LASER_BARRAGE_2 = 63297,
SPELL_SPINNING_UP = 63414,
SPELL_SHOCK_BLAST = 63631,
SPELL_P3WX2_LASER_BARRAGE_3 = 64042,
SPELL_P3WX2_LASER_BARRAGE_AURA_1 = 63274,
SPELL_P3WX2_LASER_BARRAGE_AURA_2 = 63300,
// General Vezax
SPELL_MARK_OF_THE_FACELESS = 63276,
SPELL_SHADOW_CRASH = 63277,
// Yogg-Saron
ACTION_ILLUSION_DRAGONS = 1,
ACTION_ILLUSION_ICECROWN = 2,
ACTION_ILLUSION_STORMWIND = 3,
NPC_GUARDIAN_OF_YS = 33136,
NPC_YOGG_SARON = 33288,
NPC_OMINOUS_CLOUD = 33292,
NPC_RUBY_CONSORT = 33716,
NPC_AZURE_CONSORT = 33717,
NPC_BRONZE_CONSORT = 33718,
NPC_EMERALD_CONSORT = 33719,
NPC_OBSIDIAN_CONSORT = 33720,
NPC_ALEXTRASZA = 33536,
NPC_MALYGOS_ILLUSION = 33535,
NPC_NELTHARION = 33523,
NPC_YSERA = 33495,
GO_DRAGON_SOUL = 194462,
NPC_SARA_PHASE_1 = 33134,
NPC_LICH_KING_ILLUSION = 33441,
NPC_IMMOLATED_CHAMPION = 33442,
NPC_SUIT_OF_ARMOR = 33433,
NPC_GARONA = 33436,
NPC_KING_LLANE = 33437,
NPC_DEATHSWORN_ZEALOT = 33567,
NPC_INFLUENCE_TENTACLE = 33943,
NPC_DEATH_ORB = 33882,
NPC_BRAIN = 33890,
NPC_CRUSHER_TENTACLE = 33966,
NPC_CONSTRICTOR_TENTACLE = 33983,
NPC_CORRUPTOR_TENTACLE = 33985,
NPC_IMMORTAL_GUARDIAN = 33988,
NPC_LAUGHING_SKULL = 33990,
NPC_SANITY_WELL = 33991,
NPC_DESCEND_INTO_MADNESS = 34072,
NPC_MARKED_IMMORTAL_GUARDIAN = 36064,
SPELL_SANITY = 63050,
SPELL_BRAIN_LINK = 63802,
SPELL_MALADY_OF_THE_MIND = 63830,
SPELL_SHADOW_BARRIER = 63894,
SPELL_TELEPORT_TO_CHAMBER = 63997,
SPELL_TELEPORT_TO_ICECROWN = 63998,
SPELL_TELEPORT_TO_STORMWIND = 63989,
SPELL_TELEPORT_BACK = 63992,
SPELL_CANCEL_ILLUSION_AURA = 63993,
SPELL_INDUCE_MADNESS = 64059,
SPELL_LUNATIC_GAZE_YS = 64163,
GO_FLEE_TO_THE_SURFACE_PORTAL = 194625,
// Buffs
SPELL_FROST_TRAP = 13809
};
const float ULDUAR_KOLOGARN_AXIS_Z_PATHING_ISSUE_DETECT = 420.0f;
const float ULDUAR_KOLOGARN_EYEBEAM_RADIUS = 3.0f;
const float ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD = 429.6094f;
const float ULDUAR_THORIM_AXIS_Z_PATHING_ISSUE_DETECT = 410.0f;
const float ULDUAR_AURIAYA_AXIS_Z_PATHING_ISSUE_DETECT = 410.0f;
const float ULDUAR_YOGG_SARON_BOSS_ROOM_AXIS_Z_PATHING_ISSUE_DETECT = 300.0f;
const float ULDUAR_YOGG_SARON_BRAIN_ROOM_AXIS_Z_PATHING_ISSUE_DETECT = 200.0f;
const float ULDUAR_YOGG_SARON_STORMWIND_KEEPER_RADIUS = 150.0f;
const float ULDUAR_YOGG_SARON_ICECROWN_CITADEL_RADIUS = 150.0f;
const float ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_RADIUS = 150.0f;
const float ULDUAR_YOGG_SARON_BRAIN_ROOM_RADIUS = 50.0f;
const Position ULDUAR_THORIM_NEAR_ARENA_CENTER = Position(2134.9854f, -263.11853f, 419.8465f);
const Position ULDUAR_THORIM_NEAR_ENTRANCE_POSITION = Position(2172.4355f, -258.27957f, 418.47162f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_6_YARDS_1 = Position(2237.6187f, -265.08844f, 412.17548f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_6_YARDS_2 = Position(2237.2498f, -275.81122f, 412.17548f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_5_YARDS_1 = Position(2236.895f, -294.62448f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_10_YARDS_1 = Position(2242.1162f, -310.15308f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_10_YARDS_2 = Position(2242.018f, -318.66003f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_10_YARDS_3 = Position(2242.1904f, -329.0533f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_6_YARDS_1 = Position(2219.5417f, -264.77167f, 412.17548f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_6_YARDS_2 = Position(2217.446f, -275.85248f, 412.17548f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_5_YARDS_1 = Position(2217.8877f, -295.01193f, 412.13434f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_10_YARDS_1 = Position(2212.193f, -307.44992f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_10_YARDS_2 = Position(2212.1353f, -318.20795f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_10_YARDS_3 = Position(2212.1956f, -328.0144f, 412.1348f);
const Position ULDUAR_THORIM_JUMP_END_POINT = Position(2137.8818f, -278.18942f, 419.66653f);
const Position ULDUAR_THORIM_PHASE2_TANK_SPOT = Position(2134.8572f, -287.0291f, 419.4935f);
const Position ULDUAR_THORIM_PHASE2_RANGE1_SPOT = Position(2112.8752f, -267.69305f, 419.52814f);
const Position ULDUAR_THORIM_PHASE2_RANGE2_SPOT = Position(2134.1296f, -257.3316f, 419.8462f);
const Position ULDUAR_THORIM_PHASE2_RANGE3_SPOT = Position(2156.798f, -267.57434f, 419.52722f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE1RANGE_SPOT = Position(2753.708f, 2583.9617f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE1MELEE_SPOT = Position(2746.9792f, 2573.6716f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE2RANGE_SPOT = Position(2727.7224f, 2569.527f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE2MELEE_SPOT = Position(2739.4746f, 2569.4106f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE3RANGE_SPOT = Position(2754.1294f, 2553.9954f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE3MELEE_SPOT = Position(2746.8513f, 2565.4263f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE4_TANK_SPOT = Position(2744.5754f, 2570.8657f, 364.3138f);
const Position ULDUAR_VEZAX_MARK_OF_THE_FACELESS_SPOT = Position(1913.6501f, 122.93989f, 342.38083f);
const Position ULDUAR_YOGG_SARON_MIDDLE = Position(1980.28f, -25.5868f, 329.397f);
const Position ULDUAR_YOGG_SARON_STORMWIND_KEEPER_MIDDLE = Position(1927.1511f, 68.507256f, 242.37657f);
const Position ULDUAR_YOGG_SARON_ICECROWN_CITADEL_MIDDLE = Position(1925.6553f, -121.59296f, 239.98965f);
const Position ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_MIDDLE = Position(2104.5667f, -25.509348f, 242.64679f);
const Position ULDUAR_YOGG_SARON_BRAIN_ROOM_MIDDLE = Position(1980.1971f, -27.854689f, 236.06789f);
const Position ULDUAR_YOGG_SARON_STORMWIND_KEEPER_ENTRANCE = Position(1954.06f, 21.66f, 239.71f);
const Position ULDUAR_YOGG_SARON_ICECROWN_CITADEL_ENTRANCE = Position(1950.11f, -79.284f, 239.98982f);
const Position ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_ENTRANCE = Position(2048.63f, -25.5f, 239.72f);
const Position ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT = Position(1998.5377f, -22.90317f, 324.8895f);
const Position ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT = Position(2018.7628f, -18.896868f, 327.07245f);
//
// Flame Levi
//

View File

@@ -1,4 +1,3 @@
#include "ChatHelper.h"
#include "RaidUlduarBossHelper.h"
#include "ObjectAccessor.h"
#include "GameObject.h"
@@ -9,6 +8,44 @@
#include "Playerbots.h"
#include "World.h"
const Position ULDUAR_THORIM_NEAR_ARENA_CENTER = Position(2134.9854f, -263.11853f, 419.8465f);
const Position ULDUAR_THORIM_NEAR_ENTRANCE_POSITION = Position(2172.4355f, -258.27957f, 418.47162f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_6_YARDS_1 = Position(2237.6187f, -265.08844f, 412.17548f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_6_YARDS_2 = Position(2237.2498f, -275.81122f, 412.17548f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_5_YARDS_1 = Position(2236.895f, -294.62448f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_10_YARDS_1 = Position(2242.1162f, -310.15308f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_10_YARDS_2 = Position(2242.018f, -318.66003f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_10_YARDS_3 = Position(2242.1904f, -329.0533f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_6_YARDS_1 = Position(2219.5417f, -264.77167f, 412.17548f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_6_YARDS_2 = Position(2217.446f, -275.85248f, 412.17548f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_5_YARDS_1 = Position(2217.8877f, -295.01193f, 412.13434f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_10_YARDS_1 = Position(2212.193f, -307.44992f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_10_YARDS_2 = Position(2212.1353f, -318.20795f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_10_YARDS_3 = Position(2212.1956f, -328.0144f, 412.1348f);
const Position ULDUAR_THORIM_JUMP_END_POINT = Position(2137.8818f, -278.18942f, 419.66653f);
const Position ULDUAR_THORIM_PHASE2_TANK_SPOT = Position(2134.8572f, -287.0291f, 419.4935f);
const Position ULDUAR_THORIM_PHASE2_RANGE1_SPOT = Position(2112.8752f, -267.69305f, 419.52814f);
const Position ULDUAR_THORIM_PHASE2_RANGE2_SPOT = Position(2134.1296f, -257.3316f, 419.8462f);
const Position ULDUAR_THORIM_PHASE2_RANGE3_SPOT = Position(2156.798f, -267.57434f, 419.52722f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE1RANGE_SPOT = Position(2753.708f, 2583.9617f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE1MELEE_SPOT = Position(2746.9792f, 2573.6716f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE2RANGE_SPOT = Position(2727.7224f, 2569.527f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE2MELEE_SPOT = Position(2739.4746f, 2569.4106f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE3RANGE_SPOT = Position(2754.1294f, 2553.9954f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE3MELEE_SPOT = Position(2746.8513f, 2565.4263f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE4_TANK_SPOT = Position(2744.5754f, 2570.8657f, 364.3138f);
const Position ULDUAR_VEZAX_MARK_OF_THE_FACELESS_SPOT = Position(1913.6501f, 122.93989f, 342.38083f);
const Position ULDUAR_YOGG_SARON_MIDDLE = Position(1980.28f, -25.5868f, 329.397f);
const Position ULDUAR_YOGG_SARON_STORMWIND_KEEPER_MIDDLE = Position(1927.1511f, 68.507256f, 242.37657f);
const Position ULDUAR_YOGG_SARON_ICECROWN_CITADEL_MIDDLE = Position(1925.6553f, -121.59296f, 239.98965f);
const Position ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_MIDDLE = Position(2104.5667f, -25.509348f, 242.64679f);
const Position ULDUAR_YOGG_SARON_BRAIN_ROOM_MIDDLE = Position(1980.1971f, -27.854689f, 236.06789f);
const Position ULDUAR_YOGG_SARON_STORMWIND_KEEPER_ENTRANCE = Position(1954.06f, 21.66f, 239.71f);
const Position ULDUAR_YOGG_SARON_ICECROWN_CITADEL_ENTRANCE = Position(1950.11f, -79.284f, 239.98982f);
const Position ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_ENTRANCE = Position(2048.63f, -25.5f, 239.72f);
const Position ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT = Position(1998.5377f, -22.90317f, 324.8895f);
const Position ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT = Position(2018.7628f, -18.896868f, 327.07245f);
// Prevent harpoon spam
std::unordered_map<ObjectGuid, time_t> RazorscaleBossHelper::_harpoonCooldowns;
// Prevent role assignment spam

View File

@@ -0,0 +1,341 @@
#ifndef _PLAYERBOT_RAIDULDUARBOSSHELPER_H
#define _PLAYERBOT_RAIDULDUARBOSSHELPER_H
#include <string>
#include <unordered_map>
#include <vector>
#include "AiObject.h"
#include "AiObjectContext.h"
#include "EventMap.h"
#include "ObjectGuid.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "ScriptedCreature.h"
constexpr uint32 ULDUAR_MAP_ID = 603;
enum UlduarIDs
{
// Iron Assembly
SPELL_LIGHTNING_TENDRILS_10_MAN = 61887,
SPELL_LIGHTNING_TENDRILS_25_MAN = 63486,
SPELL_OVERLOAD_10_MAN = 61869,
SPELL_OVERLOAD_25_MAN = 63481,
SPELL_OVERLOAD_10_MAN_2 = 63485,
SPELL_OVERLOAD_25_MAN_2 = 61886,
SPELL_RUNE_OF_POWER = 64320,
// Kologarn
NPC_RIGHT_ARM = 32934,
NPC_RUBBLE = 33768,
SPELL_CRUNCH_ARMOR = 64002,
SPELL_FOCUSED_EYEBEAM_10_2 = 63346,
SPELL_FOCUSED_EYEBEAM_10 = 63347,
SPELL_FOCUSED_EYEBEAM_25_2 = 63976,
SPELL_FOCUSED_EYEBEAM_25 = 63977,
// Hodir
NPC_SNOWPACKED_ICICLE = 33174,
NPC_TOASTY_FIRE = 33342,
SPELL_FLASH_FREEZE = 61968,
SPELL_BITING_COLD_PLAYER_AURA = 62039,
// Freya
NPC_SNAPLASHER = 32916,
NPC_STORM_LASHER = 32919,
NPC_DETONATING_LASHER = 32918,
NPC_ANCIENT_WATER_SPIRIT = 33202,
NPC_ANCIENT_CONSERVATOR = 33203,
NPC_HEALTHY_SPORE = 33215,
NPC_EONARS_GIFT = 33228,
GOBJECT_NATURE_BOMB = 194902,
// Thorim
NPC_DARK_RUNE_ACOLYTE_I = 32886,
NPC_CAPTURED_MERCENARY_SOLDIER_ALLY = 32885,
NPC_CAPTURED_MERCENARY_SOLDIER_HORDE = 32883,
NPC_CAPTURED_MERCENARY_CAPTAIN_ALLY = 32908,
NPC_CAPTURED_MERCENARY_CAPTAIN_HORDE = 32907,
NPC_JORMUNGAR_BEHEMOT = 32882,
NPC_DARK_RUNE_WARBRINGER = 32877,
NPC_DARK_RUNE_EVOKER = 32878,
NPC_DARK_RUNE_CHAMPION = 32876,
NPC_DARK_RUNE_COMMONER = 32904,
NPC_IRON_RING_GUARD = 32874,
NPC_RUNIC_COLOSSUS = 32872,
NPC_ANCIENT_RUNE_GIANT = 32873,
NPC_DARK_RUNE_ACOLYTE_G = 33110,
NPC_IRON_HONOR_GUARD = 32875,
SPELL_UNBALANCING_STRIKE = 62130,
// Mimiron
NPC_LEVIATHAN_MKII = 33432,
NPC_VX001 = 33651,
NPC_AERIAL_COMMAND_UNIT = 33670,
NPC_BOMB_BOT = 33836,
NPC_ROCKET_STRIKE_N = 34047,
NPC_ASSAULT_BOT = 34057,
NPC_PROXIMITY_MINE = 34362,
SPELL_P3WX2_LASER_BARRAGE_1 = 63293,
SPELL_P3WX2_LASER_BARRAGE_2 = 63297,
SPELL_SPINNING_UP = 63414,
SPELL_SHOCK_BLAST = 63631,
SPELL_P3WX2_LASER_BARRAGE_3 = 64042,
SPELL_P3WX2_LASER_BARRAGE_AURA_1 = 63274,
SPELL_P3WX2_LASER_BARRAGE_AURA_2 = 63300,
// General Vezax
SPELL_MARK_OF_THE_FACELESS = 63276,
SPELL_VEZAX_SHADOW_CRASH = 63277,
// Yogg-Saron
ACTION_ILLUSION_DRAGONS = 1,
ACTION_ILLUSION_ICECROWN = 2,
ACTION_ILLUSION_STORMWIND = 3,
NPC_GUARDIAN_OF_YS = 33136,
NPC_YOGG_SARON = 33288,
NPC_OMINOUS_CLOUD = 33292,
NPC_RUBY_CONSORT = 33716,
NPC_AZURE_CONSORT = 33717,
NPC_BRONZE_CONSORT = 33718,
NPC_EMERALD_CONSORT = 33719,
NPC_OBSIDIAN_CONSORT = 33720,
NPC_ALEXTRASZA = 33536,
NPC_MALYGOS_ILLUSION = 33535,
NPC_NELTHARION = 33523,
NPC_YSERA = 33495,
GO_DRAGON_SOUL = 194462,
NPC_SARA_PHASE_1 = 33134,
NPC_LICH_KING_ILLUSION = 33441,
NPC_IMMOLATED_CHAMPION = 33442,
NPC_SUIT_OF_ARMOR = 33433,
NPC_GARONA = 33436,
NPC_KING_LLANE = 33437,
NPC_DEATHSWORN_ZEALOT = 33567,
NPC_INFLUENCE_TENTACLE = 33943,
NPC_DEATH_ORB = 33882,
NPC_BRAIN = 33890,
NPC_CRUSHER_TENTACLE = 33966,
NPC_CONSTRICTOR_TENTACLE = 33983,
NPC_CORRUPTOR_TENTACLE = 33985,
NPC_IMMORTAL_GUARDIAN = 33988,
NPC_LAUGHING_SKULL = 33990,
NPC_SANITY_WELL = 33991,
NPC_DESCEND_INTO_MADNESS = 34072,
NPC_MARKED_IMMORTAL_GUARDIAN = 36064,
SPELL_SANITY = 63050,
SPELL_BRAIN_LINK = 63802,
SPELL_MALADY_OF_THE_MIND = 63830,
SPELL_SHADOW_BARRIER = 63894,
SPELL_TELEPORT_TO_CHAMBER = 63997,
SPELL_TELEPORT_TO_ICECROWN = 63998,
SPELL_TELEPORT_TO_STORMWIND = 63989,
SPELL_TELEPORT_BACK = 63992,
SPELL_CANCEL_ILLUSION_AURA = 63993,
SPELL_INDUCE_MADNESS = 64059,
SPELL_LUNATIC_GAZE_YS = 64163,
GO_FLEE_TO_THE_SURFACE_PORTAL = 194625,
// Buffs
SPELL_FROST_TRAP = 13809
};
constexpr float ULDUAR_KOLOGARN_AXIS_Z_PATHING_ISSUE_DETECT = 420.0f;
constexpr float ULDUAR_KOLOGARN_EYEBEAM_RADIUS = 3.0f;
constexpr float ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD = 429.6094f;
constexpr float ULDUAR_THORIM_AXIS_Z_PATHING_ISSUE_DETECT = 410.0f;
constexpr float ULDUAR_AURIAYA_AXIS_Z_PATHING_ISSUE_DETECT = 410.0f;
constexpr float ULDUAR_YOGG_SARON_BOSS_ROOM_AXIS_Z_PATHING_ISSUE_DETECT = 300.0f;
constexpr float ULDUAR_YOGG_SARON_BRAIN_ROOM_AXIS_Z_PATHING_ISSUE_DETECT = 200.0f;
constexpr float ULDUAR_YOGG_SARON_STORMWIND_KEEPER_RADIUS = 150.0f;
constexpr float ULDUAR_YOGG_SARON_ICECROWN_CITADEL_RADIUS = 150.0f;
constexpr float ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_RADIUS = 150.0f;
constexpr float ULDUAR_YOGG_SARON_BRAIN_ROOM_RADIUS = 50.0f;
extern const Position ULDUAR_THORIM_NEAR_ARENA_CENTER;
extern const Position ULDUAR_THORIM_NEAR_ENTRANCE_POSITION;
extern const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_6_YARDS_1;
extern const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_6_YARDS_2;
extern const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_5_YARDS_1;
extern const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_10_YARDS_1;
extern const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_10_YARDS_2;
extern const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_10_YARDS_3;
extern const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_6_YARDS_1;
extern const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_6_YARDS_2;
extern const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_5_YARDS_1;
extern const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_10_YARDS_1;
extern const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_10_YARDS_2;
extern const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_10_YARDS_3;
extern const Position ULDUAR_THORIM_JUMP_END_POINT;
extern const Position ULDUAR_THORIM_PHASE2_TANK_SPOT;
extern const Position ULDUAR_THORIM_PHASE2_RANGE1_SPOT;
extern const Position ULDUAR_THORIM_PHASE2_RANGE2_SPOT;
extern const Position ULDUAR_THORIM_PHASE2_RANGE3_SPOT;
extern const Position ULDUAR_MIMIRON_PHASE2_SIDE1RANGE_SPOT;
extern const Position ULDUAR_MIMIRON_PHASE2_SIDE1MELEE_SPOT;
extern const Position ULDUAR_MIMIRON_PHASE2_SIDE2RANGE_SPOT;
extern const Position ULDUAR_MIMIRON_PHASE2_SIDE2MELEE_SPOT;
extern const Position ULDUAR_MIMIRON_PHASE2_SIDE3RANGE_SPOT;
extern const Position ULDUAR_MIMIRON_PHASE2_SIDE3MELEE_SPOT;
extern const Position ULDUAR_MIMIRON_PHASE4_TANK_SPOT;
extern const Position ULDUAR_VEZAX_MARK_OF_THE_FACELESS_SPOT;
extern const Position ULDUAR_YOGG_SARON_MIDDLE;
extern const Position ULDUAR_YOGG_SARON_STORMWIND_KEEPER_MIDDLE;
extern const Position ULDUAR_YOGG_SARON_ICECROWN_CITADEL_MIDDLE;
extern const Position ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_MIDDLE;
extern const Position ULDUAR_YOGG_SARON_BRAIN_ROOM_MIDDLE;
extern const Position ULDUAR_YOGG_SARON_STORMWIND_KEEPER_ENTRANCE;
extern const Position ULDUAR_YOGG_SARON_ICECROWN_CITADEL_ENTRANCE;
extern const Position ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_ENTRANCE;
extern const Position ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT;
extern const Position ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT;
class RazorscaleBossHelper : public AiObject
{
public:
// Enums and constants specific to Razorscale
enum RazorscaleUnits : uint32
{
UNIT_RAZORSCALE = 33186,
UNIT_DARK_RUNE_SENTINEL = 33846,
UNIT_DARK_RUNE_WATCHER = 33453,
UNIT_DARK_RUNE_GUARDIAN = 33388,
UNIT_DEVOURING_FLAME = 34188,
};
enum RazorscaleGameObjects : uint32
{
GO_RAZORSCALE_HARPOON_1 = 194519,
GO_RAZORSCALE_HARPOON_2 = 194541,
GO_RAZORSCALE_HARPOON_3 = 194542,
GO_RAZORSCALE_HARPOON_4 = 194543,
};
enum RazorscaleSpells : uint32
{
SPELL_CHAIN_1 = 49679,
SPELL_CHAIN_2 = 49682,
SPELL_CHAIN_3 = 49683,
SPELL_CHAIN_4 = 49684,
SPELL_SENTINEL_WHIRLWIND = 63806,
SPELL_STUN_AURA = 62794,
SPELL_FUSEARMOR = 64771
};
static constexpr uint32 FUSEARMOR_THRESHOLD = 2;
// Constants for arena parameters
static constexpr float RAZORSCALE_FLYING_Z_THRESHOLD = 440.0f;
static constexpr float RAZORSCALE_ARENA_CENTER_X = 587.54f;
static constexpr float RAZORSCALE_ARENA_CENTER_Y = -175.04f;
static constexpr float RAZORSCALE_ARENA_RADIUS = 30.0f;
// Harpoon cooldown (seconds)
static constexpr time_t HARPOON_COOLDOWN_DURATION = 5;
// Structure for harpoon data
struct HarpoonData
{
uint32 gameObjectEntry;
uint32 chainSpellId;
};
explicit RazorscaleBossHelper(PlayerbotAI* botAI)
: AiObject(botAI), _boss(nullptr) {}
bool UpdateBossAI();
Unit* GetBoss() const;
bool IsGroundPhase() const;
bool IsFlyingPhase() const;
bool IsHarpoonFired(uint32 chainSpellId) const;
static bool IsHarpoonReady(GameObject* harpoonGO);
static void SetHarpoonOnCooldown(GameObject* harpoonGO);
GameObject* FindNearestHarpoon(float x, float y, float z) const;
static const std::vector<HarpoonData>& GetHarpoonData();
void AssignRolesBasedOnHealth();
bool AreRolesAssigned() const;
bool CanSwapRoles() const;
private:
Unit* _boss;
// A map to track the last role swap *per bot* by their GUID
static std::unordered_map<ObjectGuid, std::time_t> _lastRoleSwapTime;
// The cooldown that applies to every bot
static const std::time_t _roleSwapCooldown = 10;
static std::unordered_map<ObjectGuid, time_t> _harpoonCooldowns;
};
// template <class BossAiType>
// class GenericBossHelper : public AiObject
// {
// public:
// GenericBossHelper(PlayerbotAI* botAI, std::string name) : AiObject(botAI), _name(name) {}
// virtual bool UpdateBossAI()
// {
// if (!bot->IsInCombat())
// {
// _unit = nullptr;
// }
// if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive()))
// {
// _unit = nullptr;
// }
// if (!_unit)
// {
// _unit = AI_VALUE2(Unit*, "find target", _name);
// if (!_unit)
// {
// return false;
// }
// _target = _unit->ToCreature();
// if (!_target)
// {
// return false;
// }
// _ai = dynamic_cast<BossAiType*>(_target->GetAI());
// if (!_ai)
// {
// return false;
// }
// _event_map = &_ai->events;
// if (!_event_map)
// {
// return false;
// }
// }
// if (!_event_map)
// {
// return false;
// }
// _timer = _event_map->GetTimer();
// return true;
// }
// virtual void Reset()
// {
// _unit = nullptr;
// _target = nullptr;
// _ai = nullptr;
// _event_map = nullptr;
// _timer = 0;
// }
// protected:
// std::string _name;
// Unit* _unit = nullptr;
// Creature* _target = nullptr;
// BossAiType* _ai = nullptr;
// EventMap* _event_map = nullptr;
// uint32 _timer = 0;
// };
#endif

View File

@@ -7,6 +7,7 @@
#define _PLAYERBOT_RAIDVOAACTIONCONTEXT_H
#include "Action.h"
#include "BossAuraActions.h"
#include "NamedObjectContext.h"
#include "RaidVoAActions.h"
#include "PlayerbotAI.h"

View File

@@ -7,6 +7,7 @@
#define _PLAYERBOT_RAIDVOATRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "BossAuraTriggers.h"
#include "NamedObjectContext.h"
#include "RaidVoATriggers.h"

View File

@@ -15,8 +15,6 @@
#include "PaladinAiObjectContext.h"
#include "Playerbots.h"
#include "PriestAiObjectContext.h"
#include "RaidUlduarActionContext.h"
#include "RaidUlduarTriggerContext.h"
#include "RogueAiObjectContext.h"
#include "ShamanAiObjectContext.h"
#include "SharedValueContext.h"
@@ -49,6 +47,8 @@
#include "Ai/Raid/VaultOfArchavon/RaidVoATriggerContext.h"
#include "Ai/Raid/ObsidianSanctum/RaidOsActionContext.h"
#include "Ai/Raid/ObsidianSanctum/RaidOsTriggerContext.h"
#include "Ai/Raid/Ulduar/RaidUlduarActionContext.h"
#include "Ai/Raid/Ulduar/RaidUlduarTriggerContext.h"
#include "Ai/Raid/Onyxia/RaidOnyxiaActionContext.h"
#include "Ai/Raid/Onyxia/RaidOnyxiaTriggerContext.h"
#include "Ai/Raid/Icecrown/RaidIccActionContext.h"

View File

@@ -1585,7 +1585,7 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
strategyName = "wotlk-hol"; // Halls of Lightning
break;
case 603:
strategyName = "uld"; // Ulduar
strategyName = "ulduar"; // Ulduar
break;
case 604:
strategyName = "wotlk-gd"; // Gundrak