Compare commits

...

4 Commits

Author SHA1 Message Date
NoxMax
b7b67e0fd9
Feat/Fix: Expand follower bot flightmaster distance search and use correct config patterns (#2140)
# Pull Request

Feat:
A common problem I have with follower bots is that if I quickly run up
to a flightmaster and select a destination, as I go on my way, the bots
can't get on a damn bird and say "Cannot find any flightmaster to talk".
Guy was 8 yards away and they're completely blind to him.

This is because when you select a destination, at that moment the bot
would check `GetNPCIfCanInteractWith` from core, which uses
`INTERACTION_DISTANCE`, which is defined as 5.5 yards. So the bot has to
have caught up with you to be within 5.5 yards of the flightmaster.

This PR expands that distance to use our own
`sPlayerbotAIConfig.farDistance`, which is by default set to 20 yards.
So just as long as bots have caught up to be within 20 yards from the
flightmaster, they will follow you.

Fix:
While I was doing this, I noticed that the timings for bot flight
staggering (introduced in #1281) are defined in TaxiAction and
PlayerbotAIConfig. So I removed their definitions from TaxiAction, made
proper calls to the configs, and renamed them to similar format that
other configs use.

---

## Design Philosophy

We prioritize **stability, performance, and predictability** over
behavioral realism.
Complex player-mimicking logic is intentionally limited due to its
negative impact on scalability, maintainability, and
long-term robustness.

Excessive processing overhead can lead to server hiccups, increased CPU
usage, and degraded performance for all
participants. Because every action and
decision tree is executed **per bot and per trigger**, even small
increases in logic complexity can scale poorly and
negatively affect both players and
world (random) bots. Bots are not expected to behave perfectly, and
perfect simulation of human decision-making is not a
project goal. Increased behavioral
realism often introduces disproportionate cost, reduced predictability,
and significantly higher maintenance overhead.

Every additional branch of logic increases long-term responsibility. All
decision paths must be tested, validated, and
maintained continuously as the system evolves.
If advanced or AI-intensive behavior is introduced, the **default
configuration must remain the lightweight decision
model**. More complex behavior should only be
available as an **explicit opt-in option**, clearly documented as having
a measurable performance cost.

Principles:

- **Stability before intelligence**  
  A stable system is always preferred over a smarter one.

- **Performance is a shared resource**  
  Any increase in bot cost affects all players and all bots.

- **Simple logic scales better than smart logic**  
Predictable behavior under load is more valuable than perfect decisions.

- **Complexity must justify itself**  
  If a feature cannot clearly explain its cost, it should not exist.

- **Defaults must be cheap**  
  Expensive behavior must always be optional and clearly communicated.

- **Bots should look reasonable, not perfect**  
  The goal is believable behavior, not human simulation.

Before submitting, confirm that this change aligns with those
principles.

---

## Feature Evaluation

Please answer the following:

- Describe the **minimum logic** required to achieve the intended
behavior?
- Describe the **cheapest implementation** that produces an acceptable
result?
- Describe the **runtime cost** when this logic executes across many
bots?

Changes here use a minimal amount of code to accomplish the objective,
including using pre-defined distance values rather than creating new
ones. Changes have no effect on processing.

---

## How to Test the Changes

For expanding flightmaster search distance: You will be using the `stay`
command. A bot commanded to `stay` will still take a flight with you, if
it is near a flightmaster. So you can use the command to position the
bot exactly where you want it to be
1. Place your follower bot immediately next to the flightmaster
2. Take a flight and the bot should follow. Nothing new here
3. Place the bot about 12 yards away from flightmaster.
4. Take a flight and the bot should follow. Same as before.
5. Repeat again, but this time place the bot 22 yards away. It should
not follow you and instead say "Cannot find any flightmaster to talk"
6. The change should work correctly with `InstantFlightPaths = 0` in
worldserver.conf, or if it's set to 1/2 and bots can instantly fly.

For the config of staggering:
1. Make sure `InstantFlightPaths = 0` in worldserver.conf.
2. Change the timings in playerbots.conf under the `# FLIGHTPATH`
section.
3. Changes should be correctly reflected in world.


## Complexity & Impact

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

Does this change increase per-bot or per-tick processing?
- - [x] No
- - [ ] Yes (**describe and justify impact**)

Could this logic scale poorly under load?
- - [x] No
- - [ ] Yes (**explain why**)
---

## Defaults & Configuration

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

Follower bots search a slightly bigger distance for nearby
flightmasters.

If this introduces more advanced or AI-heavy logic:
- - [x] Lightweight mode remains the default
- - [x] More complex behavior is optional and thereby configurable
---

## AI Assistance

Was AI assistance (e.g. ChatGPT or similar tools) used while working on
this change?
- - [x] No
- - [ ] Yes (**explain below**)

If yes, please specify:

- AI tool or model used (e.g. ChatGPT, GPT-4, Claude, etc.)
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation)
- Which parts of the change were influenced or generated
- Whether the result was manually reviewed and adapted

AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
Any AI-influenced changes must be verified against existing CORE and PB
logic. We expect contributors to be honest
about what they do and do not understand.

---

## Final Checklist

- - [x] Stability is not compromised
- - [x] Performance impact is understood, tested, and acceptable
- - [x] Added logic complexity is justified and explained
- - [x] Documentation updated if needed

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.
2026-02-27 16:07:42 -08:00
Keleborn
8519b10d39
Fix movenearwateraction isUseful (#2168)
# Pull Request

Minor sign change to make check work properly. 

---

## Design Philosophy

We prioritize **stability, performance, and predictability** over
behavioral realism.
Complex player-mimicking logic is intentionally limited due to its
negative impact on scalability, maintainability, and
long-term robustness.

Excessive processing overhead can lead to server hiccups, increased CPU
usage, and degraded performance for all
participants. Because every action and
decision tree is executed **per bot and per trigger**, even small
increases in logic complexity can scale poorly and
negatively affect both players and
world (random) bots. Bots are not expected to behave perfectly, and
perfect simulation of human decision-making is not a
project goal. Increased behavioral
realism often introduces disproportionate cost, reduced predictability,
and significantly higher maintenance overhead.

Every additional branch of logic increases long-term responsibility. All
decision paths must be tested, validated, and
maintained continuously as the system evolves.
If advanced or AI-intensive behavior is introduced, the **default
configuration must remain the lightweight decision
model**. More complex behavior should only be
available as an **explicit opt-in option**, clearly documented as having
a measurable performance cost.

Principles:

- **Stability before intelligence**  
  A stable system is always preferred over a smarter one.

- **Performance is a shared resource**  
  Any increase in bot cost affects all players and all bots.

- **Simple logic scales better than smart logic**  
Predictable behavior under load is more valuable than perfect decisions.

- **Complexity must justify itself**  
  If a feature cannot clearly explain its cost, it should not exist.

- **Defaults must be cheap**  
  Expensive behavior must always be optional and clearly communicated.

- **Bots should look reasonable, not perfect**  
  The goal is believable behavior, not human simulation.

Before submitting, confirm that this change aligns with those
principles.

---

## Feature Evaluation

Please answer the following:

- Describe the **minimum logic** required to achieve the intended
behavior?
- Describe the **cheapest implementation** that produces an acceptable
result?
- Describe the **runtime cost** when this logic executes across many
bots?

---

## How to Test the Changes

- Step-by-step instructions to test the change
- Any required setup (e.g. multiple players, bots, specific
configuration)
- Expected behavior and how to verify it

## Complexity & Impact

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

Does this change increase per-bot or per-tick processing?
- - [x] No
- - [ ] Yes (**describe and justify impact**)

Could this logic scale poorly under load?
- - [x] No
- - [ ] Yes (**explain why**)
---

## Defaults & Configuration

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

If this introduces more advanced or AI-heavy logic:
- - [x] Lightweight mode remains the default
- - [ ] More complex behavior is optional and thereby configurable
---

## AI Assistance

Was AI assistance (e.g. ChatGPT or similar tools) used while working on
this change?
- - [x] No
- - [ ] Yes (**explain below**)

If yes, please specify:

- AI tool or model used (e.g. ChatGPT, GPT-4, Claude, etc.)
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation)
- Which parts of the change were influenced or generated
- Whether the result was manually reviewed and adapted

AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
Any AI-influenced changes must be verified against existing CORE and PB
logic. We expect contributors to be honest
about what they do and do not understand.

---

## Final Checklist

- - [x] Stability is not compromised
- - [x] Performance impact is understood, tested, and acceptable
- - [x] Added logic complexity is justified and explained
- - [x] Documentation updated if needed

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.
2026-02-27 16:05:31 -08:00
Keleborn
439293e100
Warnings PR 2 clean unused variables (#2107)
# Pull Request

Removed unused variables and fixed styling issues. 


## How to Test the Changes

- Step-by-step instructions to test the change
- Any required setup (e.g. multiple players, bots, specific
configuration)
- Expected behavior and how to verify it

## Complexity & Impact

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

- Does this change increase per-bot or per-tick processing?
    - [x] No
    - [ ] Yes (**describe and justify impact**)

- Could this logic scale poorly under load?
    - [x] No
    - [ ] Yes (**explain why**)

---

## Defaults & Configuration

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

If this introduces more advanced or AI-heavy logic:

- [ ] Lightweight mode remains the default
- [ ] More complex behavior is optional and thereby configurable

---

## AI Assistance

- Was AI assistance (e.g. ChatGPT or similar tools) used while working
on this change?
    - [x] No
    - [ ] Yes (**explain below**)


---

## Final Checklist

- [x] Stability is not compromised
- [x] Performance impact is understood, tested, and acceptable
- [x] Added logic complexity is justified and explained
- [x] Documentation updated if needed

---

## Notes for Reviewers

This was filtered from the code provided by SmashingQuasar. Eliminated
variables were confirmed to be not used, but unclear at times if that is
due to mistakes in writing.

---------

Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
2026-02-27 16:04:33 -08:00
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
90 changed files with 5670 additions and 916 deletions

View File

@ -24,8 +24,6 @@ bool BGJoinAction::Execute(Event /*event*/)
BattlegroundQueueTypeId queueTypeId = (BattlegroundQueueTypeId)bgList[urand(0, bgList.size() - 1)]; BattlegroundQueueTypeId queueTypeId = (BattlegroundQueueTypeId)bgList[urand(0, bgList.size() - 1)];
BattlegroundTypeId bgTypeId = BattlegroundMgr::BGTemplateId(queueTypeId); BattlegroundTypeId bgTypeId = BattlegroundMgr::BGTemplateId(queueTypeId);
BattlegroundBracketId bracketId;
bool isArena = false;
bool isRated = false; bool isRated = false;
Battleground* bg = sBattlegroundMgr->GetBattlegroundTemplate(bgTypeId); Battleground* bg = sBattlegroundMgr->GetBattlegroundTemplate(bgTypeId);
@ -37,12 +35,8 @@ bool BGJoinAction::Execute(Event /*event*/)
if (!pvpDiff) if (!pvpDiff)
return false; return false;
bracketId = pvpDiff->GetBracketId();
if (ArenaType type = ArenaType(BattlegroundMgr::BGArenaType(queueTypeId))) if (ArenaType type = ArenaType(BattlegroundMgr::BGArenaType(queueTypeId)))
{ {
isArena = true;
std::vector<uint32>::iterator i = find(ratedList.begin(), ratedList.end(), queueTypeId); std::vector<uint32>::iterator i = find(ratedList.begin(), ratedList.end(), queueTypeId);
if (i != ratedList.end()) if (i != ratedList.end())
isRated = true; isRated = true;
@ -408,8 +402,6 @@ bool BGJoinAction::JoinQueue(uint32 type)
bracketId = pvpDiff->GetBracketId(); bracketId = pvpDiff->GetBracketId();
uint32 BracketSize = bg->GetMaxPlayersPerTeam() * 2;
uint32 TeamSize = bg->GetMaxPlayersPerTeam();
TeamId teamId = bot->GetTeamId(); TeamId teamId = bot->GetTeamId();
// check if already in queue // check if already in queue
@ -486,8 +478,6 @@ bool BGJoinAction::JoinQueue(uint32 type)
if (isArena) if (isArena)
{ {
isArena = true; isArena = true;
BracketSize = type * 2;
TeamSize = type;
isRated = botAI->GetAiObjectContext()->GetValue<uint32>("arena type")->Get(); isRated = botAI->GetAiObjectContext()->GetValue<uint32>("arena type")->Get();
if (joinAsGroup) if (joinAsGroup)

View File

@ -2497,7 +2497,6 @@ bool BGTactics::selectObjective(bool reset)
EYBotStrategy strategyHorde = static_cast<EYBotStrategy>(GetBotStrategyForTeam(bg, TEAM_HORDE)); EYBotStrategy strategyHorde = static_cast<EYBotStrategy>(GetBotStrategyForTeam(bg, TEAM_HORDE));
EYBotStrategy strategyAlliance = static_cast<EYBotStrategy>(GetBotStrategyForTeam(bg, TEAM_ALLIANCE)); EYBotStrategy strategyAlliance = static_cast<EYBotStrategy>(GetBotStrategyForTeam(bg, TEAM_ALLIANCE));
EYBotStrategy strategy = (team == TEAM_ALLIANCE) ? strategyAlliance : strategyHorde; EYBotStrategy strategy = (team == TEAM_ALLIANCE) ? strategyAlliance : strategyHorde;
EYBotStrategy enemyStrategy = (team == TEAM_ALLIANCE) ? strategyHorde : strategyAlliance;
auto IsOwned = [&](uint32 nodeId) -> bool auto IsOwned = [&](uint32 nodeId) -> bool
{ return eyeOfTheStormBG->GetCapturePointInfo(nodeId)._ownerTeamId == team; }; { return eyeOfTheStormBG->GetCapturePointInfo(nodeId)._ownerTeamId == team; };
@ -3231,7 +3230,6 @@ bool BGTactics::selectObjectiveWp(std::vector<BattleBotPath*> const& vPaths)
if (bgType == BATTLEGROUND_RB) if (bgType == BATTLEGROUND_RB)
bgType = bg->GetBgTypeID(true); bgType = bg->GetBgTypeID(true);
PositionMap& posMap = context->GetValue<PositionMap&>("position")->Get();
PositionInfo pos = context->GetValue<PositionMap&>("position")->Get()["bg objective"]; PositionInfo pos = context->GetValue<PositionMap&>("position")->Get()["bg objective"];
if (!pos.isSet()) if (!pos.isSet())
return false; return false;

View File

@ -68,9 +68,7 @@ bool FollowChatShortcutAction::Execute(Event /*event*/)
std::string const target = formation->GetTargetName(); std::string const target = formation->GetTargetName();
bool moved = false; bool moved = false;
if (!target.empty()) if (!target.empty())
{
moved = Follow(AI_VALUE(Unit*, target)); moved = Follow(AI_VALUE(Unit*, target));
}
else else
{ {
WorldLocation loc = formation->GetLocation(); WorldLocation loc = formation->GetLocation();
@ -83,9 +81,7 @@ bool FollowChatShortcutAction::Execute(Event /*event*/)
} }
if (Pet* pet = bot->GetPet()) if (Pet* pet = bot->GetPet())
{
botAI->PetFollow(); botAI->PetFollow();
}
if (moved) if (moved)
{ {

View File

@ -112,7 +112,6 @@ float ChooseRpgTargetAction::getMaxRelevance(GuidPosition guidP)
bool ChooseRpgTargetAction::Execute(Event /*event*/) bool ChooseRpgTargetAction::Execute(Event /*event*/)
{ {
//TravelTarget* travelTarget = AI_VALUE(TravelTarget*, "travel target"); //not used, line marked for removal.
Player* master = botAI->GetMaster(); Player* master = botAI->GetMaster();
GuidPosition masterRpgTarget; GuidPosition masterRpgTarget;
if (master && master != bot && GET_PLAYERBOT_AI(master) && master->GetMapId() == bot->GetMapId() && !master->IsBeingTeleported()) if (master && master != bot && GET_PLAYERBOT_AI(master) && master->GetMapId() == bot->GetMapId() && !master->IsBeingTeleported())
@ -124,7 +123,6 @@ bool ChooseRpgTargetAction::Execute(Event /*event*/)
master = nullptr; master = nullptr;
std::unordered_map<ObjectGuid, uint32> targets; std::unordered_map<ObjectGuid, uint32> targets;
// uint32 num = 0; //not used, line marked for removal.
GuidVector possibleTargets = AI_VALUE(GuidVector, "possible rpg targets"); GuidVector possibleTargets = AI_VALUE(GuidVector, "possible rpg targets");
GuidVector possibleObjects = AI_VALUE(GuidVector, "nearest game objects no los"); GuidVector possibleObjects = AI_VALUE(GuidVector, "nearest game objects no los");
GuidVector possiblePlayers = AI_VALUE(GuidVector, "nearest friendly players"); GuidVector possiblePlayers = AI_VALUE(GuidVector, "nearest friendly players");

View File

@ -232,15 +232,6 @@ void ChooseTravelTargetAction::ReportTravelTarget(TravelTarget* newTarget, Trave
QuestTravelDestination* QuestDestination = (QuestTravelDestination*)destination; QuestTravelDestination* QuestDestination = (QuestTravelDestination*)destination;
Quest const* quest = QuestDestination->GetQuestTemplate(); Quest const* quest = QuestDestination->GetQuestTemplate();
WorldPosition botLocation(bot); WorldPosition botLocation(bot);
CreatureTemplate const* cInfo = nullptr;
GameObjectTemplate const* gInfo = nullptr;
if (destination->getEntry() > 0)
cInfo = sObjectMgr->GetCreatureTemplate(destination->getEntry());
else
gInfo = sObjectMgr->GetGameObjectTemplate(destination->getEntry() * -1);
std::string Sub; std::string Sub;
if (newTarget->isGroupCopy()) if (newTarget->isGroupCopy())
@ -823,10 +814,6 @@ char* strstri(char const* haystack, char const* needle);
TravelDestination* ChooseTravelTargetAction::FindDestination(Player* bot, std::string const name, bool zones, bool npcs, bool quests, bool mobs, bool bosses) TravelDestination* ChooseTravelTargetAction::FindDestination(Player* bot, std::string const name, bool zones, bool npcs, bool quests, bool mobs, bool bosses)
{ {
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
// AiObjectContext* context = botAI->GetAiObjectContext(); //not used, line marked for removal.
std::vector<TravelDestination*> dests; std::vector<TravelDestination*> dests;
//Quests //Quests

View File

@ -520,8 +520,8 @@ bool DebugAction::Execute(Event event)
botPos.setY(botPos.GetPositionY() + (dy - 5) * 5); botPos.setY(botPos.GetPositionY() + (dy - 5) * 5);
botPos.setZ(botPos.getHeight()); botPos.setZ(botPos.getHeight());
Creature* wpCreature = bot->SummonCreature(effect, botPos.GetPositionX(), botPos.GetPositionY(), botPos.GetPositionZ(), 0, bot->SummonCreature(effect, botPos.GetPositionX(), botPos.GetPositionY(), botPos.GetPositionZ(), 0,
TEMPSUMMON_TIMED_DESPAWN, 10000.0f); TEMPSUMMON_TIMED_DESPAWN, 10000.0f);
} }
} }
return true; return true;

View File

@ -85,8 +85,8 @@ void EquipAction::EquipItem(Item* item)
if (itemProto->Class == ITEM_CLASS_CONTAINER) if (itemProto->Class == ITEM_CLASS_CONTAINER)
{ {
// Attempt to equip as a bag // Attempt to equip as a bag
Bag* pBag = reinterpret_cast<Bag*>(item);
uint8 newBagSlot = GetSmallestBagSlot(); uint8 newBagSlot = GetSmallestBagSlot();
if (newBagSlot > 0) if (newBagSlot > 0)
{ {
uint16 src = ((bagIndex << 8) | slot); uint16 src = ((bagIndex << 8) | slot);

View File

@ -262,7 +262,7 @@ bool MoveNearWaterAction::isUseful()
FishingSpotValue* fishingSpotValueObject = (FishingSpotValue*)context->GetValue<WorldPosition>("fishing spot"); FishingSpotValue* fishingSpotValueObject = (FishingSpotValue*)context->GetValue<WorldPosition>("fishing spot");
WorldPosition pos = fishingSpotValueObject->Get(); WorldPosition pos = fishingSpotValueObject->Get();
return !pos.IsValid() || fishingSpotValueObject->IsStale(FISHING_LOCATION_TIMEOUT) || return !pos.IsValid() || fishingSpotValueObject->IsStale(FISHING_LOCATION_TIMEOUT) ||
bot->GetExactDist(&pos) < 0.1f; bot->GetExactDist(&pos) > 0.1f;
} }
@ -292,7 +292,6 @@ bool MoveNearWaterAction::isPossible()
// Water spot is out of range, lets look for a spot to move to for the fishing hole. // Water spot is out of range, lets look for a spot to move to for the fishing hole.
if (distance > MAX_DISTANCE_TO_WATER || distance < MIN_DISTANCE_TO_WATER) if (distance > MAX_DISTANCE_TO_WATER || distance < MIN_DISTANCE_TO_WATER)
{ {
float angle = bot->GetAngle(fishingHole.GetPositionX(), fishingHole.GetPositionY());
WorldPosition landSpot = FindLandRadialFromPosition(botAI, fishingHole, MIN_DISTANCE_TO_WATER, MAX_DISTANCE_TO_WATER, SEARCH_INCREMENT, fishingSearchWindow, 32); WorldPosition landSpot = FindLandRadialFromPosition(botAI, fishingHole, MIN_DISTANCE_TO_WATER, MAX_DISTANCE_TO_WATER, SEARCH_INCREMENT, fishingSearchWindow, 32);
if (landSpot.IsValid()) if (landSpot.IsValid())
{ {
@ -323,7 +322,6 @@ bool MoveNearWaterAction::isPossible()
if (!water.IsValid()) if (!water.IsValid())
return false; return false;
bool hasLOS = bot->IsWithinLOS(water.GetPositionX(), water.GetPositionY(), water.GetPositionZ());
float angle = bot->GetAngle(water.GetPositionX(), water.GetPositionY()); float angle = bot->GetAngle(water.GetPositionX(), water.GetPositionY());
WorldPosition landSpot = WorldPosition landSpot =
FindLandFromPosition(botAI, 0.0f, MAX_DISTANCE_TO_WATER, 1.0f, angle, water, fishingSearchWindow, false); FindLandFromPosition(botAI, 0.0f, MAX_DISTANCE_TO_WATER, 1.0f, angle, water, fishingSearchWindow, false);

View File

@ -28,7 +28,6 @@ bool GiveItemAction::Execute(Event /*event*/)
if (receiverAi->GetAiObjectContext()->GetValue<uint32>("item count", item)->Get()) if (receiverAi->GetAiObjectContext()->GetValue<uint32>("item count", item)->Get())
return true; return true;
bool moved = false;
std::vector<Item*> items = InventoryAction::parseItems(item, ITERATE_ITEMS_IN_BAGS); std::vector<Item*> items = InventoryAction::parseItems(item, ITERATE_ITEMS_IN_BAGS);
for (Item* item : items) for (Item* item : items)
{ {
@ -42,7 +41,6 @@ bool GiveItemAction::Execute(Event /*event*/)
bot->MoveItemFromInventory(item->GetBagSlot(), item->GetSlot(), true); bot->MoveItemFromInventory(item->GetBagSlot(), item->GetSlot(), true);
item->SetOwnerGUID(target->GetGUID()); item->SetOwnerGUID(target->GetGUID());
receiver->MoveItemToInventory(dest, item, true); receiver->MoveItemToInventory(dest, item, true);
moved = true;
std::ostringstream out; std::ostringstream out;
out << "Got " << chat->FormatItem(item->GetTemplate(), item->GetCount()) << " from " << bot->GetName(); out << "Got " << chat->FormatItem(item->GetTemplate(), item->GetCount()) << " from " << bot->GetName();

View File

@ -17,7 +17,6 @@
bool BuyPetitionAction::Execute(Event /*event*/) bool BuyPetitionAction::Execute(Event /*event*/)
{ {
GuidVector vendors = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get(); GuidVector vendors = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
bool vendored = false, result = false;
for (GuidVector::iterator i = vendors.begin(); i != vendors.end(); ++i) for (GuidVector::iterator i = vendors.begin(); i != vendors.end(); ++i)
{ {
ObjectGuid vendorguid = *i; ObjectGuid vendorguid = *i;
@ -97,7 +96,6 @@ bool BuyPetitionAction::canBuyPetition(Player* bot)
bool PetitionOfferAction::Execute(Event event) bool PetitionOfferAction::Execute(Event event)
{ {
uint32 petitionEntry = 5863; // GUILD_CHARTER
std::vector<Item*> petitions = AI_VALUE2(std::vector<Item*>, "inventory items", chat->FormatQItem(5863)); std::vector<Item*> petitions = AI_VALUE2(std::vector<Item*>, "inventory items", chat->FormatQItem(5863));
if (petitions.empty()) if (petitions.empty())
@ -212,7 +210,6 @@ bool PetitionOfferNearbyAction::isUseful()
bool PetitionTurnInAction::Execute(Event /*event*/) bool PetitionTurnInAction::Execute(Event /*event*/)
{ {
GuidVector vendors = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get(); GuidVector vendors = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
bool vendored = false, result = false;
std::vector<Item*> petitions = AI_VALUE2(std::vector<Item*>, "inventory items", chat->FormatQItem(5863)); std::vector<Item*> petitions = AI_VALUE2(std::vector<Item*>, "inventory items", chat->FormatQItem(5863));
if (petitions.empty()) if (petitions.empty())

View File

@ -167,8 +167,6 @@ std::vector<Player*> InviteGuildToGroupAction::getGuildMembers()
bool InviteGuildToGroupAction::Execute(Event /*event*/) bool InviteGuildToGroupAction::Execute(Event /*event*/)
{ {
Guild* guild = sGuildMgr->GetGuildById(bot->GetGuildId());
for (auto& member : getGuildMembers()) for (auto& member : getGuildMembers())
{ {
Player* player = member; Player* player = member;

View File

@ -27,7 +27,6 @@ bool LootRollAction::Execute(Event /*event*/)
continue; continue;
} }
ObjectGuid guid = roll->itemGUID; ObjectGuid guid = roll->itemGUID;
uint32 slot = roll->itemSlot;
uint32 itemId = roll->itemid; uint32 itemId = roll->itemid;
int32 randomProperty = 0; int32 randomProperty = 0;
if (roll->itemRandomPropId) if (roll->itemRandomPropId)
@ -184,7 +183,6 @@ bool MasterLootRollAction::Execute(Event event)
if (!group) if (!group)
return false; return false;
RollVote vote = CalculateRollVote(proto);
group->CountRollVote(bot->GetGUID(), creatureGuid, CalculateRollVote(proto)); group->CountRollVote(bot->GetGUID(), creatureGuid, CalculateRollVote(proto));
return true; return true;

View File

@ -16,7 +16,6 @@ bool LootStrategyAction::Execute(Event event)
{ {
std::string const strategy = event.getParam(); std::string const strategy = event.getParam();
LootObjectStack* lootItems = AI_VALUE(LootObjectStack*, "available loot");
std::set<uint32>& alwaysLootItems = AI_VALUE(std::set<uint32>&, "always loot list"); std::set<uint32>& alwaysLootItems = AI_VALUE(std::set<uint32>&, "always loot list");
Value<LootStrategy*>* lootStrategy = context->GetValue<LootStrategy*>("loot strategy"); Value<LootStrategy*>* lootStrategy = context->GetValue<LootStrategy*>("loot strategy");

View File

@ -134,7 +134,7 @@ public:
private: private:
bool CheckBagSpace(Player* bot) bool CheckBagSpace(Player* bot)
{ {
uint32 totalused = 0, total = 16; uint32 totalused = 0;
for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++)
if (bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) if (bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
++totalused; ++totalused;

View File

@ -93,9 +93,6 @@ bool MovementAction::MoveNear(WorldObject* target, float distance, MovementPrior
distance += target->GetCombatReach(); distance += target->GetCombatReach();
float x = target->GetPositionX();
float y = target->GetPositionY();
float z = target->GetPositionZ();
float followAngle = GetFollowAngle(); float followAngle = GetFollowAngle();
for (float angle = followAngle; angle <= followAngle + static_cast<float>(2 * M_PI); for (float angle = followAngle; angle <= followAngle + static_cast<float>(2 * M_PI);
@ -113,7 +110,6 @@ bool MovementAction::MoveNear(WorldObject* target, float distance, MovementPrior
return true; return true;
} }
// botAI->TellError("All paths not in LOS");
return false; return false;
} }
@ -122,9 +118,6 @@ bool MovementAction::MoveToLOS(WorldObject* target, bool ranged)
if (!target) if (!target)
return false; return false;
// std::ostringstream out; out << "Moving to LOS!";
// bot->Say(out.str(), LANG_UNIVERSAL);
float x = target->GetPositionX(); float x = target->GetPositionX();
float y = target->GetPositionY(); float y = target->GetPositionY();
float z = target->GetPositionZ(); float z = target->GetPositionZ();
@ -257,7 +250,6 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
// bot->CastStop(); // bot->CastStop();
// botAI->InterruptSpell(); // botAI->InterruptSpell();
// } // }
G3D::Vector3 endP = path.back();
DoMovePoint(bot, x, y, z, generatePath, backwards); DoMovePoint(bot, x, y, z, generatePath, backwards);
float delay = 1000.0f * MoveDelay(distance, backwards); float delay = 1000.0f * MoveDelay(distance, backwards);
if (lessDelay) if (lessDelay)
@ -772,8 +764,6 @@ bool MovementAction::MoveTo(WorldObject* target, float distance, MovementPriorit
float by = bot->GetPositionY(); float by = bot->GetPositionY();
float bz = bot->GetPositionZ(); float bz = bot->GetPositionZ();
float tx = target->GetPositionX();
float ty = target->GetPositionY();
float tz = target->GetPositionZ(); float tz = target->GetPositionZ();
float distanceToTarget = bot->GetDistance(target); float distanceToTarget = bot->GetDistance(target);
@ -805,10 +795,6 @@ bool MovementAction::ReachCombatTo(Unit* target, float distance)
if (!IsMovingAllowed(target)) if (!IsMovingAllowed(target))
return false; return false;
float bx = bot->GetPositionX();
float by = bot->GetPositionY();
float bz = bot->GetPositionZ();
float tx = target->GetPositionX(); float tx = target->GetPositionX();
float ty = target->GetPositionY(); float ty = target->GetPositionY();
float tz = target->GetPositionZ(); float tz = target->GetPositionZ();
@ -1413,7 +1399,6 @@ bool MovementAction::Flee(Unit* target)
if (botAI->IsTank(player)) if (botAI->IsTank(player))
{ {
float distanceToTank = ServerFacade::instance().GetDistance2d(bot, player); float distanceToTank = ServerFacade::instance().GetDistance2d(bot, player);
float distanceToTarget = ServerFacade::instance().GetDistance2d(bot, target);
if (distanceToTank < fleeDistance) if (distanceToTank < fleeDistance)
{ {
fleeTarget = player; fleeTarget = player;
@ -1434,8 +1419,6 @@ bool MovementAction::Flee(Unit* target)
else // bot is not targeted, try to flee dps/healers else // bot is not targeted, try to flee dps/healers
{ {
bool isHealer = botAI->IsHeal(bot); bool isHealer = botAI->IsHeal(bot);
bool isDps = !isHealer && !botAI->IsTank(bot);
bool isTank = botAI->IsTank(bot);
bool needHealer = !isHealer && AI_VALUE2(uint8, "health", "self target") < 50; bool needHealer = !isHealer && AI_VALUE2(uint8, "health", "self target") < 50;
bool isRanged = botAI->IsRanged(bot); bool isRanged = botAI->IsRanged(bot);
@ -2771,9 +2754,7 @@ bool MoveRandomAction::Execute(Event /*event*/)
float angle = (float)rand_norm() * static_cast<float>(M_PI); float angle = (float)rand_norm() * static_cast<float>(M_PI);
x += urand(0, distance) * cos(angle); x += urand(0, distance) * cos(angle);
y += urand(0, distance) * sin(angle); y += urand(0, distance) * sin(angle);
float ox = x;
float oy = y;
float oz = z;
if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
bot->GetPositionZ(), x, y, z)) bot->GetPositionZ(), x, y, z))
continue; continue;
@ -2894,10 +2875,8 @@ bool MoveAwayFromCreatureAction::isPossible() { return bot->CanFreeMove(); }
bool MoveAwayFromPlayerWithDebuffAction::Execute(Event /*event*/) bool MoveAwayFromPlayerWithDebuffAction::Execute(Event /*event*/)
{ {
Player* closestPlayer = nullptr; Group* const group = bot->GetGroup();
float minDistance = 0.0f;
Group* group = bot->GetGroup();
if (!group) if (!group)
return false; return false;

View File

@ -332,7 +332,6 @@ public:
private: private:
uint32 spellId; uint32 spellId;
float range; float range;
bool alive;
}; };
#endif #endif

View File

@ -20,7 +20,6 @@ public:
bool Execute(Event event) override; bool Execute(Event event) override;
private: private:
bool warningEnabled = true;
std::string defaultCmd; std::string defaultCmd;
}; };

View File

@ -16,7 +16,6 @@ void QueryQuestAction::TellObjective(std::string const name, uint32 available, u
bool QueryQuestAction::Execute(Event event) bool QueryQuestAction::Execute(Event event)
{ {
Player* requester = event.getOwner() ? event.getOwner() : GetMaster();
Player* bot = botAI->GetBot(); Player* bot = botAI->GetBot();
WorldPosition botPos(bot); WorldPosition botPos(bot);
WorldPosition* ptr_botpos = &botPos; WorldPosition* ptr_botpos = &botPos;

View File

@ -352,7 +352,6 @@ bool QuestUpdateAddItemAction::Execute(Event event)
uint32 itemId, count; uint32 itemId, count;
p >> itemId >> count; p >> itemId >> count;
Player* requester = event.getOwner() ? event.getOwner() : GetMaster();
auto const* itemPrototype = sObjectMgr->GetItemTemplate(itemId); auto const* itemPrototype = sObjectMgr->GetItemTemplate(itemId);
if (itemPrototype) if (itemPrototype)
{ {
@ -407,8 +406,6 @@ bool QuestItemPushResultAction::Execute(Event event)
if (!quest) if (!quest)
return false; return false;
const QuestStatusData& q_status = bot->getQuestStatusMap().at(questId);
for (int i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; i++) for (int i = 0; i < QUEST_ITEM_OBJECTIVES_COUNT; i++)
{ {
uint32 itemId = quest->RequiredItemId[i]; uint32 itemId = quest->RequiredItemId[i];
@ -448,8 +445,6 @@ bool QuestUpdateFailedTimerAction::Execute(Event event)
uint32 questId; uint32 questId;
p >> questId; p >> questId;
Player* requester = event.getOwner() ? event.getOwner() : GetMaster();
Quest const* qInfo = sObjectMgr->GetQuestTemplate(questId); Quest const* qInfo = sObjectMgr->GetQuestTemplate(questId);
if (qInfo) if (qInfo)

View File

@ -28,7 +28,7 @@ bool RememberTaxiAction::Execute(Event event)
case CMSG_ACTIVATETAXIEXPRESS: case CMSG_ACTIVATETAXIEXPRESS:
{ {
ObjectGuid guid; ObjectGuid guid;
uint32 node_count, totalcost; uint32 node_count;
p >> guid >> node_count; p >> guid >> node_count;
LastMovement& movement = context->GetValue<LastMovement&>("last taxi")->Get(); LastMovement& movement = context->GetValue<LastMovement&>("last taxi")->Get();

View File

@ -156,7 +156,6 @@ bool SayAction::isUseful()
void ChatReplyAction::ChatReplyDo(Player* bot, uint32& type, uint32& guid1, uint32& guid2, std::string& msg, std::string& chanName, std::string& name) void ChatReplyAction::ChatReplyDo(Player* bot, uint32& type, uint32& guid1, uint32& guid2, std::string& msg, std::string& chanName, std::string& name)
{ {
ChatReplyType replyType = REPLY_NOT_UNDERSTAND; // default not understand
std::string respondsText = ""; std::string respondsText = "";
// if we're just commanding bots around, don't respond... // if we're just commanding bots around, don't respond...

View File

@ -10,8 +10,10 @@
bool SecurityCheckAction::isUseful() bool SecurityCheckAction::isUseful()
{ {
return sRandomPlayerbotMgr.IsRandomBot(bot) && botAI->GetMaster() && return RandomPlayerbotMgr::instance().IsRandomBot(bot)
botAI->GetMaster()->GetSession()->GetSecurity() < SEC_GAMEMASTER && !GET_PLAYERBOT_AI(botAI->GetMaster()); && botAI->GetMaster()
&& botAI->GetMaster()->GetSession()->GetSecurity() < SEC_GAMEMASTER
&& !GET_PLAYERBOT_AI(botAI->GetMaster());
} }
bool SecurityCheckAction::Execute(Event /*event*/) bool SecurityCheckAction::Execute(Event /*event*/)

View File

@ -66,7 +66,8 @@ bool SetCraftAction::Execute(Event event)
if (!spellInfo) if (!spellInfo)
continue; continue;
if (SkillLineAbilityEntry const* skillLine = skillSpells[spellId]) SkillLineAbilityEntry const* skillLine = skillSpells[spellId];
if (skillLine != nullptr)
{ {
for (uint8 i = 0; i < 3; ++i) for (uint8 i = 0; i < 3; ++i)
{ {

View File

@ -26,20 +26,10 @@ bool SetHomeAction::Execute(Event /*event*/)
if (Unit* unit = botAI->GetUnit(selection)) if (Unit* unit = botAI->GetUnit(selection))
if (unit->HasNpcFlag(UNIT_NPC_FLAG_INNKEEPER)) if (unit->HasNpcFlag(UNIT_NPC_FLAG_INNKEEPER))
{ {
if (isRpgAction) Creature* creature = botAI->GetCreature(selection);
{ bot->GetSession()->SendBindPoint(creature);
Creature* creature = botAI->GetCreature(selection); botAI->TellMaster("This inn is my new home");
bot->GetSession()->SendBindPoint(creature); return true;
botAI->TellMaster("This inn is my new home");
return true;
}
else
{
Creature* creature = botAI->GetCreature(selection);
bot->GetSession()->SendBindPoint(creature);
botAI->TellMaster("This inn is my new home");
return true;
}
} }
GuidVector npcs = AI_VALUE(GuidVector, "nearest npcs"); GuidVector npcs = AI_VALUE(GuidVector, "nearest npcs");

View File

@ -40,9 +40,8 @@ bool ShareQuestAction::Execute(Event event)
return false; return false;
} }
bool AutoShareQuestAction::Execute(Event event) bool AutoShareQuestAction::Execute(Event /*event*/)
{ {
Player* requester = event.getOwner() ? event.getOwner() : GetMaster();
bool shared = false; bool shared = false;
for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot) for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)

View File

@ -47,7 +47,6 @@ bool StayAction::isUseful()
PositionInfo stayPosition = AI_VALUE(PositionMap&, "position")["stay"]; PositionInfo stayPosition = AI_VALUE(PositionMap&, "position")["stay"];
if (stayPosition.isSet()) if (stayPosition.isSet())
{ {
const float distance = bot->GetDistance(stayPosition.x, stayPosition.y, stayPosition.z);
if (sPlayerbotAIConfig.followDistance) if (sPlayerbotAIConfig.followDistance)
return false; return false;
} }

View File

@ -63,7 +63,6 @@ bool SuggestWhatToDoAction::Execute(Event /*event*/)
fnct_ptr(); fnct_ptr();
std::string const qualifier = "suggest what to do"; std::string const qualifier = "suggest what to do";
time_t lastSaid = AI_VALUE2(time_t, "last said", qualifier);
botAI->GetAiObjectContext()->GetValue<time_t>("last said", qualifier)->Set(time(nullptr) + urand(1, 60)); botAI->GetAiObjectContext()->GetValue<time_t>("last said", qualifier)->Set(time(nullptr) + urand(1, 60));
return true; return true;
@ -221,7 +220,7 @@ void SuggestWhatToDoAction::thunderfury()
class FindTradeItemsVisitor : public IterateItemsVisitor class FindTradeItemsVisitor : public IterateItemsVisitor
{ {
public: public:
FindTradeItemsVisitor(uint32 quality) : quality(quality), IterateItemsVisitor() {} FindTradeItemsVisitor(uint32 quality) : IterateItemsVisitor(), quality(quality) {}
bool Visit(Item* item) override bool Visit(Item* item) override
{ {

View File

@ -231,7 +231,6 @@ void TalkToQuestGiverAction::AskToSelectReward(Quest const* quest, std::ostrings
for (uint8 i = 0; i < quest->GetRewChoiceItemsCount(); ++i) for (uint8 i = 0; i < quest->GetRewChoiceItemsCount(); ++i)
{ {
ItemTemplate const* item = sObjectMgr->GetItemTemplate(quest->RewardChoiceItemId[i]); ItemTemplate const* item = sObjectMgr->GetItemTemplate(quest->RewardChoiceItemId[i]);
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", quest->RewardChoiceItemId[i]);
if (!forEquip || BestRewards(quest).count(i) > 0) if (!forEquip || BestRewards(quest).count(i) > 0)
{ {
@ -248,7 +247,6 @@ bool TurnInQueryQuestAction::Execute(Event event)
WorldPacket pakcet = event.getPacket(); WorldPacket pakcet = event.getPacket();
ObjectGuid guid; ObjectGuid guid;
uint32 questId; uint32 questId;
ObjectGuid unk1;
pakcet >> guid >> questId; pakcet >> guid >> questId;
Object* object = Object* object =
ObjectAccessor::GetObjectByTypeMask(*bot, guid, TYPEMASK_UNIT | TYPEMASK_GAMEOBJECT | TYPEMASK_ITEM); ObjectAccessor::GetObjectByTypeMask(*bot, guid, TYPEMASK_UNIT | TYPEMASK_GAMEOBJECT | TYPEMASK_ITEM);

View File

@ -51,7 +51,6 @@ bool TameAction::Execute(Event event)
{ {
std::set<std::string> normalFamilies; std::set<std::string> normalFamilies;
std::set<std::string> exoticFamilies; std::set<std::string> exoticFamilies;
Player* bot = botAI->GetBot();
// Loop over all creature templates and collect tameable families // Loop over all creature templates and collect tameable families
CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates(); CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates();

View File

@ -31,8 +31,14 @@ bool TaxiAction::Execute(Event event)
GuidVector units = *context->GetValue<GuidVector>("nearest npcs"); GuidVector units = *context->GetValue<GuidVector>("nearest npcs");
for (ObjectGuid const guid : units) for (ObjectGuid const guid : units)
{ {
Creature* npc = bot->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_FLIGHTMASTER); Creature* npc = ObjectAccessor::GetCreature(*bot, guid);
if (!npc) if (!npc || !npc->IsAlive())
continue;
if (!(npc->GetNpcFlags() & UNIT_NPC_FLAG_FLIGHTMASTER))
continue;
if (bot->GetDistance(npc) > sPlayerbotAIConfig.farDistance)
continue; continue;
uint32 curloc = sObjectMgr->GetNearestTaxiNode(npc->GetPositionX(), npc->GetPositionY(), npc->GetPositionZ(), uint32 curloc = sObjectMgr->GetNearestTaxiNode(npc->GetPositionX(), npc->GetPositionY(), npc->GetPositionZ(),
@ -50,21 +56,17 @@ bool TaxiAction::Execute(Event event)
} }
} }
// stagger bot takeoff
uint32 delayMin = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotTaxiDelayMinMs", 350u, false);
uint32 delayMax = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotTaxiDelayMaxMs", 5000u, false);
uint32 gapMs = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotTaxiGapMs", 200u, false);
uint32 gapJitterMs = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotTaxiGapJitterMs", 100u, false);
// Only for follower bots // Only for follower bots
if (botAI->HasRealPlayerMaster()) if (botAI->HasRealPlayerMaster())
{ {
uint32 index = botAI->GetGroupSlotIndex(bot); uint32 index = botAI->GetGroupSlotIndex(bot);
uint32 delay = delayMin + index * gapMs + urand(0, gapJitterMs); uint32 delay = sPlayerbotAIConfig.botTaxiDelayMin +
index * sPlayerbotAIConfig.botTaxiGapMs +
urand(0, sPlayerbotAIConfig.botTaxiGapJitterMs);
delay = std::min(delay, delayMax); delay = std::min(delay, sPlayerbotAIConfig.botTaxiDelayMax);
// Store the npcs GUID so we can re-acquire the pointer later // Store the NPC's GUID so we can re-acquire the pointer later
ObjectGuid npcGuid = npc->GetGUID(); ObjectGuid npcGuid = npc->GetGUID();
// schedule the take-off // schedule the take-off

View File

@ -95,7 +95,6 @@ bool TellAuraAction::Execute(Event /*event*/)
std::string caster_name = caster ? caster->GetName() : "unknown"; std::string caster_name = caster ? caster->GetName() : "unknown";
bool is_area = aura->IsArea(); bool is_area = aura->IsArea();
int32 duration = aura->GetDuration(); int32 duration = aura->GetDuration();
const SpellInfo* spellInfo = aura->GetSpellInfo();
int32 spellId = aura->GetSpellInfo()->Id; int32 spellId = aura->GetSpellInfo()->Id;
bool isPositive = aura->GetSpellInfo()->IsPositive(); bool isPositive = aura->GetSpellInfo()->IsPositive();
sLog->outMessage("playerbot", LOG_LEVEL_DEBUG, sLog->outMessage("playerbot", LOG_LEVEL_DEBUG,

View File

@ -64,8 +64,6 @@ bool TradeStatusAction::Execute(Event event)
uint32 discount = sRandomPlayerbotMgr.GetTradeDiscount(bot, trader); uint32 discount = sRandomPlayerbotMgr.GetTradeDiscount(bot, trader);
if (CheckTrade()) if (CheckTrade())
{ {
int32 botMoney = CalculateCost(bot, true);
std::map<uint32, uint32> givenItemIds, takenItemIds; std::map<uint32, uint32> givenItemIds, takenItemIds;
for (uint32 slot = 0; slot < TRADE_SLOT_TRADED_COUNT; ++slot) for (uint32 slot = 0; slot < TRADE_SLOT_TRADED_COUNT; ++slot)
{ {
@ -161,15 +159,11 @@ bool TradeStatusAction::CheckTrade()
if (!botAI->HasActivePlayerMaster() && GET_PLAYERBOT_AI(bot->GetTrader())) if (!botAI->HasActivePlayerMaster() && GET_PLAYERBOT_AI(bot->GetTrader()))
{ {
bool isGivingItem = false;
for (uint32 slot = 0; slot < TRADE_SLOT_TRADED_COUNT; ++slot) for (uint32 slot = 0; slot < TRADE_SLOT_TRADED_COUNT; ++slot)
{ {
Item* item = bot->GetTradeData()->GetItem((TradeSlots)slot); Item* item = bot->GetTradeData()->GetItem((TradeSlots)slot);
if (item) if (item)
{
isGivingItem = true;
break; break;
}
} }
bool isGettingItem = false; bool isGettingItem = false;
for (uint32 slot = 0; slot < TRADE_SLOT_TRADED_COUNT; ++slot) for (uint32 slot = 0; slot < TRADE_SLOT_TRADED_COUNT; ++slot)

View File

@ -134,8 +134,6 @@ bool MoveFromDarkPortalAction::Execute(Event /*event*/)
if (bot->GetTeamId() == TEAM_ALLIANCE) if (bot->GetTeamId() == TEAM_ALLIANCE)
return MoveTo(530, -319.261f, 1027.213, 54.172638f, false, true); return MoveTo(530, -319.261f, 1027.213, 54.172638f, false, true);
else
return MoveTo(530, -180.444f, 1027.947, 54.181538f, false, true);
return false; return MoveTo(530, -180.444f, 1027.947, 54.181538f, false, true);
} }

View File

@ -69,7 +69,6 @@ bool UseItemAction::UseItem(Item* item, ObjectGuid goGuid, Item* itemTarget, Uni
uint8 bagIndex = item->GetBagSlot(); uint8 bagIndex = item->GetBagSlot();
uint8 slot = item->GetSlot(); uint8 slot = item->GetSlot();
uint8 spell_index = 0;
uint8 cast_count = 1; uint8 cast_count = 1;
ObjectGuid item_guid = item->GetGUID(); ObjectGuid item_guid = item->GetGUID();
uint32 glyphIndex = 0; uint32 glyphIndex = 0;

View File

@ -108,7 +108,7 @@ std::string const WhoAction::QuerySkill(std::string const text)
return out.str(); return out.str();
} }
std::string const WhoAction::QuerySpec(std::string const text) std::string const WhoAction::QuerySpec(std::string const /*text*/)
{ {
std::ostringstream out; std::ostringstream out;

View File

@ -8,11 +8,13 @@
bool WipeAction::Execute(Event event) bool WipeAction::Execute(Event event)
{ {
Player* master = event.getOwner(); Player* const owner = event.getOwner();
Player* const master = this->botAI->GetMaster();
if (botAI->GetMaster()->GetGUID() != event.getOwner()->GetGUID()) if (owner != nullptr && master != nullptr && master->GetGUID() != owner->GetGUID())
return false; return false;
bot->Kill(bot, bot); bot->Kill(bot, bot);
return true; return true;
} }

View File

@ -8,7 +8,7 @@
#include "LootValues.h" #include "LootValues.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "Playerbots.h" #include "PlayerbotAI.h"
#include "PvpValues.h" #include "PvpValues.h"
#include "QuestValues.h" #include "QuestValues.h"

View File

@ -11,11 +11,6 @@
#include "ServerFacade.h" #include "ServerFacade.h"
#include "SharedDefines.h" #include "SharedDefines.h"
static float GetSpeedInMotion(Unit* target)
{
return target->GetSpeed(Movement::SelectSpeedType(target->GetUnitMovementFlags()));
}
bool EnemyTooCloseForSpellTrigger::IsActive() bool EnemyTooCloseForSpellTrigger::IsActive()
{ {
Unit* target = AI_VALUE(Unit*, "current target"); Unit* target = AI_VALUE(Unit*, "current target");

View File

@ -144,8 +144,6 @@ UnitPosition MultiLineUnitPlacer::Place(FormationUnit* unit, uint32 index, uint3
uint32 lineNo = index / 6; uint32 lineNo = index / 6;
uint32 indexInLine = index % 6; uint32 indexInLine = index % 6;
uint32 lineSize = std::max(count - lineNo * 6, uint32(6)); uint32 lineSize = std::max(count - lineNo * 6, uint32(6));
float x = cos(orientation) * sPlayerbotAIConfig.followDistance * lineNo;
float y = sin(orientation) * sPlayerbotAIConfig.followDistance * lineNo;
return placer.Place(unit, indexInLine, lineSize); return placer.Place(unit, indexInLine, lineSize);
} }
@ -160,17 +158,13 @@ UnitPosition SingleLineUnitPlacer::Place(FormationUnit* unit, uint32 index, uint
void FormationSlot::Move(float dx, float dy) void FormationSlot::Move(float dx, float dy)
{ {
for (FormationUnit* unit : units) for (FormationUnit* unit : units)
{
unit->SetLocation(unit->GetX() + dx, unit->GetY() + dy); unit->SetLocation(unit->GetX() + dx, unit->GetY() + dy);
}
} }
FormationSlot::~FormationSlot() FormationSlot::~FormationSlot()
{ {
for (FormationUnit* unit : units) for (FormationUnit* unit : units)
{
delete unit; delete unit;
}
units.clear(); units.clear();
} }

View File

@ -30,7 +30,6 @@ Unit* GrindTargetValue::Calculate()
Unit* GrindTargetValue::FindTargetForGrinding(uint32 assistCount) Unit* GrindTargetValue::FindTargetForGrinding(uint32 assistCount)
{ {
uint32 memberCount = 1;
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
Player* master = GetMaster(); Player* master = GetMaster();
@ -65,7 +64,6 @@ Unit* GrindTargetValue::FindTargetForGrinding(uint32 assistCount)
if (!unit->IsInWorld() || unit->IsDuringRemoveFromWorld()) if (!unit->IsInWorld() || unit->IsDuringRemoveFromWorld())
continue; continue;
auto& rep = bot->ToPlayer()->GetReputationMgr();
if (unit->ToCreature() && !unit->ToCreature()->GetCreatureTemplate()->lootid && if (unit->ToCreature() && !unit->ToCreature()->GetCreatureTemplate()->lootid &&
bot->GetReactionTo(unit) >= REP_NEUTRAL) bot->GetReactionTo(unit) >= REP_NEUTRAL)
continue; continue;

View File

@ -69,8 +69,10 @@ ItemUsage ItemUsageValue::Calculate()
if (proto->Class == ITEM_CLASS_KEY) if (proto->Class == ITEM_CLASS_KEY)
return ITEM_USAGE_USE; return ITEM_USAGE_USE;
const uint32_t maxCount = proto->MaxCount;
if (proto->Class == ITEM_CLASS_CONSUMABLE && if (proto->Class == ITEM_CLASS_CONSUMABLE &&
(proto->MaxCount == 0 || bot->GetItemCount(itemId, false) < proto->MaxCount)) (maxCount == 0 || bot->GetItemCount(itemId, false) < maxCount))
{ {
std::string const foodType = GetConsumableType(proto, bot->GetPower(POWER_MANA)); std::string const foodType = GetConsumableType(proto, bot->GetPower(POWER_MANA));

View File

@ -6,7 +6,7 @@
#include "LeastHpTargetValue.h" #include "LeastHpTargetValue.h"
#include "AttackersValue.h" #include "AttackersValue.h"
#include "PlayerbotAI.h" #include "Playerbots.h"
class FindLeastHpTargetStrategy : public FindNonCcTargetStrategy class FindLeastHpTargetStrategy : public FindNonCcTargetStrategy
{ {
@ -15,7 +15,6 @@ public:
void CheckAttacker(Unit* attacker, ThreatMgr* threatMgr) override void CheckAttacker(Unit* attacker, ThreatMgr* threatMgr) override
{ {
Player* bot = botAI->GetBot();
if (IsCcTarget(attacker)) if (IsCcTarget(attacker))
return; return;
@ -30,5 +29,6 @@ protected:
Unit* LeastHpTargetValue::Calculate() Unit* LeastHpTargetValue::Calculate()
{ {
FindLeastHpTargetStrategy strategy(botAI); FindLeastHpTargetStrategy strategy(botAI);
return FindTarget(&strategy); return FindTarget(&strategy);
} }

View File

@ -8,7 +8,6 @@
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "PartyMemberValue.h" #include "PartyMemberValue.h"
#include "PlayerbotAIConfig.h"
class PlayerbotAI; class PlayerbotAI;
class Unit; class Unit;
@ -16,8 +15,7 @@ class Unit;
class PartyMemberWithoutAuraValue : public PartyMemberValue, public Qualified class PartyMemberWithoutAuraValue : public PartyMemberValue, public Qualified
{ {
public: public:
PartyMemberWithoutAuraValue(PlayerbotAI* botAI, std::string const name = "party member without aura", PartyMemberWithoutAuraValue(PlayerbotAI* botAI, std::string const name = "party member without aura")
float range = sPlayerbotAIConfig.sightDistance)
: PartyMemberValue(botAI, name) : PartyMemberValue(botAI, name)
{ {
} }

View File

@ -5,15 +5,14 @@
#include "PossibleRpgTargetsValue.h" #include "PossibleRpgTargetsValue.h"
#include "AiObjectContext.h" #include "CellImpl.h"
#include "GridNotifiers.h"
#include "GridNotifiersImpl.h"
#include "ObjectGuid.h" #include "ObjectGuid.h"
#include "Playerbots.h"
#include "ServerFacade.h" #include "ServerFacade.h"
#include "SharedDefines.h" #include "SharedDefines.h"
#include "NearestGameObjects.h" #include "NearestGameObjects.h"
#include "GridNotifiers.h"
#include "GridNotifiersImpl.h"
#include "CellImpl.h"
#include "TravelMgr.h"
std::vector<uint32> PossibleRpgTargetsValue::allowedNpcFlags; std::vector<uint32> PossibleRpgTargetsValue::allowedNpcFlags;
@ -74,15 +73,9 @@ bool PossibleRpgTargetsValue::AcceptUnit(Unit* unit)
} }
TravelTarget* travelTarget = context->GetValue<TravelTarget*>("travel target")->Get(); TravelTarget* travelTarget = context->GetValue<TravelTarget*>("travel target")->Get();
if (travelTarget && travelTarget->getDestination() &&
if ( travelTarget->getDestination()->getEntry() == unit->GetEntry())
travelTarget != nullptr
&& travelTarget->getDestination()
&& (uint32_t)travelTarget->getDestination()->getEntry() == unit->GetEntry()
)
{
return true; return true;
}
if (urand(1, 100) < 25 && unit->IsFriendlyTo(bot)) if (urand(1, 100) < 25 && unit->IsFriendlyTo(bot))
return true; return true;

View File

@ -14,7 +14,6 @@ Unit* SnareTargetValue::Calculate()
std::string const spell = qualifier; std::string const spell = qualifier;
GuidVector attackers = botAI->GetAiObjectContext()->GetValue<GuidVector>("attackers")->Get(); GuidVector attackers = botAI->GetAiObjectContext()->GetValue<GuidVector>("attackers")->Get();
Unit* target = botAI->GetAiObjectContext()->GetValue<Unit*>("current target")->Get();
for (ObjectGuid const guid : attackers) for (ObjectGuid const guid : attackers)
{ {
Unit* unit = botAI->GetUnit(guid); Unit* unit = botAI->GetUnit(guid);

View File

@ -208,7 +208,6 @@ uint8 BagSpaceValue::Calculate()
++totalused; ++totalused;
} }
uint32 totalfree = 16 - totalused;
for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag)
{ {
const Bag* const pBag = (Bag*)bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag); const Bag* const pBag = (Bag*)bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag);
@ -218,7 +217,6 @@ uint8 BagSpaceValue::Calculate()
if (pBagProto->Class == ITEM_CLASS_CONTAINER && pBagProto->SubClass == ITEM_SUBCLASS_CONTAINER) if (pBagProto->Class == ITEM_CLASS_CONTAINER && pBagProto->SubClass == ITEM_SUBCLASS_CONTAINER)
{ {
total += pBag->GetBagSize(); total += pBag->GetBagSize();
totalfree += pBag->GetFreeSlots();
totalused += pBag->GetBagSize() - pBag->GetFreeSlots(); totalused += pBag->GetBagSize() - pBag->GetFreeSlots();
} }
} }

View File

@ -16,7 +16,6 @@
#include "ObjectAccessor.h" #include "ObjectAccessor.h"
using ai::buff::MakeAuraQualifierForBuff; using ai::buff::MakeAuraQualifierForBuff;
using ai::buff::UpgradeToGroupIfAppropriate;
// Helper : detect tank role on the target (player bot or not) return true if spec is tank or if the bot have tank strategies (bear/tank/tank face). // Helper : detect tank role on the target (player bot or not) return true if spec is tank or if the bot have tank strategies (bear/tank/tank face).
static inline bool IsTankRole(Player* p) static inline bool IsTankRole(Player* p)
@ -53,11 +52,6 @@ static inline bool IsOnlyPaladinInGroup(Player* bot)
return pals == 1u; return pals == 1u;
} }
static inline bool GroupHasTankOfClass(Group* g, uint8 classId)
{
return GroupHasTankOfClass(g, static_cast<Classes>(classId));
}
inline std::string const GetActualBlessingOfMight(Unit* target) inline std::string const GetActualBlessingOfMight(Unit* target)
{ {
if (!target->ToPlayer()) if (!target->ToPlayer())

View File

@ -283,20 +283,6 @@ static uint32 GetRequiredTotemSpellId(PlayerbotAI* ai, const char* strategies[],
return 0; // No relevant strategy active, or bot doesn't know any rank return 0; // No relevant strategy active, or bot doesn't know any rank
} }
// Get the spellId of the currently summoned totem in the slot
static uint32 GetSummonedTotemSpellId(Player* bot, uint8 slot)
{
ObjectGuid guid = bot->m_SummonSlot[slot];
if (guid.IsEmpty())
return 0;
Creature* totem = bot->GetMap()->GetCreature(guid);
if (!totem)
return 0;
return totem->GetUInt32Value(UNIT_CREATED_BY_SPELL);
}
bool NoEarthTotemTrigger::IsActive() bool NoEarthTotemTrigger::IsActive()
{ {
// Check if the bot has Stoneskin Totem (required level 4) and prevents the trigger firing if it doesn't // Check if the bot has Stoneskin Totem (required level 4) and prevents the trigger firing if it doesn't

View File

@ -359,10 +359,8 @@ class SetTotemTrigger : public Trigger
public: public:
// Template constructor: infers N (size of the id array) at compile time // Template constructor: infers N (size of the id array) at compile time
template <size_t N> template <size_t N>
SetTotemTrigger(PlayerbotAI* ai, std::string const& spellName, uint32 requiredSpellId, SetTotemTrigger(PlayerbotAI* ai, std::string const& spellName, const uint32 (&ids)[N], int actionButtonId)
const uint32 (&ids)[N], int actionButtonId)
: Trigger(ai, "set " + spellName) : Trigger(ai, "set " + spellName)
, requiredSpellId(requiredSpellId)
, totemSpellIds(ids) , totemSpellIds(ids)
, totemSpellIdsCount(N) , totemSpellIdsCount(N)
, actionButtonId(actionButtonId) , actionButtonId(actionButtonId)
@ -370,7 +368,6 @@ public:
bool IsActive() override; bool IsActive() override;
private: private:
uint32 requiredSpellId;
uint32 const* totemSpellIds; uint32 const* totemSpellIds;
size_t totemSpellIdsCount; size_t totemSpellIdsCount;
int actionButtonId; int actionButtonId;
@ -380,119 +377,119 @@ class SetStrengthOfEarthTotemTrigger : public SetTotemTrigger
{ {
public: public:
SetStrengthOfEarthTotemTrigger(PlayerbotAI* ai) SetStrengthOfEarthTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "strength of earth totem", SPELL_STRENGTH_OF_EARTH_TOTEM_RANK_1, STRENGTH_OF_EARTH_TOTEM, TOTEM_BAR_SLOT_EARTH) {} : SetTotemTrigger(ai, "strength of earth totem", STRENGTH_OF_EARTH_TOTEM, TOTEM_BAR_SLOT_EARTH) {}
}; };
class SetStoneskinTotemTrigger : public SetTotemTrigger class SetStoneskinTotemTrigger : public SetTotemTrigger
{ {
public: public:
SetStoneskinTotemTrigger(PlayerbotAI* ai) SetStoneskinTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "stoneskin totem", SPELL_STONESKIN_TOTEM_RANK_1, STONESKIN_TOTEM, TOTEM_BAR_SLOT_EARTH) {} : SetTotemTrigger(ai, "stoneskin totem", STONESKIN_TOTEM, TOTEM_BAR_SLOT_EARTH) {}
}; };
class SetTremorTotemTrigger : public SetTotemTrigger class SetTremorTotemTrigger : public SetTotemTrigger
{ {
public: public:
SetTremorTotemTrigger(PlayerbotAI* ai) SetTremorTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "tremor totem", SPELL_TREMOR_TOTEM_RANK_1, TREMOR_TOTEM, TOTEM_BAR_SLOT_EARTH) {} : SetTotemTrigger(ai, "tremor totem", TREMOR_TOTEM, TOTEM_BAR_SLOT_EARTH) {}
}; };
class SetEarthbindTotemTrigger : public SetTotemTrigger class SetEarthbindTotemTrigger : public SetTotemTrigger
{ {
public: public:
SetEarthbindTotemTrigger(PlayerbotAI* ai) SetEarthbindTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "earthbind totem", SPELL_EARTHBIND_TOTEM_RANK_1, EARTHBIND_TOTEM, TOTEM_BAR_SLOT_EARTH) {} : SetTotemTrigger(ai, "earthbind totem", EARTHBIND_TOTEM, TOTEM_BAR_SLOT_EARTH) {}
}; };
class SetSearingTotemTrigger : public SetTotemTrigger class SetSearingTotemTrigger : public SetTotemTrigger
{ {
public: public:
SetSearingTotemTrigger(PlayerbotAI* ai) SetSearingTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "searing totem", SPELL_SEARING_TOTEM_RANK_1, SEARING_TOTEM, TOTEM_BAR_SLOT_FIRE) {} : SetTotemTrigger(ai, "searing totem", SEARING_TOTEM, TOTEM_BAR_SLOT_FIRE) {}
}; };
class SetMagmaTotemTrigger : public SetTotemTrigger class SetMagmaTotemTrigger : public SetTotemTrigger
{ {
public: public:
SetMagmaTotemTrigger(PlayerbotAI* ai) SetMagmaTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "magma totem", SPELL_MAGMA_TOTEM_RANK_1, MAGMA_TOTEM, TOTEM_BAR_SLOT_FIRE) {} : SetTotemTrigger(ai, "magma totem", MAGMA_TOTEM, TOTEM_BAR_SLOT_FIRE) {}
}; };
class SetFlametongueTotemTrigger : public SetTotemTrigger class SetFlametongueTotemTrigger : public SetTotemTrigger
{ {
public: public:
SetFlametongueTotemTrigger(PlayerbotAI* ai) SetFlametongueTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "flametongue totem", SPELL_FLAMETONGUE_TOTEM_RANK_1, FLAMETONGUE_TOTEM, TOTEM_BAR_SLOT_FIRE) {} : SetTotemTrigger(ai, "flametongue totem", FLAMETONGUE_TOTEM, TOTEM_BAR_SLOT_FIRE) {}
}; };
class SetTotemOfWrathTrigger : public SetTotemTrigger class SetTotemOfWrathTrigger : public SetTotemTrigger
{ {
public: public:
SetTotemOfWrathTrigger(PlayerbotAI* ai) SetTotemOfWrathTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "totem of wrath", SPELL_TOTEM_OF_WRATH_RANK_1, TOTEM_OF_WRATH, TOTEM_BAR_SLOT_FIRE) {} : SetTotemTrigger(ai, "totem of wrath", TOTEM_OF_WRATH, TOTEM_BAR_SLOT_FIRE) {}
}; };
class SetFrostResistanceTotemTrigger : public SetTotemTrigger class SetFrostResistanceTotemTrigger : public SetTotemTrigger
{ {
public: public:
SetFrostResistanceTotemTrigger(PlayerbotAI* ai) SetFrostResistanceTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "frost resistance totem", SPELL_FROST_RESISTANCE_TOTEM_RANK_1, FROST_RESISTANCE_TOTEM, TOTEM_BAR_SLOT_FIRE) {} : SetTotemTrigger(ai, "frost resistance totem", FROST_RESISTANCE_TOTEM, TOTEM_BAR_SLOT_FIRE) {}
}; };
class SetHealingStreamTotemTrigger : public SetTotemTrigger class SetHealingStreamTotemTrigger : public SetTotemTrigger
{ {
public: public:
SetHealingStreamTotemTrigger(PlayerbotAI* ai) SetHealingStreamTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "healing stream totem", SPELL_HEALING_STREAM_TOTEM_RANK_1, HEALING_STREAM_TOTEM, TOTEM_BAR_SLOT_WATER) {} : SetTotemTrigger(ai, "healing stream totem", HEALING_STREAM_TOTEM, TOTEM_BAR_SLOT_WATER) {}
}; };
class SetManaSpringTotemTrigger : public SetTotemTrigger class SetManaSpringTotemTrigger : public SetTotemTrigger
{ {
public: public:
SetManaSpringTotemTrigger(PlayerbotAI* ai) SetManaSpringTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "mana spring totem", SPELL_MANA_SPRING_TOTEM_RANK_1, MANA_SPRING_TOTEM, TOTEM_BAR_SLOT_WATER) {} : SetTotemTrigger(ai, "mana spring totem", MANA_SPRING_TOTEM, TOTEM_BAR_SLOT_WATER) {}
}; };
class SetCleansingTotemTrigger : public SetTotemTrigger class SetCleansingTotemTrigger : public SetTotemTrigger
{ {
public: public:
SetCleansingTotemTrigger(PlayerbotAI* ai) SetCleansingTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "cleansing totem", SPELL_CLEANSING_TOTEM_RANK_1, CLEANSING_TOTEM, TOTEM_BAR_SLOT_WATER) {} : SetTotemTrigger(ai, "cleansing totem", CLEANSING_TOTEM, TOTEM_BAR_SLOT_WATER) {}
}; };
class SetFireResistanceTotemTrigger : public SetTotemTrigger class SetFireResistanceTotemTrigger : public SetTotemTrigger
{ {
public: public:
SetFireResistanceTotemTrigger(PlayerbotAI* ai) SetFireResistanceTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "fire resistance totem", SPELL_FIRE_RESISTANCE_TOTEM_RANK_1, FIRE_RESISTANCE_TOTEM, TOTEM_BAR_SLOT_WATER) {} : SetTotemTrigger(ai, "fire resistance totem", FIRE_RESISTANCE_TOTEM, TOTEM_BAR_SLOT_WATER) {}
}; };
class SetWrathOfAirTotemTrigger : public SetTotemTrigger class SetWrathOfAirTotemTrigger : public SetTotemTrigger
{ {
public: public:
SetWrathOfAirTotemTrigger(PlayerbotAI* ai) SetWrathOfAirTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "wrath of air totem", SPELL_WRATH_OF_AIR_TOTEM_RANK_1, WRATH_OF_AIR_TOTEM, TOTEM_BAR_SLOT_AIR) {} : SetTotemTrigger(ai, "wrath of air totem", WRATH_OF_AIR_TOTEM, TOTEM_BAR_SLOT_AIR) {}
}; };
class SetWindfuryTotemTrigger : public SetTotemTrigger class SetWindfuryTotemTrigger : public SetTotemTrigger
{ {
public: public:
SetWindfuryTotemTrigger(PlayerbotAI* ai) SetWindfuryTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "windfury totem", SPELL_WINDFURY_TOTEM_RANK_1, WINDFURY_TOTEM, TOTEM_BAR_SLOT_AIR) {} : SetTotemTrigger(ai, "windfury totem", WINDFURY_TOTEM, TOTEM_BAR_SLOT_AIR) {}
}; };
class SetNatureResistanceTotemTrigger : public SetTotemTrigger class SetNatureResistanceTotemTrigger : public SetTotemTrigger
{ {
public: public:
SetNatureResistanceTotemTrigger(PlayerbotAI* ai) SetNatureResistanceTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "nature resistance totem", SPELL_NATURE_RESISTANCE_TOTEM_RANK_1, NATURE_RESISTANCE_TOTEM, TOTEM_BAR_SLOT_AIR) {} : SetTotemTrigger(ai, "nature resistance totem", NATURE_RESISTANCE_TOTEM, TOTEM_BAR_SLOT_AIR) {}
}; };
class SetGroundingTotemTrigger : public SetTotemTrigger class SetGroundingTotemTrigger : public SetTotemTrigger
{ {
public: public:
SetGroundingTotemTrigger(PlayerbotAI* ai) SetGroundingTotemTrigger(PlayerbotAI* ai)
: SetTotemTrigger(ai, "grounding totem", SPELL_GROUNDING_TOTEM_RANK_1, GROUNDING_TOTEM, TOTEM_BAR_SLOT_AIR) {} : SetTotemTrigger(ai, "grounding totem", GROUNDING_TOTEM, TOTEM_BAR_SLOT_AIR) {}
}; };
#endif #endif

View File

@ -17,11 +17,9 @@ bool MoveFromBronjahmAction::Execute(Event /*event*/)
bool AttackCorruptedSoulFragmentAction::Execute(Event /*event*/) bool AttackCorruptedSoulFragmentAction::Execute(Event /*event*/)
{ {
Unit* currentTarget = AI_VALUE(Unit*, "current target");
GuidVector targets = AI_VALUE(GuidVector, "possible targets"); GuidVector targets = AI_VALUE(GuidVector, "possible targets");
// If no valid skull target, search for corrupted soul fragment // If no valid skull target, search for corrupted soul fragment
Unit* empoweredPrince = nullptr;
for (auto i = targets.begin(); i != targets.end(); ++i) for (auto i = targets.begin(); i != targets.end(); ++i)
{ {
Unit* unit = botAI->GetUnit(*i); Unit* unit = botAI->GetUnit(*i);
@ -30,8 +28,6 @@ bool AttackCorruptedSoulFragmentAction::Execute(Event /*event*/)
if (unit->GetEntry() == NPC_CORRUPTED_SOUL_FRAGMENT) if (unit->GetEntry() == NPC_CORRUPTED_SOUL_FRAGMENT)
{ {
empoweredPrince = unit;
// Mark corrupted soul fragment with skull if in group and not already marked // Mark corrupted soul fragment with skull if in group and not already marked
if (Group* group = bot->GetGroup()) if (Group* group = bot->GetGroup())
{ {

View File

@ -23,7 +23,6 @@ bool AttackSnakeWrapAction::Execute(Event /*event*/)
Unit* boss = AI_VALUE2(Unit*, "find target", "slad'ran"); Unit* boss = AI_VALUE2(Unit*, "find target", "slad'ran");
if (!boss) { return false; } if (!boss) { return false; }
Unit* snakeWrap = nullptr;
// Target is not findable from threat table using AI_VALUE2(), // Target is not findable from threat table using AI_VALUE2(),
// therefore need to search manually for the unit name // therefore need to search manually for the unit name
GuidVector targets = AI_VALUE(GuidVector, "possible targets no los"); GuidVector targets = AI_VALUE(GuidVector, "possible targets no los");

View File

@ -55,10 +55,6 @@ bool ToCLanceAction::Execute(Event /*event*/)
// If we found the lance, equip it // If we found the lance, equip it
if (lanceItem) if (lanceItem)
{ {
// Store the lance's current position
uint8 srcBag = lanceItem->GetBagSlot();
uint8 srcSlot = lanceItem->GetSlot();
// First unequip current weapon if it exists // First unequip current weapon if it exists
if (oldWeapon) if (oldWeapon)
bot->SwapItem(oldWeapon->GetPos(), lanceItem->GetPos()); bot->SwapItem(oldWeapon->GetPos(), lanceItem->GetPos());

View File

@ -399,7 +399,9 @@ bool HighKingMaulgarBanishFelstalkerAction::Execute(Event /*event*/)
} }
} }
if (warlockIndex >= 0 && warlockIndex < felStalkers.size()) const int64_t felStalkersSize = felStalkers.size();
if (warlockIndex >= 0 && warlockIndex < felStalkersSize)
{ {
Unit* assignedFelStalker = felStalkers[warlockIndex]; Unit* assignedFelStalker = felStalkers[warlockIndex];
if (!botAI->HasAura("banish", assignedFelStalker) && botAI->CanCastSpell("banish", assignedFelStalker)) if (!botAI->HasAura("banish", assignedFelStalker) && botAI->CanCastSpell("banish", assignedFelStalker))
@ -511,7 +513,6 @@ bool GruulTheDragonkillerTanksPositionBossAction::Execute(Event /*event*/)
if (distanceToTankPosition > maxDistance) if (distanceToTankPosition > maxDistance)
{ {
float step = std::min(maxDistance, distanceToTankPosition);
float moveX = bot->GetPositionX() + (dX / distanceToTankPosition) * maxDistance; float moveX = bot->GetPositionX() + (dX / distanceToTankPosition) * maxDistance;
float moveY = bot->GetPositionY() + (dY / distanceToTankPosition) * maxDistance; float moveY = bot->GetPositionY() + (dY / distanceToTankPosition) * maxDistance;
const float moveZ = position.GetPositionZ(); const float moveZ = position.GetPositionZ();

View File

@ -1034,8 +1034,6 @@ bool IccDbsTankPositionAction::CrowdControlBloodBeasts()
NPC_BLOOD_BEAST4}; NPC_BLOOD_BEAST4};
const GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); const GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
bool appliedCC = false;
for (auto const& npc : npcs) for (auto const& npc : npcs)
{ {
Unit* unit = botAI->GetUnit(npc); Unit* unit = botAI->GetUnit(npc);
@ -1054,73 +1052,43 @@ bool IccDbsTankPositionAction::CrowdControlBloodBeasts()
{ {
case CLASS_MAGE: case CLASS_MAGE:
if (!botAI->HasAura("Frost Nova", unit)) if (!botAI->HasAura("Frost Nova", unit))
{
botAI->CastSpell("Frost Nova", unit); botAI->CastSpell("Frost Nova", unit);
appliedCC = true;
}
break; break;
case CLASS_DRUID: case CLASS_DRUID:
if (!botAI->HasAura("Entangling Roots", unit)) if (!botAI->HasAura("Entangling Roots", unit))
{
botAI->CastSpell("Entangling Roots", unit); botAI->CastSpell("Entangling Roots", unit);
appliedCC = true;
}
break; break;
case CLASS_PALADIN: case CLASS_PALADIN:
if (!botAI->HasAura("Hammer of Justice", unit)) if (!botAI->HasAura("Hammer of Justice", unit))
{
botAI->CastSpell("Hammer of Justice", unit); botAI->CastSpell("Hammer of Justice", unit);
appliedCC = true;
}
break; break;
case CLASS_WARRIOR: case CLASS_WARRIOR:
if (!botAI->HasAura("Hamstring", unit)) if (!botAI->HasAura("Hamstring", unit))
{
botAI->CastSpell("Hamstring", unit); botAI->CastSpell("Hamstring", unit);
appliedCC = true;
}
break; break;
case CLASS_HUNTER: case CLASS_HUNTER:
if (!botAI->HasAura("Concussive Shot", unit)) if (!botAI->HasAura("Concussive Shot", unit))
{
botAI->CastSpell("Concussive Shot", unit); botAI->CastSpell("Concussive Shot", unit);
appliedCC = true;
}
break; break;
case CLASS_ROGUE: case CLASS_ROGUE:
if (!botAI->HasAura("Kidney Shot", unit)) if (!botAI->HasAura("Kidney Shot", unit))
{
botAI->CastSpell("Kidney Shot", unit); botAI->CastSpell("Kidney Shot", unit);
appliedCC = true;
}
break; break;
case CLASS_SHAMAN: case CLASS_SHAMAN:
if (!botAI->HasAura("Frost Shock", unit)) if (!botAI->HasAura("Frost Shock", unit))
{
botAI->CastSpell("Frost Shock", unit); botAI->CastSpell("Frost Shock", unit);
appliedCC = true;
}
break; break;
case CLASS_DEATH_KNIGHT: case CLASS_DEATH_KNIGHT:
if (!botAI->HasAura("Chains of Ice", unit)) if (!botAI->HasAura("Chains of Ice", unit))
{
botAI->CastSpell("Chains of Ice", unit); botAI->CastSpell("Chains of Ice", unit);
appliedCC = true;
}
break; break;
case CLASS_PRIEST: case CLASS_PRIEST:
if (!botAI->HasAura("Psychic Scream", unit)) if (!botAI->HasAura("Psychic Scream", unit))
{
botAI->CastSpell("Psychic Scream", unit); botAI->CastSpell("Psychic Scream", unit);
appliedCC = true;
}
break; break;
case CLASS_WARLOCK: case CLASS_WARLOCK:
if (!botAI->HasAura("Fear", unit)) if (!botAI->HasAura("Fear", unit))
{
botAI->CastSpell("Fear", unit); botAI->CastSpell("Fear", unit);
appliedCC = true;
}
break; break;
default: default:
break; break;
@ -1464,7 +1432,6 @@ int IccFestergutGroupPositionAction::CalculatePositionIndex(Group* group)
else else
{ {
// Fill remaining spots in second row // Fill remaining spots in second row
int spotsInFirstRow = 6;
int spotsInSecondRow = healerSpotsUsed - 6; int spotsInSecondRow = healerSpotsUsed - 6;
int remainingInSecondRow = 6 - spotsInSecondRow; int remainingInSecondRow = 6 - spotsInSecondRow;
@ -1511,7 +1478,6 @@ int IccFestergutGroupPositionAction::CalculatePositionIndex(Group* group)
bool IccFestergutSporeAction::Execute(Event /*event*/) bool IccFestergutSporeAction::Execute(Event /*event*/)
{ {
constexpr float POSITION_TOLERANCE = 4.0f; constexpr float POSITION_TOLERANCE = 4.0f;
constexpr float SPREAD_RADIUS = 2.0f;
// Check if bot has spore // Check if bot has spore
bool hasSpore = bot->HasAura(SPELL_GAS_SPORE); // gas spore bool hasSpore = bot->HasAura(SPELL_GAS_SPORE); // gas spore
@ -1661,7 +1627,7 @@ bool IccRotfaceTankPositionAction::PositionMainTankAndMelee(Unit* boss)
{ {
bool isBossCasting = false; bool isBossCasting = false;
if (boss && boss->HasUnitState(UNIT_STATE_CASTING) && boss->GetCurrentSpell(SPELL_SLIME_SPRAY)) if (boss && boss->HasUnitState(UNIT_STATE_CASTING) && boss->GetCurrentSpell(SPELL_SLIME_SPRAY))
bool isBossCasting = true; isBossCasting = true;
if (bot->GetExactDist2d(ICC_ROTFACE_CENTER_POSITION) > 7.0f && botAI->HasAggro(boss) && botAI->IsMainTank(bot)) if (bot->GetExactDist2d(ICC_ROTFACE_CENTER_POSITION) > 7.0f && botAI->HasAggro(boss) && botAI->IsMainTank(bot))
MoveTo(bot->GetMapId(), ICC_ROTFACE_CENTER_POSITION.GetPositionX(), MoveTo(bot->GetMapId(), ICC_ROTFACE_CENTER_POSITION.GetPositionX(),
@ -1829,21 +1795,7 @@ bool IccRotfaceGroupPositionAction::Execute(Event /*event*/)
return false; return false;
const GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); const GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
bool floodPresent = false;
for (auto const& npc : npcs)
{
Unit* unit = botAI->GetUnit(npc);
if (!unit || !botAI->HasAura("Ooze Flood", unit))
continue;
float puddleDistance = bot->GetExactDist2d(unit);
if (puddleDistance < 30.0f)
floodPresent = true;
}
Unit* bigOoze = AI_VALUE2(Unit*, "find target", "big ooze");
bool hasOozeFlood = botAI->HasAura("Ooze Flood", bot); bool hasOozeFlood = botAI->HasAura("Ooze Flood", bot);
Unit* smallOoze = AI_VALUE2(Unit*, "find target", "little ooze"); Unit* smallOoze = AI_VALUE2(Unit*, "find target", "little ooze");
bool hasMutatedInfection = botAI->HasAura("Mutated Infection", bot); bool hasMutatedInfection = botAI->HasAura("Mutated Infection", bot);
@ -2014,7 +1966,7 @@ bool IccRotfaceGroupPositionAction::PositionRangedAndHealers(Unit* boss,Unit *sm
Difficulty diff = bot->GetRaidDifficulty(); Difficulty diff = bot->GetRaidDifficulty();
bool isBossCasting = false; bool isBossCasting = false;
if (boss && boss->HasUnitState(UNIT_STATE_CASTING) && boss->GetCurrentSpell(SPELL_SLIME_SPRAY)) if (boss && boss->HasUnitState(UNIT_STATE_CASTING) && boss->GetCurrentSpell(SPELL_SLIME_SPRAY))
bool isBossCasting = true; isBossCasting = true;
bool isHeroic = (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC); bool isHeroic = (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC);
@ -2070,7 +2022,6 @@ bool IccRotfaceGroupPositionAction::FindAndMoveFromClosestMember(Unit* boss, Uni
const float maxMoveDistance = 12.0f; // Limit maximum movement distance const float maxMoveDistance = 12.0f; // Limit maximum movement distance
const float puddleSafeDistance = 30.0f; // Minimum distance to stay away from puddle const float puddleSafeDistance = 30.0f; // Minimum distance to stay away from puddle
const float minCenterDistance = 20.0f; // Minimum distance from center position const float minCenterDistance = 20.0f; // Minimum distance from center position
const bool isRanged = botAI->IsRanged(bot) || botAI->IsHeal(bot);
// Ranged: spread from other members // Ranged: spread from other members
const GuidVector members = AI_VALUE(GuidVector, "group members"); const GuidVector members = AI_VALUE(GuidVector, "group members");
@ -2233,7 +2184,6 @@ bool IccRotfaceMoveAwayFromExplosionAction::MoveToRandomSafeLocation()
// Move in increments of 5.0f towards the calculated position // Move in increments of 5.0f towards the calculated position
float currentX = bot->GetPositionX(); float currentX = bot->GetPositionX();
float currentY = bot->GetPositionY(); float currentY = bot->GetPositionY();
float currentZ = bot->GetPositionZ();
float directionX = moveX - currentX; float directionX = moveX - currentX;
float directionY = moveY - currentY; float directionY = moveY - currentY;
@ -2276,7 +2226,6 @@ Unit* IccPutricideGrowingOozePuddleAction::FindClosestThreateningPuddle()
Unit* closestPuddle = nullptr; Unit* closestPuddle = nullptr;
float closestDistance = FLT_MAX; float closestDistance = FLT_MAX;
float closestSafeDistance = BASE_RADIUS;
for (auto const& npc : npcs) for (auto const& npc : npcs)
{ {
@ -2293,7 +2242,6 @@ Unit* IccPutricideGrowingOozePuddleAction::FindClosestThreateningPuddle()
if (currentDistance < safeDistance && currentDistance < closestDistance) if (currentDistance < safeDistance && currentDistance < closestDistance)
{ {
closestDistance = currentDistance; closestDistance = currentDistance;
closestSafeDistance = safeDistance;
closestPuddle = unit; closestPuddle = unit;
} }
} }
@ -3648,7 +3596,6 @@ bool IccBpcKineticBombAction::Execute(Event /*event*/)
Unit* IccBpcKineticBombAction::FindOptimalKineticBomb() Unit* IccBpcKineticBombAction::FindOptimalKineticBomb()
{ {
static constexpr float MAX_HEIGHT_DIFF = 20.0f;
static constexpr std::array<uint32_t, 4> KINETIC_BOMB_ENTRIES = {NPC_KINETIC_BOMB1, NPC_KINETIC_BOMB2, static constexpr std::array<uint32_t, 4> KINETIC_BOMB_ENTRIES = {NPC_KINETIC_BOMB1, NPC_KINETIC_BOMB2,
NPC_KINETIC_BOMB3, NPC_KINETIC_BOMB4}; NPC_KINETIC_BOMB3, NPC_KINETIC_BOMB4};
@ -4012,7 +3959,6 @@ bool IccBqlGroupPositionAction::HandleShadowsMovement()
// Find closest safe point by searching in both directions from closest point // Find closest safe point by searching in both directions from closest point
Position safeMoveTarget = closestPoint; Position safeMoveTarget = closestPoint;
float safeMoveTargetDist = FLT_MAX;
bool foundSafe = closestIsSafe; bool foundSafe = closestIsSafe;
// Only search for safe spots if the closest point isn't already safe // Only search for safe spots if the closest point isn't already safe
@ -4083,7 +4029,6 @@ bool IccBqlGroupPositionAction::HandleShadowsMovement()
if (foundSafe) if (foundSafe)
{ {
// If we found a safe point, penalize based on travel distance along the curve to reach it // If we found a safe point, penalize based on travel distance along the curve to reach it
float stepsToCurve = minDist / 2.0f; // Approximate steps to reach the curve
float safeDist = bot->GetExactDist2d(safeMoveTarget); float safeDist = bot->GetExactDist2d(safeMoveTarget);
// Add distance penalty based on how far we need to move along the curve // Add distance penalty based on how far we need to move along the curve
@ -4364,7 +4309,6 @@ bool IccBqlGroupPositionAction::HandleGroupPosition(Unit* boss, Aura* frenzyAura
rangedBots.erase(std::remove(rangedBots.begin(), rangedBots.end(), h), rangedBots.end()); rangedBots.erase(std::remove(rangedBots.begin(), rangedBots.end(), h), rangedBots.end());
// Distribute remaining ranged evenly // Distribute remaining ranged evenly
size_t totalRanged = leftSide.size() + rightSide.size() + rangedBots.size();
size_t leftCount = leftSide.size(); size_t leftCount = leftSide.size();
size_t rightCount = rightSide.size(); size_t rightCount = rightSide.size();
for (Player* p : rangedBots) for (Player* p : rangedBots)
@ -5562,7 +5506,6 @@ bool IccValithriaDreamCloudAction::Execute(Event /*event*/)
auto it = std::find(dreamBots.begin(), dreamBots.end(), bot); auto it = std::find(dreamBots.begin(), dreamBots.end(), bot);
if (it == dreamBots.end()) if (it == dreamBots.end())
return false; return false;
size_t myIndex = std::distance(dreamBots.begin(), it);
// Check if all dream bots are stacked within 3f of the current leader (lowest guid) // Check if all dream bots are stacked within 3f of the current leader (lowest guid)
constexpr float STACK_RADIUS = 2.0f; constexpr float STACK_RADIUS = 2.0f;
@ -6699,7 +6642,6 @@ bool IccSindragosaFrostBombAction::Execute(Event /*event*/)
} }
} }
Unit* losTomb = myTombs[bestIdx]; Unit* losTomb = myTombs[bestIdx];
ObjectGuid losTombGuid = myTombGuids[bestIdx];
// Calculate position for LOS (stand at least 6.5f behind the tomb from the bomb) // Calculate position for LOS (stand at least 6.5f behind the tomb from the bomb)
float angle = marker->GetAngle(losTomb); float angle = marker->GetAngle(losTomb);
@ -6782,7 +6724,6 @@ bool IccSindragosaFrostBombAction::Execute(Event /*event*/)
// Clear the marker for our group's icon // Clear the marker for our group's icon
group->SetTargetIcon(iconIndex, bot->GetGUID(), ObjectGuid::Empty); group->SetTargetIcon(iconIndex, bot->GetGUID(), ObjectGuid::Empty);
} }
Unit* boss = AI_VALUE2(Unit*, "find target", "sindragosa");
bot->AttackStop(); bot->AttackStop();
return true; return true;
} }
@ -7021,10 +6962,7 @@ bool IccLichKingWinterAction::Execute(Event /*event*/)
{ {
const ObjectGuid currentSkullTarget = group->GetTargetIcon(7); const ObjectGuid currentSkullTarget = group->GetTargetIcon(7);
if (!currentSkullTarget.IsEmpty()) if (!currentSkullTarget.IsEmpty())
{
Unit* skullTarget = ObjectAccessor::GetUnit(*bot, currentSkullTarget);
group->SetTargetIcon(7, bot->GetGUID(), ObjectGuid::Empty); group->SetTargetIcon(7, bot->GetGUID(), ObjectGuid::Empty);
}
} }
if (isVictim) if (isVictim)
@ -7802,7 +7740,6 @@ bool IccLichKingAddsAction::Execute(Event /*event*/)
//------CHEAT------- //------CHEAT-------
} }
Unit* spiritWarden = AI_VALUE2(Unit*, "find target", "spirit warden");
bool hasPlague = botAI->HasAura("Necrotic Plague", bot); bool hasPlague = botAI->HasAura("Necrotic Plague", bot);
Unit* terenasMenethilHC = bot->FindNearestCreature(NPC_TERENAS_MENETHIL_HC, 55.0f); Unit* terenasMenethilHC = bot->FindNearestCreature(NPC_TERENAS_MENETHIL_HC, 55.0f);
@ -8475,8 +8412,6 @@ bool IccLichKingAddsAction::HandleAssistTankAddManagement(Unit* boss, Difficulty
// In heroic mode, stay at melee position // In heroic mode, stay at melee position
if (diff && (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC)) if (diff && (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC))
{ {
Unit* mainTank = AI_VALUE(Unit*, "main tank");
if (bot->GetExactDist2d(ICC_LICH_KING_ASSISTHC_POSITION.GetPositionX(), if (bot->GetExactDist2d(ICC_LICH_KING_ASSISTHC_POSITION.GetPositionX(),
ICC_LICH_KING_ASSISTHC_POSITION.GetPositionY()) > 2.0f) ICC_LICH_KING_ASSISTHC_POSITION.GetPositionY()) > 2.0f)
{ {
@ -8713,7 +8648,6 @@ void IccLichKingAddsAction::HandleDefileMechanics(Unit* boss, Difficulty diff)
// Gather all defile units // Gather all defile units
std::vector<Unit*> defiles; std::vector<Unit*> defiles;
Unit* closestDefile = nullptr;
float closestDistance = std::numeric_limits<float>::max(); float closestDistance = std::numeric_limits<float>::max();
GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
@ -8725,10 +8659,7 @@ void IccLichKingAddsAction::HandleDefileMechanics(Unit* boss, Difficulty diff)
defiles.push_back(unit); defiles.push_back(unit);
float dist = bot->GetDistance(unit); float dist = bot->GetDistance(unit);
if (dist < closestDistance) if (dist < closestDistance)
{
closestDistance = dist; closestDistance = dist;
closestDefile = unit;
}
} }
} }
@ -9068,7 +8999,6 @@ void IccLichKingAddsAction::HandleValkyrMarking(const std::vector<Unit*>& grabbi
std::sort(sortedValkyrs.begin(), sortedValkyrs.end(), [](Unit* a, Unit* b) { return a->GetGUID() < b->GetGUID(); }); std::sort(sortedValkyrs.begin(), sortedValkyrs.end(), [](Unit* a, Unit* b) { return a->GetGUID() < b->GetGUID(); });
static constexpr uint8_t ICON_INDICES[] = {7, 6, 0}; // Skull, Cross, Star static constexpr uint8_t ICON_INDICES[] = {7, 6, 0}; // Skull, Cross, Star
static constexpr const char* ICON_NAMES[] = {"skull", "cross", "star"};
// In heroic mode, clean up invalid markers for all possible icons // In heroic mode, clean up invalid markers for all possible icons
if (diff && (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC)) if (diff && (diff == RAID_DIFFICULTY_10MAN_HEROIC || diff == RAID_DIFFICULTY_25MAN_HEROIC))

View File

@ -24,8 +24,6 @@
// LK global variables // LK global variables
namespace namespace
{ {
uint32 g_lastPlagueTime = 0;
bool g_plagueAllowedToCure = false;
std::map<ObjectGuid, uint32> g_plagueTimes; std::map<ObjectGuid, uint32> g_plagueTimes;
std::map<ObjectGuid, bool> g_allowCure; std::map<ObjectGuid, bool> g_allowCure;
std::mutex g_plagueMutex; // Lock before accessing shared variables std::mutex g_plagueMutex; // Lock before accessing shared variables

View File

@ -1102,7 +1102,6 @@ bool IccLichKingShadowTrapTrigger::IsActive()
// search for all nearby traps // search for all nearby traps
GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
std::vector<Unit*> nearbyTraps; std::vector<Unit*> nearbyTraps;
bool needToMove = false;
for (auto& npc : npcs) for (auto& npc : npcs)
{ {

View File

@ -870,7 +870,6 @@ bool NetherspiteAvoidBeamAndVoidZoneAction::Execute(Event /*event*/)
if (!netherspite) if (!netherspite)
return false; return false;
auto [redBlocker, greenBlocker, blueBlocker] = GetCurrentBeamBlockers(botAI, bot);
std::vector<Unit*> voidZones = GetAllVoidZones(botAI, bot); std::vector<Unit*> voidZones = GetAllVoidZones(botAI, bot);
bool nearVoidZone = !IsSafePosition(bot->GetPositionX(), bot->GetPositionY(), bool nearVoidZone = !IsSafePosition(bot->GetPositionX(), bot->GetPositionY(),

View File

@ -8,9 +8,6 @@ bool SartharionTankPositionAction::Execute(Event /*event*/)
Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion"); Unit* boss = AI_VALUE2(Unit*, "find target", "sartharion");
if (!boss) { return false; } if (!boss) { return false; }
// Unit* shadron = AI_VALUE2(Unit*, "find target", "shadron");
// Unit* tenebron = AI_VALUE2(Unit*, "find target", "tenebron");
// Unit* vesperon = AI_VALUE2(Unit*, "find target", "vesperon");
Unit* shadron = nullptr; Unit* shadron = nullptr;
Unit* tenebron = nullptr; Unit* tenebron = nullptr;
Unit* vesperon = nullptr; Unit* vesperon = nullptr;
@ -96,9 +93,7 @@ bool AvoidTwilightFissureAction::Execute(Event /*event*/)
{ {
float currentDistance = bot->GetDistance2d(unit); float currentDistance = bot->GetDistance2d(unit);
if (currentDistance < radius) if (currentDistance < radius)
{
return MoveAway(unit, radius - currentDistance); return MoveAway(unit, radius - currentDistance);
}
} }
} }
return false; return false;
@ -127,39 +122,29 @@ bool AvoidFlameTsunamiAction::Execute(Event /*event*/)
{ {
bool wavePassed = currentPos.GetPositionX() > unit->GetPositionX(); bool wavePassed = currentPos.GetPositionX() > unit->GetPositionX();
if (wavePassed) if (wavePassed)
{
return false; return false;
}
if (bot->GetExactDist2d(currentPos.GetPositionX(), TSUNAMI_RIGHT_SAFE_ALL) > looseDistance) if (bot->GetExactDist2d(currentPos.GetPositionX(), TSUNAMI_RIGHT_SAFE_ALL) > looseDistance)
{
return MoveTo(OS_MAP_ID, currentPos.GetPositionX(), TSUNAMI_RIGHT_SAFE_ALL, currentPos.GetPositionZ(), return MoveTo(OS_MAP_ID, currentPos.GetPositionX(), TSUNAMI_RIGHT_SAFE_ALL, currentPos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT); false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
} }
else // LEFT WAVE else // LEFT WAVE
{ {
bool wavePassed = currentPos.GetPositionX() < unit->GetPositionX(); bool wavePassed = currentPos.GetPositionX() < unit->GetPositionX();
if (wavePassed) if (wavePassed)
{
return false; return false;
}
if (botAI->IsMelee(bot)) if (botAI->IsMelee(bot))
{ {
if (bot->GetExactDist2d(currentPos.GetPositionX(), TSUNAMI_LEFT_SAFE_MELEE) > looseDistance) if (bot->GetExactDist2d(currentPos.GetPositionX(), TSUNAMI_LEFT_SAFE_MELEE) > looseDistance)
{
return MoveTo(OS_MAP_ID, currentPos.GetPositionX(), TSUNAMI_LEFT_SAFE_MELEE, currentPos.GetPositionZ(), return MoveTo(OS_MAP_ID, currentPos.GetPositionX(), TSUNAMI_LEFT_SAFE_MELEE, currentPos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT); false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
} }
else // Ranged/healers else // Ranged/healers
{ {
if (bot->GetExactDist2d(currentPos.GetPositionX(), TSUNAMI_LEFT_SAFE_RANGED) > looseDistance) if (bot->GetExactDist2d(currentPos.GetPositionX(), TSUNAMI_LEFT_SAFE_RANGED) > looseDistance)
{
return MoveTo(OS_MAP_ID, currentPos.GetPositionX(), TSUNAMI_LEFT_SAFE_RANGED, currentPos.GetPositionZ(), return MoveTo(OS_MAP_ID, currentPos.GetPositionX(), TSUNAMI_LEFT_SAFE_RANGED, currentPos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT); false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
} }
} }
} }
@ -178,30 +163,18 @@ bool SartharionAttackPriorityAction::Execute(Event /*event*/)
Unit* target = nullptr; Unit* target = nullptr;
if (acolyte) if (acolyte)
{
target = acolyte; target = acolyte;
}
else if (vesperon) else if (vesperon)
{
target = vesperon; target = vesperon;
}
else if (tenebron) else if (tenebron)
{
target = tenebron; target = tenebron;
}
else if (shadron) else if (shadron)
{
target = shadron; target = shadron;
}
else if (sartharion) else if (sartharion)
{
target = sartharion; target = sartharion;
}
if (target && AI_VALUE(Unit*, "current target") != target) if (target && AI_VALUE(Unit*, "current target") != target)
{
return Attack(target); return Attack(target);
}
return false; return false;
} }
@ -215,9 +188,7 @@ bool EnterTwilightPortalAction::Execute(Event /*event*/)
if (!portal) { return false; } if (!portal) { return false; }
if (!portal->IsAtInteractDistance(bot)) if (!portal->IsAtInteractDistance(bot))
{
return MoveTo(portal, fmaxf(portal->GetInteractionDistance() - 1.0f, 0.0f)); return MoveTo(portal, fmaxf(portal->GetInteractionDistance() - 1.0f, 0.0f));
}
// Go through portal // Go through portal
WorldPacket data1(CMSG_GAMEOBJ_USE); WorldPacket data1(CMSG_GAMEOBJ_USE);
@ -230,12 +201,11 @@ bool EnterTwilightPortalAction::Execute(Event /*event*/)
bool ExitTwilightPortalAction::Execute(Event /*event*/) bool ExitTwilightPortalAction::Execute(Event /*event*/)
{ {
GameObject* portal = bot->FindNearestGameObject(GO_NORMAL_PORTAL, 100.0f); GameObject* portal = bot->FindNearestGameObject(GO_NORMAL_PORTAL, 100.0f);
if (!portal) { return false; } if (!portal)
return false;
if (!portal->IsAtInteractDistance(bot)) if (!portal->IsAtInteractDistance(bot))
{
return MoveTo(portal, fmaxf(portal->GetInteractionDistance() - 1.0f, 0.0f)); return MoveTo(portal, fmaxf(portal->GetInteractionDistance() - 1.0f, 0.0f));
}
// Go through portal // Go through portal
WorldPacket data1(CMSG_GAMEOBJ_USE); WorldPacket data1(CMSG_GAMEOBJ_USE);

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

@ -72,9 +72,7 @@ bool FlameLeviathanVehicleAction::Execute(Event /*event*/)
continue; continue;
} }
if (!target || bot->GetExactDist(target) > bot->GetExactDist(unit)) if (!target || bot->GetExactDist(target) > bot->GetExactDist(unit))
{
target = unit; target = unit;
}
} }
// Flame Leviathan is chasing me // Flame Leviathan is chasing me
if (flame && flame->GetVictim() == vehicleBase_) if (flame && flame->GetVictim() == vehicleBase_)
@ -419,9 +417,7 @@ bool RazorscaleAvoidDevouringFlameAction::Execute(Event /*event*/)
RazorscaleBossHelper razorscaleHelper(botAI); RazorscaleBossHelper razorscaleHelper(botAI);
if (!razorscaleHelper.UpdateBossAI()) if (!razorscaleHelper.UpdateBossAI())
{
return false; return false;
}
bool isMainTank = botAI->IsMainTank(bot); bool isMainTank = botAI->IsMainTank(bot);
const float flameRadius = 3.5f; const float flameRadius = 3.5f;
@ -433,9 +429,7 @@ bool RazorscaleAvoidDevouringFlameAction::Execute(Event /*event*/)
// Get the boss // Get the boss
Unit* boss = AI_VALUE2(Unit*, "find target", "razorscale"); Unit* boss = AI_VALUE2(Unit*, "find target", "razorscale");
if (!boss) if (!boss)
{
return false; return false;
}
GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs"); GuidVector npcs = AI_VALUE(GuidVector, "nearest hostile npcs");
Unit* closestFlame = nullptr; Unit* closestFlame = nullptr;
@ -458,15 +452,12 @@ bool RazorscaleAvoidDevouringFlameAction::Execute(Event /*event*/)
// Off tanks are following the main tank during grounded and should prioritise stacking // Off tanks are following the main tank during grounded and should prioritise stacking
if (razorscaleHelper.IsGroundPhase() && (botAI->IsTank(bot) && !botAI->IsMainTank(bot))) if (razorscaleHelper.IsGroundPhase() && (botAI->IsTank(bot) && !botAI->IsMainTank(bot)))
{
return false; return false;
}
// Handle movement from flames // Handle movement from flames
if (closestDistance < safeDistance) if (closestDistance < safeDistance)
{
return MoveAway(closestFlame, safeDistance); return MoveAway(closestFlame, safeDistance);
}
return false; return false;
} }
@ -486,9 +477,7 @@ bool RazorscaleAvoidDevouringFlameAction::isUseful()
{ {
float distance = bot->GetDistance2d(unit); float distance = bot->GetDistance2d(unit);
if (distance < safeDistance) if (distance < safeDistance)
{
return true; // Bot is within the danger distance return true; // Bot is within the danger distance
}
} }
} }
@ -522,9 +511,7 @@ bool RazorscaleAvoidSentinelAction::Execute(Event /*event*/)
// Move away if ranged and too close // Move away if ranged and too close
if (isRanged && bot->GetDistance2d(unit) < radius) if (isRanged && bot->GetDistance2d(unit) < radius)
{
movedAway = MoveAway(unit, radius) || movedAway; movedAway = MoveAway(unit, radius) || movedAway;
}
} }
} }
@ -547,9 +534,7 @@ bool RazorscaleAvoidSentinelAction::Execute(Event /*event*/)
// If there's no skull set yet, or the skull is on a different target, set the sentinel // If there's no skull set yet, or the skull is on a different target, set the sentinel
if (!currentSkullTarget || (lowestHealthSentinel->GetGUID() != currentSkullTarget)) if (!currentSkullTarget || (lowestHealthSentinel->GetGUID() != currentSkullTarget))
{
group->SetTargetIcon(skullIndex, bot->GetGUID(), lowestHealthSentinel->GetGUID()); group->SetTargetIcon(skullIndex, bot->GetGUID(), lowestHealthSentinel->GetGUID());
}
} }
break; // Stop after finding the first valid bot tank break; // Stop after finding the first valid bot tank
} }
@ -565,9 +550,7 @@ bool RazorscaleAvoidSentinelAction::Execute(Event /*event*/)
// If there's no skull set yet, or the skull is on a different target, set the sentinel // If there's no skull set yet, or the skull is on a different target, set the sentinel
if (!currentSkullTarget || (lowestHealthSentinel->GetGUID() != currentSkullTarget)) if (!currentSkullTarget || (lowestHealthSentinel->GetGUID() != currentSkullTarget))
{
group->SetTargetIcon(skullIndex, bot->GetGUID(), lowestHealthSentinel->GetGUID()); group->SetTargetIcon(skullIndex, bot->GetGUID(), lowestHealthSentinel->GetGUID());
}
} }
} }
@ -582,9 +565,7 @@ bool RazorscaleAvoidSentinelAction::isUseful()
// If this bot is the main tank, it should always try to mark // If this bot is the main tank, it should always try to mark
if (isMainTank) if (isMainTank)
{
return true; return true;
}
// If the main tank is a human, check if this bot is one of the first three valid bot tanks // If the main tank is a human, check if this bot is one of the first three valid bot tanks
if (mainTank && !GET_PLAYERBOT_AI(mainTank)) // Main tank is a human player if (mainTank && !GET_PLAYERBOT_AI(mainTank)) // Main tank is a human player
@ -592,9 +573,7 @@ bool RazorscaleAvoidSentinelAction::isUseful()
for (int i = 0; i < 3; ++i) for (int i = 0; i < 3; ++i)
{ {
if (botAI->IsAssistTankOfIndex(bot, i) && GET_PLAYERBOT_AI(bot)) // Bot is a valid tank if (botAI->IsAssistTankOfIndex(bot, i) && GET_PLAYERBOT_AI(bot)) // Bot is a valid tank
{
return true; // This bot should assist with marking return true; // This bot should assist with marking
}
} }
} }
@ -608,9 +587,7 @@ bool RazorscaleAvoidSentinelAction::isUseful()
if (unit && unit->GetEntry() == RazorscaleBossHelper::UNIT_DARK_RUNE_SENTINEL) if (unit && unit->GetEntry() == RazorscaleBossHelper::UNIT_DARK_RUNE_SENTINEL)
{ {
if (isRanged && bot->GetDistance2d(unit) < radius) if (isRanged && bot->GetDistance2d(unit) < radius)
{
return true; return true;
}
} }
} }
@ -633,9 +610,7 @@ bool RazorscaleAvoidWhirlwindAction::Execute(Event /*event*/)
{ {
float currentDistance = bot->GetDistance2d(unit); float currentDistance = bot->GetDistance2d(unit);
if (currentDistance < radius) if (currentDistance < radius)
{
return MoveAway(unit, radius); return MoveAway(unit, radius);
}
} }
} }
return false; return false;
@ -690,23 +665,17 @@ bool RazorscaleIgnoreBossAction::isUseful()
} }
if (!botAI->IsTank(bot)) if (!botAI->IsTank(bot))
{
return false; return false;
}
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
if (!group) if (!group)
{
return false; return false;
}
// Check if the boss is already set as the moon marker // Check if the boss is already set as the moon marker
int8 moonIndex = 4; // Moon marker index int8 moonIndex = 4; // Moon marker index
ObjectGuid currentMoonTarget = group->GetTargetIcon(moonIndex); ObjectGuid currentMoonTarget = group->GetTargetIcon(moonIndex);
if (currentMoonTarget == boss->GetGUID()) if (currentMoonTarget == boss->GetGUID())
{
return false; // Moon marker is already correctly set, no further action needed return false; // Moon marker is already correctly set, no further action needed
}
// Proceed to tank-specific logic // Proceed to tank-specific logic
Unit* mainTankUnit = AI_VALUE(Unit*, "main tank"); Unit* mainTankUnit = AI_VALUE(Unit*, "main tank");
@ -714,9 +683,7 @@ bool RazorscaleIgnoreBossAction::isUseful()
// If this bot is the main tank, it needs to set the moon marker // If this bot is the main tank, it needs to set the moon marker
if (mainTankUnit == bot) if (mainTankUnit == bot)
{
return true; return true;
}
// If the main tank is a human, check if this bot is the lowest-indexed bot tank // If the main tank is a human, check if this bot is the lowest-indexed bot tank
if (mainTank && !GET_PLAYERBOT_AI(mainTank)) // Main tank is a human player if (mainTank && !GET_PLAYERBOT_AI(mainTank)) // Main tank is a human player
@ -724,9 +691,7 @@ bool RazorscaleIgnoreBossAction::isUseful()
for (int i = 0; i < 3; ++i) // Only iterate through the first 3 indexes for (int i = 0; i < 3; ++i) // Only iterate through the first 3 indexes
{ {
if (botAI->IsAssistTankOfIndex(bot, i) && GET_PLAYERBOT_AI(bot)) // Valid bot tank if (botAI->IsAssistTankOfIndex(bot, i) && GET_PLAYERBOT_AI(bot)) // Valid bot tank
{
return true; // This bot should assign the marker return true; // This bot should assign the marker
}
} }
} }
} }
@ -1156,9 +1121,7 @@ bool IronAssemblyLightningTendrilsAction::Execute(Event /*event*/)
float currentDistance = bot->GetDistance2d(boss); float currentDistance = bot->GetDistance2d(boss);
if (currentDistance < radius) if (currentDistance < radius)
{
return MoveAway(boss, radius - currentDistance); return MoveAway(boss, radius - currentDistance);
}
return false; return false;
} }
@ -1180,9 +1143,7 @@ bool IronAssemblyOverloadAction::Execute(Event /*event*/)
float currentDistance = bot->GetDistance2d(boss); float currentDistance = bot->GetDistance2d(boss);
if (currentDistance < radius) if (currentDistance < radius)
{
return MoveAway(boss, radius - currentDistance); return MoveAway(boss, radius - currentDistance);
}
return false; return false;
} }
@ -1230,7 +1191,6 @@ bool KologarnMarkDpsTargetAction::Execute(Event /*event*/)
if (!target) if (!target)
continue; continue;
uint32 creatureId = target->GetEntry();
if (target->GetEntry() == NPC_RUBBLE && target->IsAlive()) if (target->GetEntry() == NPC_RUBBLE && target->IsAlive())
{ {
targetToMark = target; targetToMark = target;
@ -1252,21 +1212,15 @@ bool KologarnMarkDpsTargetAction::Execute(Event /*event*/)
{ {
Unit* boss = AI_VALUE2(Unit*, "find target", "kologarn"); Unit* boss = AI_VALUE2(Unit*, "find target", "kologarn");
if (boss && boss->IsAlive()) if (boss && boss->IsAlive())
{
targetToMark = boss; targetToMark = boss;
}
} }
if (!targetToMark) if (!targetToMark)
{
return false; // No target to mark return false; // No target to mark
}
Unit* leftArm = AI_VALUE2(Unit*, "find target", "left arm"); Unit* leftArm = AI_VALUE2(Unit*, "find target", "left arm");
if (leftArm && leftArm->IsAlive()) if (leftArm && leftArm->IsAlive())
{
targetToCcMark = leftArm; targetToCcMark = leftArm;
}
bool isMainTank = botAI->IsMainTank(bot); bool isMainTank = botAI->IsMainTank(bot);
Unit* mainTankUnit = AI_VALUE(Unit*, "main tank"); Unit* mainTankUnit = AI_VALUE(Unit*, "main tank");
@ -1284,13 +1238,10 @@ bool KologarnMarkDpsTargetAction::Execute(Event /*event*/)
{ {
group->SetTargetIcon(skullIndex, bot->GetGUID(), targetToMark->GetGUID()); group->SetTargetIcon(skullIndex, bot->GetGUID(), targetToMark->GetGUID());
if (targetToCcMark) if (targetToCcMark)
{
group->SetTargetIcon(moonIndex, bot->GetGUID(), targetToCcMark->GetGUID()); group->SetTargetIcon(moonIndex, bot->GetGUID(), targetToCcMark->GetGUID());
}
if (additionalTargetToMark) if (additionalTargetToMark)
{
group->SetTargetIcon(crossIndex, bot->GetGUID(), additionalTargetToMark->GetGUID()); group->SetTargetIcon(crossIndex, bot->GetGUID(), additionalTargetToMark->GetGUID());
}
return true; return true;
} }
@ -1305,13 +1256,11 @@ bool KologarnMarkDpsTargetAction::Execute(Event /*event*/)
{ {
group->SetTargetIcon(skullIndex, bot->GetGUID(), targetToMark->GetGUID()); group->SetTargetIcon(skullIndex, bot->GetGUID(), targetToMark->GetGUID());
if (targetToCcMark) if (targetToCcMark)
{
group->SetTargetIcon(moonIndex, bot->GetGUID(), targetToCcMark->GetGUID()); group->SetTargetIcon(moonIndex, bot->GetGUID(), targetToCcMark->GetGUID());
}
if (additionalTargetToMark) if (additionalTargetToMark)
{
group->SetTargetIcon(crossIndex, bot->GetGUID(), additionalTargetToMark->GetGUID()); group->SetTargetIcon(crossIndex, bot->GetGUID(), additionalTargetToMark->GetGUID());
}
return true; return true;
} }
} }
@ -1326,13 +1275,11 @@ bool KologarnMarkDpsTargetAction::Execute(Event /*event*/)
{ {
group->SetTargetIcon(skullIndex, bot->GetGUID(), targetToMark->GetGUID()); group->SetTargetIcon(skullIndex, bot->GetGUID(), targetToMark->GetGUID());
if (targetToCcMark) if (targetToCcMark)
{
group->SetTargetIcon(moonIndex, bot->GetGUID(), targetToCcMark->GetGUID()); group->SetTargetIcon(moonIndex, bot->GetGUID(), targetToCcMark->GetGUID());
}
if (additionalTargetToMark) if (additionalTargetToMark)
{
group->SetTargetIcon(crossIndex, bot->GetGUID(), additionalTargetToMark->GetGUID()); group->SetTargetIcon(crossIndex, bot->GetGUID(), additionalTargetToMark->GetGUID());
}
return true; return true;
} }
break; // Stop after finding the first valid bot tank break; // Stop after finding the first valid bot tank
@ -1379,17 +1326,11 @@ bool KologarnEyebeamAction::Execute(Event /*event*/)
bool runToLeftSide; bool runToLeftSide;
if (!distanceToLeftPoint) if (!distanceToLeftPoint)
{
runToLeftSide = true; runToLeftSide = true;
}
else if (!distanceToRightPoint) else if (!distanceToRightPoint)
{
runToLeftSide = false; runToLeftSide = false;
}
else else
{
runToLeftSide = distanceToRightPoint > distanceToLeftPoint; runToLeftSide = distanceToRightPoint > distanceToLeftPoint;
}
bool teleportedToPoint; bool teleportedToPoint;
KologarnEyebeamTrigger kologarnEyebeamTrigger(botAI); KologarnEyebeamTrigger kologarnEyebeamTrigger(botAI);
@ -1418,9 +1359,7 @@ bool KologarnEyebeamAction::isUseful()
{ {
KologarnEyebeamTrigger kologarnEyebeamTrigger(botAI); KologarnEyebeamTrigger kologarnEyebeamTrigger(botAI);
if (!kologarnEyebeamTrigger.IsActive()) if (!kologarnEyebeamTrigger.IsActive())
{
return false; return false;
}
return botAI->HasCheat(BotCheatMask::raid); return botAI->HasCheat(BotCheatMask::raid);
} }
@ -1447,9 +1386,7 @@ bool KologarnCrunchArmorAction::isUseful()
{ {
KologarnCrunchArmorTrigger kologarnCrunchArmorTrigger(botAI); KologarnCrunchArmorTrigger kologarnCrunchArmorTrigger(botAI);
if (!kologarnCrunchArmorTrigger.IsActive()) if (!kologarnCrunchArmorTrigger.IsActive())
{
return false; return false;
}
return botAI->HasCheat(BotCheatMask::raid); return botAI->HasCheat(BotCheatMask::raid);
} }
@ -1482,15 +1419,11 @@ bool HodirMoveSnowpackedIcicleAction::isUseful()
// Check boss and it is alive // Check boss and it is alive
Unit* boss = AI_VALUE2(Unit*, "find target", "hodir"); Unit* boss = AI_VALUE2(Unit*, "find target", "hodir");
if (!boss || !boss->IsAlive()) if (!boss || !boss->IsAlive())
{
return false; return false;
}
// Check if boss is casting Flash Freeze // Check if boss is casting Flash Freeze
if (!boss->HasUnitState(UNIT_STATE_CASTING) || !boss->FindCurrentSpellBySpellId(SPELL_FLASH_FREEZE)) if (!boss->HasUnitState(UNIT_STATE_CASTING) || !boss->FindCurrentSpellBySpellId(SPELL_FLASH_FREEZE))
{
return false; return false;
}
// Find the nearest Snowpacked Icicle Target // Find the nearest Snowpacked Icicle Target
Creature* target = bot->FindNearestCreature(NPC_SNOWPACKED_ICICLE, 100.0f); Creature* target = bot->FindNearestCreature(NPC_SNOWPACKED_ICICLE, 100.0f);
@ -1499,9 +1432,7 @@ bool HodirMoveSnowpackedIcicleAction::isUseful()
// Check that bot is stacked on Snowpacked Icicle // Check that bot is stacked on Snowpacked Icicle
if (bot->GetDistance2d(target->GetPositionX(), target->GetPositionY()) <= 5.0f) if (bot->GetDistance2d(target->GetPositionX(), target->GetPositionY()) <= 5.0f)
{
return false; return false;
}
return true; return true;
} }
@ -1612,42 +1543,26 @@ bool FreyaMarkDpsTargetAction::Execute(Event /*event*/)
continue; continue;
if (target->GetEntry() == NPC_EONARS_GIFT) if (target->GetEntry() == NPC_EONARS_GIFT)
{
eonarsGift = target; eonarsGift = target;
}
else if (target->GetEntry() == NPC_ANCIENT_CONSERVATOR) else if (target->GetEntry() == NPC_ANCIENT_CONSERVATOR)
{
ancientConservator = target; ancientConservator = target;
}
else if (target->GetEntry() == NPC_SNAPLASHER) else if (target->GetEntry() == NPC_SNAPLASHER)
{
snaplasher = target; snaplasher = target;
}
else if (target->GetEntry() == NPC_ANCIENT_WATER_SPIRIT) else if (target->GetEntry() == NPC_ANCIENT_WATER_SPIRIT)
{
ancientWaterSpirit = target; ancientWaterSpirit = target;
}
else if (target->GetEntry() == NPC_STORM_LASHER) else if (target->GetEntry() == NPC_STORM_LASHER)
{
stormLasher = target; stormLasher = target;
}
else if (target->GetEntry() == NPC_DETONATING_LASHER && !firstDetonatingLasher) else if (target->GetEntry() == NPC_DETONATING_LASHER && !firstDetonatingLasher)
{
firstDetonatingLasher = target; firstDetonatingLasher = target;
}
} }
// Check that eonars gift is need to be mark // Check that eonars gift is need to be mark
if (eonarsGift) if (eonarsGift)
{
targetToMark = eonarsGift; targetToMark = eonarsGift;
}
// Check that ancient conservator is need to be mark // Check that ancient conservator is need to be mark
if (ancientConservator && !targetToMark) if (ancientConservator && !targetToMark)
{
targetToMark = ancientConservator; targetToMark = ancientConservator;
}
// Check that trio of adds is need to be mark // Check that trio of adds is need to be mark
if ((snaplasher || ancientWaterSpirit || stormLasher) && !targetToMark) if ((snaplasher || ancientWaterSpirit || stormLasher) && !targetToMark)
@ -1679,14 +1594,10 @@ bool FreyaMarkDpsTargetAction::Execute(Event /*event*/)
// Check that detonating lasher is need to be mark // Check that detonating lasher is need to be mark
if (firstDetonatingLasher && !targetToMark) if (firstDetonatingLasher && !targetToMark)
{
targetToMark = firstDetonatingLasher; targetToMark = firstDetonatingLasher;
}
if (!targetToMark) if (!targetToMark)
{
return false; // No target to mark return false; // No target to mark
}
bool isMainTank = botAI->IsMainTank(bot); bool isMainTank = botAI->IsMainTank(bot);
Unit* mainTankUnit = AI_VALUE(Unit*, "main tank"); Unit* mainTankUnit = AI_VALUE(Unit*, "main tank");
@ -1778,9 +1689,7 @@ bool ThorimUnbalancingStrikeAction::isUseful()
{ {
ThorimUnbalancingStrikeTrigger thorimUnbalancingStrikeTrigger(botAI); ThorimUnbalancingStrikeTrigger thorimUnbalancingStrikeTrigger(botAI);
if (!thorimUnbalancingStrikeTrigger.IsActive()) if (!thorimUnbalancingStrikeTrigger.IsActive())
{
return false; return false;
}
return botAI->HasCheat(BotCheatMask::raid); return botAI->HasCheat(BotCheatMask::raid);
} }
@ -2188,23 +2097,15 @@ bool MimironShockBlastAction::Execute(Event /*event*/)
continue; continue;
if (target->GetEntry() == NPC_LEVIATHAN_MKII) if (target->GetEntry() == NPC_LEVIATHAN_MKII)
{
leviathanMkII = target; leviathanMkII = target;
}
else if (target->GetEntry() == NPC_VX001) else if (target->GetEntry() == NPC_VX001)
{
vx001 = target; vx001 = target;
}
else if (target->GetEntry() == NPC_AERIAL_COMMAND_UNIT) else if (target->GetEntry() == NPC_AERIAL_COMMAND_UNIT)
{
aerialCommandUnit = target; aerialCommandUnit = target;
}
} }
if (!leviathanMkII) if (!leviathanMkII)
{
return false; return false;
}
if (!vx001 && !aerialCommandUnit) if (!vx001 && !aerialCommandUnit)
{ {
@ -2213,9 +2114,8 @@ bool MimironShockBlastAction::Execute(Event /*event*/)
MoveAway(leviathanMkII, radius - currentDistance); MoveAway(leviathanMkII, radius - currentDistance);
if (botAI->IsMelee(bot)) if (botAI->IsMelee(bot))
{
botAI->SetNextCheckDelay(100); botAI->SetNextCheckDelay(100);
}
return true; return true;
} }
else else
@ -2261,15 +2161,11 @@ bool MimironP3Wx2LaserBarrageAction::Execute(Event /*event*/)
{ {
auto master = botAI->GetMaster(); auto master = botAI->GetMaster();
if (!master || !master->IsAlive()) if (!master || !master->IsAlive())
{
return false; return false;
}
if (bot->GetDistance2d(master) > 15.0f) if (bot->GetDistance2d(master) > 15.0f)
{
return bot->TeleportTo(master->GetMapId(), master->GetPositionX(), master->GetPositionY(), return bot->TeleportTo(master->GetMapId(), master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), master->GetOrientation()); master->GetPositionZ(), master->GetOrientation());
}
return MoveTo(master->GetMapId(), master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), false, return MoveTo(master->GetMapId(), master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), false,
false, false, true, MovementPriority::MOVEMENT_COMBAT, true); false, false, true, MovementPriority::MOVEMENT_COMBAT, true);
@ -2294,18 +2190,14 @@ bool MimironRapidBurstAction::Execute(Event /*event*/)
continue; continue;
if (target->GetEntry() == NPC_LEVIATHAN_MKII) if (target->GetEntry() == NPC_LEVIATHAN_MKII)
{
leviathanMkII = target; leviathanMkII = target;
}
} }
Position targetPosition; Position targetPosition;
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
if (!group) if (!group)
{
return false; return false;
}
uint32 memberSpotNumber = 0; uint32 memberSpotNumber = 0;
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
@ -2359,28 +2251,21 @@ bool MimironRapidBurstAction::Execute(Event /*event*/)
memberSpotNumber++; memberSpotNumber++;
if (memberSpotNumber == 3) if (memberSpotNumber == 3)
{
memberSpotNumber = 0; memberSpotNumber = 0;
}
} }
MoveTo(bot->GetMapId(), targetPosition.GetPositionX(), targetPosition.GetPositionY(), targetPosition.GetPositionZ(), MoveTo(bot->GetMapId(), targetPosition.GetPositionX(), targetPosition.GetPositionY(), targetPosition.GetPositionZ(),
false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false); false, false, false, true, MovementPriority::MOVEMENT_FORCED, true, false);
if (AI_VALUE(float, "disperse distance") != 0.0f) if (AI_VALUE(float, "disperse distance") != 0.0f)
{
SET_AI_VALUE(float, "disperse distance", 0.0f); SET_AI_VALUE(float, "disperse distance", 0.0f);
}
TankFaceStrategy tankFaceStrategy(botAI); TankFaceStrategy tankFaceStrategy(botAI);
if (botAI->HasStrategy(tankFaceStrategy.getName(), BotState::BOT_STATE_COMBAT)) if (botAI->HasStrategy(tankFaceStrategy.getName(), BotState::BOT_STATE_COMBAT))
{
botAI->ChangeStrategy(REMOVE_STRATEGY_CHAR + tankFaceStrategy.getName(), BotState::BOT_STATE_COMBAT); botAI->ChangeStrategy(REMOVE_STRATEGY_CHAR + tankFaceStrategy.getName(), BotState::BOT_STATE_COMBAT);
}
if (botAI->HasStrategy(tankFaceStrategy.getName(), BotState::BOT_STATE_NON_COMBAT)) if (botAI->HasStrategy(tankFaceStrategy.getName(), BotState::BOT_STATE_NON_COMBAT))
{
botAI->ChangeStrategy(REMOVE_STRATEGY_CHAR + tankFaceStrategy.getName(), BotState::BOT_STATE_NON_COMBAT); botAI->ChangeStrategy(REMOVE_STRATEGY_CHAR + tankFaceStrategy.getName(), BotState::BOT_STATE_NON_COMBAT);
}
if (bot->GetDistance(targetPosition) > 1.0f) if (bot->GetDistance(targetPosition) > 1.0f)
return false; return false;
@ -2403,53 +2288,38 @@ bool MimironAerialCommandUnitAction::Execute(Event /*event*/)
continue; continue;
if (target->GetEntry() == NPC_AERIAL_COMMAND_UNIT) if (target->GetEntry() == NPC_AERIAL_COMMAND_UNIT)
{
boss = target; boss = target;
}
else if (target->GetEntry() == NPC_BOMB_BOT) else if (target->GetEntry() == NPC_BOMB_BOT)
{
bombBot = target; bombBot = target;
}
else if (target->GetEntry() == NPC_ASSAULT_BOT) else if (target->GetEntry() == NPC_ASSAULT_BOT)
{
assaultBot = target; assaultBot = target;
}
} }
if (botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0)) if (botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0))
{ {
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
if (!group) if (!group)
{
return false; return false;
}
if (bombBot) if (bombBot)
{
group->SetTargetIcon(RtiTargetValue::crossIndex, bot->GetGUID(), bombBot->GetGUID()); group->SetTargetIcon(RtiTargetValue::crossIndex, bot->GetGUID(), bombBot->GetGUID());
}
else if (boss) else if (boss)
{
group->SetTargetIcon(RtiTargetValue::crossIndex, bot->GetGUID(), boss->GetGUID()); group->SetTargetIcon(RtiTargetValue::crossIndex, bot->GetGUID(), boss->GetGUID());
}
if (assaultBot) if (assaultBot)
{ {
ObjectGuid skullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex); ObjectGuid skullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex);
Unit* skullUnit = botAI->GetUnit(skullTarget); Unit* skullUnit = botAI->GetUnit(skullTarget);
if (!skullTarget || !skullUnit || !skullUnit->IsAlive()) if (!skullTarget || !skullUnit || !skullUnit->IsAlive())
{
group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), assaultBot->GetGUID()); group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), assaultBot->GetGUID());
}
} }
return true; return true;
} }
if (AI_VALUE(float, "disperse distance") != 5.0f) if (AI_VALUE(float, "disperse distance") != 5.0f)
{
SET_AI_VALUE(float, "disperse distance", 5.0f); SET_AI_VALUE(float, "disperse distance", 5.0f);
}
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set("cross"); botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set("cross");
return true; return true;
} }
@ -2476,10 +2346,8 @@ bool MimironRocketStrikeAction::Execute(Event /*event*/)
if (target->GetEntry() == NPC_LEVIATHAN_MKII) if (target->GetEntry() == NPC_LEVIATHAN_MKII)
leviathanMkII = target; leviathanMkII = target;
else if (target->GetEntry() == NPC_VX001) else if (target->GetEntry() == NPC_VX001)
vx001 = target; vx001 = target;
else if (target->GetEntry() == NPC_AERIAL_COMMAND_UNIT) else if (target->GetEntry() == NPC_AERIAL_COMMAND_UNIT)
aerialCommandUnit = target; aerialCommandUnit = target;
} }
@ -2536,23 +2404,15 @@ bool MimironPhase4MarkDpsAction::Execute(Event /*event*/)
continue; continue;
if (target->GetEntry() == NPC_LEVIATHAN_MKII) if (target->GetEntry() == NPC_LEVIATHAN_MKII)
{
leviathanMkII = target; leviathanMkII = target;
}
else if (target->GetEntry() == NPC_VX001) else if (target->GetEntry() == NPC_VX001)
{
vx001 = target; vx001 = target;
}
else if (target->GetEntry() == NPC_AERIAL_COMMAND_UNIT) else if (target->GetEntry() == NPC_AERIAL_COMMAND_UNIT)
{
aerialCommandUnit = target; aerialCommandUnit = target;
}
} }
if (!leviathanMkII || !vx001 || !aerialCommandUnit) if (!leviathanMkII || !vx001 || !aerialCommandUnit)
{
return false; return false;
}
if (botAI->IsMainTank(bot)) if (botAI->IsMainTank(bot))
{ {
@ -2563,31 +2423,27 @@ bool MimironPhase4MarkDpsAction::Execute(Event /*event*/)
highestHealth = leviathanMkII->GetHealth(); highestHealth = leviathanMkII->GetHealth();
highestHealthUnit = leviathanMkII; highestHealthUnit = leviathanMkII;
} }
if (vx001 && vx001->GetHealth() > highestHealth) if (vx001 && vx001->GetHealth() > highestHealth)
{ {
highestHealth = vx001->GetHealth(); highestHealth = vx001->GetHealth();
highestHealthUnit = vx001; highestHealthUnit = vx001;
} }
if (aerialCommandUnit && aerialCommandUnit->GetHealth() > highestHealth) if (aerialCommandUnit && aerialCommandUnit->GetHealth() > highestHealth)
{
highestHealthUnit = aerialCommandUnit; highestHealthUnit = aerialCommandUnit;
}
group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), highestHealthUnit->GetGUID()); group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), highestHealthUnit->GetGUID());
if (highestHealthUnit == leviathanMkII) if (highestHealthUnit == leviathanMkII)
{ {
if (AI_VALUE(std::string, "rti") == "skull") if (AI_VALUE(std::string, "rti") == "skull")
{
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set("skull"); botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set("skull");
}
} }
else else
{ {
group->SetTargetIcon(RtiTargetValue::crossIndex, bot->GetGUID(), leviathanMkII->GetGUID()); group->SetTargetIcon(RtiTargetValue::crossIndex, bot->GetGUID(), leviathanMkII->GetGUID());
if (AI_VALUE(std::string, "rti") != "cross") if (AI_VALUE(std::string, "rti") != "cross")
{
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set("cross"); botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set("cross");
}
} }
botAI->DoSpecificAction("attack rti target"); botAI->DoSpecificAction("attack rti target");
@ -2614,13 +2470,9 @@ bool MimironCheatAction::Execute(Event /*event*/)
continue; continue;
if (unit->GetEntry() == NPC_PROXIMITY_MINE) if (unit->GetEntry() == NPC_PROXIMITY_MINE)
{
unit->Kill(bot, unit); unit->Kill(bot, unit);
}
else if (unit->GetEntry() == NPC_BOMB_BOT) else if (unit->GetEntry() == NPC_BOMB_BOT)
{
unit->Kill(bot, unit); unit->Kill(bot, unit);
}
} }
return true; return true;
@ -2631,9 +2483,7 @@ bool VezaxCheatAction::Execute(Event /*event*/)
// Restore bot's mana to full // Restore bot's mana to full
uint32 maxMana = bot->GetMaxPower(POWER_MANA); uint32 maxMana = bot->GetMaxPower(POWER_MANA);
if (maxMana > 0) if (maxMana > 0)
{
bot->SetPower(POWER_MANA, maxMana); bot->SetPower(POWER_MANA, maxMana);
}
return true; return true;
} }
@ -2643,9 +2493,7 @@ bool VezaxShadowCrashAction::Execute(Event /*event*/)
// Find General Vezax boss // Find General Vezax boss
Unit* boss = AI_VALUE2(Unit*, "find target", "general vezax"); Unit* boss = AI_VALUE2(Unit*, "find target", "general vezax");
if (!boss || !boss->IsAlive()) if (!boss || !boss->IsAlive())
{
return false; return false;
}
// Get bot's current position relative to boss // Get bot's current position relative to boss
float bossX = boss->GetPositionX(); float bossX = boss->GetPositionX();
@ -2664,9 +2512,7 @@ bool VezaxShadowCrashAction::Execute(Event /*event*/)
// If too close or too far, adjust distance first // If too close or too far, adjust distance first
if (currentDistance < desiredDistance - 2.0f || currentDistance > desiredDistance + 2.0f) if (currentDistance < desiredDistance - 2.0f || currentDistance > desiredDistance + 2.0f)
{
currentDistance = desiredDistance; currentDistance = desiredDistance;
}
// Calculate movement increment - move in increments around the boss // Calculate movement increment - move in increments around the boss
float angleIncrement = M_PI / 10; float angleIncrement = M_PI / 10;
@ -2696,15 +2542,11 @@ bool YoggSaronOminousCloudCheatAction::Execute(Event /*event*/)
Unit* boss = yoggSaronTrigger.GetSaraIfAlive(); Unit* boss = yoggSaronTrigger.GetSaraIfAlive();
if (!boss) if (!boss)
{
return false; return false;
}
Creature* target = boss->FindNearestCreature(NPC_OMINOUS_CLOUD, 25.0f); Creature* target = boss->FindNearestCreature(NPC_OMINOUS_CLOUD, 25.0f);
if (!target || !target->IsAlive()) if (!target || !target->IsAlive())
{
return false; return false;
}
target->Kill(bot, target); target->Kill(bot, target);
return true; return true;
@ -2730,9 +2572,7 @@ bool YoggSaronMarkTargetAction::Execute(Event /*event*/)
{ {
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
if (!group) if (!group)
{
return false; return false;
}
YoggSaronTrigger yoggSaronTrigger(botAI); YoggSaronTrigger yoggSaronTrigger(botAI);
if (yoggSaronTrigger.IsPhase2()) if (yoggSaronTrigger.IsPhase2())
@ -2741,9 +2581,7 @@ bool YoggSaronMarkTargetAction::Execute(Event /*event*/)
{ {
Unit* crusherTentacle = bot->FindNearestCreature(NPC_CRUSHER_TENTACLE, 200.0f, true); Unit* crusherTentacle = bot->FindNearestCreature(NPC_CRUSHER_TENTACLE, 200.0f, true);
if (crusherTentacle) if (crusherTentacle)
{
crusherTentacle->Kill(bot, crusherTentacle); crusherTentacle->Kill(bot, crusherTentacle);
}
} }
ObjectGuid currentMoonTarget = group->GetTargetIcon(RtiTargetValue::moonIndex); ObjectGuid currentMoonTarget = group->GetTargetIcon(RtiTargetValue::moonIndex);
@ -2761,9 +2599,7 @@ bool YoggSaronMarkTargetAction::Execute(Event /*event*/)
{ {
nextPossibleTarget = bot->FindNearestCreature(NPC_CORRUPTOR_TENTACLE, 200.0f, true); nextPossibleTarget = bot->FindNearestCreature(NPC_CORRUPTOR_TENTACLE, 200.0f, true);
if (!nextPossibleTarget) if (!nextPossibleTarget)
{
return false; return false;
}
} }
if (currentSkullTarget) if (currentSkullTarget)
@ -2772,9 +2608,7 @@ bool YoggSaronMarkTargetAction::Execute(Event /*event*/)
if (currentSkullUnit && currentSkullUnit->IsAlive() && if (currentSkullUnit && currentSkullUnit->IsAlive() &&
currentSkullUnit->GetGUID() == nextPossibleTarget->GetGUID()) currentSkullUnit->GetGUID() == nextPossibleTarget->GetGUID())
{
return false; return false;
}
} }
group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), nextPossibleTarget->GetGUID()); group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), nextPossibleTarget->GetGUID());
@ -2783,15 +2617,11 @@ bool YoggSaronMarkTargetAction::Execute(Event /*event*/)
{ {
TankFaceStrategy tankFaceStrategy(botAI); TankFaceStrategy tankFaceStrategy(botAI);
if (botAI->HasStrategy(tankFaceStrategy.getName(), BotState::BOT_STATE_COMBAT)) if (botAI->HasStrategy(tankFaceStrategy.getName(), BotState::BOT_STATE_COMBAT))
{
botAI->ChangeStrategy(REMOVE_STRATEGY_CHAR + tankFaceStrategy.getName(), BotState::BOT_STATE_COMBAT); botAI->ChangeStrategy(REMOVE_STRATEGY_CHAR + tankFaceStrategy.getName(), BotState::BOT_STATE_COMBAT);
}
TankAssistStrategy tankAssistStrategy(botAI); TankAssistStrategy tankAssistStrategy(botAI);
if (!botAI->HasStrategy(tankAssistStrategy.getName(), BotState::BOT_STATE_COMBAT)) if (!botAI->HasStrategy(tankAssistStrategy.getName(), BotState::BOT_STATE_COMBAT))
{
botAI->ChangeStrategy(ADD_STRATEGY_CHAR + tankAssistStrategy.getName(), BotState::BOT_STATE_COMBAT); botAI->ChangeStrategy(ADD_STRATEGY_CHAR + tankAssistStrategy.getName(), BotState::BOT_STATE_COMBAT);
}
GuidVector targets = AI_VALUE(GuidVector, "nearest npcs"); GuidVector targets = AI_VALUE(GuidVector, "nearest npcs");
@ -2801,9 +2631,7 @@ bool YoggSaronMarkTargetAction::Execute(Event /*event*/)
{ {
Unit* unit = botAI->GetUnit(guid); Unit* unit = botAI->GetUnit(guid);
if (!unit || !unit->IsAlive()) if (!unit || !unit->IsAlive())
{
continue; continue;
}
if ((unit->GetEntry() == NPC_IMMORTAL_GUARDIAN || unit->GetEntry() == NPC_MARKED_IMMORTAL_GUARDIAN) && if ((unit->GetEntry() == NPC_IMMORTAL_GUARDIAN || unit->GetEntry() == NPC_MARKED_IMMORTAL_GUARDIAN) &&
unit->GetHealthPct() > 10) unit->GetHealthPct() > 10)
@ -2821,13 +2649,9 @@ bool YoggSaronMarkTargetAction::Execute(Event /*event*/)
// Added because lunatic gaze freeze all bots and they can't attack // Added because lunatic gaze freeze all bots and they can't attack
// If someone fix it then this cheat can be removed // If someone fix it then this cheat can be removed
if (botAI->HasCheat(BotCheatMask::raid)) if (botAI->HasCheat(BotCheatMask::raid))
{
lowestHealthUnit->Kill(bot, lowestHealthUnit); lowestHealthUnit->Kill(bot, lowestHealthUnit);
}
else else
{
group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), lowestHealthUnit->GetGUID()); group->SetTargetIcon(RtiTargetValue::skullIndex, bot->GetGUID(), lowestHealthUnit->GetGUID());
}
return true; return true;
} }
@ -2835,9 +2659,7 @@ bool YoggSaronMarkTargetAction::Execute(Event /*event*/)
ObjectGuid currentSkullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex); ObjectGuid currentSkullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex);
Unit* currentSkullUnit = nullptr; Unit* currentSkullUnit = nullptr;
if (currentSkullTarget) if (currentSkullTarget)
{
currentSkullUnit = botAI->GetUnit(currentSkullTarget); currentSkullUnit = botAI->GetUnit(currentSkullTarget);
}
if (!currentSkullUnit || currentSkullUnit->GetEntry() != NPC_YOGG_SARON) if (!currentSkullUnit || currentSkullUnit->GetEntry() != NPC_YOGG_SARON)
{ {
@ -2859,17 +2681,13 @@ bool YoggSaronBrainLinkAction::Execute(Event /*event*/)
{ {
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
if (!group) if (!group)
{
return false; return false;
}
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{ {
Player* player = gref->GetSource(); Player* player = gref->GetSource();
if (player && player->IsAlive() && player->HasAura(SPELL_BRAIN_LINK) && player->GetGUID() != bot->GetGUID()) if (player && player->IsAlive() && player->HasAura(SPELL_BRAIN_LINK) && player->GetGUID() != bot->GetGUID())
{
return MoveNear(player, 10.0f, MovementPriority::MOVEMENT_FORCED); return MoveNear(player, 10.0f, MovementPriority::MOVEMENT_FORCED);
}
} }
return false; return false;
@ -2879,17 +2697,13 @@ bool YoggSaronMoveToEnterPortalAction::Execute(Event /*event*/)
{ {
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
if (!group) if (!group)
{
return false; return false;
}
bool isInBrainRoomTeam = false; bool isInBrainRoomTeam = false;
int portalNumber = 0; int portalNumber = 0;
int brainRoomTeamCount = 10; int brainRoomTeamCount = 10;
if (bot->GetRaidDifficulty() == Difficulty::RAID_DIFFICULTY_10MAN_NORMAL) if (bot->GetRaidDifficulty() == Difficulty::RAID_DIFFICULTY_10MAN_NORMAL)
{
brainRoomTeamCount = 4; brainRoomTeamCount = 4;
}
Player* master = botAI->GetMaster(); Player* master = botAI->GetMaster();
if (master && !botAI->IsTank(master)) if (master && !botAI->IsTank(master))
@ -2902,9 +2716,7 @@ bool YoggSaronMoveToEnterPortalAction::Execute(Event /*event*/)
{ {
Player* member = gref->GetSource(); Player* member = gref->GetSource();
if (!member || !member->IsAlive() || botAI->IsTank(member) || botAI->GetMaster()->GetGUID() == member->GetGUID()) if (!member || !member->IsAlive() || botAI->IsTank(member) || botAI->GetMaster()->GetGUID() == member->GetGUID())
{
continue; continue;
}
portalNumber++; portalNumber++;
if (member->GetGUID() == bot->GetGUID()) if (member->GetGUID() == bot->GetGUID())
@ -2915,15 +2727,11 @@ bool YoggSaronMoveToEnterPortalAction::Execute(Event /*event*/)
brainRoomTeamCount--; brainRoomTeamCount--;
if (brainRoomTeamCount == 0) if (brainRoomTeamCount == 0)
{
break; break;
}
} }
if (!isInBrainRoomTeam) if (!isInBrainRoomTeam)
{
return false; return false;
}
Position assignedPortalPosition = yoggPortalLoc[portalNumber - 1]; Position assignedPortalPosition = yoggPortalLoc[portalNumber - 1];
@ -2980,33 +2788,24 @@ bool YoggSaronBossRoomMovementCheatAction::Execute(Event /*event*/)
{ {
FollowMasterStrategy followMasterStrategy(botAI); FollowMasterStrategy followMasterStrategy(botAI);
if (botAI->HasStrategy(followMasterStrategy.getName(), BotState::BOT_STATE_NON_COMBAT)) if (botAI->HasStrategy(followMasterStrategy.getName(), BotState::BOT_STATE_NON_COMBAT))
{
botAI->ChangeStrategy(REMOVE_STRATEGY_CHAR + followMasterStrategy.getName(), BotState::BOT_STATE_NON_COMBAT); botAI->ChangeStrategy(REMOVE_STRATEGY_CHAR + followMasterStrategy.getName(), BotState::BOT_STATE_NON_COMBAT);
}
if (!botAI->HasCheat(BotCheatMask::raid)) if (!botAI->HasCheat(BotCheatMask::raid))
{
return false; return false;
}
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
if (!group) if (!group)
{
return false; return false;
}
ObjectGuid currentSkullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex); ObjectGuid currentSkullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex);
if (!currentSkullTarget) if (!currentSkullTarget)
{
return false; return false;
}
Unit* currentSkullUnit = botAI->GetUnit(currentSkullTarget); Unit* currentSkullUnit = botAI->GetUnit(currentSkullTarget);
if (!currentSkullUnit || !currentSkullUnit->IsAlive()) if (!currentSkullUnit || !currentSkullUnit->IsAlive())
{
return false; return false;
}
return bot->TeleportTo(bot->GetMapId(), currentSkullUnit->GetPositionX(), currentSkullUnit->GetPositionY(), return bot->TeleportTo(bot->GetMapId(), currentSkullUnit->GetPositionX(), currentSkullUnit->GetPositionY(),
currentSkullUnit->GetPositionZ(), bot->GetOrientation()); currentSkullUnit->GetPositionZ(), bot->GetOrientation());
@ -3014,19 +2813,15 @@ bool YoggSaronBossRoomMovementCheatAction::Execute(Event /*event*/)
bool YoggSaronUsePortalAction::Execute(Event /*event*/) bool YoggSaronUsePortalAction::Execute(Event /*event*/)
{ {
Creature* assignedPortal = bot->FindNearestCreature(NPC_DESCEND_INTO_MADNESS, 2.0f, true); Creature* assignedPortal = bot->FindNearestCreature(NPC_DESCEND_INTO_MADNESS, 2.0f, true);
if (!assignedPortal) if (!assignedPortal)
{ return false;
return false;
}
FollowMasterStrategy followMasterStrategy(botAI); FollowMasterStrategy followMasterStrategy(botAI);
if (botAI->HasStrategy(followMasterStrategy.getName(), BotState::BOT_STATE_NON_COMBAT)) if (botAI->HasStrategy(followMasterStrategy.getName(), BotState::BOT_STATE_NON_COMBAT))
{ botAI->ChangeStrategy(ADD_STRATEGY_CHAR + followMasterStrategy.getName(), BotState::BOT_STATE_NON_COMBAT);
botAI->ChangeStrategy(ADD_STRATEGY_CHAR + followMasterStrategy.getName(), BotState::BOT_STATE_NON_COMBAT);
}
return assignedPortal->HandleSpellClick(bot); return assignedPortal->HandleSpellClick(bot);
} }
bool YoggSaronIllusionRoomAction::Execute(Event /*event*/) bool YoggSaronIllusionRoomAction::Execute(Event /*event*/)
@ -3067,15 +2862,11 @@ bool YoggSaronIllusionRoomAction::SetIllusionRtiTarget(YoggSaronTrigger yoggSaro
{ {
Unit* currentRtiTarget = yoggSaronTrigger.GetIllusionRoomRtiTarget(); Unit* currentRtiTarget = yoggSaronTrigger.GetIllusionRoomRtiTarget();
if (currentRtiTarget) if (currentRtiTarget)
{
return false; return false;
}
Unit* nextRtiTarget = yoggSaronTrigger.GetNextIllusionRoomRtiTarget(); Unit* nextRtiTarget = yoggSaronTrigger.GetNextIllusionRoomRtiTarget();
if (!nextRtiTarget) if (!nextRtiTarget)
{
return false; return false;
}
// If proper adds handling in illusion room will be implemented, then this can be removed // If proper adds handling in illusion room will be implemented, then this can be removed
if (botAI->HasCheat(BotCheatMask::raid)) if (botAI->HasCheat(BotCheatMask::raid))
@ -3090,9 +2881,7 @@ bool YoggSaronIllusionRoomAction::SetIllusionRtiTarget(YoggSaronTrigger yoggSaro
{ {
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
if (!group) if (!group)
{
return false; return false;
}
uint8 rtiIndex = RtiTargetValue::GetRtiIndex(AI_VALUE(std::string, "rti")); uint8 rtiIndex = RtiTargetValue::GetRtiIndex(AI_VALUE(std::string, "rti"));
group->SetTargetIcon(rtiIndex, bot->GetGUID(), nextRtiTarget->GetGUID()); group->SetTargetIcon(rtiIndex, bot->GetGUID(), nextRtiTarget->GetGUID());
@ -3104,23 +2893,17 @@ bool YoggSaronIllusionRoomAction::SetIllusionRtiTarget(YoggSaronTrigger yoggSaro
bool YoggSaronIllusionRoomAction::SetBrainRtiTarget(YoggSaronTrigger yoggSaronTrigger) bool YoggSaronIllusionRoomAction::SetBrainRtiTarget(YoggSaronTrigger yoggSaronTrigger)
{ {
if (AI_VALUE(std::string, "rti") == "square" || !yoggSaronTrigger.IsMasterIsInBrainRoom()) if (AI_VALUE(std::string, "rti") == "square" || !yoggSaronTrigger.IsMasterIsInBrainRoom())
{
return false; return false;
}
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set("square"); botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set("square");
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
if (!group) if (!group)
{
return false; return false;
}
Creature* brain = bot->FindNearestCreature(NPC_BRAIN, 200.0f, true); Creature* brain = bot->FindNearestCreature(NPC_BRAIN, 200.0f, true);
if (!brain) if (!brain)
{
return false; return false;
}
group->SetTargetIcon(RtiTargetValue::squareIndex, bot->GetGUID(), brain->GetGUID()); group->SetTargetIcon(RtiTargetValue::squareIndex, bot->GetGUID(), brain->GetGUID());
@ -3160,7 +2943,6 @@ bool YoggSaronMoveToExitPortalAction::Execute(Event /*event*/)
if (botAI->HasCheat(BotCheatMask::raid)) if (botAI->HasCheat(BotCheatMask::raid))
bot->TeleportTo(bot->GetMapId(), portal->GetPositionX(), portal->GetPositionY(), portal->GetPositionZ(), bot->TeleportTo(bot->GetMapId(), portal->GetPositionX(), portal->GetPositionY(), portal->GetPositionZ(),
bot->GetOrientation()); bot->GetOrientation());
else else
MoveTo(bot->GetMapId(), portal->GetPositionX(), portal->GetPositionY(), portal->GetPositionZ(), false, MoveTo(bot->GetMapId(), portal->GetPositionX(), portal->GetPositionY(), portal->GetPositionZ(), false,
false, false, true, MovementPriority::MOVEMENT_FORCED, false, false, true, MovementPriority::MOVEMENT_FORCED,
@ -3189,9 +2971,7 @@ bool YoggSaronLunaticGazeAction::Execute(Event /*event*/)
{ {
if (AI_VALUE(std::string, "rti") != "cross") if (AI_VALUE(std::string, "rti") != "cross")
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set("cross"); botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set("cross");
} }
return true; return true;
} }

View File

@ -852,8 +852,6 @@ bool ThorimMarkDpsTargetTrigger::IsActive()
Unit* runicColossus = AI_VALUE2(Unit*, "find target", "runic colossus"); Unit* runicColossus = AI_VALUE2(Unit*, "find target", "runic colossus");
Unit* ancientRuneGiant = AI_VALUE2(Unit*, "find target", "ancient rune giant"); Unit* ancientRuneGiant = AI_VALUE2(Unit*, "find target", "ancient rune giant");
Unit* ironHonorGuard = AI_VALUE2(Unit*, "find target", "iron ring guard");
Unit* ironRingGuard = AI_VALUE2(Unit*, "find target", "iron honor guard");
if (acolyte && acolyte->IsAlive() && (!currentCrossUnit || currentCrossUnit->GetEntry() != acolyte->GetEntry())) if (acolyte && acolyte->IsAlive() && (!currentCrossUnit || currentCrossUnit->GetEntry() != acolyte->GetEntry()))
return true; return true;
@ -1179,8 +1177,6 @@ bool MimironPhase1PositioningTrigger::IsActive()
} }
Unit* leviathanMkII = nullptr; Unit* leviathanMkII = nullptr;
Unit* vx001 = nullptr;
Unit* aerialCommandUnit = nullptr;
GuidVector targets = AI_VALUE(GuidVector, "possible targets"); GuidVector targets = AI_VALUE(GuidVector, "possible targets");
Unit* target = nullptr; Unit* target = nullptr;
@ -1192,19 +1188,14 @@ bool MimironPhase1PositioningTrigger::IsActive()
if (target->GetEntry() == NPC_LEVIATHAN_MKII) if (target->GetEntry() == NPC_LEVIATHAN_MKII)
leviathanMkII = target; leviathanMkII = target;
else if (target->GetEntry() == NPC_VX001) else if (target->GetEntry() == NPC_VX001)
return false; return false;
else if (target->GetEntry() == NPC_AERIAL_COMMAND_UNIT) else if (target->GetEntry() == NPC_AERIAL_COMMAND_UNIT)
return false; return false;
} }
if (!leviathanMkII || !leviathanMkII->IsAlive()) if (!leviathanMkII || !leviathanMkII->IsAlive())
{
return false; return false;
}
return AI_VALUE(float, "disperse distance") != 6.0f; return AI_VALUE(float, "disperse distance") != 6.0f;
} }
@ -1215,9 +1206,7 @@ bool MimironP3Wx2LaserBarrageTrigger::IsActive()
// Check boss and it is alive // Check boss and it is alive
if (!boss || !boss->IsAlive()) if (!boss || !boss->IsAlive())
{
return false; return false;
}
bool isCasting = boss->HasUnitState(UNIT_STATE_CASTING); bool isCasting = boss->HasUnitState(UNIT_STATE_CASTING);
bool isP3WX2LaserBarrage = boss->FindCurrentSpellBySpellId(SPELL_SPINNING_UP) || bool isP3WX2LaserBarrage = boss->FindCurrentSpellBySpellId(SPELL_SPINNING_UP) ||
@ -1230,9 +1219,7 @@ bool MimironP3Wx2LaserBarrageTrigger::IsActive()
boss->HasAura(SPELL_P3WX2_LASER_BARRAGE_AURA_1) || boss->HasAura(SPELL_P3WX2_LASER_BARRAGE_AURA_2); boss->HasAura(SPELL_P3WX2_LASER_BARRAGE_AURA_1) || boss->HasAura(SPELL_P3WX2_LASER_BARRAGE_AURA_2);
if ((!isCasting && !hasP3WX2LaserBarrageAura) || !isP3WX2LaserBarrage) if ((!isCasting && !hasP3WX2LaserBarrageAura) || !isP3WX2LaserBarrage)
{
return false; return false;
}
return true; return true;
} }
@ -1252,51 +1239,33 @@ bool MimironRapidBurstTrigger::IsActive()
continue; continue;
if (target->GetEntry() == NPC_LEVIATHAN_MKII) if (target->GetEntry() == NPC_LEVIATHAN_MKII)
{
leviathanMkII = target; leviathanMkII = target;
}
else if (target->GetEntry() == NPC_VX001) else if (target->GetEntry() == NPC_VX001)
{
vx001 = target; vx001 = target;
}
else if (target->GetEntry() == NPC_AERIAL_COMMAND_UNIT) else if (target->GetEntry() == NPC_AERIAL_COMMAND_UNIT)
{
aerialCommandUnit = target; aerialCommandUnit = target;
}
} }
if (!vx001 || !vx001->IsAlive()) if (!vx001 || !vx001->IsAlive())
{
return false; return false;
}
if (leviathanMkII && leviathanMkII->HasUnitState(UNIT_STATE_CASTING) && if (leviathanMkII && leviathanMkII->HasUnitState(UNIT_STATE_CASTING) &&
leviathanMkII->FindCurrentSpellBySpellId(SPELL_SHOCK_BLAST)) leviathanMkII->FindCurrentSpellBySpellId(SPELL_SHOCK_BLAST))
{
return false; return false;
}
if (botAI->IsMainTank(bot) && leviathanMkII && leviathanMkII->IsAlive() && leviathanMkII->GetVictim() != bot) if (botAI->IsMainTank(bot) && leviathanMkII && leviathanMkII->IsAlive() && leviathanMkII->GetVictim() != bot)
{
return false; return false;
}
if (botAI->IsMelee(bot) && !botAI->IsMainTank(bot) && leviathanMkII && aerialCommandUnit) if (botAI->IsMelee(bot) && !botAI->IsMainTank(bot) && leviathanMkII && aerialCommandUnit)
{
return false; return false;
}
MimironP3Wx2LaserBarrageTrigger mimironP3Wx2LaserBarrageTrigger(botAI); MimironP3Wx2LaserBarrageTrigger mimironP3Wx2LaserBarrageTrigger(botAI);
if (mimironP3Wx2LaserBarrageTrigger.IsActive()) if (mimironP3Wx2LaserBarrageTrigger.IsActive())
{
return false; return false;
}
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
if (!group) if (!group)
{
return false; return false;
}
uint32 memberSpotNumber = 0; uint32 memberSpotNumber = 0;
Position memberPosition; Position memberPosition;
@ -1355,9 +1324,7 @@ bool MimironRapidBurstTrigger::IsActive()
memberSpotNumber++; memberSpotNumber++;
if (memberSpotNumber == 3) if (memberSpotNumber == 3)
{
memberSpotNumber = 0; memberSpotNumber = 0;
}
} }
GuidVector npcs = AI_VALUE(GuidVector, "nearest npcs"); GuidVector npcs = AI_VALUE(GuidVector, "nearest npcs");
@ -1390,7 +1357,6 @@ bool MimironAerialCommandUnitTrigger::IsActive()
Unit* leviathanMkII = nullptr; Unit* leviathanMkII = nullptr;
Unit* vx001 = nullptr; Unit* vx001 = nullptr;
Unit* aerialCommandUnit = nullptr; Unit* aerialCommandUnit = nullptr;
//Unit* bombBot = nullptr;
Unit* assaultBot = nullptr; Unit* assaultBot = nullptr;
GuidVector targets = AI_VALUE(GuidVector, "possible targets"); GuidVector targets = AI_VALUE(GuidVector, "possible targets");
@ -1402,69 +1368,41 @@ bool MimironAerialCommandUnitTrigger::IsActive()
continue; continue;
if (target->GetEntry() == NPC_LEVIATHAN_MKII) if (target->GetEntry() == NPC_LEVIATHAN_MKII)
{
leviathanMkII = target; leviathanMkII = target;
}
else if (target->GetEntry() == NPC_VX001) else if (target->GetEntry() == NPC_VX001)
{
vx001 = target; vx001 = target;
}
else if (target->GetEntry() == NPC_AERIAL_COMMAND_UNIT) else if (target->GetEntry() == NPC_AERIAL_COMMAND_UNIT)
{
aerialCommandUnit = target; aerialCommandUnit = target;
}
//else if (target->GetEntry() == NPC_BOMB_BOT)
//{
// bombBot = target;
//}
else if (target->GetEntry() == NPC_ASSAULT_BOT) else if (target->GetEntry() == NPC_ASSAULT_BOT)
{
assaultBot = target; assaultBot = target;
}
} }
if (!aerialCommandUnit || !aerialCommandUnit->IsAlive() || leviathanMkII || vx001) if (!aerialCommandUnit || !aerialCommandUnit->IsAlive() || leviathanMkII || vx001)
{
return false; return false;
}
if (!botAI->IsRanged(bot) && !botAI->IsMainTank(bot) && !botAI->IsAssistTankOfIndex(bot, 0)) if (!botAI->IsRanged(bot) && !botAI->IsMainTank(bot) && !botAI->IsAssistTankOfIndex(bot, 0))
{
return false; return false;
}
if (botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0)) if (botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0))
{ {
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
if (!group) if (!group)
{
return false; return false;
}
ObjectGuid skullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex); ObjectGuid skullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex);
ObjectGuid crossTarget = group->GetTargetIcon(RtiTargetValue::crossIndex); ObjectGuid crossTarget = group->GetTargetIcon(RtiTargetValue::crossIndex);
//if (bombBot && bombBot->GetGUID() != crossTarget)
//{
// return true;
//}
if (!crossTarget || aerialCommandUnit->GetGUID() != crossTarget) if (!crossTarget || aerialCommandUnit->GetGUID() != crossTarget)
{
return true; return true;
}
else if (assaultBot && (!skullTarget || assaultBot->GetGUID() != skullTarget)) else if (assaultBot && (!skullTarget || assaultBot->GetGUID() != skullTarget))
{
return true; return true;
}
return false; return false;
} }
std::string rtiMark = AI_VALUE(std::string, "rti"); std::string rtiMark = AI_VALUE(std::string, "rti");
if (rtiMark != "cross") if (rtiMark != "cross")
{
return true; return true;
}
return false; return false;
} }
@ -1475,16 +1413,12 @@ bool MimironRocketStrikeTrigger::IsActive()
// Check boss and it is alive // Check boss and it is alive
if (!boss || !boss->IsAlive()) if (!boss || !boss->IsAlive())
{
return false; return false;
}
Creature* rocketStrikeN = bot->FindNearestCreature(NPC_ROCKET_STRIKE_N, 100.0f); Creature* rocketStrikeN = bot->FindNearestCreature(NPC_ROCKET_STRIKE_N, 100.0f);
if (!rocketStrikeN) if (!rocketStrikeN)
{
return false; return false;
}
return bot->GetDistance2d(rocketStrikeN->GetPositionX(), rocketStrikeN->GetPositionY()) <= 10.0f; return bot->GetDistance2d(rocketStrikeN->GetPositionX(), rocketStrikeN->GetPositionY()) <= 10.0f;
} }
@ -1497,9 +1431,7 @@ bool MimironPhase4MarkDpsTrigger::IsActive()
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
if (!group) if (!group)
{
return false; return false;
}
GuidVector targets = AI_VALUE(GuidVector, "possible targets"); GuidVector targets = AI_VALUE(GuidVector, "possible targets");
Unit* target = nullptr; Unit* target = nullptr;
@ -1510,23 +1442,15 @@ bool MimironPhase4MarkDpsTrigger::IsActive()
continue; continue;
if (target->GetEntry() == NPC_LEVIATHAN_MKII) if (target->GetEntry() == NPC_LEVIATHAN_MKII)
{
leviathanMkII = target; leviathanMkII = target;
}
else if (target->GetEntry() == NPC_VX001) else if (target->GetEntry() == NPC_VX001)
{
vx001 = target; vx001 = target;
}
else if (target->GetEntry() == NPC_AERIAL_COMMAND_UNIT) else if (target->GetEntry() == NPC_AERIAL_COMMAND_UNIT)
{
aerialCommandUnit = target; aerialCommandUnit = target;
}
} }
if (!leviathanMkII || !vx001 || !aerialCommandUnit) if (!leviathanMkII || !vx001 || !aerialCommandUnit)
{
return false; return false;
}
if (botAI->IsMainTank(bot)) if (botAI->IsMainTank(bot))
{ {
@ -1544,22 +1468,16 @@ bool MimironPhase4MarkDpsTrigger::IsActive()
highestHealthUnit = vx001; highestHealthUnit = vx001;
} }
if (aerialCommandUnit && aerialCommandUnit->GetHealth() > highestHealth) if (aerialCommandUnit && aerialCommandUnit->GetHealth() > highestHealth)
{
highestHealthUnit = aerialCommandUnit; highestHealthUnit = aerialCommandUnit;
}
ObjectGuid skullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex); ObjectGuid skullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex);
if (!skullTarget) if (!skullTarget)
{
return true; return true;
}
return highestHealthUnit->GetGUID() != skullTarget; return highestHealthUnit->GetGUID() != skullTarget;
} }
else else
{
return AI_VALUE(std::string, "rti") != "skull"; return AI_VALUE(std::string, "rti") != "skull";
}
return false; return false;
} }
@ -1567,14 +1485,10 @@ bool MimironPhase4MarkDpsTrigger::IsActive()
bool MimironCheatTrigger::IsActive() bool MimironCheatTrigger::IsActive()
{ {
if (!botAI->HasCheat(BotCheatMask::raid)) if (!botAI->HasCheat(BotCheatMask::raid))
{
return false; return false;
}
if (!botAI->IsMainTank(bot)) if (!botAI->IsMainTank(bot))
{
return false; return false;
}
GuidVector targets = AI_VALUE(GuidVector, "nearest npcs"); GuidVector targets = AI_VALUE(GuidVector, "nearest npcs");
for (const ObjectGuid& guid : targets) for (const ObjectGuid& guid : targets)
@ -1584,13 +1498,9 @@ bool MimironCheatTrigger::IsActive()
continue; continue;
if (unit->GetEntry() == NPC_PROXIMITY_MINE) if (unit->GetEntry() == NPC_PROXIMITY_MINE)
{
return true; return true;
}
else if (unit->GetEntry() == NPC_BOMB_BOT) else if (unit->GetEntry() == NPC_BOMB_BOT)
{
return true; return true;
}
} }
return false; return false;
@ -1599,22 +1509,16 @@ bool MimironCheatTrigger::IsActive()
bool VezaxCheatTrigger::IsActive() bool VezaxCheatTrigger::IsActive()
{ {
if (!botAI->HasCheat(BotCheatMask::raid)) if (!botAI->HasCheat(BotCheatMask::raid))
{
return false; return false;
}
Unit* boss = AI_VALUE2(Unit*, "find target", "general vezax"); Unit* boss = AI_VALUE2(Unit*, "find target", "general vezax");
// Check boss and it is alive // Check boss and it is alive
if (!boss || !boss->IsAlive()) if (!boss || !boss->IsAlive())
{
return false; return false;
}
if (!AI_VALUE2(bool, "has mana", "self target")) if (!AI_VALUE2(bool, "has mana", "self target"))
{
return false; return false;
}
return AI_VALUE2(uint8, "mana", "self target") < sPlayerbotAIConfig.lowMana; return AI_VALUE2(uint8, "mana", "self target") < sPlayerbotAIConfig.lowMana;
} }
@ -1625,9 +1529,7 @@ bool VezaxShadowCrashTrigger::IsActive()
// Check boss and it is alive // Check boss and it is alive
if (!boss || !boss->IsAlive()) if (!boss || !boss->IsAlive())
{
return false; return false;
}
return botAI->HasAura(SPELL_VEZAX_SHADOW_CRASH, bot); return botAI->HasAura(SPELL_VEZAX_SHADOW_CRASH, bot);
} }
@ -1638,14 +1540,10 @@ bool VezaxMarkOfTheFacelessTrigger::IsActive()
// Check boss and it is alive // Check boss and it is alive
if (!boss || !boss->IsAlive()) if (!boss || !boss->IsAlive())
{
return false; return false;
}
if (!botAI->HasAura(SPELL_MARK_OF_THE_FACELESS, bot)) if (!botAI->HasAura(SPELL_MARK_OF_THE_FACELESS, bot))
{
return false; return false;
}
float distance = bot->GetDistance2d(ULDUAR_VEZAX_MARK_OF_THE_FACELESS_SPOT.GetPositionX(), float distance = bot->GetDistance2d(ULDUAR_VEZAX_MARK_OF_THE_FACELESS_SPOT.GetPositionX(),
ULDUAR_VEZAX_MARK_OF_THE_FACELESS_SPOT.GetPositionY()); ULDUAR_VEZAX_MARK_OF_THE_FACELESS_SPOT.GetPositionY());
@ -1657,9 +1555,8 @@ Unit* YoggSaronTrigger::GetSaraIfAlive()
{ {
Unit* sara = AI_VALUE2(Unit*, "find target", "sara"); Unit* sara = AI_VALUE2(Unit*, "find target", "sara");
if (!sara || !sara->IsAlive()) if (!sara || !sara->IsAlive())
{
return nullptr; return nullptr;
}
return sara; return sara;
} }
@ -1689,9 +1586,7 @@ bool YoggSaronTrigger::IsYoggSaronFight()
Unit* yoggsaron = AI_VALUE2(Unit*, "find target", "yogg-saron"); Unit* yoggsaron = AI_VALUE2(Unit*, "find target", "yogg-saron");
if ((sara && sara->IsAlive()) || (yoggsaron && yoggsaron->IsAlive())) if ((sara && sara->IsAlive()) || (yoggsaron && yoggsaron->IsAlive()))
{
return true; return true;
}
return false; return false;
} }
@ -1699,24 +1594,16 @@ bool YoggSaronTrigger::IsYoggSaronFight()
bool YoggSaronTrigger::IsInIllusionRoom() bool YoggSaronTrigger::IsInIllusionRoom()
{ {
if (!IsInBrainLevel()) if (!IsInBrainLevel())
{
return false; return false;
}
if (IsInStormwindKeeperIllusion()) if (IsInStormwindKeeperIllusion())
{
return true; return true;
}
if (IsInIcecrownKeeperIllusion()) if (IsInIcecrownKeeperIllusion())
{
return true; return true;
}
if (IsInChamberOfTheAspectsIllusion()) if (IsInChamberOfTheAspectsIllusion())
{
return true; return true;
}
return false; return false;
} }
@ -1753,9 +1640,7 @@ bool YoggSaronTrigger::IsMasterIsInBrainRoom()
Player* master = botAI->GetMaster(); Player* master = botAI->GetMaster();
if (!master) if (!master)
{
return false; return false;
}
return master->GetDistance2d(ULDUAR_YOGG_SARON_BRAIN_ROOM_MIDDLE.GetPositionX(), return master->GetDistance2d(ULDUAR_YOGG_SARON_BRAIN_ROOM_MIDDLE.GetPositionX(),
ULDUAR_YOGG_SARON_BRAIN_ROOM_MIDDLE.GetPositionY()) < ULDUAR_YOGG_SARON_BRAIN_ROOM_MIDDLE.GetPositionY()) <
@ -1766,43 +1651,29 @@ bool YoggSaronTrigger::IsMasterIsInBrainRoom()
Position YoggSaronTrigger::GetIllusionRoomEntrancePosition() Position YoggSaronTrigger::GetIllusionRoomEntrancePosition()
{ {
if (IsInChamberOfTheAspectsIllusion()) if (IsInChamberOfTheAspectsIllusion())
{
return ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_ENTRANCE; return ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_ENTRANCE;
}
else if (IsInIcecrownKeeperIllusion()) else if (IsInIcecrownKeeperIllusion())
{
return ULDUAR_YOGG_SARON_ICECROWN_CITADEL_ENTRANCE; return ULDUAR_YOGG_SARON_ICECROWN_CITADEL_ENTRANCE;
}
else if (IsInStormwindKeeperIllusion()) else if (IsInStormwindKeeperIllusion())
{
return ULDUAR_YOGG_SARON_STORMWIND_KEEPER_ENTRANCE; return ULDUAR_YOGG_SARON_STORMWIND_KEEPER_ENTRANCE;
}
else else
{
return Position(); return Position();
}
} }
Unit* YoggSaronTrigger::GetIllusionRoomRtiTarget() Unit* YoggSaronTrigger::GetIllusionRoomRtiTarget()
{ {
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
if (!group) if (!group)
{
return nullptr; return nullptr;
}
int32_t rtiIndex = RtiTargetValue::GetRtiIndex(AI_VALUE(std::string, "rti")); int32_t rtiIndex = RtiTargetValue::GetRtiIndex(AI_VALUE(std::string, "rti"));
if (rtiIndex == -1) if (rtiIndex == -1)
{
return nullptr; // Invalid RTI mark return nullptr; // Invalid RTI mark
}
ObjectGuid currentRtiTarget = group->GetTargetIcon(rtiIndex); ObjectGuid currentRtiTarget = group->GetTargetIcon(rtiIndex);
Unit* currentRtiTargetUnit = botAI->GetUnit(currentRtiTarget); Unit* currentRtiTargetUnit = botAI->GetUnit(currentRtiTarget);
if (!currentRtiTargetUnit || !currentRtiTargetUnit->IsAlive()) if (!currentRtiTargetUnit || !currentRtiTargetUnit->IsAlive())
{
currentRtiTargetUnit = nullptr; currentRtiTargetUnit = nullptr;
}
return currentRtiTargetUnit; return currentRtiTargetUnit;
} }
@ -1812,13 +1683,10 @@ Unit* YoggSaronTrigger::GetNextIllusionRoomRtiTarget()
float detectionRadius = 0.0f; float detectionRadius = 0.0f;
if (IsInStormwindKeeperIllusion()) if (IsInStormwindKeeperIllusion())
detectionRadius = ULDUAR_YOGG_SARON_STORMWIND_KEEPER_RADIUS; detectionRadius = ULDUAR_YOGG_SARON_STORMWIND_KEEPER_RADIUS;
else if (IsInIcecrownKeeperIllusion()) else if (IsInIcecrownKeeperIllusion())
detectionRadius = ULDUAR_YOGG_SARON_ICECROWN_CITADEL_RADIUS; detectionRadius = ULDUAR_YOGG_SARON_ICECROWN_CITADEL_RADIUS;
else if (IsInChamberOfTheAspectsIllusion()) else if (IsInChamberOfTheAspectsIllusion())
detectionRadius = ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_RADIUS; detectionRadius = ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_RADIUS;
else else
return nullptr; return nullptr;
@ -1855,9 +1723,7 @@ Unit* YoggSaronTrigger::GetNextIllusionRoomRtiTarget()
} }
if (nextIllusionRoomRtiTarget) if (nextIllusionRoomRtiTarget)
{
return nextIllusionRoomRtiTarget; return nextIllusionRoomRtiTarget;
}
if (IsInStormwindKeeperIllusion()) if (IsInStormwindKeeperIllusion())
{ {
@ -1969,9 +1835,7 @@ bool YoggSaronMarkTargetTrigger::IsActive()
ObjectGuid currentMoonTarget = group->GetTargetIcon(RtiTargetValue::moonIndex); ObjectGuid currentMoonTarget = group->GetTargetIcon(RtiTargetValue::moonIndex);
Creature* yogg_saron = bot->FindNearestCreature(NPC_YOGG_SARON, 200.0f, true); Creature* yogg_saron = bot->FindNearestCreature(NPC_YOGG_SARON, 200.0f, true);
if (!currentMoonTarget || currentMoonTarget != yogg_saron->GetGUID()) if (!currentMoonTarget || currentMoonTarget != yogg_saron->GetGUID())
{
return true; return true;
}
ObjectGuid currentSkullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex); ObjectGuid currentSkullTarget = group->GetTargetIcon(RtiTargetValue::skullIndex);
@ -2234,9 +2098,7 @@ bool YoggSaronPhase3PositioningTrigger::IsActive()
if (botAI->IsRanged(bot) && bot->GetDistance2d(ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT.GetPositionX(), if (botAI->IsRanged(bot) && bot->GetDistance2d(ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT.GetPositionX(),
ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT.GetPositionY()) > 15.0f) ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT.GetPositionY()) > 15.0f)
{
return true; return true;
}
if (botAI->IsMelee(bot) && !botAI->IsTank(bot) && if (botAI->IsMelee(bot) && !botAI->IsTank(bot) &&
bot->GetDistance2d(ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionX(), bot->GetDistance2d(ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT.GetPositionX(),

View File

@ -435,7 +435,6 @@ bool NewRpgTravelFlightAction::Execute(Event /*event*/)
botAI->rpgInfo.ChangeToIdle(); botAI->rpgInfo.ChangeToIdle();
return true; return true;
} }
const TaxiNodesEntry* entry = sTaxiNodesStore.LookupEntry(data.toNode);
if (bot->GetDistance(flightMaster) > INTERACTION_DISTANCE) if (bot->GetDistance(flightMaster) > INTERACTION_DISTANCE)
return MoveFarTo(flightMaster); return MoveFarTo(flightMaster);

View File

@ -360,7 +360,6 @@ bool NewRpgBaseAction::IsWithinInteractionDist(Object* questGiver)
case TYPEID_GAMEOBJECT: case TYPEID_GAMEOBJECT:
{ {
ObjectGuid guid = questGiver->GetGUID(); ObjectGuid guid = questGiver->GetGUID();
GameobjectTypes type = GAMEOBJECT_TYPE_QUESTGIVER;
if (GameObject* go = bot->GetMap()->GetGameObject(guid)) if (GameObject* go = bot->GetMap()->GetGameObject(guid))
{ {
if (go->IsWithinDistInMap(bot)) if (go->IsWithinDistInMap(bot))
@ -546,7 +545,9 @@ bool NewRpgBaseAction::OrganizeQuestLog()
continue; continue;
const Quest* quest = sObjectMgr->GetQuestTemplate(questId); const Quest* quest = sObjectMgr->GetQuestTemplate(questId);
if (quest->GetZoneOrSort() < 0 || (quest->GetZoneOrSort() > 0 && quest->GetZoneOrSort() != bot->GetZoneId())) const int64_t botZoneId = this->bot->GetZoneId();
if (quest->GetZoneOrSort() < 0 || (quest->GetZoneOrSort() > 0 && quest->GetZoneOrSort() != botZoneId))
{ {
LOG_DEBUG("playerbots", "[New RPG] {} drop quest {}", bot->GetName(), questId); LOG_DEBUG("playerbots", "[New RPG] {} drop quest {}", bot->GetName(), questId);
WorldPacket packet(CMSG_QUESTLOG_REMOVE_QUEST); WorldPacket packet(CMSG_QUESTLOG_REMOVE_QUEST);

View File

@ -60,7 +60,10 @@ void NewRpgInfo::ChangeToIdle()
data = Idle{}; data = Idle{};
} }
bool NewRpgInfo::CanChangeTo(NewRpgStatus status) { return true; } bool NewRpgInfo::CanChangeTo(NewRpgStatus)
{
return true;
}
void NewRpgInfo::Reset() void NewRpgInfo::Reset()
{ {

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

@ -948,8 +948,6 @@ void PlayerbotFactory::InitPet()
continue; continue;
if (co->Name.size() > 21) if (co->Name.size() > 21)
continue; continue;
uint32 guid = map->GenerateLowGuid<HighGuid::Pet>();
uint32 pet_number = sObjectMgr->GeneratePetNumber();
if (bot->GetPetStable() && bot->GetPetStable()->CurrentPet) if (bot->GetPetStable() && bot->GetPetStable()->CurrentPet)
{ {
auto petGuid = bot->GetPetStable()->CurrentPet.value(); // To correct the build warnin in VS auto petGuid = bot->GetPetStable()->CurrentPet.value(); // To correct the build warnin in VS
@ -1905,7 +1903,7 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance)
if (oldItem) if (oldItem)
continue; continue;
Item* newItem = bot->EquipNewItem(dest, bestItemForSlot, true); bot->EquipNewItem(dest, bestItemForSlot, true);
bot->AutoUnequipOffhandIfNeed(); bot->AutoUnequipOffhandIfNeed();
// if (newItem) // if (newItem)
// { // {
@ -1936,7 +1934,7 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance)
(slot != EQUIPMENT_SLOT_RANGED)) (slot != EQUIPMENT_SLOT_RANGED))
continue; continue;
if (Item* oldItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) if (bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot) != nullptr)
bot->DestroyItem(INVENTORY_SLOT_BAG_0, slot, true); bot->DestroyItem(INVENTORY_SLOT_BAG_0, slot, true);
std::vector<uint32>& ids = items[slot]; std::vector<uint32>& ids = items[slot];
@ -1973,12 +1971,9 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance)
continue; continue;
Item* newItem = bot->EquipNewItem(dest, bestItemForSlot, true); Item* newItem = bot->EquipNewItem(dest, bestItemForSlot, true);
bot->EquipNewItem(dest, bestItemForSlot, true);
bot->AutoUnequipOffhandIfNeed(); bot->AutoUnequipOffhandIfNeed();
// if (newItem)
// {
// newItem->AddToWorld();
// newItem->AddToUpdateQueueOf(bot);
// }
} }
} }
} }
@ -1991,16 +1986,10 @@ bool PlayerbotFactory::IsDesiredReplacement(Item* item)
ItemTemplate const* proto = item->GetTemplate(); ItemTemplate const* proto = item->GetTemplate();
uint32 requiredLevel = proto->RequiredLevel; uint32 requiredLevel = proto->RequiredLevel;
if (!requiredLevel) if (!requiredLevel)
{
return true; return true;
}
// if (!requiredLevel)
// {
// requiredLevel = sRandomItemMgr.GetMinLevelFromCache(proto->ItemId);
// }
uint32 delta = 1 + (80 - bot->GetLevel()) / 10; uint32 delta = 1 + (80 - bot->GetLevel()) / 10;
return proto->Quality < ITEM_QUALITY_RARE || int32(bot->GetLevel() - requiredLevel) > delta; return proto->Quality < ITEM_QUALITY_RARE || (bot->GetLevel() - requiredLevel) > delta;
} }
inline Item* StoreNewItemInInventorySlot(Player* player, uint32 newItemId, uint32 count) inline Item* StoreNewItemInInventorySlot(Player* player, uint32 newItemId, uint32 count)
@ -2010,9 +1999,7 @@ inline Item* StoreNewItemInInventorySlot(Player* player, uint32 newItemId, uint3
if (msg == EQUIP_ERR_OK) if (msg == EQUIP_ERR_OK)
{ {
if (Item* newItem = player->StoreNewItem(vDest, newItemId, true, Item::GenerateItemRandomPropertyId(newItemId))) if (Item* newItem = player->StoreNewItem(vDest, newItemId, true, Item::GenerateItemRandomPropertyId(newItemId)))
{
return newItem; return newItem;
}
} }
return nullptr; return nullptr;
@ -2875,7 +2862,6 @@ void PlayerbotFactory::AddPrevQuests(uint32 questId, std::list<uint32>& questIds
void PlayerbotFactory::InitQuests(std::list<uint32>& questMap, bool withRewardItem) void PlayerbotFactory::InitQuests(std::list<uint32>& questMap, bool withRewardItem)
{ {
uint32 count = 0;
for (std::list<uint32>::iterator i = questMap.begin(); i != questMap.end(); ++i) for (std::list<uint32>::iterator i = questMap.begin(); i != questMap.end(); ++i)
{ {
uint32 questId = *i; uint32 questId = *i;
@ -3280,7 +3266,6 @@ void PlayerbotFactory::InitFood()
void PlayerbotFactory::InitReagents() void PlayerbotFactory::InitReagents()
{ {
int specTab = AiFactory::GetPlayerSpecTab(bot);
std::vector<std::pair<uint32, uint32>> items; std::vector<std::pair<uint32, uint32>> items;
switch (bot->getClass()) switch (bot->getClass())
{ {
@ -4336,15 +4321,11 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destroyOld)
continue; continue;
if (!item->IsFitToSpellRequirements(spellInfo)) if (!item->IsFitToSpellRequirements(spellInfo))
{
continue; continue;
}
uint32 requiredLevel = spellInfo->BaseLevel; uint32 requiredLevel = spellInfo->BaseLevel;
if (requiredLevel > bot->GetLevel()) if (requiredLevel > bot->GetLevel())
{
continue; continue;
}
// disable next expansion enchantments // disable next expansion enchantments
if (sPlayerbotAIConfig.limitEnchantExpansion && bot->GetLevel() <= 60 && enchantSpell >= 27899) if (sPlayerbotAIConfig.limitEnchantExpansion && bot->GetLevel() <= 60 && enchantSpell >= 27899)
@ -4364,9 +4345,8 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destroyOld)
SpellItemEnchantmentEntry const* enchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id); SpellItemEnchantmentEntry const* enchant = sSpellItemEnchantmentStore.LookupEntry(enchant_id);
if (!enchant || (enchant->slot != PERM_ENCHANTMENT_SLOT && enchant->slot != TEMP_ENCHANTMENT_SLOT)) if (!enchant || (enchant->slot != PERM_ENCHANTMENT_SLOT && enchant->slot != TEMP_ENCHANTMENT_SLOT))
{
continue; continue;
}
if (enchant->requiredSkill && if (enchant->requiredSkill &&
(!bot->HasSkill(enchant->requiredSkill) || (!bot->HasSkill(enchant->requiredSkill) ||
(bot->GetSkillValue(enchant->requiredSkill) < enchant->requiredSkillValue))) (bot->GetSkillValue(enchant->requiredSkill) < enchant->requiredSkillValue)))
@ -4374,9 +4354,8 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destroyOld)
continue; continue;
} }
if (enchant->requiredLevel > bot->GetLevel()) if (enchant->requiredLevel > bot->GetLevel())
{
continue; continue;
}
float score = calculator.CalculateEnchant(enchant_id); float score = calculator.CalculateEnchant(enchant_id);
if (score >= bestScore) if (score >= bestScore)
{ {
@ -4399,11 +4378,9 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destroyOld)
{ {
uint8 socketColor = item->GetTemplate()->Socket[enchant_slot - SOCK_ENCHANTMENT_SLOT].Color; uint8 socketColor = item->GetTemplate()->Socket[enchant_slot - SOCK_ENCHANTMENT_SLOT].Color;
if (!socketColor) if (!socketColor)
{
continue; continue;
}
int32 enchantIdChosen = -1; int32 enchantIdChosen = -1;
int32 colorChosen;
bool jewelersGemChosen; bool jewelersGemChosen;
float bestGemScore = -1; float bestGemScore = -1;
for (uint32& enchantGem : availableGems) for (uint32& enchantGem : availableGems)
@ -4448,7 +4425,6 @@ void PlayerbotFactory::ApplyEnchantAndGemsNew(bool destroyOld)
if (score > bestGemScore) if (score > bestGemScore)
{ {
enchantIdChosen = enchant_id; enchantIdChosen = enchant_id;
colorChosen = gemProperties->color;
bestGemScore = score; bestGemScore = score;
jewelersGemChosen = isJewelersGem; jewelersGemChosen = isJewelersGem;
} }

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

@ -71,7 +71,6 @@ class PlayerbotLoginQueryHolder : public LoginQueryHolder
{ {
private: private:
uint32 masterAccountId; uint32 masterAccountId;
PlayerbotHolder* playerbotHolder;
public: public:
PlayerbotLoginQueryHolder(uint32 masterAccount, uint32 accountId, ObjectGuid guid) PlayerbotLoginQueryHolder(uint32 masterAccount, uint32 accountId, ObjectGuid guid)
: LoginQueryHolder(accountId, guid), masterAccountId(masterAccount) : LoginQueryHolder(accountId, guid), masterAccountId(masterAccount)

View File

@ -994,7 +994,7 @@ void RandomPlayerbotMgr::CheckBgQueue()
// Arena logic // Arena logic
bool isRated = false; bool isRated = false;
if (uint8 arenaType = BattlegroundMgr::BGArenaType(queueTypeId)) if (BattlegroundMgr::BGArenaType(queueTypeId))
{ {
BattlegroundQueue& bgQueue = sBattlegroundMgr->GetBattlegroundQueue(queueTypeId); BattlegroundQueue& bgQueue = sBattlegroundMgr->GetBattlegroundQueue(queueTypeId);
GroupQueueInfo ginfo; GroupQueueInfo ginfo;
@ -1081,7 +1081,7 @@ void RandomPlayerbotMgr::CheckBgQueue()
BattlegroundData[queueTypeId][bracketId].minLevel = pvpDiff->minLevel; BattlegroundData[queueTypeId][bracketId].minLevel = pvpDiff->minLevel;
BattlegroundData[queueTypeId][bracketId].maxLevel = pvpDiff->maxLevel; BattlegroundData[queueTypeId][bracketId].maxLevel = pvpDiff->maxLevel;
if (uint8 arenaType = BattlegroundMgr::BGArenaType(queueTypeId)) if (BattlegroundMgr::BGArenaType(queueTypeId))
{ {
bool isRated = false; bool isRated = false;
BattlegroundQueue& bgQueue = sBattlegroundMgr->GetBattlegroundQueue(queueTypeId); BattlegroundQueue& bgQueue = sBattlegroundMgr->GetBattlegroundQueue(queueTypeId);
@ -1986,13 +1986,9 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
for (int i = bracket.low; i <= bracket.high; i++) for (int i = bracket.low; i <= bracket.high; i++)
{ {
if (forHorde) if (forHorde)
{
hordeStarterPerLevelCache[i].push_back(loc); hordeStarterPerLevelCache[i].push_back(loc);
}
if (forAlliance) if (forAlliance)
{
allianceStarterPerLevelCache[i].push_back(loc); allianceStarterPerLevelCache[i].push_back(loc);
}
} }
} while (results->NextRow()); } while (results->NextRow());
@ -3146,10 +3142,10 @@ void RandomPlayerbotMgr::PrintStats()
uint32 heal = 0; uint32 heal = 0;
uint32 tank = 0; uint32 tank = 0;
uint32 active = 0; uint32 active = 0;
uint32 update = 0; /* uint32 update = 0;
uint32 randomize = 0; uint32 randomize = 0;
uint32 teleport = 0; uint32 teleport = 0;
uint32 changeStrategy = 0; uint32 changeStrategy = 0;*/
uint32 dead = 0; uint32 dead = 0;
uint32 combat = 0; uint32 combat = 0;
// uint32 revive = 0; //not used, line marked for removal. // uint32 revive = 0; //not used, line marked for removal.
@ -3189,7 +3185,7 @@ void RandomPlayerbotMgr::PrintStats()
if (botAI->AllowActivity()) if (botAI->AllowActivity())
++active; ++active;
/* TODO: Review statistics on rpg merge
if (botAI->GetAiObjectContext()->GetValue<bool>("random bot update")->Get()) if (botAI->GetAiObjectContext()->GetValue<bool>("random bot update")->Get())
++update; ++update;
@ -3202,7 +3198,7 @@ void RandomPlayerbotMgr::PrintStats()
if (!GetEventValue(botId, "change_strategy")) if (!GetEventValue(botId, "change_strategy"))
++changeStrategy; ++changeStrategy;
*/
if (bot->isDead()) if (bot->isDead())
{ {
++dead; ++dead;

View File

@ -1832,7 +1832,6 @@ std::vector<uint32> RandomItemMgr::GetUpgradeList(Player* player, std::string sp
// get old item statWeight // get old item statWeight
uint32 oldStatWeight = 0; uint32 oldStatWeight = 0;
uint32 specId = 0; uint32 specId = 0;
uint32 closestUpgrade = 0;
uint32 closestUpgradeWeight = 0; uint32 closestUpgradeWeight = 0;
std::vector<uint32> classspecs; std::vector<uint32> classspecs;
@ -1933,7 +1932,6 @@ std::vector<uint32> RandomItemMgr::GetUpgradeList(Player* player, std::string sp
// pick closest upgrade // pick closest upgrade
if (info.weights[specId] > closestUpgradeWeight) if (info.weights[specId] > closestUpgradeWeight)
{ {
closestUpgrade = info.itemId;
closestUpgradeWeight = info.weights[specId]; closestUpgradeWeight = info.weights[specId];
} }
} }

View File

@ -415,10 +415,10 @@ bool PlayerbotAIConfig::Initialize()
useFastFlyMountAtMinLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.UseFastFlyMountAtMinLevel", 70); useFastFlyMountAtMinLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.UseFastFlyMountAtMinLevel", 70);
// stagger bot flightpath takeoff // stagger bot flightpath takeoff
delayMin = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotTaxiDelayMinMs", 350u); botTaxiDelayMin = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotTaxiDelayMinMs", 350);
delayMax = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotTaxiDelayMaxMs", 5000u); botTaxiDelayMax = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotTaxiDelayMaxMs", 5000);
gapMs = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotTaxiGapMs", 200u); botTaxiGapMs = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotTaxiGapMs", 200);
gapJitterMs = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotTaxiGapJitterMs", 100u); botTaxiGapJitterMs = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotTaxiGapJitterMs", 100);
LOG_INFO("server.loading", "Loading TalentSpecs..."); LOG_INFO("server.loading", "Loading TalentSpecs...");

View File

@ -424,10 +424,10 @@ public:
uint32 useFastFlyMountAtMinLevel; uint32 useFastFlyMountAtMinLevel;
// stagger flightpath takeoff // stagger flightpath takeoff
uint32 delayMin; uint32 botTaxiDelayMin;
uint32 delayMax; uint32 botTaxiDelayMax;
uint32 gapMs; uint32 botTaxiGapMs;
uint32 gapJitterMs; uint32 botTaxiGapJitterMs;
std::string const GetTimestampStr(); std::string const GetTimestampStr();
bool hasLog(std::string const fileName) bool hasLog(std::string const fileName)

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();
} }