diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 9a670961..bf6b0a34 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -1534,6 +1534,9 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster) case 533: strategyName = "naxx"; // Naxxramas break; + case 565: + strategyName = "gruulslair"; // Gruul's Lair + break; case 574: strategyName = "wotlk-uk"; // Utgarde Keep break; diff --git a/src/strategy/AiObjectContext.cpp b/src/strategy/AiObjectContext.cpp index f0e93fd3..4c49fe4e 100644 --- a/src/strategy/AiObjectContext.cpp +++ b/src/strategy/AiObjectContext.cpp @@ -39,6 +39,8 @@ #include "raids/blackwinglair/RaidBwlTriggerContext.h" #include "raids/karazhan/RaidKarazhanActionContext.h" #include "raids/karazhan/RaidKarazhanTriggerContext.h" +#include "raids/gruulslair/RaidGruulsLairActionContext.h" +#include "raids/gruulslair/RaidGruulsLairTriggerContext.h" #include "raids/naxxramas/RaidNaxxActionContext.h" #include "raids/naxxramas/RaidNaxxTriggerContext.h" #include "raids/eyeofeternity/RaidEoEActionContext.h" @@ -111,6 +113,7 @@ void AiObjectContext::BuildSharedActionContexts(SharedNamedObjectContextList +{ +public: + RaidGruulsLairActionContext() + { + // High King Maulgar + creators["high king maulgar main tank attack maulgar"] = &RaidGruulsLairActionContext::high_king_maulgar_main_tank_attack_maulgar; + creators["high king maulgar first assist tank attack olm"] = &RaidGruulsLairActionContext::high_king_maulgar_first_assist_tank_attack_olm; + creators["high king maulgar second assist tank attack blindeye"] = &RaidGruulsLairActionContext::high_king_maulgar_second_assist_tank_attack_blindeye; + creators["high king maulgar mage tank attack krosh"] = &RaidGruulsLairActionContext::high_king_maulgar_mage_tank_attack_krosh; + creators["high king maulgar moonkin tank attack kiggler"] = &RaidGruulsLairActionContext::high_king_maulgar_moonkin_tank_attack_kiggler; + creators["high king maulgar assign dps priority"] = &RaidGruulsLairActionContext::high_king_maulgar_assign_dps_priority; + creators["high king maulgar healer find safe position"] = &RaidGruulsLairActionContext::high_king_maulgar_healer_find_safe_position; + creators["high king maulgar run away from whirlwind"] = &RaidGruulsLairActionContext::high_king_maulgar_run_away_from_whirlwind; + creators["high king maulgar banish felstalker"] = &RaidGruulsLairActionContext::high_king_maulgar_banish_felstalker; + creators["high king maulgar misdirect olm and blindeye"] = &RaidGruulsLairActionContext::high_king_maulgar_misdirect_olm_and_blindeye; + + // Gruul the Dragonkiller + creators["gruul the dragonkiller main tank position boss"] = &RaidGruulsLairActionContext::gruul_the_dragonkiller_main_tank_position_boss; + creators["gruul the dragonkiller spread ranged"] = &RaidGruulsLairActionContext::gruul_the_dragonkiller_spread_ranged; + creators["gruul the dragonkiller shatter spread"] = &RaidGruulsLairActionContext::gruul_the_dragonkiller_shatter_spread; + } + +private: + // High King Maulgar + static Action* high_king_maulgar_main_tank_attack_maulgar(PlayerbotAI* botAI) { return new HighKingMaulgarMainTankAttackMaulgarAction(botAI); } + static Action* high_king_maulgar_first_assist_tank_attack_olm(PlayerbotAI* botAI) { return new HighKingMaulgarFirstAssistTankAttackOlmAction(botAI); } + static Action* high_king_maulgar_second_assist_tank_attack_blindeye(PlayerbotAI* botAI) { return new HighKingMaulgarSecondAssistTankAttackBlindeyeAction(botAI); } + static Action* high_king_maulgar_mage_tank_attack_krosh(PlayerbotAI* botAI) { return new HighKingMaulgarMageTankAttackKroshAction(botAI); } + static Action* high_king_maulgar_moonkin_tank_attack_kiggler(PlayerbotAI* botAI) { return new HighKingMaulgarMoonkinTankAttackKigglerAction(botAI); } + static Action* high_king_maulgar_assign_dps_priority(PlayerbotAI* botAI) { return new HighKingMaulgarAssignDPSPriorityAction(botAI); } + static Action* high_king_maulgar_healer_find_safe_position(PlayerbotAI* botAI) { return new HighKingMaulgarHealerFindSafePositionAction(botAI); } + static Action* high_king_maulgar_run_away_from_whirlwind(PlayerbotAI* botAI) { return new HighKingMaulgarRunAwayFromWhirlwindAction(botAI); } + static Action* high_king_maulgar_banish_felstalker(PlayerbotAI* botAI) { return new HighKingMaulgarBanishFelstalkerAction(botAI); } + static Action* high_king_maulgar_misdirect_olm_and_blindeye(PlayerbotAI* botAI) { return new HighKingMaulgarMisdirectOlmAndBlindeyeAction(botAI); } + + // Gruul the Dragonkiller + static Action* gruul_the_dragonkiller_main_tank_position_boss(PlayerbotAI* botAI) { return new GruulTheDragonkillerMainTankPositionBossAction(botAI); } + static Action* gruul_the_dragonkiller_spread_ranged(PlayerbotAI* botAI) { return new GruulTheDragonkillerSpreadRangedAction(botAI); } + static Action* gruul_the_dragonkiller_shatter_spread(PlayerbotAI* botAI) { return new GruulTheDragonkillerShatterSpreadAction(botAI); } +}; + +#endif diff --git a/src/strategy/raids/gruulslair/RaidGruulsLairActions.cpp b/src/strategy/raids/gruulslair/RaidGruulsLairActions.cpp new file mode 100644 index 00000000..1a98135c --- /dev/null +++ b/src/strategy/raids/gruulslair/RaidGruulsLairActions.cpp @@ -0,0 +1,696 @@ +#include "RaidGruulsLairActions.h" +#include "RaidGruulsLairHelpers.h" +#include "CreatureAI.h" +#include "Playerbots.h" +#include "Unit.h" + +using namespace GruulsLairHelpers; + +// High King Maulgar Actions + +// Main tank on Maulgar +bool HighKingMaulgarMainTankAttackMaulgarAction::Execute(Event event) +{ + Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar"); + + MarkTargetWithSquare(bot, maulgar); + SetRtiTarget(botAI, "square", maulgar); + + if (bot->GetVictim() != maulgar) + return Attack(maulgar); + + if (maulgar->GetVictim() == bot) + { + const Location& tankPosition = GruulsLairLocations::MaulgarTankPosition; + const float maxDistance = 3.0f; + + float distanceToTankPosition = bot->GetExactDist2d(tankPosition.x, tankPosition.y); + + if (distanceToTankPosition > maxDistance) + { + float dX = tankPosition.x - bot->GetPositionX(); + float dY = tankPosition.y - bot->GetPositionY(); + float dist = sqrt(dX * dX + dY * dY); + float moveX = bot->GetPositionX() + (dX / dist) * maxDistance; + float moveY = bot->GetPositionY() + (dY / dist) * maxDistance; + return MoveTo(bot->GetMapId(), moveX, moveY, tankPosition.z, false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + + float orientation = atan2(maulgar->GetPositionY() - bot->GetPositionY(), + maulgar->GetPositionX() - bot->GetPositionX()); + bot->SetFacingTo(orientation); + } + else if (!bot->IsWithinMeleeRange(maulgar)) + { + return MoveTo(maulgar->GetMapId(), maulgar->GetPositionX(), maulgar->GetPositionY(), + maulgar->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + + return false; +} + +// First offtank on Olm +bool HighKingMaulgarFirstAssistTankAttackOlmAction::Execute(Event event) +{ + Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner"); + + MarkTargetWithCircle(bot, olm); + SetRtiTarget(botAI, "circle", olm); + + if (bot->GetVictim() != olm) + return Attack(olm); + + if (olm->GetVictim() == bot) + { + const Location& tankPosition = GruulsLairLocations::OlmTankPosition; + const float maxDistance = 3.0f; + const float olmTankLeeway = 30.0f; + + float distanceOlmToTankPosition = olm->GetExactDist2d(tankPosition.x, tankPosition.y); + + if (distanceOlmToTankPosition > olmTankLeeway) + { + float dX = tankPosition.x - bot->GetPositionX(); + float dY = tankPosition.y - bot->GetPositionY(); + float dist = sqrt(dX * dX + dY * dY); + float moveX = bot->GetPositionX() + (dX / dist) * maxDistance; + float moveY = bot->GetPositionY() + (dY / dist) * maxDistance; + return MoveTo(bot->GetMapId(), moveX, moveY, tankPosition.z, false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + } + else if (!bot->IsWithinMeleeRange(olm)) + { + return MoveTo(olm->GetMapId(), olm->GetPositionX(), olm->GetPositionY(), + olm->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + + return false; +} + +// Second offtank on Blindeye +bool HighKingMaulgarSecondAssistTankAttackBlindeyeAction::Execute(Event event) +{ + Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer"); + + MarkTargetWithStar(bot, blindeye); + SetRtiTarget(botAI, "star", blindeye); + + if (bot->GetVictim() != blindeye) + return Attack(blindeye); + + if (blindeye->GetVictim() == bot) + { + const Location& tankPosition = GruulsLairLocations::BlindeyeTankPosition; + const float maxDistance = 3.0f; + + float distanceToTankPosition = bot->GetExactDist2d(tankPosition.x, tankPosition.y); + + if (distanceToTankPosition > maxDistance) + { + float dX = tankPosition.x - bot->GetPositionX(); + float dY = tankPosition.y - bot->GetPositionY(); + float dist = sqrt(dX * dX + dY * dY); + float moveX = bot->GetPositionX() + (dX / dist) * maxDistance; + float moveY = bot->GetPositionY() + (dY / dist) * maxDistance; + return MoveTo(bot->GetMapId(), moveX, moveY, tankPosition.z, false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + + float orientation = atan2(blindeye->GetPositionY() - bot->GetPositionY(), + blindeye->GetPositionX() - bot->GetPositionX()); + bot->SetFacingTo(orientation); + } + else if (!bot->IsWithinMeleeRange(blindeye)) + { + return MoveTo(blindeye->GetMapId(), blindeye->GetPositionX(), blindeye->GetPositionY(), + blindeye->GetPositionZ(), false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + + return false; +} + +// Mage with highest max HP on Krosh +bool HighKingMaulgarMageTankAttackKroshAction::Execute(Event event) +{ + Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand"); + + MarkTargetWithTriangle(bot, krosh); + SetRtiTarget(botAI, "triangle", krosh); + + if (krosh->HasAura(SPELL_SPELL_SHIELD) && botAI->CanCastSpell("spellsteal", krosh)) + return botAI->CastSpell("spellsteal", krosh); + + if (!bot->HasAura(SPELL_SPELL_SHIELD) && botAI->CanCastSpell("fire ward", bot)) + return botAI->CastSpell("fire ward", bot); + + if (bot->GetTarget() != krosh->GetGUID()) + { + bot->SetSelection(krosh->GetGUID()); + return true; + } + + if (krosh->GetVictim() == bot) + { + const Location& tankPosition = GruulsLairLocations::KroshTankPosition; + float distanceToKrosh = krosh->GetExactDist2d(tankPosition.x, tankPosition.y); + const float minDistance = 16.0f; + const float maxDistance = 29.0f; + const float tankPositionLeeway = 1.0f; + + if (distanceToKrosh > minDistance && distanceToKrosh < maxDistance) + { + if (!bot->IsWithinDist2d(tankPosition.x, tankPosition.y, tankPositionLeeway)) + { + return MoveTo(bot->GetMapId(), tankPosition.x, tankPosition.y, tankPosition.z, false, + false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); + } + + float orientation = atan2(krosh->GetPositionY() - bot->GetPositionY(), + krosh->GetPositionX() - bot->GetPositionX()); + bot->SetFacingTo(orientation); + } + else + { + Position safePos; + if (TryGetNewSafePosition(botAI, bot, safePos)) + { + return MoveTo(krosh->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ, + false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); + } + } + } + + return false; +} + +// Moonkin with highest max HP on Kiggler +bool HighKingMaulgarMoonkinTankAttackKigglerAction::Execute(Event event) +{ + Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed"); + + MarkTargetWithDiamond(bot, kiggler); + SetRtiTarget(botAI, "diamond", kiggler); + + if (bot->GetTarget() != kiggler->GetGUID()) + { + bot->SetSelection(kiggler->GetGUID()); + return true; + } + + Position safePos; + if (TryGetNewSafePosition(botAI, bot, safePos)) + { + return MoveTo(kiggler->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ, + false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); + } + + return false; +} + +bool HighKingMaulgarAssignDPSPriorityAction::Execute(Event event) +{ + // Target priority 1: Blindeye + Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer"); + if (blindeye && blindeye->IsAlive()) + { + Position safePos; + if (TryGetNewSafePosition(botAI, bot, safePos)) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(false); + return MoveTo(blindeye->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ, + false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); + } + + SetRtiTarget(botAI, "star", blindeye); + + if (bot->GetTarget() != blindeye->GetGUID()) + { + bot->SetSelection(blindeye->GetGUID()); + return Attack(blindeye); + } + + return false; + } + + // Target priority 2: Olm + Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner"); + if (olm && olm->IsAlive()) + { + Position safePos; + if (TryGetNewSafePosition(botAI, bot, safePos)) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(false); + return MoveTo(olm->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ, + false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); + } + + SetRtiTarget(botAI, "circle", olm); + + if (bot->GetTarget() != olm->GetGUID()) + { + bot->SetSelection(olm->GetGUID()); + return Attack(olm); + } + + return false; + } + + // Target priority 3a: Krosh (ranged only) + Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand"); + if (krosh && krosh->IsAlive() && botAI->IsRanged(bot)) + { + Position safePos; + if (TryGetNewSafePosition(botAI, bot, safePos)) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(false); + return MoveTo(krosh->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ, + false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); + } + + SetRtiTarget(botAI, "triangle", krosh); + + if (bot->GetTarget() != krosh->GetGUID()) + { + bot->SetSelection(krosh->GetGUID()); + return Attack(krosh); + } + + return false; + } + + // Target priority 3b: Kiggler + Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed"); + if (kiggler && kiggler->IsAlive()) + { + Position safePos; + if (TryGetNewSafePosition(botAI, bot, safePos)) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(false); + return MoveTo(kiggler->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ, + false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); + } + + SetRtiTarget(botAI, "diamond", kiggler); + + if (bot->GetTarget() != kiggler->GetGUID()) + { + bot->SetSelection(kiggler->GetGUID()); + return Attack(kiggler); + } + + return false; + } + + // Target priority 4: Maulgar + Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar"); + if (maulgar && maulgar->IsAlive()) + { + Position safePos; + if (TryGetNewSafePosition(botAI, bot, safePos)) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(false); + return MoveTo(maulgar->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ, + false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); + } + + SetRtiTarget(botAI, "square", maulgar); + + if (bot->GetTarget() != maulgar->GetGUID()) + { + bot->SetSelection(maulgar->GetGUID()); + return Attack(maulgar); + } + } + + return false; +} + +// Avoid Whirlwind and Blast Wave and generally try to stay near the center of the room +bool HighKingMaulgarHealerFindSafePositionAction::Execute(Event event) +{ + const Location& fightCenter = GruulsLairLocations::MaulgarRoomCenter; + const float maxDistanceFromFight = 50.0f; + float distToFight = bot->GetExactDist2d(fightCenter.x, fightCenter.y); + + if (distToFight > maxDistanceFromFight) + { + float angle = atan2(bot->GetPositionY() - fightCenter.y, bot->GetPositionX() - fightCenter.x); + float destX = fightCenter.x + 40.0f * cos(angle); + float destY = fightCenter.y + 40.0f * sin(angle); + float destZ = fightCenter.z; + + if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), + bot->GetPositionZ(), destX, destY, destZ)) + return false; + + return MoveTo(bot->GetMapId(), destX, destY, destZ, false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + + Position safePos; + if (TryGetNewSafePosition(botAI, bot, safePos)) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(false); + return MoveTo(bot->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ, + false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); + } + + return false; +} + +// Run away from Maulgar during Whirlwind (logic for after all other ogres are dead) +bool HighKingMaulgarRunAwayFromWhirlwindAction::Execute(Event event) +{ + Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar"); + + const float safeDistance = 10.0f; + float distance = bot->GetExactDist2d(maulgar); + + if (distance < safeDistance) + { + float angle = atan2(bot->GetPositionY() - maulgar->GetPositionY(), + bot->GetPositionX() - maulgar->GetPositionX()); + float destX = maulgar->GetPositionX() + safeDistance * cos(angle); + float destY = maulgar->GetPositionY() + safeDistance * sin(angle); + float destZ = bot->GetPositionZ(); + + if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), + bot->GetPositionZ(), destX, destY, destZ)) + return false; + + float destDist = maulgar->GetExactDist2d(destX, destY); + + if (destDist >= safeDistance - 0.1f) + { + bot->AttackStop(); + bot->InterruptNonMeleeSpells(true); + return MoveTo(maulgar->GetMapId(), destX, destY, destZ, false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + } + + return false; +} + +bool HighKingMaulgarBanishFelstalkerAction::Execute(Event event) +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + const GuidVector& npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); + std::vector felStalkers; + for (auto const& npc : npcs) + { + Unit* unit = botAI->GetUnit(npc); + if (unit && unit->GetEntry() == NPC_WILD_FEL_STALKER && unit->IsAlive()) + felStalkers.push_back(unit); + } + + std::vector warlocks; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (member && member->IsAlive() && member->getClass() == CLASS_WARLOCK && GET_PLAYERBOT_AI(member)) + warlocks.push_back(member); + } + + int warlockIndex = -1; + for (size_t i = 0; i < warlocks.size(); ++i) + { + if (warlocks[i] == bot) + { + warlockIndex = static_cast(i); + break; + } + } + + if (warlockIndex >= 0 && warlockIndex < felStalkers.size()) + { + Unit* assignedFelStalker = felStalkers[warlockIndex]; + if (!assignedFelStalker->HasAura(SPELL_BANISH) && botAI->CanCastSpell(SPELL_BANISH, assignedFelStalker, true)) + return botAI->CastSpell("banish", assignedFelStalker); + } + + return false; +} + +// Hunter 1: Misdirect Olm to first offtank and have pet attack Blindeye +// Hunter 2: Misdirect Blindeye to second offtank +bool HighKingMaulgarMisdirectOlmAndBlindeyeAction::Execute(Event event) +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + std::vector 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(i); + break; + } + } + + Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner"); + Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer"); + Player* olmTank = nullptr; + Player* blindeyeTank = nullptr; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive()) + continue; + else if (botAI->IsAssistTankOfIndex(member, 0)) olmTank = member; + else if (botAI->IsAssistTankOfIndex(member, 1)) blindeyeTank = member; + } + + switch (hunterIndex) + { + case 0: + botAI->CastSpell("misdirection", olmTank); + if (bot->HasAura(SPELL_MISDIRECTION)) + { + Pet* pet = bot->GetPet(); + if (pet && pet->IsAlive() && pet->GetVictim() != blindeye) + { + pet->ClearUnitState(UNIT_STATE_FOLLOW); + pet->AttackStop(); + pet->SetTarget(blindeye->GetGUID()); + if (pet->GetCharmInfo()) + { + pet->GetCharmInfo()->SetIsCommandAttack(true); + pet->GetCharmInfo()->SetIsAtStay(false); + pet->GetCharmInfo()->SetIsFollowing(false); + pet->GetCharmInfo()->SetIsCommandFollow(false); + pet->GetCharmInfo()->SetIsReturning(false); + } + pet->ToCreature()->AI()->AttackStart(blindeye); + } + return botAI->CastSpell("steady shot", olm); + } + break; + + case 1: + botAI->CastSpell("misdirection", blindeyeTank); + if (bot->HasAura(SPELL_MISDIRECTION)) + return botAI->CastSpell("steady shot", blindeye); + break; + + default: + break; + } + + return false; +} + +// Gruul the Dragonkiller Actions + +// Position in center of the room +bool GruulTheDragonkillerMainTankPositionBossAction::Execute(Event event) +{ + Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller"); + + if (bot->GetVictim() != gruul) + return Attack(gruul); + + if (gruul->GetVictim() == bot) + { + const Location& tankPosition = GruulsLairLocations::GruulTankPosition; + const float maxDistance = 3.0f; + + float dX = tankPosition.x - bot->GetPositionX(); + float dY = tankPosition.y - bot->GetPositionY(); + float distanceToTankPosition = bot->GetExactDist2d(tankPosition.x, tankPosition.y); + + if (distanceToTankPosition > maxDistance) + { + float step = std::min(maxDistance, distanceToTankPosition); + float moveX = bot->GetPositionX() + (dX / distanceToTankPosition) * maxDistance; + float moveY = bot->GetPositionY() + (dY / distanceToTankPosition) * maxDistance; + const float moveZ = tankPosition.z; + return MoveTo(bot->GetMapId(), moveX, moveY, moveZ, false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + + float orientation = atan2(gruul->GetPositionY() - bot->GetPositionY(), + gruul->GetPositionX() - bot->GetPositionX()); + bot->SetFacingTo(orientation); + } + else if (!bot->IsWithinMeleeRange(gruul)) + { + return MoveTo(gruul->GetMapId(), gruul->GetPositionX(), gruul->GetPositionY(), gruul->GetPositionZ(), + false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); + } + + return false; +} + +// Ranged will take initial positions around the middle of the room, 25-40 yards from center +// Ranged should spread out 10 yards from each other +bool GruulTheDragonkillerSpreadRangedAction::Execute(Event event) +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + static std::unordered_map initialPositions; + static std::unordered_map hasReachedInitialPosition; + + Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller"); + if (gruul && gruul->IsAlive() && gruul->GetHealth() == gruul->GetMaxHealth()) + { + initialPositions.clear(); + hasReachedInitialPosition.clear(); + } + + const Location& tankPosition = GruulsLairLocations::GruulTankPosition; + const float centerX = tankPosition.x; + const float centerY = tankPosition.y; + float centerZ = bot->GetPositionZ(); + const float minRadius = 25.0f; + const float maxRadius = 40.0f; + + std::vector members; + Player* closestMember = nullptr; + float closestDist = std::numeric_limits::max(); + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive()) + continue; + + members.push_back(member); + if (member != bot) + { + float dist = bot->GetExactDist2d(member); + if (dist < closestDist) + { + closestDist = dist; + closestMember = member; + } + } + } + + if (!initialPositions.count(bot->GetGUID())) + { + auto it = std::find(members.begin(), members.end(), bot); + uint8 botIndex = (it != members.end()) ? std::distance(members.begin(), it) : 0; + uint8 count = members.size(); + + float angle = 2 * M_PI * botIndex / count; + float radius = minRadius + static_cast(rand()) / + static_cast(RAND_MAX) * (maxRadius - minRadius); + float targetX = centerX + radius * cos(angle); + float targetY = centerY + radius * sin(angle); + + initialPositions[bot->GetGUID()] = Position(targetX, targetY, centerZ); + hasReachedInitialPosition[bot->GetGUID()] = false; + } + + Position targetPosition = initialPositions[bot->GetGUID()]; + if (!hasReachedInitialPosition[bot->GetGUID()]) + { + if (!bot->IsWithinDist2d(targetPosition.GetPositionX(), targetPosition.GetPositionY(), 2.0f)) + { + float destX = targetPosition.GetPositionX(); + float destY = targetPosition.GetPositionY(); + float destZ = targetPosition.GetPositionZ(); + + if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), + bot->GetPositionY(), bot->GetPositionZ(), destX, destY, destZ)) + return false; + + return MoveTo(bot->GetMapId(), destX, destY, destZ, false, false, false, false, + MovementPriority::MOVEMENT_COMBAT, true, false); + } + + hasReachedInitialPosition[bot->GetGUID()] = true; + } + + const float minSpreadDistance = 10.0f; + const float movementThreshold = 2.0f; + + if (closestMember && closestDist < minSpreadDistance - movementThreshold) + { + return FleePosition(Position(closestMember->GetPositionX(), closestMember->GetPositionY(), + closestMember->GetPositionZ()), minSpreadDistance, 0); + } + + return false; +} + +// Try to get away from other group members when Ground Slam is cast +bool GruulTheDragonkillerShatterSpreadAction::Execute(Event event) +{ + Group* group = bot->GetGroup(); + if (!group) + return false; + + GuidVector members = AI_VALUE(GuidVector, "group members"); + Unit* closestMember = nullptr; + float closestDist = std::numeric_limits::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; +} diff --git a/src/strategy/raids/gruulslair/RaidGruulsLairActions.h b/src/strategy/raids/gruulslair/RaidGruulsLairActions.h new file mode 100644 index 00000000..6faf7ed3 --- /dev/null +++ b/src/strategy/raids/gruulslair/RaidGruulsLairActions.h @@ -0,0 +1,112 @@ +#ifndef _PLAYERBOT_RAIDGRUULSLAIRACTIONS_H +#define _PLAYERBOT_RAIDGRUULSLAIRACTIONS_H + +#include "Action.h" +#include "AttackAction.h" +#include "MovementActions.h" + +class HighKingMaulgarMainTankAttackMaulgarAction : public AttackAction +{ +public: + HighKingMaulgarMainTankAttackMaulgarAction(PlayerbotAI* botAI, std::string const name = "high king maulgar main tank attack maulgar") : AttackAction(botAI, name) {}; + + bool Execute(Event event) override; +}; + +class HighKingMaulgarFirstAssistTankAttackOlmAction : public AttackAction +{ +public: + HighKingMaulgarFirstAssistTankAttackOlmAction(PlayerbotAI* botAI, std::string const name = "high king maulgar first assist tank attack olm") : AttackAction(botAI, name) {}; + + bool Execute(Event event) override; +}; + +class HighKingMaulgarSecondAssistTankAttackBlindeyeAction : public AttackAction +{ +public: + HighKingMaulgarSecondAssistTankAttackBlindeyeAction(PlayerbotAI* botAI, std::string const name = "high king maulgar second assist tank attack blindeye") : AttackAction(botAI, name) {}; + + bool Execute(Event event) override; +}; + +class HighKingMaulgarMageTankAttackKroshAction : public AttackAction +{ +public: + HighKingMaulgarMageTankAttackKroshAction(PlayerbotAI* botAI, std::string const name = "high king maulgar mage tank attack krosh") : AttackAction(botAI, name) {}; + + bool Execute(Event event) override; +}; + +class HighKingMaulgarMoonkinTankAttackKigglerAction : public AttackAction +{ +public: + HighKingMaulgarMoonkinTankAttackKigglerAction(PlayerbotAI* botAI, std::string const name = "high king maulgar moonkin tank attack kiggler") : AttackAction(botAI, name) {}; + + bool Execute(Event event) override; +}; + +class HighKingMaulgarAssignDPSPriorityAction : public AttackAction +{ +public: + HighKingMaulgarAssignDPSPriorityAction(PlayerbotAI* botAI, std::string const name = "high king maulgar assign dps priority") : AttackAction(botAI, name) {}; + + bool Execute(Event event) override; +}; + +class HighKingMaulgarHealerFindSafePositionAction : public MovementAction +{ +public: + HighKingMaulgarHealerFindSafePositionAction(PlayerbotAI* botAI, std::string const name = "high king maulgar healer find safe position") : MovementAction(botAI, name) {}; + + bool Execute(Event event) override; +}; + +class HighKingMaulgarRunAwayFromWhirlwindAction : public MovementAction +{ +public: + HighKingMaulgarRunAwayFromWhirlwindAction(PlayerbotAI* botAI, std::string const name = "high king maulgar run away from whirlwind") : MovementAction(botAI, name) {}; + + bool Execute(Event event) override; +}; + +class HighKingMaulgarBanishFelstalkerAction : public AttackAction +{ +public: + HighKingMaulgarBanishFelstalkerAction(PlayerbotAI* botAI, std::string const name = "high king maulgar banish felstalker") : AttackAction(botAI, name) {}; + + bool Execute(Event event) override; +}; + +class HighKingMaulgarMisdirectOlmAndBlindeyeAction : public AttackAction +{ +public: + HighKingMaulgarMisdirectOlmAndBlindeyeAction(PlayerbotAI* botAI, std::string const name = "high king maulgar misdirect olm and blindeye") : AttackAction(botAI, name) {}; + + bool Execute(Event event) override; +}; + +class GruulTheDragonkillerMainTankPositionBossAction : public AttackAction +{ +public: + GruulTheDragonkillerMainTankPositionBossAction(PlayerbotAI* botAI, std::string const name = "gruul the dragonkiller main tank position boss") : AttackAction(botAI, name) {}; + + bool Execute(Event event) override; +}; + +class GruulTheDragonkillerSpreadRangedAction : public MovementAction +{ +public: + GruulTheDragonkillerSpreadRangedAction(PlayerbotAI* botAI, std::string const name = "gruul the dragonkiller spread ranged") : MovementAction(botAI, name) {}; + + bool Execute(Event event) override; +}; + +class GruulTheDragonkillerShatterSpreadAction : public MovementAction +{ +public: + GruulTheDragonkillerShatterSpreadAction(PlayerbotAI* botAI, std::string const name = "gruul the dragonkiller shatter spread") : MovementAction(botAI, name) {}; + + bool Execute(Event event) override; +}; + +#endif diff --git a/src/strategy/raids/gruulslair/RaidGruulsLairHelpers.cpp b/src/strategy/raids/gruulslair/RaidGruulsLairHelpers.cpp new file mode 100644 index 00000000..0c8a23a1 --- /dev/null +++ b/src/strategy/raids/gruulslair/RaidGruulsLairHelpers.cpp @@ -0,0 +1,241 @@ +#include "RaidGruulsLairHelpers.h" +#include "AiFactory.h" +#include "GroupReference.h" +#include "Playerbots.h" +#include "Unit.h" + +namespace GruulsLairHelpers +{ + namespace GruulsLairLocations + { + // Olm does not chase properly due to the Core's caster movement issues + // Thus, the below "OlmTankPosition" is beyond the actual desired tanking location + // It is the spot to which the OlmTank runs to to pull Olm to a decent tanking location + // "MaulgarRoomCenter" is to keep healers in a centralized location + const Location MaulgarTankPosition = { 90.686f, 167.047f, -13.234f }; + const Location OlmTankPosition = { 87.485f, 234.942f, -3.635f }; + const Location BlindeyeTankPosition = { 99.681f, 213.989f, -10.345f }; + const Location KroshTankPosition = { 116.880f, 166.208f, -14.231f }; + const Location MaulgarRoomCenter = { 88.754f, 150.759f, -11.569f }; + const Location GruulTankPosition = { 241.238f, 365.025f, -4.220f }; + } + + bool IsAnyOgreBossAlive(PlayerbotAI* botAI) + { + const char* ogreBossNames[] = + { + "high king maulgar", + "kiggler the crazed", + "krosh firehand", + "olm the summoner", + "blindeye the seer" + }; + + for (const char* name : ogreBossNames) + { + Unit* boss = botAI->GetAiObjectContext()->GetValue("find target", name)->Get(); + if (!boss || !boss->IsAlive()) + continue; + return true; + } + + return false; + } + + void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId) + { + Group* group = bot->GetGroup(); + if (!target || !group) + return; + + ObjectGuid currentGuid = group->GetTargetIcon(iconId); + if (currentGuid != target->GetGUID()) + { + group->SetTargetIcon(iconId, bot->GetGUID(), target->GetGUID()); + } + } + + void MarkTargetWithSquare(Player* bot, Unit* target) + { + MarkTargetWithIcon(bot, target, RtiTargetValue::squareIndex); + } + + void MarkTargetWithStar(Player* bot, Unit* target) + { + MarkTargetWithIcon(bot, target, RtiTargetValue::starIndex); + } + + void MarkTargetWithCircle(Player* bot, Unit* target) + { + MarkTargetWithIcon(bot, target, RtiTargetValue::circleIndex); + } + + void MarkTargetWithDiamond(Player* bot, Unit* target) + { + MarkTargetWithIcon(bot, target, RtiTargetValue::diamondIndex); + } + + void MarkTargetWithTriangle(Player* bot, Unit* target) + { + MarkTargetWithIcon(bot, target, RtiTargetValue::triangleIndex); + } + + void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target) + { + if (!target) + return; + + std::string currentRti = botAI->GetAiObjectContext()->GetValue("rti")->Get(); + Unit* currentTarget = botAI->GetAiObjectContext()->GetValue("rti target")->Get(); + + if (currentRti != rtiName || currentTarget != target) + { + botAI->GetAiObjectContext()->GetValue("rti")->Set(rtiName); + botAI->GetAiObjectContext()->GetValue("rti target")->Set(target); + } + } + + bool IsKroshMageTank(PlayerbotAI* botAI, Player* bot) + { + Group* group = bot->GetGroup(); + if (!group) + return false; + + Player* highestHpMage = nullptr; + uint32 highestHp = 0; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member)) + continue; + + if (member->getClass() == CLASS_MAGE) + { + uint32 hp = member->GetMaxHealth(); + if (!highestHpMage || hp > highestHp) + { + highestHpMage = member; + highestHp = hp; + } + } + } + + return highestHpMage == bot; + } + + bool IsKigglerMoonkinTank(PlayerbotAI* botAI, Player* bot) + { + Group* group = bot->GetGroup(); + if (!group) + return false; + + Player* highestHpMoonkin = nullptr; + uint32 highestHp = 0; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member)) + continue; + + if (member->getClass() == CLASS_DRUID) + { + int tab = AiFactory::GetPlayerSpecTab(member); + if (tab == DRUID_TAB_BALANCE) + { + uint32 hp = member->GetMaxHealth(); + if (!highestHpMoonkin || hp > highestHp) + { + highestHpMoonkin = member; + highestHp = hp; + } + } + } + } + + return highestHpMoonkin == bot; + } + + bool IsPositionSafe(PlayerbotAI* botAI, Player* bot, Position pos) + { + const float KROSH_SAFE_DISTANCE = 20.0f; + const float MAULGAR_SAFE_DISTANCE = 10.0f; + bool isSafe = true; + + Unit* krosh = botAI->GetAiObjectContext()->GetValue("find target", "krosh firehand")->Get(); + if (krosh && krosh->IsAlive()) + { + float dist = sqrt(pow(pos.GetPositionX() - krosh->GetPositionX(), 2) + pow(pos.GetPositionY() - krosh->GetPositionY(), 2)); + if (dist < KROSH_SAFE_DISTANCE) + isSafe = false; + } + + Unit* maulgar = botAI->GetAiObjectContext()->GetValue("find target", "high king maulgar")->Get(); + if (botAI->IsRanged(bot) && maulgar && maulgar->IsAlive()) + { + float dist = sqrt(pow(pos.GetPositionX() - maulgar->GetPositionX(), 2) + pow(pos.GetPositionY() - maulgar->GetPositionY(), 2)); + if (dist < MAULGAR_SAFE_DISTANCE) + isSafe = false; + } + + return isSafe; + } + + bool TryGetNewSafePosition(PlayerbotAI* botAI, Player* bot, Position& outPos) + { + const float SEARCH_RADIUS = 30.0f; + const uint8 NUM_POSITIONS = 32; + + outPos = { bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ() }; + if (IsPositionSafe(botAI, bot, outPos)) + { + outPos = Position(); + return false; + } + + float bestScore = std::numeric_limits::max(); + bool foundSafeSpot = false; + Position bestPos; + + for (int i = 0; i < NUM_POSITIONS; ++i) + { + float angle = 2 * M_PI * i / NUM_POSITIONS; + Position candidatePos; + candidatePos.m_positionX = bot->GetPositionX() + SEARCH_RADIUS * cos(angle); + candidatePos.m_positionY = bot->GetPositionY() + SEARCH_RADIUS * sin(angle); + candidatePos.m_positionZ = bot->GetPositionZ(); + + float destX = candidatePos.m_positionX, destY = candidatePos.m_positionY, destZ = candidatePos.m_positionZ; + if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), + bot->GetPositionZ(), destX, destY, destZ, true)) + continue; + + if (destX != candidatePos.m_positionX || destY != candidatePos.m_positionY) + continue; + + candidatePos.m_positionX = destX; + candidatePos.m_positionY = destY; + candidatePos.m_positionZ = destZ; + + if (IsPositionSafe(botAI, bot, candidatePos)) + { + float movementDistance = sqrt(pow(destX - bot->GetPositionX(), 2) + pow(destY - bot->GetPositionY(), 2)); + if (movementDistance < bestScore) + { + bestScore = movementDistance; + bestPos = candidatePos; + foundSafeSpot = true; + } + } + } + + if (foundSafeSpot) + { + outPos = bestPos; + return true; + } + + outPos = Position(); + return false; + } +} diff --git a/src/strategy/raids/gruulslair/RaidGruulsLairHelpers.h b/src/strategy/raids/gruulslair/RaidGruulsLairHelpers.h new file mode 100644 index 00000000..aa5a83ac --- /dev/null +++ b/src/strategy/raids/gruulslair/RaidGruulsLairHelpers.h @@ -0,0 +1,62 @@ +#ifndef RAID_GRUULSLAIRHELPERS_H +#define RAID_GRUULSLAIRHELPERS_H + +#include "PlayerbotAI.h" +#include "RtiTargetValue.h" + +namespace GruulsLairHelpers +{ + enum GruulsLairSpells + { + // High King Maulgar + SPELL_WHIRLWIND = 33238, + + // Krosh Firehand + SPELL_SPELL_SHIELD = 33054, + + // Hunter + SPELL_MISDIRECTION = 34477, + + // Warlock + SPELL_BANISH = 18647, // Rank 2 + + // Gruul the Dragonkiller + SPELL_GROUND_SLAM_1 = 33525, + SPELL_GROUND_SLAM_2 = 39187, + }; + + enum GruulsLairNPCs + { + NPC_WILD_FEL_STALKER = 18847, + }; + + bool IsAnyOgreBossAlive(PlayerbotAI* botAI); + void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId); + void MarkTargetWithSquare(Player* bot, Unit* target); + void MarkTargetWithStar(Player* bot, Unit* target); + void MarkTargetWithCircle(Player* bot, Unit* target); + void MarkTargetWithDiamond(Player* bot, Unit* target); + void MarkTargetWithTriangle(Player* bot, Unit* target); + void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target); + bool IsKroshMageTank(PlayerbotAI* botAI, Player* bot); + bool IsKigglerMoonkinTank(PlayerbotAI* botAI, Player* bot); + bool IsPositionSafe(PlayerbotAI* botAI, Player* bot, Position pos); + bool TryGetNewSafePosition(PlayerbotAI* botAI, Player* bot, Position& outPos); + + struct Location + { + float x, y, z; + }; + + namespace GruulsLairLocations + { + extern const Location MaulgarTankPosition; + extern const Location OlmTankPosition; + extern const Location BlindeyeTankPosition; + extern const Location KroshTankPosition; + extern const Location MaulgarRoomCenter; + extern const Location GruulTankPosition; + } +} + +#endif diff --git a/src/strategy/raids/gruulslair/RaidGruulsLairMultipliers.cpp b/src/strategy/raids/gruulslair/RaidGruulsLairMultipliers.cpp new file mode 100644 index 00000000..5ca2de93 --- /dev/null +++ b/src/strategy/raids/gruulslair/RaidGruulsLairMultipliers.cpp @@ -0,0 +1,110 @@ +#include "RaidGruulsLairMultipliers.h" +#include "RaidGruulsLairActions.h" +#include "RaidGruulsLairHelpers.h" +#include "ChooseTargetActions.h" +#include "DruidBearActions.h" +#include "DruidCatActions.h" +#include "GenericSpellActions.h" +#include "HunterActions.h" +#include "MageActions.h" +#include "Playerbots.h" +#include "WarriorActions.h" + +using namespace GruulsLairHelpers; + +static bool IsChargeAction(Action* action) +{ + return dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action) || + dynamic_cast(action); +} + +float HighKingMaulgarDisableTankAssistMultiplier::GetValue(Action* action) +{ + if (IsAnyOgreBossAlive(botAI) && dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + +// Don't run back in during Whirlwind +float HighKingMaulgarAvoidWhirlwindMultiplier::GetValue(Action* action) +{ + Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar"); + Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed"); + Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand"); + Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner"); + Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer"); + + if (maulgar && maulgar->HasAura(SPELL_WHIRLWIND) && + (!kiggler || !kiggler->IsAlive()) && + (!krosh || !krosh->IsAlive()) && + (!olm || !olm->IsAlive()) && + (!blindeye || !blindeye->IsAlive())) + { + if (IsChargeAction(action) || (dynamic_cast(action) && + !dynamic_cast(action))) + return 0.0f; + } + + return 1.0f; +} + +// Arcane Shot will remove Spell Shield, which the mage tank needs to survive +float HighKingMaulgarDisableArcaneShotOnKroshMultiplier::GetValue(Action* action) +{ + Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand"); + Unit* target = AI_VALUE(Unit*, "current target"); + + if (krosh && target && target->GetGUID() == krosh->GetGUID() && dynamic_cast(action)) + return 0.0f; + + return 1.0f; +} + +float HighKingMaulgarDisableMageTankAOEMultiplier::GetValue(Action* action) +{ + if (IsKroshMageTank(botAI, bot) && + (dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action) || + dynamic_cast(action) || dynamic_cast(action))) + return 0.0f; + + return 1.0f; +} + +float GruulTheDragonkillerMainTankMovementMultiplier::GetValue(Action* action) +{ + Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller"); + if (!gruul) + return 1.0f; + + if (botAI->IsMainTank(bot)) + { + if (gruul->GetVictim() == bot && dynamic_cast(action)) + return 0.0f; + + if (dynamic_cast(action)) + return 0.0f; + } + + return 1.0f; +} + +float GruulTheDragonkillerGroundSlamMultiplier::GetValue(Action* action) +{ + Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller"); + if (!gruul) + return 1.0f; + + if (bot->HasAura(SPELL_GROUND_SLAM_1) || + bot->HasAura(SPELL_GROUND_SLAM_2)) + { + if ((dynamic_cast(action) && !dynamic_cast(action)) || + IsChargeAction(action)) + return 0.0f; + } + + return 1.0f; +} diff --git a/src/strategy/raids/gruulslair/RaidGruulsLairMultipliers.h b/src/strategy/raids/gruulslair/RaidGruulsLairMultipliers.h new file mode 100644 index 00000000..d67e8523 --- /dev/null +++ b/src/strategy/raids/gruulslair/RaidGruulsLairMultipliers.h @@ -0,0 +1,48 @@ +#ifndef _PLAYERBOT_RAIDGRUULSLAIRMULTIPLIERS_H +#define _PLAYERBOT_RAIDGRUULSLAIRMULTIPLIERS_H + +#include "Multiplier.h" + +class HighKingMaulgarDisableTankAssistMultiplier : public Multiplier +{ +public: + HighKingMaulgarDisableTankAssistMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "high king maulgar disable tank assist multiplier") {} + float GetValue(Action* action) override; +}; + +class HighKingMaulgarAvoidWhirlwindMultiplier : public Multiplier +{ +public: + HighKingMaulgarAvoidWhirlwindMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "high king maulgar avoid whirlwind multiplier") {} + float GetValue(Action* action) override; +}; + +class HighKingMaulgarDisableArcaneShotOnKroshMultiplier : public Multiplier +{ +public: + HighKingMaulgarDisableArcaneShotOnKroshMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "high king maulgar disable arcane shot on krosh multiplier") {} + float GetValue(Action* action) override; +}; + +class HighKingMaulgarDisableMageTankAOEMultiplier : public Multiplier +{ +public: + HighKingMaulgarDisableMageTankAOEMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "high king maulgar disable mage tank aoe multiplier") {} + float GetValue(Action* action) override; +}; + +class GruulTheDragonkillerMainTankMovementMultiplier : public Multiplier +{ +public: + GruulTheDragonkillerMainTankMovementMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "gruul the dragonkiller main tank movement multiplier") {} + float GetValue(Action* action) override; +}; + +class GruulTheDragonkillerGroundSlamMultiplier : public Multiplier +{ +public: + GruulTheDragonkillerGroundSlamMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "gruul the dragonkiller ground slam multiplier") {} + float GetValue(Action* action) override; +}; + +#endif diff --git a/src/strategy/raids/gruulslair/RaidGruulsLairStrategy.cpp b/src/strategy/raids/gruulslair/RaidGruulsLairStrategy.cpp new file mode 100644 index 00000000..bd7c9cd9 --- /dev/null +++ b/src/strategy/raids/gruulslair/RaidGruulsLairStrategy.cpp @@ -0,0 +1,56 @@ +#include "RaidGruulsLairStrategy.h" +#include "RaidGruulsLairMultipliers.h" + +void RaidGruulsLairStrategy::InitTriggers(std::vector& triggers) +{ + // High King Maulgar + triggers.push_back(new TriggerNode("high king maulgar is main tank", NextAction::array(0, + new NextAction("high king maulgar main tank attack maulgar", ACTION_RAID + 1), nullptr))); + + triggers.push_back(new TriggerNode("high king maulgar is first assist tank", NextAction::array(0, + new NextAction("high king maulgar first assist tank attack olm", ACTION_RAID + 1), nullptr))); + + triggers.push_back(new TriggerNode("high king maulgar is second assist tank", NextAction::array(0, + new NextAction("high king maulgar second assist tank attack blindeye", ACTION_RAID + 1), nullptr))); + + triggers.push_back(new TriggerNode("high king maulgar is mage tank", NextAction::array(0, + new NextAction("high king maulgar mage tank attack krosh", ACTION_RAID + 1), nullptr))); + + triggers.push_back(new TriggerNode("high king maulgar is moonkin tank", NextAction::array(0, + new NextAction("high king maulgar moonkin tank attack kiggler", ACTION_RAID + 1), nullptr))); + + triggers.push_back(new TriggerNode("high king maulgar determining kill order", NextAction::array(0, + new NextAction("high king maulgar assign dps priority", ACTION_RAID + 1), nullptr))); + + triggers.push_back(new TriggerNode("high king maulgar healer in danger", NextAction::array(0, + new NextAction("high king maulgar healer find safe position", ACTION_RAID + 1), nullptr))); + + triggers.push_back(new TriggerNode("high king maulgar boss channeling whirlwind", NextAction::array(0, + new NextAction("high king maulgar run away from whirlwind", ACTION_EMERGENCY + 6), nullptr))); + + triggers.push_back(new TriggerNode("high king maulgar wild felstalker spawned", NextAction::array(0, + new NextAction("high king maulgar banish felstalker", ACTION_RAID + 2), nullptr))); + + triggers.push_back(new TriggerNode("high king maulgar pulling olm and blindeye", NextAction::array(0, + new NextAction("high king maulgar misdirect olm and blindeye", ACTION_RAID + 2), nullptr))); + + // Gruul the Dragonkiller + triggers.push_back(new TriggerNode("gruul the dragonkiller boss engaged by main tank", NextAction::array(0, + new NextAction("gruul the dragonkiller main tank position boss", ACTION_RAID + 1), nullptr))); + + triggers.push_back(new TriggerNode("gruul the dragonkiller boss engaged by range", NextAction::array(0, + new NextAction("gruul the dragonkiller spread ranged", ACTION_RAID + 1), nullptr))); + + triggers.push_back(new TriggerNode("gruul the dragonkiller incoming shatter", NextAction::array(0, + new NextAction("gruul the dragonkiller shatter spread", ACTION_EMERGENCY + 6), nullptr))); +} + +void RaidGruulsLairStrategy::InitMultipliers(std::vector& multipliers) +{ + multipliers.push_back(new HighKingMaulgarDisableTankAssistMultiplier(botAI)); + multipliers.push_back(new HighKingMaulgarAvoidWhirlwindMultiplier(botAI)); + multipliers.push_back(new HighKingMaulgarDisableArcaneShotOnKroshMultiplier(botAI)); + multipliers.push_back(new HighKingMaulgarDisableMageTankAOEMultiplier(botAI)); + multipliers.push_back(new GruulTheDragonkillerMainTankMovementMultiplier(botAI)); + multipliers.push_back(new GruulTheDragonkillerGroundSlamMultiplier(botAI)); +} diff --git a/src/strategy/raids/gruulslair/RaidGruulsLairStrategy.h b/src/strategy/raids/gruulslair/RaidGruulsLairStrategy.h new file mode 100644 index 00000000..ba6f33f0 --- /dev/null +++ b/src/strategy/raids/gruulslair/RaidGruulsLairStrategy.h @@ -0,0 +1,18 @@ +#ifndef _PLAYERBOT_RAIDGRUULSLAIRSTRATEGY_H +#define _PLAYERBOT_RAIDGRUULSLAIRSTRATEGY_H + +#include "Strategy.h" +#include "Multiplier.h" + +class RaidGruulsLairStrategy : public Strategy +{ +public: + RaidGruulsLairStrategy(PlayerbotAI* botAI) : Strategy(botAI) {} + + std::string const getName() override { return "gruulslair"; } + + void InitTriggers(std::vector& triggers) override; + void InitMultipliers(std::vector &multipliers) override; +}; + +#endif diff --git a/src/strategy/raids/gruulslair/RaidGruulsLairTriggerContext.h b/src/strategy/raids/gruulslair/RaidGruulsLairTriggerContext.h new file mode 100644 index 00000000..fa8e76f5 --- /dev/null +++ b/src/strategy/raids/gruulslair/RaidGruulsLairTriggerContext.h @@ -0,0 +1,49 @@ +#ifndef _PLAYERBOT_RAIDGRUULSLAIRTRIGGERCONTEXT_H +#define _PLAYERBOT_RAIDGRUULSLAIRTRIGGERCONTEXT_H + +#include "RaidGruulsLairTriggers.h" +#include "AiObjectContext.h" + +class RaidGruulsLairTriggerContext : public NamedObjectContext +{ +public: + RaidGruulsLairTriggerContext() : NamedObjectContext() + { + // High King Maulgar + creators["high king maulgar is main tank"] = &RaidGruulsLairTriggerContext::high_king_maulgar_is_main_tank; + creators["high king maulgar is first assist tank"] = &RaidGruulsLairTriggerContext::high_king_maulgar_is_first_assist_tank; + creators["high king maulgar is second assist tank"] = &RaidGruulsLairTriggerContext::high_king_maulgar_is_second_assist_tank; + creators["high king maulgar is mage tank"] = &RaidGruulsLairTriggerContext::high_king_maulgar_is_mage_tank; + creators["high king maulgar is moonkin tank"] = &RaidGruulsLairTriggerContext::high_king_maulgar_is_moonkin_tank; + creators["high king maulgar determining kill order"] = &RaidGruulsLairTriggerContext::high_king_maulgar_determining_kill_order; + creators["high king maulgar healer in danger"] = &RaidGruulsLairTriggerContext::high_king_maulgar_healer_in_danger; + creators["high king maulgar boss channeling whirlwind"] = &RaidGruulsLairTriggerContext::high_king_maulgar_boss_channeling_whirlwind; + creators["high king maulgar wild felstalker spawned"] = &RaidGruulsLairTriggerContext::high_king_maulgar_wild_felstalker_spawned; + creators["high king maulgar pulling olm and blindeye"] = &RaidGruulsLairTriggerContext::high_king_maulgar_pulling_olm_and_blindeye; + + // Gruul the Dragonkiller + creators["gruul the dragonkiller boss engaged by main tank"] = &RaidGruulsLairTriggerContext::gruul_the_dragonkiller_boss_engaged_by_main_tank; + creators["gruul the dragonkiller boss engaged by range"] = &RaidGruulsLairTriggerContext::gruul_the_dragonkiller_boss_engaged_by_range; + creators["gruul the dragonkiller incoming shatter"] = &RaidGruulsLairTriggerContext::gruul_the_dragonkiller_incoming_shatter; + } + +private: + // High King Maulgar + static Trigger* high_king_maulgar_is_main_tank(PlayerbotAI* botAI) { return new HighKingMaulgarIsMainTankTrigger(botAI); } + static Trigger* high_king_maulgar_is_first_assist_tank(PlayerbotAI* botAI) { return new HighKingMaulgarIsFirstAssistTankTrigger(botAI); } + static Trigger* high_king_maulgar_is_second_assist_tank(PlayerbotAI* botAI) { return new HighKingMaulgarIsSecondAssistTankTrigger(botAI); } + static Trigger* high_king_maulgar_is_mage_tank(PlayerbotAI* botAI) { return new HighKingMaulgarIsMageTankTrigger(botAI); } + static Trigger* high_king_maulgar_is_moonkin_tank(PlayerbotAI* botAI) { return new HighKingMaulgarIsMoonkinTankTrigger(botAI); } + static Trigger* high_king_maulgar_determining_kill_order(PlayerbotAI* botAI) { return new HighKingMaulgarDeterminingKillOrderTrigger(botAI); } + static Trigger* high_king_maulgar_healer_in_danger(PlayerbotAI* botAI) { return new HighKingMaulgarHealerInDangerTrigger(botAI); } + static Trigger* high_king_maulgar_boss_channeling_whirlwind(PlayerbotAI* botAI) { return new HighKingMaulgarBossChannelingWhirlwindTrigger(botAI); } + static Trigger* high_king_maulgar_wild_felstalker_spawned(PlayerbotAI* botAI) { return new HighKingMaulgarWildFelstalkerSpawnedTrigger(botAI); } + static Trigger* high_king_maulgar_pulling_olm_and_blindeye(PlayerbotAI* botAI) { return new HighKingMaulgarPullingOlmAndBlindeyeTrigger(botAI); } + + // Gruul the Dragonkiller + static Trigger* gruul_the_dragonkiller_boss_engaged_by_main_tank(PlayerbotAI* botAI) { return new GruulTheDragonkillerBossEngagedByMainTankTrigger(botAI); } + static Trigger* gruul_the_dragonkiller_boss_engaged_by_range(PlayerbotAI* botAI) { return new GruulTheDragonkillerBossEngagedByRangeTrigger(botAI); } + static Trigger* gruul_the_dragonkiller_incoming_shatter(PlayerbotAI* botAI) { return new GruulTheDragonkillerIncomingShatterTrigger(botAI); } +}; + +#endif diff --git a/src/strategy/raids/gruulslair/RaidGruulsLairTriggers.cpp b/src/strategy/raids/gruulslair/RaidGruulsLairTriggers.cpp new file mode 100644 index 00000000..35d9f9a1 --- /dev/null +++ b/src/strategy/raids/gruulslair/RaidGruulsLairTriggers.cpp @@ -0,0 +1,160 @@ +#include "RaidGruulsLairTriggers.h" +#include "RaidGruulsLairHelpers.h" +#include "Playerbots.h" + +using namespace GruulsLairHelpers; + +// High King Maulgar Triggers + +bool HighKingMaulgarIsMainTankTrigger::IsActive() +{ + Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar"); + + return botAI->IsMainTank(bot) && maulgar && maulgar->IsAlive(); +} + +bool HighKingMaulgarIsFirstAssistTankTrigger::IsActive() +{ + Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner"); + + return botAI->IsAssistTankOfIndex(bot, 0) && olm && olm->IsAlive(); +} + +bool HighKingMaulgarIsSecondAssistTankTrigger::IsActive() +{ + Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer"); + + return botAI->IsAssistTankOfIndex(bot, 1) && blindeye && blindeye->IsAlive(); +} + +bool HighKingMaulgarIsMageTankTrigger::IsActive() +{ + Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand"); + + return IsKroshMageTank(botAI, bot) && krosh && krosh->IsAlive(); +} + +bool HighKingMaulgarIsMoonkinTankTrigger::IsActive() +{ + Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed"); + + return IsKigglerMoonkinTank(botAI, bot) && kiggler && kiggler->IsAlive(); +} + +bool HighKingMaulgarDeterminingKillOrderTrigger::IsActive() +{ + Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar"); + Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed"); + Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner"); + Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer"); + Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand"); + + return (botAI->IsDps(bot) || botAI->IsTank(bot)) && + !(botAI->IsMainTank(bot) && maulgar && maulgar->IsAlive()) && + !(botAI->IsAssistTankOfIndex(bot, 0) && olm && olm->IsAlive()) && + !(botAI->IsAssistTankOfIndex(bot, 1) && blindeye && blindeye->IsAlive()) && + !(IsKroshMageTank(botAI, bot) && krosh && krosh->IsAlive()) && + !(IsKigglerMoonkinTank(botAI, bot) && kiggler && kiggler->IsAlive()); +} + +bool HighKingMaulgarHealerInDangerTrigger::IsActive() +{ + return botAI->IsHeal(bot) && IsAnyOgreBossAlive(botAI); +} + +bool HighKingMaulgarBossChannelingWhirlwindTrigger::IsActive() +{ + Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar"); + + return maulgar && maulgar->IsAlive() && maulgar->HasAura(SPELL_WHIRLWIND) && + !botAI->IsMainTank(bot); +} + +bool HighKingMaulgarWildFelstalkerSpawnedTrigger::IsActive() +{ + Unit* felStalker = AI_VALUE2(Unit*, "find target", "wild fel stalker"); + + return felStalker && felStalker->IsAlive() && bot->getClass() == CLASS_WARLOCK; +} + +bool HighKingMaulgarPullingOlmAndBlindeyeTrigger::IsActive() +{ + Group* group = bot->GetGroup(); + if (!group || bot->getClass() != CLASS_HUNTER) + return false; + + std::vector 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(i); + break; + } + } + if (hunterIndex == -1 || hunterIndex > 1) + return false; + + Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner"); + Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer"); + Player* olmTank = nullptr; + Player* blindeyeTank = nullptr; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member || !member->IsAlive()) + continue; + else if (botAI->IsAssistTankOfIndex(member, 0)) olmTank = member; + else if (botAI->IsAssistTankOfIndex(member, 1)) blindeyeTank = member; + } + + switch (hunterIndex) + { + case 0: + return olm && olm->IsAlive() && olm->GetHealthPct() > 98.0f && + olmTank && olmTank->IsAlive() && botAI->CanCastSpell("misdirection", olmTank); + + case 1: + return blindeye && blindeye->IsAlive() && blindeye->GetHealthPct() > 90.0f && + blindeyeTank && blindeyeTank->IsAlive() && botAI->CanCastSpell("misdirection", blindeyeTank); + + default: + break; + } + + return false; +} + +// Gruul the Dragonkiller Triggers + +bool GruulTheDragonkillerBossEngagedByMainTankTrigger::IsActive() +{ + Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller"); + + return gruul && gruul->IsAlive() && botAI->IsMainTank(bot); +} + +bool GruulTheDragonkillerBossEngagedByRangeTrigger::IsActive() +{ + Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller"); + + return gruul && gruul->IsAlive() && botAI->IsRanged(bot); +} + +bool GruulTheDragonkillerIncomingShatterTrigger::IsActive() +{ + Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller"); + + return gruul && gruul->IsAlive() && + (bot->HasAura(SPELL_GROUND_SLAM_1) || + bot->HasAura(SPELL_GROUND_SLAM_2)); +} diff --git a/src/strategy/raids/gruulslair/RaidGruulsLairTriggers.h b/src/strategy/raids/gruulslair/RaidGruulsLairTriggers.h new file mode 100644 index 00000000..f3f32853 --- /dev/null +++ b/src/strategy/raids/gruulslair/RaidGruulsLairTriggers.h @@ -0,0 +1,97 @@ +#ifndef _PLAYERBOT_RAIDGRUULSLAIRTRIGGERS_H +#define _PLAYERBOT_RAIDGRUULSLAIRTRIGGERS_H + +#include "Trigger.h" + +class HighKingMaulgarIsMainTankTrigger : public Trigger +{ +public: + HighKingMaulgarIsMainTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar is main tank") {} + bool IsActive() override; +}; + +class HighKingMaulgarIsFirstAssistTankTrigger : public Trigger +{ +public: + HighKingMaulgarIsFirstAssistTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar is first assist tank") {} + bool IsActive() override; +}; + +class HighKingMaulgarIsSecondAssistTankTrigger : public Trigger +{ +public: + HighKingMaulgarIsSecondAssistTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar is second assist tank") {} + bool IsActive() override; +}; + +class HighKingMaulgarIsMageTankTrigger : public Trigger +{ +public: + HighKingMaulgarIsMageTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar is mage tank") {} + bool IsActive() override; +}; + +class HighKingMaulgarIsMoonkinTankTrigger : public Trigger +{ +public: + HighKingMaulgarIsMoonkinTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar is moonkin tank") {} + bool IsActive() override; +}; + +class HighKingMaulgarDeterminingKillOrderTrigger : public Trigger +{ +public: + HighKingMaulgarDeterminingKillOrderTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar determining kill order") {} + bool IsActive() override; +}; + +class HighKingMaulgarHealerInDangerTrigger : public Trigger +{ +public: + HighKingMaulgarHealerInDangerTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar healers in danger") {} + bool IsActive() override; +}; + +class HighKingMaulgarBossChannelingWhirlwindTrigger : public Trigger +{ +public: + HighKingMaulgarBossChannelingWhirlwindTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar boss channeling whirlwind") {} + bool IsActive() override; +}; + +class HighKingMaulgarWildFelstalkerSpawnedTrigger : public Trigger +{ +public: + HighKingMaulgarWildFelstalkerSpawnedTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar wild felstalker spawned") {} + bool IsActive() override; +}; + +class HighKingMaulgarPullingOlmAndBlindeyeTrigger : public Trigger +{ +public: + HighKingMaulgarPullingOlmAndBlindeyeTrigger(PlayerbotAI* botAI) : Trigger(botAI, "high king maulgar pulling olm and blindeye") {} + bool IsActive() override; +}; + +class GruulTheDragonkillerBossEngagedByMainTankTrigger : public Trigger +{ +public: + GruulTheDragonkillerBossEngagedByMainTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "gruul the dragonkiller boss engaged by main tank") {} + bool IsActive() override; +}; + +class GruulTheDragonkillerBossEngagedByRangeTrigger : public Trigger +{ +public: + GruulTheDragonkillerBossEngagedByRangeTrigger(PlayerbotAI* botAI) : Trigger(botAI, "gruul the dragonkiller boss engaged by range") {} + bool IsActive() override; +}; + +class GruulTheDragonkillerIncomingShatterTrigger : public Trigger +{ +public: + GruulTheDragonkillerIncomingShatterTrigger(PlayerbotAI* botAI) : Trigger(botAI, "gruul the dragonkiller incoming shatter") {} + bool IsActive() override; +}; + +#endif