Rewrite Gruul's Lair Strategies (#2473)

<!--
Thank you for contributing to mod-playerbots, please make sure that
you...
1. Submit your PR to the test-staging branch, not master.
2. Read the guidelines below before submitting.
3. Don't delete parts of this template.

DESIGN PHILOSOPHY: We prioritize STABILITY, PERFORMANCE, AND
PREDICTABILITY over behavioral realism.

Every action and decision executes PER BOT AND PER TRIGGER. Small
increases in logic complexity scale
poorly across thousands of bots and negatively affect all. We prioritize
a stable system over a smarter
one. Bots don't need to behave perfectly; believable behavior is the
goal, not human simulation.
Default behavior must be cheap in processing; expensive behavior must be
opt-in.

Before submitting, make sure your changes aligns with these principles.
-->

## Pull Request Description
<!-- Describe what this change does and why it is needed -->

Well, this is what I was doing while I let AI do the buff announcements
PR...

GL was the first strategy I wrote alone (Revision started Karazhan), and
it sucks. Since AC has now fixed Olm to actually chase and Kiggler to no
longer move to within his Arcane Explosion distance, it was a good time
to redo things. Overall, the code should be much simpler and better for
performance.

I also tweaked several strategies. My raid is close to expansion BiS now
so I tested at 20/20 damage/healing IP nerfs. One shot both, and overall
the bot coordination is a lot smoother than I remember when I was
actually running these raids.

Some stuff is still not great, like
GruulTheDragonkillerSpreadRangedAction() is overdone for sure, but I've
spent all the time I'm willing to on this one. I don't want this to
become a longer project.

## Feature Evaluation
<!--
If your PR is very minimal (comment typo, wrong ID reference, etc), and
it is very obvious it will not have
any impact on performance, you may skip these question. If necessary, a
maintainer may ask you for them later.
-->

<!-- Please answer the following: -->
- Describe the **minimum logic** required to achieve the intended
behavior.
- Describe the **processing cost** when this logic executes across many
bots.

Same structure as before. Improved commonly used methods based on
experience since I did the original strategy (e.g., how to do tank
movement or spread bots), threw out some super complicated, bulky
methods in favor of much simpler ones that work better anyway, yadda
yadda.

## How to Test the Changes
<!--
- Step-by-step instructions to test the change.
- Any required setup (e.g. multiple players, number of bots, specific
configuration).
- Expected behavior and how to verify it.
-->

Go fight HKM and Gruul. For HKM, bring three tanks, a mage, and a
boomin. For Gruul, bring an alarm clock.

## Impact Assessment
<!-- As a generic test, before and after measure of pmon (playerbot pmon
tick) can help you here. -->
- Does this change increase per-bot/per-tick processing or risk scaling
poorly with thousands of bots?
    - - [x] No, not at all
    - - [ ] Minimal impact (**explain below**)
    - - [ ] Moderate impact (**explain below**)



- Does this change modify default bot behavior?
    - - [x] No
    - - [ ] Yes (**explain why**)



- Does this change add new decision branches or increase maintenance
complexity?
    - - [x] No
    - - [ ] Yes (**explain below**)



## AI Assistance
<!--
AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
We expect contributors to be honest about what they do and do not
understand.
-->
Was AI assistance used while working on this change?
- - [x] No
- - [ ] Yes (**explain below**)
<!--
If yes, please specify:
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation).
- Which parts of the change were influenced or generated, and whether it
was thoroughly reviewed.
-->



<!--
TRANSLATIONS:
Anything new that the bots say in chat must be in a translatable format.
This is done using GetBotTextOrDefault,
which you can search for in the codebase to find examples. Your code
needs to have English as the default fallback,
while the full translations need to be in an SQL update file. The
languages in the file are the nine language
options supported by AzerothCore: English, Korean, French, German,
Chinese, Taiwanese, Spanish, Spanish Mexico, and
Russian. See
data/sql/playerbots/updates/2025_12_27_ai_playerbot_fishing_text.sql as
an example of a translation SQL
update, whose content are called within the codebase at
src/strategy/actions/FishingAction.cpp
-->

## Final Checklist

- - [x] Stability is not compromised.
- - [x] Performance impact is understood, tested, and acceptable.
- - [x] Added logic complexity is justified and explained.
- - [x] Any new bot dialogue lines are translated.
- - [x] Documentation updated if needed (Conf comments, WiKi commands).

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
This commit is contained in:
Crow 2026-06-19 18:02:43 -05:00 committed by GitHub
parent fffbf65738
commit 82b5c7538e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 816 additions and 810 deletions

View File

@ -1,5 +1,5 @@
#ifndef PLAYERBOTS_RAIDGRUULSLAIRACTIONCONTEXT_H #ifndef PLAYERBOTS_GRUULACTIONCONTEXT_H
#define PLAYERBOTS_RAIDGRUULSLAIRACTIONCONTEXT_H #define PLAYERBOTS_GRUULACTIONCONTEXT_H
#include "GruulActions.h" #include "GruulActions.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
@ -10,40 +10,101 @@ public:
RaidGruulsLairActionContext() RaidGruulsLairActionContext()
{ {
// High King Maulgar // High King Maulgar
creators["high king maulgar main tank attack maulgar"] = &RaidGruulsLairActionContext::high_king_maulgar_main_tank_attack_maulgar; creators["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; &RaidGruulsLairActionContext::high_king_maulgar_main_tank_attack_maulgar;
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 first assist tank attack olm"] =
creators["high king maulgar moonkin tank attack kiggler"] = &RaidGruulsLairActionContext::high_king_maulgar_moonkin_tank_attack_kiggler; &RaidGruulsLairActionContext::high_king_maulgar_first_assist_tank_attack_olm;
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 second assist tank attack blindeye"] =
creators["high king maulgar run away from whirlwind"] = &RaidGruulsLairActionContext::high_king_maulgar_run_away_from_whirlwind; &RaidGruulsLairActionContext::high_king_maulgar_second_assist_tank_attack_blindeye;
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; 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 run away from whirlwind"] =
&RaidGruulsLairActionContext::high_king_maulgar_run_away_from_whirlwind;
creators["high king maulgar move away from blast nova danger"] =
&RaidGruulsLairActionContext::high_king_maulgar_move_away_from_blast_nova_danger;
creators["high king maulgar banish fel stalker"] =
&RaidGruulsLairActionContext::high_king_maulgar_banish_fel_stalker;
creators["high king maulgar misdirect ogres to tanks"] =
&RaidGruulsLairActionContext::high_king_maulgar_misdirect_ogres_to_tanks;
// Gruul the Dragonkiller // Gruul the Dragonkiller
creators["gruul the dragonkiller tanks position boss"] = &RaidGruulsLairActionContext::gruul_the_dragonkiller_tanks_position_boss; creators["gruul the dragonkiller tanks position boss"] =
creators["gruul the dragonkiller spread ranged"] = &RaidGruulsLairActionContext::gruul_the_dragonkiller_spread_ranged; &RaidGruulsLairActionContext::gruul_the_dragonkiller_tanks_position_boss;
creators["gruul the dragonkiller shatter spread"] = &RaidGruulsLairActionContext::gruul_the_dragonkiller_shatter_spread;
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: private:
// High King Maulgar // High King Maulgar
static Action* high_king_maulgar_main_tank_attack_maulgar(PlayerbotAI* botAI) { return new HighKingMaulgarMainTankAttackMaulgarAction(botAI); } static Action* high_king_maulgar_main_tank_attack_maulgar(PlayerbotAI* botAI) {
static Action* high_king_maulgar_first_assist_tank_attack_olm(PlayerbotAI* botAI) { return new HighKingMaulgarFirstAssistTankAttackOlmAction(botAI); } return new HighKingMaulgarMainTankAttackMaulgarAction(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_first_assist_tank_attack_olm(PlayerbotAI* botAI) {
static Action* high_king_maulgar_assign_dps_priority(PlayerbotAI* botAI) { return new HighKingMaulgarAssignDPSPriorityAction(botAI); } return new HighKingMaulgarFirstAssistTankAttackOlmAction(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_second_assist_tank_attack_blindeye(PlayerbotAI* botAI) {
static Action* high_king_maulgar_misdirect_olm_and_blindeye(PlayerbotAI* botAI) { return new HighKingMaulgarMisdirectOlmAndBlindeyeAction(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_run_away_from_whirlwind(PlayerbotAI* botAI) {
return new HighKingMaulgarRunAwayFromWhirlwindAction(botAI);
}
static Action* high_king_maulgar_move_away_from_blast_nova_danger(PlayerbotAI* botAI) {
return new HighKingMaulgarMoveAwayFromBlastNovaDangerAction(botAI);
}
static Action* high_king_maulgar_banish_fel_stalker(PlayerbotAI* botAI) {
return new HighKingMaulgarBanishFelStalkerAction(botAI);
}
static Action* high_king_maulgar_misdirect_ogres_to_tanks(PlayerbotAI* botAI) {
return new HighKingMaulgarMisdirectOgresToTanksAction(botAI);
}
// Gruul the Dragonkiller // Gruul the Dragonkiller
static Action* gruul_the_dragonkiller_tanks_position_boss(PlayerbotAI* botAI) { return new GruulTheDragonkillerTanksPositionBossAction(botAI); } static Action* gruul_the_dragonkiller_tanks_position_boss(PlayerbotAI* botAI) {
static Action* gruul_the_dragonkiller_spread_ranged(PlayerbotAI* botAI) { return new GruulTheDragonkillerSpreadRangedAction(botAI); } return new GruulTheDragonkillerTanksPositionBossAction(botAI);
static Action* gruul_the_dragonkiller_shatter_spread(PlayerbotAI* botAI) { return new GruulTheDragonkillerShatterSpreadAction(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 #endif

View File

@ -25,18 +25,19 @@ bool HighKingMaulgarMainTankAttackMaulgarAction::Execute(Event /*event*/)
if (maulgar->GetVictim() == bot) if (maulgar->GetVictim() == bot)
{ {
const Position& position = MAULGAR_TANK_POSITION; const Position& position = MAULGAR_TANK_POSITION;
const float maxDistance = 3.0f; const float distToPosition =
bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
float distanceToPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); if (distToPosition > 3.0f)
if (distanceToPosition > maxDistance)
{ {
float dX = position.GetPositionX() - bot->GetPositionX(); const float dX = position.GetPositionX() - bot->GetPositionX();
float dY = position.GetPositionY() - bot->GetPositionY(); const float dY = position.GetPositionY() - bot->GetPositionY();
float moveX = bot->GetPositionX() + (dX / distanceToPosition) * maxDistance; const float moveDist = std::min(5.0f, distToPosition);
float moveY = bot->GetPositionY() + (dY / distanceToPosition) * maxDistance; const float moveX = bot->GetPositionX() + (dX / distToPosition) * moveDist;
return MoveTo(GRUULS_LAIR_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false, const float moveY = bot->GetPositionY() + (dY / distToPosition) * moveDist;
MovementPriority::MOVEMENT_COMBAT, true, true);
return MoveTo(GRUULS_LAIR_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false,
false, false, MovementPriority::MOVEMENT_COMBAT, true, true);
} }
} }
@ -59,19 +60,19 @@ bool HighKingMaulgarFirstAssistTankAttackOlmAction::Execute(Event /*event*/)
if (olm->GetVictim() == bot) if (olm->GetVictim() == bot)
{ {
const Position& position = OLM_TANK_POSITION; const Position& position = OLM_TANK_POSITION;
const float maxDistance = 3.0f; const float distToPosition =
const float olmTankLeeway = 30.0f; bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
float distanceOlmToPosition = olm->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); if (distToPosition > 3.0f)
if (distanceOlmToPosition > olmTankLeeway)
{ {
float dX = position.GetPositionX() - bot->GetPositionX(); const float dX = position.GetPositionX() - bot->GetPositionX();
float dY = position.GetPositionY() - bot->GetPositionY(); const float dY = position.GetPositionY() - bot->GetPositionY();
float moveX = bot->GetPositionX() + (dX / distanceOlmToPosition) * maxDistance; const float moveDist = std::min(5.0f, distToPosition);
float moveY = bot->GetPositionY() + (dY / distanceOlmToPosition) * maxDistance; const float moveX = bot->GetPositionX() + (dX / distToPosition) * moveDist;
return MoveTo(GRUULS_LAIR_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false, const float moveY = bot->GetPositionY() + (dY / distToPosition) * moveDist;
MovementPriority::MOVEMENT_COMBAT, true, false);
return MoveTo(GRUULS_LAIR_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false,
false, false, MovementPriority::MOVEMENT_COMBAT, true, true);
} }
} }
@ -94,18 +95,19 @@ bool HighKingMaulgarSecondAssistTankAttackBlindeyeAction::Execute(Event /*event*
if (blindeye->GetVictim() == bot) if (blindeye->GetVictim() == bot)
{ {
const Position& position = BLINDEYE_TANK_POSITION; const Position& position = BLINDEYE_TANK_POSITION;
const float maxDistance = 3.0f; const float distToPosition =
bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
float distanceToPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); if (distToPosition > 3.0f)
if (distanceToPosition > maxDistance)
{ {
float dX = position.GetPositionX() - bot->GetPositionX(); const float dX = position.GetPositionX() - bot->GetPositionX();
float dY = position.GetPositionY() - bot->GetPositionY(); const float dY = position.GetPositionY() - bot->GetPositionY();
float moveX = bot->GetPositionX() + (dX / distanceToPosition) * maxDistance; const float moveDist = std::min(5.0f, distToPosition);
float moveY = bot->GetPositionY() + (dY / distanceToPosition) * maxDistance; const float moveX = bot->GetPositionX() + (dX / distToPosition) * moveDist;
return MoveTo(GRUULS_LAIR_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false, const float moveY = bot->GetPositionY() + (dY / distToPosition) * moveDist;
MovementPriority::MOVEMENT_COMBAT, true, false);
return MoveTo(GRUULS_LAIR_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false,
false, false, MovementPriority::MOVEMENT_COMBAT, true, true);
} }
} }
@ -122,11 +124,17 @@ bool HighKingMaulgarMageTankAttackKroshAction::Execute(Event /*event*/)
MarkTargetWithTriangle(bot, krosh); MarkTargetWithTriangle(bot, krosh);
SetRtiTarget(botAI, "triangle", krosh); SetRtiTarget(botAI, "triangle", krosh);
if (krosh->HasAura(SPELL_SPELL_SHIELD) && botAI->CanCastSpell("spellsteal", krosh)) if (krosh->HasAura(static_cast<uint32>(GruulsLairSpells::SPELL_SPELL_SHIELD)) &&
botAI->CanCastSpell("spellsteal", krosh))
{
return botAI->CastSpell("spellsteal", krosh); return botAI->CastSpell("spellsteal", krosh);
}
if (!bot->HasAura(SPELL_SPELL_SHIELD) && botAI->CanCastSpell("fire ward", bot)) if (!bot->HasAura(static_cast<uint32>(GruulsLairSpells::SPELL_SPELL_SHIELD)) &&
botAI->CanCastSpell("fire ward", bot))
{
return botAI->CastSpell("fire ward", bot); return botAI->CastSpell("fire ward", bot);
}
if (AI_VALUE(Unit*, "current target") != krosh) if (AI_VALUE(Unit*, "current target") != krosh)
return Attack(krosh); return Attack(krosh);
@ -134,30 +142,27 @@ bool HighKingMaulgarMageTankAttackKroshAction::Execute(Event /*event*/)
if (krosh->GetVictim() == bot) if (krosh->GetVictim() == bot)
{ {
const Position& position = KROSH_TANK_POSITION; const Position& position = KROSH_TANK_POSITION;
float distanceToKrosh = krosh->GetExactDist2d(position.GetPositionX(), position.GetPositionY()); const float distanceKroshToPosition =
const float minDistance = 16.0f; krosh->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
const float maxDistance = 29.0f; constexpr float minDistance = 17.0f;
const float tankPositionLeeway = 1.0f; constexpr float maxDistance = 30.0f;
if (distanceToKrosh > minDistance && distanceToKrosh < maxDistance) if (distanceKroshToPosition > minDistance && distanceKroshToPosition < maxDistance &&
bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY()) > 1.0f)
{ {
if (!bot->IsWithinDist2d(position.GetPositionX(), position.GetPositionY(), tankPositionLeeway)) return MoveTo(GRUULS_LAIR_MAP_ID, position.GetPositionX(), position.GetPositionY(),
{ position.GetPositionZ(), false, false, false, false,
return MoveTo(GRUULS_LAIR_MAP_ID, position.GetPositionX(), position.GetPositionY(), position.GetPositionZ(), MovementPriority::MOVEMENT_COMBAT, true, false);
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
float orientation = atan2(krosh->GetPositionY() - bot->GetPositionY(),
krosh->GetPositionX() - bot->GetPositionX());
bot->SetFacingTo(orientation);
} }
else else
{ {
Position safePos; constexpr float safeDistance = 15.0f;
if (TryGetNewSafePosition(botAI, bot, safePos)) const float currentDistance = bot->GetDistance2d(krosh);
if (currentDistance < safeDistance)
{ {
return MoveTo(GRUULS_LAIR_MAP_ID, safePos.GetPositionX(), safePos.GetPositionY(), safePos.GetPositionZ(), botAI->InterruptSpell();
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); return MoveAway(krosh, safeDistance - currentDistance);
} }
} }
} }
@ -178,11 +183,16 @@ bool HighKingMaulgarMoonkinTankAttackKigglerAction::Execute(Event /*event*/)
if (AI_VALUE(Unit*, "current target") != kiggler) if (AI_VALUE(Unit*, "current target") != kiggler)
return Attack(kiggler); return Attack(kiggler);
Position safePos; if (kiggler->GetVictim() == bot)
if (TryGetNewSafePosition(botAI, bot, safePos))
{ {
return MoveTo(GRUULS_LAIR_MAP_ID, safePos.GetPositionX(), safePos.GetPositionY(), safePos.GetPositionZ(), constexpr float safeDistance = 28.5f;
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); const float currentDistance = bot->GetDistance2d(kiggler);
if (currentDistance < safeDistance)
{
botAI->InterruptSpell();
return MoveAway(kiggler, safeDistance - currentDistance);
}
} }
return false; return false;
@ -194,15 +204,7 @@ bool HighKingMaulgarAssignDPSPriorityAction::Execute(Event /*event*/)
Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer"); Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer");
if (blindeye) if (blindeye)
{ {
Position safePos; MarkTargetWithStar(bot, blindeye);
if (TryGetNewSafePosition(botAI, bot, safePos))
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
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); SetRtiTarget(botAI, "star", blindeye);
if (AI_VALUE(Unit*, "current target") != blindeye) if (AI_VALUE(Unit*, "current target") != blindeye)
@ -215,15 +217,7 @@ bool HighKingMaulgarAssignDPSPriorityAction::Execute(Event /*event*/)
Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner"); Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner");
if (olm) if (olm)
{ {
Position safePos; MarkTargetWithCircle(bot, olm);
if (TryGetNewSafePosition(botAI, bot, safePos))
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
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); SetRtiTarget(botAI, "circle", olm);
if (AI_VALUE(Unit*, "current target") != olm) if (AI_VALUE(Unit*, "current target") != olm)
@ -236,15 +230,7 @@ bool HighKingMaulgarAssignDPSPriorityAction::Execute(Event /*event*/)
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand"); Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand");
if (krosh && botAI->IsRanged(bot)) if (krosh && botAI->IsRanged(bot))
{ {
Position safePos; MarkTargetWithTriangle(bot, krosh);
if (TryGetNewSafePosition(botAI, bot, safePos))
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
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); SetRtiTarget(botAI, "triangle", krosh);
if (AI_VALUE(Unit*, "current target") != krosh) if (AI_VALUE(Unit*, "current target") != krosh)
@ -257,15 +243,7 @@ bool HighKingMaulgarAssignDPSPriorityAction::Execute(Event /*event*/)
Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed"); Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed");
if (kiggler) if (kiggler)
{ {
Position safePos; MarkTargetWithDiamond(bot, kiggler);
if (TryGetNewSafePosition(botAI, bot, safePos))
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
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); SetRtiTarget(botAI, "diamond", kiggler);
if (AI_VALUE(Unit*, "current target") != kiggler) if (AI_VALUE(Unit*, "current target") != kiggler)
@ -278,15 +256,7 @@ bool HighKingMaulgarAssignDPSPriorityAction::Execute(Event /*event*/)
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar"); Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
if (maulgar) if (maulgar)
{ {
Position safePos; MarkTargetWithSquare(bot, maulgar);
if (TryGetNewSafePosition(botAI, bot, safePos))
{
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
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); SetRtiTarget(botAI, "square", maulgar);
if (AI_VALUE(Unit*, "current target") != maulgar) if (AI_VALUE(Unit*, "current target") != maulgar)
@ -296,98 +266,71 @@ bool HighKingMaulgarAssignDPSPriorityAction::Execute(Event /*event*/)
return false; return false;
} }
// Avoid Whirlwind and Blast Wave and generally try to stay near the center of the room
bool HighKingMaulgarHealerFindSafePositionAction::Execute(Event /*event*/)
{
const Position& center = MAULGAR_ROOM_CENTER;
const float maxDistanceFromCenter = 50.0f;
float distToCenter = bot->GetExactDist2d(center.GetPositionX(), center.GetPositionY());
if (distToCenter > maxDistanceFromCenter)
{
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(GRUULS_LAIR_MAP_ID, 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(GRUULS_LAIR_MAP_ID, safePos.GetPositionX(), safePos.GetPositionY(), safePos.GetPositionZ(),
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*/) bool HighKingMaulgarRunAwayFromWhirlwindAction::Execute(Event /*event*/)
{ {
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar"); Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
if (!maulgar) if (!maulgar)
return false; return false;
const float safeDistance = 10.0f; constexpr float safeDistance = 8.0f;
float distance = bot->GetExactDist2d(maulgar); const float currentDistance = bot->GetDistance2d(maulgar);
if (distance < safeDistance) if (currentDistance < safeDistance)
{ {
float angle = atan2(bot->GetPositionY() - maulgar->GetPositionY(), botAI->InterruptSpell();
bot->GetPositionX() - maulgar->GetPositionX()); return MoveAway(maulgar, safeDistance - currentDistance);
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(GRUULS_LAIR_MAP_ID, destX, destY, destZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
} }
return false; return false;
} }
bool HighKingMaulgarBanishFelstalkerAction::Execute(Event /*event*/) bool HighKingMaulgarMoveAwayFromBlastNovaDangerAction::Execute(Event /*event*/)
{
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand");
if (!krosh)
return false;
constexpr float safeDistance = 20.0f;
constexpr uint32 minInterval = 1000;
const float currentDistance = bot->GetDistance2d(krosh);
if (currentDistance < safeDistance)
{
botAI->InterruptSpell();
return FleePosition(krosh->GetPosition(), safeDistance, minInterval);
}
return false;
}
bool HighKingMaulgarBanishFelStalkerAction::Execute(Event /*event*/)
{ {
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
if (!group) if (!group)
return false; return false;
const GuidVector& npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
std::vector<Unit*> felStalkers; std::vector<Unit*> felStalkers;
for (auto const& npc : npcs) std::list<Creature*> creatureList;
constexpr float searchRadius = 50.0f;
bot->GetCreatureListWithEntryInGrid(
creatureList, static_cast<uint32>(GruulsLairNpcs::NPC_WILD_FEL_STALKER), searchRadius);
for (Creature* creature : creatureList)
{ {
Unit* unit = botAI->GetUnit(npc); if (creature && creature->IsAlive())
if (unit && unit->GetEntry() == NPC_WILD_FEL_STALKER && unit->IsAlive()) felStalkers.push_back(creature);
felStalkers.push_back(unit);
} }
std::vector<Player*> warlocks; std::vector<Player*> warlocks;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
Player* member = ref->GetSource(); Player* member = ref->GetSource();
if (member && member->IsAlive() && member->getClass() == CLASS_WARLOCK && GET_PLAYERBOT_AI(member)) if (member && member->IsAlive() && member->getClass() == CLASS_WARLOCK &&
GET_PLAYERBOT_AI(member))
{
warlocks.push_back(member); warlocks.push_back(member);
} }
}
int warlockIndex = -1; int warlockIndex = -1;
for (size_t i = 0; i < warlocks.size(); ++i) for (size_t i = 0; i < warlocks.size(); ++i)
@ -399,21 +342,26 @@ bool HighKingMaulgarBanishFelstalkerAction::Execute(Event /*event*/)
} }
} }
const int64_t felStalkersSize = felStalkers.size(); const uint8 felStalkersSize = felStalkers.size();
if (warlockIndex >= 0 && warlockIndex < felStalkersSize) if (warlockIndex >= 0 && warlockIndex < felStalkersSize)
{ {
Unit* assignedFelStalker = felStalkers[warlockIndex]; Unit* assignedFelStalker = felStalkers[warlockIndex];
if (!botAI->HasAura("banish", assignedFelStalker) && botAI->CanCastSpell("banish", assignedFelStalker)) if (!botAI->HasAura("banish", assignedFelStalker) &&
botAI->CanCastSpell("banish", assignedFelStalker))
{
return botAI->CastSpell("banish", assignedFelStalker); return botAI->CastSpell("banish", assignedFelStalker);
} }
}
return false; return false;
} }
// Hunter 1: Misdirect Olm to first offtank and have pet attack Blindeye // Hunter 1: Misdirect Blindeye to second offtank
// Hunter 2: Misdirect Blindeye to second offtank // Hunter 2: Misdirect Olm to first offtank
bool HighKingMaulgarMisdirectOlmAndBlindeyeAction::Execute(Event /*event*/) // Hunter 3: Misdirect Kiggler to Moonkin tank
// Hunter 4: Misdirect Krosh to Mage tank
bool HighKingMaulgarMisdirectOgresToTanksAction::Execute(Event /*event*/)
{ {
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
if (!group) if (!group)
@ -423,68 +371,82 @@ bool HighKingMaulgarMisdirectOlmAndBlindeyeAction::Execute(Event /*event*/)
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
Player* member = ref->GetSource(); Player* member = ref->GetSource();
if (member && member->IsAlive() && member->getClass() == CLASS_HUNTER && GET_PLAYERBOT_AI(member)) if (member && member->IsAlive() && member->getClass() == CLASS_HUNTER &&
GET_PLAYERBOT_AI(member))
{
hunters.push_back(member); hunters.push_back(member);
} }
int hunterIndex = -1; if (hunters.size() >= 4)
break;
}
int8 hunterIndex = -1;
for (size_t i = 0; i < hunters.size(); ++i) for (size_t i = 0; i < hunters.size(); ++i)
{ {
if (hunters[i] == bot) if (hunters[i] == bot)
{ {
hunterIndex = static_cast<int>(i); hunterIndex = static_cast<int8>(i);
break; break;
} }
} }
if (hunterIndex == -1)
return false;
Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner"); Unit* ogreTarget = nullptr;
Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer"); Player* tankTarget = nullptr;
Player* olmTank = nullptr; if (hunterIndex == 0)
Player* blindeyeTank = nullptr; {
ogreTarget = AI_VALUE2(Unit*, "find target", "blindeye the seer");
tankTarget = GetGroupAssistTank(botAI, bot, 1);
}
else if (hunterIndex == 1)
{
ogreTarget = AI_VALUE2(Unit*, "find target", "olm the summoner");
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
Player* member = ref->GetSource(); if (Player* member = GetGroupAssistTank(botAI, bot, 0))
if (!member || !member->IsAlive()) {
continue; tankTarget = member;
else if (botAI->IsAssistTankOfIndex(member, 0)) olmTank = member; break;
else if (botAI->IsAssistTankOfIndex(member, 1)) blindeyeTank = member; }
}
}
else if (hunterIndex == 2)
{
ogreTarget = AI_VALUE2(Unit*, "find target", "kiggler the crazed");
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
if (Player* member = GetKigglerMoonkinTank(bot))
{
tankTarget = member;
break;
}
}
}
else if (hunterIndex == 3)
{
ogreTarget = AI_VALUE2(Unit*, "find target", "krosh firehand");
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
if (Player* member = GetKroshMageTank(bot))
{
tankTarget = member;
break;
}
}
} }
switch (hunterIndex) if (!ogreTarget || !tankTarget || !tankTarget->IsAlive())
{ return false;
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: if (botAI->CanCastSpell("misdirection", tankTarget))
botAI->CastSpell("misdirection", blindeyeTank); return botAI->CastSpell("misdirection", tankTarget);
if (bot->HasAura(SPELL_MISDIRECTION))
return botAI->CastSpell("steady shot", blindeye);
break;
default: if (bot->HasAura(static_cast<uint32>(GruulsLairSpells::SPELL_MISDIRECTION)) &&
break; botAI->CanCastSpell("steady shot", ogreTarget))
{
return botAI->CastSpell("steady shot", ogreTarget);
} }
return false; return false;
@ -505,19 +467,19 @@ bool GruulTheDragonkillerTanksPositionBossAction::Execute(Event /*event*/)
if (gruul->GetVictim() == bot) if (gruul->GetVictim() == bot)
{ {
const Position& position = GRUUL_TANK_POSITION; const Position& position = GRUUL_TANK_POSITION;
const float maxDistance = 5.0f; const float distToPosition =
bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
float dX = position.GetPositionX() - bot->GetPositionX(); if (distToPosition > 3.0f)
float dY = position.GetPositionY() - bot->GetPositionY();
float distanceToTankPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
if (distanceToTankPosition > maxDistance)
{ {
float moveX = bot->GetPositionX() + (dX / distanceToTankPosition) * maxDistance; const float dX = position.GetPositionX() - bot->GetPositionX();
float moveY = bot->GetPositionY() + (dY / distanceToTankPosition) * maxDistance; const float dY = position.GetPositionY() - bot->GetPositionY();
const float moveZ = position.GetPositionZ(); const float moveDist = std::min(5.0f, distToPosition);
return MoveTo(GRUULS_LAIR_MAP_ID, moveX, moveY, moveZ, false, false, false, false, const float moveX = bot->GetPositionX() + (dX / distToPosition) * moveDist;
MovementPriority::MOVEMENT_COMBAT, true, false); const float moveY = bot->GetPositionY() + (dY / distToPosition) * moveDist;
return MoveTo(GRUULS_LAIR_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false,
false, false, MovementPriority::MOVEMENT_COMBAT, true, true);
} }
} }
@ -528,27 +490,17 @@ bool GruulTheDragonkillerTanksPositionBossAction::Execute(Event /*event*/)
// Ranged should spread out 10 yards from each other // Ranged should spread out 10 yards from each other
bool GruulTheDragonkillerSpreadRangedAction::Execute(Event /*event*/) bool GruulTheDragonkillerSpreadRangedAction::Execute(Event /*event*/)
{ {
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
if (!gruul)
return false;
if (gruul->GetHealth() == gruul->GetMaxHealth())
_hasReachedInitialPosition = false;
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
if (!group) if (!group)
return false; 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->GetHealth() == gruul->GetMaxHealth())
{
initialPositions.erase(bot->GetGUID());
hasReachedInitialPosition.erase(bot->GetGUID());
}
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;
std::vector<Player*> members; std::vector<Player*> members;
Player* closestMember = nullptr; Player* closestMember = nullptr;
float closestDist = std::numeric_limits<float>::max(); float closestDist = std::numeric_limits<float>::max();
@ -559,6 +511,7 @@ bool GruulTheDragonkillerSpreadRangedAction::Execute(Event /*event*/)
continue; continue;
members.push_back(member); members.push_back(member);
if (member != bot) if (member != bot)
{ {
float dist = bot->GetExactDist2d(member); float dist = bot->GetExactDist2d(member);
@ -570,49 +523,53 @@ bool GruulTheDragonkillerSpreadRangedAction::Execute(Event /*event*/)
} }
} }
if (!initialPositions.count(bot->GetGUID())) const Position& position = GRUUL_TANK_POSITION;
if (_initialPosition.GetPositionX() == 0.0f && _initialPosition.GetPositionY() == 0.0f)
{ {
auto it = std::find(members.begin(), members.end(), bot); auto it = std::find(members.begin(), members.end(), bot);
uint8 botIndex = (it != members.end()) ? std::distance(members.begin(), it) : 0; uint8 botIndex = (it != members.end()) ? std::distance(members.begin(), it) : 0;
uint8 count = members.size(); uint8 count = members.size();
constexpr float minRadius = 25.0f;
constexpr float maxRadius = 40.0f;
float angle = 2 * M_PI * botIndex / count; float angle = 2 * M_PI * botIndex / count;
float radius = minRadius + static_cast<float>(rand()) / float radius = minRadius + static_cast<float>(rand()) /
static_cast<float>(RAND_MAX) * (maxRadius - minRadius); static_cast<float>(RAND_MAX) * (maxRadius - minRadius);
float targetX = centerX + radius * cos(angle); float targetX = position.GetPositionX() + radius * cos(angle);
float targetY = centerY + radius * sin(angle); float targetY = position.GetPositionY() + radius * sin(angle);
initialPositions[bot->GetGUID()] = Position(targetX, targetY, centerZ); _initialPosition = Position(targetX, targetY, position.GetPositionZ());
hasReachedInitialPosition[bot->GetGUID()] = false;
} }
Position targetPosition = initialPositions[bot->GetGUID()]; if (!_hasReachedInitialPosition)
if (!hasReachedInitialPosition[bot->GetGUID()])
{ {
if (!bot->IsWithinDist2d(targetPosition.GetPositionX(), targetPosition.GetPositionY(), 2.0f)) const float distanceToTarget =
bot->GetExactDist2d(_initialPosition.GetPositionX(), _initialPosition.GetPositionY());
if (distanceToTarget > 2.0f)
{ {
float destX = targetPosition.GetPositionX(); float destX = _initialPosition.GetPositionX();
float destY = targetPosition.GetPositionY(); float destY = _initialPosition.GetPositionY();
float destZ = targetPosition.GetPositionZ(); float destZ = _initialPosition.GetPositionZ();
if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(),
bot->GetPositionY(), bot->GetPositionZ(), destX, destY, destZ)) bot->GetPositionY(), bot->GetPositionZ(), destX, destY, destZ))
return false;
return MoveTo(GRUULS_LAIR_MAP_ID, 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(), return false;
closestMember->GetPositionZ()), minSpreadDistance, 0); }
return MoveTo(GRUULS_LAIR_MAP_ID, destX, destY, destZ, false, false, false,
false, MovementPriority::MOVEMENT_COMBAT, true, false);
}
_hasReachedInitialPosition = true;
}
else
{
constexpr float minSpreadDistance = 10.0f;
constexpr uint32 minInterval = 1000;
if (closestMember && closestDist < minSpreadDistance)
return FleePosition(closestMember->GetPosition(), minSpreadDistance, minInterval);
} }
return false; return false;
@ -621,33 +578,10 @@ bool GruulTheDragonkillerSpreadRangedAction::Execute(Event /*event*/)
// Try to get away from other group members when Ground Slam is cast // Try to get away from other group members when Ground Slam is cast
bool GruulTheDragonkillerShatterSpreadAction::Execute(Event /*event*/) bool GruulTheDragonkillerShatterSpreadAction::Execute(Event /*event*/)
{ {
Group* group = bot->GetGroup(); constexpr float safeDistance = 10.0f;
if (!group) constexpr uint32 minInterval = 0;
return false; if (Unit* nearestPlayer = GetNearestPlayerInRadius(bot, safeDistance))
return FleePosition(nearestPlayer->GetPosition(), safeDistance, minInterval);
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; return false;
} }

View File

@ -1,5 +1,5 @@
#ifndef PLAYERBOTS_RAIDGRUULSLAIRACTIONS_H #ifndef PLAYERBOTS_GRUULACTIONS_H
#define PLAYERBOTS_RAIDGRUULSLAIRACTIONS_H #define PLAYERBOTS_GRUULACTIONS_H
#include "Action.h" #include "Action.h"
#include "AttackAction.h" #include "AttackAction.h"
@ -8,104 +8,108 @@
class HighKingMaulgarMainTankAttackMaulgarAction : public AttackAction class HighKingMaulgarMainTankAttackMaulgarAction : public AttackAction
{ {
public: public:
HighKingMaulgarMainTankAttackMaulgarAction(PlayerbotAI* botAI, std::string const name = "high king maulgar main tank attack maulgar") : AttackAction(botAI, name) {}; HighKingMaulgarMainTankAttackMaulgarAction(
PlayerbotAI* botAI, std::string const name = "high king maulgar main tank attack maulgar") : AttackAction(botAI, name) {};
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
class HighKingMaulgarFirstAssistTankAttackOlmAction : public AttackAction class HighKingMaulgarFirstAssistTankAttackOlmAction : public AttackAction
{ {
public: public:
HighKingMaulgarFirstAssistTankAttackOlmAction(PlayerbotAI* botAI, std::string const name = "high king maulgar first assist tank attack olm") : AttackAction(botAI, name) {}; HighKingMaulgarFirstAssistTankAttackOlmAction(
PlayerbotAI* botAI, std::string const name = "high king maulgar first assist tank attack olm") : AttackAction(botAI, name) {};
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
class HighKingMaulgarSecondAssistTankAttackBlindeyeAction : public AttackAction class HighKingMaulgarSecondAssistTankAttackBlindeyeAction : public AttackAction
{ {
public: public:
HighKingMaulgarSecondAssistTankAttackBlindeyeAction(PlayerbotAI* botAI, std::string const name = "high king maulgar second assist tank attack blindeye") : AttackAction(botAI, name) {}; HighKingMaulgarSecondAssistTankAttackBlindeyeAction(
PlayerbotAI* botAI, std::string const name = "high king maulgar second assist tank attack blindeye") : AttackAction(botAI, name) {};
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
class HighKingMaulgarMageTankAttackKroshAction : public AttackAction class HighKingMaulgarMageTankAttackKroshAction : public AttackAction
{ {
public: public:
HighKingMaulgarMageTankAttackKroshAction(PlayerbotAI* botAI, std::string const name = "high king maulgar mage tank attack krosh") : AttackAction(botAI, name) {}; HighKingMaulgarMageTankAttackKroshAction(
PlayerbotAI* botAI, std::string const name = "high king maulgar mage tank attack krosh") : AttackAction(botAI, name) {};
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
class HighKingMaulgarMoonkinTankAttackKigglerAction : public AttackAction class HighKingMaulgarMoonkinTankAttackKigglerAction : public AttackAction
{ {
public: public:
HighKingMaulgarMoonkinTankAttackKigglerAction(PlayerbotAI* botAI, std::string const name = "high king maulgar moonkin tank attack kiggler") : AttackAction(botAI, name) {}; HighKingMaulgarMoonkinTankAttackKigglerAction(
PlayerbotAI* botAI, std::string const name = "high king maulgar moonkin tank attack kiggler") : AttackAction(botAI, name) {};
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
class HighKingMaulgarAssignDPSPriorityAction : public AttackAction class HighKingMaulgarAssignDPSPriorityAction : public AttackAction
{ {
public: public:
HighKingMaulgarAssignDPSPriorityAction(PlayerbotAI* botAI, std::string const name = "high king maulgar assign dps priority") : AttackAction(botAI, name) {}; 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; bool Execute(Event event) override;
}; };
class HighKingMaulgarRunAwayFromWhirlwindAction : public MovementAction class HighKingMaulgarRunAwayFromWhirlwindAction : public MovementAction
{ {
public: public:
HighKingMaulgarRunAwayFromWhirlwindAction(PlayerbotAI* botAI, std::string const name = "high king maulgar run away from whirlwind") : MovementAction(botAI, name) {}; HighKingMaulgarRunAwayFromWhirlwindAction(
PlayerbotAI* botAI, std::string const name = "high king maulgar run away from whirlwind") : MovementAction(botAI, name) {};
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
class HighKingMaulgarBanishFelstalkerAction : public AttackAction class HighKingMaulgarMoveAwayFromBlastNovaDangerAction : public MovementAction
{ {
public: public:
HighKingMaulgarBanishFelstalkerAction(PlayerbotAI* botAI, std::string const name = "high king maulgar banish felstalker") : AttackAction(botAI, name) {}; HighKingMaulgarMoveAwayFromBlastNovaDangerAction(
PlayerbotAI* botAI, std::string const name = "high king maulgar move away from blast nova danger") : MovementAction(botAI, name) {};
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
class HighKingMaulgarMisdirectOlmAndBlindeyeAction : public AttackAction class HighKingMaulgarBanishFelStalkerAction : public AttackAction
{ {
public: public:
HighKingMaulgarMisdirectOlmAndBlindeyeAction(PlayerbotAI* botAI, std::string const name = "high king maulgar misdirect olm and blindeye") : AttackAction(botAI, name) {}; HighKingMaulgarBanishFelStalkerAction(
PlayerbotAI* botAI, std::string const name = "high king maulgar banish fel stalker") : AttackAction(botAI, name) {};
bool Execute(Event event) override;
};
class HighKingMaulgarMisdirectOgresToTanksAction : public AttackAction
{
public:
HighKingMaulgarMisdirectOgresToTanksAction(
PlayerbotAI* botAI, std::string const name = "high king maulgar misdirect ogres to tanks") : AttackAction(botAI, name) {};
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
class GruulTheDragonkillerTanksPositionBossAction : public AttackAction class GruulTheDragonkillerTanksPositionBossAction : public AttackAction
{ {
public: public:
GruulTheDragonkillerTanksPositionBossAction(PlayerbotAI* botAI, std::string const name = "gruul the dragonkiller tanks 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; bool Execute(Event event) override;
}; };
class GruulTheDragonkillerSpreadRangedAction : public MovementAction class GruulTheDragonkillerSpreadRangedAction : public MovementAction
{ {
public: public:
GruulTheDragonkillerSpreadRangedAction(PlayerbotAI* botAI, std::string const name = "gruul the dragonkiller spread ranged") : MovementAction(botAI, name) {}; GruulTheDragonkillerSpreadRangedAction(
PlayerbotAI* botAI, std::string const name = "gruul the dragonkiller spread ranged") : MovementAction(botAI, name) {};
bool Execute(Event event) override; bool Execute(Event event) override;
private:
Position _initialPosition;
bool _hasReachedInitialPosition = false;
}; };
class GruulTheDragonkillerShatterSpreadAction : public MovementAction class GruulTheDragonkillerShatterSpreadAction : public MovementAction
{ {
public: public:
GruulTheDragonkillerShatterSpreadAction(PlayerbotAI* botAI, std::string const name = "gruul the dragonkiller shatter spread") : MovementAction(botAI, name) {}; GruulTheDragonkillerShatterSpreadAction(
PlayerbotAI* botAI, std::string const name = "gruul the dragonkiller shatter spread") : MovementAction(botAI, name) {};
bool Execute(Event event) override; bool Execute(Event event) override;
}; };

View File

@ -6,44 +6,19 @@
namespace GruulsLairHelpers namespace GruulsLairHelpers
{ {
// 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) const Position MAULGAR_TANK_POSITION = { 90.686f, 167.047f, -13.234f };
{ const Position OLM_TANK_POSITION = { 101.050f, 219.359f, -9.503f };
const char* ogreBossNames[] = const Position BLINDEYE_TANK_POSITION = { 99.681f, 213.989f, -10.345f };
{ const Position KROSH_TANK_POSITION = { 116.880f, 166.208f, -14.231f };
"high king maulgar", const Position MAULGAR_ROOM_CENTER = { 88.754f, 150.759f, -11.569f };
"kiggler the crazed", const Position GRUUL_TANK_POSITION = { 241.238f, 365.025f, -4.220f };
"krosh firehand",
"olm the summoner",
"blindeye the seer"
};
for (const char* name : ogreBossNames) Player* GetKroshMageTank(Player* bot)
{ {
Unit* boss = botAI->GetAiObjectContext()->GetValue<Unit*>("find target", name)->Get();
if (!boss || !boss->IsAlive())
continue;
return true;
}
return false;
}
bool IsKroshMageTank(Player* bot)
{
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
if (!group) if (!group)
return false; return nullptr;
// (1) First loop: Return the first assistant Mage (real player or bot) // (1) First loop: Return the first assistant Mage (real player or bot)
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
@ -53,7 +28,8 @@ namespace GruulsLairHelpers
continue; continue;
if (group->IsAssistant(member->GetGUID())) if (group->IsAssistant(member->GetGUID()))
return member == bot; return member;
} }
// (2) Fall back to bot Mage with highest HP // (2) Fall back to bot Mage with highest HP
@ -65,7 +41,9 @@ namespace GruulsLairHelpers
Player* member = ref->GetSource(); Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) || if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
member->getClass() != CLASS_MAGE) member->getClass() != CLASS_MAGE)
{
continue; continue;
}
uint32 hp = member->GetMaxHealth(); uint32 hp = member->GetMaxHealth();
if (!highestHpMage || hp > highestHp) if (!highestHpMage || hp > highestHp)
@ -75,15 +53,16 @@ namespace GruulsLairHelpers
} }
} }
// (3) Return the found Mage tank, or nullptr if none found return highestHpMage;
return highestHpMage == bot; }
}
bool IsKigglerMoonkinTank(Player* bot) Player* GetKigglerMoonkinTank(Player* bot)
{ {
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
if (!group) if (!group)
return false; return nullptr;
uint8 tab = AiFactory::GetPlayerSpecTab(bot);
// (1) First loop: Return the first assistant Moonkin (real player or bot) // (1) First loop: Return the first assistant Moonkin (real player or bot)
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
@ -92,9 +71,8 @@ namespace GruulsLairHelpers
if (!member || !member->IsAlive() || member->getClass() != CLASS_DRUID) if (!member || !member->IsAlive() || member->getClass() != CLASS_DRUID)
continue; continue;
if (group->IsAssistant(member->GetGUID()) && if (group->IsAssistant(member->GetGUID()) && tab == DRUID_TAB_BALANCE)
AiFactory::GetPlayerSpecTab(member) == DRUID_TAB_BALANCE) return member;
return member == bot;
} }
// (2) Fall back to bot Moonkin with highest HP // (2) Fall back to bot Moonkin with highest HP
@ -104,8 +82,10 @@ namespace GruulsLairHelpers
{ {
Player* member = ref->GetSource(); Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member->getClass() != CLASS_DRUID || if (!member || !member->IsAlive() || member->getClass() != CLASS_DRUID ||
!GET_PLAYERBOT_AI(member) || AiFactory::GetPlayerSpecTab(member) != DRUID_TAB_BALANCE) !GET_PLAYERBOT_AI(member) || tab != DRUID_TAB_BALANCE)
{
continue; continue;
}
uint32 hp = member->GetMaxHealth(); uint32 hp = member->GetMaxHealth();
if (!highestHpMoonkin || hp > highestHp) if (!highestHpMoonkin || hp > highestHp)
@ -115,90 +95,7 @@ namespace GruulsLairHelpers
} }
} }
// (3) Return the found Moonkin tank, or nullptr if none found return highestHpMoonkin;
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 = krosh->GetDistance2d(pos.GetPositionX(), pos.GetPositionY());
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 = maulgar->GetDistance2d(pos.GetPositionX(), pos.GetPositionY());
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.Relocate(bot->GetPositionX() + SEARCH_RADIUS * cos(angle),
bot->GetPositionY() + SEARCH_RADIUS * sin(angle),
bot->GetPositionZ());
float destX = candidatePos.GetPositionX();
float destY = candidatePos.GetPositionY();
float destZ = candidatePos.GetPositionZ();
if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
bot->GetPositionZ(), destX, destY, destZ, true))
continue;
if (destX != candidatePos.GetPositionX() || destY != candidatePos.GetPositionY())
continue;
candidatePos.Relocate(destX, destY, destZ);
if (IsPositionSafe(botAI, bot, candidatePos))
{
float movementDistance = bot->GetDistance2d(destX, destY);
if (movementDistance < bestScore)
{
bestScore = movementDistance;
bestPos = candidatePos;
foundSafeSpot = true;
}
}
}
if (foundSafeSpot)
{
outPos = bestPos;
return true;
}
outPos = Position();
return false;
}
} }

View File

@ -1,12 +1,13 @@
#ifndef RAID_GRUULSLAIRHELPERS_H #ifndef PLAYERBOTS_GRUULHELPERS_H
#define RAID_GRUULSLAIRHELPERS_H #define PLAYERBOTS_GRUULHELPERS_H
#include "PlayerbotAI.h" #include "PlayerbotAI.h"
namespace GruulsLairHelpers namespace GruulsLairHelpers
{ {
enum GruulsLairSpells
{ enum class GruulsLairSpells : uint32
{
// High King Maulgar // High King Maulgar
SPELL_WHIRLWIND = 33238, SPELL_WHIRLWIND = 33238,
@ -19,27 +20,25 @@ namespace GruulsLairHelpers
// Gruul the Dragonkiller // Gruul the Dragonkiller
SPELL_GROUND_SLAM_1 = 33525, SPELL_GROUND_SLAM_1 = 33525,
SPELL_GROUND_SLAM_2 = 39187, SPELL_GROUND_SLAM_2 = 39187,
}; };
enum GruulsLairNPCs enum class GruulsLairNpcs : uint32
{ {
NPC_WILD_FEL_STALKER = 18847, NPC_WILD_FEL_STALKER = 18847,
}; };
constexpr uint32 GRUULS_LAIR_MAP_ID = 565; constexpr uint32 GRUULS_LAIR_MAP_ID = 565;
bool IsAnyOgreBossAlive(PlayerbotAI* botAI); Player* GetKroshMageTank(Player* bot);
bool IsKroshMageTank(Player* bot); Player* GetKigglerMoonkinTank(Player* bot);
bool IsKigglerMoonkinTank(Player* bot);
bool IsPositionSafe(PlayerbotAI* botAI, Player* bot, Position pos); extern const Position MAULGAR_TANK_POSITION;
bool TryGetNewSafePosition(PlayerbotAI* botAI, Player* bot, Position& outPos); 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;
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 #endif

View File

@ -2,43 +2,68 @@
#include "GruulActions.h" #include "GruulActions.h"
#include "GruulHelpers.h" #include "GruulHelpers.h"
#include "ChooseTargetActions.h" #include "ChooseTargetActions.h"
#include "DruidBearActions.h"
#include "DruidCatActions.h"
#include "GenericSpellActions.h" #include "GenericSpellActions.h"
#include "HunterActions.h" #include "HunterActions.h"
#include "MageActions.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "ReachTargetActions.h" #include "ReachTargetActions.h"
#include "WarriorActions.h" #include "ShamanActions.h"
using namespace GruulsLairHelpers; using namespace GruulsLairHelpers;
float HighKingMaulgarDisableTankAssistMultiplier::GetValue(Action* action) float HighKingMaulgarDelayBloodlustAndHeroismMultiplier::GetValue(Action* action)
{ {
if (bot->GetVictim() == nullptr) if (bot->getClass() != CLASS_SHAMAN)
return 1.0f; return 1.0f;
if (IsAnyOgreBossAlive(botAI) && dynamic_cast<TankAssistAction*>(action)) Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer");
if (!blindeye || blindeye->GetHealthPct() < 75.0f)
return 1.0f;
if (dynamic_cast<CastHeroismAction*>(action) ||
dynamic_cast<CastBloodlustAction*>(action))
{
return 0.0f; return 0.0f;
}
return 1.0f;
}
float HighKingMaulgarControlTankActionsMultiplier::GetValue(Action* action)
{
if (!botAI->IsTank(bot))
return 1.0f;
if (!AI_VALUE2(Unit*, "find target", "high king maulgar"))
return 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
(bot->GetVictim() != nullptr && dynamic_cast<TankAssistAction*>(action)))
{
return 0.0f;
}
return 1.0f; return 1.0f;
} }
// Don't run back in during Whirlwind
float HighKingMaulgarAvoidWhirlwindMultiplier::GetValue(Action* action) float HighKingMaulgarAvoidWhirlwindMultiplier::GetValue(Action* action)
{ {
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar"); Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed"); if (!maulgar ||
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand"); !maulgar->HasAura(static_cast<uint32>(GruulsLairSpells::SPELL_WHIRLWIND)))
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 && !krosh && !olm && !blindeye)
{ {
return 1.0f;
}
if (AI_VALUE(Unit*, "current target") != maulgar)
return 1.0f;
if (botAI->IsMainTank(bot))
return 1.0f;
if (dynamic_cast<CastReachTargetSpellAction*>(action) || if (dynamic_cast<CastReachTargetSpellAction*>(action) ||
(dynamic_cast<MovementAction*>(action) && (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<HighKingMaulgarRunAwayFromWhirlwindAction*>(action))) !dynamic_cast<HighKingMaulgarRunAwayFromWhirlwindAction*>(action)))
{
return 0.0f; return 0.0f;
} }
@ -48,56 +73,82 @@ float HighKingMaulgarAvoidWhirlwindMultiplier::GetValue(Action* action)
// Arcane Shot will remove Spell Shield, which the mage tank needs to survive // Arcane Shot will remove Spell Shield, which the mage tank needs to survive
float HighKingMaulgarDisableArcaneShotOnKroshMultiplier::GetValue(Action* action) float HighKingMaulgarDisableArcaneShotOnKroshMultiplier::GetValue(Action* action)
{ {
if (bot->getClass() != CLASS_HUNTER)
return 1.0f;
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand"); Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand");
if (!krosh || AI_VALUE(Unit*, "current target") != krosh)
return 1.0f;
if (krosh && AI_VALUE(Unit*, "current target") == krosh && if (dynamic_cast<CastArcaneShotAction*>(action))
dynamic_cast<CastArcaneShotAction*>(action))
return 0.0f; return 0.0f;
return 1.0f; return 1.0f;
} }
float HighKingMaulgarDisableMageTankAOEMultiplier::GetValue(Action* action) float HighKingMaulgarDisableMageTankAoeMultiplier::GetValue(Action* action)
{ {
if (IsKroshMageTank(bot) && if (bot->getClass() != CLASS_MAGE)
(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; return 1.0f;
if (botAI->IsMainTank(bot)) if (!AI_VALUE2(Unit*, "find target", "krosh firehand"))
return 1.0f;
auto castSpellAction = dynamic_cast<CastSpellAction*>(action);
if (castSpellAction &&
castSpellAction->getThreatType() == Action::ActionThreatType::Aoe)
{ {
if (gruul->GetVictim() == bot && dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
if (dynamic_cast<AvoidAoeAction*>(action))
return 0.0f; return 0.0f;
} }
return 1.0f; return 1.0f;
} }
float GruulTheDragonkillerGroundSlamMultiplier::GetValue(Action* action) float GruulTheDragonkillerDelayBloodlustAndHeroismMultiplier::GetValue(Action* action)
{ {
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller"); if (bot->getClass() != CLASS_SHAMAN)
if (!gruul)
return 1.0f; return 1.0f;
if (bot->HasAura(SPELL_GROUND_SLAM_1) || Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
bot->HasAura(SPELL_GROUND_SLAM_2)) if (!gruul || gruul->GetHealthPct() < 95.0f)
return 1.0f;
if (dynamic_cast<CastHeroismAction*>(action) ||
dynamic_cast<CastBloodlustAction*>(action))
{
return 0.0f;
}
return 1.0f;
}
float GruulTheDragonkillerControlTankMovementMultiplier::GetValue(Action* action)
{
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
if (!gruul || gruul->GetVictim() != bot)
return 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<AvoidAoeAction*>(action))
{
return 0.0f;
}
return 1.0f;
}
float GruulTheDragonkillerStaySpreadForShatterMultiplier::GetValue(Action* action)
{
if (!bot->HasAura(static_cast<uint32>(GruulsLairSpells::SPELL_GROUND_SLAM_1)) &&
!bot->HasAura(static_cast<uint32>(GruulsLairSpells::SPELL_GROUND_SLAM_2)))
{
return 1.0f;
}
if (dynamic_cast<CastReachTargetSpellAction*>(action) ||
(dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<GruulTheDragonkillerShatterSpreadAction*>(action)))
{ {
if ((dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<GruulTheDragonkillerShatterSpreadAction*>(action)) ||
dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f; return 0.0f;
} }

View File

@ -1,47 +1,69 @@
#ifndef PLAYERBOTS_RAIDGRUULSLAIRMULTIPLIERS_H #ifndef PLAYERBOTS_GRUULMULTIPLIERS_H
#define PLAYERBOTS_RAIDGRUULSLAIRMULTIPLIERS_H #define PLAYERBOTS_GRUULMULTIPLIERS_H
#include "Multiplier.h" #include "Multiplier.h"
class HighKingMaulgarDisableTankAssistMultiplier : public Multiplier class HighKingMaulgarDelayBloodlustAndHeroismMultiplier : public Multiplier
{ {
public: public:
HighKingMaulgarDisableTankAssistMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "high king maulgar disable tank assist multiplier") {} HighKingMaulgarDelayBloodlustAndHeroismMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "high king maulgar delay bloodlust and heroism multiplier") {}
float GetValue(Action* action) override;
};
class HighKingMaulgarControlTankActionsMultiplier : public Multiplier
{
public:
HighKingMaulgarControlTankActionsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "high king maulgar control tank actions multiplier") {}
float GetValue(Action* action) override; float GetValue(Action* action) override;
}; };
class HighKingMaulgarAvoidWhirlwindMultiplier : public Multiplier class HighKingMaulgarAvoidWhirlwindMultiplier : public Multiplier
{ {
public: public:
HighKingMaulgarAvoidWhirlwindMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "high king maulgar avoid whirlwind multiplier") {} HighKingMaulgarAvoidWhirlwindMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "high king maulgar avoid whirlwind multiplier") {}
float GetValue(Action* action) override; float GetValue(Action* action) override;
}; };
class HighKingMaulgarDisableArcaneShotOnKroshMultiplier : public Multiplier class HighKingMaulgarDisableArcaneShotOnKroshMultiplier : public Multiplier
{ {
public: public:
HighKingMaulgarDisableArcaneShotOnKroshMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "high king maulgar disable arcane shot on krosh multiplier") {} HighKingMaulgarDisableArcaneShotOnKroshMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "high king maulgar disable arcane shot on krosh multiplier") {}
float GetValue(Action* action) override; float GetValue(Action* action) override;
}; };
class HighKingMaulgarDisableMageTankAOEMultiplier : public Multiplier class HighKingMaulgarDisableMageTankAoeMultiplier : public Multiplier
{ {
public: public:
HighKingMaulgarDisableMageTankAOEMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "high king maulgar disable mage tank aoe multiplier") {} HighKingMaulgarDisableMageTankAoeMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "high king maulgar disable mage tank aoe multiplier") {}
float GetValue(Action* action) override; float GetValue(Action* action) override;
}; };
class GruulTheDragonkillerMainTankMovementMultiplier : public Multiplier class GruulTheDragonkillerDelayBloodlustAndHeroismMultiplier : public Multiplier
{ {
public: public:
GruulTheDragonkillerMainTankMovementMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "gruul the dragonkiller main tank movement multiplier") {} GruulTheDragonkillerDelayBloodlustAndHeroismMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "gruul the dragonkiller delay bloodlust and heroism multiplier") {}
float GetValue(Action* action) override; float GetValue(Action* action) override;
}; };
class GruulTheDragonkillerGroundSlamMultiplier : public Multiplier class GruulTheDragonkillerControlTankMovementMultiplier : public Multiplier
{ {
public: public:
GruulTheDragonkillerGroundSlamMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "gruul the dragonkiller ground slam multiplier") {} GruulTheDragonkillerControlTankMovementMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "gruul the dragonkiller control tank movement multiplier") {}
float GetValue(Action* action) override;
};
class GruulTheDragonkillerStaySpreadForShatterMultiplier : public Multiplier
{
public:
GruulTheDragonkillerStaySpreadForShatterMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "gruul the dragonkiller stay spread for shatter multiplier") {}
float GetValue(Action* action) override; float GetValue(Action* action) override;
}; };

View File

@ -4,35 +4,35 @@
void RaidGruulsLairStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void RaidGruulsLairStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
// High King Maulgar // High King Maulgar
triggers.push_back(new TriggerNode("high king maulgar is main tank", { triggers.push_back(new TriggerNode("high king maulgar boss engaged by main tank", {
NextAction("high king maulgar main tank attack maulgar", ACTION_RAID + 1) })); NextAction("high king maulgar main tank attack maulgar", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("high king maulgar is first assist tank", { triggers.push_back(new TriggerNode("high king maulgar olm engaged by first assist tank", {
NextAction("high king maulgar first assist tank attack olm", ACTION_RAID + 1) })); NextAction("high king maulgar first assist tank attack olm", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("high king maulgar is second assist tank", { triggers.push_back(new TriggerNode("high king maulgar blindeye engaged by second assist tank", {
NextAction("high king maulgar second assist tank attack blindeye", ACTION_RAID + 1) })); NextAction("high king maulgar second assist tank attack blindeye", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("high king maulgar is mage tank", { triggers.push_back(new TriggerNode("high king maulgar krosh engaged by mage tank", {
NextAction("high king maulgar mage tank attack krosh", ACTION_RAID + 1) })); NextAction("high king maulgar mage tank attack krosh", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("high king maulgar is moonkin tank", { triggers.push_back(new TriggerNode("high king maulgar kiggler engaged by moonkin tank", {
NextAction("high king maulgar moonkin tank attack kiggler", ACTION_RAID + 1) })); NextAction("high king maulgar moonkin tank attack kiggler", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("high king maulgar determining kill order", { triggers.push_back(new TriggerNode("high king maulgar determining kill order", {
NextAction("high king maulgar assign dps priority", ACTION_RAID + 1) })); NextAction("high king maulgar assign dps priority", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("high king maulgar healer in danger", {
NextAction("high king maulgar healer find safe position", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("high king maulgar boss channeling whirlwind", { triggers.push_back(new TriggerNode("high king maulgar boss channeling whirlwind", {
NextAction("high king maulgar run away from whirlwind", ACTION_EMERGENCY + 6) })); NextAction("high king maulgar run away from whirlwind", ACTION_EMERGENCY + 7) }));
triggers.push_back(new TriggerNode("high king maulgar wild felstalker spawned", { triggers.push_back(new TriggerNode("high king maulgar krosh casts blast wave", {
NextAction("high king maulgar banish felstalker", ACTION_RAID + 2) })); NextAction("high king maulgar move away from blast nova danger", ACTION_EMERGENCY + 6) }));
triggers.push_back(new TriggerNode("high king maulgar pulling olm and blindeye", { triggers.push_back(new TriggerNode("high king maulgar wild fel stalker spawned", {
NextAction("high king maulgar misdirect olm and blindeye", ACTION_RAID + 2) })); NextAction("high king maulgar banish fel stalker", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("high king maulgar pulling ogre council", {
NextAction("high king maulgar misdirect ogres to tanks", ACTION_RAID + 2) }));
// Gruul the Dragonkiller // Gruul the Dragonkiller
triggers.push_back(new TriggerNode("gruul the dragonkiller boss engaged by tanks", { triggers.push_back(new TriggerNode("gruul the dragonkiller boss engaged by tanks", {
@ -47,10 +47,15 @@ void RaidGruulsLairStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
void RaidGruulsLairStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers) void RaidGruulsLairStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{ {
multipliers.push_back(new HighKingMaulgarDisableTankAssistMultiplier(botAI)); // High King Maulgar
multipliers.push_back(new HighKingMaulgarDelayBloodlustAndHeroismMultiplier(botAI));
multipliers.push_back(new HighKingMaulgarControlTankActionsMultiplier(botAI));
multipliers.push_back(new HighKingMaulgarAvoidWhirlwindMultiplier(botAI)); multipliers.push_back(new HighKingMaulgarAvoidWhirlwindMultiplier(botAI));
multipliers.push_back(new HighKingMaulgarDisableArcaneShotOnKroshMultiplier(botAI)); multipliers.push_back(new HighKingMaulgarDisableArcaneShotOnKroshMultiplier(botAI));
multipliers.push_back(new HighKingMaulgarDisableMageTankAOEMultiplier(botAI)); multipliers.push_back(new HighKingMaulgarDisableMageTankAoeMultiplier(botAI));
multipliers.push_back(new GruulTheDragonkillerMainTankMovementMultiplier(botAI));
multipliers.push_back(new GruulTheDragonkillerGroundSlamMultiplier(botAI)); // Gruul the Dragonkiller
multipliers.push_back(new GruulTheDragonkillerDelayBloodlustAndHeroismMultiplier(botAI));
multipliers.push_back(new GruulTheDragonkillerControlTankMovementMultiplier(botAI));
multipliers.push_back(new GruulTheDragonkillerStaySpreadForShatterMultiplier(botAI));
} }

View File

@ -1,5 +1,5 @@
#ifndef PLAYERBOTS_RAIDGRUULSLAIRSTRATEGY_H #ifndef PLAYERBOTS_GRUULSTRATEGY_H
#define PLAYERBOTS_RAIDGRUULSLAIRSTRATEGY_H #define PLAYERBOTS_GRUULSTRATEGY_H
#include "Strategy.h" #include "Strategy.h"

View File

@ -1,5 +1,5 @@
#ifndef PLAYERBOTS_RAIDGRUULSLAIRTRIGGERCONTEXT_H #ifndef PLAYERBOTS_GRUULTRIGGERCONTEXT_H
#define PLAYERBOTS_RAIDGRUULSLAIRTRIGGERCONTEXT_H #define PLAYERBOTS_GRUULTRIGGERCONTEXT_H
#include "GruulTriggers.h" #include "GruulTriggers.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
@ -10,40 +10,101 @@ public:
RaidGruulsLairTriggerContext() : NamedObjectContext<Trigger>() RaidGruulsLairTriggerContext() : NamedObjectContext<Trigger>()
{ {
// High King Maulgar // High King Maulgar
creators["high king maulgar is main tank"] = &RaidGruulsLairTriggerContext::high_king_maulgar_is_main_tank; creators["high king maulgar boss engaged by main tank"] =
creators["high king maulgar is first assist tank"] = &RaidGruulsLairTriggerContext::high_king_maulgar_is_first_assist_tank; &RaidGruulsLairTriggerContext::high_king_maulgar_boss_engaged_by_main_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 olm engaged by first assist tank"] =
creators["high king maulgar is moonkin tank"] = &RaidGruulsLairTriggerContext::high_king_maulgar_is_moonkin_tank; &RaidGruulsLairTriggerContext::high_king_maulgar_olm_engaged_by_first_assist_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 blindeye engaged by second assist tank"] =
creators["high king maulgar boss channeling whirlwind"] = &RaidGruulsLairTriggerContext::high_king_maulgar_boss_channeling_whirlwind; &RaidGruulsLairTriggerContext::high_king_maulgar_blindeye_engaged_by_second_assist_tank;
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; creators["high king maulgar krosh engaged by mage tank"] =
&RaidGruulsLairTriggerContext::high_king_maulgar_krosh_engaged_by_mage_tank;
creators["high king maulgar kiggler engaged by moonkin tank"] =
&RaidGruulsLairTriggerContext::high_king_maulgar_kiggler_engaged_by_moonkin_tank;
creators["high king maulgar determining kill order"] =
&RaidGruulsLairTriggerContext::high_king_maulgar_determining_kill_order;
creators["high king maulgar boss channeling whirlwind"] =
&RaidGruulsLairTriggerContext::high_king_maulgar_boss_channeling_whirlwind;
creators["high king maulgar krosh casts blast wave"] =
&RaidGruulsLairTriggerContext::high_king_maulgar_krosh_casts_blast_wave;
creators["high king maulgar wild fel stalker spawned"] =
&RaidGruulsLairTriggerContext::high_king_maulgar_wild_fel_stalker_spawned;
creators["high king maulgar pulling ogre council"] =
&RaidGruulsLairTriggerContext::high_king_maulgar_pulling_ogre_council;
// Gruul the Dragonkiller // Gruul the Dragonkiller
creators["gruul the dragonkiller boss engaged by tanks"] = &RaidGruulsLairTriggerContext::gruul_the_dragonkiller_boss_engaged_by_tanks; creators["gruul the dragonkiller boss engaged by tanks"] =
creators["gruul the dragonkiller boss engaged by ranged"] = &RaidGruulsLairTriggerContext::gruul_the_dragonkiller_boss_engaged_by_ranged; &RaidGruulsLairTriggerContext::gruul_the_dragonkiller_boss_engaged_by_tanks;
creators["gruul the dragonkiller incoming shatter"] = &RaidGruulsLairTriggerContext::gruul_the_dragonkiller_incoming_shatter;
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;
} }
private: private:
// High King Maulgar // High King Maulgar
static Trigger* high_king_maulgar_is_main_tank(PlayerbotAI* botAI) { return new HighKingMaulgarIsMainTankTrigger(botAI); } static Trigger* high_king_maulgar_boss_engaged_by_main_tank(PlayerbotAI* botAI) {
static Trigger* high_king_maulgar_is_first_assist_tank(PlayerbotAI* botAI) { return new HighKingMaulgarIsFirstAssistTankTrigger(botAI); } return new HighKingMaulgarBossEngagedByMainTankTrigger(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_olm_engaged_by_first_assist_tank(PlayerbotAI* botAI) {
static Trigger* high_king_maulgar_determining_kill_order(PlayerbotAI* botAI) { return new HighKingMaulgarDeterminingKillOrderTrigger(botAI); } return new HighKingMaulgarOlmEngagedByFirstAssistTankTrigger(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_blindeye_engaged_by_second_assist_tank(PlayerbotAI* botAI) {
static Trigger* high_king_maulgar_pulling_olm_and_blindeye(PlayerbotAI* botAI) { return new HighKingMaulgarPullingOlmAndBlindeyeTrigger(botAI); } return new HighKingMaulgarBlindeyeEngagedBySecondAssistTankTrigger(botAI);
}
static Trigger* high_king_maulgar_krosh_engaged_by_mage_tank(PlayerbotAI* botAI) {
return new HighKingMaulgarKroshEngagedByMageTankTrigger(botAI);
}
static Trigger* high_king_maulgar_kiggler_engaged_by_moonkin_tank(PlayerbotAI* botAI) {
return new HighKingMaulgarKigglerEngagedByMoonkinTankTrigger(botAI);
}
static Trigger* high_king_maulgar_determining_kill_order(PlayerbotAI* botAI) {
return new HighKingMaulgarDeterminingKillOrderTrigger(botAI);
}
static Trigger* high_king_maulgar_boss_channeling_whirlwind(PlayerbotAI* botAI) {
return new HighKingMaulgarBossChannelingWhirlwindTrigger(botAI);
}
static Trigger* high_king_maulgar_krosh_casts_blast_wave(PlayerbotAI* botAI) {
return new HighKingMaulgarKroshCastsBlastWaveTrigger(botAI);
}
static Trigger* high_king_maulgar_wild_fel_stalker_spawned(PlayerbotAI* botAI) {
return new HighKingMaulgarWildFelStalkerSpawnedTrigger(botAI);
}
static Trigger* high_king_maulgar_pulling_ogre_council(PlayerbotAI* botAI) {
return new HighKingMaulgarPullingOgreCouncilTrigger(botAI);
}
// Gruul the Dragonkiller // Gruul the Dragonkiller
static Trigger* gruul_the_dragonkiller_boss_engaged_by_tanks(PlayerbotAI* botAI) { return new GruulTheDragonkillerBossEngagedByTanksTrigger(botAI); } static Trigger* gruul_the_dragonkiller_boss_engaged_by_tanks(PlayerbotAI* botAI) {
static Trigger* gruul_the_dragonkiller_boss_engaged_by_ranged(PlayerbotAI* botAI) { return new GruulTheDragonkillerBossEngagedByRangedTrigger(botAI); } return new GruulTheDragonkillerBossEngagedByTanksTrigger(botAI);
static Trigger* gruul_the_dragonkiller_incoming_shatter(PlayerbotAI* botAI) { return new GruulTheDragonkillerIncomingShatterTrigger(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);
}
}; };
#endif #endif

View File

@ -6,154 +6,113 @@ using namespace GruulsLairHelpers;
// High King Maulgar Triggers // High King Maulgar Triggers
bool HighKingMaulgarIsMainTankTrigger::IsActive() bool HighKingMaulgarBossEngagedByMainTankTrigger::IsActive()
{ {
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar"); return botAI->IsMainTank(bot) &&
AI_VALUE2(Unit*, "find target", "high king maulgar");
return botAI->IsMainTank(bot) && maulgar;
} }
bool HighKingMaulgarIsFirstAssistTankTrigger::IsActive() bool HighKingMaulgarOlmEngagedByFirstAssistTankTrigger::IsActive()
{ {
Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner"); return botAI->IsAssistTankOfIndex(bot, 0, false) &&
AI_VALUE2(Unit*, "find target", "olm the summoner");
return botAI->IsAssistTankOfIndex(bot, 0, false) && olm;
} }
bool HighKingMaulgarIsSecondAssistTankTrigger::IsActive() bool HighKingMaulgarBlindeyeEngagedBySecondAssistTankTrigger::IsActive()
{ {
Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer"); return botAI->IsAssistTankOfIndex(bot, 1, false) &&
AI_VALUE2(Unit*, "find target", "blindeye the seer");
return botAI->IsAssistTankOfIndex(bot, 1, false) && blindeye;
} }
bool HighKingMaulgarIsMageTankTrigger::IsActive() bool HighKingMaulgarKroshEngagedByMageTankTrigger::IsActive()
{ {
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand"); return bot->getClass() == CLASS_MAGE &&
AI_VALUE2(Unit*, "find target", "krosh firehand") &&
return IsKroshMageTank(bot) && krosh; GetKroshMageTank(bot) == bot;
} }
bool HighKingMaulgarIsMoonkinTankTrigger::IsActive() bool HighKingMaulgarKigglerEngagedByMoonkinTankTrigger::IsActive()
{ {
Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed"); return bot->getClass() == CLASS_DRUID &&
AI_VALUE2(Unit*, "find target", "kiggler the crazed") &&
return IsKigglerMoonkinTank(bot) && kiggler; GetKigglerMoonkinTank(bot) == bot;
} }
bool HighKingMaulgarDeterminingKillOrderTrigger::IsActive() bool HighKingMaulgarDeterminingKillOrderTrigger::IsActive()
{ {
if (botAI->IsHeal(bot) || botAI->IsMainTank(bot))
return false;
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar"); Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed"); if (!maulgar)
Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner"); return false;
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)) && if (botAI->IsAssistTankOfIndex(bot, 0, false))
!(botAI->IsMainTank(bot) && maulgar) && return !AI_VALUE2(Unit*, "find target", "olm the summoner");
!(botAI->IsAssistTankOfIndex(bot, 0, false) && olm) &&
!(botAI->IsAssistTankOfIndex(bot, 1, false) && blindeye) &&
!(IsKroshMageTank(bot) && krosh) &&
!(IsKigglerMoonkinTank(bot) && kiggler);
}
bool HighKingMaulgarHealerInDangerTrigger::IsActive() if (botAI->IsAssistTankOfIndex(bot, 1, false))
{ return !AI_VALUE2(Unit*, "find target", "blindeye the seer");
return botAI->IsHeal(bot) && IsAnyOgreBossAlive(botAI);
if (bot->getClass() == CLASS_MAGE && GetKroshMageTank(bot) == bot)
return !AI_VALUE2(Unit*, "find target", "krosh firehand");
if (bot->getClass() == CLASS_DRUID && GetKigglerMoonkinTank(bot) == bot)
return !AI_VALUE2(Unit*, "find target", "kiggler the crazed");
return true;
} }
bool HighKingMaulgarBossChannelingWhirlwindTrigger::IsActive() bool HighKingMaulgarBossChannelingWhirlwindTrigger::IsActive()
{ {
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar"); Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
if (!maulgar ||
!maulgar->HasAura(static_cast<uint32>(GruulsLairSpells::SPELL_WHIRLWIND)))
{
return false;
}
return maulgar && maulgar->HasAura(SPELL_WHIRLWIND) && return !botAI->IsMainTank(bot);
!botAI->IsMainTank(bot);
} }
bool HighKingMaulgarWildFelstalkerSpawnedTrigger::IsActive() bool HighKingMaulgarKroshCastsBlastWaveTrigger::IsActive()
{ {
Unit* felStalker = AI_VALUE2(Unit*, "find target", "wild fel stalker"); if (!AI_VALUE2(Unit*, "find target", "krosh firehand"))
return false;
return felStalker && bot->getClass() == CLASS_WARLOCK; return !botAI->IsTank(bot) && GetKroshMageTank(bot) != bot;
} }
bool HighKingMaulgarPullingOlmAndBlindeyeTrigger::IsActive() bool HighKingMaulgarWildFelStalkerSpawnedTrigger::IsActive()
{ {
Group* group = bot->GetGroup(); return bot->getClass() == CLASS_WARLOCK &&
if (!group || bot->getClass() != CLASS_HUNTER) AI_VALUE2(Unit*, "find target", "wild fel stalker");
}
bool HighKingMaulgarPullingOgreCouncilTrigger::IsActive()
{
if (bot->getClass() != CLASS_HUNTER)
return false; 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"); Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer");
Player* olmTank = nullptr; return blindeye && blindeye->GetHealthPct() > 80.0f;
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->GetHealthPct() > 98.0f &&
olmTank && botAI->CanCastSpell("misdirection", olmTank);
case 1:
return blindeye && blindeye->GetHealthPct() > 90.0f &&
blindeyeTank && botAI->CanCastSpell("misdirection", blindeyeTank);
default:
break;
}
return false;
} }
// Gruul the Dragonkiller Triggers // Gruul the Dragonkiller Triggers
bool GruulTheDragonkillerBossEngagedByTanksTrigger::IsActive() bool GruulTheDragonkillerBossEngagedByTanksTrigger::IsActive()
{ {
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller"); return botAI->IsTank(bot) &&
AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
return gruul && botAI->IsTank(bot);
} }
bool GruulTheDragonkillerBossEngagedByRangedTrigger::IsActive() bool GruulTheDragonkillerBossEngagedByRangedTrigger::IsActive()
{ {
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller"); return botAI->IsRanged(bot) &&
AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
return gruul && botAI->IsRanged(bot);
} }
bool GruulTheDragonkillerIncomingShatterTrigger::IsActive() bool GruulTheDragonkillerIncomingShatterTrigger::IsActive()
{ {
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller"); return bot->HasAura(static_cast<uint32>(GruulsLairSpells::SPELL_GROUND_SLAM_1)) ||
bot->HasAura(static_cast<uint32>(GruulsLairSpells::SPELL_GROUND_SLAM_2));
return gruul && (bot->HasAura(SPELL_GROUND_SLAM_1) ||
bot->HasAura(SPELL_GROUND_SLAM_2));
} }

View File

@ -1,96 +1,109 @@
#ifndef PLAYERBOTS_RAIDGRUULSLAIRTRIGGERS_H #ifndef PLAYERBOTS_GRUULTRIGGERS_H
#define PLAYERBOTS_RAIDGRUULSLAIRTRIGGERS_H #define PLAYERBOTS_GRUULTRIGGERS_H
#include "Trigger.h" #include "Trigger.h"
class HighKingMaulgarIsMainTankTrigger : public Trigger class HighKingMaulgarBossEngagedByMainTankTrigger : public Trigger
{ {
public: public:
HighKingMaulgarIsMainTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar is main tank") {} HighKingMaulgarBossEngagedByMainTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar engaged by main tank") {}
bool IsActive() override; bool IsActive() override;
}; };
class HighKingMaulgarIsFirstAssistTankTrigger : public Trigger class HighKingMaulgarOlmEngagedByFirstAssistTankTrigger : public Trigger
{ {
public: public:
HighKingMaulgarIsFirstAssistTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar is first assist tank") {} HighKingMaulgarOlmEngagedByFirstAssistTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar olm engaged by first assist tank") {}
bool IsActive() override; bool IsActive() override;
}; };
class HighKingMaulgarIsSecondAssistTankTrigger : public Trigger class HighKingMaulgarBlindeyeEngagedBySecondAssistTankTrigger : public Trigger
{ {
public: public:
HighKingMaulgarIsSecondAssistTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar is second assist tank") {} HighKingMaulgarBlindeyeEngagedBySecondAssistTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar blindeye engaged by second assist tank") {}
bool IsActive() override; bool IsActive() override;
}; };
class HighKingMaulgarIsMageTankTrigger : public Trigger class HighKingMaulgarKroshEngagedByMageTankTrigger : public Trigger
{ {
public: public:
HighKingMaulgarIsMageTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar is mage tank") {} HighKingMaulgarKroshEngagedByMageTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar krosh engaged by mage tank") {}
bool IsActive() override; bool IsActive() override;
}; };
class HighKingMaulgarIsMoonkinTankTrigger : public Trigger class HighKingMaulgarKigglerEngagedByMoonkinTankTrigger : public Trigger
{ {
public: public:
HighKingMaulgarIsMoonkinTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar is moonkin tank") {} HighKingMaulgarKigglerEngagedByMoonkinTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar kiggler engaged by moonkin tank") {}
bool IsActive() override; bool IsActive() override;
}; };
class HighKingMaulgarDeterminingKillOrderTrigger : public Trigger class HighKingMaulgarDeterminingKillOrderTrigger : public Trigger
{ {
public: public:
HighKingMaulgarDeterminingKillOrderTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar determining kill order") {} HighKingMaulgarDeterminingKillOrderTrigger(
bool IsActive() override; PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar determining kill order") {}
};
class HighKingMaulgarHealerInDangerTrigger : public Trigger
{
public:
HighKingMaulgarHealerInDangerTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar healers in danger") {}
bool IsActive() override; bool IsActive() override;
}; };
class HighKingMaulgarBossChannelingWhirlwindTrigger : public Trigger class HighKingMaulgarBossChannelingWhirlwindTrigger : public Trigger
{ {
public: public:
HighKingMaulgarBossChannelingWhirlwindTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar boss channeling whirlwind") {} HighKingMaulgarBossChannelingWhirlwindTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar boss channeling whirlwind") {}
bool IsActive() override; bool IsActive() override;
}; };
class HighKingMaulgarWildFelstalkerSpawnedTrigger : public Trigger class HighKingMaulgarKroshCastsBlastWaveTrigger : public Trigger
{ {
public: public:
HighKingMaulgarWildFelstalkerSpawnedTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar wild felstalker spawned") {} HighKingMaulgarKroshCastsBlastWaveTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar krosh casts blast wave") {}
bool IsActive() override; bool IsActive() override;
}; };
class HighKingMaulgarPullingOlmAndBlindeyeTrigger : public Trigger class HighKingMaulgarWildFelStalkerSpawnedTrigger : public Trigger
{ {
public: public:
HighKingMaulgarPullingOlmAndBlindeyeTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar pulling olm and blindeye") {} HighKingMaulgarWildFelStalkerSpawnedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar wild fel stalker spawned") {}
bool IsActive() override;
};
class HighKingMaulgarPullingOgreCouncilTrigger : public Trigger
{
public:
HighKingMaulgarPullingOgreCouncilTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar pulling ogre council") {}
bool IsActive() override; bool IsActive() override;
}; };
class GruulTheDragonkillerBossEngagedByTanksTrigger : public Trigger class GruulTheDragonkillerBossEngagedByTanksTrigger : public Trigger
{ {
public: public:
GruulTheDragonkillerBossEngagedByTanksTrigger(PlayerbotAI* botAI) : Trigger(botAI, "gruul the dragonkiller boss engaged by tanks") {} GruulTheDragonkillerBossEngagedByTanksTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "gruul the dragonkiller boss engaged by tanks") {}
bool IsActive() override; bool IsActive() override;
}; };
class GruulTheDragonkillerBossEngagedByRangedTrigger : public Trigger class GruulTheDragonkillerBossEngagedByRangedTrigger : public Trigger
{ {
public: public:
GruulTheDragonkillerBossEngagedByRangedTrigger(PlayerbotAI* botAI) : Trigger(botAI, "gruul the dragonkiller boss engaged by ranged") {} GruulTheDragonkillerBossEngagedByRangedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "gruul the dragonkiller boss engaged by ranged") {}
bool IsActive() override; bool IsActive() override;
}; };
class GruulTheDragonkillerIncomingShatterTrigger : public Trigger class GruulTheDragonkillerIncomingShatterTrigger : public Trigger
{ {
public: public:
GruulTheDragonkillerIncomingShatterTrigger(PlayerbotAI* botAI) : Trigger(botAI, "gruul the dragonkiller incoming shatter") {} GruulTheDragonkillerIncomingShatterTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "gruul the dragonkiller incoming shatter") {}
bool IsActive() override; bool IsActive() override;
}; };