Implement Tempest Keep: The Eye Strategies (#1943)

Edit: Descriptions of methods are out of date right now. To be updated.

This one comes with the same caveats as SSC about requiring ownership
from somebody with C++ knowledge, except I think the matter is even more
acute here because these strategies incorporate a novel approach
proposed by Timberpoes. By redeclaring the entire bossai class for
Kael’thas, it was possible to add new member functions to the class in
order to access its private member variables. This allows bots to have
visibility into boss mechanics beyond what they could do with ordinary
techniques and is similar in approach to what was done by the Naxx
strategies, except that this approach does not require any modifications
to the core. I used it for only one mechanic, which was to detect
Kael’thas’s phase. That was very helpful because the fight is divided
into 5 phases, and distinguishing between them with traditional
techniques requires lookups of a dozen NPCs and comparisons of their
various unit states, react states, and auras; by accessing his bossai,
this can all be avoided. However, there is far more potential beyond
this if the approach is an acceptable one.

On with the (shit)show.

### Trash
In a perfect world, there would be many strategies for TK trash, which
is easily more difficult than two of the bosses. It’s a real pain to do
though because to solve the biggest issues properly, each pack would
have to be handled a little differently. So the only thing I’ve included
is for Mages to cast polymorph on the Crimson Hand Centurions when they
are channeling Arcane Flurry. The purpose is not to actually keep them
CC’d but to interrupt their channel.

### Al’ar
This fight sucked so much to write a strategy for. The only silver
lining is that being the post-nerf version, the boss moves between only
4 platform locations (instead of 6), and movement between them is on a
fixed rotation (interrupted by Flame Quills) instead of being random.
Thus, a strategy can be consistently replicated, and the fight can be
done with only 3 tanks (2 on the platforms for the boss and 1 below for
adds).

**Phase 1:**
I’m going to call the platform that Al’ar lands at after the pull
“platform 0” because that reflects the indices in the code. In a
clockwise direction, the remaining platforms will be referred to as
platforms 1, 2, and 3, respectively.
The best way to pull is to first put all ranged, as well as tanks other
than your main tank and first assistant tank, on nc +stay below platform
0. Then, go up the ramp to platform 0 with your main tank, first
assistant tank, and melee dps following you, then hit Al’ar with any
ranged attack or spell to start the fight.

- Your main tank will start at platform 0, and your first assistant tank
will immediately move to platform 1. When Al’ar moves to platform 1,
your main tank will move to platform 2. When Al’ar moves to platform 2,
your first assistant tank will move to platform 3. When Al’ar moves to
platform 3, your main tank will move back to platform 0. This assures a
tank is available to receive Al’ar after every platform movement (every
30 seconds).
- Melee DPS will follow Al’ar as it moves between platforms.
- Each platform is mapped to a corresponding ground location below it.
Ranged DPS and healers will follow Al’ar by moving to the corresponding
ground location as it flies between platforms.
- After each platform move, an Ember of Al’ar will spawn. Your second
assistant tank will pick up the Ember and move it to the point that is
25 yards away from the ground position corresponding to Al’ar’s platform
(on an invisible line between such ground position and the middle of the
room). Ranged DPS will then focus down the Ember before switching back
to Al’ar (this positioning is so that ranged are not hit by the Ember
Blast explosion that happens whenever an Ember dies).
- Each time Al’ar leaves a platform, it has a chance to instead fly up
high in the middle of the room to perform Flame Quills, which will
one-shot anybody on the upper level or ramps. When Al’ar begins the
Flame Quills sequence, all bots on the top level will jump off. FYI,
Al’ar’s usage of Flame Quills is not entirely random: there is a 20%
chance for it to do so after the first platform move, and the chance
increases by another 20% after each subsequent platform move that does
not trigger Flame Quills (reset after each Flame Quills sequence).
- After Flame Quills, Al’ar will randomly land at either platform 0 or
3. To prepare for this, bots will move to assigned positions during the
Flame Quills sequence:
- Ranged and the second assistant tank will wait in the middle of the
room.
- Melee DPS will wait at a point that is between the base of each ramp.
  - The main tank will wait at the base of the ramp to platform 0.
- The first assistant tank will wait at the base of the ramp to platform
3.
- Once Al’ar lands, the regular Phase 1 strategies resume.
- When Al’ar “dies,” it disappears and moves to the center of the room,
where it casts Rebirth and returns to full HP. Bots will wait outside of
the radius of the Rebirth explosion for Phase 2 to start.

Phase 2:

- Your main tank will tank Al’ar initially. When Al’ar casts Melt Armor,
your first assistant tank will taunt Al’ar and take over. The tank swaps
will continue back and forth every time Melt Armor is cast.
- Bots will avoid Flame Patches. FWIW, the standard co +avoid aoe
strategy does work for Flame Patches, but avoid aoe provides no buffer
distance so as you’ve probably noticed, it doesn’t provide for
preemptive avoidance. Also, avoid aoe does not consider multiple hazards
together so it can be an issue when movement needs to take into account
more than one hazard, plus when a strategy requires particular bot
movement, it’s better to account for the hazards within that movement
strategy instead of relying on separate methods that can create
conflicts.
- When Al’ar takes to the sky to perform Dive Bomb, bots will spread out
(and continue to avoid Flame Patches). After the Dive Bomb, Al’ar does
another Rebirth explosion. I have tried a million different things to
properly detect this full sequence (even accessing the bossAI like I did
with Kael’thas) and cannot get it to work properly. Ultimately, all I’ve
been able to get to work at all with respect to the final explosion is
for bots to detect the 2-second cast of the Rebirth and run out. It is
not enough time for bots that are too close when the cast happens so
some bots may get hit, but if you have adequate gear, they should
survive.
- After each Dive Bomb, 2 Embers will spawn. Your second assistant tank
will tank one Ember, and either the main tank or first assistant tank,
whichever one is not tanking Al’ar at the time, will tank the other
Ember. They will both move the Embers away from bots, and ranged DPS
will focus both Embers down before switching back to Al’ar.
- Because the room is so large, it is possible for bots to get too far
away from active combat (particularly if they are thrown across the room
by Ember Blast) so there is also a method for them to run back toward
the center if they get too far away.

### Void Reaver
Ironically, what was often considered the easiest boss in 25-player
content in TBC is the only boss with an ability (Arcane Orb) that I do
not believe can be avoided by bots, even with access to Void Reaver’s
boss script. Therefore, every single Arcane Orb is going to hit its
target, so the strategy can only try to limit the damage by spreading
ranged bots in two rings around Void Reaver (one for healers and one for
ranged DPS, to try to ensure sufficient distribution of healers). The
tanks will all fight for aggro (necessary due to Knock Away) and try to
keep Void Reaver in the middle of the room. Bots that can wipe aggro or
otherwise gain invulnerability are directed to use the applicable
abilities as soon as they pick up aggro (e.g., Soulshatter). He’s still
easy, but if you have IP nerfs, it’s a little bit of a gear check.

### High Astromancer Solarian
No boss was hit harder by nerfs in TBC than Solarian, whose encounter
went from a totally unique fight that required arcane resistance to a
fight that is kind of just an easier Baron Geddon. IMO, she is the
easiest boss in TBC 25-player raids.

- Ranged bots stack up at a distance from Solarian; this leaves all bots
with plenty of space to run away from other bots when they get Wrath of
the Astromancer.
- When Solarian vanishes, all bots will stack to AoE down the Solarium
Agents that spawn.
- When Solarian returns with two Solarium Priests, melee will divide
into two groups, with one focused on each Solarium Priest. I think this
method is not working correctly right now because when one Priest dies,
the bots still on the second Priest are leaving it. I’ll need to decide
whether I want to figure it out or just get rid of it because this fight
is so easy regardless.
- Priest bots will cast Fear Ward on the main tank to block the Psychic
Scream during the final tank-and-spank Voidwalker phase, and the main
tank will pick up Voidwalker Solarian as soon as she transforms.

Note that the bots will not be knocked into the air by Wrath of the
Astromancer. The issue is due to the presence of a check for knockbacks
in Playerbots that causes bots to ignore knockbacks that would launch
them at a velocity beyond a hardcoded value. I’ve increased that
velocity limit on my own fork, and it does allow Wrath of the
Astromancer (and other knockbacks that otherwise don’t work) to work on
bots. But that’s obviously a broader issue and not addressed in this PR,
and bots don’t take fall damage in any case.

### Kael’thas Sunstrider

So this strategy has 23(!) action methods. But like in retail, this is
actually an easy fight once it is learned because it is highly scripted.

Unlike in other strategies I’ve done, the bots probably cannot do this
fight by themselves unless they are way overgeared. This is because
there are a few windows during which bots need to position themselves
properly based on dynamic factors. But no RTSC is needed—you just need
to have bots follow you to the right locations. Also note that the gear
check for this strategy is higher than in retail because you have to get
all of the legendary weapons down and looted before the advisors aggro
in Phase 3, or it’s going to be an absolute shitshow (with human
players, you can deal with there still being a couple of weapons up).
For a point of reference, when I was first working on this strategy with
damage reduced to 50% and bots pretty close to T4 BiS, I had almost no
margin of error (I would usually get the weapons down with barely a
second to spare).

You will need at least 2 tanks, but 3 is better. Your main tank will
need to be able to equip the legendary shield so you must use a Warrior
or Paladin. However, it is ideal for the first assistant tank to be a
Druid because they can equip the legendary staff.

**Phase 1:**
Fun fact—when you “kill” the advisors in this phase, they don’t actually
die but get an aura applied called “Permanent Feign Death” (nice
oxymoron).

- _Thaladred_: You’re supposed to kite him, and bots can’t really kite,
so the method is a poor man’s method of having the bot move away from
him in a straight line when fixated. You want him to die in the far
Southern part of the room. If he dies in a bad location, you may as well
call a wipe and restart. What will work best for you will depend on your
DPS since you don’t want to kill him before he gets to the location you
want but also don’t want bots to be trapped up against a wall since they
can’t properly kite him. The way that works best for me is to have bots
stay back while I aggro the boss, and wait until right before Thaladred
switches to his second fixate target before attacking. Note that if you
do put bots on stay, when you put them back on follow, the bot that is
then being fixated will remain on stay (because they need to disregard
movement orders other than running away from Thaladred). So after
Thaladred dies, make sure to manually type /follow or the bot that was
fixated when you took the bots off of stay will not rejoin the fight.
- _Sanguinar_: He will be tanked by your main tank, who will be targeted
by your Priests for Fear Ward. Bots will wait to engage him; I made it a
very generous time (12 seconds) because there is absolutely no rush in
Phase 1. There’s no sense in being aggressive. During that time, the
main tank will drag Sanguinar to the West wall.
- _Capernian_: This is the first make-or-break part of the fight. Phase
1 Capernian was the most frequent cause for wipes for me.
- She should be tanked by a Warlock. If you want to pick your Warlock
tank, you can do so by the assistant flag, but if you don’t, the
strategy will just pick your highest HP Warlock. If you raid without a
Warlock, then you’re insane, but at least there’s a guard so your server
won’t crash?
- You do not need to add the tank strategy to your Warlock. There is a
method that will automatically switch your selected tank Warlock between
DPS and tank strategies at appropriate times because you need to squeeze
out every drop of DPS you can get, particularly for Phase 2, where
you’ll need your Warlock to be blowing up weapons with Seed of
Corruption instead of spamming Searing Pain. You’ll want your Warlock to
start with a DPS strategy as usual (since they should be DPSing
Thaladred).
- To engage Capernian, start running East right before Sanguinar dies.
She will activate quickly, and you want to try to get in front of her
(but not too close) before she aggros.
- When Capernian aggros, your Warlock tank will immediately switch to
the tank strategy and attack. Your main tank will run toward Capernian
but not actually attack; their purpose will be to bait her Conflagration
to reduce the chance that it hits your Warlock tank. Other melee will
not engage Capernian. Ranged DPS will be idle for 12 seconds; during
this time, you should run South to make sure they are not in range of
Capernian. After 12 seconds, your ranged DPS will activate, move into
range and spread out, and attack (it doesn’t seem possible to outrange
Conflagrate, so if bots don’t spread, she will annihilate the entire
ranged group with a single cast). Ideally, you kill her not too far from
her starting position. If she ends up in the middle of the room, you
should probably wipe and start over.
- _Telonicus_: He is very easy in retail but actually is a big risk for
wipes with respect to bots because his bombs will one-shot any non-tank,
and bots will stupidly stand in front of him without a proper strategy.
You should keep some distance from him before he aggros. Your first
assistant tank will pick him up and move him to the West wall near
Sanguinar. Again, there is a 12-second delay before DPS starts. Your
melee DPS are coded to stay directly behind him and not get too close so
they don’t get hit by bombs.

**Phase 2:**
Kael’thas will summon all weapons immediately after Telonicus is down.
Just before Telonicus is down, you should move to the platform where the
advisors originally were—you’ll be in better position for the raid to
AoE down the weapons.

- Your main tank will pick up the axe and move it away from the group.
The axe is the biggest threat during this phase and can easily one-shot
casters if not pulled away.
- One of your Hunters will attempt to get aggro on the bow and move away
from the group (as a hacky way of trying to turn the bow away from the
group because you can’t really get a bot to do that directly). This
method is hit or miss, but it shouldn’t be that big of a deal if your
Hunter doesn’t pull it off properly.
- Everybody else will prioritize weapons in the following order (but
most damage will come from AoE, which is what you want or you will not
beat the timer): staff, mace, sword, dagger, axe (ranged only), bow, and
shield.
- As weapons are defeated, bots will loot and equip them. If you have
not disabled bot announcements in your config, you get to see your
entire raid go nuts because they looted legendary items.
- Here is what weapons bots will loot and equip. I don't know anything
about DKs, having never played WotLK, so tell me if anything is wrong
for them.
- _Healers:_ Mace (if a healer normally uses a staff, it's best if they
keep an OH in their bags for this fight)
- _Tanks:_ Shield and sword for Paladins and DK, shield and dagger for
warriors, staff for Druid
  - _Offensive_ casters: Staff
- _Rogues:_ Sword and dagger if Combat or Subtlety, dagger only if
Assassination
  - _DPS Death Knights, Retribution Paladins, Arms Warriors_: Axe
- _Fury Warriors_: Dagger. I understand that due to Titan Grip, they
should also have the Axe for best DPS; however, Fury Warriors have awful
DPS (we’re talking barely above Prot-level) at this stage. Thus, my view
is it is better to give them only the dagger so they will MH it and help
break MC in Phase 4, since they will contribute hardly any DPS
regardless.
  - _Cat Druids_: Staff
  - _Enhancement Shamans_: Dagger
- _Hunters:_ Bow and dagger. Note that I do NOT have them loot the sword
because they need the dagger in their mainhand to use to break MC in
Phase 4; whatever marginal benefit they get from the sword as a stat
stick is not worth losing this capability. If your Hunter uses a 2H, it
is best to have them carry a 1H in their inventory so they can put
something in the OH after they equip the dagger.
- After looting weapons, bots with the staff will use it (once) to
activate the Mental Protection Field. Hunters will use the bow to
generate the legendary arrows and equip those (and will continue to do
so during the fight if they use up the arrows).
- If you wipe from this point forward, everybody will lose their
legendary weapons, and by default, most bots will not automatically
reequip their own weapons until a loot event occurs. This was extremely
annoying, and therefore there is a noncombat method implemented that
causes everybody to equip upgrades when they get within 150 yards of
Kael’thas. I considered applying this to the whole instance, but I’m not
sure if some people would not like that so I decided to limit things to
the Kael’thas encounter.

**Phase 3:**
I highly recommend you have your Shamans drop Tremor Totems (co +tremor)
during this phase. Doing so is not coded because I wanted to leave
flexibility, but I think it is very helpful for Sanguinar. After the
weapons die, you want to move your bots to a central location between
the advisors. If Thaladred died closer to the middle of the room,
ideally you position to the side of Thaladred so when he fixates he will
not chase bots North into the other advisors.

- Shamans will immediately use Heroism/Bloodlust.
- Your melee tanks will bring Sanguinar and Telonicus to their tanking
positions (same as Phase 1). If your first assistant tank is a Druid,
they will be immune to Telonicus’s Remote Toy due to having the
legendary staff’s aura activated and will also make your main tank
immune.
- One healer will stay by the Sanguinar and Telonicus tanking positions
to heal the tanks. Once IsHealAssistantOfIndex() is fixed, you will be
able to select this healer with the assistant flag. Right now, this will
just be the last healer that joined your raid (per standard AC logic).
- DPS priority will be Thaladred, Capernian (ranged only), Sanguinar,
Telonicus. As with retail, the most chaotic period will be before
Thaladred is killed, particularly if he chases bots into other advisors.
I don’t have a great solution for this, but Capernian is significantly
less dangerous during this phase thanks to the legendary staff. This is
the last true breakpoint—if you get Thaladred down with your raid mostly
intact, you are very likely to get the kill.

**Phase 4:**
Kael’thas will aggro immediately after all advisors are dead.

- Your main tank will position Kael’thas at his original position.
- Bots will move out of Flame Strikes.
- Assist tanks will pick up Phoenixes. Since they die over time anyway,
bots will not waste time attacking them. When Phoenixes die, they turn
into an Egg—at that point, bots will switch to the Egg to destroy it
before the Phoenix is reborn.
- When Kael’thas puts up Shock Barrier and starts casting Pyroblast on
your main tank (a one-shot), all bots will focus DPS on him (even if
there is an egg up). You have 4 seconds to break the barrier (80K HP)
and interrupt his Pyroblast. It is likely that you will not be able to
if you are playing with IP nerfs and are in T4 gear. However, the main
tank will use the legendary shield’s ability, which will allow them to
absorb one cast, giving you 8 seconds to break the barrier and interrupt
Pyroblast. Bots will put top priority on interrupting Pyroblast as soon
as the barrier is down.
- If a bot (or player) is mind controlled, bots with the legendary
dagger (other than tanks) will move to MC’d players and use the
following attacks to break MC: Shiv (Rogues), Hamstring (Warriors), Wing
Clip (Hunters), and Stormstrike (Shamans).

**Phase 5:**
At 50% HP, Kael’thas enters a long RP sequence. This is a good time to
kill any remaining Phoenixes and/or Eggs.
- Kael’thas stops casting Pyroblast and Mind Control.
- His main new ability is Gravity Lapse, and it doesn’t work properly on
bots... He sucks in the entire raid then knocks everybody back in a
different direction. What is supposed to happen is that players will end
up floating in midair in different directions and at different heights.
However, bots will immediately fall to the ground after getting knocked
back. They will not actually hit the ground though and instead remain in
a flying state right above the floor.
- If you could move in 3D space, Netherbeam would be very easy to deal
with. However, because that is not available to bots, they can spread
only in 2D space and thus need to move farther to get properly spread,
and they waste the first moments falling straight down. As a result, the
damage from Netherbeam can be quite high, and the beginning of Gravity
Lapse requires a lot of healing. I don’t really have a better way of
dealing with this.
- FWIW, I don’t think there is any existing method to make bots disperse
in 3D anyway.
- Kael’thas is supposed to use Nether Void when players are in midair,
which creates clouds that reduce your max HP and thus make it more
challenging to maneuver, but AC is bugged and he doesn’t use the ability
at all (there’s been an open issue about this forever).

For fuck's sake, that's all.

---------

Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com>
Co-authored-by: bash <hermensb@gmail.com>
Co-authored-by: Revision <tkn963@gmail.com>
This commit is contained in:
Crow
2026-02-27 18:04:10 -06:00
committed by GitHub
parent 1401657a6d
commit be2cf436ea
21 changed files with 5544 additions and 154 deletions

View File

@@ -77,39 +77,101 @@ void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target)
// Intended for purposes of storing and erasing timers and trackers in associative containers // Intended for purposes of storing and erasing timers and trackers in associative containers
bool IsMechanicTrackerBot(PlayerbotAI* botAI, Player* bot, uint32 mapId, Player* exclude) bool IsMechanicTrackerBot(PlayerbotAI* botAI, Player* bot, uint32 mapId, Player* exclude)
{ {
if (Group* group = bot->GetGroup()) if (!botAI->IsDps(bot) || !bot->IsAlive() || bot->GetMapId() != mapId)
{ return false;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member->GetMapId() != mapId ||
!GET_PLAYERBOT_AI(member) || !botAI->IsDps(member))
continue;
if (member != exclude) Group* group = bot->GetGroup();
return member == bot; if (!group)
} return false;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member->GetMapId() != mapId || member == exclude)
continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (!memberAI || !memberAI->IsDps(member))
continue;
return member == bot;
} }
return false; return false;
} }
// Return the first matching alive unit from a cell search of nearby npcs // Requires the main tank to be alive
// More responsive than "find target," but performance cost is much higher // Note that IsMainTank() will return the player with the main tank flag, even if dead
// Re: using the third parameter (false by default), some units are never considered Player* GetGroupMainTank(PlayerbotAI* botAI, Player* bot)
// to be in combat (e.g., totems) {
Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry, bool requireInCombat) Group* group = bot->GetGroup();
if (!group)
return nullptr;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive())
continue;
if (botAI->IsMainTank(member))
return member;
}
return nullptr;
}
// Returns the alive assist tank of the specified index (0 = first, 1 = second, etc.)
// Priority: Assistants first, then Non-Assistants.
Player* GetGroupAssistTank(PlayerbotAI* botAI, Player* bot, uint8 index)
{
Group* group = bot->GetGroup();
if (!group)
return nullptr;
uint8 assistantCount = 0;
std::vector<Player*> nonAssistantTanks;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive())
continue;
if (botAI->IsAssistTank(member))
{
if (group->IsAssistant(member->GetGUID()))
{
if (assistantCount == index)
return member;
assistantCount++;
}
else
{
nonAssistantTanks.push_back(member);
}
}
}
// If the index wasn't found among assistants, check the non-assistants that were saved
uint8 nonAssistantIndex = index - assistantCount;
if (nonAssistantIndex < nonAssistantTanks.size())
return nonAssistantTanks[nonAssistantIndex];
return nullptr;
}
// Return the first matching alive unit from PossibleTargetsValue within sightDistance from config
Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry)
{ {
auto const& npcs = auto const& npcs =
botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get(); botAI->GetAiObjectContext()->GetValue<GuidVector>("possible targets no los")->Get();
for (auto const& npcGuid : npcs) for (auto const& npcGuid : npcs)
{ {
Unit* unit = botAI->GetUnit(npcGuid); Unit* unit = botAI->GetUnit(npcGuid);
if (unit && unit->IsAlive() && unit->GetEntry() == entry) if (unit && unit->IsAlive() && unit->GetEntry() == entry)
{ return unit;
if (!requireInCombat || unit->IsInCombat())
return unit;
}
} }
return nullptr; return nullptr;

View File

@@ -15,7 +15,10 @@ void MarkTargetWithCross(Player* bot, Unit* target);
void MarkTargetWithMoon(Player* bot, Unit* target); void MarkTargetWithMoon(Player* bot, Unit* target);
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target); void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target);
bool IsMechanicTrackerBot(PlayerbotAI* botAI, Player* bot, uint32 mapId, Player* exclude = nullptr); bool IsMechanicTrackerBot(PlayerbotAI* botAI, Player* bot, uint32 mapId, Player* exclude = nullptr);
Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry, bool requireInCombat = false); Player* GetGroupMainTank(PlayerbotAI* botAI, Player* bot);
Player* GetGroupAssistTank(PlayerbotAI* botAI, Player* bot, uint8 index);
Unit* GetFirstAliveUnitByEntry(
PlayerbotAI* botAI, uint32 entry);
Unit* GetNearestPlayerInRadius(Player* bot, float radius); Unit* GetNearestPlayerInRadius(Player* bot, float radius);
#endif #endif

View File

@@ -6,9 +6,10 @@
#include "RaidMcStrategy.h" #include "RaidMcStrategy.h"
#include "RaidBwlStrategy.h" #include "RaidBwlStrategy.h"
#include "RaidKarazhanStrategy.h" #include "RaidKarazhanStrategy.h"
#include "RaidMagtheridonStrategy.h"
#include "RaidGruulsLairStrategy.h" #include "RaidGruulsLairStrategy.h"
#include "RaidMagtheridonStrategy.h"
#include "RaidSSCStrategy.h" #include "RaidSSCStrategy.h"
#include "RaidTempestKeepStrategy.h"
#include "RaidOsStrategy.h" #include "RaidOsStrategy.h"
#include "RaidEoEStrategy.h" #include "RaidEoEStrategy.h"
#include "RaidVoAStrategy.h" #include "RaidVoAStrategy.h"
@@ -25,9 +26,10 @@ public:
creators["moltencore"] = &RaidStrategyContext::moltencore; creators["moltencore"] = &RaidStrategyContext::moltencore;
creators["bwl"] = &RaidStrategyContext::bwl; creators["bwl"] = &RaidStrategyContext::bwl;
creators["karazhan"] = &RaidStrategyContext::karazhan; creators["karazhan"] = &RaidStrategyContext::karazhan;
creators["magtheridon"] = &RaidStrategyContext::magtheridon;
creators["gruulslair"] = &RaidStrategyContext::gruulslair; creators["gruulslair"] = &RaidStrategyContext::gruulslair;
creators["magtheridon"] = &RaidStrategyContext::magtheridon;
creators["ssc"] = &RaidStrategyContext::ssc; creators["ssc"] = &RaidStrategyContext::ssc;
creators["tempestkeep"] = &RaidStrategyContext::tempestkeep;
creators["wotlk-os"] = &RaidStrategyContext::wotlk_os; creators["wotlk-os"] = &RaidStrategyContext::wotlk_os;
creators["wotlk-eoe"] = &RaidStrategyContext::wotlk_eoe; creators["wotlk-eoe"] = &RaidStrategyContext::wotlk_eoe;
creators["voa"] = &RaidStrategyContext::voa; creators["voa"] = &RaidStrategyContext::voa;
@@ -41,9 +43,10 @@ private:
static Strategy* moltencore(PlayerbotAI* botAI) { return new RaidMcStrategy(botAI); } static Strategy* moltencore(PlayerbotAI* botAI) { return new RaidMcStrategy(botAI); }
static Strategy* bwl(PlayerbotAI* botAI) { return new RaidBwlStrategy(botAI); } static Strategy* bwl(PlayerbotAI* botAI) { return new RaidBwlStrategy(botAI); }
static Strategy* karazhan(PlayerbotAI* botAI) { return new RaidKarazhanStrategy(botAI); } static Strategy* karazhan(PlayerbotAI* botAI) { return new RaidKarazhanStrategy(botAI); }
static Strategy* magtheridon(PlayerbotAI* botAI) { return new RaidMagtheridonStrategy(botAI); }
static Strategy* gruulslair(PlayerbotAI* botAI) { return new RaidGruulsLairStrategy(botAI); } static Strategy* gruulslair(PlayerbotAI* botAI) { return new RaidGruulsLairStrategy(botAI); }
static Strategy* magtheridon(PlayerbotAI* botAI) { return new RaidMagtheridonStrategy(botAI); }
static Strategy* ssc(PlayerbotAI* botAI) { return new RaidSSCStrategy(botAI); } static Strategy* ssc(PlayerbotAI* botAI) { return new RaidSSCStrategy(botAI); }
static Strategy* tempestkeep(PlayerbotAI* botAI) { return new RaidTempestKeepStrategy(botAI); }
static Strategy* wotlk_os(PlayerbotAI* botAI) { return new RaidOsStrategy(botAI); } static Strategy* wotlk_os(PlayerbotAI* botAI) { return new RaidOsStrategy(botAI); }
static Strategy* wotlk_eoe(PlayerbotAI* botAI) { return new RaidEoEStrategy(botAI); } static Strategy* wotlk_eoe(PlayerbotAI* botAI) { return new RaidEoEStrategy(botAI); }
static Strategy* voa(PlayerbotAI* botAI) { return new RaidVoAStrategy(botAI); } static Strategy* voa(PlayerbotAI* botAI) { return new RaidVoAStrategy(botAI); }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,413 @@
#ifndef _PLAYERBOT_RAIDTEMPESTKEEPACTIONS_H
#define _PLAYERBOT_RAIDTEMPESTKEEPACTIONS_H
#include "RaidTempestKeepHelpers.h"
#include "RaidTempestKeepKaelthasBossAI.h"
#include "Action.h"
#include "AttackAction.h"
#include "MovementActions.h"
using namespace TempestKeepHelpers;
// Trash
class CrimsonHandCenturionCastPolymorphAction : public Action
{
public:
CrimsonHandCenturionCastPolymorphAction(
PlayerbotAI* botAI, std::string const name = "crimson hand centurion cast polymorph") : Action(botAI, name) {}
bool Execute(Event event) override;
};
// Al'ar <Phoenix God>
class AlarMisdirectBossToMainTankAction : public AttackAction
{
public:
AlarMisdirectBossToMainTankAction(
PlayerbotAI* botAI, std::string const name = "al'ar misdirect boss to main tank") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class AlarBossTanksMoveBetweenPlatformsAction : public AttackAction
{
public:
AlarBossTanksMoveBetweenPlatformsAction(
PlayerbotAI* botAI, std::string const name = "al'ar boss tanks move between platforms") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
private:
bool PositionMainTank(Unit* alar, int8 locationIndex);
bool PositionAssistTank(Unit* alar, int8 locationIndex);
};
class AlarMeleeDpsMoveBetweenPlatformsAction : public AttackAction
{
public:
AlarMeleeDpsMoveBetweenPlatformsAction(
PlayerbotAI* botAI, std::string const name = "al'ar melee dps move between platforms") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class AlarRangedAndEmberTankMoveUnderPlatformsAction : public AttackAction
{
public:
AlarRangedAndEmberTankMoveUnderPlatformsAction(
PlayerbotAI* botAI, std::string const name = "al'ar ranged and ember tank move under platforms") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class AlarAssistTanksPickUpEmbersAction : public AttackAction
{
public:
AlarAssistTanksPickUpEmbersAction(
PlayerbotAI* botAI, std::string const name = "al'ar assist tanks pick up embers") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
private:
bool HandlePhase1Embers(Unit* alar);
bool HandlePhase2Embers(Unit* alar);
};
class AlarRangedDpsPrioritizeEmbersAction : public AttackAction
{
public:
AlarRangedDpsPrioritizeEmbersAction(
PlayerbotAI* botAI, std::string const name = "al'ar ranged dps prioritize embers") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class AlarJumpFromPlatformAction : public MovementAction
{
public:
AlarJumpFromPlatformAction(
PlayerbotAI* botAI, std::string const name = "al'ar jump from platform") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class AlarMoveAwayFromRebirthAction : public MovementAction
{
public:
AlarMoveAwayFromRebirthAction(
PlayerbotAI* botAI, std::string const name = "al'ar move away from rebirth") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class AlarSwapTanksOnBossAction : public AttackAction
{
public:
AlarSwapTanksOnBossAction(
PlayerbotAI* botAI, std::string const name = "al'ar swap tanks on boss") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class AlarAvoidFlamePatchesAndDiveBombsAction : public MovementAction
{
public:
AlarAvoidFlamePatchesAndDiveBombsAction(
PlayerbotAI* botAI, std::string const name = "al'ar avoid flame patches and dive bombs") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
private:
bool AvoidFlamePatch();
bool HandleDiveBomb(Unit* alar);
};
class AlarReturnToRoomCenterAction : public MovementAction
{
public:
AlarReturnToRoomCenterAction(
PlayerbotAI* botAI, std::string const name = "al'ar return to room center") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class AlarManagePhaseTrackerAction : public Action
{
public:
AlarManagePhaseTrackerAction(
PlayerbotAI* botAI, std::string const name = "al'ar manage phase tracker") : Action(botAI, name) {}
bool Execute(Event event) override;
};
// Void Reaver
class VoidReaverTanksPositionBossAction : public AttackAction
{
public:
VoidReaverTanksPositionBossAction(
PlayerbotAI* botAI, std::string const name = "void reaver tanks position boss") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class VoidReaverUseAggroDumpAbilityAction : public Action
{
public:
VoidReaverUseAggroDumpAbilityAction(
PlayerbotAI* botAI, std::string const name = "void reaver use aggro dump ability") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class VoidReaverSpreadRangedAction : public MovementAction
{
public:
VoidReaverSpreadRangedAction(
PlayerbotAI* botAI, std::string const name = "void reaver spread ranged") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
private:
int GetHealerIndex(Group* group, int& healerCount);
int GetRangedDpsIndex(Group* group, int& rangedDpsCount);
};
class VoidReaverAvoidArcaneOrbAction : public MovementAction
{
public:
VoidReaverAvoidArcaneOrbAction(
PlayerbotAI* botAI, std::string const name = "void reaver avoid arcane orb") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class VoidReaverEraseTrackersAction : public Action
{
public:
VoidReaverEraseTrackersAction(
PlayerbotAI* botAI, std::string const name = "void reaver erase trackers") : Action(botAI, name) {}
bool Execute(Event event) override;
};
// High Astromancer Solarian
class HighAstromancerSolarianRangedLeaveSpaceForMeleeAction : public MovementAction
{
public:
HighAstromancerSolarianRangedLeaveSpaceForMeleeAction(
PlayerbotAI* botAI, std::string const name = "high astromancer solarian ranged leave space for melee") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class HighAstromancerSolarianMoveAwayFromGroupAction : public MovementAction
{
public:
HighAstromancerSolarianMoveAwayFromGroupAction(
PlayerbotAI* botAI, std::string const name = "high astromancer solarian move away from group") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class HighAstromancerSolarianStackForAoeAction : public MovementAction
{
public:
HighAstromancerSolarianStackForAoeAction(
PlayerbotAI* botAI, std::string const name = "high astromancer solarian stack for aoe") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class HighAstromancerSolarianTargetSolariumPriestsAction : public AttackAction
{
public:
HighAstromancerSolarianTargetSolariumPriestsAction(
PlayerbotAI* botAI, std::string const name = "high astromancer solarian target solarium priests") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
private:
std::pair<Unit*, Unit*> GetSolariumPriests(PlayerbotAI* botAI);
std::vector<Player*> GetMeleeBots(Group* group);
Unit* AssignSolariumPriestsToBots(const std::pair<Unit*, Unit*>& priestsPair, const std::vector<Player*>& meleeMembers);
};
class HighAstromancerSolarianCastFearWardOnMainTankAction : public Action
{
public:
HighAstromancerSolarianCastFearWardOnMainTankAction(
PlayerbotAI* botAI, std::string const name = "high astromancer solarian cast fear ward on main tank") : Action(botAI, name) {}
bool Execute(Event event) override;
};
// Kael'thas Sunstrider <Lord of the Blood Elves>
class KaelthasSunstriderKiteThaladredAction : public MovementAction
{
public:
KaelthasSunstriderKiteThaladredAction(
PlayerbotAI* botAI) : MovementAction(botAI, "kael'thas sunstrider kite thaladred") {}
bool Execute(Event event) override;
};
class KaelthasSunstriderMisdirectAdvisorsToTanksAction : public AttackAction
{
public:
KaelthasSunstriderMisdirectAdvisorsToTanksAction(
PlayerbotAI* botAI, std::string const name = "kael'thas sunstrider misdirect advisors to tanks") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class KaelthasSunstriderMainTankPositionSanguinarAction : public AttackAction
{
public:
KaelthasSunstriderMainTankPositionSanguinarAction(
PlayerbotAI* botAI, std::string const name = "kael'thas sunstrider main tank position sanguinar") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class KaelthasSunstriderCastFearWardOnSanguinarTankAction : public Action
{
public:
KaelthasSunstriderCastFearWardOnSanguinarTankAction(
PlayerbotAI* botAI, std::string const name = "kael'thas sunstrider cast fear ward on sanguinar tank") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class KaelthasSunstriderWarlockTankPositionCapernianAction : public AttackAction
{
public:
KaelthasSunstriderWarlockTankPositionCapernianAction(
PlayerbotAI* botAI, std::string const name = "kael'thas sunstrider warlock tank position capernian") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class KaelthasSunstriderSpreadAndMoveAwayFromCapernianAction : public MovementAction
{
public:
KaelthasSunstriderSpreadAndMoveAwayFromCapernianAction(
PlayerbotAI* botAI, std::string const name = "kael'thas sunstrider spread and move away from capernian") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
private:
bool RangedBotsDisperse(boss_kaelthas* kaelAI, Unit* capernian);
bool MeleeStayBackFromCapernian(Unit* capernian);
};
class KaelthasSunstriderFirstAssistTankPositionTelonicusAction : public AttackAction
{
public:
KaelthasSunstriderFirstAssistTankPositionTelonicusAction(
PlayerbotAI* botAI, std::string const name = "kael'thas sunstrider first assist tank position telonicus") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class KaelthasSunstriderHandleAdvisorRolesInPhase3Action : public MovementAction
{
public:
KaelthasSunstriderHandleAdvisorRolesInPhase3Action(
PlayerbotAI* botAI, std::string const name = "kael'thas sunstrider handle advisor roles in phase 3") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class KaelthasSunstriderAssignAdvisorDpsPriorityAction : public AttackAction
{
public:
KaelthasSunstriderAssignAdvisorDpsPriorityAction(
PlayerbotAI* botAI, std::string const name = "kael'thas sunstrider assign advisor dps priority") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class KaelthasSunstriderManageAdvisorDpsTimerAction : public Action
{
public:
KaelthasSunstriderManageAdvisorDpsTimerAction(
PlayerbotAI* botAI, std::string const name = "kael'thas sunstrider manage advisor dps timer") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class KaelthasSunstriderAssignLegendaryWeaponDpsPriorityAction : public AttackAction
{
public:
KaelthasSunstriderAssignLegendaryWeaponDpsPriorityAction(
PlayerbotAI* botAI, std::string const name = "kael'thas sunstrider assign legendary weapon dps priority") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class KaelthasSunstriderMoveDevastationAwayAction : public AttackAction
{
public:
KaelthasSunstriderMoveDevastationAwayAction(
PlayerbotAI* botAI, std::string const name = "kael'thas sunstrider move devastation away") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class KaelthasSunstriderLootLegendaryWeaponsAction : public MovementAction
{
public:
KaelthasSunstriderLootLegendaryWeaponsAction(
PlayerbotAI* botAI) : MovementAction(botAI, "kael'thas sunstrider loot legendary weapons") {}
bool Execute(Event event) override;
private:
bool ShouldBotLootWeapon(uint32 weaponEntry);
bool LootWeapon(uint32 weaponEntry, uint32 itemId);
};
class KaelthasSunstriderUseLegendaryWeaponsAction : public Action
{
public:
KaelthasSunstriderUseLegendaryWeaponsAction(
PlayerbotAI* botAI) : Action(botAI, "kael'thas sunstrider use legendary weapons") {}
bool Execute(Event event) override;
private:
bool UsePhaseshiftBulwark();
bool UseStaffOfDisintegration();
bool UseNetherstrandLongbow();
bool UseEquippedItemWithPacket(Item* item);
};
class KaelthasSunstriderReequipGearAction : public Action
{
public:
KaelthasSunstriderReequipGearAction(
PlayerbotAI* botAI) : Action(botAI, "kael'thas sunstrider reequip gear") {}
bool Execute(Event event) override;
};
class KaelthasSunstriderMainTankPositionBossAction : public AttackAction
{
public:
KaelthasSunstriderMainTankPositionBossAction(
PlayerbotAI* botAI, std::string const name = "kael'thas sunstrider main tank position boss") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class KaelthasSunstriderAvoidFlameStrikeAction : public MovementAction
{
public:
KaelthasSunstriderAvoidFlameStrikeAction(
PlayerbotAI* botAI, std::string const name = "kael'thas sunstrider avoid flame strike") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class KaelthasSunstriderHandlePhoenixesAndEggsAction : public AttackAction
{
public:
KaelthasSunstriderHandlePhoenixesAndEggsAction(
PlayerbotAI* botAI, std::string const name = "kael'thas sunstrider handle phoenixes and eggs") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
private:
bool AssistTanksPickUpPhoenixes();
bool NonTanksDestroyEggsAndAvoidPhoenixes();
};
class KaelthasSunstriderBreakMindControlAction : public AttackAction
{
public:
KaelthasSunstriderBreakMindControlAction(
PlayerbotAI* botAI, std::string const name = "kael'thas sunstrider break mind control") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class KaelthasSunstriderBreakThroughShockBarrierAction : public AttackAction
{
public:
KaelthasSunstriderBreakThroughShockBarrierAction(
PlayerbotAI* botAI, std::string const name = "kael'thas sunstrider break through shock barrier") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class KaelthasSunstriderSpreadOutInMidairAction : public MovementAction
{
public:
KaelthasSunstriderSpreadOutInMidairAction(
PlayerbotAI* botAI, std::string const name = "kael'thas sunstrider spread out in midair") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
#endif

View File

@@ -0,0 +1,422 @@
#include "RaidTempestKeepMultipliers.h"
#include "RaidTempestKeepActions.h"
#include "RaidTempestKeepHelpers.h"
#include "RaidTempestKeepKaelthasBossAI.h"
#include "ChooseTargetActions.h"
#include "DKActions.h"
#include "DruidActions.h"
#include "DruidBearActions.h"
#include "EquipAction.h"
#include "FollowActions.h"
#include "HunterActions.h"
#include "MageActions.h"
#include "PaladinActions.h"
#include "Playerbots.h"
#include "RogueActions.h"
#include "ShamanActions.h"
#include "WarlockActions.h"
#include "WarriorActions.h"
// Al'ar <Phoenix God>
float AlarMoveBetweenPlatformsMultiplier::GetValue(Action* action)
{
Unit* alar = AI_VALUE2(Unit*, "find target", "al'ar");
if (!alar)
return 1.0f;
if (isAlarInPhase2[alar->GetMap()->GetInstanceId()])
return 1.0f;
if (dynamic_cast<ReachTargetAction*>(action) ||
dynamic_cast<TankFaceAction*>(action) ||
dynamic_cast<CastKillingSpreeAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action))
return 0.0f;
if (botAI->IsDps(bot) &&
dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
return 1.0f;
}
float AlarDisableDisperseMultiplier::GetValue(Action* action)
{
if (!AI_VALUE2(Unit*, "find target", "al'ar"))
return 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action) &&
!dynamic_cast<TankFaceAction*>(action) &&
!dynamic_cast<SetBehindTargetAction*>(action))
return 0.0f;
if (dynamic_cast<FollowAction*>(action) ||
dynamic_cast<FleeAction*>(action))
return 0.0f;
return 1.0f;
}
float AlarDisableTankAssistMultiplier::GetValue(Action* action)
{
if (bot->GetVictim() == nullptr)
return 1.0f;
if (!botAI->IsTank(bot))
return 1.0f;
if (!AI_VALUE2(Unit*, "find target", "al'ar"))
return 1.0f;
if (dynamic_cast<TankAssistAction*>(action))
return 0.0f;
return 1.0f;
}
float AlarStayAwayFromRebirthMultiplier::GetValue(Action* action)
{
Unit* alar = AI_VALUE2(Unit*, "find target", "al'ar");
if (!alar)
return 1.0f;
Creature* alarCreature = alar->ToCreature();
if (!alarCreature || alarCreature->GetReactState() != REACT_PASSIVE)
return 1.0f;
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<AlarMoveAwayFromRebirthAction*>(action) &&
!dynamic_cast<AlarAvoidFlamePatchesAndDiveBombsAction*>(action))
return 0.0f;
return 1.0f;
}
float AlarPhase2NoTankingIfArmorMeltedMultiplier::GetValue(Action* action)
{
if (!bot->HasAura(SPELL_MELT_ARMOR))
return 1.0f;
Unit* alar = AI_VALUE2(Unit*, "find target", "al'ar");
if (!alar || bot->GetTarget() != alar->GetGUID())
return 1.0f;
if (dynamic_cast<CastTauntAction*>(action) ||
dynamic_cast<CastGrowlAction*>(action) ||
dynamic_cast<CastHandOfReckoningAction*>(action) ||
dynamic_cast<CastDarkCommandAction*>(action))
return 0.0f;
return 1.0f;
}
// Void Reaver
float VoidReaverMaintainPositionsMultiplier::GetValue(Action* action)
{
if (!AI_VALUE2(Unit*, "find target", "void reaver"))
return 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action) &&
!dynamic_cast<SetBehindTargetAction*>(action))
return 0.0f;
return 1.0f;
}
// High Astromancer Solarian
float HighAstromancerSolarianMaintainPositionMultiplier::GetValue(Action* action)
{
Unit* astromancer = AI_VALUE2(Unit*, "find target", "high astromancer solarian");
if (!astromancer || astromancer->HasAura(SPELL_SOLARIAN_TRANSFORM))
return 1.0f;
if (botAI->IsRanged(bot) &&
(dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action)))
return 0.0f;
if (!bot->HasAura(SPELL_WRATH_OF_THE_ASTROMANCER))
return 1.0f;
if (dynamic_cast<CastReachTargetSpellAction*>(action) ||
(dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<HighAstromancerSolarianMoveAwayFromGroupAction*>(action)))
return 0.0f;
return 1.0f;
}
float HighAstromancerSolarianDisableTankAssistMultiplier::GetValue(Action* action)
{
if (bot->GetVictim() == nullptr)
return 1.0f;
if (!botAI->IsTank(bot))
return 1.0f;
if (!AI_VALUE2(Unit*, "find target", "solarium priest"))
return 1.0f;
if (dynamic_cast<TankAssistAction*>(action))
return 0.0f;
return 1.0f;
}
// Kael'thas Sunstrider <Lord of the Blood Elves>
float KaelthasSunstriderWaitForDpsMultiplier::GetValue(Action* action)
{
Unit* kaelthas = AI_VALUE2(Unit*, "find target", "kael'thas sunstrider");
if (!kaelthas)
return 1.0f;
boss_kaelthas* kaelAI = dynamic_cast<boss_kaelthas*>(kaelthas->GetAI());
if (!kaelAI || kaelAI->GetPhase() != PHASE_SINGLE_ADVISOR)
return 1.0f;
if (dynamic_cast<KaelthasSunstriderMisdirectAdvisorsToTanksAction*>(action))
return 1.0f;
const time_t now = std::time(nullptr);
constexpr uint8 dpsWaitSeconds = 10;
auto it = advisorDpsWaitTimer.find(kaelthas->GetMap()->GetInstanceId());
if (it == advisorDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds)
{
Unit* sanguinar = AI_VALUE2(Unit*, "find target", "lord sanguinar");
Unit* capernian = AI_VALUE2(Unit*, "find target", "grand astromancer capernian");
Unit* telonicus = AI_VALUE2(Unit*, "find target", "master engineer telonicus");
auto isAdvisorActive = [](Unit* advisor)
{
return advisor && !advisor->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE) &&
!advisor->HasAura(SPELL_PERMANENT_FEIGN_DEATH);
};
if ((isAdvisorActive(sanguinar) && botAI->IsMainTank(bot)) ||
(isAdvisorActive(telonicus) && botAI->IsAssistTankOfIndex(bot, 0, true)) ||
(isAdvisorActive(capernian) && (botAI->IsMainTank(bot) || GetCapernianTank(bot) == bot)))
return 1.0f;
bool shouldHoldDps =
(isAdvisorActive(sanguinar) && !botAI->IsMainTank(bot)) ||
(isAdvisorActive(telonicus) && !botAI->IsAssistTankOfIndex(bot, 0, true)) ||
(isAdvisorActive(capernian) && !botAI->IsMainTank(bot) && GetCapernianTank(bot) != bot);
if (shouldHoldDps &&
(dynamic_cast<AttackAction*>(action) ||
(dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action))))
return 0.0f;
}
return 1.0f;
}
float KaelthasSunstriderKiteThaladredMultiplier::GetValue(Action* action)
{
Unit* kaelthas = AI_VALUE2(Unit*, "find target", "kael'thas sunstrider");
if (!kaelthas)
return 1.0f;
boss_kaelthas* kaelAI = dynamic_cast<boss_kaelthas*>(kaelthas->GetAI());
if (!kaelAI)
return 1.0f;
if (botAI->IsTank(bot) && kaelAI->GetPhase() == PHASE_ALL_ADVISORS)
return 1.0f;
Unit* thaladred = AI_VALUE2(Unit*, "find target", "thaladred the darkener");
if (!thaladred || thaladred->GetVictim() != bot ||
thaladred->HasAura(SPELL_PERMANENT_FEIGN_DEATH))
return 1.0f;
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<KaelthasSunstriderKiteThaladredAction*>(action))
return 0.0f;
return 1.0f;
}
float KaelthasSunstriderControlMisdirectionMultiplier::GetValue(Action* action)
{
if (bot->getClass() != CLASS_HUNTER)
return 1.0f;
Unit* kaelthas = AI_VALUE2(Unit*, "find target", "kael'thas sunstrider");
if (!kaelthas)
return 1.0f;
boss_kaelthas* kaelAI = dynamic_cast<boss_kaelthas*>(kaelthas->GetAI());
if (!kaelAI || kaelAI->GetPhase() == PHASE_FINAL)
return 1.0f;
if (dynamic_cast<CastMisdirectionOnMainTankAction*>(action))
return 0.0f;
return 1.0f;
}
float KaelthasSunstriderKeepDistanceFromCapernianMultiplier::GetValue(Action* action)
{
Unit* kaelthas = AI_VALUE2(Unit*, "find target", "kael'thas sunstrider");
if (!kaelthas)
return 1.0f;
boss_kaelthas* kaelAI = dynamic_cast<boss_kaelthas*>(kaelthas->GetAI());
if (!kaelAI || kaelAI->GetPhase() != PHASE_SINGLE_ADVISOR)
return 1.0f;
Unit* capernian = AI_VALUE2(Unit*, "find target", "grand astromancer capernian");
if (!capernian || capernian->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE) ||
capernian->HasAura(SPELL_PERMANENT_FEIGN_DEATH))
return 1.0f;
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<AttackAction*>(action) &&
!dynamic_cast<KaelthasSunstriderSpreadAndMoveAwayFromCapernianAction*>(action))
return 0.0f;
return 1.0f;
}
float KaelthasSunstriderManageWeaponTankingMultiplier::GetValue(Action* action)
{
if (!botAI->IsTank(bot))
return 1.0f;
Unit* kaelthas = AI_VALUE2(Unit*, "find target", "kael'thas sunstrider");
if (!kaelthas)
return 1.0f;
boss_kaelthas* kaelAI = dynamic_cast<boss_kaelthas*>(kaelthas->GetAI());
if (!kaelAI)
return 1.0f;
if (kaelAI->GetPhase() != PHASE_WEAPONS &&
dynamic_cast<TankFaceAction*>(action))
return 0.0f;
if (!botAI->IsMainTank(bot))
return 1.0f;
// Try to keep main tank from grabbing aggro on any weapon other than the axe
if (kaelAI->GetPhase() == PHASE_WEAPONS &&
(dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<CastTauntAction*>(action) ||
dynamic_cast<CastChallengingShoutAction*>(action) ||
dynamic_cast<CastThunderClapAction*>(action) ||
dynamic_cast<CastShockwaveAction*>(action) ||
dynamic_cast<CastCleaveAction*>(action) ||
dynamic_cast<CastGrowlAction*>(action) ||
dynamic_cast<CastSwipeAction*>(action) ||
dynamic_cast<CastHandOfReckoningAction*>(action) ||
dynamic_cast<CastAvengersShieldAction*>(action) ||
dynamic_cast<CastConsecrationAction*>(action) ||
dynamic_cast<CastDarkCommandAction*>(action) ||
dynamic_cast<CastDeathAndDecayAction*>(action) ||
dynamic_cast<CastPestilenceAction*>(action) ||
dynamic_cast<CastBloodBoilAction*>(action)))
return 0.0f;
return 1.0f;
}
float KaelthasSunstriderDisableAdvisorTankAssistMultiplier::GetValue(Action* action)
{
if (bot->GetVictim() == nullptr || !botAI->IsTank(bot))
return 1.0f;
Unit* kaelthas = AI_VALUE2(Unit*, "find target", "kael'thas sunstrider");
if (!kaelthas)
return 1.0f;
boss_kaelthas* kaelAI = dynamic_cast<boss_kaelthas*>(kaelthas->GetAI());
if (!kaelAI)
return 1.0f;
if (kaelAI->GetPhase() != PHASE_SINGLE_ADVISOR &&
kaelAI->GetPhase() != PHASE_ALL_ADVISORS)
return 1.0f;
if (dynamic_cast<TankAssistAction*>(action))
return 0.0f;
return 1.0f;
}
float KaelthasSunstriderDisableDisperseMultiplier::GetValue(Action* action)
{
if (!AI_VALUE2(Unit*, "find target", "kael'thas sunstrider"))
return 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action) &&
!dynamic_cast<TankFaceAction*>(action) &&
!dynamic_cast<SetBehindTargetAction*>(action))
return 0.0f;
return 1.0f;
}
// Bloodlust/Heroism and other major cooldowns should be used at the start of Phase 3
float KaelthasSunstriderDelayCooldownsMultiplier::GetValue(Action* action)
{
Unit* kaelthas = AI_VALUE2(Unit*, "find target", "kael'thas sunstrider");
if (!kaelthas)
return 1.0f;
boss_kaelthas* kaelAI = dynamic_cast<boss_kaelthas*>(kaelthas->GetAI());
if (!kaelAI || kaelAI->GetPhase() == PHASE_ALL_ADVISORS ||
kaelAI->GetPhase() == PHASE_FINAL)
return 1.0f;
if (bot->getClass() == CLASS_SHAMAN &&
(dynamic_cast<CastBloodlustAction*>(action) ||
dynamic_cast<CastHeroismAction*>(action)))
return 0.0f;
if (botAI->IsDps(bot) &&
(dynamic_cast<CastMetamorphosisAction*>(action) ||
dynamic_cast<CastAdrenalineRushAction*>(action) ||
dynamic_cast<CastBladeFlurryAction*>(action) ||
dynamic_cast<CastIcyVeinsAction*>(action) ||
dynamic_cast<CastColdSnapAction*>(action) ||
dynamic_cast<CastArcanePowerAction*>(action) ||
dynamic_cast<CastPresenceOfMindAction*>(action) ||
dynamic_cast<CastCombustionAction*>(action) ||
dynamic_cast<CastRapidFireAction*>(action) ||
dynamic_cast<CastReadinessAction*>(action) ||
dynamic_cast<CastAvengingWrathAction*>(action) ||
dynamic_cast<CastElementalMasteryAction*>(action) ||
dynamic_cast<CastFeralSpiritAction*>(action) ||
dynamic_cast<CastFireElementalTotemAction*>(action) ||
dynamic_cast<CastFireElementalTotemMeleeAction*>(action) ||
dynamic_cast<CastForceOfNatureAction*>(action) ||
dynamic_cast<CastArmyOfTheDeadAction*>(action) ||
dynamic_cast<CastSummonGargoyleAction*>(action) ||
dynamic_cast<CastBerserkingAction*>(action) ||
dynamic_cast<CastBloodFuryAction*>(action) ||
dynamic_cast<UseTrinketAction*>(action)))
return 0.0f;
return 1.0f;
}
float KaelthasSunstriderStaySpreadDuringGravityLapseMultiplier::GetValue(Action* action)
{
if (!bot->HasAura(SPELL_GRAVITY_LAPSE))
return 1.0f;
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<KaelthasSunstriderSpreadOutInMidairAction*>(action))
return 0.0f;
return 1.0f;
}

View File

@@ -0,0 +1,150 @@
#ifndef _PLAYERBOT_RAIDTEMPESTKEEPMULTIPLIERS_H
#define _PLAYERBOT_RAIDTEMPESTKEEPMULTIPLIERS_H
#include "Multiplier.h"
// Al'ar <Phoenix God>
class AlarMoveBetweenPlatformsMultiplier : public Multiplier
{
public:
AlarMoveBetweenPlatformsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "al'ar move between platforms multiplier") {}
virtual float GetValue(Action* action);
};
class AlarDisableDisperseMultiplier : public Multiplier
{
public:
AlarDisableDisperseMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "al'ar disable disperse multiplier") {}
virtual float GetValue(Action* action);
};
class AlarDisableTankAssistMultiplier : public Multiplier
{
public:
AlarDisableTankAssistMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "al'ar disable tank assist multiplier") {}
virtual float GetValue(Action* action);
};
class AlarStayAwayFromRebirthMultiplier : public Multiplier
{
public:
AlarStayAwayFromRebirthMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "al'ar stay away from rebirth multiplier") {}
virtual float GetValue(Action* action);
};
class AlarPhase2NoTankingIfArmorMeltedMultiplier : public Multiplier
{
public:
AlarPhase2NoTankingIfArmorMeltedMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "al'ar phase 2 no tanking if armor melted multiplier") {}
virtual float GetValue(Action* action);
};
// Void Reaver
class VoidReaverMaintainPositionsMultiplier : public Multiplier
{
public:
VoidReaverMaintainPositionsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "void reaver maintain positions multiplier") {}
virtual float GetValue(Action* action);
};
// High Astromancer Solarian
class HighAstromancerSolarianDisableTankAssistMultiplier : public Multiplier
{
public:
HighAstromancerSolarianDisableTankAssistMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "high astromancer solarian disable tank assist multiplier") {}
virtual float GetValue(Action* action);
};
class HighAstromancerSolarianMaintainPositionMultiplier : public Multiplier
{
public:
HighAstromancerSolarianMaintainPositionMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "high astromancer solarian maintain position multiplier") {}
virtual float GetValue(Action* action);
};
// Kael'thas Sunstrider <Lord of the Blood Elves>
class KaelthasSunstriderWaitForDpsMultiplier : public Multiplier
{
public:
KaelthasSunstriderWaitForDpsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "kael'thas sunstrider wait for dps multiplier") {}
virtual float GetValue(Action* action);
};
class KaelthasSunstriderKiteThaladredMultiplier : public Multiplier
{
public:
KaelthasSunstriderKiteThaladredMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "kael'thas sunstrider kite thaladred multiplier") {}
virtual float GetValue(Action* action);
};
class KaelthasSunstriderControlMisdirectionMultiplier : public Multiplier
{
public:
KaelthasSunstriderControlMisdirectionMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "kael'thas sunstrider control misdirection multiplier") {}
virtual float GetValue(Action* action);
};
class KaelthasSunstriderKeepDistanceFromCapernianMultiplier : public Multiplier
{
public:
KaelthasSunstriderKeepDistanceFromCapernianMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "kael'thas sunstrider keep distance from capernian multiplier") {}
virtual float GetValue(Action* action);
};
class KaelthasSunstriderManageWeaponTankingMultiplier : public Multiplier
{
public:
KaelthasSunstriderManageWeaponTankingMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "kael'thas sunstrider manage weapon tanking multiplier") {}
virtual float GetValue(Action* action);
};
class KaelthasSunstriderDisableAdvisorTankAssistMultiplier : public Multiplier
{
public:
KaelthasSunstriderDisableAdvisorTankAssistMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "kael'thas sunstrider disable advisor tank assist multiplier") {}
virtual float GetValue(Action* action);
};
class KaelthasSunstriderDisableDisperseMultiplier : public Multiplier
{
public:
KaelthasSunstriderDisableDisperseMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "kael'thas sunstrider disable disperse multiplier") {}
virtual float GetValue(Action* action);
};
class KaelthasSunstriderDelayCooldownsMultiplier : public Multiplier
{
public:
KaelthasSunstriderDelayCooldownsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "kael'thas sunstrider delay cooldowns multiplier") {}
virtual float GetValue(Action* action);
};
class KaelthasSunstriderStaySpreadDuringGravityLapseMultiplier : public Multiplier
{
public:
KaelthasSunstriderStaySpreadDuringGravityLapseMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "kael'thas sunstrider stay spread during gravity lapse multiplier") {}
virtual float GetValue(Action* action);
};
#endif

View File

@@ -0,0 +1,289 @@
#ifndef _PLAYERBOT_RAIDTEMPESTKEEPACTIONCONTEXT_H
#define _PLAYERBOT_RAIDTEMPESTKEEPACTIONCONTEXT_H
#include "RaidTempestKeepActions.h"
#include "NamedObjectContext.h"
class RaidTempestKeepActionContext : public NamedObjectContext<Action>
{
public:
RaidTempestKeepActionContext()
{
// Trash
creators["crimson hand centurion cast polymorph"] =
&RaidTempestKeepActionContext::crimson_hand_centurion_cast_polymorph;
// Al'ar <Phoenix God>
creators["al'ar misdirect boss to main tank"] =
&RaidTempestKeepActionContext::alar_misdirect_boss_to_main_tank;
creators["al'ar boss tanks move between platforms"] =
&RaidTempestKeepActionContext::alar_boss_tanks_move_between_platforms;
creators["al'ar melee dps move between platforms"] =
&RaidTempestKeepActionContext::alar_melee_dps_move_between_platforms;
creators["al'ar ranged and ember tank move under platforms"] =
&RaidTempestKeepActionContext::alar_ranged_and_ember_tank_move_under_platforms;
creators["al'ar assist tanks pick up embers"] =
&RaidTempestKeepActionContext::alar_assist_tanks_pick_up_embers;
creators["al'ar ranged dps prioritize embers"] =
&RaidTempestKeepActionContext::alar_ranged_dps_prioritize_embers;
creators["al'ar jump from platform"] =
&RaidTempestKeepActionContext::alar_jump_from_platform;
creators["al'ar move away from rebirth"] =
&RaidTempestKeepActionContext::alar_move_away_from_rebirth;
creators["al'ar swap tanks on boss"] =
&RaidTempestKeepActionContext::alar_swap_tanks_on_boss;
creators["al'ar avoid flame patches and dive bombs"] =
&RaidTempestKeepActionContext::alar_avoid_flame_patches_and_dive_bombs;
creators["al'ar return to room center"] =
&RaidTempestKeepActionContext::alar_return_to_room_center;
creators["al'ar manage phase tracker"] =
&RaidTempestKeepActionContext::alar_manage_phase_tracker;
// Void Reaver
creators["void reaver tanks position boss"] =
&RaidTempestKeepActionContext::void_reaver_tanks_position_boss;
creators["void reaver use aggro dump ability"] =
&RaidTempestKeepActionContext::void_reaver_use_aggro_dump_ability;
creators["void reaver spread ranged"] =
&RaidTempestKeepActionContext::void_reaver_spread_ranged;
creators["void reaver avoid arcane orb"] =
&RaidTempestKeepActionContext::void_reaver_avoid_arcane_orb;
creators["void reaver erase trackers"] =
&RaidTempestKeepActionContext::void_reaver_erase_trackers;
// High Astromancer Solarian
creators["high astromancer solarian ranged leave space for melee"] =
&RaidTempestKeepActionContext::high_astromancer_solarian_ranged_leave_space_for_melee;
creators["high astromancer solarian move away from group"] =
&RaidTempestKeepActionContext::high_astromancer_solarian_move_away_from_group;
creators["high astromancer solarian stack for aoe"] =
&RaidTempestKeepActionContext::high_astromancer_solarian_stack_for_aoe;
creators["high astromancer solarian target solarium priests"] =
&RaidTempestKeepActionContext::high_astromancer_solarian_target_solarium_priests;
creators["high astromancer solarian cast fear ward on main tank"] =
&RaidTempestKeepActionContext::high_astromancer_solarian_cast_fear_ward_on_main_tank;
// Kael'thas Sunstrider <Lord of the Blood Elves>
creators["kael'thas sunstrider kite thaladred"] =
&RaidTempestKeepActionContext::kaelthas_sunstrider_kite_thaladred;
creators["kael'thas sunstrider misdirect advisors to tanks"] =
&RaidTempestKeepActionContext::kaelthas_sunstrider_misdirect_advisors_to_tanks;
creators["kael'thas sunstrider main tank position sanguinar"] =
&RaidTempestKeepActionContext::kaelthas_sunstrider_main_tank_position_sanguinar;
creators["kael'thas sunstrider cast fear ward on sanguinar tank"] =
&RaidTempestKeepActionContext::kaelthas_sunstrider_cast_fear_ward_on_sanguinar_tank;
creators["kael'thas sunstrider warlock tank position capernian"] =
&RaidTempestKeepActionContext::kaelthas_sunstrider_warlock_tank_position_capernian;
creators["kael'thas sunstrider spread and move away from capernian"] =
&RaidTempestKeepActionContext::kaelthas_sunstrider_spread_and_move_away_from_capernian;
creators["kael'thas sunstrider first assist tank position telonicus"] =
&RaidTempestKeepActionContext::kaelthas_sunstrider_first_assist_tank_position_telonicus;
creators["kael'thas sunstrider handle advisor roles in phase 3"] =
&RaidTempestKeepActionContext::kaelthas_sunstrider_handle_advisor_roles_in_phase_3;
creators["kael'thas sunstrider assign advisor dps priority"] =
&RaidTempestKeepActionContext::kaelthas_sunstrider_assign_advisor_dps_priority;
creators["kael'thas sunstrider manage advisor dps timer"] =
&RaidTempestKeepActionContext::kaelthas_sunstrider_manage_advisor_dps_timer;
creators["kael'thas sunstrider assign legendary weapon dps priority"] =
&RaidTempestKeepActionContext::kaelthas_sunstrider_assign_legendary_weapon_dps_priority;
creators["kael'thas sunstrider main tank move devastation away"] =
&RaidTempestKeepActionContext::kaelthas_sunstrider_main_tank_move_devastation_away;
creators["kael'thas sunstrider loot legendary weapons"] =
&RaidTempestKeepActionContext::kaelthas_sunstrider_loot_legendary_weapons;
creators["kael'thas sunstrider use legendary weapons"] =
&RaidTempestKeepActionContext::kaelthas_sunstrider_use_legendary_weapons;
creators["kael'thas sunstrider reequip gear"] =
&RaidTempestKeepActionContext::kaelthas_sunstrider_reequip_gear;
creators["kael'thas sunstrider main tank position boss"] =
&RaidTempestKeepActionContext::kaelthas_sunstrider_main_tank_position_boss;
creators["kael'thas sunstrider avoid flame strike"] =
&RaidTempestKeepActionContext::kaelthas_sunstrider_avoid_flame_strike;
creators["kael'thas sunstrider handle phoenixes and eggs"] =
&RaidTempestKeepActionContext::kaelthas_sunstrider_handle_phoenixes_and_eggs;
creators["kael'thas sunstrider break mind control"] =
&RaidTempestKeepActionContext::kaelthas_sunstrider_break_mind_control;
creators["kael'thas sunstrider break through shock barrier"] =
&RaidTempestKeepActionContext::kaelthas_sunstrider_break_through_shock_barrier;
creators["kael'thas sunstrider spread out in midair"] =
&RaidTempestKeepActionContext::kaelthas_sunstrider_spread_out_in_midair;
}
private:
// Trash
static Action* crimson_hand_centurion_cast_polymorph(
PlayerbotAI* botAI) { return new CrimsonHandCenturionCastPolymorphAction(botAI); }
// Al'ar <Phoenix God>
static Action* alar_misdirect_boss_to_main_tank(
PlayerbotAI* botAI) { return new AlarMisdirectBossToMainTankAction(botAI); }
static Action* alar_boss_tanks_move_between_platforms(
PlayerbotAI* botAI) { return new AlarBossTanksMoveBetweenPlatformsAction(botAI); }
static Action* alar_melee_dps_move_between_platforms(
PlayerbotAI* botAI) { return new AlarMeleeDpsMoveBetweenPlatformsAction(botAI); }
static Action* alar_ranged_and_ember_tank_move_under_platforms(
PlayerbotAI* botAI) { return new AlarRangedAndEmberTankMoveUnderPlatformsAction(botAI); }
static Action* alar_assist_tanks_pick_up_embers(
PlayerbotAI* botAI) { return new AlarAssistTanksPickUpEmbersAction(botAI); }
static Action* alar_ranged_dps_prioritize_embers(
PlayerbotAI* botAI) { return new AlarRangedDpsPrioritizeEmbersAction(botAI); }
static Action* alar_jump_from_platform(
PlayerbotAI* botAI) { return new AlarJumpFromPlatformAction(botAI); }
static Action* alar_move_away_from_rebirth(
PlayerbotAI* botAI) { return new AlarMoveAwayFromRebirthAction(botAI); }
static Action* alar_swap_tanks_on_boss(
PlayerbotAI* botAI) { return new AlarSwapTanksOnBossAction(botAI); }
static Action* alar_avoid_flame_patches_and_dive_bombs(
PlayerbotAI* botAI) { return new AlarAvoidFlamePatchesAndDiveBombsAction(botAI); }
static Action* alar_return_to_room_center(
PlayerbotAI* botAI) { return new AlarReturnToRoomCenterAction(botAI); }
static Action* alar_manage_phase_tracker(
PlayerbotAI* botAI) { return new AlarManagePhaseTrackerAction(botAI); }
// Void Reaver
static Action* void_reaver_tanks_position_boss(
PlayerbotAI* botAI) { return new VoidReaverTanksPositionBossAction(botAI); }
static Action* void_reaver_use_aggro_dump_ability(
PlayerbotAI* botAI) { return new VoidReaverUseAggroDumpAbilityAction(botAI); }
static Action* void_reaver_spread_ranged(
PlayerbotAI* botAI) { return new VoidReaverSpreadRangedAction(botAI); }
static Action* void_reaver_avoid_arcane_orb(
PlayerbotAI* botAI) { return new VoidReaverAvoidArcaneOrbAction(botAI); }
static Action* void_reaver_erase_trackers(
PlayerbotAI* botAI) { return new VoidReaverEraseTrackersAction(botAI); }
// High Astromancer Solarian
static Action* high_astromancer_solarian_ranged_leave_space_for_melee(
PlayerbotAI* botAI) { return new HighAstromancerSolarianRangedLeaveSpaceForMeleeAction(botAI); }
static Action* high_astromancer_solarian_move_away_from_group(
PlayerbotAI* botAI) { return new HighAstromancerSolarianMoveAwayFromGroupAction(botAI); }
static Action* high_astromancer_solarian_stack_for_aoe(
PlayerbotAI* botAI) { return new HighAstromancerSolarianStackForAoeAction(botAI); }
static Action* high_astromancer_solarian_target_solarium_priests(
PlayerbotAI* botAI) { return new HighAstromancerSolarianTargetSolariumPriestsAction(botAI); }
static Action* high_astromancer_solarian_cast_fear_ward_on_main_tank(
PlayerbotAI* botAI) { return new HighAstromancerSolarianCastFearWardOnMainTankAction(botAI); }
// Kael'thas Sunstrider <Lord of the Blood Elves>
static Action* kaelthas_sunstrider_kite_thaladred(
PlayerbotAI* botAI) { return new KaelthasSunstriderKiteThaladredAction(botAI); }
static Action* kaelthas_sunstrider_misdirect_advisors_to_tanks(
PlayerbotAI* botAI) { return new KaelthasSunstriderMisdirectAdvisorsToTanksAction(botAI); }
static Action* kaelthas_sunstrider_main_tank_position_sanguinar(
PlayerbotAI* botAI) { return new KaelthasSunstriderMainTankPositionSanguinarAction(botAI); }
static Action* kaelthas_sunstrider_cast_fear_ward_on_sanguinar_tank(
PlayerbotAI* botAI) { return new KaelthasSunstriderCastFearWardOnSanguinarTankAction(botAI); }
static Action* kaelthas_sunstrider_warlock_tank_position_capernian(
PlayerbotAI* botAI) { return new KaelthasSunstriderWarlockTankPositionCapernianAction(botAI); }
static Action* kaelthas_sunstrider_spread_and_move_away_from_capernian(
PlayerbotAI* botAI) { return new KaelthasSunstriderSpreadAndMoveAwayFromCapernianAction(botAI); }
static Action* kaelthas_sunstrider_first_assist_tank_position_telonicus(
PlayerbotAI* botAI) { return new KaelthasSunstriderFirstAssistTankPositionTelonicusAction(botAI); }
static Action* kaelthas_sunstrider_handle_advisor_roles_in_phase_3(
PlayerbotAI* botAI) { return new KaelthasSunstriderHandleAdvisorRolesInPhase3Action(botAI); }
static Action* kaelthas_sunstrider_assign_advisor_dps_priority(
PlayerbotAI* botAI) { return new KaelthasSunstriderAssignAdvisorDpsPriorityAction(botAI); }
static Action* kaelthas_sunstrider_manage_advisor_dps_timer(
PlayerbotAI* botAI) { return new KaelthasSunstriderManageAdvisorDpsTimerAction(botAI); }
static Action* kaelthas_sunstrider_assign_legendary_weapon_dps_priority(
PlayerbotAI* botAI) { return new KaelthasSunstriderAssignLegendaryWeaponDpsPriorityAction(botAI); }
static Action* kaelthas_sunstrider_main_tank_move_devastation_away(
PlayerbotAI* botAI) { return new KaelthasSunstriderMoveDevastationAwayAction(botAI); }
static Action* kaelthas_sunstrider_loot_legendary_weapons(
PlayerbotAI* botAI) { return new KaelthasSunstriderLootLegendaryWeaponsAction(botAI); }
static Action* kaelthas_sunstrider_use_legendary_weapons(
PlayerbotAI* botAI) { return new KaelthasSunstriderUseLegendaryWeaponsAction(botAI); }
static Action* kaelthas_sunstrider_reequip_gear(
PlayerbotAI* botAI) { return new KaelthasSunstriderReequipGearAction(botAI); }
static Action* kaelthas_sunstrider_main_tank_position_boss(
PlayerbotAI* botAI) { return new KaelthasSunstriderMainTankPositionBossAction(botAI); }
static Action* kaelthas_sunstrider_avoid_flame_strike(
PlayerbotAI* botAI) { return new KaelthasSunstriderAvoidFlameStrikeAction(botAI); }
static Action* kaelthas_sunstrider_handle_phoenixes_and_eggs(
PlayerbotAI* botAI) { return new KaelthasSunstriderHandlePhoenixesAndEggsAction(botAI); }
static Action* kaelthas_sunstrider_break_mind_control(
PlayerbotAI* botAI) { return new KaelthasSunstriderBreakMindControlAction(botAI); }
static Action* kaelthas_sunstrider_break_through_shock_barrier(
PlayerbotAI* botAI) { return new KaelthasSunstriderBreakThroughShockBarrierAction(botAI); }
static Action* kaelthas_sunstrider_spread_out_in_midair(
PlayerbotAI* botAI) { return new KaelthasSunstriderSpreadOutInMidairAction(botAI); }
};
#endif

View File

@@ -0,0 +1,265 @@
#ifndef _PLAYERBOT_RAIDTEMPESTKEEPTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDTEMPESTKEEPTRIGGERCONTEXT_H
#include "RaidTempestKeepTriggers.h"
#include "AiObjectContext.h"
class RaidTempestKeepTriggerContext : public NamedObjectContext<Trigger>
{
public:
RaidTempestKeepTriggerContext()
{
// Trash
creators["crimson hand centurion casts arcane volley"] =
&RaidTempestKeepTriggerContext::crimson_hand_centurion_casts_arcane_volley;
// Al'ar <Phoenix God>
creators["al'ar pulling boss"] =
&RaidTempestKeepTriggerContext::alar_pulling_boss;
creators["al'ar boss is flying between platforms"] =
&RaidTempestKeepTriggerContext::alar_boss_is_flying_between_platforms;
creators["al'ar embers of al'ar explode upon death"] =
&RaidTempestKeepTriggerContext::alar_embers_of_alar_explode_upon_death;
creators["al'ar killing embers of al'ar damages boss"] =
&RaidTempestKeepTriggerContext::alar_killing_embers_of_alar_damages_boss;
creators["al'ar incoming flame quills"] =
&RaidTempestKeepTriggerContext::alar_incoming_flame_quills;
creators["al'ar rising from the ashes"] =
&RaidTempestKeepTriggerContext::alar_rising_from_the_ashes;
creators["al'ar everything is on fire in phase 2"] =
&RaidTempestKeepTriggerContext::alar_everything_is_on_fire_in_phase_2;
creators["al'ar phase 2 encounter is at room center"] =
&RaidTempestKeepTriggerContext::alar_phase_2_encounter_is_at_room_center;
creators["al'ar strategy changes between phases"] =
&RaidTempestKeepTriggerContext::alar_strategy_changes_between_phases;
// Void Reaver
creators["void reaver boss casts pounding"] =
&RaidTempestKeepTriggerContext::void_reaver_boss_casts_pounding;
creators["void reaver knock away reduces tank aggro"] =
&RaidTempestKeepTriggerContext::void_reaver_knock_away_reduces_tank_aggro;
creators["void reaver boss launches arcane orbs"] =
&RaidTempestKeepTriggerContext::void_reaver_boss_launches_arcane_orbs;
creators["void reaver arcane orb is incoming"] =
&RaidTempestKeepTriggerContext::void_reaver_arcane_orb_is_incoming;
creators["void reaver bot is not in combat"] =
&RaidTempestKeepTriggerContext::void_reaver_bot_is_not_in_combat;
// High Astromancer Solarian
creators["high astromancer solarian boss casts wrath of the astromancer"] =
&RaidTempestKeepTriggerContext::high_astromancer_solarian_boss_casts_wrath_of_the_astromancer;
creators["high astromancer solarian bot has wrath of the astromancer"] =
&RaidTempestKeepTriggerContext::high_astromancer_solarian_bot_has_wrath_of_the_astromancer;
creators["high astromancer solarian boss has vanished"] =
&RaidTempestKeepTriggerContext::high_astromancer_solarian_boss_has_vanished;
creators["high astromancer solarian solarium priests spawned"] =
&RaidTempestKeepTriggerContext::high_astromancer_solarian_solarium_priests_spawned;
creators["high astromancer solarian boss casts psychic scream"] =
&RaidTempestKeepTriggerContext::high_astromancer_solarian_boss_casts_psychic_scream;
// Kael'thas Sunstrider <Lord of the Blood Elves>
creators["kael'thas sunstrider thaladred is fixated on bot"] =
&RaidTempestKeepTriggerContext::kaelthas_sunstrider_thaladred_is_fixated_on_bot;
creators["kael'thas sunstrider pulling tankable advisors"] =
&RaidTempestKeepTriggerContext::kaelthas_sunstrider_pulling_tankable_advisors;
creators["kael'thas sunstrider sanguinar engaged by main tank"] =
&RaidTempestKeepTriggerContext::kaelthas_sunstrider_sanguinar_engaged_by_main_tank;
creators["kael'thas sunstrider sanguinar casts bellowing roar"] =
&RaidTempestKeepTriggerContext::kaelthas_sunstrider_sanguinar_casts_bellowing_roar;
creators["kael'thas sunstrider capernian should be tanked by a warlock"] =
&RaidTempestKeepTriggerContext::kaelthas_sunstrider_capernian_should_be_tanked_by_a_warlock;
creators["kael'thas sunstrider capernian casts arcane burst and conflagration"] =
&RaidTempestKeepTriggerContext::kaelthas_sunstrider_capernian_casts_arcane_burst_and_conflagration;
creators["kael'thas sunstrider telonicus engaged by first assist tank"] =
&RaidTempestKeepTriggerContext::kaelthas_sunstrider_telonicus_engaged_by_first_assist_tank;
creators["kael'thas sunstrider bots have specific roles in phase 3"] =
&RaidTempestKeepTriggerContext::kaelthas_sunstrider_bots_have_specific_roles_in_phase_3;
creators["kael'thas sunstrider determining advisor kill order"] =
&RaidTempestKeepTriggerContext::kaelthas_sunstrider_determining_advisor_kill_order;
creators["kael'thas sunstrider waiting for tanks to get aggro on advisors"] =
&RaidTempestKeepTriggerContext::kaelthas_sunstrider_waiting_for_tanks_to_get_aggro_on_advisors;
creators["kael'thas sunstrider legendary weapons are alive"] =
&RaidTempestKeepTriggerContext::kaelthas_sunstrider_legendary_weapons_are_alive;
creators["kael'thas sunstrider legendary axe casts whirlwind"] =
&RaidTempestKeepTriggerContext::kaelthas_sunstrider_legendary_axe_casts_whirlwind;
creators["kael'thas sunstrider legendary weapons are dead and lootable"] =
&RaidTempestKeepTriggerContext::kaelthas_sunstrider_legendary_weapons_are_dead_and_lootable;
creators["kael'thas sunstrider legendary weapons are equipped"] =
&RaidTempestKeepTriggerContext::kaelthas_sunstrider_legendary_weapons_are_equipped;
creators["kael'thas sunstrider legendary weapons were lost"] =
&RaidTempestKeepTriggerContext::kaelthas_sunstrider_legendary_weapons_were_lost;
creators["kael'thas sunstrider boss has entered the fight"] =
&RaidTempestKeepTriggerContext::kaelthas_sunstrider_boss_has_entered_the_fight;
creators["kael'thas sunstrider raid member is mind controlled"] =
&RaidTempestKeepTriggerContext::kaelthas_sunstrider_raid_member_is_mind_controlled;
creators["kael'thas sunstrider phoenixes and eggs are spawning"] =
&RaidTempestKeepTriggerContext::kaelthas_sunstrider_phoenixes_and_eggs_are_spawning;
creators["kael'thas sunstrider boss is casting pyroblast"] =
&RaidTempestKeepTriggerContext::kaelthas_sunstrider_boss_is_casting_pyroblast;
creators["kael'thas sunstrider boss is manipulating gravity"] =
&RaidTempestKeepTriggerContext::kaelthas_sunstrider_boss_is_manipulating_gravity;
}
private:
// Trash
static Trigger* crimson_hand_centurion_casts_arcane_volley(
PlayerbotAI* botAI) { return new CrimsonHandCenturionCastsArcaneVolleyTrigger(botAI); }
// Al'ar <Phoenix God>
static Trigger* alar_pulling_boss(
PlayerbotAI* botAI) { return new AlarPullingBossTrigger(botAI); }
static Trigger* alar_boss_is_flying_between_platforms(
PlayerbotAI* botAI) { return new AlarBossIsFlyingBetweenPlatformsTrigger(botAI); }
static Trigger* alar_embers_of_alar_explode_upon_death(
PlayerbotAI* botAI) { return new AlarEmbersOfAlarExplodeUponDeathTrigger(botAI); }
static Trigger* alar_killing_embers_of_alar_damages_boss(
PlayerbotAI* botAI) { return new AlarKillingEmbersOfAlarDamagesBossTrigger(botAI); }
static Trigger* alar_incoming_flame_quills(
PlayerbotAI* botAI) { return new AlarIncomingFlameQuillsTrigger(botAI); }
static Trigger* alar_rising_from_the_ashes(
PlayerbotAI* botAI) { return new AlarRisingFromTheAshesTrigger(botAI); }
static Trigger* alar_everything_is_on_fire_in_phase_2(
PlayerbotAI* botAI) { return new AlarEverythingIsOnFireInPhase2Trigger(botAI); }
static Trigger* alar_phase_2_encounter_is_at_room_center(
PlayerbotAI* botAI) { return new AlarPhase2EncounterIsAtRoomCenterTrigger(botAI); }
static Trigger* alar_strategy_changes_between_phases(
PlayerbotAI* botAI) { return new AlarStrategyChangesBetweenPhasesTrigger(botAI); }
// Void Reaver
static Trigger* void_reaver_boss_casts_pounding(
PlayerbotAI* botAI) { return new VoidReaverBossCastsPoundingTrigger(botAI); }
static Trigger* void_reaver_knock_away_reduces_tank_aggro(
PlayerbotAI* botAI) { return new VoidReaverKnockAwayReducesTankAggroTrigger(botAI); }
static Trigger* void_reaver_boss_launches_arcane_orbs(
PlayerbotAI* botAI) { return new VoidReaverBossLaunchesArcaneOrbsTrigger(botAI); }
static Trigger* void_reaver_arcane_orb_is_incoming(
PlayerbotAI* botAI) { return new VoidReaverArcaneOrbIsIncomingTrigger(botAI); }
static Trigger* void_reaver_bot_is_not_in_combat(
PlayerbotAI* botAI) { return new VoidReaverBotIsNotInCombatTrigger(botAI); }
// High Astromancer Solarian
static Trigger* high_astromancer_solarian_boss_casts_wrath_of_the_astromancer(
PlayerbotAI* botAI) { return new HighAstromancerSolarianBossCastsWrathOfTheAstromancerTrigger(botAI); }
static Trigger* high_astromancer_solarian_bot_has_wrath_of_the_astromancer(
PlayerbotAI* botAI) { return new HighAstromancerSolarianBotHasWrathOfTheAstromancerTrigger(botAI); }
static Trigger* high_astromancer_solarian_boss_has_vanished(
PlayerbotAI* botAI) { return new HighAstromancerSolarianBossHasVanishedTrigger(botAI); }
static Trigger* high_astromancer_solarian_solarium_priests_spawned(
PlayerbotAI* botAI) { return new HighAstromancerSolarianSolariumPriestsSpawnedTrigger(botAI); }
static Trigger* high_astromancer_solarian_boss_casts_psychic_scream(
PlayerbotAI* botAI) { return new HighAstromancerSolarianBossCastsPsychicScreamTrigger(botAI); }
// Kael'thas Sunstrider <Lord of the Blood Elves>
static Trigger* kaelthas_sunstrider_thaladred_is_fixated_on_bot(
PlayerbotAI* botAI) { return new KaelthasSunstriderThaladredIsFixatedOnBotTrigger(botAI); }
static Trigger* kaelthas_sunstrider_pulling_tankable_advisors(
PlayerbotAI* botAI) { return new KaelthasSunstriderPullingTankableAdvisorsTrigger(botAI); }
static Trigger* kaelthas_sunstrider_sanguinar_engaged_by_main_tank(
PlayerbotAI* botAI) { return new KaelthasSunstriderSanguinarEngagedByMainTankTrigger(botAI); }
static Trigger* kaelthas_sunstrider_sanguinar_casts_bellowing_roar(
PlayerbotAI* botAI) { return new KaelthasSunstriderSanguinarCastsBellowingRoarTrigger(botAI); }
static Trigger* kaelthas_sunstrider_capernian_should_be_tanked_by_a_warlock(
PlayerbotAI* botAI) { return new KaelthasSunstriderCapernianShouldBeTankedByAWarlockTrigger(botAI); }
static Trigger* kaelthas_sunstrider_capernian_casts_arcane_burst_and_conflagration(
PlayerbotAI* botAI) { return new KaelthasSunstriderCapernianCastsArcaneBurstAndConflagrationTrigger(botAI); }
static Trigger* kaelthas_sunstrider_telonicus_engaged_by_first_assist_tank(
PlayerbotAI* botAI) { return new KaelthasSunstriderTelonicusEngagedByFirstAssistTankTrigger(botAI); }
static Trigger* kaelthas_sunstrider_bots_have_specific_roles_in_phase_3(
PlayerbotAI* botAI) { return new KaelthasSunstriderBotsHaveSpecificRolesInPhase3Trigger(botAI); }
static Trigger* kaelthas_sunstrider_determining_advisor_kill_order(
PlayerbotAI* botAI) { return new KaelthasSunstriderDeterminingAdvisorKillOrderTrigger(botAI); }
static Trigger* kaelthas_sunstrider_waiting_for_tanks_to_get_aggro_on_advisors(
PlayerbotAI* botAI) { return new KaelthasSunstriderWaitingForTanksToGetAggroOnAdvisorsTrigger(botAI); }
static Trigger* kaelthas_sunstrider_legendary_weapons_are_alive(
PlayerbotAI* botAI) { return new KaelthasSunstriderLegendaryWeaponsAreAliveTrigger(botAI); }
static Trigger* kaelthas_sunstrider_legendary_axe_casts_whirlwind(
PlayerbotAI* botAI) { return new KaelthasSunstriderLegendaryAxeCastsWhirlwindTrigger(botAI); }
static Trigger* kaelthas_sunstrider_legendary_weapons_are_dead_and_lootable(
PlayerbotAI* botAI) { return new KaelthasSunstriderLegendaryWeaponsAreDeadAndLootableTrigger(botAI); }
static Trigger* kaelthas_sunstrider_legendary_weapons_are_equipped(
PlayerbotAI* botAI) { return new KaelthasSunstriderLegendaryWeaponsAreEquippedTrigger(botAI); }
static Trigger* kaelthas_sunstrider_legendary_weapons_were_lost(
PlayerbotAI* botAI) { return new KaelthasSunstriderLegendaryWeaponsWereLostTrigger(botAI); }
static Trigger* kaelthas_sunstrider_boss_has_entered_the_fight(
PlayerbotAI* botAI) { return new KaelthasSunstriderBossHasEnteredTheFightTrigger(botAI); }
static Trigger* kaelthas_sunstrider_raid_member_is_mind_controlled(
PlayerbotAI* botAI) { return new KaelthasSunstriderRaidMemberIsMindControlledTrigger(botAI); }
static Trigger* kaelthas_sunstrider_phoenixes_and_eggs_are_spawning(
PlayerbotAI* botAI) { return new KaelthasSunstriderPhoenixesAndEggsAreSpawningTrigger(botAI); }
static Trigger* kaelthas_sunstrider_boss_is_casting_pyroblast(
PlayerbotAI* botAI) { return new KaelthasSunstriderBossIsCastingPyroblastTrigger(botAI); }
static Trigger* kaelthas_sunstrider_boss_is_manipulating_gravity(
PlayerbotAI* botAI) { return new KaelthasSunstriderBossIsManipulatingGravityTrigger(botAI); }
};
#endif

View File

@@ -0,0 +1,162 @@
#include "RaidTempestKeepStrategy.h"
#include "RaidTempestKeepMultipliers.h"
void RaidTempestKeepStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
// Trash
triggers.push_back(new TriggerNode("crimson hand centurion casts arcane volley", {
NextAction("crimson hand centurion cast polymorph", ACTION_RAID + 1) }));
// Al'ar <Phoenix God>
triggers.push_back(new TriggerNode("al'ar pulling boss", {
NextAction("al'ar misdirect boss to main tank", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode("al'ar boss is flying between platforms", {
NextAction("al'ar boss tanks move between platforms", ACTION_RAID + 1),
NextAction("al'ar melee dps move between platforms", ACTION_RAID + 1),
NextAction("al'ar ranged and ember tank move under platforms", ACTION_RAID + 4) }));
triggers.push_back(new TriggerNode("al'ar embers of al'ar explode upon death", {
NextAction("al'ar assist tanks pick up embers", ACTION_RAID + 3) }));
triggers.push_back(new TriggerNode("al'ar killing embers of al'ar damages boss", {
NextAction("al'ar ranged dps prioritize embers", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("al'ar incoming flame quills", {
NextAction("al'ar jump from platform", ACTION_EMERGENCY + 7) }));
triggers.push_back(new TriggerNode("al'ar rising from the ashes", {
NextAction("al'ar move away from rebirth", ACTION_EMERGENCY + 7) }));
triggers.push_back(new TriggerNode("al'ar everything is on fire in phase 2", {
NextAction("al'ar swap tanks on boss", ACTION_EMERGENCY + 2),
NextAction("al'ar avoid flame patches and dive bombs", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode("al'ar phase 2 encounter is at room center", {
NextAction("al'ar return to room center", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("al'ar strategy changes between phases", {
NextAction("al'ar manage phase tracker", ACTION_EMERGENCY + 10) }));
// Void Reaver
triggers.push_back(new TriggerNode("void reaver boss casts pounding", {
NextAction("void reaver tanks position boss", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("void reaver knock away reduces tank aggro", {
NextAction("void reaver use aggro dump ability", ACTION_EMERGENCY + 6) }));
triggers.push_back(new TriggerNode("void reaver boss launches arcane orbs", {
NextAction("void reaver spread ranged", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("void reaver arcane orb is incoming", {
NextAction("void reaver avoid arcane orb", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode("void reaver bot is not in combat", {
NextAction("void reaver erase trackers", ACTION_EMERGENCY + 11) }));
// High Astromancer Solarian
triggers.push_back(new TriggerNode("high astromancer solarian boss casts wrath of the astromancer", {
NextAction("high astromancer solarian ranged leave space for melee", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("high astromancer solarian bot has wrath of the astromancer", {
NextAction("high astromancer solarian move away from group", ACTION_EMERGENCY + 6) }));
triggers.push_back(new TriggerNode("high astromancer solarian boss has vanished", {
NextAction("high astromancer solarian stack for aoe", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("high astromancer solarian solarium priests spawned", {
NextAction("high astromancer solarian target solarium priests", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("high astromancer solarian boss casts psychic scream", {
NextAction("high astromancer solarian cast fear ward on main tank", ACTION_RAID + 2) }));
// Kael'thas Sunstrider <Lord of the Blood Elves>
triggers.push_back(new TriggerNode("kael'thas sunstrider thaladred is fixated on bot", {
NextAction("kael'thas sunstrider kite thaladred", ACTION_EMERGENCY + 6) }));
triggers.push_back(new TriggerNode("kael'thas sunstrider pulling tankable advisors", {
NextAction("kael'thas sunstrider misdirect advisors to tanks", ACTION_EMERGENCY + 2) }));
triggers.push_back(new TriggerNode("kael'thas sunstrider sanguinar engaged by main tank", {
NextAction("kael'thas sunstrider main tank position sanguinar", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("kael'thas sunstrider sanguinar casts bellowing roar", {
NextAction("kael'thas sunstrider cast fear ward on sanguinar tank", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("kael'thas sunstrider capernian should be tanked by a warlock", {
NextAction("kael'thas sunstrider warlock tank position capernian", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("kael'thas sunstrider capernian casts arcane burst and conflagration", {
NextAction("kael'thas sunstrider spread and move away from capernian", ACTION_RAID + 3) }));
triggers.push_back(new TriggerNode("kael'thas sunstrider telonicus engaged by first assist tank", {
NextAction("kael'thas sunstrider first assist tank position telonicus", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("kael'thas sunstrider bots have specific roles in phase 3", {
NextAction("kael'thas sunstrider handle advisor roles in phase 3", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("kael'thas sunstrider determining advisor kill order", {
NextAction("kael'thas sunstrider assign advisor dps priority", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("kael'thas sunstrider waiting for tanks to get aggro on advisors", {
NextAction("kael'thas sunstrider manage advisor dps timer", ACTION_EMERGENCY + 10) }));
triggers.push_back(new TriggerNode("kael'thas sunstrider legendary weapons are alive", {
NextAction("kael'thas sunstrider assign legendary weapon dps priority", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("kael'thas sunstrider legendary axe casts whirlwind", {
NextAction("kael'thas sunstrider main tank move devastation away", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode("kael'thas sunstrider legendary weapons are dead and lootable", {
NextAction("kael'thas sunstrider loot legendary weapons", ACTION_RAID) }));
triggers.push_back(new TriggerNode("kael'thas sunstrider legendary weapons are equipped", {
NextAction("kael'thas sunstrider use legendary weapons", ACTION_EMERGENCY + 6) }));
triggers.push_back(new TriggerNode("kael'thas sunstrider legendary weapons were lost", {
NextAction("kael'thas sunstrider reequip gear", ACTION_EMERGENCY + 11) }));
triggers.push_back(new TriggerNode("kael'thas sunstrider boss has entered the fight", {
NextAction("kael'thas sunstrider main tank position boss", ACTION_RAID + 1),
NextAction("kael'thas sunstrider avoid flame strike", ACTION_EMERGENCY + 8) }));
triggers.push_back(new TriggerNode("kael'thas sunstrider phoenixes and eggs are spawning", {
NextAction("kael'thas sunstrider handle phoenixes and eggs", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("kael'thas sunstrider raid member is mind controlled", {
NextAction("kael'thas sunstrider break mind control", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode("kael'thas sunstrider boss is casting pyroblast", {
NextAction("kael'thas sunstrider break through shock barrier", ACTION_EMERGENCY + 7) }));
triggers.push_back(new TriggerNode("kael'thas sunstrider boss is manipulating gravity", {
NextAction("kael'thas sunstrider spread out in midair", ACTION_EMERGENCY + 1) }));
}
void RaidTempestKeepStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{
// Alar <Phoenix God>
multipliers.push_back(new AlarMoveBetweenPlatformsMultiplier(botAI));
multipliers.push_back(new AlarDisableDisperseMultiplier(botAI));
multipliers.push_back(new AlarDisableTankAssistMultiplier(botAI));
multipliers.push_back(new AlarStayAwayFromRebirthMultiplier(botAI));
multipliers.push_back(new AlarPhase2NoTankingIfArmorMeltedMultiplier(botAI));
// Void Reaver
multipliers.push_back(new VoidReaverMaintainPositionsMultiplier(botAI));
// High Astromancer Solarian
multipliers.push_back(new HighAstromancerSolarianDisableTankAssistMultiplier(botAI));
multipliers.push_back(new HighAstromancerSolarianMaintainPositionMultiplier(botAI));
// Kael'thas Sunstrider <Lord of the Blood Elves>
multipliers.push_back(new KaelthasSunstriderWaitForDpsMultiplier(botAI));
multipliers.push_back(new KaelthasSunstriderKiteThaladredMultiplier(botAI));
multipliers.push_back(new KaelthasSunstriderControlMisdirectionMultiplier(botAI));
multipliers.push_back(new KaelthasSunstriderKeepDistanceFromCapernianMultiplier(botAI));
multipliers.push_back(new KaelthasSunstriderManageWeaponTankingMultiplier(botAI));
multipliers.push_back(new KaelthasSunstriderDisableAdvisorTankAssistMultiplier(botAI));
multipliers.push_back(new KaelthasSunstriderDisableDisperseMultiplier(botAI));
multipliers.push_back(new KaelthasSunstriderDelayCooldownsMultiplier(botAI));
multipliers.push_back(new KaelthasSunstriderStaySpreadDuringGravityLapseMultiplier(botAI));
}

View File

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

View File

@@ -0,0 +1,527 @@
#include "RaidTempestKeepTriggers.h"
#include "RaidTempestKeepHelpers.h"
#include "RaidTempestKeepActions.h"
#include "RaidTempestKeepKaelthasBossAI.h"
#include "Playerbots.h"
#include "RaidBossHelpers.h"
using namespace TempestKeepHelpers;
// Trash
bool CrimsonHandCenturionCastsArcaneVolleyTrigger::IsActive()
{
return bot->getClass() == CLASS_MAGE &&
AI_VALUE2(Unit*, "find target", "crimson hand centurion");
}
// Al'ar <Phoenix God>
bool AlarPullingBossTrigger::IsActive()
{
if (bot->getClass() != CLASS_HUNTER)
return false;
Unit* alar = AI_VALUE2(Unit*, "find target", "al'ar");
return alar && alar->GetHealthPct() > 98.0f;
}
bool AlarBossIsFlyingBetweenPlatformsTrigger::IsActive()
{
Unit* alar = AI_VALUE2(Unit*, "find target", "al'ar");
if (!alar || !alar->IsVisible())
return false;
if (isAlarInPhase2[alar->GetMap()->GetInstanceId()])
return false;
int8 locationIndex = GetAlarCurrentLocationIndex(alar);
if (locationIndex == LOCATION_NONE)
{
Position dest;
locationIndex = GetAlarDestinationLocationIndex(alar, dest);
}
return locationIndex != POINT_QUILL_OR_DIVE_IDX &&
locationIndex != POINT_MIDDLE_IDX;
}
bool AlarEmbersOfAlarExplodeUponDeathTrigger::IsActive()
{
return botAI->IsTank(bot) && AI_VALUE2(Unit*, "find target", "ember of al'ar");
}
bool AlarKillingEmbersOfAlarDamagesBossTrigger::IsActive()
{
return botAI->IsRangedDps(bot) &&
AI_VALUE2(Unit*, "find target", "ember of al'ar");
}
bool AlarIncomingFlameQuillsTrigger::IsActive()
{
Unit* alar = AI_VALUE2(Unit*, "find target", "al'ar");
if (!alar || isAlarInPhase2[alar->GetMap()->GetInstanceId()])
return false;
Position dest;
return GetAlarCurrentLocationIndex(alar) == POINT_QUILL_OR_DIVE_IDX ||
GetAlarDestinationLocationIndex(alar, dest) == POINT_QUILL_OR_DIVE_IDX;
}
bool AlarRisingFromTheAshesTrigger::IsActive()
{
Unit* alar = AI_VALUE2(Unit*, "find target", "al'ar");
if (!alar)
return false;
if (isAlarInPhase2[alar->GetMap()->GetInstanceId()])
return false;
Creature* alarCreature = alar->ToCreature();
return alarCreature && alarCreature->GetReactState() == REACT_PASSIVE;
}
bool AlarEverythingIsOnFireInPhase2Trigger::IsActive()
{
Unit* alar = AI_VALUE2(Unit*, "find target", "al'ar");
return alar && isAlarInPhase2[alar->GetMap()->GetInstanceId()];
}
bool AlarPhase2EncounterIsAtRoomCenterTrigger::IsActive()
{
if (bot->GetVictim())
return false;
Unit* alar = AI_VALUE2(Unit*, "find target", "al'ar");
if (!alar || !isAlarInPhase2[alar->GetMap()->GetInstanceId()])
return false;
Creature* alarCreature = alar->ToCreature();
if (alarCreature && alarCreature->GetReactState() == REACT_PASSIVE)
return false;
Position dest;
return GetAlarCurrentLocationIndex(alar) != POINT_QUILL_OR_DIVE_IDX &&
GetAlarDestinationLocationIndex(alar, dest) != POINT_QUILL_OR_DIVE_IDX;
}
bool AlarStrategyChangesBetweenPhasesTrigger::IsActive()
{
return botAI->IsDps(bot) && IsMechanicTrackerBot(botAI, bot, TEMPEST_KEEP_MAP_ID) &&
AI_VALUE2(Unit*, "find target", "al'ar");
}
// Void Reaver
bool VoidReaverBossCastsPoundingTrigger::IsActive()
{
if (!botAI->IsTank(bot))
return false;
Unit* voidReaver = AI_VALUE2(Unit*, "find target", "void reaver");
return voidReaver && voidReaver->GetVictim() == bot;
}
bool VoidReaverKnockAwayReducesTankAggroTrigger::IsActive()
{
if (botAI->IsTank(bot))
return false;
if (bot->getClass() == CLASS_DEATH_KNIGHT ||
bot->getClass() == CLASS_DRUID ||
bot->getClass() == CLASS_SHAMAN ||
bot->getClass() == CLASS_WARRIOR)
return false;
Unit* voidReaver = AI_VALUE2(Unit*, "find target", "void reaver");
return voidReaver && voidReaver->GetVictim() == bot;
}
bool VoidReaverBossLaunchesArcaneOrbsTrigger::IsActive()
{
if (!botAI->IsRanged(bot))
return false;
Unit* voidReaver = AI_VALUE2(Unit*, "find target", "void reaver");
return voidReaver && voidReaver->GetVictim() != bot;
}
bool VoidReaverArcaneOrbIsIncomingTrigger::IsActive()
{
if (botAI->IsTank(bot))
return false;
Unit* voidReaver = AI_VALUE2(Unit*, "find target", "void reaver");
if (!voidReaver || voidReaver->GetVictim() == bot)
return false;
auto it = voidReaverArcaneOrbs.find(bot->GetMap()->GetInstanceId());
if (it == voidReaverArcaneOrbs.end() || it->second.empty())
return false;
uint32 currentTime = getMSTime();
constexpr uint32 orbDuration = 7000;
constexpr float safeDistance = 22.0f;
for (auto const& orb : it->second)
{
if (getMSTimeDiff(orb.castTime, currentTime) <= orbDuration &&
bot->GetExactDist2d(orb.destination.GetPositionX(),
orb.destination.GetPositionY()) < safeDistance)
return true;
}
return false;
}
bool VoidReaverBotIsNotInCombatTrigger::IsActive()
{
return !bot->IsInCombat();
}
// High Astromancer Solarian
bool HighAstromancerSolarianBossCastsWrathOfTheAstromancerTrigger::IsActive()
{
if (bot->HasAura(SPELL_WRATH_OF_THE_ASTROMANCER))
return false;
if (!botAI->IsRanged(bot))
return false;
Unit* astromancer = AI_VALUE2(Unit*, "find target", "high astromancer solarian");
return astromancer && !astromancer->HasAura(SPELL_SOLARIAN_TRANSFORM);
}
bool HighAstromancerSolarianBotHasWrathOfTheAstromancerTrigger::IsActive()
{
return bot->HasAura(SPELL_WRATH_OF_THE_ASTROMANCER);
}
bool HighAstromancerSolarianBossHasVanishedTrigger::IsActive()
{
if (bot->HasAura(SPELL_WRATH_OF_THE_ASTROMANCER))
return false;
Unit* astromancer = AI_VALUE2(Unit*, "find target", "high astromancer solarian");
if (!astromancer)
return false;
Creature* astromancerCreature = astromancer->ToCreature();
return astromancerCreature &&
astromancerCreature->GetReactState() == REACT_PASSIVE;
}
bool HighAstromancerSolarianSolariumPriestsSpawnedTrigger::IsActive()
{
return botAI->IsMelee(bot) && AI_VALUE2(Unit*, "find target", "solarium priest");
}
bool HighAstromancerSolarianBossCastsPsychicScreamTrigger::IsActive()
{
if (bot->getClass() != CLASS_PRIEST)
return false;
Unit* astromancer = AI_VALUE2(Unit*, "find target", "high astromancer solarian");
return astromancer && astromancer->HasAura(SPELL_SOLARIAN_TRANSFORM);
}
// Kael'thas Sunstrider <Lord of the Blood Elves>
bool KaelthasSunstriderThaladredIsFixatedOnBotTrigger::IsActive()
{
Unit* kaelthas = AI_VALUE2(Unit*, "find target", "kael'thas sunstrider");
if (!kaelthas)
return false;
Unit* thaladred = AI_VALUE2(Unit*, "find target", "thaladred the darkener");
if (!thaladred || thaladred->GetVictim() != bot)
return false;
boss_kaelthas* kaelAI = dynamic_cast<boss_kaelthas*>(kaelthas->GetAI());
if (!kaelAI)
return false;
return !(botAI->IsTank(bot) && kaelAI->GetPhase() == PHASE_ALL_ADVISORS);
}
bool KaelthasSunstriderPullingTankableAdvisorsTrigger::IsActive()
{
if (bot->getClass() != CLASS_HUNTER)
return false;
Unit* kaelthas = AI_VALUE2(Unit*, "find target", "kael'thas sunstrider");
if (!kaelthas)
return false;
boss_kaelthas* kaelAI = dynamic_cast<boss_kaelthas*>(kaelthas->GetAI());
return kaelAI && (kaelAI->GetPhase() == PHASE_SINGLE_ADVISOR ||
kaelAI->GetPhase() == PHASE_ALL_ADVISORS);
}
bool KaelthasSunstriderSanguinarEngagedByMainTankTrigger::IsActive()
{
if (!botAI->IsMainTank(bot))
return false;
Unit* sanguinar = AI_VALUE2(Unit*, "find target", "lord sanguinar");
return sanguinar && !sanguinar->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE) &&
!sanguinar->HasAura(SPELL_PERMANENT_FEIGN_DEATH);
}
bool KaelthasSunstriderSanguinarCastsBellowingRoarTrigger::IsActive()
{
if (bot->getClass() != CLASS_PRIEST)
return false;
Unit* kaelthas = AI_VALUE2(Unit*, "find target", "kael'thas sunstrider");
if (!kaelthas)
return false;
boss_kaelthas* kaelAI = dynamic_cast<boss_kaelthas*>(kaelthas->GetAI());
if (!kaelAI)
return false;
if (kaelAI->GetPhase() != PHASE_SINGLE_ADVISOR &&
kaelAI->GetPhase() != PHASE_TRANSITION &&
kaelAI->GetPhase() != PHASE_ALL_ADVISORS)
return false;
Player* mainTank = GetGroupMainTank(botAI, bot);
if (!mainTank || mainTank->HasAura(SPELL_FEAR_WARD))
return false;
return botAI->CanCastSpell("fear ward", mainTank);
}
bool KaelthasSunstriderCapernianShouldBeTankedByAWarlockTrigger::IsActive()
{
if (bot->getClass() != CLASS_WARLOCK)
return false;
Unit* capernian = AI_VALUE2(Unit*, "find target", "grand astromancer capernian");
if (!capernian || capernian->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE) ||
capernian->HasAura(SPELL_PERMANENT_FEIGN_DEATH))
return false;
return GetCapernianTank(bot) == bot;
}
bool KaelthasSunstriderCapernianCastsArcaneBurstAndConflagrationTrigger::IsActive()
{
Unit* capernian = AI_VALUE2(Unit*, "find target", "grand astromancer capernian");
if (!capernian || capernian->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE) ||
capernian->HasAura(SPELL_PERMANENT_FEIGN_DEATH))
return false;
return GetCapernianTank(bot) != bot;
}
bool KaelthasSunstriderTelonicusEngagedByFirstAssistTankTrigger::IsActive()
{
if (!botAI->IsAssistTankOfIndex(bot, 0, false))
return false;
Unit* telonicus = AI_VALUE2(Unit*, "find target", "master engineer telonicus");
return telonicus && !telonicus->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE) &&
!telonicus->HasAura(SPELL_PERMANENT_FEIGN_DEATH);
}
bool KaelthasSunstriderBotsHaveSpecificRolesInPhase3Trigger::IsActive()
{
Unit* kaelthas = AI_VALUE2(Unit*, "find target", "kael'thas sunstrider");
if (!kaelthas)
return false;
if (!AI_VALUE2(Unit*, "find target", "master engineer telonicus") &&
!AI_VALUE2(Unit*, "find target", "lord sanguinar"))
return false;
boss_kaelthas* kaelAI = dynamic_cast<boss_kaelthas*>(kaelthas->GetAI());
if (!kaelAI || kaelAI->GetPhase() != PHASE_ALL_ADVISORS)
return false;
return botAI->IsAssistHealOfIndex(bot, 0, true) ||
botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0, true) ||
(bot->getClass() == CLASS_WARLOCK && GetCapernianTank(bot) == bot);
}
bool KaelthasSunstriderDeterminingAdvisorKillOrderTrigger::IsActive()
{
if (botAI->IsHeal(bot) ||
botAI->IsMainTank(bot) ||
botAI->IsAssistTankOfIndex(bot, 0, true))
return false;
Unit* kaelthas = AI_VALUE2(Unit*, "find target", "kael'thas sunstrider");
if (!kaelthas)
return false;
boss_kaelthas* kaelAI = dynamic_cast<boss_kaelthas*>(kaelthas->GetAI());
return kaelAI && (kaelAI->GetPhase() == PHASE_SINGLE_ADVISOR ||
kaelAI->GetPhase() == PHASE_ALL_ADVISORS);
}
bool KaelthasSunstriderWaitingForTanksToGetAggroOnAdvisorsTrigger::IsActive()
{
if (!botAI->IsDps(bot))
return false;
Unit* kaelthas = AI_VALUE2(Unit*, "find target", "kael'thas sunstrider");
if (!kaelthas)
return false;
boss_kaelthas* kaelAI = dynamic_cast<boss_kaelthas*>(kaelthas->GetAI());
if (!kaelAI || kaelAI->GetPhase() != PHASE_SINGLE_ADVISOR)
return false;
return IsMechanicTrackerBot(botAI, bot, TEMPEST_KEEP_MAP_ID, GetCapernianTank(bot));
}
bool KaelthasSunstriderLegendaryWeaponsAreAliveTrigger::IsActive()
{
if (botAI->IsMainTank(bot))
return false;
Unit* kaelthas = AI_VALUE2(Unit*, "find target", "kael'thas sunstrider");
if (!kaelthas)
return false;
boss_kaelthas* kaelAI = dynamic_cast<boss_kaelthas*>(kaelthas->GetAI());
return kaelAI && kaelAI->GetPhase() == PHASE_WEAPONS;
}
bool KaelthasSunstriderLegendaryAxeCastsWhirlwindTrigger::IsActive()
{
return botAI->IsMainTank(bot) &&
AI_VALUE2(Unit*, "find target", "devastation");
}
bool KaelthasSunstriderLegendaryWeaponsAreDeadAndLootableTrigger::IsActive()
{
Unit* kaelthas = AI_VALUE2(Unit*, "find target", "kael'thas sunstrider");
if (!kaelthas)
return false;
boss_kaelthas* kaelAI = dynamic_cast<boss_kaelthas*>(kaelthas->GetAI());
if (!kaelAI ||
(kaelAI->GetPhase() != PHASE_WEAPONS && kaelAI->GetPhase() != PHASE_ALL_ADVISORS))
return false;
Unit* axe = AI_VALUE2(Unit*, "find target", "devastation");
if (axe && axe->GetVictim() == bot)
return false;
return IsAnyLegendaryWeaponDead(bot);
}
bool KaelthasSunstriderLegendaryWeaponsAreEquippedTrigger::IsActive()
{
if (!AI_VALUE2(Unit*, "find target", "kael'thas sunstrider"))
return false;
return bot->HasItemCount(ITEM_STAFF_OF_DISINTEGRATION, 1, false) ||
bot->HasItemCount(ITEM_NETHERSTRAND_LONGBOW, 1, false) ||
bot->HasItemCount(ITEM_PHASESHIFT_BULWARK, 1, false);
}
bool KaelthasSunstriderLegendaryWeaponsWereLostTrigger::IsActive()
{
if (bot->GetMapId() != TEMPEST_KEEP_MAP_ID)
return false;
Map* map = bot->GetMap();
if (!map)
return false;
constexpr uint32 KAELTHAS_DB_GUID = 158218;
auto const& creatureStore = map->GetCreatureBySpawnIdStore();
auto it = creatureStore.find(KAELTHAS_DB_GUID);
if (it == creatureStore.end())
return false;
Creature* kaelthas = it->second;
if (!kaelthas || bot->GetExactDist2d(kaelthas) > 150.0f)
return false;
const std::array<uint8, 3> weaponSlots =
{
EQUIPMENT_SLOT_MAINHAND,
EQUIPMENT_SLOT_OFFHAND,
EQUIPMENT_SLOT_RANGED
};
for (uint8 slot : weaponSlots)
{
if (!bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot) &&
HasEquippableItemForSlot(bot, slot))
return true;
}
return false;
}
bool KaelthasSunstriderBossHasEnteredTheFightTrigger::IsActive()
{
Unit* kaelthas = AI_VALUE2(Unit*, "find target", "kael'thas sunstrider");
if (!kaelthas)
return false;
boss_kaelthas* kaelAI = dynamic_cast<boss_kaelthas*>(kaelthas->GetAI());
return kaelAI && kaelAI->GetPhase() == PHASE_FINAL;
}
bool KaelthasSunstriderPhoenixesAndEggsAreSpawningTrigger::IsActive()
{
Unit* kaelthas = AI_VALUE2(Unit*, "find target", "kael'thas sunstrider");
if (!kaelthas)
return false;
if (botAI->IsTank(bot) && kaelthas->GetVictim() == bot)
return false;
return AI_VALUE2(Unit*, "find target", "phoenix") ||
AI_VALUE2(Unit*, "find target", "phoenix egg");
}
bool KaelthasSunstriderRaidMemberIsMindControlledTrigger::IsActive()
{
Unit* kaelthas = AI_VALUE2(Unit*, "find target", "kael'thas sunstrider");
if (!kaelthas)
return false;
if (botAI->IsTank(bot) && kaelthas->GetVictim() == bot)
return false;
if (!bot->HasItemCount(ITEM_INFINITY_BLADE, 1, true))
return false;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive())
continue;
if (member->HasAura(SPELL_KAELTHAS_MIND_CONTROL))
return true;
}
}
return false;
}
bool KaelthasSunstriderBossIsCastingPyroblastTrigger::IsActive()
{
if (!botAI->IsDps(bot))
return false;
Unit* kaelthas = AI_VALUE2(Unit*, "find target", "kael'thas sunstrider");
return kaelthas && kaelthas->HasAura(SPELL_SHOCK_BARRIER);
}
bool KaelthasSunstriderBossIsManipulatingGravityTrigger::IsActive()
{
return bot->HasAura(SPELL_GRAVITY_LAPSE);
}

View File

@@ -0,0 +1,338 @@
#ifndef _PLAYERBOT_RAIDTEMPESTKEEPTRIGGERS_H
#define _PLAYERBOT_RAIDTEMPESTKEEPTRIGGERS_H
#include "Trigger.h"
// General
// Trash
class CrimsonHandCenturionCastsArcaneVolleyTrigger : public Trigger
{
public:
CrimsonHandCenturionCastsArcaneVolleyTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "crimson hand centurion casts arcane volley") {}
bool IsActive() override;
};
// Al'ar <Phoenix God>
class AlarPullingBossTrigger : public Trigger
{
public:
AlarPullingBossTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "al'ar pulling boss") {}
bool IsActive() override;
};
class AlarBossIsFlyingBetweenPlatformsTrigger : public Trigger
{
public:
AlarBossIsFlyingBetweenPlatformsTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "al'ar boss is flying between platforms") {}
bool IsActive() override;
};
class AlarEmbersOfAlarExplodeUponDeathTrigger : public Trigger
{
public:
AlarEmbersOfAlarExplodeUponDeathTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "al'ar embers of al'ar explode upon death") {}
bool IsActive() override;
};
class AlarKillingEmbersOfAlarDamagesBossTrigger : public Trigger
{
public:
AlarKillingEmbersOfAlarDamagesBossTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "al'ar killing embers of al'ar damages boss") {}
bool IsActive() override;
};
class AlarIncomingFlameQuillsTrigger : public Trigger
{
public:
AlarIncomingFlameQuillsTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "al'ar incoming flame quills") {}
bool IsActive() override;
};
class AlarRisingFromTheAshesTrigger : public Trigger
{
public:
AlarRisingFromTheAshesTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "al'ar rising from the ashes") {}
bool IsActive() override;
};
class AlarEverythingIsOnFireInPhase2Trigger : public Trigger
{
public:
AlarEverythingIsOnFireInPhase2Trigger(
PlayerbotAI* botAI) : Trigger(botAI, "al'ar everything is on fire in phase 2") {}
bool IsActive() override;
};
class AlarPhase2EncounterIsAtRoomCenterTrigger : public Trigger
{
public:
AlarPhase2EncounterIsAtRoomCenterTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "al'ar phase 2 encounter is at room center") {}
bool IsActive() override;
};
class AlarStrategyChangesBetweenPhasesTrigger : public Trigger
{
public:
AlarStrategyChangesBetweenPhasesTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "al'ar strategy changes between phases") {}
bool IsActive() override;
};
// Void Reaver
class VoidReaverBossCastsPoundingTrigger : public Trigger
{
public:
VoidReaverBossCastsPoundingTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "void reaver boss casts pounding") {}
bool IsActive() override;
};
class VoidReaverKnockAwayReducesTankAggroTrigger : public Trigger
{
public:
VoidReaverKnockAwayReducesTankAggroTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "void reaver knock away reduces tank aggro") {}
bool IsActive() override;
};
class VoidReaverBossLaunchesArcaneOrbsTrigger : public Trigger
{
public:
VoidReaverBossLaunchesArcaneOrbsTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "void reaver boss launches arcane orbs") {}
bool IsActive() override;
};
class VoidReaverArcaneOrbIsIncomingTrigger : public Trigger
{
public:
VoidReaverArcaneOrbIsIncomingTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "void reaver arcane orb is incoming") {}
bool IsActive() override;
};
class VoidReaverBotIsNotInCombatTrigger : public Trigger
{
public:
VoidReaverBotIsNotInCombatTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "void reaver bot is not in combat") {}
bool IsActive() override;
};
// High Astromancer Solarian
class HighAstromancerSolarianBossCastsWrathOfTheAstromancerTrigger : public Trigger
{
public:
HighAstromancerSolarianBossCastsWrathOfTheAstromancerTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "high astromancer solarian boss casts wrath of the astromancer") {}
bool IsActive() override;
};
class HighAstromancerSolarianBotHasWrathOfTheAstromancerTrigger : public Trigger
{
public:
HighAstromancerSolarianBotHasWrathOfTheAstromancerTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "high astromancer solarian bot has wrath of the astromancer") {}
bool IsActive() override;
};
class HighAstromancerSolarianBossHasVanishedTrigger : public Trigger
{
public:
HighAstromancerSolarianBossHasVanishedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "high astromancer solarian boss has vanished") {}
bool IsActive() override;
};
class HighAstromancerSolarianSolariumPriestsSpawnedTrigger : public Trigger
{
public:
HighAstromancerSolarianSolariumPriestsSpawnedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "high astromancer solarian solarium priests spawned") {}
bool IsActive() override;
};
class HighAstromancerSolarianBossCastsPsychicScreamTrigger : public Trigger
{
public:
HighAstromancerSolarianBossCastsPsychicScreamTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "high astromancer boss casts psychic scream") {}
bool IsActive() override;
};
// Kael'thas Sunstrider <Lord of the Blood Elves>
class KaelthasSunstriderThaladredIsFixatedOnBotTrigger : public Trigger
{
public:
KaelthasSunstriderThaladredIsFixatedOnBotTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kael'thas sunstrider thaladred is fixated on bot") {}
bool IsActive() override;
};
class KaelthasSunstriderPullingTankableAdvisorsTrigger : public Trigger
{
public:
KaelthasSunstriderPullingTankableAdvisorsTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kael'thas sunstrider pulling tankable advisors") {}
bool IsActive() override;
};
class KaelthasSunstriderSanguinarEngagedByMainTankTrigger : public Trigger
{
public:
KaelthasSunstriderSanguinarEngagedByMainTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kael'thas sunstrider sanguinar engaged by main tank") {}
bool IsActive() override;
};
class KaelthasSunstriderSanguinarCastsBellowingRoarTrigger : public Trigger
{
public:
KaelthasSunstriderSanguinarCastsBellowingRoarTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kael'thas sunstrider sanguinar casts bellowing roar") {}
bool IsActive() override;
};
class KaelthasSunstriderCapernianShouldBeTankedByAWarlockTrigger : public Trigger
{
public:
KaelthasSunstriderCapernianShouldBeTankedByAWarlockTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kael'thas sunstrider capernian should be tanked by a warlock") {}
bool IsActive() override;
};
class KaelthasSunstriderCapernianCastsArcaneBurstAndConflagrationTrigger : public Trigger
{
public:
KaelthasSunstriderCapernianCastsArcaneBurstAndConflagrationTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kael'thas sunstrider capernian casts arcane burst and conflagration") {}
bool IsActive() override;
};
class KaelthasSunstriderTelonicusEngagedByFirstAssistTankTrigger : public Trigger
{
public:
KaelthasSunstriderTelonicusEngagedByFirstAssistTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kael'thas sunstrider telonicus engaged by first assist tank") {}
bool IsActive() override;
};
class KaelthasSunstriderBotsHaveSpecificRolesInPhase3Trigger : public Trigger
{
public:
KaelthasSunstriderBotsHaveSpecificRolesInPhase3Trigger(
PlayerbotAI* botAI) : Trigger(botAI, "kael'thas sunstrider bots have specific roles in phase 3") {}
bool IsActive() override;
};
class KaelthasSunstriderDeterminingAdvisorKillOrderTrigger : public Trigger
{
public:
KaelthasSunstriderDeterminingAdvisorKillOrderTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kael'thas sunstrider determining advisor kill order") {}
bool IsActive() override;
};
class KaelthasSunstriderWaitingForTanksToGetAggroOnAdvisorsTrigger : public Trigger
{
public:
KaelthasSunstriderWaitingForTanksToGetAggroOnAdvisorsTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kael'thas sunstrider waiting for tanks to get aggro on advisors") {}
bool IsActive() override;
};
class KaelthasSunstriderLegendaryWeaponsAreAliveTrigger : public Trigger
{
public:
KaelthasSunstriderLegendaryWeaponsAreAliveTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kael'thas sunstrider legendary weapons are alive") {}
bool IsActive() override;
};
class KaelthasSunstriderLegendaryAxeCastsWhirlwindTrigger : public Trigger
{
public:
KaelthasSunstriderLegendaryAxeCastsWhirlwindTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kael'thas sunstrider legendary axe casts whirlwind") {}
bool IsActive() override;
};
class KaelthasSunstriderLegendaryWeaponsAreDeadAndLootableTrigger : public Trigger
{
public:
KaelthasSunstriderLegendaryWeaponsAreDeadAndLootableTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kael'thas sunstrider legendary weapons are dead and lootable") {}
bool IsActive() override;
};
class KaelthasSunstriderLegendaryWeaponsAreEquippedTrigger : public Trigger
{
public:
KaelthasSunstriderLegendaryWeaponsAreEquippedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kael'thas sunstrider legendary weapons are equipped") {}
bool IsActive() override;
};
class KaelthasSunstriderLegendaryWeaponsWereLostTrigger : public Trigger
{
public:
KaelthasSunstriderLegendaryWeaponsWereLostTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kael'thas sunstrider legendary weapons were lost") {}
bool IsActive() override;
};
class KaelthasSunstriderBossHasEnteredTheFightTrigger : public Trigger
{
public:
KaelthasSunstriderBossHasEnteredTheFightTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kael'thas sunstrider boss has entered the fight") {}
bool IsActive() override;
};
class KaelthasSunstriderPhoenixesAndEggsAreSpawningTrigger : public Trigger
{
public:
KaelthasSunstriderPhoenixesAndEggsAreSpawningTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kael'thas sunstrider phoenixes and eggs are spawning") {}
bool IsActive() override;
};
class KaelthasSunstriderRaidMemberIsMindControlledTrigger : public Trigger
{
public:
KaelthasSunstriderRaidMemberIsMindControlledTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kael'thas sunstrider raid member is mind controlled") {}
bool IsActive() override;
};
class KaelthasSunstriderBossIsCastingPyroblastTrigger : public Trigger
{
public:
KaelthasSunstriderBossIsCastingPyroblastTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kael'thas sunstrider boss is casting pyroblast") {}
bool IsActive() override;
};
class KaelthasSunstriderBossIsManipulatingGravityTrigger : public Trigger
{
public:
KaelthasSunstriderBossIsManipulatingGravityTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kael'thas sunstrider boss is manipulating gravity") {}
bool IsActive() override;
};
#endif

View File

@@ -0,0 +1,425 @@
#include "RaidTempestKeepHelpers.h"
#include "RaidTempestKeepActions.h"
#include "LootObjectStack.h"
#include "Playerbots.h"
#include "RaidBossHelpers.h"
namespace TempestKeepHelpers
{
// General
Unit* GetNearestNonTankPlayerInRadius(PlayerbotAI* botAI, Player* bot, float radius)
{
Unit* nearestPlayer = nullptr;
float nearestDistance = radius;
Group* group = bot->GetGroup();
if (!group)
return nullptr;
for (GroupReference* ref = group->GetFirstMember(); ref != nullptr; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member == bot || botAI->IsTank(member))
continue;
float distance = bot->GetExactDist2d(member);
if (distance < nearestDistance)
{
nearestDistance = distance;
nearestPlayer = member;
}
}
return nearestPlayer;
}
std::vector<Unit*> GetAllHazardTriggers(Player* bot, uint32 npcEntry, float searchRadius)
{
std::vector<Unit*> hazardTriggers;
std::list<Creature*> creatureList;
bot->GetCreatureListWithEntryInGrid(creatureList, npcEntry, searchRadius);
for (Creature* creature : creatureList)
{
if (creature && creature->IsAlive())
hazardTriggers.push_back(creature);
}
return hazardTriggers;
}
Position FindSafestNearbyPosition(Player* bot, const std::vector<Unit*>& hazards,
float hazardRadius, const Position* center)
{
constexpr float searchStep = M_PI / 8.0f;
constexpr float minDistance = 2.0f;
constexpr float maxDistance = 30.0f;
constexpr float distanceStep = 1.0f;
Position bestPos;
float minMoveDistance = std::numeric_limits<float>::max();
bool foundSafe = false;
for (float distance = minDistance; distance <= maxDistance; distance += distanceStep)
{
for (float angle = 0.0f; angle < 2 * M_PI; angle += searchStep)
{
const Position& searchCenter = center ? *center : bot->GetPosition();
float x = searchCenter.GetPositionX() + distance * std::cos(angle);
float y = searchCenter.GetPositionY() + distance * std::sin(angle);
bool isSafe = true;
for (Unit* hazard : hazards)
{
if (hazard->GetExactDist2d(x, y) < hazardRadius)
{
isSafe = false;
break;
}
}
if (!isSafe)
continue;
Position testPos(x, y, bot->GetPositionZ());
bool pathSafe = IsPathSafeFromHazards(bot->GetPosition(), testPos, hazards, hazardRadius);
if (pathSafe || !foundSafe)
{
float moveDistance = bot->GetExactDist2d(x, y);
if (pathSafe && (!foundSafe || moveDistance < minMoveDistance))
{
bestPos = testPos;
minMoveDistance = moveDistance;
foundSafe = true;
}
else if (!foundSafe && moveDistance < minMoveDistance)
{
bestPos = testPos;
minMoveDistance = moveDistance;
}
}
}
if (foundSafe)
break;
}
return bestPos;
}
bool IsPathSafeFromHazards(
const Position& start, const Position& end, const std::vector<Unit*>& hazards, float hazardRadius)
{
constexpr uint8 numChecks = 10;
float dx = end.GetPositionX() - start.GetPositionX();
float dy = end.GetPositionY() - start.GetPositionY();
for (uint8 i = 1; i <= numChecks; ++i)
{
float ratio = static_cast<float>(i) / numChecks;
float checkX = start.GetPositionX() + dx * ratio;
float checkY = start.GetPositionY() + dy * ratio;
for (Unit* hazard : hazards)
{
float distToHazard = hazard->GetExactDist2d(checkX, checkY);
if (distToHazard < hazardRadius)
return false;
}
}
return true;
}
// Al'ar <Phoenix God>
const Position ALAR_PLATFORM_0 = { 335.638f, 59.4879f, 17.9319f }; // West Platform
const Position ALAR_PLATFORM_1 = { 388.751f, 31.7312f, 20.2636f }; // Northwest Platform
const Position ALAR_PLATFORM_2 = { 388.791f, -33.1059f, 20.2636f }; // Northeast Platform
const Position ALAR_PLATFORM_3 = { 332.723f, -61.159f, 17.9791f }; // East Platform
const std::array<Position, 4> PLATFORM_POSITIONS =
{
ALAR_PLATFORM_0,
ALAR_PLATFORM_1,
ALAR_PLATFORM_2,
ALAR_PLATFORM_3
};
const Position ALAR_GROUND_0 = { 336.439f, 48.181f, -2.389f }; // Ground counterpart to West Platform
const Position ALAR_GROUND_1 = { 379.122f, 25.146f, -2.385f }; // Ground counterpart to Northwest Platform
const Position ALAR_GROUND_2 = { 378.583f, -27.481f, -2.385f }; // Ground counterpart to Northeast Platform
const Position ALAR_GROUND_3 = { 331.631f, -49.716f, -2.389f }; // Ground counterpart to East Platform
const std::array<Position, 4> GROUND_POSITIONS =
{
ALAR_GROUND_0,
ALAR_GROUND_1,
ALAR_GROUND_2,
ALAR_GROUND_3
};
const Position ALAR_ROOM_CENTER = { 330.611f, -2.540f, -2.389f };
const Position ALAR_POINT_QUILL_OR_DIVE = { 332.000f, 0.010f, 43.000f };
const Position ALAR_POINT_MIDDLE = { 331.000f, 0.010f, -2.380f };
const Position ALAR_SE_RAMP_BASE = { 281.064f, -36.590f, -2.389f };
const Position ALAR_SW_RAMP_BASE = { 281.064f, 36.590f, -2.389f };
const Position ALAR_ROOM_S_CENTER = { 281.064f, 0.000f, -2.389f };
std::unordered_map<uint32, bool> lastRebirthState;
std::unordered_map<uint32, bool> isAlarInPhase2;
int8 GetAlarDestinationLocationIndex(Unit* alar, Position& dest)
{
if (!alar)
return LOCATION_NONE;
float x, y, z;
if (!alar->GetMotionMaster()->GetDestination(x, y, z))
return LOCATION_NONE;
dest.Relocate(x, y, z);
const std::array<Position, 6> locations =
{
ALAR_PLATFORM_0,
ALAR_PLATFORM_1,
ALAR_PLATFORM_2,
ALAR_PLATFORM_3,
ALAR_POINT_QUILL_OR_DIVE,
ALAR_POINT_MIDDLE,
};
float minDist = std::numeric_limits<float>::max();
int8 locationIndex = LOCATION_NONE;
for (int8 i = 0; i < TOTAL_ALAR_LOCATIONS; ++i)
{
float dist = dest.GetExactDist2d(&locations[i]);
if (dist < minDist)
{
minDist = dist;
locationIndex = i;
}
}
if (minDist > 0.1f)
return LOCATION_NONE;
return locationIndex;
}
int8 GetAlarCurrentLocationIndex(Unit* alar)
{
if (!alar)
return LOCATION_NONE;
const std::array<Position, 6> locations =
{
ALAR_PLATFORM_0,
ALAR_PLATFORM_1,
ALAR_PLATFORM_2,
ALAR_PLATFORM_3,
ALAR_POINT_QUILL_OR_DIVE,
ALAR_POINT_MIDDLE,
};
float minDist = std::numeric_limits<float>::max();
int8 locationIndex = LOCATION_NONE;
for (int8 i = 0; i < TOTAL_ALAR_LOCATIONS; ++i)
{
float dist = alar->GetPosition().GetExactDist2d(&locations[i]);
if (dist < minDist)
{
minDist = dist;
locationIndex = i;
}
}
if (minDist > 0.1f)
return LOCATION_NONE;
return locationIndex;
}
void GetClosestPlatformAndGround(const Position& botPos, int8& closestPlatform, Position& ground)
{
float minDist = std::numeric_limits<float>::max();
closestPlatform = -1;
for (int8 i = 0; i < 4; ++i)
{
float dist = botPos.GetExactDist2d(&PLATFORM_POSITIONS[i]);
if (dist < minDist)
{
minDist = dist;
closestPlatform = i;
}
}
ground = GROUND_POSITIONS[closestPlatform];
}
std::pair<Unit*, Unit*> GetFirstTwoEmbersOfAlar(PlayerbotAI* botAI)
{
Unit* firstEmber = nullptr;
Unit* secondEmber = nullptr;
for (auto const& guid :
botAI->GetAiObjectContext()->GetValue<GuidVector>("possible targets no los")->Get())
{
Unit* unit = botAI->GetUnit(guid);
if (unit && unit->IsAlive() && unit->GetEntry() == NPC_EMBER_OF_ALAR)
{
if (!firstEmber)
{
firstEmber = unit;
}
else if (!secondEmber)
{
secondEmber = unit;
break;
}
}
}
return { firstEmber, secondEmber };
}
Player* GetSecondEmberTank(PlayerbotAI* botAI)
{
Player* mainTank = GetGroupMainTank(botAI, botAI->GetBot());
Player* assistTank = GetGroupAssistTank(botAI, botAI->GetBot(), 0);
bool mainTankHasMelt = mainTank && mainTank->HasAura(SPELL_MELT_ARMOR);
bool assistTankHasMelt = assistTank && assistTank->HasAura(SPELL_MELT_ARMOR);
if (mainTankHasMelt)
return mainTank;
if (assistTankHasMelt || (!mainTankHasMelt && !assistTankHasMelt))
return assistTank;
return nullptr;
}
// Void Reaver
const Position VOID_REAVER_TANK_POSITION = { 423.845f, 371.733f, 14.897f };
std::unordered_map<ObjectGuid, bool> hasReachedVoidReaverPosition;
std::unordered_map<uint32, std::vector<ArcaneOrbData>> voidReaverArcaneOrbs;
// Kael'thas Sunstrider <Lord of the Blood Elves>
const Position SANGUINAR_TANK_POSITION = { 775.478f, 39.888f, 46.780f };
const Position SANGUINAR_WAITING_POSITION = { 761.850f, 27.459f, 46.779f };
const Position TELONICUS_TANK_POSITION = { 773.717f, 44.091f, 46.780f };
const Position TELONICUS_WAITING_POSITION = { 754.347f, 31.739f, 46.796f };
const Position ADVISOR_HEAL_POSITION = { 752.171f, 19.494f, 46.779f };
const Position CAPERNIAN_WAITING_POSITION = { 743.897f, -11.575f, 46.779f };
const Position KAELTHAS_TANK_POSITION = { 799.390f, -0.837f, 48.729f };
std::unordered_map<uint32, time_t> advisorDpsWaitTimer;
// (1) First priority is an assistant Warlock (real player or bot)
// (2) If no assistant Warlock, then look for any Warlock bot
Player* GetCapernianTank(Player* bot)
{
Group* group = bot->GetGroup();
if (!group)
return nullptr;
Player* fallbackWarlock = nullptr;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member->getClass() != CLASS_WARLOCK)
continue;
if (group->IsAssistant(member->GetGUID()))
return member;
if (!fallbackWarlock && GET_PLAYERBOT_AI(member))
fallbackWarlock = member;
}
return fallbackWarlock;
}
// One Hunter will start on Sanguinar in Phase 3 with Melee to apply Armor Disruption
// (1) First priority is an assistant Hunter (real player or bot)
// (2) If no assistant Hunter, then look for any Hunter bot
bool IsDebuffHunter(Player* bot)
{
if (bot->getClass() != CLASS_HUNTER || !bot->IsAlive())
return false;
Group* group = bot->GetGroup();
if (!group)
return false;
Player* fallbackHunter = nullptr;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member->getClass() != CLASS_HUNTER)
continue;
if (group->IsAssistant(member->GetGUID()))
return member == bot;
if (!fallbackHunter && GET_PLAYERBOT_AI(member))
fallbackHunter = member;
}
return fallbackHunter == bot;
}
bool IsAnyLegendaryWeaponDead(Player* bot)
{
static const std::array<uint32, 7> weaponEntries =
{
NPC_STAFF_OF_DISINTEGRATION,
NPC_COSMIC_INFUSER,
NPC_INFINITY_BLADES,
NPC_WARP_SLICER,
NPC_PHASESHIFT_BULWARK,
NPC_NETHERSTRAND_LONGBOW,
NPC_DEVASTATION
};
constexpr float searchRadius = 100.0f;
for (uint32 entry : weaponEntries)
{
Creature* weapon = bot->FindNearestCreature(entry, searchRadius, false);
if (weapon && !weapon->IsAlive())
return true;
}
return false;
}
bool HasEquippableItemForSlot(Player* bot, uint8 slot)
{
for (uint8 i = 0; i < 5; ++i)
{
uint8 bag = (i == 0) ? INVENTORY_SLOT_BAG_0 : (INVENTORY_SLOT_BAG_START + i - 1);
uint8 startSlot = (bag == INVENTORY_SLOT_BAG_0) ? INVENTORY_SLOT_ITEM_START : 0;
uint8 endSlot = (bag == INVENTORY_SLOT_BAG_0) ? INVENTORY_SLOT_ITEM_END :
(bot->GetBagByPos(bag) ? bot->GetBagByPos(bag)->GetBagSize() : 0);
for (uint8 bagSlot = startSlot; bagSlot < endSlot; ++bagSlot)
{
Item* item = bot->GetItemByPos(bag, bagSlot);
if (!item || !item->GetTemplate())
continue;
uint16 dest = 0;
if (bot->CanEquipItem(slot, dest, item, false) == EQUIP_ERR_OK)
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,158 @@
#ifndef _PLAYERBOT_RAIDTEMPESTKEEPHELPERS_H_
#define _PLAYERBOT_RAIDTEMPESTKEEPHELPERS_H_
#include <ctime>
#include <unordered_map>
#include <vector>
#include "AiObject.h"
#include "Position.h"
#include "Unit.h"
namespace TempestKeepHelpers
{
enum TempestKeepSpells
{
// Trash
SPELL_ARCANE_FLURRY = 37268,
// Al'ar
SPELL_REBIRTH_PHASE2 = 34342,
SPELL_REBIRTH_DIVE = 35369,
SPELL_MELT_ARMOR = 35410,
// Void Reaver
SPELL_ARCANE_ORB = 34172,
// High Astromancer Solarian
SPELL_SOLARIAN_TRANSFORM = 39117,
SPELL_WRATH_OF_THE_ASTROMANCER = 42783,
// Kael'thas Sunstrider
SPELL_PERMANENT_FEIGN_DEATH = 29266,
SPELL_GRAVITY_LAPSE = 39432,
SPELL_KAEL_FULL_POWER = 36187,
SPELL_MENTAL_PROTECTION_FIELD = 36480, // Staff of Disintegration
SPELL_ARCANE_BARRIER = 36481, // Phaseshift Bulwark
SPELL_KAELTHAS_MIND_CONTROL = 36797,
SPELL_SHOCK_BARRIER = 36815,
SPELL_STAFF_FROSTBOLT = 36990,
// Hunter
SPELL_MISDIRECTION = 35079,
// Priest
SPELL_FEAR_WARD = 6346,
};
enum TempestKeepNPCs
{
// Al'ar
NPC_EMBER_OF_ALAR = 19551,
NPC_FLAME_PATCH = 20602,
// High Astromancer Solarian
NPC_SOLARIUM_PRIEST = 18806,
// Kael'thas Sunstrider
NPC_KAELTHAS_SUNSTRIDER = 19622,
NPC_NETHERSTRAND_LONGBOW = 21268,
NPC_DEVASTATION = 21269,
NPC_COSMIC_INFUSER = 21270,
NPC_INFINITY_BLADES = 21271, // Item is singular, but NPC is plural
NPC_WARP_SLICER = 21272,
NPC_PHASESHIFT_BULWARK = 21273,
NPC_STAFF_OF_DISINTEGRATION = 21274,
// NPC_NETHER_VAPOR = 21002, // Unimplemented in AC; method needed if fixed
NPC_PHOENIX = 21362,
NPC_PHOENIX_EGG = 21364,
NPC_FLAME_STRIKE_TRIGGER = 21369,
};
enum TempestKeepItems
{
// Kael'thas Sunstrider
ITEM_WARP_SLICER = 30311,
ITEM_INFINITY_BLADE = 30312,
ITEM_STAFF_OF_DISINTEGRATION = 30313,
ITEM_PHASESHIFT_BULWARK = 30314,
ITEM_DEVASTATION = 30316,
ITEM_COSMIC_INFUSER = 30317,
ITEM_NETHERSTRAND_LONGBOW = 30318,
ITEM_NETHER_SPIKES = 30319,
};
// General
constexpr uint32 TEMPEST_KEEP_MAP_ID = 550;
Unit* GetNearestNonTankPlayerInRadius(PlayerbotAI* botAI, Player* bot, float radius);
std::vector<Unit*> GetAllHazardTriggers(Player* bot, uint32 npcEntry, float searchRadius);
Position FindSafestNearbyPosition(Player* bot, const std::vector<Unit*>& hazards,
float hazardRadius, const Position* center = nullptr);
bool IsPathSafeFromHazards(
const Position& start, const Position& end, const std::vector<Unit*>& hazards,
float hazardRadius);
// Al'ar <Phoenix God>
enum AlarLocationIndex
{
PLATFORM_0_IDX,
PLATFORM_1_IDX,
PLATFORM_2_IDX,
PLATFORM_3_IDX,
POINT_QUILL_OR_DIVE_IDX,
POINT_MIDDLE_IDX,
LOCATION_NONE = -1
};
constexpr float ALAR_BALCONY_Z = 17.0f;
extern const Position ALAR_PLATFORM_0;
extern const Position ALAR_PLATFORM_1;
extern const Position ALAR_PLATFORM_2;
extern const Position ALAR_PLATFORM_3;
extern const std::array<Position, 4> PLATFORM_POSITIONS;
extern const Position ALAR_GROUND_0;
extern const Position ALAR_GROUND_1;
extern const Position ALAR_GROUND_2;
extern const Position ALAR_GROUND_3;
extern const std::array<Position, 4> GROUND_POSITIONS;
extern const Position ALAR_ROOM_CENTER;
extern const Position ALAR_POINT_QUILL_OR_DIVE;
extern const Position ALAR_POINT_MIDDLE;
extern const Position ALAR_SE_RAMP_BASE;
extern const Position ALAR_SW_RAMP_BASE;
extern const Position ALAR_ROOM_S_CENTER;
constexpr uint8 TOTAL_ALAR_LOCATIONS = 6;
extern std::unordered_map<uint32, bool> lastRebirthState;
extern std::unordered_map<uint32, bool> isAlarInPhase2;
int8 GetAlarDestinationLocationIndex(Unit* alar, Position& dest);
int8 GetAlarCurrentLocationIndex(Unit* alar);
void GetClosestPlatformAndGround(
const Position& botPos, int8& closestPlatform, Position& ground);
std::pair<Unit*, Unit*> GetFirstTwoEmbersOfAlar(PlayerbotAI* botAI);
Player* GetSecondEmberTank(PlayerbotAI* botAI);
// Void Reaver
extern const Position VOID_REAVER_TANK_POSITION;
extern std::unordered_map<ObjectGuid, bool> hasReachedVoidReaverPosition;
struct ArcaneOrbData
{
Position destination;
uint32 castTime;
};
extern std::unordered_map<uint32, std::vector<ArcaneOrbData>> voidReaverArcaneOrbs;
// Kael'thas Sunstrider <Lord of the Blood Elves>
extern const Position SANGUINAR_TANK_POSITION;
extern const Position SANGUINAR_WAITING_POSITION;
extern const Position TELONICUS_TANK_POSITION;
extern const Position TELONICUS_WAITING_POSITION;
extern const Position CAPERNIAN_WAITING_POSITION;
extern const Position ADVISOR_HEAL_POSITION;
extern const Position KAELTHAS_TANK_POSITION;
extern std::unordered_map<uint32, time_t> advisorDpsWaitTimer;
Player* GetCapernianTank(Player* bot);
bool IsDebuffHunter(Player* bot);
bool IsAnyLegendaryWeaponDead(Player* bot);
bool HasEquippableItemForSlot(Player* bot, uint8 slot);
}
#endif

View File

@@ -0,0 +1,54 @@
#ifndef _PLAYERBOT_RAIDTEMPESTKEEPKAELTHASBOSSAI_H_
#define _PLAYERBOT_RAIDTEMPESTKEEPKAELTHASBOSSAI_H_
#include "ScriptedCreature.h"
enum KTYells
{
};
enum KTPhases
{
PHASE_NONE = 0,
PHASE_SINGLE_ADVISOR = 1,
PHASE_WEAPONS = 2,
PHASE_TRANSITION = 3,
PHASE_ALL_ADVISORS = 4,
PHASE_FINAL = 5
};
enum KTActions
{
};
struct boss_kaelthas : public BossAI
{
boss_kaelthas(Creature* creature);
void PrepareAdvisors();
void SetRoomState(GOState state);
void Reset() override;
void AttackStart(Unit* who) override;
void MoveInLineOfSight(Unit* who) override;
void KilledUnit(Unit* victim) override;
void JustSummoned(Creature* summon) override;
void SpellHit(Unit* caster, SpellInfo const* spell) override;
void MovementInform(uint32 type, uint32 point) override;
void ExecuteMiddleEvent();
void IntroduceNewAdvisor(KTYells talkIntroduction, KTActions kaelAction);
void PhaseEnchantedWeaponsExecute();
void PhaseAllAdvisorsExecute();
void PhaseKaelExecute();
void UpdateAI(uint32 diff) override;
bool CheckEvadeIfOutOfCombatArea() const override;
void JustDied(Unit* killer) override;
uint32 GetPhase() const { return _phase; } // This is the only addition to the existing class
private:
uint32 _phase;
uint8 _advisorsAlive;
bool _transitionSceneReached = false;
};
#endif

View File

@@ -0,0 +1,47 @@
#include "RaidTempestKeepHelpers.h"
#include "ObjectAccessor.h"
#include "Player.h"
#include "ScriptMgr.h"
#include "Spell.h"
#include "Timer.h"
using namespace TempestKeepHelpers;
class BossListenerScript : public AllSpellScript
{
public:
BossListenerScript() : AllSpellScript("BossListenerScript") { }
void OnSpellCast(Spell* spell, Unit* caster, SpellInfo const* spellInfo, bool /*skipCheck*/) override
{
if (spellInfo->Id != SPELL_ARCANE_ORB)
return;
std::list<TargetInfo> const& targets = *spell->GetUniqueTargetInfo();
if (targets.empty())
return;
Player* target = ObjectAccessor::GetPlayer(*caster, targets.front().targetGUID);
if (!target)
return;
auto& orbs = voidReaverArcaneOrbs[caster->GetMap()->GetInstanceId()];
uint32 currentTime = getMSTime();
ArcaneOrbData orbData;
orbData.destination = target->GetPosition();
orbData.castTime = currentTime;
orbs.push_back(orbData);
orbs.erase(std::remove_if(orbs.begin(), orbs.end(),
[currentTime](const ArcaneOrbData& orb) {
return getMSTimeDiff(orb.castTime, currentTime) > 5000;
}), orbs.end());
}
};
void AddSC_TempestKeepBotScripts()
{
new BossListenerScript();
}

View File

@@ -37,12 +37,14 @@
#include "Ai/Raid/BlackwingLair/RaidBwlTriggerContext.h" #include "Ai/Raid/BlackwingLair/RaidBwlTriggerContext.h"
#include "Ai/Raid/Karazhan/RaidKarazhanActionContext.h" #include "Ai/Raid/Karazhan/RaidKarazhanActionContext.h"
#include "Ai/Raid/Karazhan/RaidKarazhanTriggerContext.h" #include "Ai/Raid/Karazhan/RaidKarazhanTriggerContext.h"
#include "Ai/Raid/Magtheridon/RaidMagtheridonActionContext.h"
#include "Ai/Raid/Magtheridon/RaidMagtheridonTriggerContext.h"
#include "Ai/Raid/GruulsLair/RaidGruulsLairActionContext.h" #include "Ai/Raid/GruulsLair/RaidGruulsLairActionContext.h"
#include "Ai/Raid/GruulsLair/RaidGruulsLairTriggerContext.h" #include "Ai/Raid/GruulsLair/RaidGruulsLairTriggerContext.h"
#include "Ai/Raid/Magtheridon/RaidMagtheridonActionContext.h"
#include "Ai/Raid/Magtheridon/RaidMagtheridonTriggerContext.h"
#include "Ai/Raid/SerpentshrineCavern/RaidSSCActionContext.h" #include "Ai/Raid/SerpentshrineCavern/RaidSSCActionContext.h"
#include "Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h" #include "Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h"
#include "Ai/Raid/TempestKeep/RaidTempestKeepActionContext.h"
#include "Ai/Raid/TempestKeep/RaidTempestKeepTriggerContext.h"
#include "Ai/Raid/EyeOfEternity/RaidEoEActionContext.h" #include "Ai/Raid/EyeOfEternity/RaidEoEActionContext.h"
#include "Ai/Raid/EyeOfEternity/RaidEoETriggerContext.h" #include "Ai/Raid/EyeOfEternity/RaidEoETriggerContext.h"
#include "Ai/Raid/VaultOfArchavon/RaidVoAActionContext.h" #include "Ai/Raid/VaultOfArchavon/RaidVoAActionContext.h"
@@ -115,9 +117,10 @@ void AiObjectContext::BuildSharedActionContexts(SharedNamedObjectContextList<Act
actionContexts.Add(new RaidMcActionContext()); actionContexts.Add(new RaidMcActionContext());
actionContexts.Add(new RaidBwlActionContext()); actionContexts.Add(new RaidBwlActionContext());
actionContexts.Add(new RaidKarazhanActionContext()); actionContexts.Add(new RaidKarazhanActionContext());
actionContexts.Add(new RaidMagtheridonActionContext());
actionContexts.Add(new RaidGruulsLairActionContext()); actionContexts.Add(new RaidGruulsLairActionContext());
actionContexts.Add(new RaidMagtheridonActionContext());
actionContexts.Add(new RaidSSCActionContext()); actionContexts.Add(new RaidSSCActionContext());
actionContexts.Add(new RaidTempestKeepActionContext());
actionContexts.Add(new RaidOsActionContext()); actionContexts.Add(new RaidOsActionContext());
actionContexts.Add(new RaidEoEActionContext()); actionContexts.Add(new RaidEoEActionContext());
actionContexts.Add(new RaidVoAActionContext()); actionContexts.Add(new RaidVoAActionContext());
@@ -150,9 +153,10 @@ void AiObjectContext::BuildSharedTriggerContexts(SharedNamedObjectContextList<Tr
triggerContexts.Add(new RaidMcTriggerContext()); triggerContexts.Add(new RaidMcTriggerContext());
triggerContexts.Add(new RaidBwlTriggerContext()); triggerContexts.Add(new RaidBwlTriggerContext());
triggerContexts.Add(new RaidKarazhanTriggerContext()); triggerContexts.Add(new RaidKarazhanTriggerContext());
triggerContexts.Add(new RaidMagtheridonTriggerContext());
triggerContexts.Add(new RaidGruulsLairTriggerContext()); triggerContexts.Add(new RaidGruulsLairTriggerContext());
triggerContexts.Add(new RaidMagtheridonTriggerContext());
triggerContexts.Add(new RaidSSCTriggerContext()); triggerContexts.Add(new RaidSSCTriggerContext());
triggerContexts.Add(new RaidTempestKeepTriggerContext());
triggerContexts.Add(new RaidOsTriggerContext()); triggerContexts.Add(new RaidOsTriggerContext());
triggerContexts.Add(new RaidEoETriggerContext()); triggerContexts.Add(new RaidEoETriggerContext());
triggerContexts.Add(new RaidVoATriggerContext()); triggerContexts.Add(new RaidVoATriggerContext());

View File

@@ -1556,6 +1556,9 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
case 548: case 548:
strategyName = "ssc"; // Serpentshrine Cavern strategyName = "ssc"; // Serpentshrine Cavern
break; break;
case 550:
strategyName = "tempestkeep"; // Tempest Keep
break;
case 565: case 565:
strategyName = "gruulslair"; // Gruul's Lair strategyName = "gruulslair"; // Gruul's Lair
break; break;
@@ -1797,98 +1800,104 @@ bool PlayerbotAI::IsCombo(Player* player)
bool PlayerbotAI::IsRangedDps(Player* player, bool bySpec) { return IsRanged(player, bySpec) && IsDps(player, bySpec); } bool PlayerbotAI::IsRangedDps(Player* player, bool bySpec) { return IsRanged(player, bySpec) && IsDps(player, bySpec); }
bool PlayerbotAI::IsAssistHealOfIndex(Player* player, int index, bool ignoreDeadPlayers) bool PlayerbotAI::IsAssistHealOfIndex(Player* player, uint8 index, bool ignoreDeadPlayers)
{ {
if (!IsHeal(player))
return false;
if (ignoreDeadPlayers && !player->IsAlive())
return false;
Group* group = player->GetGroup(); Group* group = player->GetGroup();
if (!group) if (!group)
return false; return false;
int counter = 0; uint8 totalAssistants = 0;
uint8 assistantsBeforePlayer = 0;
uint8 nonAssistantsBeforePlayer = 0;
bool playerFound = false;
// First, assistants
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
Player* member = ref->GetSource(); Player* member = ref->GetSource();
if (!member) if (!member || (ignoreDeadPlayers && !member->IsAlive()) || !IsHeal(member))
continue; continue;
if (ignoreDeadPlayers && !member->IsAlive()) bool isAssistant = group->IsAssistant(member->GetGUID());
continue;
if (group->IsAssistant(member->GetGUID()) && IsHeal(member)) if (isAssistant)
totalAssistants++;
if (member == player)
playerFound = true;
else if (!playerFound)
{ {
if (index == counter) if (isAssistant)
return player == member; assistantsBeforePlayer++;
counter++; else
nonAssistantsBeforePlayer++;
} }
} }
// If not enough assistants, get non-assistants if (!playerFound)
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) return false;
{
Player* member = ref->GetSource();
if (!member)
continue;
if (ignoreDeadPlayers && !member->IsAlive()) // If the player is an assistant, their index is just the number of assistants before them.
continue; // If they are a non-assistant, their index is shifted by the total number of assistants.
uint8 playerIndex = group->IsAssistant(player->GetGUID())
? assistantsBeforePlayer : (totalAssistants + nonAssistantsBeforePlayer);
if (!group->IsAssistant(member->GetGUID()) && IsHeal(member)) return playerIndex == index;
{
if (index == counter)
return player == member;
counter++;
}
}
return false;
} }
bool PlayerbotAI::IsAssistRangedDpsOfIndex(Player* player, int index, bool ignoreDeadPlayers) bool PlayerbotAI::IsAssistRangedDpsOfIndex(Player* player, uint8 index, bool ignoreDeadPlayers)
{ {
if (!IsRangedDps(player))
return false;
if (ignoreDeadPlayers && !player->IsAlive())
return false;
Group* group = player->GetGroup(); Group* group = player->GetGroup();
if (!group) if (!group)
return false; return false;
int counter = 0; uint8 totalAssistants = 0;
uint8 assistantsBeforePlayer = 0;
uint8 nonAssistantsBeforePlayer = 0;
bool playerFound = false;
// First, assistants
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
Player* member = ref->GetSource(); Player* member = ref->GetSource();
if (!member) if (!member || (ignoreDeadPlayers && !member->IsAlive()) || !IsRangedDps(member))
continue; continue;
if (ignoreDeadPlayers && !member->IsAlive()) bool isAssistant = group->IsAssistant(member->GetGUID());
continue;
if (group->IsAssistant(member->GetGUID()) && IsRangedDps(member)) if (isAssistant)
totalAssistants++;
if (member == player)
playerFound = true;
else if (!playerFound)
{ {
if (index == counter) if (isAssistant)
return player == member; assistantsBeforePlayer++;
counter++; else
nonAssistantsBeforePlayer++;
} }
} }
// If not enough assistants, get non-assistants if (!playerFound)
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) return false;
{
Player* member = ref->GetSource();
if (!member)
continue;
if (ignoreDeadPlayers && !member->IsAlive()) // If the player is an assistant, their index is just the number of assistants before them.
continue; // If they are a non-assistant, their index is shifted by the total number of assistants.
uint8 playerIndex = group->IsAssistant(player->GetGUID())
? assistantsBeforePlayer : (totalAssistants + nonAssistantsBeforePlayer);
if (!group->IsAssistant(member->GetGUID()) && IsRangedDps(member)) return playerIndex == index;
{
if (index == counter)
return player == member;
counter++;
}
}
return false;
} }
bool PlayerbotAI::HasAggro(Unit* unit) bool PlayerbotAI::HasAggro(Unit* unit)
@@ -2226,43 +2235,44 @@ bool PlayerbotAI::IsDps(Player* player, bool bySpec)
return false; return false;
} }
bool PlayerbotAI::IsMainTank(Player* player) bool PlayerbotAI::IsMainTank(Player* player, bool ignoreMemberFlag)
{ {
Group* group = player->GetGroup(); Group* group = player->GetGroup();
if (!group) if (!group)
{
return IsTank(player); return IsTank(player);
}
ObjectGuid mainTank = ObjectGuid(); ObjectGuid mainTank = ObjectGuid();
Group::MemberSlotList const& slots = group->GetMemberSlots();
for (Group::member_citerator itr = slots.begin(); itr != slots.end(); ++itr) // (1) Check for main tank flag (any class or spec)
if (!ignoreMemberFlag)
{ {
if (itr->flags & MEMBER_FLAG_MAINTANK) Group::MemberSlotList const& slots = group->GetMemberSlots();
for (Group::member_citerator itr = slots.begin(); itr != slots.end(); ++itr)
{ {
mainTank = itr->guid; if (itr->flags & MEMBER_FLAG_MAINTANK)
break; {
mainTank = itr->guid;
break;
}
} }
if (mainTank != ObjectGuid::Empty)
return player->GetGUID() == mainTank;
} }
if (mainTank != ObjectGuid::Empty) // (2) If no main tank flag, return the first tank
{ if (!IsTank(player) || !player->IsAlive())
return player->GetGUID() == mainTank; return false;
}
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
Player* member = ref->GetSource(); Player* member = ref->GetSource();
if (!member) if (!member)
{
continue; continue;
}
if (IsTank(member) && member->IsAlive()) if (IsTank(member) && member->IsAlive())
{
return player->GetGUID() == member->GetGUID(); return player->GetGUID() == member->GetGUID();
}
} }
return false; return false;
@@ -2281,47 +2291,31 @@ bool PlayerbotAI::IsBotMainTank(Player* player)
return false; return false;
if (IsMainTank(player)) if (IsMainTank(player))
{
return true; return true;
}
Group* group = player->GetGroup(); Group* group = player->GetGroup();
if (!group) if (!group)
{ return true;
return true; // If no group, consider the bot as main tank
}
int32 botAssistTankIndex = GetAssistTankIndex(player); int32 botAssistTankIndex = GetAssistTankIndex(player);
if (botAssistTankIndex == -1) if (botAssistTankIndex == -1)
{
return false; return false;
}
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{ {
Player* member = gref->GetSource(); Player* member = gref->GetSource();
if (!member) if (!member)
{
continue; continue;
}
int32 memberAssistTankIndex = GetAssistTankIndex(member); int32 memberAssistTankIndex = GetAssistTankIndex(member);
if (memberAssistTankIndex == -1) if (memberAssistTankIndex == -1)
{
continue; continue;
}
if (memberAssistTankIndex == botAssistTankIndex && player == member) if (memberAssistTankIndex == botAssistTankIndex && player == member)
{
return true; return true;
}
if (memberAssistTankIndex < botAssistTankIndex && member->GetSession()->IsBot()) if (memberAssistTankIndex < botAssistTankIndex && member->GetSession()->IsBot())
{
return false; return false;
}
return false;
} }
return false; return false;
@@ -2331,73 +2325,76 @@ uint32 PlayerbotAI::GetGroupTankNum(Player* player)
{ {
Group* group = player->GetGroup(); Group* group = player->GetGroup();
if (!group) if (!group)
{
return 0; return 0;
}
uint32 result = 0; uint32 result = 0;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
Player* member = ref->GetSource(); Player* member = ref->GetSource();
if (!member) if (!member)
{
continue; continue;
}
if (IsTank(member) && member->IsAlive()) if (IsTank(member) && member->IsAlive())
{
result++; result++;
}
} }
return result; return result;
} }
bool PlayerbotAI::IsAssistTank(Player* player) { return IsTank(player) && !IsMainTank(player); } bool PlayerbotAI::IsAssistTank(Player* player)
bool PlayerbotAI::IsAssistTankOfIndex(Player* player, int index, bool ignoreDeadPlayers)
{ {
return IsTank(player) && !IsMainTank(player);
}
bool PlayerbotAI::IsAssistTankOfIndex(Player* player, uint8 index, bool ignoreDeadPlayers)
{
if (!IsAssistTank(player))
return false;
if (ignoreDeadPlayers && !player->IsAlive())
return false;
Group* group = player->GetGroup(); Group* group = player->GetGroup();
if (!group) if (!group)
return false; return false;
int counter = 0; uint8 totalAssistants = 0;
uint8 assistantsBeforePlayer = 0;
uint8 nonAssistantsBeforePlayer = 0;
bool playerFound = false;
// First, assistants
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{ {
Player* member = ref->GetSource(); Player* member = ref->GetSource();
if (!member) if (!member || (ignoreDeadPlayers && !member->IsAlive()) || !IsAssistTank(member))
continue; continue;
if (ignoreDeadPlayers && !member->IsAlive()) bool isAssistant = group->IsAssistant(member->GetGUID());
continue;
if (group->IsAssistant(member->GetGUID()) && IsAssistTank(member)) if (isAssistant)
totalAssistants++;
if (member == player)
playerFound = true;
else if (!playerFound)
{ {
if (index == counter) if (isAssistant)
return player == member; assistantsBeforePlayer++;
counter++; else
nonAssistantsBeforePlayer++;
} }
} }
// If not enough assistants, get non-assistants if (!playerFound)
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) return false;
{
Player* member = ref->GetSource();
if (!member)
continue;
if (ignoreDeadPlayers && !member->IsAlive()) // If the player is an assistant, their index is just the number of assistants before them.
continue; // If they are a non-assistant, their index is shifted by the total number of assistants.
uint8 playerIndex = group->IsAssistant(player->GetGUID())
? assistantsBeforePlayer : (totalAssistants + nonAssistantsBeforePlayer);
if (!group->IsAssistant(member->GetGUID()) && IsAssistTank(member)) return playerIndex == index;
{
if (index == counter)
return player == member;
counter++;
}
}
return false;
} }
namespace acore namespace acore

View File

@@ -423,12 +423,12 @@ public:
static bool IsRangedDps(Player* player, bool bySpec = false); static bool IsRangedDps(Player* player, bool bySpec = false);
static bool IsCombo(Player* player); static bool IsCombo(Player* player);
static bool IsBotMainTank(Player* player); static bool IsBotMainTank(Player* player);
static bool IsMainTank(Player* player); static bool IsMainTank(Player* player, bool ignoreMemberFlag = false);
static uint32 GetGroupTankNum(Player* player); static uint32 GetGroupTankNum(Player* player);
static bool IsAssistTank(Player* player); static bool IsAssistTank(Player* player);
static bool IsAssistTankOfIndex(Player* player, int index, bool ignoreDeadPlayers = false); static bool IsAssistTankOfIndex(Player* player, uint8 index, bool ignoreDeadPlayers = false);
static bool IsAssistHealOfIndex(Player* player, int index, bool ignoreDeadPlayers = false); static bool IsAssistHealOfIndex(Player* player, uint8 index, bool ignoreDeadPlayers = false);
static bool IsAssistRangedDpsOfIndex(Player* player, int index, bool ignoreDeadPlayers = false); static bool IsAssistRangedDpsOfIndex(Player* player, uint8 index, bool ignoreDeadPlayers = false);
bool HasAggro(Unit* unit); bool HasAggro(Unit* unit);
static int32 GetAssistTankIndex(Player* player); static int32 GetAssistTankIndex(Player* player);
int32 GetGroupSlotIndex(Player* player); int32 GetGroupSlotIndex(Player* player);

View File

@@ -520,6 +520,8 @@ public:
void AddPlayerbotsSecureLoginScripts(); void AddPlayerbotsSecureLoginScripts();
void AddSC_TempestKeepBotScripts();
void AddPlayerbotsScripts() void AddPlayerbotsScripts()
{ {
new PlayerbotsDatabaseScript(); new PlayerbotsDatabaseScript();
@@ -532,4 +534,5 @@ void AddPlayerbotsScripts()
AddPlayerbotsSecureLoginScripts(); AddPlayerbotsSecureLoginScripts();
AddPlayerbotsCommandscripts(); AddPlayerbotsCommandscripts();
PlayerBotsGuildValidationScript(); PlayerBotsGuildValidationScript();
AddSC_TempestKeepBotScripts();
} }