Files
mod-playerbots/src/Bot/PlayerbotAI.h
Crow be2cf436ea 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>
2026-02-27 16:04:10 -08:00

655 lines
21 KiB
C++

/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_PLAYERbotAI_H
#define _PLAYERBOT_PLAYERbotAI_H
#include <stack>
#include "Chat.h"
#include "ChatFilter.h"
#include "ChatHelper.h"
#include "CreatureData.h"
#include "Event.h"
#include "Item.h"
#include "NewRpgInfo.h"
#include "NewRpgStrategy.h"
#include "PlayerbotAIBase.h"
#include "PlayerbotAIConfig.h"
#include "PlayerbotSecurity.h"
#include "PlayerbotTextMgr.h"
#include "SpellAuras.h"
#include "Util.h"
#include "WorldPacket.h"
class AiObjectContext;
class Creature;
class Engine;
class ExternalEventHelper;
class Gameobject;
class Item;
class ObjectGuid;
class Player;
class PlayerbotMgr;
class Spell;
class SpellInfo;
class Unit;
class WorldObject;
class WorldPosition;
struct CreatureData;
struct GameObjectData;
enum StrategyType : uint32;
enum HealingItemId
{
HEALTHSTONE = 5512,
MAJOR_HEALING_POTION = 13446,
WHIPPER_ROOT_TUBER = 11951,
NIGHT_DRAGON_BREATH = 11952,
LIMITED_INVULNERABILITY_POTION = 3387,
GREATER_DREAMLESS_SLEEP_POTION = 22886,
SUPERIOR_HEALING_POTION = 3928,
CRYSTAL_RESTORE = 11564,
DREAMLESS_SLEEP_POTION = 12190,
GREATER_HEALING_POTION = 1710,
HEALING_POTION = 929,
LESSER_HEALING_POTION = 858,
DISCOLORED_HEALING_POTION = 3391,
MINOR_HEALING_POTION = 118,
VOLATILE_HEALING_POTION = 28100,
SUPER_HEALING_POTION = 22829,
CRYSTAL_HEALING_POTION = 13462,
FEL_REGENERATION_POTION = 28101,
MAJOR_DREAMLESS_SLEEP_POTION = 20002
};
enum BotState
{
BOT_STATE_COMBAT = 0,
BOT_STATE_NON_COMBAT = 1,
BOT_STATE_DEAD = 2,
BOT_STATE_MAX
};
bool IsAlliance(uint8 race);
class PlayerbotChatHandler : protected ChatHandler
{
public:
explicit PlayerbotChatHandler(Player* pMasterPlayer);
void sysmessage(std::string const str) { SendSysMessage(str.c_str()); }
uint32 extractQuestId(std::string const str);
uint32 extractSpellId(std::string const str)
{
char* source = (char*)str.c_str();
return extractSpellIdFromLink(source);
}
};
class MinValueCalculator
{
public:
MinValueCalculator(float def = 0.0f) : param(nullptr), minValue(def) {}
void probe(float value, void* p)
{
if (!param || minValue >= value)
{
minValue = value;
param = p;
}
}
void* param;
float minValue;
};
enum ChatChannelSource
{
SRC_GUILD,
SRC_WORLD,
SRC_GENERAL,
SRC_TRADE,
SRC_LOOKING_FOR_GROUP,
SRC_LOCAL_DEFENSE,
SRC_WORLD_DEFENSE,
SRC_GUILD_RECRUITMENT,
SRC_SAY,
SRC_WHISPER,
SRC_EMOTE,
SRC_TEXT_EMOTE,
SRC_YELL,
SRC_PARTY,
SRC_RAID,
SRC_UNDEFINED
};
static std::map<ChatChannelSource, std::string> ChatChannelSourceStr = {
{SRC_GUILD, "SRC_GUILD"},
{SRC_WORLD, "SRC_WORLD"},
{SRC_GENERAL, "SRC_GENERAL"},
{SRC_TRADE, "SRC_TRADE"},
{SRC_LOOKING_FOR_GROUP, "SRC_LOOKING_FOR_GROUP"},
{SRC_LOCAL_DEFENSE, "SRC_LOCAL_DEFENSE"},
{SRC_WORLD_DEFENSE, "SRC_WORLD_DEFENSE"},
{SRC_GUILD_RECRUITMENT, "SRC_GUILD_RECRUITMENT"},
{SRC_SAY, "SRC_SAY"},
{SRC_WHISPER, "SRC_WHISPER"},
{SRC_EMOTE, "SRC_EMOTE"},
{SRC_TEXT_EMOTE, "SRC_TEXT_EMOTE"},
{SRC_YELL, "SRC_YELL"},
{SRC_PARTY, "SRC_PARTY"},
{SRC_RAID, "SRC_RAID"},
{SRC_UNDEFINED, "SRC_UNDEFINED"}};
enum ChatChannelId
{
GENERAL = 1,
TRADE = 2,
LOCAL_DEFENSE = 22,
WORLD_DEFENSE = 23,
LOOKING_FOR_GROUP = 26,
GUILD_RECRUITMENT = 25,
};
enum RoguePoisonId
{
INSTANT_POISON = 6947,
INSTANT_POISON_II = 6949,
INSTANT_POISON_III = 6950,
INSTANT_POISON_IV = 8926,
INSTANT_POISON_V = 8927,
INSTANT_POISON_VI = 8928,
INSTANT_POISON_VII = 21927,
INSTANT_POISON_VIII = 43230,
INSTANT_POISON_IX = 43231,
DEADLY_POISON = 2892,
DEADLY_POISON_II = 2893,
DEADLY_POISON_III = 8984,
DEADLY_POISON_IV = 8985,
DEADLY_POISON_V = 20844,
DEADLY_POISON_VI = 22053,
DEADLY_POISON_VII = 22054,
DEADLY_POISON_VIII = 43232,
DEADLY_POISON_IX = 43233
};
enum SharpeningStoneId
{
ROUGH_SHARPENING_STONE = 2862,
COARSE_SHARPENING_STONE = 2863,
HEAVY_SHARPENING_STONE = 2871,
SOLID_SHARPENING_STONE = 7964,
DENSE_SHARPENING_STONE = 12404,
ELEMENTAL_SHARPENING_STONE = 18262,
FEL_SHARPENING_STONE = 23528,
ADAMANTITE_SHARPENING_STONE = 23529
};
enum WeightstoneId
{
ROUGH_WEIGHTSTONE = 3239,
COARSE_WEIGHTSTONE = 3240,
HEAVY_WEIGHTSTONE = 3241,
SOLID_WEIGHTSTONE = 7965,
DENSE_WEIGHTSTONE = 12643,
FEL_WEIGHTSTONE = 28420,
ADAMANTITE_WEIGHTSTONE = 28421
};
enum WizardOilId
{
MINOR_WIZARD_OIL = 20744,
LESSER_WIZARD_OIL = 20746,
WIZARD_OIL = 20750,
BRILLIANT_WIZARD_OIL = 20749,
SUPERIOR_WIZARD_OIL = 22522
};
enum ManaOilId
{
MINOR_MANA_OIL = 20745,
LESSER_MANA_OIL = 20747,
BRILLIANT_MANA_OIL = 20748,
SUPERIOR_MANA_OIL = 22521
};
enum class BotTypeNumber : uint8
{
ACTIVITY_TYPE_NUMBER = 1,
GROUPER_TYPE_NUMBER = 2,
GUILDER_TYPE_NUMBER = 3,
};
enum class GrouperType : uint8
{
SOLO = 0,
MEMBER = 1,
LEADER_2 = 2,
LEADER_3 = 3,
LEADER_4 = 4,
LEADER_5 = 5
};
enum class GuilderType : uint8
{
SOLO = 0,
TINY = 30,
SMALL = 50,
MEDIUM = 70,
LARGE = 120,
VERY_LARGE = 250
};
enum ActivityType
{
GRIND_ACTIVITY = 1,
RPG_ACTIVITY = 2,
TRAVEL_ACTIVITY = 3,
OUT_OF_PARTY_ACTIVITY = 4,
PACKET_ACTIVITY = 5,
DETAILED_MOVE_ACTIVITY = 6,
PARTY_ACTIVITY = 7,
ALL_ACTIVITY = 8,
MAX_ACTIVITY_TYPE
};
enum BotRoles : uint8
{
BOT_ROLE_NONE = 0x00,
BOT_ROLE_TANK = 0x01,
BOT_ROLE_HEALER = 0x02,
BOT_ROLE_DPS = 0x04
};
enum HUNTER_TABS
{
HUNTER_TAB_BEAST_MASTERY,
HUNTER_TAB_MARKSMANSHIP,
HUNTER_TAB_SURVIVAL,
};
enum ROGUE_TABS
{
ROGUE_TAB_ASSASSINATION,
ROGUE_TAB_COMBAT,
ROGUE_TAB_SUBTLETY,
};
enum PRIEST_TABS
{
PRIEST_TAB_DISCIPLINE,
PRIEST_TAB_HOLY,
PRIEST_TAB_SHADOW,
};
enum DEATH_KNIGHT_TABS
{
DEATH_KNIGHT_TAB_BLOOD,
DEATH_KNIGHT_TAB_FROST,
DEATH_KNIGHT_TAB_UNHOLY,
};
enum DRUID_TABS
{
DRUID_TAB_BALANCE,
DRUID_TAB_FERAL,
DRUID_TAB_RESTORATION,
};
enum MAGE_TABS
{
MAGE_TAB_ARCANE,
MAGE_TAB_FIRE,
MAGE_TAB_FROST,
};
enum SHAMAN_TABS
{
SHAMAN_TAB_ELEMENTAL,
SHAMAN_TAB_ENHANCEMENT,
SHAMAN_TAB_RESTORATION,
};
enum PALADIN_TABS
{
PALADIN_TAB_HOLY,
PALADIN_TAB_PROTECTION,
PALADIN_TAB_RETRIBUTION,
};
enum WARLOCK_TABS
{
WARLOCK_TAB_AFFLICTION,
WARLOCK_TAB_DEMONOLOGY,
WARLOCK_TAB_DESTRUCTION,
};
enum WARRIOR_TABS
{
WARRIOR_TAB_ARMS,
WARRIOR_TAB_FURY,
WARRIOR_TAB_PROTECTION,
};
class PacketHandlingHelper
{
public:
void AddHandler(uint16 opcode, std::string const handler);
void Handle(ExternalEventHelper& helper);
void AddPacket(WorldPacket const& packet);
private:
std::map<uint16, std::string> handlers;
std::stack<WorldPacket> queue;
};
class ChatCommandHolder
{
public:
ChatCommandHolder(std::string const command, Player* owner = nullptr, uint32 type = CHAT_MSG_WHISPER,
time_t time = 0)
: command(command), owner(owner), type(type), time(time)
{
}
ChatCommandHolder(ChatCommandHolder const& other)
: command(other.command), owner(other.owner), type(other.type), time(other.time)
{
}
const std::string& GetCommand() { return command; }
Player* GetOwner() { return owner; }
uint32& GetType() { return type; }
time_t& GetTime() { return time; }
private:
std::string const command;
Player* owner;
uint32 type;
time_t time;
};
class PlayerbotAI : public PlayerbotAIBase
{
public:
PlayerbotAI();
PlayerbotAI(Player* bot);
virtual ~PlayerbotAI();
void UpdateAI(uint32 elapsed, bool minimal = false) override;
void UpdateAIInternal(uint32 elapsed, bool minimal = false) override;
std::string const HandleRemoteCommand(std::string const command);
void HandleCommand(uint32 type, std::string const text, Player* fromPlayer);
void QueueChatResponse(const ChatQueuedReply reply);
void HandleBotOutgoingPacket(WorldPacket const& packet);
void HandleMasterIncomingPacket(WorldPacket const& packet);
void HandleMasterOutgoingPacket(WorldPacket const& packet);
void HandleTeleportAck();
void ChangeEngine(BotState type);
void ChangeEngineOnCombat();
void ChangeEngineOnNonCombat();
void DoNextAction(bool minimal = false);
virtual bool DoSpecificAction(std::string const name, Event event = Event(), bool silent = false,
std::string const qualifier = "");
void ChangeStrategy(std::string const name, BotState type);
void ClearStrategies(BotState type);
std::vector<std::string> GetStrategies(BotState type);
void ApplyInstanceStrategies(uint32 mapId, bool tellMaster = false);
void EvaluateHealerDpsStrategy();
bool ContainsStrategy(StrategyType type);
bool HasStrategy(std::string const name, BotState type);
BotState GetState() { return currentState; };
void ResetStrategies(bool load = false);
void ReInitCurrentEngine();
void Reset(bool full = false);
void LeaveOrDisbandGroup();
static bool IsTank(Player* player, bool bySpec = false);
static bool IsHeal(Player* player, bool bySpec = false);
static bool IsDps(Player* player, bool bySpec = false);
static bool IsRanged(Player* player, bool bySpec = false);
static bool IsMelee(Player* player, bool bySpec = false);
static bool IsCaster(Player* player, bool bySpec = false);
static bool IsRangedDps(Player* player, bool bySpec = false);
static bool IsCombo(Player* player);
static bool IsBotMainTank(Player* player);
static bool IsMainTank(Player* player, bool ignoreMemberFlag = false);
static uint32 GetGroupTankNum(Player* player);
static bool IsAssistTank(Player* player);
static bool IsAssistTankOfIndex(Player* player, uint8 index, bool ignoreDeadPlayers = false);
static bool IsAssistHealOfIndex(Player* player, uint8 index, bool ignoreDeadPlayers = false);
static bool IsAssistRangedDpsOfIndex(Player* player, uint8 index, bool ignoreDeadPlayers = false);
bool HasAggro(Unit* unit);
static int32 GetAssistTankIndex(Player* player);
int32 GetGroupSlotIndex(Player* player);
int32 GetRangedIndex(Player* player);
int32 GetClassIndex(Player* player, uint8 cls);
int32 GetRangedDpsIndex(Player* player);
int32 GetMeleeIndex(Player* player);
Creature* GetCreature(ObjectGuid guid);
Unit* GetUnit(ObjectGuid guid);
Player* GetPlayer(ObjectGuid guid);
static Unit* GetUnit(CreatureData const* creatureData);
GameObject* GetGameObject(ObjectGuid guid);
// static GameObject* GetGameObject(GameObjectData const* gameObjectData);
WorldObject* GetWorldObject(ObjectGuid guid);
std::vector<Player*> GetAllPlayersInGroup();
std::vector<Player*> GetRealPlayersInGroup();
const AreaTableEntry* GetCurrentArea();
const AreaTableEntry* GetCurrentZone();
static std::string GetLocalizedAreaName(const AreaTableEntry* entry);
static std::string GetLocalizedCreatureName(uint32 entry);
static std::string GetLocalizedGameObjectName(uint32 entry);
bool TellMaster(std::ostringstream& stream, PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL);
bool TellMaster(std::string const text, PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL);
bool TellMasterNoFacing(std::ostringstream& stream,
PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL);
bool TellMasterNoFacing(std::string const text,
PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL);
bool TellError(std::string const text, PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL);
bool SayToGuild(const std::string& msg);
bool SayToWorld(const std::string& msg);
bool SayToChannel(const std::string& msg, const ChatChannelId& chanId);
bool SayToParty(const std::string& msg);
bool SayToRaid(const std::string& msg);
bool Yell(const std::string& msg);
bool Say(const std::string& msg);
bool Whisper(const std::string& msg, const std::string& receiverName);
void SpellInterrupted(uint32 spellid);
int32 CalculateGlobalCooldown(uint32 spellid);
void InterruptSpell();
void RemoveAura(std::string const name);
void RemoveShapeshift();
void WaitForSpellCast(Spell* spell);
bool PlaySound(uint32 emote);
bool PlayEmote(uint32 emote);
void Ping(float x, float y);
Item* FindPoison() const;
Item* FindAmmo() const;
Item* FindBandage() const;
Item* FindOpenableItem() const;
Item* FindLockedItem() const;
Item* FindConsumable(uint32 itemId) const;
Item* FindStoneFor(Item* weapon) const;
Item* FindOilFor(Item* weapon) const;
void ImbueItem(Item* item, uint32 targetFlag, ObjectGuid targetGUID);
void ImbueItem(Item* item, uint8 targetInventorySlot);
void ImbueItem(Item* item, Unit* target);
void ImbueItem(Item* item);
void EnchantItemT(uint32 spellid, uint8 slot);
uint32 GetBuffedCount(Player* player, std::string const spellname);
int32 GetNearGroupMemberCount(float dis = sPlayerbotAIConfig.sightDistance);
virtual bool CanCastSpell(std::string const name, Unit* target, Item* itemTarget = nullptr);
virtual bool CastSpell(std::string const name, Unit* target, Item* itemTarget = nullptr);
virtual bool HasAura(std::string const spellName, Unit* player, bool maxStack = false, bool checkIsOwner = false,
int maxAmount = -1, bool checkDuration = false);
virtual bool HasAnyAuraOf(Unit* player, ...);
virtual bool IsInterruptableSpellCasting(Unit* player, std::string const spell);
virtual bool HasAuraToDispel(Unit* player, uint32 dispelType);
bool CanCastSpell(uint32 spellid, Unit* target, bool checkHasSpell = true, Item* itemTarget = nullptr,
Item* castItem = nullptr);
bool CanCastSpell(uint32 spellid, GameObject* goTarget, bool checkHasSpell = true);
bool CanCastSpell(uint32 spellid, float x, float y, float z, bool checkHasSpell = true,
Item* itemTarget = nullptr);
bool HasAura(uint32 spellId, Unit const* player);
Aura* GetAura(std::string const spellName, Unit* unit, bool checkIsOwner = false, bool checkDuration = false,
int checkStack = -1);
bool CastSpell(uint32 spellId, Unit* target, Item* itemTarget = nullptr);
bool CastSpell(uint32 spellId, float x, float y, float z, Item* itemTarget = nullptr);
bool canDispel(SpellInfo const* spellInfo, uint32 dispelType);
bool CanCastVehicleSpell(uint32 spellid, Unit* target);
bool CastVehicleSpell(uint32 spellId, Unit* target);
bool CastVehicleSpell(uint32 spellId, float x, float y, float z);
bool IsInVehicle(bool canControl = false, bool canCast = false, bool canAttack = false, bool canTurn = false,
bool fixed = false);
uint32 GetEquipGearScore(Player* player);
//uint32 GetEquipGearScore(Player* player, bool withBags, bool withBank);
static uint32 GetMixedGearScore(Player* player, bool withBags, bool withBank, uint32 topN = 0);
bool HasSkill(SkillType skill);
bool IsAllowedCommand(std::string const text);
float GetRange(std::string const type);
Player* GetBot() { return bot; }
Player* GetMaster() { return master; }
Player* FindNewMaster();
// Checks if the bot is really a player. Players always have themselves as master.
bool IsRealPlayer() { return master ? (master == bot) : false; }
// Bot has a master that is a player.
bool HasRealPlayerMaster();
// Bot has a master that is activly playing.
bool HasActivePlayerMaster();
// Get the group leader or the master of the bot.
// Checks if the bot is summoned as alt of a player
bool IsAlt();
Player* GetGroupLeader();
// Returns a semi-random (cycling) number that is fixed for each bot.
uint32 GetFixedBotNumer(uint32 maxNum = 100, float cyclePerMin = 1);
GrouperType GetGrouperType();
GuilderType GetGuilderType();
bool HasPlayerNearby(WorldPosition* pos, float range = sPlayerbotAIConfig.reactDistance);
bool HasPlayerNearby(float range = sPlayerbotAIConfig.reactDistance);
bool HasManyPlayersNearby(uint32 trigerrValue = 20, float range = sPlayerbotAIConfig.sightDistance);
bool AllowActive(ActivityType activityType);
bool AllowActivity(ActivityType activityType = ALL_ACTIVITY, bool checkNow = false);
uint32 AutoScaleActivity(uint32 mod);
// Check if player is safe to use.
bool IsSafe(Player* player);
bool IsSafe(WorldObject* obj);
ChatChannelSource GetChatChannelSource(Player* bot, uint32 type, std::string channelName);
bool CheckLocationDistanceByLevel(Player* player, const WorldLocation &loc, bool fromStartUp = false);
bool HasCheat(BotCheatMask mask)
{
return ((uint32)mask & (uint32)cheatMask) != 0 ||
((uint32)mask & (uint32)sPlayerbotAIConfig.botCheatMask) != 0;
}
BotCheatMask GetCheat() { return cheatMask; }
void SetCheat(BotCheatMask mask) { cheatMask = mask; }
void SetMaster(Player* newMaster) { master = newMaster; }
AiObjectContext* GetAiObjectContext() { return aiObjectContext; }
ChatHelper* GetChatHelper() { return &chatHelper; }
bool IsOpposing(Player* player);
static bool IsOpposing(uint8 race1, uint8 race2);
PlayerbotSecurity* GetSecurity() { return &security; }
Position GetJumpDestination() { return jumpDestination; }
void SetJumpDestination(Position pos) { jumpDestination = pos; }
void ResetJumpDestination() { jumpDestination = Position(); }
bool CanMove();
bool IsInRealGuild();
static std::vector<std::string> dispel_whitelist;
bool EqualLowercaseName(std::string s1, std::string s2);
InventoryResult CanEquipItem(uint8 slot, uint16& dest, Item* pItem, bool swap, bool not_loading = true) const;
uint8 FindEquipSlot(ItemTemplate const* proto, uint32 slot, bool swap) const;
std::vector<Item*> GetInventoryAndEquippedItems();
std::vector<Item*> GetInventoryItems();
uint32 GetInventoryItemsCountWithId(uint32 itemId);
bool HasItemInInventory(uint32 itemId);
std::vector<std::pair<const Quest*, uint32>> GetCurrentQuestsRequiringItemId(uint32 itemId);
uint32 GetReactDelay();
std::vector<const Quest*> GetAllCurrentQuests();
std::vector<const Quest*> GetCurrentIncompleteQuests();
std::set<uint32> GetAllCurrentQuestIds();
std::set<uint32> GetCurrentIncompleteQuestIds();
void PetFollow();
static float GetItemScoreMultiplier(ItemQualities quality);
static bool IsHealingSpell(uint32 spellFamilyName, flag96 spelFalimyFlags);
static SpellFamilyNames Class2SpellFamilyName(uint8 cls);
NewRpgInfo rpgInfo;
NewRpgStatistic rpgStatistic;
std::unordered_set<uint32> lowPriorityQuest;
time_t bgReleaseAttemptTime = 0;
// Schedules a callback to run once after <delayMs> milliseconds.
void AddTimedEvent(std::function<void()> callback, uint32 delayMs);
private:
static void _fillGearScoreData(Player* player, Item* item, std::vector<uint32>* gearScore, uint32& twoHandScore,
bool mixed = false);
bool IsTellAllowed(PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL);
void UpdateAIGroupMaster();
Item* FindItemInInventory(std::function<bool(ItemTemplate const*)> checkItem) const;
void HandleCommands();
void HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang = LANG_UNIVERSAL);
bool _isBotInitializing = false;
inline bool IsValidUnit(const Unit* unit) const
{
return unit && unit->IsInWorld() && !unit->IsDuringRemoveFromWorld();
}
inline bool IsValidPlayer(const Player* player) const
{
return player && player->GetSession() && player->IsInWorld() && !player->IsDuringRemoveFromWorld() &&
!player->IsBeingTeleported();
}
protected:
Player* bot;
Player* master;
uint32 accountId;
AiObjectContext* aiObjectContext;
Engine* currentEngine;
Engine* engines[BOT_STATE_MAX];
BotState currentState;
ChatHelper chatHelper;
std::list<ChatCommandHolder> chatCommands;
std::list<ChatQueuedReply> chatReplies;
PacketHandlingHelper botOutgoingPacketHandlers;
PacketHandlingHelper masterIncomingPacketHandlers;
PacketHandlingHelper masterOutgoingPacketHandlers;
CompositeChatFilter chatFilter;
PlayerbotSecurity security;
std::map<std::string, time_t> whispers;
std::pair<ChatMsg, time_t> currentChat;
static std::set<std::string> unsecuredCommands;
bool allowActive[MAX_ACTIVITY_TYPE];
time_t allowActiveCheckTimer[MAX_ACTIVITY_TYPE];
bool inCombat = false;
BotCheatMask cheatMask = BotCheatMask::none;
Position jumpDestination = Position();
uint32 nextTransportCheck = 0;
};
#endif