Implement Gruul's Lair strategy (#1647)

* Implement Gruul's Lair strategy

* minor non-gameplay tweaks to code

* Use multiplier for tank assist

* HKM warlock & Gruul tank tweaks

* Fixed declarations

* rewrote HKM + minor Gruul tweaks

* Update PlayerbotAI.cpp

* modernize code and address comments

* clean ups to tank logic

* Oops.

* Remove post-move delay

For actions like positioning bosses, the standard post-movement delay should be overridden IMO since a player would be sequencing their actions in this type of situation

* Update RaidGruulsLairActions.cpp

* Replace break statements with return true

* enum class to enum

* moved all isuseful checks to triggers

* Split multipliers and improved banish logic

* Update for comments

* changes to int

* use helpers for marking icons

* address compile errors

* correct MoveTo and use RTI helper

* address comments and rename actions/triggers

* adjust alignment of location constants

* fix some crappy returns

* allow return true when changing targets

* change indents and move enums inside namespace

* style changes, trim trailing whitespaces, address comments
This commit is contained in:
Crow
2025-11-04 16:01:30 -06:00
committed by GitHub
parent e35900f9d0
commit 983a55da86
15 changed files with 1708 additions and 0 deletions

View File

@@ -1534,6 +1534,9 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
case 533:
strategyName = "naxx"; // Naxxramas
break;
case 565:
strategyName = "gruulslair"; // Gruul's Lair
break;
case 574:
strategyName = "wotlk-uk"; // Utgarde Keep
break;

View File

@@ -39,6 +39,8 @@
#include "raids/blackwinglair/RaidBwlTriggerContext.h"
#include "raids/karazhan/RaidKarazhanActionContext.h"
#include "raids/karazhan/RaidKarazhanTriggerContext.h"
#include "raids/gruulslair/RaidGruulsLairActionContext.h"
#include "raids/gruulslair/RaidGruulsLairTriggerContext.h"
#include "raids/naxxramas/RaidNaxxActionContext.h"
#include "raids/naxxramas/RaidNaxxTriggerContext.h"
#include "raids/eyeofeternity/RaidEoEActionContext.h"
@@ -111,6 +113,7 @@ void AiObjectContext::BuildSharedActionContexts(SharedNamedObjectContextList<Act
actionContexts.Add(new RaidMcActionContext());
actionContexts.Add(new RaidBwlActionContext());
actionContexts.Add(new RaidKarazhanActionContext());
actionContexts.Add(new RaidGruulsLairActionContext());
actionContexts.Add(new RaidNaxxActionContext());
actionContexts.Add(new RaidOsActionContext());
actionContexts.Add(new RaidEoEActionContext());
@@ -144,6 +147,7 @@ void AiObjectContext::BuildSharedTriggerContexts(SharedNamedObjectContextList<Tr
triggerContexts.Add(new RaidMcTriggerContext());
triggerContexts.Add(new RaidBwlTriggerContext());
triggerContexts.Add(new RaidKarazhanTriggerContext());
triggerContexts.Add(new RaidGruulsLairTriggerContext());
triggerContexts.Add(new RaidNaxxTriggerContext());
triggerContexts.Add(new RaidOsTriggerContext());
triggerContexts.Add(new RaidEoETriggerContext());

View File

@@ -6,6 +6,7 @@
#include "RaidMcStrategy.h"
#include "RaidBwlStrategy.h"
#include "RaidKarazhanStrategy.h"
#include "RaidGruulsLairStrategy.h"
#include "RaidNaxxStrategy.h"
#include "RaidOsStrategy.h"
#include "RaidEoEStrategy.h"
@@ -23,6 +24,7 @@ public:
creators["mc"] = &RaidStrategyContext::mc;
creators["bwl"] = &RaidStrategyContext::bwl;
creators["karazhan"] = &RaidStrategyContext::karazhan;
creators["gruulslair"] = &RaidStrategyContext::gruulslair;
creators["naxx"] = &RaidStrategyContext::naxx;
creators["wotlk-os"] = &RaidStrategyContext::wotlk_os;
creators["wotlk-eoe"] = &RaidStrategyContext::wotlk_eoe;
@@ -37,6 +39,7 @@ private:
static Strategy* mc(PlayerbotAI* botAI) { return new RaidMcStrategy(botAI); }
static Strategy* bwl(PlayerbotAI* botAI) { return new RaidBwlStrategy(botAI); }
static Strategy* karazhan(PlayerbotAI* botAI) { return new RaidKarazhanStrategy(botAI); }
static Strategy* gruulslair(PlayerbotAI* botAI) { return new RaidGruulsLairStrategy(botAI); }
static Strategy* naxx(PlayerbotAI* botAI) { return new RaidNaxxStrategy(botAI); }
static Strategy* wotlk_os(PlayerbotAI* botAI) { return new RaidOsStrategy(botAI); }
static Strategy* wotlk_eoe(PlayerbotAI* botAI) { return new RaidEoEStrategy(botAI); }

View File

@@ -0,0 +1,49 @@
#ifndef _PLAYERBOT_RAIDGRUULSLAIRACTIONCONTEXT_H
#define _PLAYERBOT_RAIDGRUULSLAIRACTIONCONTEXT_H
#include "RaidGruulsLairActions.h"
#include "NamedObjectContext.h"
class RaidGruulsLairActionContext : public NamedObjectContext<Action>
{
public:
RaidGruulsLairActionContext()
{
// High King Maulgar
creators["high king maulgar main tank attack maulgar"] = &RaidGruulsLairActionContext::high_king_maulgar_main_tank_attack_maulgar;
creators["high king maulgar first assist tank attack olm"] = &RaidGruulsLairActionContext::high_king_maulgar_first_assist_tank_attack_olm;
creators["high king maulgar second assist tank attack blindeye"] = &RaidGruulsLairActionContext::high_king_maulgar_second_assist_tank_attack_blindeye;
creators["high king maulgar mage tank attack krosh"] = &RaidGruulsLairActionContext::high_king_maulgar_mage_tank_attack_krosh;
creators["high king maulgar moonkin tank attack kiggler"] = &RaidGruulsLairActionContext::high_king_maulgar_moonkin_tank_attack_kiggler;
creators["high king maulgar assign dps priority"] = &RaidGruulsLairActionContext::high_king_maulgar_assign_dps_priority;
creators["high king maulgar healer find safe position"] = &RaidGruulsLairActionContext::high_king_maulgar_healer_find_safe_position;
creators["high king maulgar run away from whirlwind"] = &RaidGruulsLairActionContext::high_king_maulgar_run_away_from_whirlwind;
creators["high king maulgar banish felstalker"] = &RaidGruulsLairActionContext::high_king_maulgar_banish_felstalker;
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 spread ranged"] = &RaidGruulsLairActionContext::gruul_the_dragonkiller_spread_ranged;
creators["gruul the dragonkiller shatter spread"] = &RaidGruulsLairActionContext::gruul_the_dragonkiller_shatter_spread;
}
private:
// High King Maulgar
static Action* high_king_maulgar_main_tank_attack_maulgar(PlayerbotAI* botAI) { return new HighKingMaulgarMainTankAttackMaulgarAction(botAI); }
static Action* high_king_maulgar_first_assist_tank_attack_olm(PlayerbotAI* botAI) { return new HighKingMaulgarFirstAssistTankAttackOlmAction(botAI); }
static Action* high_king_maulgar_second_assist_tank_attack_blindeye(PlayerbotAI* botAI) { return new HighKingMaulgarSecondAssistTankAttackBlindeyeAction(botAI); }
static Action* high_king_maulgar_mage_tank_attack_krosh(PlayerbotAI* botAI) { return new HighKingMaulgarMageTankAttackKroshAction(botAI); }
static Action* high_king_maulgar_moonkin_tank_attack_kiggler(PlayerbotAI* botAI) { return new HighKingMaulgarMoonkinTankAttackKigglerAction(botAI); }
static Action* high_king_maulgar_assign_dps_priority(PlayerbotAI* botAI) { return new HighKingMaulgarAssignDPSPriorityAction(botAI); }
static Action* high_king_maulgar_healer_find_safe_position(PlayerbotAI* botAI) { return new HighKingMaulgarHealerFindSafePositionAction(botAI); }
static Action* high_king_maulgar_run_away_from_whirlwind(PlayerbotAI* botAI) { return new HighKingMaulgarRunAwayFromWhirlwindAction(botAI); }
static Action* high_king_maulgar_banish_felstalker(PlayerbotAI* botAI) { return new HighKingMaulgarBanishFelstalkerAction(botAI); }
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_spread_ranged(PlayerbotAI* botAI) { return new GruulTheDragonkillerSpreadRangedAction(botAI); }
static Action* gruul_the_dragonkiller_shatter_spread(PlayerbotAI* botAI) { return new GruulTheDragonkillerShatterSpreadAction(botAI); }
};
#endif

View File

@@ -0,0 +1,696 @@
#include "RaidGruulsLairActions.h"
#include "RaidGruulsLairHelpers.h"
#include "CreatureAI.h"
#include "Playerbots.h"
#include "Unit.h"
using namespace GruulsLairHelpers;
// High King Maulgar Actions
// Main tank on Maulgar
bool HighKingMaulgarMainTankAttackMaulgarAction::Execute(Event event)
{
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
MarkTargetWithSquare(bot, maulgar);
SetRtiTarget(botAI, "square", maulgar);
if (bot->GetVictim() != maulgar)
return Attack(maulgar);
if (maulgar->GetVictim() == bot)
{
const Location& tankPosition = GruulsLairLocations::MaulgarTankPosition;
const float maxDistance = 3.0f;
float distanceToTankPosition = bot->GetExactDist2d(tankPosition.x, tankPosition.y);
if (distanceToTankPosition > 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 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;
}
// First offtank on Olm
bool HighKingMaulgarFirstAssistTankAttackOlmAction::Execute(Event event)
{
Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner");
MarkTargetWithCircle(bot, olm);
SetRtiTarget(botAI, "circle", olm);
if (bot->GetVictim() != olm)
return Attack(olm);
if (olm->GetVictim() == bot)
{
const Location& tankPosition = GruulsLairLocations::OlmTankPosition;
const float maxDistance = 3.0f;
const float olmTankLeeway = 30.0f;
float distanceOlmToTankPosition = olm->GetExactDist2d(tankPosition.x, tankPosition.y);
if (distanceOlmToTankPosition > 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,
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;
}
// Second offtank on Blindeye
bool HighKingMaulgarSecondAssistTankAttackBlindeyeAction::Execute(Event event)
{
Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer");
MarkTargetWithStar(bot, blindeye);
SetRtiTarget(botAI, "star", blindeye);
if (bot->GetVictim() != blindeye)
return Attack(blindeye);
if (blindeye->GetVictim() == bot)
{
const Location& tankPosition = GruulsLairLocations::BlindeyeTankPosition;
const float maxDistance = 3.0f;
float distanceToTankPosition = bot->GetExactDist2d(tankPosition.x, tankPosition.y);
if (distanceToTankPosition > 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 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;
}
// Mage with highest max HP on Krosh
bool HighKingMaulgarMageTankAttackKroshAction::Execute(Event event)
{
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand");
MarkTargetWithTriangle(bot, krosh);
SetRtiTarget(botAI, "triangle", krosh);
if (krosh->HasAura(SPELL_SPELL_SHIELD) && botAI->CanCastSpell("spellsteal", krosh))
return botAI->CastSpell("spellsteal", krosh);
if (!bot->HasAura(SPELL_SPELL_SHIELD) && botAI->CanCastSpell("fire ward", bot))
return botAI->CastSpell("fire ward", bot);
if (bot->GetTarget() != krosh->GetGUID())
{
bot->SetSelection(krosh->GetGUID());
return true;
}
if (krosh->GetVictim() == bot)
{
const Location& tankPosition = GruulsLairLocations::KroshTankPosition;
float distanceToKrosh = krosh->GetExactDist2d(tankPosition.x, tankPosition.y);
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))
{
return MoveTo(bot->GetMapId(), tankPosition.x, tankPosition.y, tankPosition.z, false,
false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
float orientation = atan2(krosh->GetPositionY() - bot->GetPositionY(),
krosh->GetPositionX() - bot->GetPositionX());
bot->SetFacingTo(orientation);
}
else
{
Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos))
{
return MoveTo(krosh->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ,
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
}
}
return false;
}
// Moonkin with highest max HP on Kiggler
bool HighKingMaulgarMoonkinTankAttackKigglerAction::Execute(Event event)
{
Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed");
MarkTargetWithDiamond(bot, kiggler);
SetRtiTarget(botAI, "diamond", kiggler);
if (bot->GetTarget() != kiggler->GetGUID())
{
bot->SetSelection(kiggler->GetGUID());
return true;
}
Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos))
{
return MoveTo(kiggler->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ,
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
return false;
}
bool HighKingMaulgarAssignDPSPriorityAction::Execute(Event event)
{
// Target priority 1: Blindeye
Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer");
if (blindeye && blindeye->IsAlive())
{
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,
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())
{
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,
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))
{
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,
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())
{
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,
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())
{
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,
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;
}
// 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);
if (distToFight > maxDistanceFromFight)
{
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;
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,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos))
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
return MoveTo(bot->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ,
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
return false;
}
// Run away from Maulgar during Whirlwind (logic for after all other ogres are dead)
bool HighKingMaulgarRunAwayFromWhirlwindAction::Execute(Event event)
{
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
const float safeDistance = 10.0f;
float distance = bot->GetExactDist2d(maulgar);
if (distance < safeDistance)
{
float angle = atan2(bot->GetPositionY() - maulgar->GetPositionY(),
bot->GetPositionX() - maulgar->GetPositionX());
float destX = maulgar->GetPositionX() + safeDistance * cos(angle);
float destY = maulgar->GetPositionY() + safeDistance * sin(angle);
float destZ = bot->GetPositionZ();
if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
bot->GetPositionZ(), destX, destY, destZ))
return false;
float destDist = maulgar->GetExactDist2d(destX, destY);
if (destDist >= safeDistance - 0.1f)
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(true);
return MoveTo(maulgar->GetMapId(), destX, destY, destZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
}
return false;
}
bool HighKingMaulgarBanishFelstalkerAction::Execute(Event event)
{
Group* group = bot->GetGroup();
if (!group)
return false;
const GuidVector& npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
std::vector<Unit*> felStalkers;
for (auto const& npc : npcs)
{
Unit* unit = botAI->GetUnit(npc);
if (unit && unit->GetEntry() == NPC_WILD_FEL_STALKER && unit->IsAlive())
felStalkers.push_back(unit);
}
std::vector<Player*> warlocks;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->IsAlive() && member->getClass() == CLASS_WARLOCK && GET_PLAYERBOT_AI(member))
warlocks.push_back(member);
}
int warlockIndex = -1;
for (size_t i = 0; i < warlocks.size(); ++i)
{
if (warlocks[i] == bot)
{
warlockIndex = static_cast<int>(i);
break;
}
}
if (warlockIndex >= 0 && warlockIndex < felStalkers.size())
{
Unit* assignedFelStalker = felStalkers[warlockIndex];
if (!assignedFelStalker->HasAura(SPELL_BANISH) && botAI->CanCastSpell(SPELL_BANISH, assignedFelStalker, true))
return botAI->CastSpell("banish", assignedFelStalker);
}
return false;
}
// Hunter 1: Misdirect Olm to first offtank and have pet attack Blindeye
// Hunter 2: Misdirect Blindeye to second offtank
bool HighKingMaulgarMisdirectOlmAndBlindeyeAction::Execute(Event event)
{
Group* group = bot->GetGroup();
if (!group)
return false;
std::vector<Player*> hunters;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->IsAlive() && member->getClass() == CLASS_HUNTER && GET_PLAYERBOT_AI(member))
hunters.push_back(member);
}
int hunterIndex = -1;
for (size_t i = 0; i < hunters.size(); ++i)
{
if (hunters[i] == bot)
{
hunterIndex = static_cast<int>(i);
break;
}
}
Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner");
Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer");
Player* olmTank = nullptr;
Player* blindeyeTank = nullptr;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive())
continue;
else if (botAI->IsAssistTankOfIndex(member, 0)) olmTank = member;
else if (botAI->IsAssistTankOfIndex(member, 1)) blindeyeTank = member;
}
switch (hunterIndex)
{
case 0:
botAI->CastSpell("misdirection", olmTank);
if (bot->HasAura(SPELL_MISDIRECTION))
{
Pet* pet = bot->GetPet();
if (pet && pet->IsAlive() && pet->GetVictim() != blindeye)
{
pet->ClearUnitState(UNIT_STATE_FOLLOW);
pet->AttackStop();
pet->SetTarget(blindeye->GetGUID());
if (pet->GetCharmInfo())
{
pet->GetCharmInfo()->SetIsCommandAttack(true);
pet->GetCharmInfo()->SetIsAtStay(false);
pet->GetCharmInfo()->SetIsFollowing(false);
pet->GetCharmInfo()->SetIsCommandFollow(false);
pet->GetCharmInfo()->SetIsReturning(false);
}
pet->ToCreature()->AI()->AttackStart(blindeye);
}
return botAI->CastSpell("steady shot", olm);
}
break;
case 1:
botAI->CastSpell("misdirection", blindeyeTank);
if (bot->HasAura(SPELL_MISDIRECTION))
return botAI->CastSpell("steady shot", blindeye);
break;
default:
break;
}
return false;
}
// Gruul the Dragonkiller Actions
// Position in center of the room
bool GruulTheDragonkillerMainTankPositionBossAction::Execute(Event event)
{
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
if (bot->GetVictim() != gruul)
return Attack(gruul);
if (gruul->GetVictim() == bot)
{
const Location& tankPosition = GruulsLairLocations::GruulTankPosition;
const float maxDistance = 3.0f;
float dX = tankPosition.x - bot->GetPositionX();
float dY = tankPosition.y - bot->GetPositionY();
float distanceToTankPosition = bot->GetExactDist2d(tankPosition.x, tankPosition.y);
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,
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;
}
// Ranged will take initial positions around the middle of the room, 25-40 yards from center
// Ranged should spread out 10 yards from each other
bool GruulTheDragonkillerSpreadRangedAction::Execute(Event event)
{
Group* group = bot->GetGroup();
if (!group)
return false;
static std::unordered_map<ObjectGuid, Position> initialPositions;
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())
{
initialPositions.clear();
hasReachedInitialPosition.clear();
}
const Location& tankPosition = GruulsLairLocations::GruulTankPosition;
const float centerX = tankPosition.x;
const float centerY = tankPosition.y;
float centerZ = bot->GetPositionZ();
const float minRadius = 25.0f;
const float maxRadius = 40.0f;
std::vector<Player*> members;
Player* closestMember = nullptr;
float closestDist = std::numeric_limits<float>::max();
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive())
continue;
members.push_back(member);
if (member != bot)
{
float dist = bot->GetExactDist2d(member);
if (dist < closestDist)
{
closestDist = dist;
closestMember = member;
}
}
}
if (!initialPositions.count(bot->GetGUID()))
{
auto it = std::find(members.begin(), members.end(), bot);
uint8 botIndex = (it != members.end()) ? std::distance(members.begin(), it) : 0;
uint8 count = members.size();
float angle = 2 * M_PI * botIndex / count;
float radius = minRadius + static_cast<float>(rand()) /
static_cast<float>(RAND_MAX) * (maxRadius - minRadius);
float targetX = centerX + radius * cos(angle);
float targetY = centerY + radius * sin(angle);
initialPositions[bot->GetGUID()] = Position(targetX, targetY, centerZ);
hasReachedInitialPosition[bot->GetGUID()] = false;
}
Position targetPosition = initialPositions[bot->GetGUID()];
if (!hasReachedInitialPosition[bot->GetGUID()])
{
if (!bot->IsWithinDist2d(targetPosition.GetPositionX(), targetPosition.GetPositionY(), 2.0f))
{
float destX = targetPosition.GetPositionX();
float destY = targetPosition.GetPositionY();
float destZ = targetPosition.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,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
hasReachedInitialPosition[bot->GetGUID()] = true;
}
const float minSpreadDistance = 10.0f;
const float movementThreshold = 2.0f;
if (closestMember && closestDist < minSpreadDistance - movementThreshold)
{
return FleePosition(Position(closestMember->GetPositionX(), closestMember->GetPositionY(),
closestMember->GetPositionZ()), minSpreadDistance, 0);
}
return false;
}
// Try to get away from other group members when Ground Slam is cast
bool GruulTheDragonkillerShatterSpreadAction::Execute(Event event)
{
Group* group = bot->GetGroup();
if (!group)
return false;
GuidVector members = AI_VALUE(GuidVector, "group members");
Unit* closestMember = nullptr;
float closestDist = std::numeric_limits<float>::max();
for (auto& member : members)
{
Unit* unit = botAI->GetUnit(member);
if (!unit || bot->GetGUID() == member)
continue;
const float dist = bot->GetExactDist2d(unit);
if (dist < closestDist)
{
closestDist = dist;
closestMember = unit;
}
}
if (closestMember)
{
return FleePosition(Position(closestMember->GetPositionX(), closestMember->GetPositionY(),
closestMember->GetPositionZ()), 6.0f, 0);
}
return false;
}

View File

@@ -0,0 +1,112 @@
#ifndef _PLAYERBOT_RAIDGRUULSLAIRACTIONS_H
#define _PLAYERBOT_RAIDGRUULSLAIRACTIONS_H
#include "Action.h"
#include "AttackAction.h"
#include "MovementActions.h"
class HighKingMaulgarMainTankAttackMaulgarAction : public AttackAction
{
public:
HighKingMaulgarMainTankAttackMaulgarAction(PlayerbotAI* botAI, std::string const name = "high king maulgar main tank attack maulgar") : AttackAction(botAI, name) {};
bool Execute(Event event) override;
};
class HighKingMaulgarFirstAssistTankAttackOlmAction : public AttackAction
{
public:
HighKingMaulgarFirstAssistTankAttackOlmAction(PlayerbotAI* botAI, std::string const name = "high king maulgar first assist tank attack olm") : AttackAction(botAI, name) {};
bool Execute(Event event) override;
};
class HighKingMaulgarSecondAssistTankAttackBlindeyeAction : public AttackAction
{
public:
HighKingMaulgarSecondAssistTankAttackBlindeyeAction(PlayerbotAI* botAI, std::string const name = "high king maulgar second assist tank attack blindeye") : AttackAction(botAI, name) {};
bool Execute(Event event) override;
};
class HighKingMaulgarMageTankAttackKroshAction : public AttackAction
{
public:
HighKingMaulgarMageTankAttackKroshAction(PlayerbotAI* botAI, std::string const name = "high king maulgar mage tank attack krosh") : AttackAction(botAI, name) {};
bool Execute(Event event) override;
};
class HighKingMaulgarMoonkinTankAttackKigglerAction : public AttackAction
{
public:
HighKingMaulgarMoonkinTankAttackKigglerAction(PlayerbotAI* botAI, std::string const name = "high king maulgar moonkin tank attack kiggler") : AttackAction(botAI, name) {};
bool Execute(Event event) override;
};
class HighKingMaulgarAssignDPSPriorityAction : public AttackAction
{
public:
HighKingMaulgarAssignDPSPriorityAction(PlayerbotAI* botAI, std::string const name = "high king maulgar assign dps priority") : AttackAction(botAI, name) {};
bool Execute(Event event) override;
};
class HighKingMaulgarHealerFindSafePositionAction : public MovementAction
{
public:
HighKingMaulgarHealerFindSafePositionAction(PlayerbotAI* botAI, std::string const name = "high king maulgar healer find safe position") : MovementAction(botAI, name) {};
bool Execute(Event event) override;
};
class HighKingMaulgarRunAwayFromWhirlwindAction : public MovementAction
{
public:
HighKingMaulgarRunAwayFromWhirlwindAction(PlayerbotAI* botAI, std::string const name = "high king maulgar run away from whirlwind") : MovementAction(botAI, name) {};
bool Execute(Event event) override;
};
class HighKingMaulgarBanishFelstalkerAction : public AttackAction
{
public:
HighKingMaulgarBanishFelstalkerAction(PlayerbotAI* botAI, std::string const name = "high king maulgar banish felstalker") : AttackAction(botAI, name) {};
bool Execute(Event event) override;
};
class HighKingMaulgarMisdirectOlmAndBlindeyeAction : public AttackAction
{
public:
HighKingMaulgarMisdirectOlmAndBlindeyeAction(PlayerbotAI* botAI, std::string const name = "high king maulgar misdirect olm and blindeye") : AttackAction(botAI, name) {};
bool Execute(Event event) override;
};
class GruulTheDragonkillerMainTankPositionBossAction : public AttackAction
{
public:
GruulTheDragonkillerMainTankPositionBossAction(PlayerbotAI* botAI, std::string const name = "gruul the dragonkiller main tank position boss") : AttackAction(botAI, name) {};
bool Execute(Event event) override;
};
class GruulTheDragonkillerSpreadRangedAction : public MovementAction
{
public:
GruulTheDragonkillerSpreadRangedAction(PlayerbotAI* botAI, std::string const name = "gruul the dragonkiller spread ranged") : MovementAction(botAI, name) {};
bool Execute(Event event) override;
};
class GruulTheDragonkillerShatterSpreadAction : public MovementAction
{
public:
GruulTheDragonkillerShatterSpreadAction(PlayerbotAI* botAI, std::string const name = "gruul the dragonkiller shatter spread") : MovementAction(botAI, name) {};
bool Execute(Event event) override;
};
#endif

View File

@@ -0,0 +1,241 @@
#include "RaidGruulsLairHelpers.h"
#include "AiFactory.h"
#include "GroupReference.h"
#include "Playerbots.h"
#include "Unit.h"
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 };
}
bool IsAnyOgreBossAlive(PlayerbotAI* botAI)
{
const char* ogreBossNames[] =
{
"high king maulgar",
"kiggler the crazed",
"krosh firehand",
"olm the summoner",
"blindeye the seer"
};
for (const char* name : ogreBossNames)
{
Unit* boss = botAI->GetAiObjectContext()->GetValue<Unit*>("find target", name)->Get();
if (!boss || !boss->IsAlive())
continue;
return true;
}
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;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member))
continue;
if (member->getClass() == CLASS_MAGE)
{
uint32 hp = member->GetMaxHealth();
if (!highestHpMage || hp > highestHp)
{
highestHpMage = member;
highestHp = hp;
}
}
}
return highestHpMage == bot;
}
bool IsKigglerMoonkinTank(PlayerbotAI* botAI, Player* bot)
{
Group* group = bot->GetGroup();
if (!group)
return false;
Player* highestHpMoonkin = 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))
continue;
if (member->getClass() == CLASS_DRUID)
{
int tab = AiFactory::GetPlayerSpecTab(member);
if (tab == DRUID_TAB_BALANCE)
{
uint32 hp = member->GetMaxHealth();
if (!highestHpMoonkin || hp > highestHp)
{
highestHpMoonkin = member;
highestHp = hp;
}
}
}
}
return highestHpMoonkin == bot;
}
bool IsPositionSafe(PlayerbotAI* botAI, Player* bot, Position pos)
{
const float KROSH_SAFE_DISTANCE = 20.0f;
const float MAULGAR_SAFE_DISTANCE = 10.0f;
bool isSafe = true;
Unit* krosh = botAI->GetAiObjectContext()->GetValue<Unit*>("find target", "krosh firehand")->Get();
if (krosh && krosh->IsAlive())
{
float dist = sqrt(pow(pos.GetPositionX() - krosh->GetPositionX(), 2) + pow(pos.GetPositionY() - krosh->GetPositionY(), 2));
if (dist < KROSH_SAFE_DISTANCE)
isSafe = false;
}
Unit* maulgar = botAI->GetAiObjectContext()->GetValue<Unit*>("find target", "high king maulgar")->Get();
if (botAI->IsRanged(bot) && maulgar && maulgar->IsAlive())
{
float dist = sqrt(pow(pos.GetPositionX() - maulgar->GetPositionX(), 2) + pow(pos.GetPositionY() - maulgar->GetPositionY(), 2));
if (dist < MAULGAR_SAFE_DISTANCE)
isSafe = false;
}
return isSafe;
}
bool TryGetNewSafePosition(PlayerbotAI* botAI, Player* bot, Position& outPos)
{
const float SEARCH_RADIUS = 30.0f;
const uint8 NUM_POSITIONS = 32;
outPos = { bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ() };
if (IsPositionSafe(botAI, bot, outPos))
{
outPos = Position();
return false;
}
float bestScore = std::numeric_limits<float>::max();
bool foundSafeSpot = false;
Position bestPos;
for (int i = 0; i < NUM_POSITIONS; ++i)
{
float angle = 2 * M_PI * i / NUM_POSITIONS;
Position candidatePos;
candidatePos.m_positionX = bot->GetPositionX() + SEARCH_RADIUS * cos(angle);
candidatePos.m_positionY = bot->GetPositionY() + SEARCH_RADIUS * sin(angle);
candidatePos.m_positionZ = bot->GetPositionZ();
float destX = candidatePos.m_positionX, destY = candidatePos.m_positionY, destZ = candidatePos.m_positionZ;
if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
bot->GetPositionZ(), destX, destY, destZ, true))
continue;
if (destX != candidatePos.m_positionX || destY != candidatePos.m_positionY)
continue;
candidatePos.m_positionX = destX;
candidatePos.m_positionY = destY;
candidatePos.m_positionZ = destZ;
if (IsPositionSafe(botAI, bot, candidatePos))
{
float movementDistance = sqrt(pow(destX - bot->GetPositionX(), 2) + pow(destY - bot->GetPositionY(), 2));
if (movementDistance < bestScore)
{
bestScore = movementDistance;
bestPos = candidatePos;
foundSafeSpot = true;
}
}
}
if (foundSafeSpot)
{
outPos = bestPos;
return true;
}
outPos = Position();
return false;
}
}

View File

@@ -0,0 +1,62 @@
#ifndef RAID_GRUULSLAIRHELPERS_H
#define RAID_GRUULSLAIRHELPERS_H
#include "PlayerbotAI.h"
#include "RtiTargetValue.h"
namespace GruulsLairHelpers
{
enum GruulsLairSpells
{
// High King Maulgar
SPELL_WHIRLWIND = 33238,
// Krosh Firehand
SPELL_SPELL_SHIELD = 33054,
// Hunter
SPELL_MISDIRECTION = 34477,
// Warlock
SPELL_BANISH = 18647, // Rank 2
// Gruul the Dragonkiller
SPELL_GROUND_SLAM_1 = 33525,
SPELL_GROUND_SLAM_2 = 39187,
};
enum GruulsLairNPCs
{
NPC_WILD_FEL_STALKER = 18847,
};
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;
}
}
#endif

View File

@@ -0,0 +1,110 @@
#include "RaidGruulsLairMultipliers.h"
#include "RaidGruulsLairActions.h"
#include "RaidGruulsLairHelpers.h"
#include "ChooseTargetActions.h"
#include "DruidBearActions.h"
#include "DruidCatActions.h"
#include "GenericSpellActions.h"
#include "HunterActions.h"
#include "MageActions.h"
#include "Playerbots.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))
return 0.0f;
return 1.0f;
}
// Don't run back in during Whirlwind
float HighKingMaulgarAvoidWhirlwindMultiplier::GetValue(Action* action)
{
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed");
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand");
Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner");
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()))
{
if (IsChargeAction(action) || (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<HighKingMaulgarRunAwayFromWhirlwindAction*>(action)))
return 0.0f;
}
return 1.0f;
}
// Arcane Shot will remove Spell Shield, which the mage tank needs to survive
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))
return 0.0f;
return 1.0f;
}
float HighKingMaulgarDisableMageTankAOEMultiplier::GetValue(Action* action)
{
if (IsKroshMageTank(botAI, bot) &&
(dynamic_cast<CastFrostNovaAction*>(action) || dynamic_cast<CastBlizzardAction*>(action) ||
dynamic_cast<CastConeOfColdAction*>(action) || dynamic_cast<CastFlamestrikeAction*>(action) ||
dynamic_cast<CastDragonsBreathAction*>(action) || dynamic_cast<CastBlastWaveAction*>(action)))
return 0.0f;
return 1.0f;
}
float GruulTheDragonkillerMainTankMovementMultiplier::GetValue(Action* action)
{
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
if (!gruul)
return 1.0f;
if (botAI->IsMainTank(bot))
{
if (gruul->GetVictim() == bot && dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
if (dynamic_cast<AvoidAoeAction*>(action))
return 0.0f;
}
return 1.0f;
}
float GruulTheDragonkillerGroundSlamMultiplier::GetValue(Action* action)
{
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
if (!gruul)
return 1.0f;
if (bot->HasAura(SPELL_GROUND_SLAM_1) ||
bot->HasAura(SPELL_GROUND_SLAM_2))
{
if ((dynamic_cast<MovementAction*>(action) && !dynamic_cast<GruulTheDragonkillerShatterSpreadAction*>(action)) ||
IsChargeAction(action))
return 0.0f;
}
return 1.0f;
}

View File

@@ -0,0 +1,48 @@
#ifndef _PLAYERBOT_RAIDGRUULSLAIRMULTIPLIERS_H
#define _PLAYERBOT_RAIDGRUULSLAIRMULTIPLIERS_H
#include "Multiplier.h"
class HighKingMaulgarDisableTankAssistMultiplier : public Multiplier
{
public:
HighKingMaulgarDisableTankAssistMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "high king maulgar disable tank assist multiplier") {}
float GetValue(Action* action) override;
};
class HighKingMaulgarAvoidWhirlwindMultiplier : public Multiplier
{
public:
HighKingMaulgarAvoidWhirlwindMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "high king maulgar avoid whirlwind multiplier") {}
float GetValue(Action* action) override;
};
class HighKingMaulgarDisableArcaneShotOnKroshMultiplier : public Multiplier
{
public:
HighKingMaulgarDisableArcaneShotOnKroshMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "high king maulgar disable arcane shot on krosh multiplier") {}
float GetValue(Action* action) override;
};
class HighKingMaulgarDisableMageTankAOEMultiplier : public Multiplier
{
public:
HighKingMaulgarDisableMageTankAOEMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "high king maulgar disable mage tank aoe multiplier") {}
float GetValue(Action* action) override;
};
class GruulTheDragonkillerMainTankMovementMultiplier : public Multiplier
{
public:
GruulTheDragonkillerMainTankMovementMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "gruul the dragonkiller main tank movement multiplier") {}
float GetValue(Action* action) override;
};
class GruulTheDragonkillerGroundSlamMultiplier : public Multiplier
{
public:
GruulTheDragonkillerGroundSlamMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "gruul the dragonkiller ground slam multiplier") {}
float GetValue(Action* action) override;
};
#endif

View File

@@ -0,0 +1,56 @@
#include "RaidGruulsLairStrategy.h"
#include "RaidGruulsLairMultipliers.h"
void RaidGruulsLairStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
// High King Maulgar
triggers.push_back(new TriggerNode("high king maulgar is main tank", NextAction::array(0,
new NextAction("high king maulgar main tank attack maulgar", ACTION_RAID + 1), nullptr)));
triggers.push_back(new TriggerNode("high king maulgar is first assist tank", NextAction::array(0,
new NextAction("high king maulgar first assist tank attack olm", ACTION_RAID + 1), nullptr)));
triggers.push_back(new TriggerNode("high king maulgar is second assist tank", NextAction::array(0,
new NextAction("high king maulgar second assist tank attack blindeye", ACTION_RAID + 1), nullptr)));
triggers.push_back(new TriggerNode("high king maulgar is mage tank", NextAction::array(0,
new NextAction("high king maulgar mage tank attack krosh", ACTION_RAID + 1), nullptr)));
triggers.push_back(new TriggerNode("high king maulgar is moonkin tank", NextAction::array(0,
new NextAction("high king maulgar moonkin tank attack kiggler", ACTION_RAID + 1), nullptr)));
triggers.push_back(new TriggerNode("high king maulgar determining kill order", NextAction::array(0,
new NextAction("high king maulgar assign dps priority", ACTION_RAID + 1), nullptr)));
triggers.push_back(new TriggerNode("high king maulgar healer in danger", NextAction::array(0,
new NextAction("high king maulgar healer find safe position", ACTION_RAID + 1), nullptr)));
triggers.push_back(new TriggerNode("high king maulgar boss channeling whirlwind", NextAction::array(0,
new NextAction("high king maulgar run away from whirlwind", ACTION_EMERGENCY + 6), nullptr)));
triggers.push_back(new TriggerNode("high king maulgar wild felstalker spawned", NextAction::array(0,
new NextAction("high king maulgar banish felstalker", ACTION_RAID + 2), nullptr)));
triggers.push_back(new TriggerNode("high king maulgar pulling olm and blindeye", NextAction::array(0,
new NextAction("high king maulgar misdirect olm and blindeye", ACTION_RAID + 2), nullptr)));
// Gruul the Dragonkiller
triggers.push_back(new TriggerNode("gruul the dragonkiller boss engaged by main tank", NextAction::array(0,
new NextAction("gruul the dragonkiller main tank position boss", ACTION_RAID + 1), nullptr)));
triggers.push_back(new TriggerNode("gruul the dragonkiller boss engaged by range", NextAction::array(0,
new NextAction("gruul the dragonkiller spread ranged", ACTION_RAID + 1), nullptr)));
triggers.push_back(new TriggerNode("gruul the dragonkiller incoming shatter", NextAction::array(0,
new NextAction("gruul the dragonkiller shatter spread", ACTION_EMERGENCY + 6), nullptr)));
}
void RaidGruulsLairStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{
multipliers.push_back(new HighKingMaulgarDisableTankAssistMultiplier(botAI));
multipliers.push_back(new HighKingMaulgarAvoidWhirlwindMultiplier(botAI));
multipliers.push_back(new HighKingMaulgarDisableArcaneShotOnKroshMultiplier(botAI));
multipliers.push_back(new HighKingMaulgarDisableMageTankAOEMultiplier(botAI));
multipliers.push_back(new GruulTheDragonkillerMainTankMovementMultiplier(botAI));
multipliers.push_back(new GruulTheDragonkillerGroundSlamMultiplier(botAI));
}

View File

@@ -0,0 +1,18 @@
#ifndef _PLAYERBOT_RAIDGRUULSLAIRSTRATEGY_H
#define _PLAYERBOT_RAIDGRUULSLAIRSTRATEGY_H
#include "Strategy.h"
#include "Multiplier.h"
class RaidGruulsLairStrategy : public Strategy
{
public:
RaidGruulsLairStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
std::string const getName() override { return "gruulslair"; }
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
void InitMultipliers(std::vector<Multiplier*> &multipliers) override;
};
#endif

View File

@@ -0,0 +1,49 @@
#ifndef _PLAYERBOT_RAIDGRUULSLAIRTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDGRUULSLAIRTRIGGERCONTEXT_H
#include "RaidGruulsLairTriggers.h"
#include "AiObjectContext.h"
class RaidGruulsLairTriggerContext : public NamedObjectContext<Trigger>
{
public:
RaidGruulsLairTriggerContext() : NamedObjectContext<Trigger>()
{
// High King Maulgar
creators["high king maulgar is main tank"] = &RaidGruulsLairTriggerContext::high_king_maulgar_is_main_tank;
creators["high king maulgar is first assist tank"] = &RaidGruulsLairTriggerContext::high_king_maulgar_is_first_assist_tank;
creators["high king maulgar is second assist tank"] = &RaidGruulsLairTriggerContext::high_king_maulgar_is_second_assist_tank;
creators["high king maulgar is mage tank"] = &RaidGruulsLairTriggerContext::high_king_maulgar_is_mage_tank;
creators["high king maulgar is moonkin tank"] = &RaidGruulsLairTriggerContext::high_king_maulgar_is_moonkin_tank;
creators["high king maulgar determining kill order"] = &RaidGruulsLairTriggerContext::high_king_maulgar_determining_kill_order;
creators["high king maulgar healer in danger"] = &RaidGruulsLairTriggerContext::high_king_maulgar_healer_in_danger;
creators["high king maulgar boss channeling whirlwind"] = &RaidGruulsLairTriggerContext::high_king_maulgar_boss_channeling_whirlwind;
creators["high king maulgar wild felstalker spawned"] = &RaidGruulsLairTriggerContext::high_king_maulgar_wild_felstalker_spawned;
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 incoming shatter"] = &RaidGruulsLairTriggerContext::gruul_the_dragonkiller_incoming_shatter;
}
private:
// High King Maulgar
static Trigger* high_king_maulgar_is_main_tank(PlayerbotAI* botAI) { return new HighKingMaulgarIsMainTankTrigger(botAI); }
static Trigger* high_king_maulgar_is_first_assist_tank(PlayerbotAI* botAI) { return new HighKingMaulgarIsFirstAssistTankTrigger(botAI); }
static Trigger* high_king_maulgar_is_second_assist_tank(PlayerbotAI* botAI) { return new HighKingMaulgarIsSecondAssistTankTrigger(botAI); }
static Trigger* high_king_maulgar_is_mage_tank(PlayerbotAI* botAI) { return new HighKingMaulgarIsMageTankTrigger(botAI); }
static Trigger* high_king_maulgar_is_moonkin_tank(PlayerbotAI* botAI) { return new HighKingMaulgarIsMoonkinTankTrigger(botAI); }
static Trigger* high_king_maulgar_determining_kill_order(PlayerbotAI* botAI) { return new HighKingMaulgarDeterminingKillOrderTrigger(botAI); }
static Trigger* high_king_maulgar_healer_in_danger(PlayerbotAI* botAI) { return new HighKingMaulgarHealerInDangerTrigger(botAI); }
static Trigger* high_king_maulgar_boss_channeling_whirlwind(PlayerbotAI* botAI) { return new HighKingMaulgarBossChannelingWhirlwindTrigger(botAI); }
static Trigger* high_king_maulgar_wild_felstalker_spawned(PlayerbotAI* botAI) { return new HighKingMaulgarWildFelstalkerSpawnedTrigger(botAI); }
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_incoming_shatter(PlayerbotAI* botAI) { return new GruulTheDragonkillerIncomingShatterTrigger(botAI); }
};
#endif

View File

@@ -0,0 +1,160 @@
#include "RaidGruulsLairTriggers.h"
#include "RaidGruulsLairHelpers.h"
#include "Playerbots.h"
using namespace GruulsLairHelpers;
// High King Maulgar Triggers
bool HighKingMaulgarIsMainTankTrigger::IsActive()
{
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
return botAI->IsMainTank(bot) && maulgar && maulgar->IsAlive();
}
bool HighKingMaulgarIsFirstAssistTankTrigger::IsActive()
{
Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner");
return botAI->IsAssistTankOfIndex(bot, 0) && olm && olm->IsAlive();
}
bool HighKingMaulgarIsSecondAssistTankTrigger::IsActive()
{
Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer");
return botAI->IsAssistTankOfIndex(bot, 1) && blindeye && blindeye->IsAlive();
}
bool HighKingMaulgarIsMageTankTrigger::IsActive()
{
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand");
return IsKroshMageTank(botAI, bot) && krosh && krosh->IsAlive();
}
bool HighKingMaulgarIsMoonkinTankTrigger::IsActive()
{
Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed");
return IsKigglerMoonkinTank(botAI, bot) && kiggler && kiggler->IsAlive();
}
bool HighKingMaulgarDeterminingKillOrderTrigger::IsActive()
{
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed");
Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner");
Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer");
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());
}
bool HighKingMaulgarHealerInDangerTrigger::IsActive()
{
return botAI->IsHeal(bot) && IsAnyOgreBossAlive(botAI);
}
bool HighKingMaulgarBossChannelingWhirlwindTrigger::IsActive()
{
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
return maulgar && maulgar->IsAlive() && maulgar->HasAura(SPELL_WHIRLWIND) &&
!botAI->IsMainTank(bot);
}
bool HighKingMaulgarWildFelstalkerSpawnedTrigger::IsActive()
{
Unit* felStalker = AI_VALUE2(Unit*, "find target", "wild fel stalker");
return felStalker && felStalker->IsAlive() && bot->getClass() == CLASS_WARLOCK;
}
bool HighKingMaulgarPullingOlmAndBlindeyeTrigger::IsActive()
{
Group* group = bot->GetGroup();
if (!group || bot->getClass() != CLASS_HUNTER)
return false;
std::vector<Player*> hunters;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->IsAlive() && member->getClass() == CLASS_HUNTER && GET_PLAYERBOT_AI(member))
hunters.push_back(member);
}
int hunterIndex = -1;
for (size_t i = 0; i < hunters.size(); ++i)
{
if (hunters[i] == bot)
{
hunterIndex = static_cast<int>(i);
break;
}
}
if (hunterIndex == -1 || hunterIndex > 1)
return false;
Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner");
Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer");
Player* olmTank = nullptr;
Player* blindeyeTank = nullptr;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive())
continue;
else if (botAI->IsAssistTankOfIndex(member, 0)) olmTank = member;
else if (botAI->IsAssistTankOfIndex(member, 1)) blindeyeTank = member;
}
switch (hunterIndex)
{
case 0:
return olm && olm->IsAlive() && olm->GetHealthPct() > 98.0f &&
olmTank && olmTank->IsAlive() && botAI->CanCastSpell("misdirection", olmTank);
case 1:
return blindeye && blindeye->IsAlive() && blindeye->GetHealthPct() > 90.0f &&
blindeyeTank && blindeyeTank->IsAlive() && botAI->CanCastSpell("misdirection", blindeyeTank);
default:
break;
}
return false;
}
// Gruul the Dragonkiller Triggers
bool GruulTheDragonkillerBossEngagedByMainTankTrigger::IsActive()
{
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
return gruul && gruul->IsAlive() && botAI->IsMainTank(bot);
}
bool GruulTheDragonkillerBossEngagedByRangeTrigger::IsActive()
{
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
return gruul && gruul->IsAlive() && 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));
}

View File

@@ -0,0 +1,97 @@
#ifndef _PLAYERBOT_RAIDGRUULSLAIRTRIGGERS_H
#define _PLAYERBOT_RAIDGRUULSLAIRTRIGGERS_H
#include "Trigger.h"
class HighKingMaulgarIsMainTankTrigger : public Trigger
{
public:
HighKingMaulgarIsMainTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar is main tank") {}
bool IsActive() override;
};
class HighKingMaulgarIsFirstAssistTankTrigger : public Trigger
{
public:
HighKingMaulgarIsFirstAssistTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar is first assist tank") {}
bool IsActive() override;
};
class HighKingMaulgarIsSecondAssistTankTrigger : public Trigger
{
public:
HighKingMaulgarIsSecondAssistTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar is second assist tank") {}
bool IsActive() override;
};
class HighKingMaulgarIsMageTankTrigger : public Trigger
{
public:
HighKingMaulgarIsMageTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar is mage tank") {}
bool IsActive() override;
};
class HighKingMaulgarIsMoonkinTankTrigger : public Trigger
{
public:
HighKingMaulgarIsMoonkinTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar is moonkin tank") {}
bool IsActive() override;
};
class HighKingMaulgarDeterminingKillOrderTrigger : public Trigger
{
public:
HighKingMaulgarDeterminingKillOrderTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar determining kill order") {}
bool IsActive() override;
};
class HighKingMaulgarHealerInDangerTrigger : public Trigger
{
public:
HighKingMaulgarHealerInDangerTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar healers in danger") {}
bool IsActive() override;
};
class HighKingMaulgarBossChannelingWhirlwindTrigger : public Trigger
{
public:
HighKingMaulgarBossChannelingWhirlwindTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar boss channeling whirlwind") {}
bool IsActive() override;
};
class HighKingMaulgarWildFelstalkerSpawnedTrigger : public Trigger
{
public:
HighKingMaulgarWildFelstalkerSpawnedTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar wild felstalker spawned") {}
bool IsActive() override;
};
class HighKingMaulgarPullingOlmAndBlindeyeTrigger : public Trigger
{
public:
HighKingMaulgarPullingOlmAndBlindeyeTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar pulling olm and blindeye") {}
bool IsActive() override;
};
class GruulTheDragonkillerBossEngagedByMainTankTrigger : public Trigger
{
public:
GruulTheDragonkillerBossEngagedByMainTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "gruul the dragonkiller boss engaged by main tank") {}
bool IsActive() override;
};
class GruulTheDragonkillerBossEngagedByRangeTrigger : public Trigger
{
public:
GruulTheDragonkillerBossEngagedByRangeTrigger(PlayerbotAI* botAI) : Trigger(botAI, "gruul the dragonkiller boss engaged by range") {}
bool IsActive() override;
};
class GruulTheDragonkillerIncomingShatterTrigger : public Trigger
{
public:
GruulTheDragonkillerIncomingShatterTrigger(PlayerbotAI* botAI) : Trigger(botAI, "gruul the dragonkiller incoming shatter") {}
bool IsActive() override;
};
#endif