mirror of
https://github.com/liyunfan1223/mod-playerbots.git
synced 2026-06-21 07:59:25 +02:00
Edited: Below description of methods were brought up to date as of the PR coming off of draft. ### General I've starting leveraging, to the extent possible, an out-of-combat method to erase map keys. This is mostly useful for timers that need to start upon the pull because I dislike having to rely on a check for a boss to be at 100% HP (or 99.9% or whatever) because it can be unreliable sometimes. ### Trash Underbog Colossi: Some Colossi leave behind a lake of toxin when they die that quickly kills any player that is standing in it. The pool is a dynamic-object-generated AoE, and bots will not avoid it on their own (I think because the AoE is out of combat, plus the radius is much larger than the default avoidance radius in the config). The method does not require bots to be in combat, and simply gets bots to run out of the toxin. You will probably still get a couple of idiots who drink in the middle of it, but in my experience, the vast majority of the raid gets out, and healers that escape can easily keep up a couple of fools until they've drank to full. Greyheart Tidecallers: Bots will mark and destroy Water Elemental Totems immediately. ### Hydross the Unstable The strategy uses 2 tanks, with the main tank assigned to the frost phase and the 1st assistant tank assigned to the nature phase. - The main tank will tank the frost phase, and the first assistant tank will tank the nature phase. They each have designated spots and will wait at their spots twiddling their thumbs while Hydross is in the other phase. - Hunters will misdirect to the applicable tank upon the pull and after each phase change. - The phase change process begins 1 second after Hydross reaches 100% Marks. The current tank will begin moving to the next phase tank's spot for the next tank to take over as soon as Hydross transitions. - DPS is ordered to stop after Hydross reaches 100% Marks until 5 seconds after he transitions. - Bots will prioritize the elementals adds after every phase change, unless Hydross is under 10% HP, in which case they should ignore the adds and burn the boss. - Ranged bots should spread during the frost phase to mitigate the impact of Water Tombs. ### The Lurker Below - There is a designated spot for the main tank. - Ranged DPS will fan out over a 120-degree arc that is centered directly across from the tank spot (to try to spread to reduce Geyser damage while also keeping them behind Lurker). - When Spout begins, all bots will run around behind Lurker. The intent is to keep a distance with a radius of 20 or 21 yards and within 45 degrees (either side) of directly behind him. Movement is specifically tangential along an arc so bots don't run in front of Lurker. - Spout's duration is tracked by a timer. The mechanics of the spell itself are rather unique and don't involve a continuous cast or aura to track easily so I settled for the timer. - If you have 3 (or more) tanks, each of the first 3 tanks will be assigned to one of the 3 Coilfang Guardians during the submerge phase. ### Leotheras the Blind The fight is designed for a Warlock tank. You can choose the Warlock tank by giving a Warlock the Assistant flag. If you don't do that, your highest HP Warlock will be picked. Do NOT switch the Warlock tank to a co +tank strategy--the designated Warlock is hardcoded to spam Searing Pain on Demon Leo and otherwise will engage in normal DPS strategies. If you don't have a Warlock at all, the strategy has some methods built in to try to make things work as best as possible with a melee tank. - The Spellbinders get marked with skulls and killed in order. - There is no designated spot or designated tank for the human phase. Your tanks will fight for aggro. Ranged bots will attempt to keep some distance, and when Whirlwind starts, everybody will run away from Leotheras. - During the demon phase, your melee tanks should take a backseat to your Warlock tank, who will receive help in the form of Misdirection. Bots will get the hell away from the Warlock tank so the Warlock tank should be taking every Chaos Blast alone. - During the final phase, your regular tanks will tank Leotheras, and the Warlock tank will tank his Shadow. The melee tanks will attempt to separate Leotheras from his Shadow so bots can focus down Leotheras without getting hit with Chaos Blasts. - Bots will wait 5 seconds to DPS after every transition into human phase, 12 seconds to DPS after every transition into demon phase, and 8 seconds to DPS after the transition into the final phase. There is no waiting on DPS after Whirlwinds, even though it would be ideal. It's not a big deal to live without, and for various reasons, it would have been a pain in the ass to deal with. - Bots will save Bloodlust/Heroism until after Spellbinders are down. - To deal with the Inner Demons, I disabled DPS assist for bots who are targeted and force them to focus only on their Inner Demons. This is sufficient in my experience for all DPS bots and Protection Warriors and Paladins to kill their Inner Demons, even at 50% damage. Feral Tank Druids and Healers still need help, so the strategy hardcodes their actions while fighting Inner Demons. For example, Resto Druids are coded to shift out of Tree Form, cast Barkskin on themselves, and just spam Wrath until the Inner Demon is dead. There are no bot strategy changes used for this method. ### Fathom-Lord Karathress You will need 4 tanks. Your main tank will tank Karathress, and an assistant tank will tank each Fathom Guard. If you have fewer than 4 tanks, then the priority order for tank assignment will be Karathress, Caribdis, Sharkkis, and then Tidalvess. - Roughly, the tank spots are (1) for Karathress, near where he starts but closer to the ledge for LoS reasons, (2) for Sharkkis, North from his starting location on the other side of the ramp, (3) for Tidalvess, Northwest from his starting location near the pillar, and (4) for Caribdis, far to the West of her starting position, near the corner. - Note that the tanks will probably clip through the terrain a bit when going to their positions. This is due to me implementing a forced MoveTo to the tank position coordinates. There is something weird about the maps in Karathress's room, and the tanks will take some really screwed up paths without making them go directly to the exact coordinates. So this looks stupid but is necessary. - One healer will be assigned to heal the Caribdis tank. Because AC Playerbots does not yet have a focus heal strategy, this just means that such healer has a designated location near the Caribdis tank's location. This healer can be selected with the Assistant flag. - Hunters will misdirect the Fathom Guards onto their applicable tanks. If you don't have three Hunters, the priority is Caribdis, Tidalvess, then Sharkkis. - DPS will wait 12 seconds to begin attacking. After that, they will prioritize targets as follows: - (1): Melee will always prioritize Spitfire Totems as soon as they spawn. This will continue through the duration of the fight. - (2): All bots will kill Tidalvess first. - (3): Melee bots will move to Sharkkis, and ranged bots will move to Caribdis. I understand this is not the standard kill order for players, which would have the entire raid kill Sharkkis next. The reasons I have done this differently are because melee DPS is much stronger with 3.3.5 talents vs. in retail TBC, and because bots get really thrown off by Cyclones and therefore they struggle to kill Caribdis quickly. You do not want Karathress below 75% HP before all Fathom-Guards are dead or he gets a huge damage buff. - (4) If Caribdis dies first, ranged bots will help with Sharkkis. - (5) Everybody kills Sharkkis's pet. - (6) Everybody kills Karathress. ### Morogrim Tidewalker - The main tank will pull the boss to the Northeast pillar, with the tank's back against the pillar. - A hunter will misdirect the boss onto the main tank upon the pull. - When the boss gets to 26% HP, the main tank will begin moving the boss to the Northeast corner of the room in preparation for Phase 2 (which begins at 25%). The tank will move in two steps to get around the pillar. - When the boss gets to 25% HP, ranged will follow the main tank to the corner and stack up right behind the boss. They will also move in two steps. - There is no method for melee since they will just naturally follow the boss anyway. ### Lady Vashj **Phase 1**: - The main tank will tank Vashj in the center of the arena. - If a Shaman is in the main tank's group, that Shaman will attempt to keep a Grounding Totem down in range of the main tank to absorb Shock Blast. This should continue in Phase 3. - Ranged bots will spread out in a semicircle around the center of the arena. - If any bot other than the main tank gets Static Charge, it will run away from other bots. If the main tank gets Static Charge, other bots will run away from the main tank. This method should continue in Phase 3. - If any bot is Entangled and has Static Charge, the bot will attempt to use Cloak of Shadows if it is a Rogue, and Paladins will attempt to use Hand of Freedom. This method should continue in Phase 3 (with some modifications). - Bots will not use Bloodlust or Heroism (saved for Phase 3). Bots will not use any other major cooldowns, either, such as Metamorphosis (saved for Phase 2 and 3). **Phase 2**: There are two central mechanics to this phase, both of which were challenging to get bots to execute properly. First is the system of prioritizing adds. The large playing field and multiple types of adds coming from random directions make this phase not doable with realistic DPS under the standard Playerbots target selection system. Therefore, I took inspiration from liyunfan's Naxx strategy for Phase 1 of Kel'Thuzad to disable dps assist and create a custom target selection system. First, a cheat with respect to the Coilfang Striders: - Tanks will permanently have the Fear Ward aura applied to them if you have raid cheats enabled. This allows them to tank the Coilfang Striders. The standard strategy was to have an Elemental Shaman kite the Strider around the perimeter of the arena, with ranged players (including healers) spamming DoTs on the Strider. If you can make bots do this, then great, but it's far beyond my capabilities. Therefore, with the cheat, the first assistant tank is responsible for tanking Striders and keeping them away from Core passers (described below) and Vashj. Evidently it was (and is, in TBC Classic) possible to tank (and melee DPS) Striders by wearing a Dire Maul Ogre Suit, which would give you enough reach to stay out of the Strider's fear. I actually tried that, and it does not work, either because AC's radiuses are not the same or just because bots do not maintain the same level of precise positioning. But anyway, the point is that technically the Striders are tankable by real players, so maybe that will make you feel better about using this cheat (it's fine enough rationalizing for me). I found this fight to be unmanageable without this cheat (i.e., using a method that would only have bots try to run away from Striders) because each Strider was guaranteed to wipe out a couple of bots, and you really cannot afford to lose anyone. YMMV though. - If cheats are enabled for Striders, Hunters will attempt to Misdirect the Striders to the first assist tank. - If cheats are not enabled, bots will attempt to use slows/roots to stop the Striders. I have some logic for them to use Netherweave Nets, but I suspect it does not actually work so I may remove it instead of trying to get it to function properly. Target priority is as follows: - Hunters and Mages: Enchanted Elementals, Coilfang Striders, Coilfang Elites. - Other Ranged Bots: Elites, Striders, Elementals. - Melee DPS: Elementals, Elites. - Tanks: Elites, Elementals (except if cheats are enabled, the first assistant tank will instead prioritize Striders and then Elementals) - Everybody else (basically means healers): Elementals, Elites, Striders - If there is more than one of the same target, bots will prioritize the one that is closer to Vashj. - In all cases, the valid attack ranged is limited so that bots should not leave the central platform. - If somehow a bot ends up too far from the center of the room and is not actively attacking anything, there is logic to make them run back. Handling Tainted Elementals and the Tainted Core: I will make another post about this later. It is easily the most complicated strategy I've ever worked on (far beyond anything on Kael'thas even) so will necessitate a long explanation. The tl;dr is that there is a chain of two-to-four bots that receive/pass the Tainted Core before using it on a Shield Generator, and if you are playing by yourself, you probably need to turn raid cheats on, in which case there will also be a bot that teleports to, kills, and loots the Tainted Elementals (i.e., the bots will then handle the entire sequence of shutting down Shield Generators). **Phase 3**: - The main tank will pick up Vashj immediately and try to keep her away from Enchanted Elementals. - DPS will burn down residual adds from Phase 2 in the order of (1) elementals, (2) strider for ranged only (if you have more than one up, you're dead), and (3) elites (hopefully you have only one up, but two with one almost dead is possible). - Hunters will kill Toxic Sporebats. This works quite well, but they (and anybody else if ordered to target Sporebats) have a tendency to levitate up into the pipes at the top of the room when killing the Sporebats. To counteract this, a method forcibly teleports bots to the ground if they get more than 2 yards above the ground. - The Phase 1 Cloak of Shadows/Hand of Freedom method is now expanded to include bots Entangled in the Sporebat poison pools (with Hand of Freedom usage prioritized on the main tank). - There is a specific method to avoid the Sporebat poison pools. The Vashj tank will move backwards when avoiding poison. --------- Co-authored-by: kadeshar <kadeshar@gmail.com>
671 lines
19 KiB
C++
671 lines
19 KiB
C++
#include "RaidSSCTriggers.h"
|
|
#include "RaidSSCHelpers.h"
|
|
#include "RaidSSCActions.h"
|
|
#include "AiFactory.h"
|
|
#include "Corpse.h"
|
|
#include "LootObjectStack.h"
|
|
#include "ObjectAccessor.h"
|
|
#include "Playerbots.h"
|
|
#include "RaidBossHelpers.h"
|
|
|
|
using namespace SerpentShrineCavernHelpers;
|
|
|
|
// General
|
|
bool SerpentShrineCavernBotIsNotInCombatTrigger::IsActive()
|
|
{
|
|
return !bot->IsInCombat();
|
|
}
|
|
|
|
// Trash Mobs
|
|
|
|
bool UnderbogColossusSpawnedToxicPoolAfterDeathTrigger::IsActive()
|
|
{
|
|
return bot->HasAura(SPELL_TOXIC_POOL);
|
|
}
|
|
|
|
bool GreyheartTidecallerWaterElementalTotemSpawnedTrigger::IsActive()
|
|
{
|
|
return botAI->IsDps(bot) &&
|
|
GetFirstAliveUnitByEntry(botAI, NPC_WATER_ELEMENTAL_TOTEM);
|
|
}
|
|
|
|
// Hydross the Unstable <Duke of Currents>
|
|
|
|
bool HydrossTheUnstableBotIsFrostTankTrigger::IsActive()
|
|
{
|
|
return AI_VALUE2(Unit*, "find target", "hydross the unstable") &&
|
|
botAI->IsMainTank(bot);
|
|
}
|
|
|
|
bool HydrossTheUnstableBotIsNatureTankTrigger::IsActive()
|
|
{
|
|
return AI_VALUE2(Unit*, "find target", "hydross the unstable") &&
|
|
botAI->IsAssistTankOfIndex(bot, 0, true);
|
|
}
|
|
|
|
bool HydrossTheUnstableElementalsSpawnedTrigger::IsActive()
|
|
{
|
|
Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable");
|
|
if (hydross && hydross->GetHealthPct() < 10.0f)
|
|
return false;
|
|
|
|
if (!AI_VALUE2(Unit*, "find target", "pure spawn of hydross") &&
|
|
!AI_VALUE2(Unit*, "find target", "tainted spawn of hydross"))
|
|
return false;
|
|
|
|
return !botAI->IsHeal(bot) && !botAI->IsMainTank(bot) &&
|
|
!botAI->IsAssistTankOfIndex(bot, 0, true);
|
|
}
|
|
|
|
bool HydrossTheUnstableDangerFromWaterTombsTrigger::IsActive()
|
|
{
|
|
return botAI->IsRanged(bot) &&
|
|
AI_VALUE2(Unit*, "find target", "hydross the unstable");
|
|
}
|
|
|
|
bool HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger::IsActive()
|
|
{
|
|
return bot->getClass() == CLASS_HUNTER &&
|
|
AI_VALUE2(Unit*, "find target", "hydross the unstable");
|
|
}
|
|
|
|
bool HydrossTheUnstableAggroResetsUponPhaseChangeTrigger::IsActive()
|
|
{
|
|
if (!AI_VALUE2(Unit*, "find target", "hydross the unstable"))
|
|
return false;
|
|
|
|
return bot->getClass() != CLASS_HUNTER &&
|
|
!botAI->IsHeal(bot) &&
|
|
!botAI->IsMainTank(bot) &&
|
|
!botAI->IsAssistTankOfIndex(bot, 0, true);
|
|
}
|
|
|
|
bool HydrossTheUnstableNeedToManageTimersTrigger::IsActive()
|
|
{
|
|
return AI_VALUE2(Unit*, "find target", "hydross the unstable") &&
|
|
IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr);
|
|
}
|
|
|
|
// The Lurker Below
|
|
|
|
bool TheLurkerBelowSpoutIsActiveTrigger::IsActive()
|
|
{
|
|
Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below");
|
|
if (!lurker)
|
|
return false;
|
|
|
|
const time_t now = std::time(nullptr);
|
|
|
|
auto it = lurkerSpoutTimer.find(lurker->GetMap()->GetInstanceId());
|
|
return it != lurkerSpoutTimer.end() && it->second > now;
|
|
}
|
|
|
|
bool TheLurkerBelowBossIsActiveForMainTankTrigger::IsActive()
|
|
{
|
|
Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below");
|
|
if (!lurker)
|
|
return false;
|
|
|
|
if (!botAI->IsMainTank(bot))
|
|
return false;
|
|
|
|
const time_t now = std::time(nullptr);
|
|
|
|
auto it = lurkerSpoutTimer.find(lurker->GetMap()->GetInstanceId());
|
|
return lurker->getStandState() != UNIT_STAND_STATE_SUBMERGED &&
|
|
(it == lurkerSpoutTimer.end() || it->second <= now);
|
|
}
|
|
|
|
bool TheLurkerBelowBossCastsGeyserTrigger::IsActive()
|
|
{
|
|
if (!botAI->IsRanged(bot))
|
|
return false;
|
|
|
|
Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below");
|
|
if (!lurker)
|
|
return false;
|
|
|
|
const time_t now = std::time(nullptr);
|
|
|
|
auto it = lurkerSpoutTimer.find(lurker->GetMap()->GetInstanceId());
|
|
return lurker->getStandState() != UNIT_STAND_STATE_SUBMERGED &&
|
|
(it == lurkerSpoutTimer.end() || it->second <= now);
|
|
}
|
|
|
|
// Trigger will be active only if there are at least 3 tanks in the raid
|
|
bool TheLurkerBelowBossIsSubmergedTrigger::IsActive()
|
|
{
|
|
Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below");
|
|
if (!lurker || lurker->getStandState() != UNIT_STAND_STATE_SUBMERGED)
|
|
return false;
|
|
|
|
Player* mainTank = nullptr;
|
|
Player* firstAssistTank = nullptr;
|
|
Player* secondAssistTank = nullptr;
|
|
|
|
Group* group = bot->GetGroup();
|
|
if (!group)
|
|
return false;
|
|
|
|
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
|
{
|
|
Player* member = ref->GetSource();
|
|
if (!member || !member->IsAlive())
|
|
continue;
|
|
|
|
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
|
|
if (!memberAI)
|
|
continue;
|
|
|
|
if (!mainTank && memberAI->IsMainTank(member))
|
|
mainTank = member;
|
|
else if (!firstAssistTank && memberAI->IsAssistTankOfIndex(member, 0, true))
|
|
firstAssistTank = member;
|
|
else if (!secondAssistTank && memberAI->IsAssistTankOfIndex(member, 1, true))
|
|
secondAssistTank = member;
|
|
}
|
|
|
|
if (!mainTank || !firstAssistTank || !secondAssistTank)
|
|
return false;
|
|
|
|
return bot == mainTank || bot == firstAssistTank || bot == secondAssistTank;
|
|
}
|
|
|
|
bool TheLurkerBelowNeedToPrepareTimerForSpoutTrigger::IsActive()
|
|
{
|
|
return AI_VALUE2(Unit*, "find target", "the lurker below") &&
|
|
IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr);
|
|
}
|
|
|
|
// Leotheras the Blind
|
|
|
|
bool LeotherasTheBlindBossIsInactiveTrigger::IsActive()
|
|
{
|
|
return AI_VALUE2(Unit*, "find target", "greyheart spellbinder");
|
|
}
|
|
|
|
bool LeotherasTheBlindBossTransformedIntoDemonFormTrigger::IsActive()
|
|
{
|
|
if (!AI_VALUE2(Unit*, "find target", "leotheras the blind"))
|
|
return false;
|
|
|
|
if (GetLeotherasDemonFormTank(bot) != bot)
|
|
return false;
|
|
|
|
return GetActiveLeotherasDemon(botAI);
|
|
}
|
|
|
|
bool LeotherasTheBlindOnlyWarlockShouldTankDemonFormTrigger::IsActive()
|
|
{
|
|
if (botAI->IsRanged(bot) || !botAI->IsTank(bot))
|
|
return false;
|
|
|
|
if (!AI_VALUE2(Unit*, "find target", "leotheras the blind"))
|
|
return false;
|
|
|
|
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
|
|
return false;
|
|
|
|
if (!GetLeotherasDemonFormTank(bot))
|
|
return false;
|
|
|
|
return GetPhase2LeotherasDemon(botAI);
|
|
}
|
|
|
|
bool LeotherasTheBlindBossEngagedByRangedTrigger::IsActive()
|
|
{
|
|
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
|
|
return false;
|
|
|
|
if (!botAI->IsRanged(bot))
|
|
return false;
|
|
|
|
Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind");
|
|
if (!leotheras)
|
|
return false;
|
|
|
|
return !leotheras->HasAura(SPELL_LEOTHERAS_BANISHED) &&
|
|
!leotheras->HasAura(SPELL_WHIRLWIND) &&
|
|
!leotheras->HasAura(SPELL_WHIRLWIND_CHANNEL);
|
|
}
|
|
|
|
bool LeotherasTheBlindBossChannelingWhirlwindTrigger::IsActive()
|
|
{
|
|
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
|
|
return false;
|
|
|
|
if (botAI->IsTank(bot) && botAI->IsMelee(bot))
|
|
return false;
|
|
|
|
Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind");
|
|
if (!leotheras || leotheras->HasAura(SPELL_LEOTHERAS_BANISHED))
|
|
return false;
|
|
|
|
return leotheras->HasAura(SPELL_WHIRLWIND) ||
|
|
leotheras->HasAura(SPELL_WHIRLWIND_CHANNEL);
|
|
}
|
|
|
|
bool LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger::IsActive()
|
|
{
|
|
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
|
|
return false;
|
|
|
|
if (botAI->IsRanged(bot))
|
|
return false;
|
|
|
|
Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST);
|
|
if (!chaosBlast || chaosBlast->GetStackAmount() < 5)
|
|
return false;
|
|
|
|
if (!GetLeotherasDemonFormTank(bot) && botAI->IsMainTank(bot))
|
|
return false;
|
|
|
|
return GetPhase2LeotherasDemon(botAI);
|
|
}
|
|
|
|
bool LeotherasTheBlindInnerDemonHasAwakenedTrigger::IsActive()
|
|
{
|
|
return bot->HasAura(SPELL_INSIDIOUS_WHISPER) &&
|
|
GetLeotherasDemonFormTank(bot) != bot;
|
|
}
|
|
|
|
bool LeotherasTheBlindEnteredFinalPhaseTrigger::IsActive()
|
|
{
|
|
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
|
|
return false;
|
|
|
|
if (botAI->IsHeal(bot))
|
|
return false;
|
|
|
|
if (GetLeotherasDemonFormTank(bot) == bot)
|
|
return false;
|
|
|
|
return GetPhase3LeotherasDemon(botAI) &&
|
|
GetLeotherasHuman(botAI);
|
|
}
|
|
|
|
bool LeotherasTheBlindDemonFormTankNeedsAggro::IsActive()
|
|
{
|
|
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
|
|
return false;
|
|
|
|
if (bot->getClass() != CLASS_HUNTER)
|
|
return false;
|
|
|
|
return AI_VALUE2(Unit*, "find target", "leotheras the blind");
|
|
}
|
|
|
|
bool LeotherasTheBlindBossWipesAggroUponPhaseChangeTrigger::IsActive()
|
|
{
|
|
return AI_VALUE2(Unit*, "find target", "leotheras the blind") &&
|
|
IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr);
|
|
}
|
|
|
|
// Fathom-Lord Karathress
|
|
|
|
bool FathomLordKarathressBossEngagedByMainTankTrigger::IsActive()
|
|
{
|
|
return AI_VALUE2(Unit*, "find target", "fathom-lord karathress") &&
|
|
botAI->IsMainTank(bot);
|
|
}
|
|
|
|
bool FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger::IsActive()
|
|
{
|
|
return AI_VALUE2(Unit*, "find target", "fathom-guard caribdis") &&
|
|
botAI->IsAssistTankOfIndex(bot, 0, false);
|
|
}
|
|
|
|
bool FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger::IsActive()
|
|
{
|
|
return AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis") &&
|
|
botAI->IsAssistTankOfIndex(bot, 1, false);
|
|
}
|
|
|
|
bool FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger::IsActive()
|
|
{
|
|
return AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess") &&
|
|
botAI->IsAssistTankOfIndex(bot, 2, false);
|
|
}
|
|
|
|
bool FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger::IsActive()
|
|
{
|
|
Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis");
|
|
if (!caribdis)
|
|
return false;
|
|
|
|
if (!botAI->IsAssistHealOfIndex(bot, 0, true))
|
|
return false;
|
|
|
|
Player* firstAssistTank = nullptr;
|
|
if (Group* group = bot->GetGroup())
|
|
{
|
|
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
|
{
|
|
Player* member = ref->GetSource();
|
|
if (!member || !member->IsAlive())
|
|
continue;
|
|
|
|
if (botAI->IsAssistTankOfIndex(member, 0, false))
|
|
{
|
|
firstAssistTank = member;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return firstAssistTank;
|
|
}
|
|
|
|
bool FathomLordKarathressPullingBossesTrigger::IsActive()
|
|
{
|
|
if (bot->getClass() != CLASS_HUNTER)
|
|
return false;
|
|
|
|
Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress");
|
|
return karathress && karathress->GetHealthPct() > 98.0f;
|
|
}
|
|
|
|
bool FathomLordKarathressDeterminingKillOrderTrigger::IsActive()
|
|
{
|
|
if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress"))
|
|
return false;
|
|
|
|
if (botAI->IsHeal(bot))
|
|
return false;
|
|
|
|
if (botAI->IsDps(bot))
|
|
return true;
|
|
else if (botAI->IsAssistTankOfIndex(bot, 0, false))
|
|
return !AI_VALUE2(Unit*, "find target", "fathom-guard caribdis");
|
|
else if (botAI->IsAssistTankOfIndex(bot, 1, false))
|
|
return !AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis");
|
|
else if (botAI->IsAssistTankOfIndex(bot, 2, false))
|
|
return !AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess");
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool FathomLordKarathressTanksNeedToEstablishAggroTrigger::IsActive()
|
|
{
|
|
return AI_VALUE2(Unit*, "find target", "fathom-lord karathress") &&
|
|
IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr);
|
|
}
|
|
|
|
// Morogrim Tidewalker
|
|
|
|
bool MorogrimTidewalkerPullingBossTrigger::IsActive()
|
|
{
|
|
if (bot->getClass() != CLASS_HUNTER)
|
|
return false;
|
|
|
|
Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker");
|
|
return tidewalker && tidewalker->GetHealthPct() > 95.0f;
|
|
}
|
|
|
|
bool MorogrimTidewalkerBossEngagedByMainTankTrigger::IsActive()
|
|
{
|
|
return AI_VALUE2(Unit*, "find target", "morogrim tidewalker") &&
|
|
botAI->IsMainTank(bot);
|
|
}
|
|
|
|
bool MorogrimTidewalkerWaterGlobulesAreIncomingTrigger::IsActive()
|
|
{
|
|
if (!botAI->IsRanged(bot))
|
|
return false;
|
|
|
|
Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker");
|
|
return tidewalker && tidewalker->GetHealthPct() < 25.0f;
|
|
}
|
|
|
|
// Lady Vashj <Coilfang Matron>
|
|
|
|
bool LadyVashjBossEngagedByMainTankTrigger::IsActive()
|
|
{
|
|
return AI_VALUE2(Unit*, "find target", "lady vashj") &&
|
|
!IsLadyVashjInPhase2(botAI) && botAI->IsMainTank(bot);
|
|
}
|
|
|
|
bool LadyVashjBossEngagedByRangedInPhase1Trigger::IsActive()
|
|
{
|
|
return botAI->IsRanged(bot) && IsLadyVashjInPhase1(botAI);
|
|
}
|
|
|
|
bool LadyVashjCastsShockBlastOnHighestAggroTrigger::IsActive()
|
|
{
|
|
if (bot->getClass() != CLASS_SHAMAN)
|
|
return false;
|
|
|
|
if (!AI_VALUE2(Unit*, "find target", "lady vashj") ||
|
|
IsLadyVashjInPhase2(botAI))
|
|
return false;
|
|
|
|
if (!IsMainTankInSameSubgroup(bot))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LadyVashjBotHasStaticChargeTrigger::IsActive()
|
|
{
|
|
if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
|
|
return false;
|
|
|
|
if (Group* group = bot->GetGroup())
|
|
{
|
|
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
|
{
|
|
Player* member = ref->GetSource();
|
|
if (member && member->HasAura(SPELL_STATIC_CHARGE))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool LadyVashjPullingBossInPhase1AndPhase3Trigger::IsActive()
|
|
{
|
|
if (bot->getClass() != CLASS_HUNTER)
|
|
return false;
|
|
|
|
Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj");
|
|
if (!vashj)
|
|
return false;
|
|
|
|
return (vashj->GetHealthPct() <= 100.0f && vashj->GetHealthPct() > 90.0f) ||
|
|
(!vashj->HasUnitState(UNIT_STATE_ROOT) && vashj->GetHealthPct() <= 50.0f &&
|
|
vashj->GetHealthPct() > 40.0f);
|
|
}
|
|
|
|
bool LadyVashjAddsSpawnInPhase2AndPhase3Trigger::IsActive()
|
|
{
|
|
if (botAI->IsHeal(bot))
|
|
return false;
|
|
|
|
return AI_VALUE2(Unit*, "find target", "lady vashj") &&
|
|
!IsLadyVashjInPhase1(botAI);
|
|
}
|
|
|
|
bool LadyVashjCoilfangStriderIsApproachingTrigger::IsActive()
|
|
{
|
|
return AI_VALUE2(Unit*, "find target", "coilfang strider");
|
|
}
|
|
|
|
bool LadyVashjTaintedElementalCheatTrigger::IsActive()
|
|
{
|
|
if (!botAI->HasCheat(BotCheatMask::raid))
|
|
return false;
|
|
|
|
if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
|
|
return false;
|
|
|
|
bool taintedPresent = false;
|
|
Unit* taintedUnit = AI_VALUE2(Unit*, "find target", "tainted elemental");
|
|
if (taintedUnit)
|
|
taintedPresent = true;
|
|
else
|
|
{
|
|
GuidVector corpses = AI_VALUE(GuidVector, "nearest corpses");
|
|
for (auto const& guid : corpses)
|
|
{
|
|
LootObject loot(bot, guid);
|
|
WorldObject* object = loot.GetWorldObject(bot);
|
|
if (!object)
|
|
continue;
|
|
|
|
if (Creature* creature = object->ToCreature())
|
|
{
|
|
if (creature->GetEntry() == NPC_TAINTED_ELEMENTAL && !creature->IsAlive())
|
|
{
|
|
taintedPresent = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!taintedPresent)
|
|
return false;
|
|
|
|
Group* group = bot->GetGroup();
|
|
if (!group)
|
|
return false;
|
|
|
|
return (GetDesignatedCoreLooter(group, botAI) == bot &&
|
|
!bot->HasItemCount(ITEM_TAINTED_CORE, 1, false));
|
|
}
|
|
|
|
bool LadyVashjTaintedCoreWasLootedTrigger::IsActive()
|
|
{
|
|
if (!AI_VALUE2(Unit*, "find target", "lady vashj") || !IsLadyVashjInPhase2(botAI))
|
|
return false;
|
|
|
|
Group* group = bot->GetGroup();
|
|
if (!group)
|
|
return false;
|
|
|
|
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
|
|
Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI);
|
|
Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI);
|
|
Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI);
|
|
Player* fourthCorePasser = GetFourthTaintedCorePasser(group, botAI);
|
|
|
|
auto hasCore = [](Player* player) -> bool
|
|
{
|
|
return player && player->HasItemCount(ITEM_TAINTED_CORE, 1, false);
|
|
};
|
|
|
|
if (bot == designatedLooter)
|
|
{
|
|
if (!hasCore(bot))
|
|
return false;
|
|
}
|
|
else if (bot == firstCorePasser)
|
|
{
|
|
if (hasCore(secondCorePasser) || hasCore(thirdCorePasser) ||
|
|
hasCore(fourthCorePasser))
|
|
return false;
|
|
}
|
|
else if (bot == secondCorePasser)
|
|
{
|
|
if (hasCore(thirdCorePasser) || hasCore(fourthCorePasser))
|
|
return false;
|
|
}
|
|
else if (bot == thirdCorePasser)
|
|
{
|
|
if (hasCore(fourthCorePasser))
|
|
return false;
|
|
}
|
|
else if (bot != fourthCorePasser)
|
|
return false;
|
|
|
|
if (AnyRecentCoreInInventory(group, botAI))
|
|
return true;
|
|
|
|
// First and second passers move to positions as soon as the elemental appears
|
|
if (AI_VALUE2(Unit*, "find target", "tainted elemental") &&
|
|
(bot == firstCorePasser || bot == secondCorePasser))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool LadyVashjTaintedCoreIsUnusableTrigger::IsActive()
|
|
{
|
|
Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj");
|
|
if (!vashj)
|
|
return false;
|
|
|
|
if (!IsLadyVashjInPhase2(botAI))
|
|
return bot->HasItemCount(ITEM_TAINTED_CORE, 1, false);
|
|
|
|
Group* group = bot->GetGroup();
|
|
if (!group)
|
|
return false;
|
|
|
|
Player* coreHandlers[] =
|
|
{
|
|
GetDesignatedCoreLooter(group, botAI),
|
|
GetFirstTaintedCorePasser(group, botAI),
|
|
GetSecondTaintedCorePasser(group, botAI),
|
|
GetThirdTaintedCorePasser(group, botAI),
|
|
GetFourthTaintedCorePasser(group, botAI)
|
|
};
|
|
|
|
if (bot->HasItemCount(ITEM_TAINTED_CORE, 1, false))
|
|
{
|
|
for (Player* coreHandler : coreHandlers)
|
|
{
|
|
if (coreHandler && bot == coreHandler)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool LadyVashjNeedToResetCorePassingTrackersTrigger::IsActive()
|
|
{
|
|
Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj");
|
|
if (!vashj || IsLadyVashjInPhase2(botAI))
|
|
return false;
|
|
|
|
Group* group = bot->GetGroup();
|
|
if (!group)
|
|
return false;
|
|
|
|
return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr) ||
|
|
GetDesignatedCoreLooter(group, botAI) == bot ||
|
|
GetFirstTaintedCorePasser(group, botAI) == bot ||
|
|
GetSecondTaintedCorePasser(group, botAI) == bot ||
|
|
GetThirdTaintedCorePasser(group, botAI) == bot ||
|
|
GetFourthTaintedCorePasser(group, botAI) == bot;
|
|
}
|
|
|
|
bool LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger::IsActive()
|
|
{
|
|
return IsLadyVashjInPhase3(botAI);
|
|
}
|
|
|
|
bool LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger::IsActive()
|
|
{
|
|
if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
|
|
return false;
|
|
|
|
if (Group* group = bot->GetGroup())
|
|
{
|
|
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
|
{
|
|
Player* member = ref->GetSource();
|
|
if (!member || !member->HasAura(SPELL_ENTANGLE))
|
|
continue;
|
|
|
|
if (botAI->IsMelee(member))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|