Compare commits

..

19 Commits

Author SHA1 Message Date
Keleborn
a6a6af1b4d Merge pull request #2128 from mod-playerbots/test-staging
Master update from Test-staging: Fix ObjectAccessor retrieval, optimize EquipActions, and implement RaidBossHelpers
2026-02-13 09:16:49 -08:00
privatecore
610fdc16d7 Fix bug with GetCreature + GetGameObject = use ObjectAccessor's methods instead (#2105)
# Pull Request

https://en.cppreference.com/w/cpp/algorithm/equal_range.html
> second is an iterator to the first element of the range [first, last)
ordered after value (or last if no such element is found).

The original code uses `return bounds.second->second`, which causes the
wrong creature/gameobject to be returned. Instead, both methods
(`GetCreature` and `GetGameObject`) now utilize ObjectAccessor's methods
to retrieve the correct entities. These built-in methods offer a safer
way to access objects. Additionally, `GetUnit` no longer includes
redundant creature processing before checks and now has the same logic
as the `ObjectAccessor::GetUnit` method.

Furthermore, `GuidPosition::isDead` method has been renamed to
`GuidPosition::IsCreatureOrGOAccessible` and updated, as it is used only
for creatures (NOT units) and gameobjects.

---

## 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.

---

## How to Test the Changes

The behavior has not changed after all.

## 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**)

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.

---------

Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
2026-02-08 09:36:56 -08:00
dillyns
c9c936d5c1 Add Unending Breath to Warlock NonCombat Strat (#2074)
# Pull Request

Adds actions and triggers for Warlock class to cast Unending Breath when
swimming, following the existing implementation for Shaman Water
Breathing.

---

## Feature Evaluation

Add triggers for Warlock noncombat strategy for Unending Breath on self
and party.
Triggers should only be active while swimming.
Minimal runtime cost on Warlock bots trigger processing.

---

## How to Test the Changes

- Bring a Warlock bot into water
- It should cast Unending Breath on itself and anyone in the party

## Complexity & Impact

- Does this change add new decision branches?
    - [ ] No
    - [x] Yes (**explain below**)
It adds triggers to Warlock to decide when to cast Unending Breath on
self or party members.

- Does this change increase per-bot or per-tick processing?
    - [ ] No
    - [x] Yes (**describe and justify impact**)
Minimal additional processing for Warlock triggers, same as already
existing triggers for Shaman.

- 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?
    - [ ] No
    - [x] Yes (**explain below**)

Claude was used to explore the codebase to find similar implementations
that already existed.

---

## 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.

Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
2026-02-08 09:36:27 -08:00
kadeshar
cfb2ed4bf3 Merge pull request #2124 from kadeshar/oculus-drake-fix
Oculus drake mounting fix
2026-02-08 17:10:18 +01:00
Hokken
e9e79ad696 Fix LootRollLevel=1 to match documented 'greed' behavior (#2068)
## Summary

Fixes `AiPlayerbot.LootRollLevel = 1` to actually behave as "greed" mode
per the config documentation.

## Problem

The config documentation states:
```conf
# Bots' loot roll level (0 = pass, 1 = greed, 2 = need)
# Default: 1 (greed)
AiPlayerbot.LootRollLevel = 1
```

However, level 1 was converting **all GREED votes to PASS**, causing
bots to pass on almost everything:

| Item Type | AI Decision | Level 1 Behavior (Before) | Expected |
|-----------|-------------|---------------------------|----------|
| Gear upgrade | NEED | GREED ✓ | GREED |
| Usable gear (not upgrade) | GREED | **PASS** ✗ | GREED |
| Crafting materials | GREED | **PASS** ✗ | GREED |
| Recipes, consumables | GREED | **PASS** ✗ | GREED |

The only items bots would greed on were direct gear upgrades (originally
NEED, downgraded to GREED).

## Root Cause

In `LootRollAction.cpp`, lines 104-107 were converting GREED to PASS:

```cpp
else if (vote == GREED)
{
    vote = PASS;  // This breaks "greed" mode
}
```

## Fix

Remove the GREED→PASS conversion. Level 1 now only downgrades NEED to
GREED (as intended), preserving GREED votes for useful items.

## Behavior After Fix

| Level | Description | Behavior |
|-------|-------------|----------|
| 0 | Pass | Always pass on all items |
| 1 | Greed | Greed on useful items, never need |
| 2 | Need | Full AI logic (need/greed/pass) |

## Test Plan

- [ ] Set `AiPlayerbot.LootRollLevel = 1`
- [ ] Kill mobs that drop crafting materials, recipes, or non-upgrade
gear
- [ ] Verify bots greed on useful items instead of passing
- [ ] Verify bots still pass on junk items
- [ ] Verify bots never roll need (only greed)

Co-authored-by: Hokken <Hokken@users.noreply.github.com>
2026-02-08 12:45:03 +01:00
Keleborn
3db2a5a193 Refactor of EquipActions (#1994)
#PR Description 

The root cause of issue #1987 was the AI Value item usage becoming a
very expensive call when bots gained professions accidentally.

My original approach was to eliminate it entirely, but after inputs and
testing I decided to introduce a more focused Ai value "Item upgrade"
that only checks equipment and ammo inheriting directly from item usage,
so the logic is unified between them.

Upgrades are now only assessed when receiving an item that can be
equipped.

Additionally, I noticed that winning loot rolls did not trigger the
upgrade action, so I added a new package handler for that.


Performance needs to be re-evaluated, but I expect a reduction in calls
and in the cost of each call.

I tested with bots and selfbot in deadmines and ahadowfang keep.

---------

Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
2026-02-08 12:41:33 +01:00
Crow
8585f10f48 Implement Serpentshrine Cavern Strategies (#1888)
Edited: Below description of methods were brought up to date as of the
PR coming off of draft.

### General
I've starting leveraging, to the extent possible, an out-of-combat
method to erase map keys. This is mostly useful for timers that need to
start upon the pull because I dislike having to rely on a check for a
boss to be at 100% HP (or 99.9% or whatever) because it can be
unreliable sometimes.

### Trash
Underbog Colossi: Some Colossi leave behind a lake of toxin when they
die that quickly kills any player that is standing in it. The pool is a
dynamic-object-generated AoE, and bots will not avoid it on their own (I
think because the AoE is out of combat, plus the radius is much larger
than the default avoidance radius in the config). The method does not
require bots to be in combat, and simply gets bots to run out of the
toxin. You will probably still get a couple of idiots who drink in the
middle of it, but in my experience, the vast majority of the raid gets
out, and healers that escape can easily keep up a couple of fools until
they've drank to full.

Greyheart Tidecallers: Bots will mark and destroy Water Elemental Totems
immediately.

### Hydross the Unstable
The strategy uses 2 tanks, with the main tank assigned to the frost
phase and the 1st assistant tank assigned to the nature phase.

- The main tank will tank the frost phase, and the first assistant tank
will tank the nature phase. They each have designated spots and will
wait at their spots twiddling their thumbs while Hydross is in the other
phase.
- Hunters will misdirect to the applicable tank upon the pull and after
each phase change.
- The phase change process begins 1 second after Hydross reaches 100%
Marks. The current tank will begin moving to the next phase tank's spot
for the next tank to take over as soon as Hydross transitions.
- DPS is ordered to stop after Hydross reaches 100% Marks until 5
seconds after he transitions.
- Bots will prioritize the elementals adds after every phase change,
unless Hydross is under 10% HP, in which case they should ignore the
adds and burn the boss.
- Ranged bots should spread during the frost phase to mitigate the
impact of Water Tombs.

### The Lurker Below

- There is a designated spot for the main tank.
- Ranged DPS will fan out over a 120-degree arc that is centered
directly across from the tank spot (to try to spread to reduce Geyser
damage while also keeping them behind Lurker).
- When Spout begins, all bots will run around behind Lurker. The intent
is to keep a distance with a radius of 20 or 21 yards and within 45
degrees (either side) of directly behind him. Movement is specifically
tangential along an arc so bots don't run in front of Lurker.
- Spout's duration is tracked by a timer. The mechanics of the spell
itself are rather unique and don't involve a continuous cast or aura to
track easily so I settled for the timer.
- If you have 3 (or more) tanks, each of the first 3 tanks will be
assigned to one of the 3 Coilfang Guardians during the submerge phase.

### Leotheras the Blind
The fight is designed for a Warlock tank. You can choose the Warlock
tank by giving a Warlock the Assistant flag. If you don't do that, your
highest HP Warlock will be picked. Do NOT switch the Warlock tank to a
co +tank strategy--the designated Warlock is hardcoded to spam Searing
Pain on Demon Leo and otherwise will engage in normal DPS strategies. If
you don't have a Warlock at all, the strategy has some methods built in
to try to make things work as best as possible with a melee tank.

- The Spellbinders get marked with skulls and killed in order.
- There is no designated spot or designated tank for the human phase.
Your tanks will fight for aggro. Ranged bots will attempt to keep some
distance, and when Whirlwind starts, everybody will run away from
Leotheras.
- During the demon phase, your melee tanks should take a backseat to
your Warlock tank, who will receive help in the form of Misdirection.
Bots will get the hell away from the Warlock tank so the Warlock tank
should be taking every Chaos Blast alone.
- During the final phase, your regular tanks will tank Leotheras, and
the Warlock tank will tank his Shadow. The melee tanks will attempt to
separate Leotheras from his Shadow so bots can focus down Leotheras
without getting hit with Chaos Blasts.
- Bots will wait 5 seconds to DPS after every transition into human
phase, 12 seconds to DPS after every transition into demon phase, and 8
seconds to DPS after the transition into the final phase. There is no
waiting on DPS after Whirlwinds, even though it would be ideal. It's not
a big deal to live without, and for various reasons, it would have been
a pain in the ass to deal with.
- Bots will save Bloodlust/Heroism until after Spellbinders are down.
- To deal with the Inner Demons, I disabled DPS assist for bots who are
targeted and force them to focus only on their Inner Demons. This is
sufficient in my experience for all DPS bots and Protection Warriors and
Paladins to kill their Inner Demons, even at 50% damage. Feral Tank
Druids and Healers still need help, so the strategy hardcodes their
actions while fighting Inner Demons. For example, Resto Druids are coded
to shift out of Tree Form, cast Barkskin on themselves, and just spam
Wrath until the Inner Demon is dead. There are no bot strategy changes
used for this method.

### Fathom-Lord Karathress
You will need 4 tanks. Your main tank will tank Karathress, and an
assistant tank will tank each Fathom Guard. If you have fewer than 4
tanks, then the priority order for tank assignment will be Karathress,
Caribdis, Sharkkis, and then Tidalvess.

- Roughly, the tank spots are (1) for Karathress, near where he starts
but closer to the ledge for LoS reasons, (2) for Sharkkis, North from
his starting location on the other side of the ramp, (3) for Tidalvess,
Northwest from his starting location near the pillar, and (4) for
Caribdis, far to the West of her starting position, near the corner.
- Note that the tanks will probably clip through the terrain a bit when
going to their positions. This is due to me implementing a forced MoveTo
to the tank position coordinates. There is something weird about the
maps in Karathress's room, and the tanks will take some really screwed
up paths without making them go directly to the exact coordinates. So
this looks stupid but is necessary.
- One healer will be assigned to heal the Caribdis tank. Because AC
Playerbots does not yet have a focus heal strategy, this just means that
such healer has a designated location near the Caribdis tank's location.
This healer can be selected with the Assistant flag.
- Hunters will misdirect the Fathom Guards onto their applicable tanks.
If you don't have three Hunters, the priority is Caribdis, Tidalvess,
then Sharkkis.
- DPS will wait 12 seconds to begin attacking. After that, they will
prioritize targets as follows:
- (1): Melee will always prioritize Spitfire Totems as soon as they
spawn. This will continue through the duration of the fight.
- (2): All bots will kill Tidalvess first.
- (3): Melee bots will move to Sharkkis, and ranged bots will move to
Caribdis. I understand this is not the standard kill order for players,
which would have the entire raid kill Sharkkis next. The reasons I have
done this differently are because melee DPS is much stronger with 3.3.5
talents vs. in retail TBC, and because bots get really thrown off by
Cyclones and therefore they struggle to kill Caribdis quickly. You do
not want Karathress below 75% HP before all Fathom-Guards are dead or he
gets a huge damage buff.
- (4) If Caribdis dies first, ranged bots will help with Sharkkis.
- (5) Everybody kills Sharkkis's pet.
- (6) Everybody kills Karathress.

### Morogrim Tidewalker

- The main tank will pull the boss to the Northeast pillar, with the
tank's back against the pillar.
- A hunter will misdirect the boss onto the main tank upon the pull.
- When the boss gets to 26% HP, the main tank will begin moving the boss
to the Northeast corner of the room in preparation for Phase 2 (which
begins at 25%). The tank will move in two steps to get around the
pillar.
- When the boss gets to 25% HP, ranged will follow the main tank to the
corner and stack up right behind the boss. They will also move in two
steps.
- There is no method for melee since they will just naturally follow the
boss anyway.

### Lady Vashj

**Phase 1**:
- The main tank will tank Vashj in the center of the arena.
- If a Shaman is in the main tank's group, that Shaman will attempt to
keep a Grounding Totem down in range of the main tank to absorb Shock
Blast. This should continue in Phase 3.
- Ranged bots will spread out in a semicircle around the center of the
arena.
- If any bot other than the main tank gets Static Charge, it will run
away from other bots. If the main tank gets Static Charge, other bots
will run away from the main tank. This method should continue in Phase
3.
- If any bot is Entangled and has Static Charge, the bot will attempt to
use Cloak of Shadows if it is a Rogue, and Paladins will attempt to use
Hand of Freedom. This method should continue in Phase 3 (with some
modifications).
- Bots will not use Bloodlust or Heroism (saved for Phase 3). Bots will
not use any other major cooldowns, either, such as Metamorphosis (saved
for Phase 2 and 3).

**Phase 2**:
There are two central mechanics to this phase, both of which were
challenging to get bots to execute properly. First is the system of
prioritizing adds. The large playing field and multiple types of adds
coming from random directions make this phase not doable with realistic
DPS under the standard Playerbots target selection system. Therefore, I
took inspiration from liyunfan's Naxx strategy for Phase 1 of Kel'Thuzad
to disable dps assist and create a custom target selection system.

First, a cheat with respect to the Coilfang Striders: 
- Tanks will permanently have the Fear Ward aura applied to them if you
have raid cheats enabled. This allows them to tank the Coilfang
Striders. The standard strategy was to have an Elemental Shaman kite the
Strider around the perimeter of the arena, with ranged players
(including healers) spamming DoTs on the Strider. If you can make bots
do this, then great, but it's far beyond my capabilities. Therefore,
with the cheat, the first assistant tank is responsible for tanking
Striders and keeping them away from Core passers (described below) and
Vashj. Evidently it was (and is, in TBC Classic) possible to tank (and
melee DPS) Striders by wearing a Dire Maul Ogre Suit, which would give
you enough reach to stay out of the Strider's fear. I actually tried
that, and it does not work, either because AC's radiuses are not the
same or just because bots do not maintain the same level of precise
positioning. But anyway, the point is that technically the Striders are
tankable by real players, so maybe that will make you feel better about
using this cheat (it's fine enough rationalizing for me). I found this
fight to be unmanageable without this cheat (i.e., using a method that
would only have bots try to run away from Striders) because each Strider
was guaranteed to wipe out a couple of bots, and you really cannot
afford to lose anyone. YMMV though.
- If cheats are enabled for Striders, Hunters will attempt to Misdirect
the Striders to the first assist tank.
- If cheats are not enabled, bots will attempt to use slows/roots to
stop the Striders. I have some logic for them to use Netherweave Nets,
but I suspect it does not actually work so I may remove it instead of
trying to get it to function properly.

Target priority is as follows:
- Hunters and Mages: Enchanted Elementals, Coilfang Striders, Coilfang
Elites.
- Other Ranged Bots: Elites, Striders, Elementals. 
- Melee DPS: Elementals, Elites.
- Tanks: Elites, Elementals (except if cheats are enabled, the first
assistant tank will instead prioritize Striders and then Elementals)
- Everybody else (basically means healers): Elementals, Elites, Striders
- If there is more than one of the same target, bots will prioritize the
one that is closer to Vashj.
- In all cases, the valid attack ranged is limited so that bots should
not leave the central platform.
- If somehow a bot ends up too far from the center of the room and is
not actively attacking anything, there is logic to make them run back.

Handling Tainted Elementals and the Tainted Core: I will make another
post about this later. It is easily the most complicated strategy I've
ever worked on (far beyond anything on Kael'thas even) so will
necessitate a long explanation. The tl;dr is that there is a chain of
two-to-four bots that receive/pass the Tainted Core before using it on a
Shield Generator, and if you are playing by yourself, you probably need
to turn raid cheats on, in which case there will also be a bot that
teleports to, kills, and loots the Tainted Elementals (i.e., the bots
will then handle the entire sequence of shutting down Shield
Generators).

**Phase 3**:
- The main tank will pick up Vashj immediately and try to keep her away
from Enchanted Elementals.
- DPS will burn down residual adds from Phase 2 in the order of (1)
elementals, (2) strider for ranged only (if you have more than one up,
you're dead), and (3) elites (hopefully you have only one up, but two
with one almost dead is possible).
- Hunters will kill Toxic Sporebats. This works quite well, but they
(and anybody else if ordered to target Sporebats) have a tendency to
levitate up into the pipes at the top of the room when killing the
Sporebats. To counteract this, a method forcibly teleports bots to the
ground if they get more than 2 yards above the ground.
- The Phase 1 Cloak of Shadows/Hand of Freedom method is now expanded to
include bots Entangled in the Sporebat poison pools (with Hand of
Freedom usage prioritized on the main tank).
- There is a specific method to avoid the Sporebat poison pools. The
Vashj tank will move backwards when avoiding poison.

---------

Co-authored-by: kadeshar <kadeshar@gmail.com>
2026-02-08 12:31:23 +01:00
kadeshar
79fb3a5bbc - Fixed Oculus drake mounting 2026-02-07 17:53:55 +01:00
kadeshar
6ed3f24ecb Enforce test fix (#2122)
CI/CD PR

---------

Co-authored-by: Crow <pengchengw@me.com>
2026-02-07 07:34:15 -08:00
Alex Dcnh
76b6df9ea3 Extend SummonWhenGroup to auto-added bots (#2034)
### Summary
Extend AiPlayerbot.SummonWhenGroup to apply when bots are auto-added to
a group (e.g., addclass bots or raidus style auto invites).

### Motivation
Bots added automatically to a group never accept a normal invite, so
they do not trigger the summon-on-accept path. When SummonWhenGroup is
enabled, these bots should also be teleported next to the master to
match expected behavior.

### Implementation details
Hook the summon behavior right after automatic group addition.
2026-02-06 14:20:31 -08:00
kadeshar
026df0dabe Chilton wand fix (#2115)
# Pull Request

Added Chilton wand to excluded to equipment items for bots and unified 2
exclusion lists to single one.
Resolves: https://github.com/mod-playerbots/mod-playerbots/issues/2093

---

## How to Test the Changes

Couldnt reproduce Chilton wand bug then testing sound impossible.
Someone can try getting this items on shaman.

## Complexity & Impact

Does this change add new decision branches?
- - [x] No
- - [ ] Yes

Does this change increase per-bot or per-tick processing?
- - [x] No
- - [ ] Yes

Could this logic scale poorly under load?
- - [x] No
- - [ ] Yes
---

## Defaults & Configuration

Does this change modify default bot behavior?
- - [x] No
- - [ ] Yes

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
---

## 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

---
2026-02-06 11:57:48 -08:00
Crow
b31bda85ee Refactor raid strategy framework (#2069)
# Pull Request

The purposes of this PR are to (1) establish a general raid helper
framework for the benefit of future raid strategies and (2) make some
improvements to problematic areas of the raid strategy code.

List of changes:
1. Added new RaidBossHelpers.cpp and RaidBossHelpers.h files in the Raid
folder.

3. Moved reused helpers from Karazhan, Gruul, and Magtheridon strategies
to the new helper files.
4. Modified the prior function that assigned a DPS bot to store and
erase timers and trackers in associative containers--the function now
includes parameters for mapId (so a bot that is not in the instance will
not be assigned) and for the ability to exclude a bot (useful for
excluding particular important roles, such as a Warlock tank, so they
are not bogged down by these extra tasks at critical moments). I also
renamed it from IsInstanceTimerManager to IsMechanicTrackerBot.
5. Moved all helper files in raid strategies to Util folders (was needed
for ICC, MC, and Ulduar).
6. Renamed and reordered includes of Ulduar files in AiObjectContext.cpp
to match other raid strategies.
a. This initially caused compile errors which made me realize that the
existing code had several problems with missing includes and was
compiling only due to the prior ordering in AiObjectContext.cpp.
Therefore, I added the missing includes to Molten Core, Ulduar, and
Vault of Archavon strategies.
b. Ulduar and Old Kingdom were also using the same constant name for a
spell--the reordering caused a compile error here as well, which just
highlighted an existing problem that was being hidden. I renamed the
constant for Ulduar to fix this, but I think the better approach going
forward would be to use a namespace or enum class. But that is for
another time and probably another person.
7. Several changes with respect to Ulduar files:
a. The position constants and enums for spells and NPCs and such were in
the trigger header file. I did not think that made sense so moved them
to existing helper files.
b. Since the strategy does not use multipliers, I removed all files and
references to multipliers in it.
c. I removed some unneeded includes. I did not do a detailed review to
determine what else could be removed--I just took some out that I could
tell right away were not needed.
d. I renamed the ingame strategy name from "uld" to "ulduar," which I
think is clearer and is still plenty short.
8. Partial refactor of Gruul and Magtheridon strategies:
a. I did not due a full refactoring but made some quick changes to
things I did previously that were rather stupid like repeating
calculations, having useless logic like pointless IsAlive() checks for
creatures already on the hostile references list, and not using the
existing Position class for coordinates.
b. There were a few substantive changes, such as allowing players to
pick Maulgar mage and moonkin tanks with the assistant flag, but a
greater refactoring of the strategies themselves is beyond this PR.
c. I was clearing some containers used for Gruul and Magtheridon
strategies; the methods are now fixed to erase only the applicable keys
so that in the unlikely event that one server has multiple groups
running Gruul or Magtheridon at the same time, there won't be timer or
position tracker conflicts.

## How to Test the Changes

1. Enter any raid instance that has any code impacted by this PR
2. Engage bosses and observe if any strategies are now broken

I personally tested Maulgar, Gruul, and Magtheridon and confirmed that
they still work as intended.

## Complexity & Impact

I do not expect this PR to have any relevant changes to in-game
performance, but I will defer to those more knowledgeable than I if
there are concerns in this area. As I've mentioned before, you can
consider me to be like a person who has taken half an intro C++ course
at best.

## AI Assistance

None beyond autocomplete of repetitive changes.

---------

Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
2026-02-06 11:55:43 -08:00
kadeshar
bebac60c51 test-staging alignment (#2121)
# Pull Request

Describe what this change does and why it is needed...

---

## 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?
- - [ ] No
- - [ ] Yes (**explain below**)

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

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

## Defaults & Configuration

Does this change modify default bot behavior?
- - [ ] 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?
- - [ ] 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

- - [ ] Stability is not compromised
- - [ ] Performance impact is understood, tested, and acceptable
- - [ ] Added logic complexity is justified and explained
- - [ ] 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.

---------

Co-authored-by: Crow <pengchengw@me.com>
2026-02-06 11:55:28 -08:00
Crow
52d4191b43 Correct Zone ID 10 in config (Deadwind Pass -> Duskwood) (#2109)
Simple fix to config--zone ID 10 is Duskwood, not Deadwind Pass, as pointed out by @privatecore
2026-02-04 16:01:18 -08:00
kadeshar
254055ff32 Workaround for checkboxes without task (#2116)
# Pull Request

Describe what this change does and why it is needed...

---

## 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?
```
[ ] No
[ ] Yes (**explain below**)
```

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

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

## Defaults & Configuration

Does this change modify default bot behavior?
```
[ ] 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?
```
[ ] 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

- [ ] Stability is not compromised
- [ ] Performance impact is understood, tested, and acceptable
- [ ] Added logic complexity is justified and explained
- [ ] 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-03 23:08:53 +01:00
bashermens
31765c77fa Update PULL_REQUEST_TEMPLATE.md (#2114) 2026-02-03 21:48:58 +01:00
Thomas Frey
c86032f43b Convert PlayerBots tables to InnoDB (#2083)
Convert PlayerBots tables to InnoDB (disable strict mode during
conversion)

# Pull Request

### This change converts the PlayerBots-related tables from MyISAM to
InnoDB.

**Why this is beneficial (even without fixing a specific bug):**

- Crash safety & data integrity: InnoDB is transactional and uses redo
logs; it provides automatic crash recovery, unlike MyISAM which can
require manual repairs after unclean shutdowns.
- Row-level locking: InnoDB reduces write contention and improves
concurrency under bot-heavy workloads compared to MyISAM’s table-level
locks.
- Consistent reads: InnoDB supports MVCC, enabling stable reads while
writes are happening—useful for mixed read/write access patterns.
- Operational robustness: Better behavior under backup/restore and
replication scenarios; fewer “table marked as crashed” style issues.

Strict mode handling:
The migration toggles innodb_strict_mode off only for the session to
prevent the conversion from failing on edge-case legacy definitions,
then re-enables it immediately after.

---

## How to Test the Changes

- Step-by-step instructions to test the change
Run the SQL script in the Playerbot database.
- Any required setup (e.g. multiple players, bots, specific
configuration)
No
- Expected behavior and how to verify it
All tables should now have been converted from InnoDB to MyISAM.
This script should return nothing:

```
SELECT
    t.TABLE_SCHEMA AS db_name,
    t.TABLE_NAME   AS table_name,
    t.ENGINE       AS storage_engine
FROM information_schema.TABLES t
WHERE t.TABLE_SCHEMA = DATABASE()
-- With phpMyAdmin, use the following and insert your database name, e.g., “acore_playerbots.”
-- WHERE t.TABLE_SCHEMA = 'YOUR_PLAYERBOT_DB'
  AND t.TABLE_TYPE = 'BASE TABLE'
  AND t.ENGINE = 'MyISAM'
ORDER BY t.TABLE_NAME;
```

## 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**)

---

## 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
- [ ] Documentation updated if needed
- [x] I tested this script on a server with 2000 bots for 6 days
(running 24/h) and had no issues with it.

---


## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.
2026-02-02 22:42:02 +01:00
Alex Dcnh
ba835250c8 New whisper command "pvp stats" that allows players to ask a bot to report its current Arena Points, Honor Points, and Arena Teams (#2071)
# Pull Request

This PR adds a new whisper command "pvp stats" that allows players to
ask a bot to report its current Arena Points, Honor Points, and Arena
Teams (name and team rating).

Reason:
Due to a client limitation in WoW 3.3.5a, the inspection window does not
display another player's Arena or Honor points , only team data.
This command provides an easy in-game way to check a bot’s PvP
currencies without modifying the client or core packets.

---

## Design Philosophy

Uses existing core getters (GetArenaPoints, GetHonorPoints,
GetArenaTeamId, etc.).
Fully integrated into the chat command system (ChatTriggerContext,
ChatActionContext).
Safe, no gameplay changes, purely informational.
No harcoded texts, use database local instead

---

## How to Test the Changes

/w BotName pvp stats

Bot reply:

[PVP] Arena Points: 302 | Honor Points: 11855
[PVP] 2v2: <The Fighters> (rating 2000)
[PVP] 3v3: <The Trio> (rating 573)

## 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**)

---

## 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

---

Multibot already ready

Here is a sample of multibot when merged:
<img width="706" height="737" alt="image"
src="https://github.com/user-attachments/assets/5bcdd9f8-e2fc-4c29-a497-9fffba5dfd4e"
/>

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.

---------

Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
2026-02-02 22:40:12 +01:00
bashermens
8c2a27b9fe Update check_pr_source.yml (#2101) 2026-02-01 22:41:49 +01:00
94 changed files with 8992 additions and 1395 deletions

View File

@@ -1,13 +1,15 @@
name: Enforce test-staging → main name: Enforce test-staging → master
on: on:
pull_request: pull_request:
branches: branches:
- main - master
- test-staging
jobs: jobs:
require-test-staging: require-test-staging:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
if: github.event.pull_request.base.ref == 'master'
steps: steps:
- name: Ensure PR source is test-staging - name: Ensure PR source is test-staging
run: | run: |

View File

@@ -2,9 +2,9 @@ name: Codestyle
on: on:
push: push:
branches: [ "master" ] branches: [ "master", "test-staging" ]
pull_request: pull_request:
branches: [ "master" ] branches: [ "master", "test-staging" ]
concurrency: concurrency:
group: "codestyle-${{ github.event.pull_request.number }}" group: "codestyle-${{ github.event.pull_request.number }}"

View File

@@ -2,9 +2,9 @@ name: ubuntu-build
on: on:
push: push:
branches: [ "master" ] branches: [ "master", "test-staging" ]
pull_request: pull_request:
branches: [ "master" ] branches: [ "master", "test-staging" ]
concurrency: concurrency:
group: "core-build-${{ github.event.pull_request.number }}" group: "core-build-${{ github.event.pull_request.number }}"

View File

@@ -1,9 +1,9 @@
name: macos-build name: macos-build
on: on:
push: push:
branches: [ "master" ] branches: [ "master", "test-staging" ]
pull_request: pull_request:
branches: [ "master" ] branches: [ "master", "test-staging" ]
concurrency: concurrency:
group: "macos-build-${{ github.event.pull_request.number }}" group: "macos-build-${{ github.event.pull_request.number }}"

View File

@@ -1,9 +1,9 @@
name: windows-build name: windows-build
on: on:
push: push:
branches: [ "master" ] branches: [ "master", "test-staging" ]
pull_request: pull_request:
branches: [ "master" ] branches: [ "master", "test-staging" ]
concurrency: concurrency:
group: "windows-build-${{ github.event.pull_request.number }}" group: "windows-build-${{ github.event.pull_request.number }}"

View File

@@ -66,38 +66,35 @@ Please answer the following:
## Complexity & Impact ## Complexity & Impact
- Does this change add new decision branches? Does this change add new decision branches?
- [ ] No - - [ ] No
- [ ] Yes (**explain below**) - - [ ] Yes (**explain below**)
- Does this change increase per-bot or per-tick processing? Does this change increase per-bot or per-tick processing?
- [ ] No - - [ ] No
- [ ] Yes (**describe and justify impact**) - - [ ] Yes (**describe and justify impact**)
- Could this logic scale poorly under load?
- [ ] No
- [ ] Yes (**explain why**)
Could this logic scale poorly under load?
- - [ ] No
- - [ ] Yes (**explain why**)
--- ---
## Defaults & Configuration ## Defaults & Configuration
- Does this change modify default bot behavior? Does this change modify default bot behavior?
- [ ] No - - [ ] No
- [ ] Yes (**explain why**) - - [ ] Yes (**explain why**)
If this introduces more advanced or AI-heavy logic: If this introduces more advanced or AI-heavy logic:
- - [ ] Lightweight mode remains the default
- [ ] Lightweight mode remains the default - - [ ] More complex behavior is optional and thereby configurable
- [ ] More complex behavior is optional and thereby configurable
--- ---
## AI Assistance ## AI Assistance
- Was AI assistance (e.g. ChatGPT or similar tools) used while working on this change? Was AI assistance (e.g. ChatGPT or similar tools) used while working on this change?
- [ ] No - - [ ] No
- [ ] Yes (**explain below**) - - [ ] Yes (**explain below**)
If yes, please specify: If yes, please specify:
@@ -114,10 +111,10 @@ about what they do and do not understand.
## Final Checklist ## Final Checklist
- [ ] Stability is not compromised - - [ ] Stability is not compromised
- [ ] Performance impact is understood, tested, and acceptable - - [ ] Performance impact is understood, tested, and acceptable
- [ ] Added logic complexity is justified and explained - - [ ] Added logic complexity is justified and explained
- [ ] Documentation updated if needed - - [ ] Documentation updated if needed
--- ---

71
README_CN.md Normal file
View File

@@ -0,0 +1,71 @@
[English](README.md) | [Español](README_ES.md) | [中文](README_CN.md)
# 玩家机器人模块
欢迎使用AzerothCore的玩家机器人模块这是一个基于IKE3玩家机器人的正在进行中的项目。这些玩家机器人利用实际的玩家数据使您能够与您自己的替身进行交互组建队伍升级角色等等。
如果您遇到任何错误或出现崩溃请您将它们报告为GitHub问题。您宝贵的反馈将帮助我们协作改进和增强这个项目。
## 安装
请注意此模块需要对AzerothCore进行特定的自定义更改。为了确保兼容性您必须使用我fork的自定义分支来编译它可以在这里找到[mod-playerbots/azerothcore-wotlk/tree/Playerbot](https://github.com/mod-playerbots/azerothcore-wotlk/tree/Playerbot)。
要安装此模块请参考AzerothCore Wiki的详细说明[AzerothCore安装指南](https://www.azerothcore.org/wiki/installation)。
我们提供了一个简单的方法来克隆该模块:
```bash
git clone https://github.com/mod-playerbots/azerothcore-wotlk.git --branch=Playerbot
cd azerothcore-wotlk/modules
git clone https://github.com/mod-playerbots/mod-playerbots.git --branch=master
```
## 快速开始与文档
要快速开始并了解一系列命令您可以参考ike3原版playerbots的手册。该模块提供了大部分基本命令。您可以在此找到文档[IKE3 Playerbots 文档](https://ike3.github.io/mangosbot-docs/)。请注意,在我们的模块中,您需要将文档中所有的 `.bot` 替换为 `.playerbot bot`
请注意,由于项目仍在开发中,新添加的命令的文档目前尚不完善。
## 进展
该模块主要强调以下关键功能,并在这些领域实施了改进:
- **世界中的机器人(随机机器人):** 我们增强了随机机器人的行为,使它们更接近真实玩家的表现,从而创建了更真实的玩家服务器环境。
- **团队副本中的机器人:** 我们赋予机器人征服具有挑战性的团队副本内容的能力通过为各种Boss实施特定策略使团队副本更加吸引人。此外我们增强了机器人在DPS、治疗和坦克等各种角色中的能力确保它们有效地为团队的成功做出贡献。
- **战场中的机器人:** 机器人现在能够与真实玩家一起积极参与战场为这些PvP场景增添了深度和刺激。
- **与机器人的交互:** 我们改进了真实玩家和机器人之间的交互,使玩家能够在与机器人伙伴合作时完成任务并升级多个角色。
- **玩家进阶路径:** 我们设计了一个改进的玩家进阶路径,辅以机器人,为玩家提供了一种替代且引人入胜的游戏体验。
- **稳定性:** 我们的努力主要集中在增强使用Playerbots模块时AzerothCore的整体稳定性。这些改进旨在防止服务器崩溃并确保所有用户都能获得更流畅的体验。
- **配置:** 我们引入了一系列可配置的选项,以满足不同需求的玩家,从而提供更个性化的体验。
值得注意的是,随着我们继续改进项目,还有大量工作需要完成。我们欢迎每个人以不同的方式做出贡献。
## 插件
为了更好地控制机器人并简化命令的使用,您还可以使用我们的插件:[Unbot Addon](https://github.com/liyunfan1223/unbot-addon)。目前,该插件仅对简体中文客户端提供更好的支持。
## 常见问题
**机器人无法释放技能**
- 请确保必要的英文DBC文件enUS存在。
**编译错误**
- 我们支持Ubuntu、Windows和macOS。
- 我们建立了持续集成工作流。您可以在[GitHub Actions](https://github.com/mod-playerbots/mod-playerbots/actions)中查看构建状态。
- 如果最新的构建状态失败,请恢复到上一个提交。我们将尽快解决此问题。
## 致谢
该模块的代码来自[ZhengPeiRu21/mod-playerbots](https://github.com/ZhengPeiRu21/mod-playerbots)和[celguar/mangosbot-bots](https://github.com/celguar/mangosbot-bots)。我们衷心感谢@ZhengPeiRu21和@celguar对维护该模块的持续努力
我们还要向所有为playerbot开发做出贡献的个人表示诚挚的感谢。您的奉献和努力对塑造这个项目至关重要我们对您的贡献表示感谢。

View File

@@ -558,7 +558,7 @@ AiPlayerbot.AutoGearScoreLimit = 0
# "mana" (bots have infinite mana) # "mana" (bots have infinite mana)
# "power" (bots have infinite energy, rage, and runic power) # "power" (bots have infinite energy, rage, and runic power)
# "taxi" (bots may use all flight paths, though they will not actually learn them) # "taxi" (bots may use all flight paths, though they will not actually learn them)
# "raid" (bots use cheats implemented into raid strategies (currently only for Ulduar)) # "raid" (bots use cheats implemented into raid strategies (currently only for SSC and Ulduar))
# To use multiple cheats, separate them by commas below (e.g., to enable all, use "gold,health,mana,power,raid,taxi") # To use multiple cheats, separate them by commas below (e.g., to enable all, use "gold,health,mana,power,raid,taxi")
# Default: food, taxi, and raid are enabled # Default: food, taxi, and raid are enabled
AiPlayerbot.BotCheats = "food,taxi,raid" AiPlayerbot.BotCheats = "food,taxi,raid"
@@ -990,7 +990,7 @@ AiPlayerbot.ZoneBracket.3433 = 10,22
AiPlayerbot.ZoneBracket.3525 = 10,21 AiPlayerbot.ZoneBracket.3525 = 10,21
# Classic WoW - High-level zones: # Classic WoW - High-level zones:
# Deadwind Pass (Zone ID: 10 Default Min,Max: 19,33) # Duskwood (Zone ID: 10 Default Min,Max: 19,33)
# Wetlands (Zone ID: 11 Default Min,Max: 21,30) # Wetlands (Zone ID: 11 Default Min,Max: 21,30)
# Redridge Mountains (Zone ID: 44 Default Min,Max: 16,28) # Redridge Mountains (Zone ID: 44 Default Min,Max: 16,28)
# Hillsbrad Foothills (Zone ID: 267 Default Min,Max: 20,34) # Hillsbrad Foothills (Zone ID: 267 Default Min,Max: 20,34)

View File

@@ -0,0 +1,9 @@
-- Temporarily disables innodb_strict_mode for the session to allow the script to complete even if legacy table definitions contain InnoDB-incompatible attributes
SET SESSION innodb_strict_mode = 0;
-- Change the tables to InnoDB
ALTER TABLE playerbots_guild_names ENGINE=InnoDB;
ALTER TABLE playerbots_names ENGINE=InnoDB;
-- Re-enables innodb_strict_mode
SET SESSION innodb_strict_mode = 1;

View File

@@ -0,0 +1,18 @@
-- Temporarily disables innodb_strict_mode for the session to allow the script to complete even if legacy table definitions contain InnoDB-incompatible attributes
SET SESSION innodb_strict_mode = 0;
-- Change the tables to InnoDB
ALTER TABLE playerbots_dungeon_suggestion_abbrevation ENGINE=InnoDB;
ALTER TABLE playerbots_dungeon_suggestion_definition ENGINE=InnoDB;
ALTER TABLE playerbots_dungeon_suggestion_strategy ENGINE=InnoDB;
ALTER TABLE playerbots_equip_cache ENGINE=InnoDB;
ALTER TABLE playerbots_item_info_cache ENGINE=InnoDB;
ALTER TABLE playerbots_rarity_cache ENGINE=InnoDB;
ALTER TABLE playerbots_rnditem_cache ENGINE=InnoDB;
ALTER TABLE playerbots_tele_cache ENGINE=InnoDB;
ALTER TABLE playerbots_travelnode ENGINE=InnoDB;
ALTER TABLE playerbots_travelnode_link ENGINE=InnoDB;
ALTER TABLE playerbots_travelnode_path ENGINE=InnoDB;
-- Re-enables innodb_strict_mode
SET SESSION innodb_strict_mode = 1;

View File

@@ -0,0 +1,101 @@
-- #########################################################
-- Playerbots - Add PVP / Arena texts for TellPvpAction
-- Localized for all WotLK locales (koKR, frFR, deDE, zhCN,
-- zhTW, esES, esMX, ruRU)
-- #########################################################
-- ---------------------------------------------------------
-- pvp_currency
-- [PVP] Arena points: %arena_points | Honor Points: %honor_points
-- ---------------------------------------------------------
INSERT INTO `ai_playerbot_texts`
(`name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
SELECT
'pvp_currency',
'[PVP] Arena points: %arena_points | Honor Points: %honor_points',
0, 0,
-- koKR
'[PVP] 투기장 점수: %arena_points | 명예 점수: %honor_points',
-- frFR
'[PVP] Points d''arène : %arena_points | Points d''honneur : %honor_points',
-- deDE
'[PVP] Arenapunkte: %arena_points | Ehrenpunkte: %honor_points',
-- zhCN
'[PVP] 竞技场点数:%arena_points | 荣誉点数:%honor_points',
-- zhTW
'[PVP] 競技場點數:%arena_points | 榮譽點數:%honor_points',
-- esES
'[PVP] Puntos de arena: %arena_points | Puntos de honor: %honor_points',
-- esMX
'[PVP] Puntos de arena: %arena_points | Puntos de honor: %honor_points',
-- ruRU
'[PVP] Очки арены: %arena_points | Очки чести: %honor_points'
WHERE NOT EXISTS (
SELECT 1 FROM `ai_playerbot_texts` WHERE `name` = 'pvp_currency'
);
-- ---------------------------------------------------------
-- pvp_arena_team
-- [PVP] %bracket: <%team_name> (rating %team_rating)
-- ---------------------------------------------------------
INSERT INTO `ai_playerbot_texts`
(`name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
SELECT
'pvp_arena_team',
'[PVP] %bracket: <%team_name> (rating %team_rating)',
0, 0,
-- koKR
'[PVP] %bracket: <%team_name> (평점 %team_rating)',
-- frFR
'[PVP] %bracket : <%team_name> (cote %team_rating)',
-- deDE
'[PVP] %bracket: <%team_name> (Wertung %team_rating)',
-- zhCN
'[PVP] %bracket: <%team_name> (评分 %team_rating)',
-- zhTW
'[PVP] %bracket: <%team_name> (評分 %team_rating)',
-- esES
'[PVP] %bracket: <%team_name> (índice %team_rating)',
-- esMX
'[PVP] %bracket: <%team_name> (índice %team_rating)',
-- ruRU
'[PVP] %bracket: <%team_name> (рейтинг %team_rating)'
WHERE NOT EXISTS (
SELECT 1 FROM `ai_playerbot_texts` WHERE `name` = 'pvp_arena_team'
);
-- ---------------------------------------------------------
-- pvp_no_arena_team
-- [PVP] I have no Arena Team.
-- ---------------------------------------------------------
INSERT INTO `ai_playerbot_texts`
(`name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
SELECT
'pvp_no_arena_team',
'[PVP] I have no Arena Team.',
0, 0,
-- koKR
'[PVP] 투기장 팀이 없습니다.',
-- frFR
'[PVP] Je n''ai aucune équipe d''arène.',
-- deDE
'[PVP] Ich habe kein Arenateam.',
-- zhCN
'[PVP] 我没有竞技场战队。',
-- zhTW
'[PVP] 我沒有競技場隊伍。',
-- esES
'[PVP] No tengo equipo de arena.',
-- esMX
'[PVP] No tengo equipo de arena.',
-- ruRU
'[PVP] У меня нет команды арены.'
WHERE NOT EXISTS (
SELECT 1 FROM `ai_playerbot_texts` WHERE `name` = 'pvp_no_arena_team'
);

View File

@@ -328,7 +328,43 @@ void EquipAction::EquipItem(Item* item)
botAI->TellMaster(out); botAI->TellMaster(out);
} }
bool EquipUpgradesAction::Execute(Event event) ItemIds EquipAction::SelectInventoryItemsToEquip()
{
CollectItemsVisitor visitor;
IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS);
ItemIds items;
for (auto i = visitor.items.begin(); i != visitor.items.end(); ++i)
{
Item* item = *i;
if (!item)
continue;
ItemTemplate const* itemTemplate = item->GetTemplate();
if (!itemTemplate)
continue;
//TODO Expand to Glyphs and Gems, that can be placed in equipment
//Pre-filter non-equipable items
if (itemTemplate->InventoryType == INVTYPE_NON_EQUIP)
continue;
int32 randomProperty = item->GetItemRandomPropertyId();
uint32 itemId = item->GetTemplate()->ItemId;
std::string itemUsageParam;
if (randomProperty != 0)
itemUsageParam = std::to_string(itemId) + "," + std::to_string(randomProperty);
else
itemUsageParam = std::to_string(itemId);
ItemUsage usage = AI_VALUE2(ItemUsage, "item upgrade", itemUsageParam);
if (usage == ITEM_USAGE_EQUIP || usage == ITEM_USAGE_REPLACE || usage == ITEM_USAGE_BAD_EQUIP)
items.insert(itemId);
}
return items;
}
bool EquipUpgradesTriggeredAction::Execute(Event event)
{ {
if (!sPlayerbotAIConfig.autoEquipUpgradeLoot && !sRandomPlayerbotMgr.IsRandomBot(bot)) if (!sPlayerbotAIConfig.autoEquipUpgradeLoot && !sRandomPlayerbotMgr.IsRandomBot(bot))
return false; return false;
@@ -361,72 +397,18 @@ bool EquipUpgradesAction::Execute(Event event)
p >> itemId; p >> itemId;
ItemTemplate const* item = sObjectMgr->GetItemTemplate(itemId); ItemTemplate const* item = sObjectMgr->GetItemTemplate(itemId);
if (item->Class == ITEM_CLASS_TRADE_GOODS && item->SubClass == ITEM_SUBCLASS_MEAT) if (item->InventoryType == INVTYPE_NON_EQUIP)
return false; return false;
} }
CollectItemsVisitor visitor; ItemIds items = SelectInventoryItemsToEquip();
IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS);
ItemIds items;
for (auto i = visitor.items.begin(); i != visitor.items.end(); ++i)
{
Item* item = *i;
if (!item)
break;
int32 randomProperty = item->GetItemRandomPropertyId();
uint32 itemId = item->GetTemplate()->ItemId;
std::string itemUsageParam;
if (randomProperty != 0)
{
itemUsageParam = std::to_string(itemId) + "," + std::to_string(randomProperty);
}
else
{
itemUsageParam = std::to_string(itemId);
}
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", itemUsageParam);
if (usage == ITEM_USAGE_EQUIP || usage == ITEM_USAGE_REPLACE || usage == ITEM_USAGE_BAD_EQUIP)
{
items.insert(itemId);
}
}
EquipItems(items); EquipItems(items);
return true; return true;
} }
bool EquipUpgradeAction::Execute(Event event) bool EquipUpgradeAction::Execute(Event event)
{ {
CollectItemsVisitor visitor; ItemIds items = SelectInventoryItemsToEquip();
IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS);
ItemIds items;
for (auto i = visitor.items.begin(); i != visitor.items.end(); ++i)
{
Item* item = *i;
if (!item)
break;
int32 randomProperty = item->GetItemRandomPropertyId();
uint32 itemId = item->GetTemplate()->ItemId;
std::string itemUsageParam;
if (randomProperty != 0)
{
itemUsageParam = std::to_string(itemId) + "," + std::to_string(randomProperty);
}
else
{
itemUsageParam = std::to_string(itemId);
}
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", itemUsageParam);
if (usage == ITEM_USAGE_EQUIP || usage == ITEM_USAGE_REPLACE || usage == ITEM_USAGE_BAD_EQUIP)
{
items.insert(itemId);
}
}
EquipItems(items); EquipItems(items);
return true; return true;
} }

View File

@@ -8,6 +8,7 @@
#include "ChatHelper.h" #include "ChatHelper.h"
#include "InventoryAction.h" #include "InventoryAction.h"
#include "Item.h"
class FindItemVisitor; class FindItemVisitor;
class Item; class Item;
@@ -20,6 +21,7 @@ public:
bool Execute(Event event) override; bool Execute(Event event) override;
void EquipItems(ItemIds ids); void EquipItems(ItemIds ids);
ItemIds SelectInventoryItemsToEquip();
private: private:
void EquipItem(FindItemVisitor* visitor); void EquipItem(FindItemVisitor* visitor);
@@ -27,10 +29,10 @@ private:
void EquipItem(Item* item); void EquipItem(Item* item);
}; };
class EquipUpgradesAction : public EquipAction class EquipUpgradesTriggeredAction : public EquipAction
{ {
public: public:
EquipUpgradesAction(PlayerbotAI* botAI, std::string const name = "equip upgrades") : EquipAction(botAI, name) {} explicit EquipUpgradesTriggeredAction(PlayerbotAI* botAI, std::string const name = "equip upgrades") : EquipAction(botAI, name) {}
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
@@ -38,7 +40,7 @@ public:
class EquipUpgradeAction : public EquipAction class EquipUpgradeAction : public EquipAction
{ {
public: public:
EquipUpgradeAction(PlayerbotAI* botAI, std::string const name = "equip upgrade") : EquipAction(botAI, name) {} explicit EquipUpgradeAction(PlayerbotAI* botAI, std::string const name = "equip upgrade") : EquipAction(botAI, name) {}
bool Execute(Event event) override; bool Execute(Event event) override;
}; };

View File

@@ -90,6 +90,8 @@ bool LootRollAction::Execute(Event event)
} }
else if (sPlayerbotAIConfig.lootRollLevel == 1) else if (sPlayerbotAIConfig.lootRollLevel == 1)
{ {
// Level 1 = "greed" mode: bots greed on useful items but never need
// Only downgrade NEED to GREED, preserve GREED votes as-is
if (vote == NEED) if (vote == NEED)
{ {
if (RollUniqueCheck(proto, bot)) if (RollUniqueCheck(proto, bot))
@@ -101,10 +103,6 @@ bool LootRollAction::Execute(Event event)
vote = GREED; vote = GREED;
} }
} }
else if (vote == GREED)
{
vote = PASS;
}
} }
switch (group->GetLootMethod()) switch (group->GetLootMethod())
{ {

View File

@@ -0,0 +1,100 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "TellPvpStatsAction.h"
#include <map>
#include "ArenaTeam.h"
#include "ArenaTeamMgr.h"
#include "Event.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "PlayerbotTextMgr.h"
#include "Playerbots.h"
#include "SharedDefines.h"
#include "Language.h"
namespace
{
inline char const* BracketName(uint8 slot)
{
switch (slot)
{
case ARENA_SLOT_2v2: return "2v2";
case ARENA_SLOT_3v3: return "3v3";
default: return "5v5"; // ARENA_SLOT_5v5
}
}
}
bool TellPvpStatsAction::Execute(Event event)
{
if (!bot)
return false;
// Prefer the actual chat sender (whisper / say / etc.) if available.
Player* requester = nullptr;
if (Unit* owner = event.getOwner())
requester = owner->ToPlayer();
// Fallback to master if event owner is not available.
if (!requester)
requester = GetMaster();
// If we still do not have a valid player to answer to, bail out.
if (!requester)
return false;
// PVP currencies
std::map<std::string, std::string> currencyPlaceholders;
currencyPlaceholders["%arena_points"] = std::to_string(bot->GetArenaPoints());
currencyPlaceholders["%honor_points"] = std::to_string(bot->GetHonorPoints());
std::string const currencyText = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pvp_currency",
"[PVP] Arena points: %arena_points | Honor Points: %honor_points",
currencyPlaceholders);
bot->Whisper(currencyText, LANG_UNIVERSAL, requester);
// Arena Teams by slot
bool anyTeam = false;
for (uint8 slot = 0; slot < MAX_ARENA_SLOT; ++slot)
{
uint32 const teamId = bot->GetArenaTeamId(slot);
if (!teamId)
continue;
if (ArenaTeam* team = sArenaTeamMgr->GetArenaTeamById(teamId))
{
anyTeam = true;
std::map<std::string, std::string> placeholders;
placeholders["%bracket"] = BracketName(slot);
placeholders["%team_name"] = team->GetName();
placeholders["%team_rating"] = std::to_string(team->GetRating());
std::string const teamText = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pvp_arena_team",
"[PVP] %bracket: <%team_name> (rating %team_rating)",
placeholders);
bot->Whisper(teamText, LANG_UNIVERSAL, requester);
}
}
if (!anyTeam)
{
std::string const noTeamText = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pvp_no_arena_team",
"[PVP] I have no Arena Team.",
std::map<std::string, std::string>());
bot->Whisper(noTeamText, LANG_UNIVERSAL, requester);
}
return true;
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_TELLPVPSTATSACTION_H
#define _PLAYERBOT_TELLPVPSTATSACTION_H
#include "Action.h"
class PlayerbotAI;
class TellPvpStatsAction : public Action
{
public:
TellPvpStatsAction(PlayerbotAI* botAI) : Action(botAI, "tell pvp stats") {}
bool Execute(Event event) override;
};
#endif

View File

@@ -17,9 +17,9 @@ public:
SummonAction(PlayerbotAI* botAI, std::string const name = "summon") : MovementAction(botAI, name) {} SummonAction(PlayerbotAI* botAI, std::string const name = "summon") : MovementAction(botAI, name) {}
bool Execute(Event event) override; bool Execute(Event event) override;
bool Teleport(Player* summoner, Player* player, bool preserveAuras);
protected: protected:
bool Teleport(Player* summoner, Player* player, bool preserveAuras);
bool SummonUsingGos(Player* summoner, Player* player, bool preserveAuras); bool SummonUsingGos(Player* summoner, Player* player, bool preserveAuras);
bool SummonUsingNpcs(Player* summoner, Player* player, bool preserveAuras); bool SummonUsingNpcs(Player* summoner, Player* player, bool preserveAuras);
}; };

View File

@@ -67,6 +67,7 @@
#include "TellItemCountAction.h" #include "TellItemCountAction.h"
#include "TellLosAction.h" #include "TellLosAction.h"
#include "TellReputationAction.h" #include "TellReputationAction.h"
#include "TellPvpStatsAction.h"
#include "TellTargetAction.h" #include "TellTargetAction.h"
#include "TradeAction.h" #include "TradeAction.h"
#include "TrainerAction.h" #include "TrainerAction.h"
@@ -97,6 +98,7 @@ public:
creators["quests"] = &ChatActionContext::quests; creators["quests"] = &ChatActionContext::quests;
creators["leave"] = &ChatActionContext::leave; creators["leave"] = &ChatActionContext::leave;
creators["reputation"] = &ChatActionContext::reputation; creators["reputation"] = &ChatActionContext::reputation;
creators["tell pvp stats"] = &ChatActionContext::tell_pvp_stats;
creators["log"] = &ChatActionContext::log; creators["log"] = &ChatActionContext::log;
creators["los"] = &ChatActionContext::los; creators["los"] = &ChatActionContext::los;
creators["rpg status"] = &ChatActionContext::rpg_status; creators["rpg status"] = &ChatActionContext::rpg_status;
@@ -118,7 +120,7 @@ public:
creators["use"] = &ChatActionContext::use; creators["use"] = &ChatActionContext::use;
creators["item count"] = &ChatActionContext::item_count; creators["item count"] = &ChatActionContext::item_count;
creators["equip"] = &ChatActionContext::equip; creators["equip"] = &ChatActionContext::equip;
creators["equip upgrades"] = &ChatActionContext::equip_upgrades; creators["equip upgrades"] = &ChatActionContext::equip_upgrade;
creators["unequip"] = &ChatActionContext::unequip; creators["unequip"] = &ChatActionContext::unequip;
creators["sell"] = &ChatActionContext::sell; creators["sell"] = &ChatActionContext::sell;
creators["buy"] = &ChatActionContext::buy; creators["buy"] = &ChatActionContext::buy;
@@ -256,7 +258,6 @@ private:
static Action* talents(PlayerbotAI* botAI) { return new ChangeTalentsAction(botAI); } static Action* talents(PlayerbotAI* botAI) { return new ChangeTalentsAction(botAI); }
static Action* equip(PlayerbotAI* botAI) { return new EquipAction(botAI); } static Action* equip(PlayerbotAI* botAI) { return new EquipAction(botAI); }
static Action* equip_upgrades(PlayerbotAI* botAI) { return new EquipUpgradesAction(botAI); }
static Action* unequip(PlayerbotAI* botAI) { return new UnequipAction(botAI); } static Action* unequip(PlayerbotAI* botAI) { return new UnequipAction(botAI); }
static Action* sell(PlayerbotAI* botAI) { return new SellAction(botAI); } static Action* sell(PlayerbotAI* botAI) { return new SellAction(botAI); }
static Action* buy(PlayerbotAI* botAI) { return new BuyAction(botAI); } static Action* buy(PlayerbotAI* botAI) { return new BuyAction(botAI); }
@@ -279,6 +280,7 @@ private:
static Action* quests(PlayerbotAI* botAI) { return new ListQuestsAction(botAI); } static Action* quests(PlayerbotAI* botAI) { return new ListQuestsAction(botAI); }
static Action* leave(PlayerbotAI* botAI) { return new LeaveGroupAction(botAI); } static Action* leave(PlayerbotAI* botAI) { return new LeaveGroupAction(botAI); }
static Action* reputation(PlayerbotAI* botAI) { return new TellReputationAction(botAI); } static Action* reputation(PlayerbotAI* botAI) { return new TellReputationAction(botAI); }
static Action* tell_pvp_stats(PlayerbotAI* botAI) { return new TellPvpStatsAction(botAI); }
static Action* log(PlayerbotAI* botAI) { return new LogLevelAction(botAI); } static Action* log(PlayerbotAI* botAI) { return new LogLevelAction(botAI); }
static Action* los(PlayerbotAI* botAI) { return new TellLosAction(botAI); } static Action* los(PlayerbotAI* botAI) { return new TellLosAction(botAI); }
static Action* rpg_status(PlayerbotAI* botAI) { return new TellRpgStatusAction(botAI); } static Action* rpg_status(PlayerbotAI* botAI) { return new TellRpgStatusAction(botAI); }

View File

@@ -24,6 +24,7 @@ public:
creators["leave"] = &ChatTriggerContext::leave; creators["leave"] = &ChatTriggerContext::leave;
creators["rep"] = &ChatTriggerContext::reputation; creators["rep"] = &ChatTriggerContext::reputation;
creators["reputation"] = &ChatTriggerContext::reputation; creators["reputation"] = &ChatTriggerContext::reputation;
creators["pvp stats"] = &ChatTriggerContext::pvp_stats;
creators["log"] = &ChatTriggerContext::log; creators["log"] = &ChatTriggerContext::log;
creators["los"] = &ChatTriggerContext::los; creators["los"] = &ChatTriggerContext::los;
creators["rpg status"] = &ChatTriggerContext::rpg_status; creators["rpg status"] = &ChatTriggerContext::rpg_status;
@@ -224,6 +225,7 @@ private:
static Trigger* stats(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "stats"); } static Trigger* stats(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "stats"); }
static Trigger* leave(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "leave"); } static Trigger* leave(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "leave"); }
static Trigger* reputation(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "reputation"); } static Trigger* reputation(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "reputation"); }
static Trigger* pvp_stats(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "pvp stats"); }
static Trigger* log(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "log"); } static Trigger* log(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "log"); }
static Trigger* los(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "los"); } static Trigger* los(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "los"); }
static Trigger* rpg_status(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "rpg status"); } static Trigger* rpg_status(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "rpg status"); }

View File

@@ -27,6 +27,7 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
PassTroughStrategy::InitTriggers(triggers); PassTroughStrategy::InitTriggers(triggers);
triggers.push_back(new TriggerNode("rep", { NextAction("reputation", relevance) })); triggers.push_back(new TriggerNode("rep", { NextAction("reputation", relevance) }));
triggers.push_back(new TriggerNode("pvp stats", { NextAction("tell pvp stats", relevance) }));
triggers.push_back(new TriggerNode("q", { NextAction("query quest", relevance), triggers.push_back(new TriggerNode("q", { NextAction("query quest", relevance),
NextAction("query item usage", relevance) })); NextAction("query item usage", relevance) }));
triggers.push_back(new TriggerNode("add all loot", { NextAction("add all loot", relevance), triggers.push_back(new TriggerNode("add all loot", { NextAction("add all loot", relevance),
@@ -116,6 +117,7 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas
supported.push_back("stats"); supported.push_back("stats");
supported.push_back("leave"); supported.push_back("leave");
supported.push_back("reputation"); supported.push_back("reputation");
supported.push_back("tell pvp stats");
supported.push_back("log"); supported.push_back("log");
supported.push_back("los"); supported.push_back("los");
supported.push_back("rpg status"); supported.push_back("rpg status");

View File

@@ -42,6 +42,7 @@ void WorldPacketHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
NextAction("query item usage", relevance), NextAction("query item usage", relevance),
NextAction("equip upgrades", relevance) })); NextAction("equip upgrades", relevance) }));
triggers.push_back(new TriggerNode("item push result", { NextAction("quest item push result", relevance) })); triggers.push_back(new TriggerNode("item push result", { NextAction("quest item push result", relevance) }));
triggers.push_back(new TriggerNode("loot roll won", { NextAction("equip upgrades", relevance) }));
triggers.push_back(new TriggerNode("ready check finished", { NextAction("finish ready check", relevance) })); triggers.push_back(new TriggerNode("ready check finished", { NextAction("finish ready check", relevance) }));
// triggers.push_back(new TriggerNode("often", { NextAction("security check", relevance), NextAction("check mail", relevance) })); // triggers.push_back(new TriggerNode("often", { NextAction("security check", relevance), NextAction("check mail", relevance) }));
triggers.push_back(new TriggerNode("guild invite", { NextAction("guild accept", relevance) })); triggers.push_back(new TriggerNode("guild invite", { NextAction("guild accept", relevance) }));

View File

@@ -19,19 +19,9 @@
ItemUsage ItemUsageValue::Calculate() ItemUsage ItemUsageValue::Calculate()
{ {
uint32 itemId = 0; ParsedItemUsage const parsed = GetItemIdFromQualifier();
uint32 randomPropertyId = 0; uint32 itemId = parsed.itemId;
size_t pos = qualifier.find(","); uint32 randomPropertyId = parsed.randomPropertyId;
if (pos != std::string::npos)
{
itemId = atoi(qualifier.substr(0, pos).c_str());
randomPropertyId = atoi(qualifier.substr(pos + 1).c_str());
}
else
{
itemId = atoi(qualifier.c_str());
}
if (!itemId) if (!itemId)
return ITEM_USAGE_NONE; return ITEM_USAGE_NONE;
@@ -142,96 +132,30 @@ ItemUsage ItemUsageValue::Calculate()
// If the loot is from an item in the bots bags, ignore syncQuestWithPlayer // If the loot is from an item in the bots bags, ignore syncQuestWithPlayer
if (isLootFromItem && botNeedsItemForQuest) if (isLootFromItem && botNeedsItemForQuest)
{
return ITEM_USAGE_QUEST; return ITEM_USAGE_QUEST;
}
// If the bot is NOT acting alone and the master needs this quest item, defer to the master // If the bot is NOT acting alone and the master needs this quest item, defer to the master
if (!isSelfBot && masterNeedsItemForQuest) if (!isSelfBot && masterNeedsItemForQuest)
{
return ITEM_USAGE_NONE; return ITEM_USAGE_NONE;
}
// If the bot itself needs the item for a quest, allow looting // If the bot itself needs the item for a quest, allow looting
if (botNeedsItemForQuest) if (botNeedsItemForQuest)
{
return ITEM_USAGE_QUEST; return ITEM_USAGE_QUEST;
}
if (proto->Class == ITEM_CLASS_PROJECTILE && bot->CanUseItem(proto) == EQUIP_ERR_OK) if (proto->Class == ITEM_CLASS_PROJECTILE && bot->CanUseItem(proto) == EQUIP_ERR_OK)
{ {
if (bot->getClass() == CLASS_HUNTER || bot->getClass() == CLASS_ROGUE || bot->getClass() == CLASS_WARRIOR) ItemUsage ammoUsage = QueryItemUsageForAmmo(proto);
{ if (ammoUsage != ITEM_USAGE_NONE)
Item* rangedWeapon = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED); return ammoUsage;
uint32 requiredSubClass = 0;
if (rangedWeapon)
{
switch (rangedWeapon->GetTemplate()->SubClass)
{
case ITEM_SUBCLASS_WEAPON_GUN:
requiredSubClass = ITEM_SUBCLASS_BULLET;
break;
case ITEM_SUBCLASS_WEAPON_BOW:
case ITEM_SUBCLASS_WEAPON_CROSSBOW:
requiredSubClass = ITEM_SUBCLASS_ARROW;
break;
}
}
// Ensure the item is the correct ammo type for the equipped ranged weapon
if (proto->SubClass == requiredSubClass)
{
float ammoCount = BetterStacks(proto, "ammo");
float requiredAmmo = (bot->getClass() == CLASS_HUNTER) ? 8 : 2; // Hunters get 8 stacks, others 2
uint32 currentAmmoId = bot->GetUInt32Value(PLAYER_AMMO_ID);
// Check if the bot has an ammo type assigned
if (currentAmmoId == 0)
{
return ITEM_USAGE_EQUIP; // Equip the ammo if no ammo
}
// Compare new ammo vs current equipped ammo
ItemTemplate const* currentAmmoProto = sObjectMgr->GetItemTemplate(currentAmmoId);
if (currentAmmoProto)
{
uint32 currentAmmoDPS = (currentAmmoProto->Damage[0].DamageMin + currentAmmoProto->Damage[0].DamageMax) * 1000 / 2;
uint32 newAmmoDPS = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2;
if (newAmmoDPS > currentAmmoDPS) // New ammo meets upgrade condition
{
return ITEM_USAGE_EQUIP;
}
if (newAmmoDPS < currentAmmoDPS) // New ammo is worse
{
return ITEM_USAGE_NONE;
}
}
// Ensure we have enough ammo in the inventory
if (ammoCount < requiredAmmo)
{
ammoCount += CurrentStacks(proto);
if (ammoCount < requiredAmmo) // Buy ammo to reach the proper supply
return ITEM_USAGE_AMMO;
else if (ammoCount < requiredAmmo + 1)
return ITEM_USAGE_KEEP; // Keep the ammo if we don't have too much.
}
}
}
} }
// Need to add something like free bagspace or item value. // Need to add something like free bagspace or item value.
if (proto->SellPrice > 0) if (proto->SellPrice > 0)
{ {
if (proto->Quality >= ITEM_QUALITY_NORMAL && !isSoulbound) if (proto->Quality >= ITEM_QUALITY_NORMAL && !isSoulbound)
{
return ITEM_USAGE_AH; return ITEM_USAGE_AH;
}
else else
{
return ITEM_USAGE_VENDOR; return ITEM_USAGE_VENDOR;
}
} }
return ITEM_USAGE_NONE; return ITEM_USAGE_NONE;
@@ -480,6 +404,80 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
return ITEM_USAGE_NONE; return ITEM_USAGE_NONE;
} }
ItemUsage ItemUsageValue::QueryItemUsageForAmmo(ItemTemplate const* proto)
{
if (bot->getClass() != CLASS_HUNTER || bot->getClass() != CLASS_ROGUE || bot->getClass() != CLASS_WARRIOR)
return ITEM_USAGE_NONE;
Item* rangedWeapon = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED);
uint32 requiredSubClass = 0;
if (rangedWeapon)
{
switch (rangedWeapon->GetTemplate()->SubClass)
{
case ITEM_SUBCLASS_WEAPON_GUN:
requiredSubClass = ITEM_SUBCLASS_BULLET;
break;
case ITEM_SUBCLASS_WEAPON_BOW:
case ITEM_SUBCLASS_WEAPON_CROSSBOW:
requiredSubClass = ITEM_SUBCLASS_ARROW;
break;
}
}
// Ensure the item is the correct ammo type for the equipped ranged weapon
if (proto->SubClass == requiredSubClass)
{
float ammoCount = BetterStacks(proto, "ammo");
float requiredAmmo = (bot->getClass() == CLASS_HUNTER) ? 8 : 2; // Hunters get 8 stacks, others 2
uint32 currentAmmoId = bot->GetUInt32Value(PLAYER_AMMO_ID);
// Check if the bot has an ammo type assigned
if (currentAmmoId == 0)
return ITEM_USAGE_EQUIP; // Equip the ammo if no ammo
// Compare new ammo vs current equipped ammo
ItemTemplate const* currentAmmoProto = sObjectMgr->GetItemTemplate(currentAmmoId);
if (currentAmmoProto)
{
uint32 currentAmmoDPS = (currentAmmoProto->Damage[0].DamageMin + currentAmmoProto->Damage[0].DamageMax) * 1000 / 2;
uint32 newAmmoDPS = (proto->Damage[0].DamageMin + proto->Damage[0].DamageMax) * 1000 / 2;
if (newAmmoDPS > currentAmmoDPS) // New ammo meets upgrade condition
return ITEM_USAGE_EQUIP;
if (newAmmoDPS < currentAmmoDPS) // New ammo is worse
return ITEM_USAGE_NONE;
}
// Ensure we have enough ammo in the inventory
if (ammoCount < requiredAmmo)
{
ammoCount += CurrentStacks(proto);
if (ammoCount < requiredAmmo) // Buy ammo to reach the proper supply
return ITEM_USAGE_AMMO;
else if (ammoCount < requiredAmmo + 1)
return ITEM_USAGE_KEEP; // Keep the ammo if we don't have too much.
}
}
return ITEM_USAGE_NONE;
}
ParsedItemUsage ItemUsageValue::GetItemIdFromQualifier()
{
ParsedItemUsage parsed;
size_t const pos = qualifier.find(",");
if (pos != std::string::npos)
{
parsed.itemId = atoi(qualifier.substr(0, pos).c_str());
parsed.randomPropertyId = atoi(qualifier.substr(pos + 1).c_str());
return parsed;
}
else
parsed.itemId = atoi(qualifier.c_str());
return parsed;
}
// Return smaltest bag size equipped // Return smaltest bag size equipped
uint32 ItemUsageValue::GetSmallestBagSize() uint32 ItemUsageValue::GetSmallestBagSize()
{ {
@@ -913,3 +911,25 @@ std::string const ItemUsageValue::GetConsumableType(ItemTemplate const* proto, b
return ""; return "";
} }
ItemUsage ItemUpgradeValue::Calculate()
{
ParsedItemUsage parsed = GetItemIdFromQualifier();
uint32 itemId = parsed.itemId;
uint32 randomPropertyId = parsed.randomPropertyId;
if (!itemId)
return ITEM_USAGE_NONE;
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (!proto)
return ITEM_USAGE_NONE;
ItemUsage equip = QueryItemUsageForEquip(proto, randomPropertyId);
if (equip != ITEM_USAGE_NONE)
return equip;
if (proto->Class == ITEM_CLASS_PROJECTILE && bot->CanUseItem(proto) == EQUIP_ERR_OK)
return QueryItemUsageForAmmo(proto);
return ITEM_USAGE_NONE;
}

View File

@@ -14,7 +14,11 @@ class Player;
class PlayerbotAI; class PlayerbotAI;
struct ItemTemplate; struct ItemTemplate;
struct ParsedItemUsage
{
uint32 itemId = 0;
int32 randomPropertyId = 0;
};
enum ItemUsage : uint32 enum ItemUsage : uint32
{ {
ITEM_USAGE_NONE = 0, ITEM_USAGE_NONE = 0,
@@ -42,8 +46,12 @@ public:
ItemUsage Calculate() override; ItemUsage Calculate() override;
private: protected:
ItemUsage QueryItemUsageForEquip(ItemTemplate const* proto, int32 randomPropertyId = 0); ItemUsage QueryItemUsageForEquip(ItemTemplate const* proto, int32 randomPropertyId = 0);
ItemUsage QueryItemUsageForAmmo(ItemTemplate const* proto);
ParsedItemUsage GetItemIdFromQualifier();
private:
uint32 GetSmallestBagSize(); uint32 GetSmallestBagSize();
bool IsItemUsefulForQuest(Player* player, ItemTemplate const* proto); bool IsItemUsefulForQuest(Player* player, ItemTemplate const* proto);
bool IsItemNeededForSkill(ItemTemplate const* proto); bool IsItemNeededForSkill(ItemTemplate const* proto);
@@ -61,4 +69,14 @@ public:
static std::string const GetConsumableType(ItemTemplate const* proto, bool hasMana); static std::string const GetConsumableType(ItemTemplate const* proto, bool hasMana);
}; };
class ItemUpgradeValue : public ItemUsageValue
{
public:
ItemUpgradeValue(PlayerbotAI* botAI, std::string const name = "item upgrade") : ItemUsageValue(botAI, name)
{
}
ItemUsage Calculate() override;
};
#endif #endif

View File

@@ -173,7 +173,7 @@ std::vector<GuidPosition> ActiveQuestGiversValue::Calculate()
continue; continue;
} }
if (guidp.isDead()) if (!guidp.IsCreatureOrGOAccessible())
continue; continue;
retQuestGivers.push_back(guidp); retQuestGivers.push_back(guidp);
@@ -231,7 +231,7 @@ std::vector<GuidPosition> ActiveQuestTakersValue::Calculate()
for (auto& guidp : entry.second) for (auto& guidp : entry.second)
{ {
if (guidp.isDead()) if (!guidp.IsCreatureOrGOAccessible())
continue; continue;
retQuestTakers.push_back(guidp); retQuestTakers.push_back(guidp);
@@ -298,7 +298,7 @@ std::vector<GuidPosition> ActiveQuestObjectivesValue::Calculate()
{ {
for (auto& guidp : entry.second) for (auto& guidp : entry.second)
{ {
if (guidp.isDead()) if (!guidp.IsCreatureOrGOAccessible())
continue; continue;
retQuestObjectives.push_back(guidp); retQuestObjectives.push_back(guidp);

View File

@@ -216,6 +216,7 @@ public:
creators["formation"] = &ValueContext::formation; creators["formation"] = &ValueContext::formation;
creators["stance"] = &ValueContext::stance; creators["stance"] = &ValueContext::stance;
creators["item usage"] = &ValueContext::item_usage; creators["item usage"] = &ValueContext::item_usage;
creators["item upgrade"] = &ValueContext::item_upgrade;
creators["speed"] = &ValueContext::speed; creators["speed"] = &ValueContext::speed;
creators["last said"] = &ValueContext::last_said; creators["last said"] = &ValueContext::last_said;
creators["last emote"] = &ValueContext::last_emote; creators["last emote"] = &ValueContext::last_emote;
@@ -341,6 +342,7 @@ private:
static UntypedValue* already_seen_players(PlayerbotAI* botAI) { return new AlreadySeenPlayersValue(botAI); } static UntypedValue* already_seen_players(PlayerbotAI* botAI) { return new AlreadySeenPlayersValue(botAI); }
static UntypedValue* new_player_nearby(PlayerbotAI* botAI) { return new NewPlayerNearbyValue(botAI); } static UntypedValue* new_player_nearby(PlayerbotAI* botAI) { return new NewPlayerNearbyValue(botAI); }
static UntypedValue* item_usage(PlayerbotAI* botAI) { return new ItemUsageValue(botAI); } static UntypedValue* item_usage(PlayerbotAI* botAI) { return new ItemUsageValue(botAI); }
static UntypedValue* item_upgrade(PlayerbotAI* botAI) { return new ItemUpgradeValue(botAI); }
static UntypedValue* formation(PlayerbotAI* botAI) { return new FormationValue(botAI); } static UntypedValue* formation(PlayerbotAI* botAI) { return new FormationValue(botAI); }
static UntypedValue* stance(PlayerbotAI* botAI) { return new StanceValue(botAI); } static UntypedValue* stance(PlayerbotAI* botAI) { return new StanceValue(botAI); }
static UntypedValue* mana_save_level(PlayerbotAI* botAI) { return new ManaSaveLevelValue(botAI); } static UntypedValue* mana_save_level(PlayerbotAI* botAI) { return new ManaSaveLevelValue(botAI); }

View File

@@ -46,6 +46,7 @@ public:
creators["questgiver quest details"] = &WorldPacketTriggerContext::questgiver_quest_details; creators["questgiver quest details"] = &WorldPacketTriggerContext::questgiver_quest_details;
creators["item push result"] = &WorldPacketTriggerContext::item_push_result; creators["item push result"] = &WorldPacketTriggerContext::item_push_result;
creators["loot roll won"] = &WorldPacketTriggerContext::loot_roll_won;
creators["party command"] = &WorldPacketTriggerContext::party_command; creators["party command"] = &WorldPacketTriggerContext::party_command;
creators["taxi done"] = &WorldPacketTriggerContext::taxi_done; creators["taxi done"] = &WorldPacketTriggerContext::taxi_done;
creators["cast failed"] = &WorldPacketTriggerContext::cast_failed; creators["cast failed"] = &WorldPacketTriggerContext::cast_failed;
@@ -92,6 +93,7 @@ private:
static Trigger* taxi_done(PlayerbotAI* botAI) { return new WorldPacketTrigger(botAI, "taxi done"); } static Trigger* taxi_done(PlayerbotAI* botAI) { return new WorldPacketTrigger(botAI, "taxi done"); }
static Trigger* party_command(PlayerbotAI* botAI) { return new WorldPacketTrigger(botAI, "party command"); } static Trigger* party_command(PlayerbotAI* botAI) { return new WorldPacketTrigger(botAI, "party command"); }
static Trigger* item_push_result(PlayerbotAI* botAI) { return new WorldPacketTrigger(botAI, "item push result"); } static Trigger* item_push_result(PlayerbotAI* botAI) { return new WorldPacketTrigger(botAI, "item push result"); }
static Trigger* loot_roll_won(PlayerbotAI* botAI) { return new WorldPacketTrigger(botAI, "loot roll won"); }
// quest // quest
static Trigger* quest_update_add_kill(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "quest update add kill"); } static Trigger* quest_update_add_kill(PlayerbotAI* ai) { return new WorldPacketTrigger(ai, "quest update add kill"); }

View File

@@ -41,6 +41,18 @@ public:
std::string const GetTargetName() override { return "pet target"; } std::string const GetTargetName() override { return "pet target"; }
}; };
class CastUnendingBreathAction : public CastBuffSpellAction
{
public:
CastUnendingBreathAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "unending breath") {}
};
class CastUnendingBreathOnPartyAction : public BuffOnPartyAction
{
public:
CastUnendingBreathOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "unending breath") {}
};
class CreateSoulShardAction : public Action class CreateSoulShardAction : public Action
{ {
public: public:

View File

@@ -85,6 +85,8 @@ void GenericWarlockNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& tr
triggers.push_back(new TriggerNode("too many soul shards", { NextAction("destroy soul shard", 60.0f) })); triggers.push_back(new TriggerNode("too many soul shards", { NextAction("destroy soul shard", 60.0f) }));
triggers.push_back(new TriggerNode("soul link", { NextAction("soul link", 28.0f) })); triggers.push_back(new TriggerNode("soul link", { NextAction("soul link", 28.0f) }));
triggers.push_back(new TriggerNode("demon armor", { NextAction("fel armor", 27.0f) })); triggers.push_back(new TriggerNode("demon armor", { NextAction("fel armor", 27.0f) }));
triggers.push_back(new TriggerNode("unending breath", { NextAction("unending breath", 12.0f) }));
triggers.push_back(new TriggerNode("unending breath on party", { NextAction("unending breath on party", 11.0f) }));
triggers.push_back(new TriggerNode("no healthstone", { NextAction("create healthstone", 26.0f) })); triggers.push_back(new TriggerNode("no healthstone", { NextAction("create healthstone", 26.0f) }));
triggers.push_back(new TriggerNode("no soulstone", { NextAction("create soulstone", 25.0f) })); triggers.push_back(new TriggerNode("no soulstone", { NextAction("create soulstone", 25.0f) }));
triggers.push_back(new TriggerNode("life tap", { NextAction("life tap", 23.0f) })); triggers.push_back(new TriggerNode("life tap", { NextAction("life tap", 23.0f) }));

View File

@@ -79,6 +79,16 @@ bool SoulLinkTrigger::IsActive()
return !botAI->HasAura("soul link", target); return !botAI->HasAura("soul link", target);
} }
bool UnendingBreathTrigger::IsActive()
{
return BuffTrigger::IsActive() && AI_VALUE2(bool, "swimming", "self target");
}
bool UnendingBreathOnPartyTrigger::IsActive()
{
return BuffOnPartyTrigger::IsActive() && AI_VALUE2(bool, "swimming", "self target");
}
bool DemonicEmpowermentTrigger::IsActive() bool DemonicEmpowermentTrigger::IsActive()
{ {
Pet* pet = bot->GetPet(); Pet* pet = bot->GetPet();

View File

@@ -32,6 +32,20 @@ public:
bool IsActive() override; bool IsActive() override;
}; };
class UnendingBreathTrigger : public BuffTrigger
{
public:
UnendingBreathTrigger(PlayerbotAI* botAI) : BuffTrigger(botAI, "unending breath", 5 * 2000) {}
bool IsActive() override;
};
class UnendingBreathOnPartyTrigger : public BuffOnPartyTrigger
{
public:
UnendingBreathOnPartyTrigger(PlayerbotAI* botAI) : BuffOnPartyTrigger(botAI, "unending breath on party", 2 * 2000) {}
bool IsActive() override;
};
class OutOfSoulShardsTrigger : public Trigger class OutOfSoulShardsTrigger : public Trigger
{ {
public: public:

View File

@@ -143,6 +143,8 @@ public:
creators["shadow trance"] = &WarlockTriggerFactoryInternal::shadow_trance; creators["shadow trance"] = &WarlockTriggerFactoryInternal::shadow_trance;
creators["demon armor"] = &WarlockTriggerFactoryInternal::demon_armor; creators["demon armor"] = &WarlockTriggerFactoryInternal::demon_armor;
creators["soul link"] = &WarlockTriggerFactoryInternal::soul_link; creators["soul link"] = &WarlockTriggerFactoryInternal::soul_link;
creators["unending breath"] = &WarlockTriggerFactoryInternal::unending_breath;
creators["unending breath on party"] = &WarlockTriggerFactoryInternal::unending_breath_on_party;
creators["no soul shard"] = &WarlockTriggerFactoryInternal::no_soul_shard; creators["no soul shard"] = &WarlockTriggerFactoryInternal::no_soul_shard;
creators["too many soul shards"] = &WarlockTriggerFactoryInternal::too_many_soul_shards; creators["too many soul shards"] = &WarlockTriggerFactoryInternal::too_many_soul_shards;
creators["no healthstone"] = &WarlockTriggerFactoryInternal::HasHealthstone; creators["no healthstone"] = &WarlockTriggerFactoryInternal::HasHealthstone;
@@ -189,6 +191,8 @@ private:
static Trigger* shadow_trance(PlayerbotAI* botAI) { return new ShadowTranceTrigger(botAI); } static Trigger* shadow_trance(PlayerbotAI* botAI) { return new ShadowTranceTrigger(botAI); }
static Trigger* demon_armor(PlayerbotAI* botAI) { return new DemonArmorTrigger(botAI); } static Trigger* demon_armor(PlayerbotAI* botAI) { return new DemonArmorTrigger(botAI); }
static Trigger* soul_link(PlayerbotAI* botAI) { return new SoulLinkTrigger(botAI); } static Trigger* soul_link(PlayerbotAI* botAI) { return new SoulLinkTrigger(botAI); }
static Trigger* unending_breath(PlayerbotAI* botAI) { return new UnendingBreathTrigger(botAI); }
static Trigger* unending_breath_on_party(PlayerbotAI* botAI) { return new UnendingBreathOnPartyTrigger(botAI); }
static Trigger* no_soul_shard(PlayerbotAI* botAI) { return new OutOfSoulShardsTrigger(botAI); } static Trigger* no_soul_shard(PlayerbotAI* botAI) { return new OutOfSoulShardsTrigger(botAI); }
static Trigger* too_many_soul_shards(PlayerbotAI* botAI) { return new TooManySoulShardsTrigger(botAI); } static Trigger* too_many_soul_shards(PlayerbotAI* botAI) { return new TooManySoulShardsTrigger(botAI); }
static Trigger* HasHealthstone(PlayerbotAI* botAI) { return new HasHealthstoneTrigger(botAI); } static Trigger* HasHealthstone(PlayerbotAI* botAI) { return new HasHealthstoneTrigger(botAI); }
@@ -240,6 +244,8 @@ public:
creators["demon armor"] = &WarlockAiObjectContextInternal::demon_armor; creators["demon armor"] = &WarlockAiObjectContextInternal::demon_armor;
creators["demon skin"] = &WarlockAiObjectContextInternal::demon_skin; creators["demon skin"] = &WarlockAiObjectContextInternal::demon_skin;
creators["soul link"] = &WarlockAiObjectContextInternal::soul_link; creators["soul link"] = &WarlockAiObjectContextInternal::soul_link;
creators["unending breath"] = &WarlockAiObjectContextInternal::unending_breath;
creators["unending breath on party"] = &WarlockAiObjectContextInternal::unending_breath_on_party;
creators["create soul shard"] = &WarlockAiObjectContextInternal::create_soul_shard; creators["create soul shard"] = &WarlockAiObjectContextInternal::create_soul_shard;
creators["destroy soul shard"] = &WarlockAiObjectContextInternal::destroy_soul_shard; creators["destroy soul shard"] = &WarlockAiObjectContextInternal::destroy_soul_shard;
creators["create healthstone"] = &WarlockAiObjectContextInternal::create_healthstone; creators["create healthstone"] = &WarlockAiObjectContextInternal::create_healthstone;
@@ -313,6 +319,8 @@ private:
static Action* demon_armor(PlayerbotAI* botAI) { return new CastDemonArmorAction(botAI); } static Action* demon_armor(PlayerbotAI* botAI) { return new CastDemonArmorAction(botAI); }
static Action* demon_skin(PlayerbotAI* botAI) { return new CastDemonSkinAction(botAI); } static Action* demon_skin(PlayerbotAI* botAI) { return new CastDemonSkinAction(botAI); }
static Action* soul_link(PlayerbotAI* botAI) { return new CastSoulLinkAction(botAI); } static Action* soul_link(PlayerbotAI* botAI) { return new CastSoulLinkAction(botAI); }
static Action* unending_breath(PlayerbotAI* botAI) { return new CastUnendingBreathAction(botAI); }
static Action* unending_breath_on_party(PlayerbotAI* botAI) { return new CastUnendingBreathOnPartyAction(botAI); }
static Action* create_soul_shard(PlayerbotAI* botAI) { return new CreateSoulShardAction(botAI); } static Action* create_soul_shard(PlayerbotAI* botAI) { return new CreateSoulShardAction(botAI); }
static Action* destroy_soul_shard(PlayerbotAI* botAI) { return new DestroySoulShardAction(botAI); } static Action* destroy_soul_shard(PlayerbotAI* botAI) { return new DestroySoulShardAction(botAI); }
static Action* create_healthstone(PlayerbotAI* botAI) { return new CastCreateHealthstoneAction(botAI); } static Action* create_healthstone(PlayerbotAI* botAI) { return new CastCreateHealthstoneAction(botAI); }

View File

@@ -62,7 +62,7 @@ bool MountDrakeAction::Execute(Event event)
break; break;
} }
std::vector<Player*> players = botAI->GetPlayersInGroup(); std::vector<Player*> players = botAI->GetAllPlayersInGroup();
for (Player* player : players) for (Player* player : players)
{ {
if (!player || !player->IsInWorld() || player->IsDuringRemoveFromWorld()) if (!player || !player->IsInWorld() || player->IsDuringRemoveFromWorld())

View File

@@ -2,6 +2,7 @@
#include "RaidGruulsLairHelpers.h" #include "RaidGruulsLairHelpers.h"
#include "CreatureAI.h" #include "CreatureAI.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "RaidBossHelpers.h"
#include "Unit.h" #include "Unit.h"
using namespace GruulsLairHelpers; using namespace GruulsLairHelpers;
@@ -12,6 +13,8 @@ using namespace GruulsLairHelpers;
bool HighKingMaulgarMainTankAttackMaulgarAction::Execute(Event event) bool HighKingMaulgarMainTankAttackMaulgarAction::Execute(Event event)
{ {
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar"); Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
if (!maulgar)
return false;
MarkTargetWithSquare(bot, maulgar); MarkTargetWithSquare(bot, maulgar);
SetRtiTarget(botAI, "square", maulgar); SetRtiTarget(botAI, "square", maulgar);
@@ -21,31 +24,20 @@ bool HighKingMaulgarMainTankAttackMaulgarAction::Execute(Event event)
if (maulgar->GetVictim() == bot) if (maulgar->GetVictim() == bot)
{ {
const Location& tankPosition = GruulsLairLocations::MaulgarTankPosition; const Position& position = MAULGAR_TANK_POSITION;
const float maxDistance = 3.0f; const float maxDistance = 3.0f;
float distanceToTankPosition = bot->GetExactDist2d(tankPosition.x, tankPosition.y); float distanceToPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
if (distanceToTankPosition > maxDistance) if (distanceToPosition > maxDistance)
{ {
float dX = tankPosition.x - bot->GetPositionX(); float dX = position.GetPositionX() - bot->GetPositionX();
float dY = tankPosition.y - bot->GetPositionY(); float dY = position.GetPositionY() - bot->GetPositionY();
float dist = sqrt(dX * dX + dY * dY); float moveX = bot->GetPositionX() + (dX / distanceToPosition) * maxDistance;
float moveX = bot->GetPositionX() + (dX / dist) * maxDistance; float moveY = bot->GetPositionY() + (dY / distanceToPosition) * maxDistance;
float moveY = bot->GetPositionY() + (dY / dist) * maxDistance; return MoveTo(GRUULS_LAIR_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false,
return MoveTo(bot->GetMapId(), moveX, moveY, tankPosition.z, false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, true);
MovementPriority::MOVEMENT_COMBAT, true, false);
} }
float orientation = atan2(maulgar->GetPositionY() - bot->GetPositionY(),
maulgar->GetPositionX() - bot->GetPositionX());
bot->SetFacingTo(orientation);
}
else if (!bot->IsWithinMeleeRange(maulgar))
{
return MoveTo(maulgar->GetMapId(), maulgar->GetPositionX(), maulgar->GetPositionY(),
maulgar->GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
} }
return false; return false;
@@ -55,6 +47,8 @@ bool HighKingMaulgarMainTankAttackMaulgarAction::Execute(Event event)
bool HighKingMaulgarFirstAssistTankAttackOlmAction::Execute(Event event) bool HighKingMaulgarFirstAssistTankAttackOlmAction::Execute(Event event)
{ {
Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner"); Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner");
if (!olm)
return false;
MarkTargetWithCircle(bot, olm); MarkTargetWithCircle(bot, olm);
SetRtiTarget(botAI, "circle", olm); SetRtiTarget(botAI, "circle", olm);
@@ -64,29 +58,22 @@ bool HighKingMaulgarFirstAssistTankAttackOlmAction::Execute(Event event)
if (olm->GetVictim() == bot) if (olm->GetVictim() == bot)
{ {
const Location& tankPosition = GruulsLairLocations::OlmTankPosition; const Position& position = OLM_TANK_POSITION;
const float maxDistance = 3.0f; const float maxDistance = 3.0f;
const float olmTankLeeway = 30.0f; const float olmTankLeeway = 30.0f;
float distanceOlmToTankPosition = olm->GetExactDist2d(tankPosition.x, tankPosition.y); float distanceOlmToPosition = olm->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
if (distanceOlmToTankPosition > olmTankLeeway) if (distanceOlmToPosition > olmTankLeeway)
{ {
float dX = tankPosition.x - bot->GetPositionX(); float dX = position.GetPositionX() - bot->GetPositionX();
float dY = tankPosition.y - bot->GetPositionY(); float dY = position.GetPositionY() - bot->GetPositionY();
float dist = sqrt(dX * dX + dY * dY); float moveX = bot->GetPositionX() + (dX / distanceOlmToPosition) * maxDistance;
float moveX = bot->GetPositionX() + (dX / dist) * maxDistance; float moveY = bot->GetPositionY() + (dY / distanceOlmToPosition) * maxDistance;
float moveY = bot->GetPositionY() + (dY / dist) * maxDistance; return MoveTo(GRUULS_LAIR_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false,
return MoveTo(bot->GetMapId(), moveX, moveY, tankPosition.z, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false); MovementPriority::MOVEMENT_COMBAT, true, false);
} }
} }
else if (!bot->IsWithinMeleeRange(olm))
{
return MoveTo(olm->GetMapId(), olm->GetPositionX(), olm->GetPositionY(),
olm->GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
return false; return false;
} }
@@ -95,6 +82,8 @@ bool HighKingMaulgarFirstAssistTankAttackOlmAction::Execute(Event event)
bool HighKingMaulgarSecondAssistTankAttackBlindeyeAction::Execute(Event event) bool HighKingMaulgarSecondAssistTankAttackBlindeyeAction::Execute(Event event)
{ {
Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer"); Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer");
if (!blindeye)
return false;
MarkTargetWithStar(bot, blindeye); MarkTargetWithStar(bot, blindeye);
SetRtiTarget(botAI, "star", blindeye); SetRtiTarget(botAI, "star", blindeye);
@@ -104,31 +93,20 @@ bool HighKingMaulgarSecondAssistTankAttackBlindeyeAction::Execute(Event event)
if (blindeye->GetVictim() == bot) if (blindeye->GetVictim() == bot)
{ {
const Location& tankPosition = GruulsLairLocations::BlindeyeTankPosition; const Position& position = BLINDEYE_TANK_POSITION;
const float maxDistance = 3.0f; const float maxDistance = 3.0f;
float distanceToTankPosition = bot->GetExactDist2d(tankPosition.x, tankPosition.y); float distanceToPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
if (distanceToTankPosition > maxDistance) if (distanceToPosition > maxDistance)
{ {
float dX = tankPosition.x - bot->GetPositionX(); float dX = position.GetPositionX() - bot->GetPositionX();
float dY = tankPosition.y - bot->GetPositionY(); float dY = position.GetPositionY() - bot->GetPositionY();
float dist = sqrt(dX * dX + dY * dY); float moveX = bot->GetPositionX() + (dX / distanceToPosition) * maxDistance;
float moveX = bot->GetPositionX() + (dX / dist) * maxDistance; float moveY = bot->GetPositionY() + (dY / distanceToPosition) * maxDistance;
float moveY = bot->GetPositionY() + (dY / dist) * maxDistance; return MoveTo(GRUULS_LAIR_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false,
return MoveTo(bot->GetMapId(), moveX, moveY, tankPosition.z, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false); MovementPriority::MOVEMENT_COMBAT, true, false);
} }
float orientation = atan2(blindeye->GetPositionY() - bot->GetPositionY(),
blindeye->GetPositionX() - bot->GetPositionX());
bot->SetFacingTo(orientation);
}
else if (!bot->IsWithinMeleeRange(blindeye))
{
return MoveTo(blindeye->GetMapId(), blindeye->GetPositionX(), blindeye->GetPositionY(),
blindeye->GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
} }
return false; return false;
@@ -138,6 +116,8 @@ bool HighKingMaulgarSecondAssistTankAttackBlindeyeAction::Execute(Event event)
bool HighKingMaulgarMageTankAttackKroshAction::Execute(Event event) bool HighKingMaulgarMageTankAttackKroshAction::Execute(Event event)
{ {
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand"); Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand");
if (!krosh)
return false;
MarkTargetWithTriangle(bot, krosh); MarkTargetWithTriangle(bot, krosh);
SetRtiTarget(botAI, "triangle", krosh); SetRtiTarget(botAI, "triangle", krosh);
@@ -149,25 +129,22 @@ bool HighKingMaulgarMageTankAttackKroshAction::Execute(Event event)
return botAI->CastSpell("fire ward", bot); return botAI->CastSpell("fire ward", bot);
if (bot->GetTarget() != krosh->GetGUID()) if (bot->GetTarget() != krosh->GetGUID())
{ return Attack(krosh);
bot->SetSelection(krosh->GetGUID());
return true;
}
if (krosh->GetVictim() == bot) if (krosh->GetVictim() == bot)
{ {
const Location& tankPosition = GruulsLairLocations::KroshTankPosition; const Position& position = KROSH_TANK_POSITION;
float distanceToKrosh = krosh->GetExactDist2d(tankPosition.x, tankPosition.y); float distanceToKrosh = krosh->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
const float minDistance = 16.0f; const float minDistance = 16.0f;
const float maxDistance = 29.0f; const float maxDistance = 29.0f;
const float tankPositionLeeway = 1.0f; const float tankPositionLeeway = 1.0f;
if (distanceToKrosh > minDistance && distanceToKrosh < maxDistance) if (distanceToKrosh > minDistance && distanceToKrosh < maxDistance)
{ {
if (!bot->IsWithinDist2d(tankPosition.x, tankPosition.y, tankPositionLeeway)) if (!bot->IsWithinDist2d(position.GetPositionX(), position.GetPositionY(), tankPositionLeeway))
{ {
return MoveTo(bot->GetMapId(), tankPosition.x, tankPosition.y, tankPosition.z, false, return MoveTo(GRUULS_LAIR_MAP_ID, position.GetPositionX(), position.GetPositionY(), position.GetPositionZ(),
false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
} }
float orientation = atan2(krosh->GetPositionY() - bot->GetPositionY(), float orientation = atan2(krosh->GetPositionY() - bot->GetPositionY(),
@@ -179,7 +156,7 @@ bool HighKingMaulgarMageTankAttackKroshAction::Execute(Event event)
Position safePos; Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos)) if (TryGetNewSafePosition(botAI, bot, safePos))
{ {
return MoveTo(krosh->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ, return MoveTo(GRUULS_LAIR_MAP_ID, safePos.GetPositionX(), safePos.GetPositionY(), safePos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
} }
} }
@@ -192,20 +169,19 @@ bool HighKingMaulgarMageTankAttackKroshAction::Execute(Event event)
bool HighKingMaulgarMoonkinTankAttackKigglerAction::Execute(Event event) bool HighKingMaulgarMoonkinTankAttackKigglerAction::Execute(Event event)
{ {
Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed"); Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed");
if (!kiggler)
return false;
MarkTargetWithDiamond(bot, kiggler); MarkTargetWithDiamond(bot, kiggler);
SetRtiTarget(botAI, "diamond", kiggler); SetRtiTarget(botAI, "diamond", kiggler);
if (bot->GetTarget() != kiggler->GetGUID()) if (bot->GetTarget() != kiggler->GetGUID())
{ return Attack(kiggler);
bot->SetSelection(kiggler->GetGUID());
return true;
}
Position safePos; Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos)) if (TryGetNewSafePosition(botAI, bot, safePos))
{ {
return MoveTo(kiggler->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ, return MoveTo(GRUULS_LAIR_MAP_ID, safePos.GetPositionX(), safePos.GetPositionY(), safePos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
} }
@@ -216,120 +192,105 @@ bool HighKingMaulgarAssignDPSPriorityAction::Execute(Event event)
{ {
// Target priority 1: Blindeye // Target priority 1: Blindeye
Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer"); Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer");
if (blindeye && blindeye->IsAlive()) if (blindeye)
{ {
Position safePos; Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos)) if (TryGetNewSafePosition(botAI, bot, safePos))
{ {
bot->AttackStop(); bot->AttackStop();
bot->InterruptNonMeleeSpells(false); bot->InterruptNonMeleeSpells(false);
return MoveTo(blindeye->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ, return MoveTo(GRUULS_LAIR_MAP_ID, safePos.GetPositionX(), safePos.GetPositionY(), safePos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
} }
SetRtiTarget(botAI, "star", blindeye); SetRtiTarget(botAI, "star", blindeye);
if (bot->GetTarget() != blindeye->GetGUID()) if (bot->GetTarget() != blindeye->GetGUID())
{
bot->SetSelection(blindeye->GetGUID());
return Attack(blindeye); return Attack(blindeye);
}
return false; return false;
} }
// Target priority 2: Olm // Target priority 2: Olm
Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner"); Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner");
if (olm && olm->IsAlive()) if (olm)
{ {
Position safePos; Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos)) if (TryGetNewSafePosition(botAI, bot, safePos))
{ {
bot->AttackStop(); bot->AttackStop();
bot->InterruptNonMeleeSpells(false); bot->InterruptNonMeleeSpells(false);
return MoveTo(olm->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ, return MoveTo(GRUULS_LAIR_MAP_ID, safePos.GetPositionX(), safePos.GetPositionY(), safePos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
} }
SetRtiTarget(botAI, "circle", olm); SetRtiTarget(botAI, "circle", olm);
if (bot->GetTarget() != olm->GetGUID()) if (bot->GetTarget() != olm->GetGUID())
{
bot->SetSelection(olm->GetGUID());
return Attack(olm); return Attack(olm);
}
return false; return false;
} }
// Target priority 3a: Krosh (ranged only) // Target priority 3a: Krosh (ranged only)
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand"); Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand");
if (krosh && krosh->IsAlive() && botAI->IsRanged(bot)) if (krosh && botAI->IsRanged(bot))
{ {
Position safePos; Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos)) if (TryGetNewSafePosition(botAI, bot, safePos))
{ {
bot->AttackStop(); bot->AttackStop();
bot->InterruptNonMeleeSpells(false); bot->InterruptNonMeleeSpells(false);
return MoveTo(krosh->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ, return MoveTo(GRUULS_LAIR_MAP_ID, safePos.GetPositionX(), safePos.GetPositionY(), safePos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
} }
SetRtiTarget(botAI, "triangle", krosh); SetRtiTarget(botAI, "triangle", krosh);
if (bot->GetTarget() != krosh->GetGUID()) if (bot->GetTarget() != krosh->GetGUID())
{
bot->SetSelection(krosh->GetGUID());
return Attack(krosh); return Attack(krosh);
}
return false; return false;
} }
// Target priority 3b: Kiggler // Target priority 3b: Kiggler
Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed"); Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed");
if (kiggler && kiggler->IsAlive()) if (kiggler)
{ {
Position safePos; Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos)) if (TryGetNewSafePosition(botAI, bot, safePos))
{ {
bot->AttackStop(); bot->AttackStop();
bot->InterruptNonMeleeSpells(false); bot->InterruptNonMeleeSpells(false);
return MoveTo(kiggler->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ, return MoveTo(GRUULS_LAIR_MAP_ID, safePos.GetPositionX(), safePos.GetPositionY(), safePos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
} }
SetRtiTarget(botAI, "diamond", kiggler); SetRtiTarget(botAI, "diamond", kiggler);
if (bot->GetTarget() != kiggler->GetGUID()) if (bot->GetTarget() != kiggler->GetGUID())
{
bot->SetSelection(kiggler->GetGUID());
return Attack(kiggler); return Attack(kiggler);
}
return false; return false;
} }
// Target priority 4: Maulgar // Target priority 4: Maulgar
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar"); Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
if (maulgar && maulgar->IsAlive()) if (maulgar)
{ {
Position safePos; Position safePos;
if (TryGetNewSafePosition(botAI, bot, safePos)) if (TryGetNewSafePosition(botAI, bot, safePos))
{ {
bot->AttackStop(); bot->AttackStop();
bot->InterruptNonMeleeSpells(false); bot->InterruptNonMeleeSpells(false);
return MoveTo(maulgar->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ, return MoveTo(GRUULS_LAIR_MAP_ID, safePos.GetPositionX(), safePos.GetPositionY(), safePos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
} }
SetRtiTarget(botAI, "square", maulgar); SetRtiTarget(botAI, "square", maulgar);
if (bot->GetTarget() != maulgar->GetGUID()) if (bot->GetTarget() != maulgar->GetGUID())
{
bot->SetSelection(maulgar->GetGUID());
return Attack(maulgar); return Attack(maulgar);
}
} }
return false; return false;
@@ -338,22 +299,22 @@ bool HighKingMaulgarAssignDPSPriorityAction::Execute(Event event)
// Avoid Whirlwind and Blast Wave and generally try to stay near the center of the room // Avoid Whirlwind and Blast Wave and generally try to stay near the center of the room
bool HighKingMaulgarHealerFindSafePositionAction::Execute(Event event) bool HighKingMaulgarHealerFindSafePositionAction::Execute(Event event)
{ {
const Location& fightCenter = GruulsLairLocations::MaulgarRoomCenter; const Position& center = MAULGAR_ROOM_CENTER;
const float maxDistanceFromFight = 50.0f; const float maxDistanceFromCenter = 50.0f;
float distToFight = bot->GetExactDist2d(fightCenter.x, fightCenter.y); float distToCenter = bot->GetExactDist2d(center.GetPositionX(), center.GetPositionY());
if (distToFight > maxDistanceFromFight) if (distToCenter > maxDistanceFromCenter)
{ {
float angle = atan2(bot->GetPositionY() - fightCenter.y, bot->GetPositionX() - fightCenter.x); float angle = atan2(bot->GetPositionY() - center.GetPositionY(), bot->GetPositionX() - center.GetPositionX());
float destX = fightCenter.x + 40.0f * cos(angle); float destX = center.GetPositionX() + 40.0f * cos(angle);
float destY = fightCenter.y + 40.0f * sin(angle); float destY = center.GetPositionY() + 40.0f * sin(angle);
float destZ = fightCenter.z; float destZ = center.GetPositionZ();
if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
bot->GetPositionZ(), destX, destY, destZ)) bot->GetPositionZ(), destX, destY, destZ))
return false; return false;
return MoveTo(bot->GetMapId(), destX, destY, destZ, false, false, false, false, return MoveTo(GRUULS_LAIR_MAP_ID, destX, destY, destZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false); MovementPriority::MOVEMENT_COMBAT, true, false);
} }
@@ -362,7 +323,7 @@ bool HighKingMaulgarHealerFindSafePositionAction::Execute(Event event)
{ {
bot->AttackStop(); bot->AttackStop();
bot->InterruptNonMeleeSpells(false); bot->InterruptNonMeleeSpells(false);
return MoveTo(bot->GetMapId(), safePos.m_positionX, safePos.m_positionY, safePos.m_positionZ, return MoveTo(GRUULS_LAIR_MAP_ID, safePos.GetPositionX(), safePos.GetPositionY(), safePos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false); false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
} }
@@ -373,6 +334,8 @@ bool HighKingMaulgarHealerFindSafePositionAction::Execute(Event event)
bool HighKingMaulgarRunAwayFromWhirlwindAction::Execute(Event event) bool HighKingMaulgarRunAwayFromWhirlwindAction::Execute(Event event)
{ {
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar"); Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
if (!maulgar)
return false;
const float safeDistance = 10.0f; const float safeDistance = 10.0f;
float distance = bot->GetExactDist2d(maulgar); float distance = bot->GetExactDist2d(maulgar);
@@ -395,7 +358,7 @@ bool HighKingMaulgarRunAwayFromWhirlwindAction::Execute(Event event)
{ {
bot->AttackStop(); bot->AttackStop();
bot->InterruptNonMeleeSpells(true); bot->InterruptNonMeleeSpells(true);
return MoveTo(maulgar->GetMapId(), destX, destY, destZ, false, false, false, false, return MoveTo(GRUULS_LAIR_MAP_ID, destX, destY, destZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false); MovementPriority::MOVEMENT_COMBAT, true, false);
} }
} }
@@ -439,7 +402,7 @@ bool HighKingMaulgarBanishFelstalkerAction::Execute(Event event)
if (warlockIndex >= 0 && warlockIndex < felStalkers.size()) if (warlockIndex >= 0 && warlockIndex < felStalkers.size())
{ {
Unit* assignedFelStalker = felStalkers[warlockIndex]; Unit* assignedFelStalker = felStalkers[warlockIndex];
if (!assignedFelStalker->HasAura(SPELL_BANISH) && botAI->CanCastSpell(SPELL_BANISH, assignedFelStalker, true)) if (!botAI->HasAura("banish", assignedFelStalker) && botAI->CanCastSpell("banish", assignedFelStalker))
return botAI->CastSpell("banish", assignedFelStalker); return botAI->CastSpell("banish", assignedFelStalker);
} }
@@ -528,40 +491,33 @@ bool HighKingMaulgarMisdirectOlmAndBlindeyeAction::Execute(Event event)
// Gruul the Dragonkiller Actions // Gruul the Dragonkiller Actions
// Position in center of the room // Position in center of the room
bool GruulTheDragonkillerMainTankPositionBossAction::Execute(Event event) bool GruulTheDragonkillerTanksPositionBossAction::Execute(Event event)
{ {
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller"); Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
if (!gruul)
return false;
if (bot->GetVictim() != gruul) if (bot->GetVictim() != gruul)
return Attack(gruul); return Attack(gruul);
if (gruul->GetVictim() == bot) if (gruul->GetVictim() == bot)
{ {
const Location& tankPosition = GruulsLairLocations::GruulTankPosition; const Position& position = GRUUL_TANK_POSITION;
const float maxDistance = 3.0f; const float maxDistance = 5.0f;
float dX = tankPosition.x - bot->GetPositionX(); float dX = position.GetPositionX() - bot->GetPositionX();
float dY = tankPosition.y - bot->GetPositionY(); float dY = position.GetPositionY() - bot->GetPositionY();
float distanceToTankPosition = bot->GetExactDist2d(tankPosition.x, tankPosition.y); float distanceToTankPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
if (distanceToTankPosition > maxDistance) if (distanceToTankPosition > maxDistance)
{ {
float step = std::min(maxDistance, distanceToTankPosition); 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 = tankPosition.z; const float moveZ = position.GetPositionZ();
return MoveTo(bot->GetMapId(), moveX, moveY, moveZ, false, false, false, false, return MoveTo(GRUULS_LAIR_MAP_ID, moveX, moveY, moveZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false); MovementPriority::MOVEMENT_COMBAT, true, false);
} }
float orientation = atan2(gruul->GetPositionY() - bot->GetPositionY(),
gruul->GetPositionX() - bot->GetPositionX());
bot->SetFacingTo(orientation);
}
else if (!bot->IsWithinMeleeRange(gruul))
{
return MoveTo(gruul->GetMapId(), gruul->GetPositionX(), gruul->GetPositionY(), gruul->GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true, false);
} }
return false; return false;
@@ -579,16 +535,16 @@ bool GruulTheDragonkillerSpreadRangedAction::Execute(Event event)
static std::unordered_map<ObjectGuid, bool> hasReachedInitialPosition; static std::unordered_map<ObjectGuid, bool> hasReachedInitialPosition;
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller"); Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
if (gruul && gruul->IsAlive() && gruul->GetHealth() == gruul->GetMaxHealth()) if (gruul && gruul->GetHealth() == gruul->GetMaxHealth())
{ {
initialPositions.clear(); initialPositions.erase(bot->GetGUID());
hasReachedInitialPosition.clear(); hasReachedInitialPosition.erase(bot->GetGUID());
} }
const Location& tankPosition = GruulsLairLocations::GruulTankPosition; const Position& position = GRUUL_TANK_POSITION;
const float centerX = tankPosition.x; const float centerX = position.GetPositionX();
const float centerY = tankPosition.y; const float centerY = position.GetPositionY();
float centerZ = bot->GetPositionZ(); const float centerZ = position.GetPositionZ();
const float minRadius = 25.0f; const float minRadius = 25.0f;
const float maxRadius = 40.0f; const float maxRadius = 40.0f;
@@ -642,7 +598,7 @@ bool GruulTheDragonkillerSpreadRangedAction::Execute(Event event)
bot->GetPositionY(), bot->GetPositionZ(), destX, destY, destZ)) bot->GetPositionY(), bot->GetPositionZ(), destX, destY, destZ))
return false; return false;
return MoveTo(bot->GetMapId(), destX, destY, destZ, false, false, false, false, return MoveTo(GRUULS_LAIR_MAP_ID, destX, destY, destZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false); MovementPriority::MOVEMENT_COMBAT, true, false);
} }

View File

@@ -85,10 +85,10 @@ public:
bool Execute(Event event) override; bool Execute(Event event) override;
}; };
class GruulTheDragonkillerMainTankPositionBossAction : public AttackAction class GruulTheDragonkillerTanksPositionBossAction : public AttackAction
{ {
public: public:
GruulTheDragonkillerMainTankPositionBossAction(PlayerbotAI* botAI, std::string const name = "gruul the dragonkiller main tank position boss") : AttackAction(botAI, name) {}; GruulTheDragonkillerTanksPositionBossAction(PlayerbotAI* botAI, std::string const name = "gruul the dragonkiller tanks position boss") : AttackAction(botAI, name) {};
bool Execute(Event event) override; bool Execute(Event event) override;
}; };

View File

@@ -8,18 +8,11 @@
#include "HunterActions.h" #include "HunterActions.h"
#include "MageActions.h" #include "MageActions.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "ReachTargetActions.h"
#include "WarriorActions.h" #include "WarriorActions.h"
using namespace GruulsLairHelpers; using namespace GruulsLairHelpers;
static bool IsChargeAction(Action* action)
{
return dynamic_cast<CastChargeAction*>(action) ||
dynamic_cast<CastInterceptAction*>(action) ||
dynamic_cast<CastFeralChargeBearAction*>(action) ||
dynamic_cast<CastFeralChargeCatAction*>(action);
}
float HighKingMaulgarDisableTankAssistMultiplier::GetValue(Action* action) float HighKingMaulgarDisableTankAssistMultiplier::GetValue(Action* action)
{ {
if (IsAnyOgreBossAlive(botAI) && dynamic_cast<TankAssistAction*>(action)) if (IsAnyOgreBossAlive(botAI) && dynamic_cast<TankAssistAction*>(action))
@@ -38,12 +31,10 @@ float HighKingMaulgarAvoidWhirlwindMultiplier::GetValue(Action* action)
Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer"); Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer");
if (maulgar && maulgar->HasAura(SPELL_WHIRLWIND) && if (maulgar && maulgar->HasAura(SPELL_WHIRLWIND) &&
(!kiggler || !kiggler->IsAlive()) && !kiggler && !krosh && !olm && !blindeye)
(!krosh || !krosh->IsAlive()) &&
(!olm || !olm->IsAlive()) &&
(!blindeye || !blindeye->IsAlive()))
{ {
if (IsChargeAction(action) || (dynamic_cast<MovementAction*>(action) && if (dynamic_cast<CastReachTargetSpellAction*>(action) ||
(dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<HighKingMaulgarRunAwayFromWhirlwindAction*>(action))) !dynamic_cast<HighKingMaulgarRunAwayFromWhirlwindAction*>(action)))
return 0.0f; return 0.0f;
} }
@@ -57,7 +48,8 @@ float HighKingMaulgarDisableArcaneShotOnKroshMultiplier::GetValue(Action* action
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand"); Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand");
Unit* target = AI_VALUE(Unit*, "current target"); Unit* target = AI_VALUE(Unit*, "current target");
if (krosh && target && target->GetGUID() == krosh->GetGUID() && dynamic_cast<CastArcaneShotAction*>(action)) if (krosh && target && target->GetGUID() == krosh->GetGUID() &&
dynamic_cast<CastArcaneShotAction*>(action))
return 0.0f; return 0.0f;
return 1.0f; return 1.0f;
@@ -101,8 +93,9 @@ float GruulTheDragonkillerGroundSlamMultiplier::GetValue(Action* action)
if (bot->HasAura(SPELL_GROUND_SLAM_1) || if (bot->HasAura(SPELL_GROUND_SLAM_1) ||
bot->HasAura(SPELL_GROUND_SLAM_2)) bot->HasAura(SPELL_GROUND_SLAM_2))
{ {
if ((dynamic_cast<MovementAction*>(action) && !dynamic_cast<GruulTheDragonkillerShatterSpreadAction*>(action)) || if ((dynamic_cast<MovementAction*>(action) &&
IsChargeAction(action)) !dynamic_cast<GruulTheDragonkillerShatterSpreadAction*>(action)) ||
dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f; return 0.0f;
} }

View File

@@ -22,7 +22,7 @@ public:
creators["high king maulgar misdirect olm and blindeye"] = &RaidGruulsLairActionContext::high_king_maulgar_misdirect_olm_and_blindeye; creators["high king maulgar misdirect olm and blindeye"] = &RaidGruulsLairActionContext::high_king_maulgar_misdirect_olm_and_blindeye;
// Gruul the Dragonkiller // Gruul the Dragonkiller
creators["gruul the dragonkiller main tank position boss"] = &RaidGruulsLairActionContext::gruul_the_dragonkiller_main_tank_position_boss; creators["gruul the dragonkiller tanks position boss"] = &RaidGruulsLairActionContext::gruul_the_dragonkiller_tanks_position_boss;
creators["gruul the dragonkiller spread ranged"] = &RaidGruulsLairActionContext::gruul_the_dragonkiller_spread_ranged; creators["gruul the dragonkiller spread ranged"] = &RaidGruulsLairActionContext::gruul_the_dragonkiller_spread_ranged;
creators["gruul the dragonkiller shatter spread"] = &RaidGruulsLairActionContext::gruul_the_dragonkiller_shatter_spread; creators["gruul the dragonkiller shatter spread"] = &RaidGruulsLairActionContext::gruul_the_dragonkiller_shatter_spread;
} }
@@ -41,7 +41,7 @@ private:
static Action* high_king_maulgar_misdirect_olm_and_blindeye(PlayerbotAI* botAI) { return new HighKingMaulgarMisdirectOlmAndBlindeyeAction(botAI); } static Action* high_king_maulgar_misdirect_olm_and_blindeye(PlayerbotAI* botAI) { return new HighKingMaulgarMisdirectOlmAndBlindeyeAction(botAI); }
// Gruul the Dragonkiller // Gruul the Dragonkiller
static Action* gruul_the_dragonkiller_main_tank_position_boss(PlayerbotAI* botAI) { return new GruulTheDragonkillerMainTankPositionBossAction(botAI); } static Action* gruul_the_dragonkiller_tanks_position_boss(PlayerbotAI* botAI) { return new GruulTheDragonkillerTanksPositionBossAction(botAI); }
static Action* gruul_the_dragonkiller_spread_ranged(PlayerbotAI* botAI) { return new GruulTheDragonkillerSpreadRangedAction(botAI); } static Action* gruul_the_dragonkiller_spread_ranged(PlayerbotAI* botAI) { return new GruulTheDragonkillerSpreadRangedAction(botAI); }
static Action* gruul_the_dragonkiller_shatter_spread(PlayerbotAI* botAI) { return new GruulTheDragonkillerShatterSpreadAction(botAI); } static Action* gruul_the_dragonkiller_shatter_spread(PlayerbotAI* botAI) { return new GruulTheDragonkillerShatterSpreadAction(botAI); }
}; };

View File

@@ -22,8 +22,8 @@ public:
creators["high king maulgar pulling olm and blindeye"] = &RaidGruulsLairTriggerContext::high_king_maulgar_pulling_olm_and_blindeye; creators["high king maulgar pulling olm and blindeye"] = &RaidGruulsLairTriggerContext::high_king_maulgar_pulling_olm_and_blindeye;
// Gruul the Dragonkiller // Gruul the Dragonkiller
creators["gruul the dragonkiller boss engaged by main tank"] = &RaidGruulsLairTriggerContext::gruul_the_dragonkiller_boss_engaged_by_main_tank; creators["gruul the dragonkiller boss engaged by tanks"] = &RaidGruulsLairTriggerContext::gruul_the_dragonkiller_boss_engaged_by_tanks;
creators["gruul the dragonkiller boss engaged by range"] = &RaidGruulsLairTriggerContext::gruul_the_dragonkiller_boss_engaged_by_range; creators["gruul the dragonkiller boss engaged by ranged"] = &RaidGruulsLairTriggerContext::gruul_the_dragonkiller_boss_engaged_by_ranged;
creators["gruul the dragonkiller incoming shatter"] = &RaidGruulsLairTriggerContext::gruul_the_dragonkiller_incoming_shatter; creators["gruul the dragonkiller incoming shatter"] = &RaidGruulsLairTriggerContext::gruul_the_dragonkiller_incoming_shatter;
} }
@@ -41,8 +41,8 @@ private:
static Trigger* high_king_maulgar_pulling_olm_and_blindeye(PlayerbotAI* botAI) { return new HighKingMaulgarPullingOlmAndBlindeyeTrigger(botAI); } static Trigger* high_king_maulgar_pulling_olm_and_blindeye(PlayerbotAI* botAI) { return new HighKingMaulgarPullingOlmAndBlindeyeTrigger(botAI); }
// Gruul the Dragonkiller // Gruul the Dragonkiller
static Trigger* gruul_the_dragonkiller_boss_engaged_by_main_tank(PlayerbotAI* botAI) { return new GruulTheDragonkillerBossEngagedByMainTankTrigger(botAI); } static Trigger* gruul_the_dragonkiller_boss_engaged_by_tanks(PlayerbotAI* botAI) { return new GruulTheDragonkillerBossEngagedByTanksTrigger(botAI); }
static Trigger* gruul_the_dragonkiller_boss_engaged_by_range(PlayerbotAI* botAI) { return new GruulTheDragonkillerBossEngagedByRangeTrigger(botAI); } static Trigger* gruul_the_dragonkiller_boss_engaged_by_ranged(PlayerbotAI* botAI) { return new GruulTheDragonkillerBossEngagedByRangedTrigger(botAI); }
static Trigger* gruul_the_dragonkiller_incoming_shatter(PlayerbotAI* botAI) { return new GruulTheDragonkillerIncomingShatterTrigger(botAI); } static Trigger* gruul_the_dragonkiller_incoming_shatter(PlayerbotAI* botAI) { return new GruulTheDragonkillerIncomingShatterTrigger(botAI); }
}; };

View File

@@ -35,10 +35,10 @@ void RaidGruulsLairStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
NextAction("high king maulgar misdirect olm and blindeye", ACTION_RAID + 2) })); NextAction("high king maulgar misdirect olm and blindeye", ACTION_RAID + 2) }));
// Gruul the Dragonkiller // Gruul the Dragonkiller
triggers.push_back(new TriggerNode("gruul the dragonkiller boss engaged by main tank", { triggers.push_back(new TriggerNode("gruul the dragonkiller boss engaged by tanks", {
NextAction("gruul the dragonkiller main tank position boss", ACTION_RAID + 1) })); NextAction("gruul the dragonkiller tanks position boss", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("gruul the dragonkiller boss engaged by range", { triggers.push_back(new TriggerNode("gruul the dragonkiller boss engaged by ranged", {
NextAction("gruul the dragonkiller spread ranged", ACTION_RAID + 1) })); NextAction("gruul the dragonkiller spread ranged", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("gruul the dragonkiller incoming shatter", { triggers.push_back(new TriggerNode("gruul the dragonkiller incoming shatter", {

View File

@@ -10,35 +10,35 @@ bool HighKingMaulgarIsMainTankTrigger::IsActive()
{ {
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar"); Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
return botAI->IsMainTank(bot) && maulgar && maulgar->IsAlive(); return botAI->IsMainTank(bot) && maulgar;
} }
bool HighKingMaulgarIsFirstAssistTankTrigger::IsActive() bool HighKingMaulgarIsFirstAssistTankTrigger::IsActive()
{ {
Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner"); Unit* olm = AI_VALUE2(Unit*, "find target", "olm the summoner");
return botAI->IsAssistTankOfIndex(bot, 0) && olm && olm->IsAlive(); return botAI->IsAssistTankOfIndex(bot, 0, false) && olm;
} }
bool HighKingMaulgarIsSecondAssistTankTrigger::IsActive() bool HighKingMaulgarIsSecondAssistTankTrigger::IsActive()
{ {
Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer"); Unit* blindeye = AI_VALUE2(Unit*, "find target", "blindeye the seer");
return botAI->IsAssistTankOfIndex(bot, 1) && blindeye && blindeye->IsAlive(); return botAI->IsAssistTankOfIndex(bot, 1, false) && blindeye;
} }
bool HighKingMaulgarIsMageTankTrigger::IsActive() bool HighKingMaulgarIsMageTankTrigger::IsActive()
{ {
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand"); Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand");
return IsKroshMageTank(botAI, bot) && krosh && krosh->IsAlive(); return IsKroshMageTank(botAI, bot) && krosh;
} }
bool HighKingMaulgarIsMoonkinTankTrigger::IsActive() bool HighKingMaulgarIsMoonkinTankTrigger::IsActive()
{ {
Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed"); Unit* kiggler = AI_VALUE2(Unit*, "find target", "kiggler the crazed");
return IsKigglerMoonkinTank(botAI, bot) && kiggler && kiggler->IsAlive(); return IsKigglerMoonkinTank(botAI, bot) && kiggler;
} }
bool HighKingMaulgarDeterminingKillOrderTrigger::IsActive() bool HighKingMaulgarDeterminingKillOrderTrigger::IsActive()
@@ -50,11 +50,11 @@ bool HighKingMaulgarDeterminingKillOrderTrigger::IsActive()
Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand"); Unit* krosh = AI_VALUE2(Unit*, "find target", "krosh firehand");
return (botAI->IsDps(bot) || botAI->IsTank(bot)) && return (botAI->IsDps(bot) || botAI->IsTank(bot)) &&
!(botAI->IsMainTank(bot) && maulgar && maulgar->IsAlive()) && !(botAI->IsMainTank(bot) && maulgar) &&
!(botAI->IsAssistTankOfIndex(bot, 0) && olm && olm->IsAlive()) && !(botAI->IsAssistTankOfIndex(bot, 0, false) && olm) &&
!(botAI->IsAssistTankOfIndex(bot, 1) && blindeye && blindeye->IsAlive()) && !(botAI->IsAssistTankOfIndex(bot, 1, false) && blindeye) &&
!(IsKroshMageTank(botAI, bot) && krosh && krosh->IsAlive()) && !(IsKroshMageTank(botAI, bot) && krosh) &&
!(IsKigglerMoonkinTank(botAI, bot) && kiggler && kiggler->IsAlive()); !(IsKigglerMoonkinTank(botAI, bot) && kiggler);
} }
bool HighKingMaulgarHealerInDangerTrigger::IsActive() bool HighKingMaulgarHealerInDangerTrigger::IsActive()
@@ -66,7 +66,7 @@ bool HighKingMaulgarBossChannelingWhirlwindTrigger::IsActive()
{ {
Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar"); Unit* maulgar = AI_VALUE2(Unit*, "find target", "high king maulgar");
return maulgar && maulgar->IsAlive() && maulgar->HasAura(SPELL_WHIRLWIND) && return maulgar && maulgar->HasAura(SPELL_WHIRLWIND) &&
!botAI->IsMainTank(bot); !botAI->IsMainTank(bot);
} }
@@ -74,7 +74,7 @@ bool HighKingMaulgarWildFelstalkerSpawnedTrigger::IsActive()
{ {
Unit* felStalker = AI_VALUE2(Unit*, "find target", "wild fel stalker"); Unit* felStalker = AI_VALUE2(Unit*, "find target", "wild fel stalker");
return felStalker && felStalker->IsAlive() && bot->getClass() == CLASS_WARLOCK; return felStalker && bot->getClass() == CLASS_WARLOCK;
} }
bool HighKingMaulgarPullingOlmAndBlindeyeTrigger::IsActive() bool HighKingMaulgarPullingOlmAndBlindeyeTrigger::IsActive()
@@ -120,12 +120,12 @@ bool HighKingMaulgarPullingOlmAndBlindeyeTrigger::IsActive()
switch (hunterIndex) switch (hunterIndex)
{ {
case 0: case 0:
return olm && olm->IsAlive() && olm->GetHealthPct() > 98.0f && return olm && olm->GetHealthPct() > 98.0f &&
olmTank && olmTank->IsAlive() && botAI->CanCastSpell("misdirection", olmTank); olmTank && botAI->CanCastSpell("misdirection", olmTank);
case 1: case 1:
return blindeye && blindeye->IsAlive() && blindeye->GetHealthPct() > 90.0f && return blindeye && blindeye->GetHealthPct() > 90.0f &&
blindeyeTank && blindeyeTank->IsAlive() && botAI->CanCastSpell("misdirection", blindeyeTank); blindeyeTank && botAI->CanCastSpell("misdirection", blindeyeTank);
default: default:
break; break;
@@ -136,25 +136,24 @@ bool HighKingMaulgarPullingOlmAndBlindeyeTrigger::IsActive()
// Gruul the Dragonkiller Triggers // Gruul the Dragonkiller Triggers
bool GruulTheDragonkillerBossEngagedByMainTankTrigger::IsActive() bool GruulTheDragonkillerBossEngagedByTanksTrigger::IsActive()
{ {
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller"); Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
return gruul && gruul->IsAlive() && botAI->IsMainTank(bot); return gruul && botAI->IsTank(bot);
} }
bool GruulTheDragonkillerBossEngagedByRangeTrigger::IsActive() bool GruulTheDragonkillerBossEngagedByRangedTrigger::IsActive()
{ {
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller"); Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
return gruul && gruul->IsAlive() && botAI->IsRanged(bot); return gruul && botAI->IsRanged(bot);
} }
bool GruulTheDragonkillerIncomingShatterTrigger::IsActive() bool GruulTheDragonkillerIncomingShatterTrigger::IsActive()
{ {
Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller"); Unit* gruul = AI_VALUE2(Unit*, "find target", "gruul the dragonkiller");
return gruul && gruul->IsAlive() && return gruul && (bot->HasAura(SPELL_GROUND_SLAM_1) ||
(bot->HasAura(SPELL_GROUND_SLAM_1) || bot->HasAura(SPELL_GROUND_SLAM_2));
bot->HasAura(SPELL_GROUND_SLAM_2));
} }

View File

@@ -73,17 +73,17 @@ public:
bool IsActive() override; bool IsActive() override;
}; };
class GruulTheDragonkillerBossEngagedByMainTankTrigger : public Trigger class GruulTheDragonkillerBossEngagedByTanksTrigger : public Trigger
{ {
public: public:
GruulTheDragonkillerBossEngagedByMainTankTrigger(PlayerbotAI* botAI) : Trigger(botAI, "gruul the dragonkiller boss engaged by main tank") {} GruulTheDragonkillerBossEngagedByTanksTrigger(PlayerbotAI* botAI) : Trigger(botAI, "gruul the dragonkiller boss engaged by tanks") {}
bool IsActive() override; bool IsActive() override;
}; };
class GruulTheDragonkillerBossEngagedByRangeTrigger : public Trigger class GruulTheDragonkillerBossEngagedByRangedTrigger : public Trigger
{ {
public: public:
GruulTheDragonkillerBossEngagedByRangeTrigger(PlayerbotAI* botAI) : Trigger(botAI, "gruul the dragonkiller boss engaged by range") {} GruulTheDragonkillerBossEngagedByRangedTrigger(PlayerbotAI* botAI) : Trigger(botAI, "gruul the dragonkiller boss engaged by ranged") {}
bool IsActive() override; bool IsActive() override;
}; };

View File

@@ -6,19 +6,16 @@
namespace GruulsLairHelpers namespace GruulsLairHelpers
{ {
namespace GruulsLairLocations // Olm does not chase properly due to the Core's caster movement issues
{ // Thus, the below "OlmTankPosition" is beyond the actual desired tanking location
// Olm does not chase properly due to the Core's caster movement issues // It is the spot to which the OlmTank runs to to pull Olm to a decent tanking location
// Thus, the below "OlmTankPosition" is beyond the actual desired tanking location // "MaulgarRoomCenter" is to keep healers in a centralized location
// It is the spot to which the OlmTank runs to to pull Olm to a decent tanking location const Position MAULGAR_TANK_POSITION = { 90.686f, 167.047f, -13.234f };
// "MaulgarRoomCenter" is to keep healers in a centralized location const Position OLM_TANK_POSITION = { 87.485f, 234.942f, -3.635f };
const Location MaulgarTankPosition = { 90.686f, 167.047f, -13.234f }; const Position BLINDEYE_TANK_POSITION = { 99.681f, 213.989f, -10.345f };
const Location OlmTankPosition = { 87.485f, 234.942f, -3.635f }; const Position KROSH_TANK_POSITION = { 116.880f, 166.208f, -14.231f };
const Location BlindeyeTankPosition = { 99.681f, 213.989f, -10.345f }; const Position MAULGAR_ROOM_CENTER = { 88.754f, 150.759f, -11.569f };
const Location KroshTankPosition = { 116.880f, 166.208f, -14.231f }; const Position GRUUL_TANK_POSITION = { 241.238f, 365.025f, -4.220f };
const Location MaulgarRoomCenter = { 88.754f, 150.759f, -11.569f };
const Location GruulTankPosition = { 241.238f, 365.025f, -4.220f };
}
bool IsAnyOgreBossAlive(PlayerbotAI* botAI) bool IsAnyOgreBossAlive(PlayerbotAI* botAI)
{ {
@@ -42,84 +39,43 @@ namespace GruulsLairHelpers
return false; return false;
} }
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId)
{
Group* group = bot->GetGroup();
if (!target || !group)
return;
ObjectGuid currentGuid = group->GetTargetIcon(iconId);
if (currentGuid != target->GetGUID())
{
group->SetTargetIcon(iconId, bot->GetGUID(), target->GetGUID());
}
}
void MarkTargetWithSquare(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::squareIndex);
}
void MarkTargetWithStar(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::starIndex);
}
void MarkTargetWithCircle(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::circleIndex);
}
void MarkTargetWithDiamond(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::diamondIndex);
}
void MarkTargetWithTriangle(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::triangleIndex);
}
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target)
{
if (!target)
return;
std::string currentRti = botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Get();
Unit* currentTarget = botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Get();
if (currentRti != rtiName || currentTarget != target)
{
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set(rtiName);
botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Set(target);
}
}
bool IsKroshMageTank(PlayerbotAI* botAI, Player* bot) bool IsKroshMageTank(PlayerbotAI* botAI, Player* bot)
{ {
Group* group = bot->GetGroup(); Group* group = bot->GetGroup();
if (!group) if (!group)
return false; return false;
Player* highestHpMage = nullptr; // (1) First loop: Return the first assistant Mage (real player or bot)
uint32 highestHp = 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 || !member->IsAlive() || !GET_PLAYERBOT_AI(member)) if (!member || !member->IsAlive() || member->getClass() != CLASS_MAGE)
continue; continue;
if (member->getClass() == CLASS_MAGE) if (group->IsAssistant(member->GetGUID()))
return member == bot;
}
// (2) Fall back to bot Mage with highest HP
Player* highestHpMage = nullptr;
uint32 highestHp = 0;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
member->getClass() != CLASS_MAGE)
continue;
uint32 hp = member->GetMaxHealth();
if (!highestHpMage || hp > highestHp)
{ {
uint32 hp = member->GetMaxHealth(); highestHpMage = member;
if (!highestHpMage || hp > highestHp) highestHp = hp;
{
highestHpMage = member;
highestHp = hp;
}
} }
} }
// (3) Return the found Mage tank, or nullptr if none found
return highestHpMage == bot; return highestHpMage == bot;
} }
@@ -129,30 +85,37 @@ namespace GruulsLairHelpers
if (!group) if (!group)
return false; return false;
Player* highestHpMoonkin = nullptr; // (1) First loop: Return the first assistant Moonkin (real player or bot)
uint32 highestHp = 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 || !member->IsAlive() || !GET_PLAYERBOT_AI(member)) if (!member || !member->IsAlive() || member->getClass() != CLASS_DRUID)
continue; continue;
if (member->getClass() == CLASS_DRUID) if (group->IsAssistant(member->GetGUID()) &&
AiFactory::GetPlayerSpecTab(member) == DRUID_TAB_BALANCE)
return member == bot;
}
// (2) Fall back to bot Moonkin with highest HP
Player* highestHpMoonkin = nullptr;
uint32 highestHp = 0;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member->getClass() != CLASS_DRUID ||
!GET_PLAYERBOT_AI(member) || AiFactory::GetPlayerSpecTab(member) != DRUID_TAB_BALANCE)
continue;
uint32 hp = member->GetMaxHealth();
if (!highestHpMoonkin || hp > highestHp)
{ {
int tab = AiFactory::GetPlayerSpecTab(member); highestHpMoonkin = member;
if (tab == DRUID_TAB_BALANCE) highestHp = hp;
{
uint32 hp = member->GetMaxHealth();
if (!highestHpMoonkin || hp > highestHp)
{
highestHpMoonkin = member;
highestHp = hp;
}
}
} }
} }
// (3) Return the found Moonkin tank, or nullptr if none found
return highestHpMoonkin == bot; return highestHpMoonkin == bot;
} }

View File

@@ -2,23 +2,19 @@
#define RAID_GRUULSLAIRHELPERS_H #define RAID_GRUULSLAIRHELPERS_H
#include "PlayerbotAI.h" #include "PlayerbotAI.h"
#include "RtiTargetValue.h"
namespace GruulsLairHelpers namespace GruulsLairHelpers
{ {
enum GruulsLairSpells enum GruulsLairSpells
{ {
// High King Maulgar // High King Maulgar
SPELL_WHIRLWIND = 33238, SPELL_WHIRLWIND = 33238,
// Krosh Firehand // Krosh Firehand
SPELL_SPELL_SHIELD = 33054, SPELL_SPELL_SHIELD = 33054,
// Hunter // Hunter
SPELL_MISDIRECTION = 35079, SPELL_MISDIRECTION = 35079,
// Warlock
SPELL_BANISH = 18647, // Rank 2
// Gruul the Dragonkiller // Gruul the Dragonkiller
SPELL_GROUND_SLAM_1 = 33525, SPELL_GROUND_SLAM_1 = 33525,
@@ -30,33 +26,20 @@ namespace GruulsLairHelpers
NPC_WILD_FEL_STALKER = 18847, NPC_WILD_FEL_STALKER = 18847,
}; };
constexpr uint32 GRUULS_LAIR_MAP_ID = 565;
bool IsAnyOgreBossAlive(PlayerbotAI* botAI); bool IsAnyOgreBossAlive(PlayerbotAI* botAI);
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId);
void MarkTargetWithSquare(Player* bot, Unit* target);
void MarkTargetWithStar(Player* bot, Unit* target);
void MarkTargetWithCircle(Player* bot, Unit* target);
void MarkTargetWithDiamond(Player* bot, Unit* target);
void MarkTargetWithTriangle(Player* bot, Unit* target);
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target);
bool IsKroshMageTank(PlayerbotAI* botAI, Player* bot); bool IsKroshMageTank(PlayerbotAI* botAI, Player* bot);
bool IsKigglerMoonkinTank(PlayerbotAI* botAI, Player* bot); bool IsKigglerMoonkinTank(PlayerbotAI* botAI, Player* bot);
bool IsPositionSafe(PlayerbotAI* botAI, Player* bot, Position pos); bool IsPositionSafe(PlayerbotAI* botAI, Player* bot, Position pos);
bool TryGetNewSafePosition(PlayerbotAI* botAI, Player* bot, Position& outPos); bool TryGetNewSafePosition(PlayerbotAI* botAI, Player* bot, Position& outPos);
struct Location extern const Position MAULGAR_TANK_POSITION;
{ extern const Position OLM_TANK_POSITION;
float x, y, z; extern const Position BLINDEYE_TANK_POSITION;
}; extern const Position KROSH_TANK_POSITION;
extern const Position MAULGAR_ROOM_CENTER;
namespace GruulsLairLocations extern const Position GRUUL_TANK_POSITION;
{
extern const Location MaulgarTankPosition;
extern const Location OlmTankPosition;
extern const Location BlindeyeTankPosition;
extern const Location KroshTankPosition;
extern const Location MaulgarRoomCenter;
extern const Location GruulTankPosition;
}
} }
#endif #endif

View File

@@ -2,6 +2,7 @@
#include "RaidKarazhanHelpers.h" #include "RaidKarazhanHelpers.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "PlayerbotTextMgr.h" #include "PlayerbotTextMgr.h"
#include "RaidBossHelpers.h"
using namespace KarazhanHelpers; using namespace KarazhanHelpers;
@@ -44,7 +45,7 @@ bool AttumenTheHuntsmanMarkTargetAction::Execute(Event event)
Unit* attumenMounted = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED); Unit* attumenMounted = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED);
if (attumenMounted) if (attumenMounted)
{ {
if (IsInstanceTimerManager(botAI, bot)) if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
MarkTargetWithStar(bot, attumenMounted); MarkTargetWithStar(bot, attumenMounted);
SetRtiTarget(botAI, "star", attumenMounted); SetRtiTarget(botAI, "star", attumenMounted);
@@ -57,7 +58,7 @@ bool AttumenTheHuntsmanMarkTargetAction::Execute(Event event)
} }
else if (Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight")) else if (Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight"))
{ {
if (IsInstanceTimerManager(botAI, bot)) if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
MarkTargetWithStar(bot, midnight); MarkTargetWithStar(bot, midnight);
if (!botAI->IsAssistTankOfIndex(bot, 0)) if (!botAI->IsAssistTankOfIndex(bot, 0))
@@ -180,7 +181,7 @@ bool MoroesMarkTargetAction::Execute(Event event)
if (target) if (target)
{ {
if (IsInstanceTimerManager(botAI, bot)) if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
MarkTargetWithSkull(bot, target); MarkTargetWithSkull(bot, target);
SetRtiTarget(botAI, "skull", target); SetRtiTarget(botAI, "skull", target);
@@ -405,7 +406,7 @@ bool TheCuratorMarkAstralFlareAction::Execute(Event event)
if (!flare) if (!flare)
return false; return false;
if (IsInstanceTimerManager(botAI, bot)) if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
MarkTargetWithSkull(bot, flare); MarkTargetWithSkull(bot, flare);
SetRtiTarget(botAI, "skull", flare); SetRtiTarget(botAI, "skull", flare);
@@ -469,11 +470,11 @@ bool TheCuratorSpreadRangedAction::Execute(Event event)
// Prioritize (1) Demon Chains, (2) Kil'rek, (3) Illhoof // Prioritize (1) Demon Chains, (2) Kil'rek, (3) Illhoof
bool TerestianIllhoofMarkTargetAction::Execute(Event event) bool TerestianIllhoofMarkTargetAction::Execute(Event event)
{ {
Unit* demonChains = AI_VALUE2(Unit*, "find target", "demon chains"); Unit* demonChains = GetFirstAliveUnitByEntry(botAI, NPC_DEMON_CHAINS);
Unit* kilrek = AI_VALUE2(Unit*, "find target", "kil'rek"); Unit* kilrek = GetFirstAliveUnitByEntry(botAI, NPC_KILREK);
Unit* illhoof = AI_VALUE2(Unit*, "find target", "terestian illhoof"); Unit* illhoof = AI_VALUE2(Unit*, "find target", "terestian illhoof");
Unit* target = GetFirstAliveUnit({demonChains, kilrek, illhoof});
Unit* target = GetFirstAliveUnit({demonChains, kilrek, illhoof});
if (target) if (target)
MarkTargetWithSkull(bot, target); MarkTargetWithSkull(bot, target);
@@ -1007,7 +1008,7 @@ bool NetherspiteManageTimersAndTrackersAction::Execute(Event event)
if (netherspite->GetHealth() == netherspite->GetMaxHealth() && if (netherspite->GetHealth() == netherspite->GetMaxHealth() &&
!netherspite->HasAura(SPELL_GREEN_BEAM_HEAL)) !netherspite->HasAura(SPELL_GREEN_BEAM_HEAL))
{ {
if (IsInstanceTimerManager(botAI, bot)) if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
netherspiteDpsWaitTimer.insert_or_assign(instanceId, now); netherspiteDpsWaitTimer.insert_or_assign(instanceId, now);
if (botAI->IsTank(bot) && !bot->HasAura(SPELL_RED_BEAM_DEBUFF)) if (botAI->IsTank(bot) && !bot->HasAura(SPELL_RED_BEAM_DEBUFF))
@@ -1018,7 +1019,7 @@ bool NetherspiteManageTimersAndTrackersAction::Execute(Event event)
} }
else if (netherspite->HasAura(SPELL_NETHERSPITE_BANISHED)) else if (netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
{ {
if (IsInstanceTimerManager(botAI, bot)) if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
netherspiteDpsWaitTimer.erase(instanceId); netherspiteDpsWaitTimer.erase(instanceId);
if (botAI->IsTank(bot)) if (botAI->IsTank(bot))
@@ -1029,7 +1030,7 @@ bool NetherspiteManageTimersAndTrackersAction::Execute(Event event)
} }
else if (!netherspite->HasAura(SPELL_NETHERSPITE_BANISHED)) else if (!netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
{ {
if (IsInstanceTimerManager(botAI, bot)) if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
netherspiteDpsWaitTimer.try_emplace(instanceId, now); netherspiteDpsWaitTimer.try_emplace(instanceId, now);
if (botAI->IsTank(bot) && bot->HasAura(SPELL_RED_BEAM_DEBUFF)) if (botAI->IsTank(bot) && bot->HasAura(SPELL_RED_BEAM_DEBUFF))
@@ -1458,7 +1459,7 @@ bool NightbaneManageTimersAndTrackersAction::Execute(Event event)
if (botAI->IsRanged(bot)) if (botAI->IsRanged(bot))
nightbaneRangedStep.erase(botGuid); nightbaneRangedStep.erase(botGuid);
if (IsInstanceTimerManager(botAI, bot)) if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
nightbaneDpsWaitTimer.erase(instanceId); nightbaneDpsWaitTimer.erase(instanceId);
} }
// Erase flight phase timer and Rain of Bones tracker on ground phase and start DPS wait timer // Erase flight phase timer and Rain of Bones tracker on ground phase and start DPS wait timer
@@ -1466,7 +1467,7 @@ bool NightbaneManageTimersAndTrackersAction::Execute(Event event)
{ {
nightbaneRainOfBonesHit.erase(botGuid); nightbaneRainOfBonesHit.erase(botGuid);
if (IsInstanceTimerManager(botAI, bot)) if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
{ {
nightbaneFlightPhaseStartTimer.erase(instanceId); nightbaneFlightPhaseStartTimer.erase(instanceId);
nightbaneDpsWaitTimer.try_emplace(instanceId, now); nightbaneDpsWaitTimer.try_emplace(instanceId, now);
@@ -1482,7 +1483,7 @@ bool NightbaneManageTimersAndTrackersAction::Execute(Event event)
if (botAI->IsRanged(bot)) if (botAI->IsRanged(bot))
nightbaneRangedStep.erase(botGuid); nightbaneRangedStep.erase(botGuid);
if (IsInstanceTimerManager(botAI, bot)) if (IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
{ {
nightbaneDpsWaitTimer.erase(instanceId); nightbaneDpsWaitTimer.erase(instanceId);
nightbaneFlightPhaseStartTimer.try_emplace(instanceId, now); nightbaneFlightPhaseStartTimer.try_emplace(instanceId, now);

View File

@@ -10,6 +10,7 @@
#include "MageActions.h" #include "MageActions.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "PriestActions.h" #include "PriestActions.h"
#include "RaidBossHelpers.h"
#include "ReachTargetActions.h" #include "ReachTargetActions.h"
#include "RogueActions.h" #include "RogueActions.h"
#include "ShamanActions.h" #include "ShamanActions.h"
@@ -242,6 +243,9 @@ float PrinceMalchezaarEnfeebleKeepDistanceMultiplier::GetValue(Action* action)
if (bot->HasAura(SPELL_ENFEEBLE)) if (bot->HasAura(SPELL_ENFEEBLE))
{ {
if (dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
if (dynamic_cast<MovementAction*>(action) && if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<PrinceMalchezaarEnfeebledAvoidHazardAction*>(action)) !dynamic_cast<PrinceMalchezaarEnfeebledAvoidHazardAction*>(action))
return 0.0f; return 0.0f;

View File

@@ -2,6 +2,7 @@
#include "RaidKarazhanHelpers.h" #include "RaidKarazhanHelpers.h"
#include "RaidKarazhanActions.h" #include "RaidKarazhanActions.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "RaidBossHelpers.h"
using namespace KarazhanHelpers; using namespace KarazhanHelpers;
@@ -40,7 +41,7 @@ bool AttumenTheHuntsmanAttumenIsMountedTrigger::IsActive()
bool AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger::IsActive() bool AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger::IsActive()
{ {
if (!IsInstanceTimerManager(botAI, bot)) if (!IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
return false; return false;
Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight"); Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight");
@@ -110,7 +111,7 @@ bool BigBadWolfBossIsChasingLittleRedRidingHoodTrigger::IsActive()
bool RomuloAndJulianneBothBossesRevivedTrigger::IsActive() bool RomuloAndJulianneBothBossesRevivedTrigger::IsActive()
{ {
if (!IsInstanceTimerManager(botAI, bot)) if (!IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
return false; return false;
Unit* romulo = AI_VALUE2(Unit*, "find target", "romulo"); Unit* romulo = AI_VALUE2(Unit*, "find target", "romulo");
@@ -126,7 +127,7 @@ bool RomuloAndJulianneBothBossesRevivedTrigger::IsActive()
bool WizardOfOzNeedTargetPriorityTrigger::IsActive() bool WizardOfOzNeedTargetPriorityTrigger::IsActive()
{ {
if (!IsInstanceTimerManager(botAI, bot)) if (!IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
return false; return false;
Unit* dorothee = AI_VALUE2(Unit*, "find target", "dorothee"); Unit* dorothee = AI_VALUE2(Unit*, "find target", "dorothee");
@@ -178,7 +179,7 @@ bool TheCuratorBossAstralFlaresCastArcingSearTrigger::IsActive()
bool TerestianIllhoofNeedTargetPriorityTrigger::IsActive() bool TerestianIllhoofNeedTargetPriorityTrigger::IsActive()
{ {
if (!IsInstanceTimerManager(botAI, bot)) if (!IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
return false; return false;
Unit* illhoof = AI_VALUE2(Unit*, "find target", "terestian illhoof"); Unit* illhoof = AI_VALUE2(Unit*, "find target", "terestian illhoof");
@@ -202,7 +203,7 @@ bool ShadeOfAranFlameWreathIsActiveTrigger::IsActive()
// Exclusion of Banish is so the player may Banish elementals if they wish // Exclusion of Banish is so the player may Banish elementals if they wish
bool ShadeOfAranConjuredElementalsSummonedTrigger::IsActive() bool ShadeOfAranConjuredElementalsSummonedTrigger::IsActive()
{ {
if (!IsInstanceTimerManager(botAI, bot)) if (!IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
return false; return false;
Unit* elemental = AI_VALUE2(Unit*, "find target", "conjured elemental"); Unit* elemental = AI_VALUE2(Unit*, "find target", "conjured elemental");
@@ -279,7 +280,7 @@ bool NetherspiteBossIsBanishedTrigger::IsActive()
bool NetherspiteNeedToManageTimersAndTrackersTrigger::IsActive() bool NetherspiteNeedToManageTimersAndTrackersTrigger::IsActive()
{ {
if (!botAI->IsTank(bot) && !IsInstanceTimerManager(botAI, bot)) if (!botAI->IsTank(bot) && !IsMechanicTrackerBot(botAI, bot, KARAZHAN_MAP_ID, nullptr))
return false; return false;
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite"); Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");

View File

@@ -1,7 +1,6 @@
#include "RaidKarazhanHelpers.h" #include "RaidKarazhanHelpers.h"
#include "RaidKarazhanActions.h" #include "RaidKarazhanActions.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "RtiTargetValue.h"
namespace KarazhanHelpers namespace KarazhanHelpers
{ {
@@ -52,75 +51,6 @@ namespace KarazhanHelpers
const Position NIGHTBANE_FLIGHT_STACK_POSITION = { -11159.555f, -1893.526f, 91.473f }; // Broken Barrel const Position NIGHTBANE_FLIGHT_STACK_POSITION = { -11159.555f, -1893.526f, 91.473f }; // Broken Barrel
const Position NIGHTBANE_RAIN_OF_BONES_POSITION = { -11165.233f, -1911.123f, 91.473f }; const Position NIGHTBANE_RAIN_OF_BONES_POSITION = { -11165.233f, -1911.123f, 91.473f };
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId)
{
if (!target)
return;
if (Group* group = bot->GetGroup())
{
ObjectGuid currentGuid = group->GetTargetIcon(iconId);
if (currentGuid != target->GetGUID())
group->SetTargetIcon(iconId, bot->GetGUID(), target->GetGUID());
}
}
void MarkTargetWithSkull(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::skullIndex);
}
void MarkTargetWithSquare(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::squareIndex);
}
void MarkTargetWithStar(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::starIndex);
}
void MarkTargetWithCircle(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::circleIndex);
}
void MarkTargetWithMoon(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::moonIndex);
}
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target)
{
if (!target)
return;
std::string currentRti = botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Get();
Unit* currentTarget = botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Get();
if (currentRti != rtiName || currentTarget != target)
{
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set(rtiName);
botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Set(target);
}
}
// Only one bot is needed to set/reset instance-wide timers
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot)
{
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->IsAlive() && botAI->IsDps(member) && GET_PLAYERBOT_AI(member))
return member == bot;
}
}
return false;
}
Unit* GetFirstAliveUnit(const std::vector<Unit*>& units) Unit* GetFirstAliveUnit(const std::vector<Unit*>& units)
{ {
for (Unit* unit : units) for (Unit* unit : units)
@@ -132,44 +62,6 @@ namespace KarazhanHelpers
return nullptr; return nullptr;
} }
Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry)
{
const GuidVector npcs = botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest hostile npcs")->Get();
for (auto const& npcGuid : npcs)
{
Unit* unit = botAI->GetUnit(npcGuid);
if (unit && unit->IsAlive() && unit->GetEntry() == entry)
return unit;
}
return nullptr;
}
Unit* GetNearestPlayerInRadius(Player* bot, float radius)
{
Unit* nearestPlayer = nullptr;
float nearestDistance = radius;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref != nullptr; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member == bot)
continue;
float distance = bot->GetExactDist2d(member);
if (distance < nearestDistance)
{
nearestDistance = distance;
nearestPlayer = member;
}
}
}
return nearestPlayer;
}
bool IsFlameWreathActive(PlayerbotAI* botAI, Player* bot) bool IsFlameWreathActive(PlayerbotAI* botAI, Player* bot)
{ {
Unit* aran = botAI->GetAiObjectContext()->GetValue<Unit*>("find target", "shade of aran")->Get(); Unit* aran = botAI->GetAiObjectContext()->GetValue<Unit*>("find target", "shade of aran")->Get();

View File

@@ -61,6 +61,11 @@ namespace KarazhanHelpers
NPC_ATTUMEN_THE_HUNTSMAN = 15550, NPC_ATTUMEN_THE_HUNTSMAN = 15550,
NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED = 16152, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED = 16152,
// Terestian Illhoof
NPC_TERESTIAN_ILLHOOF = 15688,
NPC_DEMON_CHAINS = 17248,
NPC_KILREK = 17229,
// Shade of Aran // Shade of Aran
NPC_CONJURED_ELEMENTAL = 17167, NPC_CONJURED_ELEMENTAL = 17167,
@@ -74,8 +79,8 @@ namespace KarazhanHelpers
NPC_NETHERSPITE_INFERNAL = 17646, NPC_NETHERSPITE_INFERNAL = 17646,
}; };
const uint32 KARAZHAN_MAP_ID = 532; constexpr uint32 KARAZHAN_MAP_ID = 532;
const float NIGHTBANE_FLIGHT_Z = 95.0f; constexpr float NIGHTBANE_FLIGHT_Z = 95.0f;
// Attumen the Huntsman // Attumen the Huntsman
extern std::unordered_map<uint32, time_t> attumenDpsWaitTimer; extern std::unordered_map<uint32, time_t> attumenDpsWaitTimer;
@@ -105,17 +110,7 @@ namespace KarazhanHelpers
extern const Position NIGHTBANE_FLIGHT_STACK_POSITION; extern const Position NIGHTBANE_FLIGHT_STACK_POSITION;
extern const Position NIGHTBANE_RAIN_OF_BONES_POSITION; extern const Position NIGHTBANE_RAIN_OF_BONES_POSITION;
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId);
void MarkTargetWithSkull(Player* bot, Unit* target);
void MarkTargetWithSquare(Player* bot, Unit* target);
void MarkTargetWithStar(Player* bot, Unit* target);
void MarkTargetWithCircle(Player* bot, Unit* target);
void MarkTargetWithMoon(Player* bot, Unit* target);
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target);
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot);
Unit* GetFirstAliveUnit(const std::vector<Unit*>& units); Unit* GetFirstAliveUnit(const std::vector<Unit*>& units);
Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry);
Unit* GetNearestPlayerInRadius(Player* bot, float radius);
bool IsFlameWreathActive(PlayerbotAI* botAI, Player* bot); bool IsFlameWreathActive(PlayerbotAI* botAI, Player* bot);
std::vector<Player*> GetRedBlockers(PlayerbotAI* botAI, Player* bot); std::vector<Player*> GetRedBlockers(PlayerbotAI* botAI, Player* bot);
std::vector<Player*> GetBlueBlockers(PlayerbotAI* botAI, Player* bot); std::vector<Player*> GetBlueBlockers(PlayerbotAI* botAI, Player* bot);

View File

@@ -4,6 +4,7 @@
#include "ObjectAccessor.h" #include "ObjectAccessor.h"
#include "ObjectGuid.h" #include "ObjectGuid.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "RaidBossHelpers.h"
using namespace MagtheridonHelpers; using namespace MagtheridonHelpers;
@@ -14,46 +15,45 @@ bool MagtheridonMainTankAttackFirstThreeChannelersAction::Execute(Event event)
return false; return false;
Creature* channelerSquare = GetChanneler(bot, SOUTH_CHANNELER); Creature* channelerSquare = GetChanneler(bot, SOUTH_CHANNELER);
if (channelerSquare && channelerSquare->IsAlive()) if (channelerSquare)
MarkTargetWithSquare(bot, channelerSquare); MarkTargetWithSquare(bot, channelerSquare);
Creature* channelerStar = GetChanneler(bot, WEST_CHANNELER); Creature* channelerStar = GetChanneler(bot, WEST_CHANNELER);
if (channelerStar && channelerStar->IsAlive()) if (channelerStar)
MarkTargetWithStar(bot, channelerStar); MarkTargetWithStar(bot, channelerStar);
Creature* channelerCircle = GetChanneler(bot, EAST_CHANNELER); Creature* channelerCircle = GetChanneler(bot, EAST_CHANNELER);
if (channelerCircle && channelerCircle->IsAlive()) if (channelerCircle)
MarkTargetWithCircle(bot, channelerCircle); MarkTargetWithCircle(bot, channelerCircle);
// After first three channelers are dead, wait for Magtheridon to activate // After first three channelers are dead, wait for Magtheridon to activate
if ((!channelerSquare || !channelerSquare->IsAlive()) && if (!channelerSquare && !channelerStar && !channelerCircle)
(!channelerStar || !channelerStar->IsAlive()) &&
(!channelerCircle || !channelerCircle->IsAlive()))
{ {
const Location& position = MagtheridonsLairLocations::WaitingForMagtheridonPosition; const Position& position = WAITING_FOR_MAGTHERIDON_POSITION;
if (!bot->IsWithinDist2d(position.x, position.y, 2.0f)) if (!bot->IsWithinDist2d(position.GetPositionX(), position.GetPositionY(), 2.0f))
{ {
return MoveTo(bot->GetMapId(), position.x, position.y, position.z, false, false, false, false, return MoveTo(MAGTHERIDON_MAP_ID, position.GetPositionX(), position.GetPositionY(),
position.GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false); MovementPriority::MOVEMENT_COMBAT, true, false);
} }
bot->SetFacingTo(position.orientation); bot->SetFacingTo(position.GetOrientation());
return true; return true;
} }
Creature* currentTarget = nullptr; Creature* currentTarget = nullptr;
std::string rtiName; std::string rtiName;
if (channelerSquare && channelerSquare->IsAlive()) if (channelerSquare)
{ {
currentTarget = channelerSquare; currentTarget = channelerSquare;
rtiName = "square"; rtiName = "square";
} }
else if (channelerStar && channelerStar->IsAlive()) else if (channelerStar)
{ {
currentTarget = channelerStar; currentTarget = channelerStar;
rtiName = "star"; rtiName = "star";
} }
else if (channelerCircle && channelerCircle->IsAlive()) else if (channelerCircle)
{ {
currentTarget = channelerCircle; currentTarget = channelerCircle;
rtiName = "circle"; rtiName = "circle";
@@ -70,7 +70,7 @@ bool MagtheridonMainTankAttackFirstThreeChannelersAction::Execute(Event event)
bool MagtheridonFirstAssistTankAttackNWChannelerAction::Execute(Event event) bool MagtheridonFirstAssistTankAttackNWChannelerAction::Execute(Event event)
{ {
Creature* channelerDiamond = GetChanneler(bot, NORTHWEST_CHANNELER); Creature* channelerDiamond = GetChanneler(bot, NORTHWEST_CHANNELER);
if (!channelerDiamond || !channelerDiamond->IsAlive()) if (!channelerDiamond)
return false; return false;
MarkTargetWithDiamond(bot, channelerDiamond); MarkTargetWithDiamond(bot, channelerDiamond);
@@ -81,18 +81,18 @@ bool MagtheridonFirstAssistTankAttackNWChannelerAction::Execute(Event event)
if (channelerDiamond->GetVictim() == bot) if (channelerDiamond->GetVictim() == bot)
{ {
const Location& position = MagtheridonsLairLocations::NWChannelerTankPosition; const Position& position = NW_CHANNELER_TANK_POSITION;
const float maxDistance = 3.0f; const float maxDistance = 3.0f;
float distanceToPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
if (bot->GetExactDist2d(position.x, position.y) > maxDistance) if (distanceToPosition > maxDistance)
{ {
float dX = position.x - bot->GetPositionX(); float dX = position.GetPositionX() - bot->GetPositionX();
float dY = position.y - bot->GetPositionY(); float dY = position.GetPositionY() - bot->GetPositionY();
float dist = sqrt(dX * dX + dY * dY); float moveX = bot->GetPositionX() + (dX / distanceToPosition) * maxDistance;
float moveX = bot->GetPositionX() + (dX / dist) * maxDistance; float moveY = bot->GetPositionY() + (dY / distanceToPosition) * maxDistance;
float moveY = bot->GetPositionY() + (dY / dist) * maxDistance;
return MoveTo(bot->GetMapId(), moveX, moveY, position.z, false, false, false, false, return MoveTo(MAGTHERIDON_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false); MovementPriority::MOVEMENT_COMBAT, true, false);
} }
} }
@@ -103,7 +103,7 @@ bool MagtheridonFirstAssistTankAttackNWChannelerAction::Execute(Event event)
bool MagtheridonSecondAssistTankAttackNEChannelerAction::Execute(Event event) bool MagtheridonSecondAssistTankAttackNEChannelerAction::Execute(Event event)
{ {
Creature* channelerTriangle = GetChanneler(bot, NORTHEAST_CHANNELER); Creature* channelerTriangle = GetChanneler(bot, NORTHEAST_CHANNELER);
if (!channelerTriangle || !channelerTriangle->IsAlive()) if (!channelerTriangle)
return false; return false;
MarkTargetWithTriangle(bot, channelerTriangle); MarkTargetWithTriangle(bot, channelerTriangle);
@@ -114,18 +114,18 @@ bool MagtheridonSecondAssistTankAttackNEChannelerAction::Execute(Event event)
if (channelerTriangle->GetVictim() == bot) if (channelerTriangle->GetVictim() == bot)
{ {
const Location& position = MagtheridonsLairLocations::NEChannelerTankPosition; const Position& position = NE_CHANNELER_TANK_POSITION;
const float maxDistance = 3.0f; const float maxDistance = 3.0f;
float distanceToPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
if (bot->GetExactDist2d(position.x, position.y) > maxDistance) if (distanceToPosition > maxDistance)
{ {
float dX = position.x - bot->GetPositionX(); float dX = position.GetPositionX() - bot->GetPositionX();
float dY = position.y - bot->GetPositionY(); float dY = position.GetPositionY() - bot->GetPositionY();
float dist = sqrt(dX * dX + dY * dY); float moveX = bot->GetPositionX() + (dX / distanceToPosition) * maxDistance;
float moveX = bot->GetPositionX() + (dX / dist) * maxDistance; float moveY = bot->GetPositionY() + (dY / distanceToPosition) * maxDistance;
float moveY = bot->GetPositionY() + (dY / dist) * maxDistance;
return MoveTo(bot->GetMapId(), moveX, moveY, position.z, false, false, false, false, return MoveTo(MAGTHERIDON_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false); MovementPriority::MOVEMENT_COMBAT, true, false);
} }
} }
@@ -175,7 +175,7 @@ bool MagtheridonMisdirectHellfireChannelers::Execute(Event event)
switch (hunterIndex) switch (hunterIndex)
{ {
case 0: case 0:
if (mainTank && channelerStar && channelerStar->IsAlive() && if (mainTank && channelerStar &&
channelerStar->GetVictim() != mainTank) channelerStar->GetVictim() != mainTank)
{ {
if (botAI->CanCastSpell("misdirection", mainTank)) if (botAI->CanCastSpell("misdirection", mainTank))
@@ -190,7 +190,7 @@ bool MagtheridonMisdirectHellfireChannelers::Execute(Event event)
break; break;
case 1: case 1:
if (mainTank && channelerCircle && channelerCircle->IsAlive() && if (mainTank && channelerCircle &&
channelerCircle->GetVictim() != mainTank) channelerCircle->GetVictim() != mainTank)
{ {
if (botAI->CanCastSpell("misdirection", mainTank)) if (botAI->CanCastSpell("misdirection", mainTank))
@@ -215,90 +215,69 @@ bool MagtheridonAssignDPSPriorityAction::Execute(Event event)
{ {
// Listed in order of priority // Listed in order of priority
Creature* channelerSquare = GetChanneler(bot, SOUTH_CHANNELER); Creature* channelerSquare = GetChanneler(bot, SOUTH_CHANNELER);
if (channelerSquare && channelerSquare->IsAlive()) if (channelerSquare)
{ {
SetRtiTarget(botAI, "square", channelerSquare); SetRtiTarget(botAI, "square", channelerSquare);
if (bot->GetTarget() != channelerSquare->GetGUID()) if (bot->GetTarget() != channelerSquare->GetGUID())
{
bot->SetSelection(channelerSquare->GetGUID());
return Attack(channelerSquare); return Attack(channelerSquare);
}
return false; return false;
} }
Creature* channelerStar = GetChanneler(bot, WEST_CHANNELER); Creature* channelerStar = GetChanneler(bot, WEST_CHANNELER);
if (channelerStar && channelerStar->IsAlive()) if (channelerStar)
{ {
SetRtiTarget(botAI, "star", channelerStar); SetRtiTarget(botAI, "star", channelerStar);
if (bot->GetTarget() != channelerStar->GetGUID()) if (bot->GetTarget() != channelerStar->GetGUID())
{
bot->SetSelection(channelerStar->GetGUID());
return Attack(channelerStar); return Attack(channelerStar);
}
return false; return false;
} }
Creature* channelerCircle = GetChanneler(bot, EAST_CHANNELER); Creature* channelerCircle = GetChanneler(bot, EAST_CHANNELER);
if (channelerCircle && channelerCircle->IsAlive()) if (channelerCircle)
{ {
SetRtiTarget(botAI, "circle", channelerCircle); SetRtiTarget(botAI, "circle", channelerCircle);
if (bot->GetTarget() != channelerCircle->GetGUID()) if (bot->GetTarget() != channelerCircle->GetGUID())
{
bot->SetSelection(channelerCircle->GetGUID());
return Attack(channelerCircle); return Attack(channelerCircle);
}
return false; return false;
} }
Creature* channelerDiamond = GetChanneler(bot, NORTHWEST_CHANNELER); Creature* channelerDiamond = GetChanneler(bot, NORTHWEST_CHANNELER);
if (channelerDiamond && channelerDiamond->IsAlive()) if (channelerDiamond)
{ {
SetRtiTarget(botAI, "diamond", channelerDiamond); SetRtiTarget(botAI, "diamond", channelerDiamond);
if (bot->GetTarget() != channelerDiamond->GetGUID()) if (bot->GetTarget() != channelerDiamond->GetGUID())
{
bot->SetSelection(channelerDiamond->GetGUID());
return Attack(channelerDiamond); return Attack(channelerDiamond);
}
return false; return false;
} }
Creature* channelerTriangle = GetChanneler(bot, NORTHEAST_CHANNELER); Creature* channelerTriangle = GetChanneler(bot, NORTHEAST_CHANNELER);
if (channelerTriangle && channelerTriangle->IsAlive()) if (channelerTriangle)
{ {
SetRtiTarget(botAI, "triangle", channelerTriangle); SetRtiTarget(botAI, "triangle", channelerTriangle);
if (bot->GetTarget() != channelerTriangle->GetGUID()) if (bot->GetTarget() != channelerTriangle->GetGUID())
{
bot->SetSelection(channelerTriangle->GetGUID());
return Attack(channelerTriangle); return Attack(channelerTriangle);
}
return false; return false;
} }
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon"); Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
if (magtheridon && !magtheridon->HasAura(SPELL_SHADOW_CAGE) && if (magtheridon && !magtheridon->HasAura(SPELL_SHADOW_CAGE) &&
(!channelerSquare || !channelerSquare->IsAlive()) && !channelerSquare && !channelerStar && !channelerCircle &&
(!channelerStar || !channelerStar->IsAlive()) && !channelerDiamond && !channelerTriangle)
(!channelerCircle || !channelerCircle->IsAlive()) &&
(!channelerDiamond || !channelerDiamond->IsAlive()) &&
(!channelerTriangle || !channelerTriangle->IsAlive()))
{ {
SetRtiTarget(botAI, "cross", magtheridon); SetRtiTarget(botAI, "cross", magtheridon);
if (bot->GetTarget() != magtheridon->GetGUID()) if (bot->GetTarget() != magtheridon->GetGUID())
{
bot->SetSelection(magtheridon->GetGUID());
return Attack(magtheridon); return Attack(magtheridon);
}
} }
return false; return false;
@@ -343,15 +322,15 @@ bool MagtheridonWarlockCCBurningAbyssalAction::Execute(Event event)
if (warlockIndex >= 0 && warlockIndex < abyssals.size()) if (warlockIndex >= 0 && warlockIndex < abyssals.size())
{ {
Unit* assignedAbyssal = abyssals[warlockIndex]; Unit* assignedAbyssal = abyssals[warlockIndex];
if (!assignedAbyssal->HasAura(SPELL_BANISH) && botAI->CanCastSpell(SPELL_BANISH, assignedAbyssal, true)) if (!botAI->HasAura("banish", assignedAbyssal) && botAI->CanCastSpell("banish", assignedAbyssal))
return botAI->CastSpell("banish", assignedAbyssal); return botAI->CastSpell("banish", assignedAbyssal);
} }
for (size_t i = warlocks.size(); i < abyssals.size(); ++i) for (size_t i = warlocks.size(); i < abyssals.size(); ++i)
{ {
Unit* excessAbyssal = abyssals[i]; Unit* excessAbyssal = abyssals[i];
if (!excessAbyssal->HasAura(SPELL_BANISH) && !excessAbyssal->HasAura(SPELL_FEAR) && if (!botAI->HasAura("banish", excessAbyssal) && !botAI->HasAura("fear", excessAbyssal) &&
botAI->CanCastSpell(SPELL_FEAR, excessAbyssal, true)) botAI->CanCastSpell("fear", excessAbyssal))
return botAI->CastSpell("fear", excessAbyssal); return botAI->CastSpell("fear", excessAbyssal);
} }
@@ -373,22 +352,20 @@ bool MagtheridonMainTankPositionBossAction::Execute(Event event)
if (magtheridon->GetVictim() == bot) if (magtheridon->GetVictim() == bot)
{ {
const Location& position = MagtheridonsLairLocations::MagtheridonTankPosition; const Position& position = MAGTHERIDON_TANK_POSITION;
const float maxDistance = 2.0f; const float maxDistance = 2.0f;
float distanceToPosition = bot->GetExactDist2d(position.GetPositionX(), position.GetPositionY());
if (bot->GetExactDist2d(position.x, position.y) > maxDistance) if (distanceToPosition > maxDistance)
{ {
float dX = position.x - bot->GetPositionX(); float dX = position.GetPositionX() - bot->GetPositionX();
float dY = position.y - bot->GetPositionY(); float dY = position.GetPositionY() - bot->GetPositionY();
float dist = sqrt(dX * dX + dY * dY); float moveX = bot->GetPositionX() + (dX / distanceToPosition) * maxDistance;
float moveX = bot->GetPositionX() + (dX / dist) * maxDistance; float moveY = bot->GetPositionY() + (dY / distanceToPosition) * maxDistance;
float moveY = bot->GetPositionY() + (dY / dist) * maxDistance;
return MoveTo(bot->GetMapId(), moveX, moveY, position.z, false, false, false, false, return MoveTo(MAGTHERIDON_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, true); MovementPriority::MOVEMENT_COMBAT, true, true);
} }
bot->SetFacingTo(position.orientation);
} }
return false; return false;
@@ -440,13 +417,13 @@ bool MagtheridonSpreadRangedAction::Execute(Event event)
} }
bool isHealer = botAI->IsHeal(bot); bool isHealer = botAI->IsHeal(bot);
const Location& center = isHealer const Position& center = isHealer
? MagtheridonsLairLocations::HealerSpreadPosition ? HEALER_SPREAD_POSITION
: MagtheridonsLairLocations::RangedSpreadPosition; : RANGED_SPREAD_POSITION;
float maxSpreadRadius = isHealer ? 15.0f : 20.0f; float maxSpreadRadius = isHealer ? 15.0f : 20.0f;
float centerX = center.x; float centerX = center.GetPositionX();
float centerY = center.y; float centerY = center.GetPositionY();
float centerZ = bot->GetPositionZ(); float centerZ = center.GetPositionZ();
const float radiusBuffer = 3.0f; const float radiusBuffer = 3.0f;
if (!initialPositions.count(bot->GetGUID())) if (!initialPositions.count(bot->GetGUID()))
@@ -479,7 +456,7 @@ bool MagtheridonSpreadRangedAction::Execute(Event event)
bot->AttackStop(); bot->AttackStop();
bot->InterruptNonMeleeSpells(false); bot->InterruptNonMeleeSpells(false);
return MoveTo(bot->GetMapId(), destX, destY, destZ, false, false, false, false, return MoveTo(MAGTHERIDON_MAP_ID, destX, destY, destZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false); MovementPriority::MOVEMENT_COMBAT, true, false);
} }
hasReachedInitialPosition[bot->GetGUID()] = true; hasReachedInitialPosition[bot->GetGUID()] = true;
@@ -499,7 +476,7 @@ bool MagtheridonSpreadRangedAction::Execute(Event event)
{ {
bot->AttackStop(); bot->AttackStop();
bot->InterruptNonMeleeSpells(false); bot->InterruptNonMeleeSpells(false);
return MoveTo(bot->GetMapId(), targetX, targetY, centerZ, false, false, false, false, return MoveTo(MAGTHERIDON_MAP_ID, targetX, targetY, centerZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false); MovementPriority::MOVEMENT_COMBAT, true, false);
} }
} }
@@ -593,7 +570,7 @@ bool MagtheridonUseManticronCubeAction::HandleWaitingPhase(const CubeInfo& cubeI
{ {
bot->AttackStop(); bot->AttackStop();
bot->InterruptNonMeleeSpells(true); bot->InterruptNonMeleeSpells(true);
return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, false, return MoveTo(MAGTHERIDON_MAP_ID, targetX, targetY, targetZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false); MovementPriority::MOVEMENT_COMBAT, true, false);
} }
} }
@@ -603,7 +580,7 @@ bool MagtheridonUseManticronCubeAction::HandleWaitingPhase(const CubeInfo& cubeI
float fallbackY = cubeInfo.y + sin(angle) * safeWaitDistance; float fallbackY = cubeInfo.y + sin(angle) * safeWaitDistance;
float fallbackZ = bot->GetPositionZ(); float fallbackZ = bot->GetPositionZ();
return MoveTo(bot->GetMapId(), fallbackX, fallbackY, fallbackZ, false, false, false, false, return MoveTo(MAGTHERIDON_MAP_ID, fallbackX, fallbackY, fallbackZ, false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false); MovementPriority::MOVEMENT_COMBAT, true, false);
} }
@@ -638,7 +615,7 @@ bool MagtheridonUseManticronCubeAction::HandleCubeInteraction(const CubeInfo& cu
bot->AttackStop(); bot->AttackStop();
bot->InterruptNonMeleeSpells(true); bot->InterruptNonMeleeSpells(true);
return MoveTo(bot->GetMapId(), targetX, targetY, targetZ, false, false, false, false, return MoveTo(MAGTHERIDON_MAP_ID, targetX, targetY, targetZ, false, false, false, false,
MovementPriority::MOVEMENT_FORCED, true, false); MovementPriority::MOVEMENT_FORCED, true, false);
} }
@@ -663,14 +640,14 @@ bool MagtheridonManageTimersAndAssignmentsAction::Execute(Event event)
magtheridon->FindCurrentSpellBySpellId(SPELL_BLAST_NOVA); magtheridon->FindCurrentSpellBySpellId(SPELL_BLAST_NOVA);
bool lastBlastNova = lastBlastNovaState[instanceId]; bool lastBlastNova = lastBlastNovaState[instanceId];
if (lastBlastNova && !blastNovaActive && IsInstanceTimerManager(botAI, bot)) if (lastBlastNova && !blastNovaActive && IsMechanicTrackerBot(botAI, bot, MAGTHERIDON_MAP_ID, nullptr))
blastNovaTimer[instanceId] = now; blastNovaTimer[instanceId] = now;
lastBlastNovaState[instanceId] = blastNovaActive; lastBlastNovaState[instanceId] = blastNovaActive;
if (!magtheridon->HasAura(SPELL_SHADOW_CAGE)) if (!magtheridon->HasAura(SPELL_SHADOW_CAGE))
{ {
if (IsInstanceTimerManager(botAI, bot)) if (IsMechanicTrackerBot(botAI, bot, MAGTHERIDON_MAP_ID, nullptr))
{ {
spreadWaitTimer.try_emplace(instanceId, now); spreadWaitTimer.try_emplace(instanceId, now);
blastNovaTimer.try_emplace(instanceId, now); blastNovaTimer.try_emplace(instanceId, now);
@@ -679,11 +656,12 @@ bool MagtheridonManageTimersAndAssignmentsAction::Execute(Event event)
} }
else else
{ {
MagtheridonSpreadRangedAction::initialPositions.clear(); ObjectGuid guid = bot->GetGUID();
MagtheridonSpreadRangedAction::hasReachedInitialPosition.clear(); MagtheridonSpreadRangedAction::initialPositions.erase(guid);
botToCubeAssignment.clear(); MagtheridonSpreadRangedAction::hasReachedInitialPosition.erase(guid);
botToCubeAssignment.erase(guid);
if (IsInstanceTimerManager(botAI, bot)) if (IsMechanicTrackerBot(botAI, bot, MAGTHERIDON_MAP_ID, nullptr))
{ {
spreadWaitTimer.erase(instanceId); spreadWaitTimer.erase(instanceId);
blastNovaTimer.erase(instanceId); blastNovaTimer.erase(instanceId);

View File

@@ -6,8 +6,6 @@
#include "AttackAction.h" #include "AttackAction.h"
#include "MovementActions.h" #include "MovementActions.h"
using namespace MagtheridonHelpers;
class MagtheridonMainTankAttackFirstThreeChannelersAction : public AttackAction class MagtheridonMainTankAttackFirstThreeChannelersAction : public AttackAction
{ {
public: public:
@@ -85,8 +83,8 @@ public:
private: private:
bool HandleCubeRelease(Unit* magtheridon, GameObject* cube); bool HandleCubeRelease(Unit* magtheridon, GameObject* cube);
bool ShouldActivateCubeLogic(Unit* magtheridon); bool ShouldActivateCubeLogic(Unit* magtheridon);
bool HandleWaitingPhase(const CubeInfo& cubeInfo); bool HandleWaitingPhase(const MagtheridonHelpers::CubeInfo& cubeInfo);
bool HandleCubeInteraction(const CubeInfo& cubeInfo, GameObject* cube); bool HandleCubeInteraction(const MagtheridonHelpers::CubeInfo& cubeInfo, GameObject* cube);
}; };
class MagtheridonManageTimersAndAssignmentsAction : public Action class MagtheridonManageTimersAndAssignmentsAction : public Action

View File

@@ -8,6 +8,7 @@
#include "GenericSpellActions.h" #include "GenericSpellActions.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "WarlockActions.h" #include "WarlockActions.h"
#include "WipeAction.h"
using namespace MagtheridonHelpers; using namespace MagtheridonHelpers;
@@ -24,10 +25,10 @@ float MagtheridonUseManticronCubeMultiplier::GetValue(Action* action)
auto it = botToCubeAssignment.find(bot->GetGUID()); auto it = botToCubeAssignment.find(bot->GetGUID());
if (it != botToCubeAssignment.end()) if (it != botToCubeAssignment.end())
{ {
if (dynamic_cast<MagtheridonUseManticronCubeAction*>(action)) if (dynamic_cast<WipeAction*>(action))
return 1.0f; return 1.0f;
else if (!dynamic_cast<MagtheridonUseManticronCubeAction*>(action))
return 0.0f; return 0.0f;
} }
} }
@@ -41,28 +42,31 @@ float MagtheridonWaitToAttackMultiplier::GetValue(Action* action)
if (!magtheridon || magtheridon->HasAura(SPELL_SHADOW_CAGE)) if (!magtheridon || magtheridon->HasAura(SPELL_SHADOW_CAGE))
return 1.0f; return 1.0f;
if (botAI->IsMainTank(bot))
return 1.0f;
const uint8 dpsWaitSeconds = 6; const uint8 dpsWaitSeconds = 6;
auto it = dpsWaitTimer.find(magtheridon->GetMap()->GetInstanceId()); auto it = dpsWaitTimer.find(magtheridon->GetMap()->GetInstanceId());
if (it == dpsWaitTimer.end() || if (it == dpsWaitTimer.end() ||
(time(nullptr) - it->second) < dpsWaitSeconds) (time(nullptr) - it->second) < dpsWaitSeconds)
{ {
if (!botAI->IsMainTank(bot) && (dynamic_cast<AttackAction*>(action) || if (dynamic_cast<AttackAction*>(action) ||
(!botAI->IsHeal(bot) && dynamic_cast<CastSpellAction*>(action)))) (!botAI->IsHeal(bot) && dynamic_cast<CastSpellAction*>(action)))
return 0.0f; return 0.0f;
} }
return 1.0f; return 1.0f;
} }
// No tank assist for offtanks during the channeler phase
// So they don't try to pull channelers from each other or the main tank
float MagtheridonDisableOffTankAssistMultiplier::GetValue(Action* action) float MagtheridonDisableOffTankAssistMultiplier::GetValue(Action* action)
{ {
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon"); Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
Unit* channeler = AI_VALUE2(Unit*, "find target", "hellfire channeler");
if (!magtheridon) if (!magtheridon)
return 1.0f; return 1.0f;
if (bot->GetVictim() == nullptr)
return 1.0f;
if ((botAI->IsAssistTankOfIndex(bot, 0) || botAI->IsAssistTankOfIndex(bot, 1)) && if ((botAI->IsAssistTankOfIndex(bot, 0) || botAI->IsAssistTankOfIndex(bot, 1)) &&
dynamic_cast<TankAssistAction*>(action)) dynamic_cast<TankAssistAction*>(action))
return 0.0f; return 0.0f;

View File

@@ -18,7 +18,7 @@ bool MagtheridonNWChannelerEngagedByFirstAssistTankTrigger::IsActive()
Creature* channelerDiamond = GetChanneler(bot, NORTHWEST_CHANNELER); Creature* channelerDiamond = GetChanneler(bot, NORTHWEST_CHANNELER);
return magtheridon && botAI->IsAssistTankOfIndex(bot, 0) && return magtheridon && botAI->IsAssistTankOfIndex(bot, 0) &&
channelerDiamond && channelerDiamond->IsAlive(); channelerDiamond;
} }
bool MagtheridonNEChannelerEngagedBySecondAssistTankTrigger::IsActive() bool MagtheridonNEChannelerEngagedBySecondAssistTankTrigger::IsActive()
@@ -27,7 +27,7 @@ bool MagtheridonNEChannelerEngagedBySecondAssistTankTrigger::IsActive()
Creature* channelerTriangle = GetChanneler(bot, NORTHEAST_CHANNELER); Creature* channelerTriangle = GetChanneler(bot, NORTHEAST_CHANNELER);
return magtheridon && botAI->IsAssistTankOfIndex(bot, 1) && return magtheridon && botAI->IsAssistTankOfIndex(bot, 1) &&
channelerTriangle && channelerTriangle->IsAlive(); channelerTriangle;
} }
bool MagtheridonPullingWestAndEastChannelersTrigger::IsActive() bool MagtheridonPullingWestAndEastChannelersTrigger::IsActive()
@@ -38,8 +38,7 @@ bool MagtheridonPullingWestAndEastChannelersTrigger::IsActive()
Creature* channelerCircle = GetChanneler(bot, EAST_CHANNELER); Creature* channelerCircle = GetChanneler(bot, EAST_CHANNELER);
return magtheridon && bot->getClass() == CLASS_HUNTER && return magtheridon && bot->getClass() == CLASS_HUNTER &&
((channelerStar && channelerStar->IsAlive()) || (channelerStar || channelerCircle);
(channelerCircle && channelerCircle->IsAlive()));
} }
bool MagtheridonDeterminingKillOrderTrigger::IsActive() bool MagtheridonDeterminingKillOrderTrigger::IsActive()
@@ -51,12 +50,11 @@ bool MagtheridonDeterminingKillOrderTrigger::IsActive()
Creature* channelerTriangle = GetChanneler(bot, NORTHEAST_CHANNELER); Creature* channelerTriangle = GetChanneler(bot, NORTHEAST_CHANNELER);
if (!magtheridon || botAI->IsHeal(bot) || botAI->IsMainTank(bot) || if (!magtheridon || botAI->IsHeal(bot) || botAI->IsMainTank(bot) ||
(botAI->IsAssistTankOfIndex(bot, 0) && channelerDiamond && channelerDiamond->IsAlive()) || (botAI->IsAssistTankOfIndex(bot, 0) && channelerDiamond) ||
(botAI->IsAssistTankOfIndex(bot, 1) && channelerTriangle && channelerTriangle->IsAlive())) (botAI->IsAssistTankOfIndex(bot, 1) && channelerTriangle))
return false; return false;
return (channeler && channeler->IsAlive()) || (magtheridon && return channeler || (magtheridon && !magtheridon->HasAura(SPELL_SHADOW_CAGE));
!magtheridon->HasAura(SPELL_SHADOW_CAGE));
} }
bool MagtheridonBurningAbyssalSpawnedTrigger::IsActive() bool MagtheridonBurningAbyssalSpawnedTrigger::IsActive()
@@ -84,10 +82,8 @@ bool MagtheridonBossEngagedByMainTankTrigger::IsActive()
bool MagtheridonBossEngagedByRangedTrigger::IsActive() bool MagtheridonBossEngagedByRangedTrigger::IsActive()
{ {
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon"); Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
Unit* channeler = AI_VALUE2(Unit*, "find target", "hellfire channeler");
return magtheridon && botAI->IsRanged(bot) && return magtheridon && !magtheridon->HasAura(SPELL_SHADOW_CAGE) && botAI->IsRanged(bot);
!(channeler && channeler->IsAlive());
} }
bool MagtheridonIncomingBlastNovaTrigger::IsActive() bool MagtheridonIncomingBlastNovaTrigger::IsActive()
@@ -122,7 +118,5 @@ bool MagtheridonIncomingBlastNovaTrigger::IsActive()
bool MagtheridonNeedToManageTimersAndAssignmentsTrigger::IsActive() bool MagtheridonNeedToManageTimersAndAssignmentsTrigger::IsActive()
{ {
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon"); return AI_VALUE2(Unit*, "find target", "magtheridon");
return magtheridon;
} }

View File

@@ -1,22 +1,18 @@
#include "RaidMagtheridonHelpers.h" #include "RaidMagtheridonHelpers.h"
#include "Creature.h" #include "Creature.h"
#include "GameObject.h" #include "GameObject.h"
#include "GroupReference.h"
#include "Map.h" #include "Map.h"
#include "ObjectGuid.h" #include "ObjectGuid.h"
#include "Playerbots.h" #include "Playerbots.h"
namespace MagtheridonHelpers namespace MagtheridonHelpers
{ {
namespace MagtheridonsLairLocations const Position WAITING_FOR_MAGTHERIDON_POSITION = { 1.359f, 2.048f, -0.406f, 3.135f };
{ const Position MAGTHERIDON_TANK_POSITION = { 22.827f, 2.105f, -0.406f, 3.135f };
const Location WaitingForMagtheridonPosition = { 1.359f, 2.048f, -0.406f, 3.135f }; const Position NW_CHANNELER_TANK_POSITION = { -11.764f, 30.818f, -0.411f, 0.0f };
const Location MagtheridonTankPosition = { 22.827f, 2.105f, -0.406f, 3.135f }; const Position NE_CHANNELER_TANK_POSITION = { -12.490f, -26.211f, -0.411f, 0.0f };
const Location NWChannelerTankPosition = { -11.764f, 30.818f, -0.411f, 0.0f }; const Position RANGED_SPREAD_POSITION = { -14.890f, 1.995f, -0.406f, 0.0f };
const Location NEChannelerTankPosition = { -12.490f, -26.211f, -0.411f, 0.0f }; const Position HEALER_SPREAD_POSITION = { -2.265f, 1.874f, -0.404f, 0.0f };
const Location RangedSpreadPosition = { -14.890f, 1.995f, -0.406f, 0.0f };
const Location HealerSpreadPosition = { -2.265f, 1.874f, -0.404f, 0.0f };
}
// Identify channelers by their database GUIDs // Identify channelers by their database GUIDs
Creature* GetChanneler(Player* bot, uint32 dbGuid) Creature* GetChanneler(Player* bot, uint32 dbGuid)
@@ -29,63 +25,11 @@ namespace MagtheridonHelpers
if (it == map->GetCreatureBySpawnIdStore().end()) if (it == map->GetCreatureBySpawnIdStore().end())
return nullptr; return nullptr;
return it->second; Creature* channeler = it->second;
} if (!channeler->IsAlive())
return nullptr;
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId) return channeler;
{
Group* group = bot->GetGroup();
if (!target || !group)
return;
ObjectGuid currentGuid = group->GetTargetIcon(iconId);
if (currentGuid != target->GetGUID())
group->SetTargetIcon(iconId, bot->GetGUID(), target->GetGUID());
}
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target)
{
if (!target)
return;
std::string currentRti = botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Get();
Unit* currentTarget = botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Get();
if (currentRti != rtiName || currentTarget != target)
{
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set(rtiName);
botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Set(target);
}
}
void MarkTargetWithSquare(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::squareIndex);
}
void MarkTargetWithStar(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::starIndex);
}
void MarkTargetWithCircle(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::circleIndex);
}
void MarkTargetWithDiamond(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::diamondIndex);
}
void MarkTargetWithTriangle(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::triangleIndex);
}
void MarkTargetWithCross(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::crossIndex);
} }
const std::vector<uint32> MANTICRON_CUBE_DB_GUIDS = { 43157, 43158, 43159, 43160, 43161 }; const std::vector<uint32> MANTICRON_CUBE_DB_GUIDS = { 43157, 43158, 43159, 43160, 43161 };
@@ -208,19 +152,4 @@ namespace MagtheridonHelpers
return true; return true;
} }
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot)
{
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->IsAlive() && botAI->IsDps(member) && GET_PLAYERBOT_AI(member))
return member == bot;
}
}
return true;
}
} }

View File

@@ -8,7 +8,6 @@
#include "Group.h" #include "Group.h"
#include "ObjectGuid.h" #include "ObjectGuid.h"
#include "PlayerbotAI.h" #include "PlayerbotAI.h"
#include "RtiTargetValue.h"
namespace MagtheridonHelpers namespace MagtheridonHelpers
{ {
@@ -19,10 +18,6 @@ namespace MagtheridonHelpers
SPELL_BLAST_NOVA = 30616, SPELL_BLAST_NOVA = 30616,
SPELL_SHADOW_GRASP = 30410, SPELL_SHADOW_GRASP = 30410,
// Warlock
SPELL_BANISH = 18647,
SPELL_FEAR = 6215,
// Hunter // Hunter
SPELL_MISDIRECTION = 35079, SPELL_MISDIRECTION = 35079,
}; };
@@ -38,6 +33,7 @@ namespace MagtheridonHelpers
GO_BLAZE = 181832, GO_BLAZE = 181832,
}; };
constexpr uint32 MAGTHERIDON_MAP_ID = 544;
constexpr uint32 SOUTH_CHANNELER = 90978; constexpr uint32 SOUTH_CHANNELER = 90978;
constexpr uint32 WEST_CHANNELER = 90979; constexpr uint32 WEST_CHANNELER = 90979;
constexpr uint32 NORTHWEST_CHANNELER = 90980; constexpr uint32 NORTHWEST_CHANNELER = 90980;
@@ -45,31 +41,14 @@ namespace MagtheridonHelpers
constexpr uint32 NORTHEAST_CHANNELER = 90981; constexpr uint32 NORTHEAST_CHANNELER = 90981;
Creature* GetChanneler(Player* bot, uint32 dbGuid); Creature* GetChanneler(Player* bot, uint32 dbGuid);
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId);
void MarkTargetWithSquare(Player* bot, Unit* target);
void MarkTargetWithStar(Player* bot, Unit* target);
void MarkTargetWithCircle(Player* bot, Unit* target);
void MarkTargetWithDiamond(Player* bot, Unit* target);
void MarkTargetWithTriangle(Player* bot, Unit* target);
void MarkTargetWithCross(Player* bot, Unit* target);
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target);
bool IsSafeFromMagtheridonHazards(PlayerbotAI* botAI, Player* bot, float x, float y, float z); bool IsSafeFromMagtheridonHazards(PlayerbotAI* botAI, Player* bot, float x, float y, float z);
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot);
struct Location extern const Position WAITING_FOR_MAGTHERIDON_POSITION;
{ extern const Position MAGTHERIDON_TANK_POSITION;
float x, y, z, orientation; extern const Position NW_CHANNELER_TANK_POSITION;
}; extern const Position NE_CHANNELER_TANK_POSITION;
extern const Position RANGED_SPREAD_POSITION;
namespace MagtheridonsLairLocations extern const Position HEALER_SPREAD_POSITION;
{
extern const Location WaitingForMagtheridonPosition;
extern const Location MagtheridonTankPosition;
extern const Location NWChannelerTankPosition;
extern const Location NEChannelerTankPosition;
extern const Location RangedSpreadPosition;
extern const Location HealerSpreadPosition;
}
struct CubeInfo struct CubeInfo
{ {

View File

@@ -2,6 +2,7 @@
#define _PLAYERBOT_RAIDMCACTIONCONTEXT_H #define _PLAYERBOT_RAIDMCACTIONCONTEXT_H
#include "Action.h" #include "Action.h"
#include "BossAuraActions.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "RaidMcActions.h" #include "RaidMcActions.h"

View File

@@ -2,6 +2,7 @@
#define _PLAYERBOT_RAIDMCTRIGGERCONTEXT_H #define _PLAYERBOT_RAIDMCTRIGGERCONTEXT_H
#include "AiObjectContext.h" #include "AiObjectContext.h"
#include "BossAuraTriggers.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "RaidMcTriggers.h" #include "RaidMcTriggers.h"

View File

@@ -0,0 +1,142 @@
#include "RaidBossHelpers.h"
#include "Playerbots.h"
#include "RtiTargetValue.h"
// Functions to mark targets with raid target icons
// Note that these functions do not allow the player to change the icon during the encounter
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId)
{
if (!target)
return;
if (Group* group = bot->GetGroup())
{
ObjectGuid currentGuid = group->GetTargetIcon(iconId);
if (currentGuid != target->GetGUID())
group->SetTargetIcon(iconId, bot->GetGUID(), target->GetGUID());
}
}
void MarkTargetWithSkull(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::skullIndex);
}
void MarkTargetWithSquare(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::squareIndex);
}
void MarkTargetWithStar(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::starIndex);
}
void MarkTargetWithCircle(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::circleIndex);
}
void MarkTargetWithDiamond(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::diamondIndex);
}
void MarkTargetWithTriangle(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::triangleIndex);
}
void MarkTargetWithCross(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::crossIndex);
}
void MarkTargetWithMoon(Player* bot, Unit* target)
{
MarkTargetWithIcon(bot, target, RtiTargetValue::moonIndex);
}
// For bots to set their raid target icon to the specified icon on the specified target
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target)
{
if (!target)
return;
std::string currentRti = botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Get();
Unit* currentTarget = botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Get();
if (currentRti != rtiName || currentTarget != target)
{
botAI->GetAiObjectContext()->GetValue<std::string>("rti")->Set(rtiName);
botAI->GetAiObjectContext()->GetValue<Unit*>("rti target")->Set(target);
}
}
// Return the first alive DPS bot in the specified instance map, excluding any specified bot
// Intended for purposes of storing and erasing timers and trackers in associative containers
bool IsMechanicTrackerBot(PlayerbotAI* botAI, Player* bot, uint32 mapId, Player* exclude)
{
if (Group* group = bot->GetGroup())
{
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)
return member == bot;
}
}
return false;
}
// Return the first matching alive unit from a cell search of nearby npcs
// More responsive than "find target," but performance cost is much higher
// Re: using the third parameter (false by default), some units are never considered
// to be in combat (e.g., totems)
Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry, bool requireInCombat)
{
auto const& npcs =
botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest npcs")->Get();
for (auto const& npcGuid : npcs)
{
Unit* unit = botAI->GetUnit(npcGuid);
if (unit && unit->IsAlive() && unit->GetEntry() == entry)
{
if (!requireInCombat || unit->IsInCombat())
return unit;
}
}
return nullptr;
}
// Return the nearest alive player (human or bot) within the specified radius
Unit* GetNearestPlayerInRadius(Player* bot, float radius)
{
Unit* nearestPlayer = nullptr;
float nearestDistance = radius;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref != nullptr; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member == bot)
continue;
float distance = bot->GetExactDist2d(member);
if (distance < nearestDistance)
{
nearestDistance = distance;
nearestPlayer = member;
}
}
}
return nearestPlayer;
}

View File

@@ -0,0 +1,21 @@
#ifndef _PLAYERBOT_RAIDBOSSHELPERS_H_
#define _PLAYERBOT_RAIDBOSSHELPERS_H_
#include "AiObject.h"
#include "Unit.h"
void MarkTargetWithIcon(Player* bot, Unit* target, uint8 iconId);
void MarkTargetWithSkull(Player* bot, Unit* target);
void MarkTargetWithSquare(Player* bot, Unit* target);
void MarkTargetWithStar(Player* bot, Unit* target);
void MarkTargetWithCircle(Player* bot, Unit* target);
void MarkTargetWithDiamond(Player* bot, Unit* target);
void MarkTargetWithTriangle(Player* bot, Unit* target);
void MarkTargetWithCross(Player* bot, Unit* target);
void MarkTargetWithMoon(Player* bot, Unit* target);
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target);
bool IsMechanicTrackerBot(PlayerbotAI* botAI, Player* bot, uint32 mapId, Player* exclude = nullptr);
Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry, bool requireInCombat = false);
Unit* GetNearestPlayerInRadius(Player* bot, float radius);
#endif

View File

@@ -8,6 +8,7 @@
#include "RaidKarazhanStrategy.h" #include "RaidKarazhanStrategy.h"
#include "RaidMagtheridonStrategy.h" #include "RaidMagtheridonStrategy.h"
#include "RaidGruulsLairStrategy.h" #include "RaidGruulsLairStrategy.h"
#include "RaidSSCStrategy.h"
#include "RaidOsStrategy.h" #include "RaidOsStrategy.h"
#include "RaidEoEStrategy.h" #include "RaidEoEStrategy.h"
#include "RaidVoAStrategy.h" #include "RaidVoAStrategy.h"
@@ -26,10 +27,11 @@ public:
creators["karazhan"] = &RaidStrategyContext::karazhan; creators["karazhan"] = &RaidStrategyContext::karazhan;
creators["magtheridon"] = &RaidStrategyContext::magtheridon; creators["magtheridon"] = &RaidStrategyContext::magtheridon;
creators["gruulslair"] = &RaidStrategyContext::gruulslair; creators["gruulslair"] = &RaidStrategyContext::gruulslair;
creators["ssc"] = &RaidStrategyContext::ssc;
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;
creators["uld"] = &RaidStrategyContext::uld; creators["ulduar"] = &RaidStrategyContext::ulduar;
creators["onyxia"] = &RaidStrategyContext::onyxia; creators["onyxia"] = &RaidStrategyContext::onyxia;
creators["icc"] = &RaidStrategyContext::icc; creators["icc"] = &RaidStrategyContext::icc;
} }
@@ -41,11 +43,12 @@ private:
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* 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* ssc(PlayerbotAI* botAI) { return new RaidSSCStrategy(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); }
static Strategy* onyxia(PlayerbotAI* botAI) { return new RaidOnyxiaStrategy(botAI); } static Strategy* onyxia(PlayerbotAI* botAI) { return new RaidOnyxiaStrategy(botAI); }
static Strategy* uld(PlayerbotAI* botAI) { return new RaidUlduarStrategy(botAI); } static Strategy* ulduar(PlayerbotAI* botAI) { return new RaidUlduarStrategy(botAI); }
static Strategy* icc(PlayerbotAI* botAI) { return new RaidIccStrategy(botAI); } static Strategy* icc(PlayerbotAI* botAI) { return new RaidIccStrategy(botAI); }
}; };

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,457 @@
#ifndef _PLAYERBOT_RAIDSSCACTIONS_H
#define _PLAYERBOT_RAIDSSCACTIONS_H
#include "Action.h"
#include "AttackAction.h"
#include "MovementActions.h"
// General
class SerpentShrineCavernEraseTimersAndTrackersAction : public Action
{
public:
SerpentShrineCavernEraseTimersAndTrackersAction(
PlayerbotAI* botAI, std::string const name = "serpent shrine cavern erase timers and trackers") : Action(botAI, name) {}
bool Execute(Event event) override;
};
// Trash
class UnderbogColossusEscapeToxicPoolAction : public MovementAction
{
public:
UnderbogColossusEscapeToxicPoolAction(
PlayerbotAI* botAI, std::string const name = "underbog colossus escape toxic pool") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class GreyheartTidecallerMarkWaterElementalTotemAction : public Action
{
public:
GreyheartTidecallerMarkWaterElementalTotemAction(
PlayerbotAI* botAI, std::string const name = "greyheart tidecaller mark water elemental totem") : Action(botAI, name) {}
bool Execute(Event event) override;
};
// Hydross the Unstable <Duke of Currents>
class HydrossTheUnstablePositionFrostTankAction : public AttackAction
{
public:
HydrossTheUnstablePositionFrostTankAction(
PlayerbotAI* botAI, std::string const name = "hydross the unstable position frost tank") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class HydrossTheUnstablePositionNatureTankAction : public AttackAction
{
public:
HydrossTheUnstablePositionNatureTankAction(
PlayerbotAI* botAI, std::string const name = "hydross the unstable position nature tank") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class HydrossTheUnstablePrioritizeElementalAddsAction : public AttackAction
{
public:
HydrossTheUnstablePrioritizeElementalAddsAction(
PlayerbotAI* botAI, std::string const name = "hydross the unstable prioritize elemental adds") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class HydrossTheUnstableFrostPhaseSpreadOutAction : public MovementAction
{
public:
HydrossTheUnstableFrostPhaseSpreadOutAction(
PlayerbotAI* botAI, std::string const name = "hydross the unstable frost phase spread out") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class HydrossTheUnstableMisdirectBossToTankAction : public Action
{
public:
HydrossTheUnstableMisdirectBossToTankAction(
PlayerbotAI* botAI, std::string const name = "hydross the unstable misdirect boss to tank") : Action(botAI, name) {}
bool Execute(Event event) override;
private:
bool TryMisdirectToFrostTank(Unit* hydross, Group* group);
bool TryMisdirectToNatureTank(Unit* hydross, Group* group);
};
class HydrossTheUnstableStopDpsUponPhaseChangeAction : public Action
{
public:
HydrossTheUnstableStopDpsUponPhaseChangeAction(
PlayerbotAI* botAI, std::string const name = "hydross the unstable stop dps upon phase change") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class HydrossTheUnstableManageTimersAction : public Action
{
public:
HydrossTheUnstableManageTimersAction(
PlayerbotAI* botAI, std::string const name = "hydross the unstable manage timers") : Action(botAI, name) {}
bool Execute(Event event) override;
};
// The Lurker Below
class TheLurkerBelowRunAroundBehindBossAction : public MovementAction
{
public:
TheLurkerBelowRunAroundBehindBossAction(
PlayerbotAI* botAI, std::string const name = "the lurker below run around behind boss") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class TheLurkerBelowPositionMainTankAction : public AttackAction
{
public:
TheLurkerBelowPositionMainTankAction(
PlayerbotAI* botAI, std::string const name = "the lurker below position main tank") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class TheLurkerBelowSpreadRangedInArcAction : public MovementAction
{
public:
TheLurkerBelowSpreadRangedInArcAction(
PlayerbotAI* botAI, std::string const name = "the lurker below spread ranged in arc") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class TheLurkerBelowTanksPickUpAddsAction : public AttackAction
{
public:
TheLurkerBelowTanksPickUpAddsAction(
PlayerbotAI* botAI, std::string const name = "the lurker below tanks pick up adds") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class TheLurkerBelowManageSpoutTimerAction : public Action
{
public:
TheLurkerBelowManageSpoutTimerAction(
PlayerbotAI* botAI, std::string const name = "the lurker below manage spout timer") : Action(botAI, name) {}
bool Execute(Event event) override;
};
// Leotheras the Blind
class LeotherasTheBlindTargetSpellbindersAction : public Action
{
public:
LeotherasTheBlindTargetSpellbindersAction(
PlayerbotAI* botAI, std::string const name = "leotheras the blind target spellbinders") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class LeotherasTheBlindPositionRangedAction : public MovementAction
{
public:
LeotherasTheBlindPositionRangedAction(
PlayerbotAI* botAI, std::string const name = "leotheras the blind position ranged") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class LeotherasTheBlindDemonFormTankAttackBossAction : public AttackAction
{
public:
LeotherasTheBlindDemonFormTankAttackBossAction(
PlayerbotAI* botAI, std::string const name = "leotheras the blind demon form tank attack boss") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class LeotherasTheBlindMeleeTanksDontAttackDemonFormAction : public Action
{
public:
LeotherasTheBlindMeleeTanksDontAttackDemonFormAction(
PlayerbotAI* botAI, std::string const name = "leotheras the blind melee tanks don't attack demon form") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class LeotherasTheBlindRunAwayFromWhirlwindAction : public MovementAction
{
public:
LeotherasTheBlindRunAwayFromWhirlwindAction(
PlayerbotAI* botAI, std::string const name = "leotheras the blind run away from whirlwind") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class LeotherasTheBlindMeleeDpsRunAwayFromBossAction : public MovementAction
{
public:
LeotherasTheBlindMeleeDpsRunAwayFromBossAction(
PlayerbotAI* botAI, std::string const name = "leotheras the blind melee dps run away from boss") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class LeotherasTheBlindDestroyInnerDemonAction : public AttackAction
{
public:
LeotherasTheBlindDestroyInnerDemonAction(
PlayerbotAI* botAI, std::string const name = "leotheras the blind destroy inner demon") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
private:
bool HandleFeralTankStrategy(Unit* innerDemon);
bool HandleHealerStrategy(Unit* innerDemon);
};
class LeotherasTheBlindFinalPhaseAssignDpsPriorityAction : public AttackAction
{
public:
LeotherasTheBlindFinalPhaseAssignDpsPriorityAction(
PlayerbotAI* botAI, std::string const name = "leotheras the blind final phase assign dps priority") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class LeotherasTheBlindMisdirectBossToDemonFormTankAction : public AttackAction
{
public:
LeotherasTheBlindMisdirectBossToDemonFormTankAction(
PlayerbotAI* botAI, std::string const name = "leotheras the blind misdirect boss to demon form tank") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class LeotherasTheBlindManageDpsWaitTimersAction : public Action
{
public:
LeotherasTheBlindManageDpsWaitTimersAction(
PlayerbotAI* botAI, std::string const name = "leotheras the blind manage dps wait timers") : Action(botAI, name) {}
bool Execute(Event event) override;
};
// Fathom-Lord Karathress
class FathomLordKarathressMainTankPositionBossAction : public AttackAction
{
public:
FathomLordKarathressMainTankPositionBossAction(
PlayerbotAI* botAI, std::string const name = "fathom-lord karathress main tank position boss") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class FathomLordKarathressFirstAssistTankPositionCaribdisAction : public AttackAction
{
public:
FathomLordKarathressFirstAssistTankPositionCaribdisAction(
PlayerbotAI* botAI, std::string const name = "fathom-lord karathress first assist tank position caribdis") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class FathomLordKarathressSecondAssistTankPositionSharkkisAction : public AttackAction
{
public:
FathomLordKarathressSecondAssistTankPositionSharkkisAction(
PlayerbotAI* botAI, std::string const name = "fathom-lord karathress second assist tank position sharkkis") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class FathomLordKarathressThirdAssistTankPositionTidalvessAction : public AttackAction
{
public:
FathomLordKarathressThirdAssistTankPositionTidalvessAction(
PlayerbotAI* botAI, std::string const name = "fathom-lord karathress third assist tank position tidalvess") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class FathomLordKarathressPositionCaribdisTankHealerAction : public MovementAction
{
public:
FathomLordKarathressPositionCaribdisTankHealerAction(
PlayerbotAI* botAI, std::string const name = "fathom-lord karathress position caribdis tank healer") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class FathomLordKarathressMisdirectBossesToTanksAction : public AttackAction
{
public:
FathomLordKarathressMisdirectBossesToTanksAction(
PlayerbotAI* botAI, std::string const name = "fathom-lord karathress misdirect bosses to tanks") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class FathomLordKarathressAssignDpsPriorityAction : public AttackAction
{
public:
FathomLordKarathressAssignDpsPriorityAction(
PlayerbotAI* botAI, std::string const name = "fathom-lord karathress assign dps priority") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class FathomLordKarathressManageDpsTimerAction : public Action
{
public:
FathomLordKarathressManageDpsTimerAction(
PlayerbotAI* botAI, std::string const name = "fathom-lord karathress manage dps timer") : Action(botAI, name) {}
bool Execute(Event event) override;
};
// Morogrim Tidewalker
class MorogrimTidewalkerMisdirectBossToMainTankAction : public AttackAction
{
public:
MorogrimTidewalkerMisdirectBossToMainTankAction(
PlayerbotAI* botAI, std::string const name = "morogrim tidewalker misdirect boss to main tank") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class MorogrimTidewalkerMoveBossToTankPositionAction : public AttackAction
{
public:
MorogrimTidewalkerMoveBossToTankPositionAction(
PlayerbotAI* botAI, std::string const name = "morogrim tidewalker move boss to tank position") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
private:
bool MoveToPhase1TankPosition(Unit* tidewalker);
bool MoveToPhase2TankPosition(Unit* tidewalker);
};
class MorogrimTidewalkerPhase2RepositionRangedAction : public MovementAction
{
public:
MorogrimTidewalkerPhase2RepositionRangedAction(
PlayerbotAI* botAI, std::string const name = "morogrim tidewalker phase 2 reposition ranged") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
// Lady Vashj <Coilfang Matron>
class LadyVashjMainTankPositionBossAction : public AttackAction
{
public:
LadyVashjMainTankPositionBossAction(
PlayerbotAI* botAI, std::string const name = "lady vashj main tank position boss") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class LadyVashjPhase1SpreadRangedInArcAction : public MovementAction
{
public:
LadyVashjPhase1SpreadRangedInArcAction(
PlayerbotAI* botAI, std::string const name = "lady vashj phase 1 spread ranged in arc") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class LadyVashjSetGroundingTotemInMainTankGroupAction : public MovementAction
{
public:
LadyVashjSetGroundingTotemInMainTankGroupAction(
PlayerbotAI* botAI, std::string const name = "lady vashj set grounding totem in main tank group") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class LadyVashjStaticChargeMoveAwayFromGroupAction : public MovementAction
{
public:
LadyVashjStaticChargeMoveAwayFromGroupAction(
PlayerbotAI* botAI, std::string const name = "lady vashj static charge move away from group") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class LadyVashjMisdirectBossToMainTankAction : public AttackAction
{
public:
LadyVashjMisdirectBossToMainTankAction(
PlayerbotAI* botAI, std::string const name = "lady vashj misdirect boss to main tank") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class LadyVashjAssignPhase2AndPhase3DpsPriorityAction : public AttackAction
{
public:
LadyVashjAssignPhase2AndPhase3DpsPriorityAction(
PlayerbotAI* botAI, std::string const name = "lady vashj assign phase 2 and phase 3 dps priority") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class LadyVashjMisdirectStriderToFirstAssistTankAction : public AttackAction
{
public:
LadyVashjMisdirectStriderToFirstAssistTankAction(
PlayerbotAI* botAI, std::string const name = "lady vashj misdirect strider to first assist tank") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class LadyVashjTankAttackAndMoveAwayStriderAction : public AttackAction
{
public:
LadyVashjTankAttackAndMoveAwayStriderAction(
PlayerbotAI* botAI, std::string const name = "lady vashj tank attack and move away strider") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class LadyVashjTeleportToTaintedElementalAction : public AttackAction
{
public:
LadyVashjTeleportToTaintedElementalAction(
PlayerbotAI* botAI, std::string const name = "lady vashj teleport to tainted elemental") : AttackAction(botAI, name) {}
bool Execute(Event event) override;
};
class LadyVashjLootTaintedCoreAction : public MovementAction
{
public:
LadyVashjLootTaintedCoreAction(
PlayerbotAI* botAI, std::string const name = "lady vashj loot tainted core") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
};
class LadyVashjPassTheTaintedCoreAction : public MovementAction
{
public:
LadyVashjPassTheTaintedCoreAction(
PlayerbotAI* botAI, std::string const name = "lady vashj pass the tainted core") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
private:
bool LineUpFirstCorePasser(Player* designatedLooter, Unit* closestTrigger);
bool LineUpSecondCorePasser(Player* firstCorePasser, Unit* closestTrigger);
bool LineUpThirdCorePasser(Player* designatedLooter, Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger);
bool LineUpFourthCorePasser(Player* firstCorePasser, Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger);
bool IsFirstCorePasserInIntendedPosition(Player* designatedLooter, Player* firstCorePasser, Unit* closestTrigger);
bool IsSecondCorePasserInIntendedPosition(Player* firstCorePasser, Player* secondCorePasser, Unit* closestTrigger);
bool IsThirdCorePasserInIntendedPosition(Player* secondCorePasser, Player* thirdCorePasser, Unit* closestTrigger);
bool IsFourthCorePasserInIntendedPosition(Player* thirdCorePasser, Player* fourthCorePasser, Unit* closestTrigger);
void ScheduleTransferCoreAfterImbue(PlayerbotAI* botAI, Player* giver, Player* receiver);
bool UseCoreOnNearestGenerator(const uint32 instanceId);
};
class LadyVashjDestroyTaintedCoreAction : public Action
{
public:
LadyVashjDestroyTaintedCoreAction(PlayerbotAI* botAI, std::string const name = "lady vashj destroy tainted core") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class LadyVashjEraseCorePassingTrackersAction : public Action
{
public:
LadyVashjEraseCorePassingTrackersAction(PlayerbotAI* botAI, std::string const name = "lady vashj erase core passing trackers") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class LadyVashjAvoidToxicSporesAction : public MovementAction
{
public:
LadyVashjAvoidToxicSporesAction(PlayerbotAI* botAI, std::string const name = "lady vashj avoid toxic spores") : MovementAction(botAI, name) {}
bool Execute(Event event) override;
static std::vector<Unit*> GetAllSporeDropTriggers(PlayerbotAI* botAI, Player* bot);
private:
Position FindSafestNearbyPosition(const std::vector<Unit*>& spores, const Position& position, float maxRadius, float hazardRadius);
bool IsPathSafeFromSpores(const Position& start, const Position& end, const std::vector<Unit*>& spores, float hazardRadius);
};
class LadyVashjUseFreeActionAbilitiesAction : public Action
{
public:
LadyVashjUseFreeActionAbilitiesAction(PlayerbotAI* botAI, std::string const name = "lady vashj use free action abilities") : Action(botAI, name) {}
bool Execute(Event event) override;
};
#endif

View File

@@ -0,0 +1,799 @@
#include "RaidSSCMultipliers.h"
#include "RaidSSCActions.h"
#include "RaidSSCHelpers.h"
#include "ChooseTargetActions.h"
#include "DestroyItemAction.h"
#include "DKActions.h"
#include "DruidActions.h"
#include "DruidBearActions.h"
#include "DruidCatActions.h"
#include "DruidShapeshiftActions.h"
#include "FollowActions.h"
#include "GenericSpellActions.h"
#include "HunterActions.h"
#include "LootAction.h"
#include "MageActions.h"
#include "PaladinActions.h"
#include "Playerbots.h"
#include "ReachTargetActions.h"
#include "RogueActions.h"
#include "ShamanActions.h"
#include "WarlockActions.h"
#include "WarriorActions.h"
#include "WipeAction.h"
using namespace SerpentShrineCavernHelpers;
// Trash
float UnderbogColossusEscapeToxicPoolMultiplier::GetValue(Action* action)
{
if (bot->HasAura(SPELL_TOXIC_POOL))
{
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<UnderbogColossusEscapeToxicPoolAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Hydross the Unstable <Duke of Currents>
float HydrossTheUnstableDisableTankActionsMultiplier::GetValue(Action* action)
{
if (!botAI->IsMainTank(bot) && !botAI->IsAssistTankOfIndex(bot, 0, true))
return 1.0f;
Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable");
if (!hydross)
return 1.0f;
if (dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
if (dynamic_cast<CastReachTargetSpellAction*>(action) ||
dynamic_cast<ReachTargetAction*>(action) ||
(dynamic_cast<AttackAction*>(action) &&
!dynamic_cast<HydrossTheUnstablePositionFrostTankAction*>(action) &&
!dynamic_cast<HydrossTheUnstablePositionNatureTankAction*>(action)))
{
if ((botAI->IsMainTank(bot) && hydross->HasAura(SPELL_CORRUPTION)) ||
(botAI->IsAssistTankOfIndex(bot, 0, true) && !hydross->HasAura(SPELL_CORRUPTION)))
return 0.0f;
}
return 1.0f;
}
float HydrossTheUnstableWaitForDpsMultiplier::GetValue(Action* action)
{
Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable");
if (!hydross)
return 1.0f;
Unit* waterElemental = AI_VALUE2(Unit*, "find target", "pure spawn of hydross");
Unit* natureElemental = AI_VALUE2(Unit*, "find target", "tainted spawn of hydross");
if (botAI->IsAssistTank(bot) && !botAI->IsAssistTankOfIndex(bot, 0, true) &&
(waterElemental || natureElemental))
return 1.0f;
if (dynamic_cast<HydrossTheUnstableMisdirectBossToTankAction*>(action))
return 1.0f;
const uint32 instanceId = hydross->GetMap()->GetInstanceId();
const time_t now = std::time(nullptr);
constexpr uint8 phaseChangeWaitSeconds = 1;
constexpr uint8 dpsWaitSeconds = 5;
if (!hydross->HasAura(SPELL_CORRUPTION) && !botAI->IsMainTank(bot))
{
auto itDps = hydrossFrostDpsWaitTimer.find(instanceId);
auto itPhase = hydrossChangeToFrostPhaseTimer.find(instanceId);
bool justChanged = (itDps == hydrossFrostDpsWaitTimer.end() ||
(now - itDps->second) < dpsWaitSeconds);
bool aboutToChange = (itPhase != hydrossChangeToFrostPhaseTimer.end() &&
(now - itPhase->second) > phaseChangeWaitSeconds);
if (justChanged || aboutToChange)
{
if (dynamic_cast<AttackAction*>(action) ||
(dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
}
}
if (hydross->HasAura(SPELL_CORRUPTION) && !botAI->IsAssistTankOfIndex(bot, 0, true))
{
auto itDps = hydrossNatureDpsWaitTimer.find(instanceId);
auto itPhase = hydrossChangeToNaturePhaseTimer.find(instanceId);
bool justChanged = (itDps == hydrossNatureDpsWaitTimer.end() ||
(now - itDps->second) < dpsWaitSeconds);
bool aboutToChange = (itPhase != hydrossChangeToNaturePhaseTimer.end() &&
(now - itPhase->second) > phaseChangeWaitSeconds);
if (justChanged || aboutToChange)
{
if (dynamic_cast<AttackAction*>(action) ||
(dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
}
}
return 1.0f;
}
float HydrossTheUnstableControlMisdirectionMultiplier::GetValue(Action* action)
{
if (bot->getClass() != CLASS_HUNTER)
return 1.0f;
if (AI_VALUE2(Unit*, "find target", "hydross the unstable"))
{
if (dynamic_cast<CastMisdirectionOnMainTankAction*>(action))
return 0.0f;
}
return 1.0f;
}
// The Lurker Below
float TheLurkerBelowStayAwayFromSpoutMultiplier::GetValue(Action* action)
{
Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below");
if (!lurker)
return 1.0f;
const time_t now = std::time(nullptr);
auto it = lurkerSpoutTimer.find(lurker->GetMap()->GetInstanceId());
if (it != lurkerSpoutTimer.end() && it->second > now)
{
if (dynamic_cast<CastReachTargetSpellAction*>(action) ||
dynamic_cast<CastKillingSpreeAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action))
return 0.0f;
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<AttackAction*>(action) &&
!dynamic_cast<TheLurkerBelowRunAroundBehindBossAction*>(action))
return 0.0f;
}
return 1.0f;
}
float TheLurkerBelowMaintainRangedSpreadMultiplier::GetValue(Action* action)
{
if (!botAI->IsRanged(bot))
return 1.0f;
if (AI_VALUE2(Unit*, "find target", "the lurker below"))
{
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Disable tank assist during Submerge only if there are 3 or more tanks in the raid
float TheLurkerBelowDisableTankAssistMultiplier::GetValue(Action* action)
{
if (!botAI->IsTank(bot))
return 1.0f;
if (bot->GetVictim() == nullptr)
return 1.0f;
Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below");
if (!lurker || lurker->getStandState() != UNIT_STAND_STATE_SUBMERGED)
return 1.0f;
Group* group = bot->GetGroup();
if (!group)
return 1.0f;
uint8 tankCount = 0;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive())
continue;
if (botAI->IsTank(member))
++tankCount;
}
if (tankCount >= 3)
{
if (dynamic_cast<TankAssistAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Leotheras the Blind
float LeotherasTheBlindAvoidWhirlwindMultiplier::GetValue(Action* action)
{
if (botAI->IsTank(bot))
return 1.0f;
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return 1.0f;
Unit* leotherasHuman = GetLeotherasHuman(botAI);
if (!leotherasHuman)
return 1.0f;
if (!leotherasHuman->HasAura(SPELL_LEOTHERAS_BANISHED) &&
(leotherasHuman->HasAura(SPELL_WHIRLWIND) ||
leotherasHuman->HasAura(SPELL_WHIRLWIND_CHANNEL)))
{
if (dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<AttackAction*>(action) &&
!dynamic_cast<LeotherasTheBlindRunAwayFromWhirlwindAction*>(action))
return 0.0f;
}
return 1.0f;
}
float LeotherasTheBlindDisableTankActionsMultiplier::GetValue(Action* action)
{
if (!botAI->IsTank(bot) || bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return 1.0f;
if (!AI_VALUE2(Unit*, "find target", "leotheras the blind"))
return 1.0f;
if (GetPhase2LeotherasDemon(botAI) && dynamic_cast<AttackAction*>(action))
return 0.0f;
if (!GetPhase3LeotherasDemon(botAI) && dynamic_cast<CastBerserkAction*>(action))
return 0.0f;
return 1.0f;
}
float LeotherasTheBlindFocusOnInnerDemonMultiplier::GetValue(Action* action)
{
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
{
if (dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<DpsAssistAction*>(action) ||
dynamic_cast<CastHealingSpellAction*>(action) ||
dynamic_cast<CastCureSpellAction*>(action) ||
dynamic_cast<CurePartyMemberAction*>(action) ||
dynamic_cast<CastBuffSpellAction*>(action) ||
dynamic_cast<ResurrectPartyMemberAction*>(action) ||
dynamic_cast<PartyMemberActionNameSupport*>(action) ||
dynamic_cast<CastBearFormAction*>(action) ||
dynamic_cast<CastDireBearFormAction*>(action) ||
dynamic_cast<CastTreeFormAction*>(action))
return 0.0f;
}
return 1.0f;
}
float LeotherasTheBlindMeleeDpsAvoidChaosBlastMultiplier::GetValue(Action* action)
{
if (botAI->IsRanged(bot) || botAI->IsTank(bot))
return 1.0f;
if (!GetPhase2LeotherasDemon(botAI))
return 1.0f;
Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST);
if (chaosBlast && chaosBlast->GetStackAmount() >= 5)
{
if (dynamic_cast<AttackAction*>(action) ||
dynamic_cast<ReachTargetAction*>(action) ||
dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<CastReachTargetSpellAction*>(action) ||
dynamic_cast<CastKillingSpreeAction*>(action))
return 0.0f;
}
return 1.0f;
}
float LeotherasTheBlindWaitForDpsMultiplier::GetValue(Action* action)
{
Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind");
if (!leotheras)
return 1.0f;
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return 1.0f;
if (dynamic_cast<LeotherasTheBlindMisdirectBossToDemonFormTankAction*>(action))
return 1.0f;
const uint32 instanceId = leotheras->GetMap()->GetInstanceId();
const time_t now = std::time(nullptr);
constexpr uint8 dpsWaitSecondsPhase1 = 5;
Unit* leotherasHuman = GetLeotherasHuman(botAI);
Unit* leotherasPhase3Demon = GetPhase3LeotherasDemon(botAI);
if (leotherasHuman && !leotherasHuman->HasAura(SPELL_LEOTHERAS_BANISHED) &&
!leotherasPhase3Demon)
{
if (botAI->IsTank(bot))
return 1.0f;
auto it = leotherasHumanFormDpsWaitTimer.find(instanceId);
if (it == leotherasHumanFormDpsWaitTimer.end() ||
(now - it->second) < dpsWaitSecondsPhase1)
{
if (dynamic_cast<AttackAction*>(action) ||
(dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
}
}
constexpr uint8 dpsWaitSecondsPhase2 = 12;
Unit* leotherasPhase2Demon = GetPhase2LeotherasDemon(botAI);
Player* demonFormTank = GetLeotherasDemonFormTank(bot);
if (leotherasPhase2Demon)
{
if (demonFormTank && demonFormTank == bot)
return 1.0f;
if (!demonFormTank && botAI->IsTank(bot))
return 1.0f;
auto it = leotherasDemonFormDpsWaitTimer.find(instanceId);
if (it == leotherasDemonFormDpsWaitTimer.end() ||
(now - it->second) < dpsWaitSecondsPhase2)
{
if (dynamic_cast<AttackAction*>(action) ||
(dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
}
}
constexpr uint8 dpsWaitSecondsPhase3 = 8;
if (leotherasPhase3Demon)
{
if ((demonFormTank && demonFormTank == bot) || botAI->IsTank(bot))
return 1.0f;
auto it = leotherasFinalPhaseDpsWaitTimer.find(instanceId);
if (it == leotherasFinalPhaseDpsWaitTimer.end() ||
(now - it->second) < dpsWaitSecondsPhase3)
{
if (dynamic_cast<AttackAction*>(action) ||
(dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
}
}
return 1.0f;
}
// Don't use Bloodlust/Heroism during the Channeler phase
float LeotherasTheBlindDelayBloodlustAndHeroismMultiplier::GetValue(Action* action)
{
if (bot->getClass() != CLASS_SHAMAN)
return 1.0f;
Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind");
if (leotheras && leotheras->HasAura(SPELL_LEOTHERAS_BANISHED))
{
if (dynamic_cast<CastHeroismAction*>(action) ||
dynamic_cast<CastBloodlustAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Fathom-Lord Karathress
float FathomLordKarathressDisableTankActionsMultiplier::GetValue(Action* action)
{
if (!botAI->IsTank(bot))
return 1.0f;
if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress"))
return 1.0f;
if (bot->GetVictim() != nullptr && dynamic_cast<TankAssistAction*>(action))
return 0.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<AvoidAoeAction*>(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 FathomLordKarathressDisableAoeMultiplier::GetValue(Action* action)
{
if (!botAI->IsDps(bot))
return 1.0f;
if (AI_VALUE2(Unit*, "find target", "fathom-lord karathress"))
{
if (auto castSpellAction = dynamic_cast<CastSpellAction*>(action))
{
if (castSpellAction->getThreatType() == Action::ActionThreatType::Aoe)
return 0.0f;
}
}
return 1.0f;
}
float FathomLordKarathressControlMisdirectionMultiplier::GetValue(Action* action)
{
if (bot->getClass() != CLASS_HUNTER)
return 1.0f;
if (AI_VALUE2(Unit*, "find target", "fathom-lord karathress"))
{
if (dynamic_cast<CastMisdirectionOnMainTankAction*>(action))
return 0.0f;
}
return 1.0f;
}
float FathomLordKarathressWaitForDpsMultiplier::GetValue(Action* action)
{
if (botAI->IsTank(bot))
return 1.0f;
Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress");
if (!karathress)
return 1.0f;
if (dynamic_cast<FathomLordKarathressMisdirectBossesToTanksAction*>(action))
return 1.0f;
const time_t now = std::time(nullptr);
constexpr uint8 dpsWaitSeconds = 12;
auto it = karathressDpsWaitTimer.find(karathress->GetMap()->GetInstanceId());
if (it == karathressDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds)
{
if (dynamic_cast<AttackAction*>(action) ||
(dynamic_cast<CastSpellAction*>(action) &&
!dynamic_cast<CastHealingSpellAction*>(action)))
return 0.0f;
}
return 1.0f;
}
float FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier::GetValue(Action* action)
{
if (!botAI->IsAssistHealOfIndex(bot, 0, true))
return 1.0f;
if (AI_VALUE2(Unit*, "find target", "fathom-guard caribdis"))
{
if (dynamic_cast<FleeAction*>(action) ||
dynamic_cast<FollowAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Morogrim Tidewalker
// Use Bloodlust/Heroism after the first Murloc spawn
float MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier::GetValue(Action* action)
{
if (bot->getClass() != CLASS_SHAMAN)
return 1.0f;
if (!AI_VALUE2(Unit*, "find target", "morogrim tidewalker"))
return 1.0f;
if (!AI_VALUE2(Unit*, "find target", "tidewalker lurker"))
{
if (dynamic_cast<CastHeroismAction*>(action) ||
dynamic_cast<CastBloodlustAction*>(action))
return 0.0f;
}
return 1.0f;
}
float MorogrimTidewalkerDisableTankActionsMultiplier::GetValue(Action* action)
{
if (!botAI->IsMainTank(bot))
return 1.0f;
if (AI_VALUE2(Unit*, "find target", "morogrim tidewalker"))
{
if (dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
}
return 1.0f;
}
float MorogrimTidewalkerMaintainPhase2StackingMultiplier::GetValue(Action* action)
{
if (!botAI->IsRanged(bot))
return 1.0f;
Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker");
if (!tidewalker)
return 1.0f;
if (tidewalker->GetHealthPct() < 25.0f)
{
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Lady Vashj <Coilfang Matron>
// Wait until phase 3 to use Bloodlust/Heroism
// Don't use other major cooldowns in Phase 1, either
float LadyVashjDelayCooldownsMultiplier::GetValue(Action* action)
{
if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
return 1.0f;
if (bot->getClass() == CLASS_SHAMAN)
{
if (IsLadyVashjInPhase3(botAI))
return 1.0f;
if (dynamic_cast<CastBloodlustAction*>(action) ||
dynamic_cast<CastHeroismAction*>(action))
return 0.0f;
}
if (botAI->IsDps(bot) && IsLadyVashjInPhase1(botAI))
{
if (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 LadyVashjMaintainPhase1RangedSpreadMultiplier::GetValue(Action* action)
{
if (!botAI->IsRanged(bot))
return 1.0f;
if (AI_VALUE2(Unit*, "find target", "lady vashj") &&
IsLadyVashjInPhase1(botAI))
{
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<CastBlinkBackAction*>(action))
return 0.0f;
}
return 1.0f;
}
float LadyVashjStaticChargeStayAwayFromGroupMultiplier::GetValue(Action* action)
{
if (botAI->IsMainTank(bot) || !bot->HasAura(SPELL_STATIC_CHARGE))
return 1.0f;
if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
return 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action) ||
dynamic_cast<ReachTargetAction*>(action) ||
dynamic_cast<FollowAction*>(action) ||
dynamic_cast<CastKillingSpreeAction*>(action) ||
dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
return 1.0f;
}
// Bots should not loot the core with normal looting logic
float LadyVashjDoNotLootTheTaintedCoreMultiplier::GetValue(Action* action)
{
if (AI_VALUE2(Unit*, "find target", "lady vashj"))
{
if (dynamic_cast<LootAction*>(action))
return 0.0f;
}
return 1.0f;
}
float LadyVashjCorePassersPrioritizePositioningMultiplier::GetValue(Action* action)
{
if (!AI_VALUE2(Unit*, "find target", "lady vashj") ||
!IsLadyVashjInPhase2(botAI))
return 1.0f;
if (dynamic_cast<WipeAction*>(action) ||
dynamic_cast<DestroyItemAction*>(action) ||
dynamic_cast<LadyVashjDestroyTaintedCoreAction*>(action))
return 1.0f;
Group* group = bot->GetGroup();
if (!group)
return 1.0f;
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI);
Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI);
Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI);
Player* fourthCorePasser = GetFourthTaintedCorePasser(group, botAI);
auto hasCore = [](Player* player)
{
return player && player->HasItemCount(ITEM_TAINTED_CORE, 1, false);
};
if (hasCore(bot))
{
if (!dynamic_cast<LadyVashjPassTheTaintedCoreAction*>(action))
return 0.0f;
}
if (bot == designatedLooter)
{
if (!hasCore(bot))
return 1.0f;
}
else if (bot == firstCorePasser)
{
if (hasCore(secondCorePasser) || hasCore(thirdCorePasser) ||
hasCore(fourthCorePasser))
return 1.0f;
}
else if (bot == secondCorePasser)
{
if (hasCore(thirdCorePasser) || hasCore(fourthCorePasser))
return 1.0f;
}
else if (bot == thirdCorePasser)
{
if (hasCore(fourthCorePasser))
return 1.0f;
}
else if (bot != fourthCorePasser)
return 1.0f;
if (AI_VALUE2(Unit*, "find target", "tainted elemental") &&
(bot == firstCorePasser || bot == secondCorePasser))
{
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<LadyVashjPassTheTaintedCoreAction*>(action))
return 0.0f;
}
if (AnyRecentCoreInInventory(group, botAI))
{
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<LadyVashjPassTheTaintedCoreAction*>(action))
return 0.0f;
}
return 1.0f;
}
// All of phases 2 and 3 require a custom movement and targeting system
// So the standard target selection system must be disabled
float LadyVashjDisableAutomaticTargetingAndMovementModifier::GetValue(Action *action)
{
if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
return 1.0f;
if (dynamic_cast<AvoidAoeAction*>(action))
return 0.0f;
if (IsLadyVashjInPhase2(botAI))
{
if (dynamic_cast<DpsAssistAction*>(action) ||
dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<FollowAction*>(action) ||
dynamic_cast<FleeAction*>(action))
return 0.0f;
if (!botAI->IsHeal(bot) && dynamic_cast<CastHealingSpellAction*>(action))
return 0.0f;
Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental");
if (enchanted && bot->GetVictim() == enchanted)
{
if (dynamic_cast<CastDebuffSpellOnAttackerAction*>(action))
return 0.0f;
}
}
if (IsLadyVashjInPhase3(botAI))
{
if (dynamic_cast<DpsAssistAction*>(action))
return 0.0f;
Unit* enchanted = AI_VALUE2(Unit*, "find target", "enchanted elemental");
Unit* strider = AI_VALUE2(Unit*, "find target", "coilfang strider");
Unit* elite = AI_VALUE2(Unit*, "find target", "coilfang elite");
if (enchanted || strider || elite)
{
if (dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<FollowAction*>(action) ||
dynamic_cast<FleeAction*>(action))
return 0.0f;
if (enchanted && bot->GetVictim() == enchanted)
{
if (dynamic_cast<CastDebuffSpellOnAttackerAction*>(action))
return 0.0f;
}
}
else if (dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
}
return 1.0f;
}

View File

@@ -0,0 +1,236 @@
#ifndef _PLAYERBOT_RAIDSSCMULTIPLIERS_H
#define _PLAYERBOT_RAIDSSCMULTIPLIERS_H
#include "Multiplier.h"
// Trash
class UnderbogColossusEscapeToxicPoolMultiplier : public Multiplier
{
public:
UnderbogColossusEscapeToxicPoolMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "underbog colossus escape toxic pool") {}
virtual float GetValue(Action* action);
};
// Hydross the Unstable <Duke of Currents>
class HydrossTheUnstableDisableTankActionsMultiplier : public Multiplier
{
public:
HydrossTheUnstableDisableTankActionsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "hydross the unstable disable tank actions") {}
virtual float GetValue(Action* action);
};
class HydrossTheUnstableWaitForDpsMultiplier : public Multiplier
{
public:
HydrossTheUnstableWaitForDpsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "hydross the unstable wait for dps") {}
virtual float GetValue(Action* action);
};
class HydrossTheUnstableControlMisdirectionMultiplier : public Multiplier
{
public:
HydrossTheUnstableControlMisdirectionMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "hydross the unstable control misdirection") {}
virtual float GetValue(Action* action);
};
// The Lurker Below
class TheLurkerBelowStayAwayFromSpoutMultiplier : public Multiplier
{
public:
TheLurkerBelowStayAwayFromSpoutMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "the lurker below stay away from spout") {}
virtual float GetValue(Action* action);
};
class TheLurkerBelowMaintainRangedSpreadMultiplier : public Multiplier
{
public:
TheLurkerBelowMaintainRangedSpreadMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "the lurker below maintain ranged spread") {}
virtual float GetValue(Action* action);
};
class TheLurkerBelowDisableTankAssistMultiplier : public Multiplier
{
public:
TheLurkerBelowDisableTankAssistMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "the lurker below disable tank assist") {}
virtual float GetValue(Action* action);
};
// Leotheras the Blind
class LeotherasTheBlindAvoidWhirlwindMultiplier : public Multiplier
{
public:
LeotherasTheBlindAvoidWhirlwindMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind avoid whirlwind") {}
virtual float GetValue(Action* action);
};
class LeotherasTheBlindDisableTankActionsMultiplier : public Multiplier
{
public:
LeotherasTheBlindDisableTankActionsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind disable tank actions") {}
virtual float GetValue(Action* action);
};
class LeotherasTheBlindMeleeDpsAvoidChaosBlastMultiplier : public Multiplier
{
public:
LeotherasTheBlindMeleeDpsAvoidChaosBlastMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind melee dps avoid chaos blast") {}
virtual float GetValue(Action* action);
};
class LeotherasTheBlindFocusOnInnerDemonMultiplier : public Multiplier
{
public:
LeotherasTheBlindFocusOnInnerDemonMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind focus on inner demon") {}
virtual float GetValue(Action* action);
};
class LeotherasTheBlindWaitForDpsMultiplier : public Multiplier
{
public:
LeotherasTheBlindWaitForDpsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind wait for dps") {}
virtual float GetValue(Action* action);
};
class LeotherasTheBlindDelayBloodlustAndHeroismMultiplier : public Multiplier
{
public:
LeotherasTheBlindDelayBloodlustAndHeroismMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "leotheras the blind delay bloodlust and heroism") {}
virtual float GetValue(Action* action);
};
// Fathom-Lord Karathress
class FathomLordKarathressDisableTankActionsMultiplier : public Multiplier
{
public:
FathomLordKarathressDisableTankActionsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "fathom-lord karathress disable tank actions") {}
virtual float GetValue(Action* action);
};
class FathomLordKarathressDisableAoeMultiplier : public Multiplier
{
public:
FathomLordKarathressDisableAoeMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "fathom-lord karathress disable aoe") {}
virtual float GetValue(Action* action);
};
class FathomLordKarathressControlMisdirectionMultiplier : public Multiplier
{
public:
FathomLordKarathressControlMisdirectionMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "fathom-lord karathress control misdirection") {}
virtual float GetValue(Action* action);
};
class FathomLordKarathressWaitForDpsMultiplier : public Multiplier
{
public:
FathomLordKarathressWaitForDpsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "fathom-lord karathress wait for dps") {}
virtual float GetValue(Action* action);
};
class FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier : public Multiplier
{
public:
FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "fathom-lord karathress caribdis tank healer maintain position") {}
virtual float GetValue(Action* action);
};
// Morogrim Tidewalker
class MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier : public Multiplier
{
public:
MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "morogrim tidewalker delay bloodlust and heroism") {}
virtual float GetValue(Action* action);
};
class MorogrimTidewalkerDisableTankActionsMultiplier : public Multiplier
{
public:
MorogrimTidewalkerDisableTankActionsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "morogrim tidewalker disable tank actions") {}
virtual float GetValue(Action* action);
};
class MorogrimTidewalkerMaintainPhase2StackingMultiplier : public Multiplier
{
public:
MorogrimTidewalkerMaintainPhase2StackingMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "morogrim tidewalker maintain phase2 stacking") {}
virtual float GetValue(Action* action);
};
// Lady Vashj <Coilfang Matron>
class LadyVashjDelayCooldownsMultiplier : public Multiplier
{
public:
LadyVashjDelayCooldownsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj delay cooldowns") {}
virtual float GetValue(Action* action);
};
class LadyVashjMaintainPhase1RangedSpreadMultiplier : public Multiplier
{
public:
LadyVashjMaintainPhase1RangedSpreadMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj maintain phase1 ranged spread") {}
virtual float GetValue(Action* action);
};
class LadyVashjStaticChargeStayAwayFromGroupMultiplier : public Multiplier
{
public:
LadyVashjStaticChargeStayAwayFromGroupMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj static charge stay away from group") {}
virtual float GetValue(Action* action);
};
class LadyVashjDoNotLootTheTaintedCoreMultiplier : public Multiplier
{
public:
LadyVashjDoNotLootTheTaintedCoreMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj do not loot the tainted core") {}
virtual float GetValue(Action* action);
};
class LadyVashjCorePassersPrioritizePositioningMultiplier : public Multiplier
{
public:
LadyVashjCorePassersPrioritizePositioningMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj core passers prioritize positioning") {}
virtual float GetValue(Action* action);
};
class LadyVashjDisableAutomaticTargetingAndMovementModifier : public Multiplier
{
public:
LadyVashjDisableAutomaticTargetingAndMovementModifier(
PlayerbotAI* botAI) : Multiplier(botAI, "lady vashj disable automatic targeting and movement") {}
virtual float GetValue(Action* action);
};
#endif

View File

@@ -0,0 +1,337 @@
#ifndef _PLAYERBOT_RAIDSSCACTIONCONTEXT_H
#define _PLAYERBOT_RAIDSSCACTIONCONTEXT_H
#include "RaidSSCActions.h"
#include "NamedObjectContext.h"
class RaidSSCActionContext : public NamedObjectContext<Action>
{
public:
RaidSSCActionContext()
{
// General
creators["serpent shrine cavern erase timers and trackers"] =
&RaidSSCActionContext::serpent_shrine_cavern_erase_timers_and_trackers;
// Trash
creators["underbog colossus escape toxic pool"] =
&RaidSSCActionContext::underbog_colossus_escape_toxic_pool;
creators["greyheart tidecaller mark water elemental totem"] =
&RaidSSCActionContext::greyheart_tidecaller_mark_water_elemental_totem;
// Hydross the Unstable <Duke of Currents>
creators["hydross the unstable position frost tank"] =
&RaidSSCActionContext::hydross_the_unstable_position_frost_tank;
creators["hydross the unstable position nature tank"] =
&RaidSSCActionContext::hydross_the_unstable_position_nature_tank;
creators["hydross the unstable prioritize elemental adds"] =
&RaidSSCActionContext::hydross_the_unstable_prioritize_elemental_adds;
creators["hydross the unstable frost phase spread out"] =
&RaidSSCActionContext::hydross_the_unstable_frost_phase_spread_out;
creators["hydross the unstable misdirect boss to tank"] =
&RaidSSCActionContext::hydross_the_unstable_misdirect_boss_to_tank;
creators["hydross the unstable stop dps upon phase change"] =
&RaidSSCActionContext::hydross_the_unstable_stop_dps_upon_phase_change;
creators["hydross the unstable manage timers"] =
&RaidSSCActionContext::hydross_the_unstable_manage_timers;
// The Lurker Below
creators["the lurker below run around behind boss"] =
&RaidSSCActionContext::the_lurker_below_run_around_behind_boss;
creators["the lurker below position main tank"] =
&RaidSSCActionContext::the_lurker_below_position_main_tank;
creators["the lurker below spread ranged in arc"] =
&RaidSSCActionContext::the_lurker_below_spread_ranged_in_arc;
creators["the lurker below tanks pick up adds"] =
&RaidSSCActionContext::the_lurker_below_tanks_pick_up_adds;
creators["the lurker below manage spout timer"] =
&RaidSSCActionContext::the_lurker_below_manage_spout_timer;
// Leotheras the Blind
creators["leotheras the blind target spellbinders"] =
&RaidSSCActionContext::leotheras_the_blind_target_spellbinders;
creators["leotheras the blind demon form tank attack boss"] =
&RaidSSCActionContext::leotheras_the_blind_demon_form_tank_attack_boss;
creators["leotheras the blind melee tanks don't attack demon form"] =
&RaidSSCActionContext::leotheras_the_blind_melee_tanks_dont_attack_demon_form;
creators["leotheras the blind position ranged"] =
&RaidSSCActionContext::leotheras_the_blind_position_ranged;
creators["leotheras the blind run away from whirlwind"] =
&RaidSSCActionContext::leotheras_the_blind_run_away_from_whirlwind;
creators["leotheras the blind melee dps run away from boss"] =
&RaidSSCActionContext::leotheras_the_blind_melee_dps_run_away_from_boss;
creators["leotheras the blind destroy inner demon"] =
&RaidSSCActionContext::leotheras_the_blind_destroy_inner_demon;
creators["leotheras the blind final phase assign dps priority"] =
&RaidSSCActionContext::leotheras_the_blind_final_phase_assign_dps_priority;
creators["leotheras the blind misdirect boss to demon form tank"] =
&RaidSSCActionContext::leotheras_the_blind_misdirect_boss_to_demon_form_tank;
creators["leotheras the blind manage dps wait timers"] =
&RaidSSCActionContext::leotheras_the_blind_manage_dps_wait_timers;
// Fathom-Lord Karathress
creators["fathom-lord karathress main tank position boss"] =
&RaidSSCActionContext::fathom_lord_karathress_main_tank_position_boss;
creators["fathom-lord karathress first assist tank position caribdis"] =
&RaidSSCActionContext::fathom_lord_karathress_first_assist_tank_position_caribdis;
creators["fathom-lord karathress second assist tank position sharkkis"] =
&RaidSSCActionContext::fathom_lord_karathress_second_assist_tank_position_sharkkis;
creators["fathom-lord karathress third assist tank position tidalvess"] =
&RaidSSCActionContext::fathom_lord_karathress_third_assist_tank_position_tidalvess;
creators["fathom-lord karathress position caribdis tank healer"] =
&RaidSSCActionContext::fathom_lord_karathress_position_caribdis_tank_healer;
creators["fathom-lord karathress misdirect bosses to tanks"] =
&RaidSSCActionContext::fathom_lord_karathress_misdirect_bosses_to_tanks;
creators["fathom-lord karathress assign dps priority"] =
&RaidSSCActionContext::fathom_lord_karathress_assign_dps_priority;
creators["fathom-lord karathress manage dps timer"] =
&RaidSSCActionContext::fathom_lord_karathress_manage_dps_timer;
// Morogrim Tidewalker
creators["morogrim tidewalker misdirect boss to main tank"] =
&RaidSSCActionContext::morogrim_tidewalker_misdirect_boss_to_main_tank;
creators["morogrim tidewalker move boss to tank position"] =
&RaidSSCActionContext::morogrim_tidewalker_move_boss_to_tank_position;
creators["morogrim tidewalker phase 2 reposition ranged"] =
&RaidSSCActionContext::morogrim_tidewalker_phase_2_reposition_ranged;
// Lady Vashj <Coilfang Matron>
creators["lady vashj main tank position boss"] =
&RaidSSCActionContext::lady_vashj_main_tank_position_boss;
creators["lady vashj phase 1 spread ranged in arc"] =
&RaidSSCActionContext::lady_vashj_phase_1_spread_ranged_in_arc;
creators["lady vashj set grounding totem in main tank group"] =
&RaidSSCActionContext::lady_vashj_set_grounding_totem_in_main_tank_group;
creators["lady vashj static charge move away from group"] =
&RaidSSCActionContext::lady_vashj_static_charge_move_away_from_group;
creators["lady vashj misdirect boss to main tank"] =
&RaidSSCActionContext::lady_vashj_misdirect_boss_to_main_tank;
creators["lady vashj assign phase 2 and phase 3 dps priority"] =
&RaidSSCActionContext::lady_vashj_assign_phase_2_and_phase_3_dps_priority;
creators["lady vashj misdirect strider to first assist tank"] =
&RaidSSCActionContext::lady_vashj_misdirect_strider_to_first_assist_tank;
creators["lady vashj tank attack and move away strider"] =
&RaidSSCActionContext::lady_vashj_tank_attack_and_move_away_strider;
creators["lady vashj loot tainted core"] =
&RaidSSCActionContext::lady_vashj_loot_tainted_core;
creators["lady vashj teleport to tainted elemental"] =
&RaidSSCActionContext::lady_vashj_teleport_to_tainted_elemental;
creators["lady vashj pass the tainted core"] =
&RaidSSCActionContext::lady_vashj_pass_the_tainted_core;
creators["lady vashj destroy tainted core"] =
&RaidSSCActionContext::lady_vashj_destroy_tainted_core;
creators["lady vashj erase core passing trackers"] =
&RaidSSCActionContext::lady_vashj_erase_core_passing_trackers;
creators["lady vashj avoid toxic spores"] =
&RaidSSCActionContext::lady_vashj_avoid_toxic_spores;
creators["lady vashj use free action abilities"] =
&RaidSSCActionContext::lady_vashj_use_free_action_abilities;
}
private:
// General
static Action* serpent_shrine_cavern_erase_timers_and_trackers(
PlayerbotAI* botAI) { return new SerpentShrineCavernEraseTimersAndTrackersAction(botAI); }
// Trash
static Action* underbog_colossus_escape_toxic_pool(
PlayerbotAI* botAI) { return new UnderbogColossusEscapeToxicPoolAction(botAI); }
static Action* greyheart_tidecaller_mark_water_elemental_totem(
PlayerbotAI* botAI) { return new GreyheartTidecallerMarkWaterElementalTotemAction(botAI); }
// Hydross the Unstable <Duke of Currents>
static Action* hydross_the_unstable_position_frost_tank(
PlayerbotAI* botAI) { return new HydrossTheUnstablePositionFrostTankAction(botAI); }
static Action* hydross_the_unstable_position_nature_tank(
PlayerbotAI* botAI) { return new HydrossTheUnstablePositionNatureTankAction(botAI); }
static Action* hydross_the_unstable_prioritize_elemental_adds(
PlayerbotAI* botAI) { return new HydrossTheUnstablePrioritizeElementalAddsAction(botAI); }
static Action* hydross_the_unstable_frost_phase_spread_out(
PlayerbotAI* botAI) { return new HydrossTheUnstableFrostPhaseSpreadOutAction(botAI); }
static Action* hydross_the_unstable_misdirect_boss_to_tank(
PlayerbotAI* botAI) { return new HydrossTheUnstableMisdirectBossToTankAction(botAI); }
static Action* hydross_the_unstable_stop_dps_upon_phase_change(
PlayerbotAI* botAI) { return new HydrossTheUnstableStopDpsUponPhaseChangeAction(botAI); }
static Action* hydross_the_unstable_manage_timers(
PlayerbotAI* botAI) { return new HydrossTheUnstableManageTimersAction(botAI); }
// The Lurker Below
static Action* the_lurker_below_run_around_behind_boss(
PlayerbotAI* botAI) { return new TheLurkerBelowRunAroundBehindBossAction(botAI); }
static Action* the_lurker_below_position_main_tank(
PlayerbotAI* botAI) { return new TheLurkerBelowPositionMainTankAction(botAI); }
static Action* the_lurker_below_spread_ranged_in_arc(
PlayerbotAI* botAI) { return new TheLurkerBelowSpreadRangedInArcAction(botAI); }
static Action* the_lurker_below_tanks_pick_up_adds(
PlayerbotAI* botAI) { return new TheLurkerBelowTanksPickUpAddsAction(botAI); }
static Action* the_lurker_below_manage_spout_timer(
PlayerbotAI* botAI) { return new TheLurkerBelowManageSpoutTimerAction(botAI); }
// Leotheras the Blind
static Action* leotheras_the_blind_target_spellbinders(
PlayerbotAI* botAI) { return new LeotherasTheBlindTargetSpellbindersAction(botAI); }
static Action* leotheras_the_blind_demon_form_tank_attack_boss(
PlayerbotAI* botAI) { return new LeotherasTheBlindDemonFormTankAttackBossAction(botAI); }
static Action* leotheras_the_blind_melee_tanks_dont_attack_demon_form(
PlayerbotAI* botAI) { return new LeotherasTheBlindMeleeTanksDontAttackDemonFormAction(botAI); }
static Action* leotheras_the_blind_position_ranged(
PlayerbotAI* botAI) { return new LeotherasTheBlindPositionRangedAction(botAI); }
static Action* leotheras_the_blind_run_away_from_whirlwind(
PlayerbotAI* botAI) { return new LeotherasTheBlindRunAwayFromWhirlwindAction(botAI); }
static Action* leotheras_the_blind_melee_dps_run_away_from_boss(
PlayerbotAI* botAI) { return new LeotherasTheBlindMeleeDpsRunAwayFromBossAction(botAI); }
static Action* leotheras_the_blind_destroy_inner_demon(
PlayerbotAI* botAI) { return new LeotherasTheBlindDestroyInnerDemonAction(botAI); }
static Action* leotheras_the_blind_misdirect_boss_to_demon_form_tank(
PlayerbotAI* botAI) { return new LeotherasTheBlindMisdirectBossToDemonFormTankAction(botAI); }
static Action* leotheras_the_blind_final_phase_assign_dps_priority(
PlayerbotAI* botAI) { return new LeotherasTheBlindFinalPhaseAssignDpsPriorityAction(botAI); }
static Action* leotheras_the_blind_manage_dps_wait_timers(
PlayerbotAI* botAI) { return new LeotherasTheBlindManageDpsWaitTimersAction(botAI); }
// Fathom-Lord Karathress
static Action* fathom_lord_karathress_main_tank_position_boss(
PlayerbotAI* botAI) { return new FathomLordKarathressMainTankPositionBossAction(botAI); }
static Action* fathom_lord_karathress_first_assist_tank_position_caribdis(
PlayerbotAI* botAI) { return new FathomLordKarathressFirstAssistTankPositionCaribdisAction(botAI); }
static Action* fathom_lord_karathress_second_assist_tank_position_sharkkis(
PlayerbotAI* botAI) { return new FathomLordKarathressSecondAssistTankPositionSharkkisAction(botAI); }
static Action* fathom_lord_karathress_third_assist_tank_position_tidalvess(
PlayerbotAI* botAI) { return new FathomLordKarathressThirdAssistTankPositionTidalvessAction(botAI); }
static Action* fathom_lord_karathress_position_caribdis_tank_healer(
PlayerbotAI* botAI) { return new FathomLordKarathressPositionCaribdisTankHealerAction(botAI); }
static Action* fathom_lord_karathress_misdirect_bosses_to_tanks(
PlayerbotAI* botAI) { return new FathomLordKarathressMisdirectBossesToTanksAction(botAI); }
static Action* fathom_lord_karathress_assign_dps_priority(
PlayerbotAI* botAI) { return new FathomLordKarathressAssignDpsPriorityAction(botAI); }
static Action* fathom_lord_karathress_manage_dps_timer(
PlayerbotAI* botAI) { return new FathomLordKarathressManageDpsTimerAction(botAI); }
// Morogrim Tidewalker
static Action* morogrim_tidewalker_misdirect_boss_to_main_tank(
PlayerbotAI* botAI) { return new MorogrimTidewalkerMisdirectBossToMainTankAction(botAI); }
static Action* morogrim_tidewalker_move_boss_to_tank_position(
PlayerbotAI* botAI) { return new MorogrimTidewalkerMoveBossToTankPositionAction(botAI); }
static Action* morogrim_tidewalker_phase_2_reposition_ranged(
PlayerbotAI* botAI) { return new MorogrimTidewalkerPhase2RepositionRangedAction(botAI); }
// Lady Vashj <Coilfang Matron>
static Action* lady_vashj_main_tank_position_boss(
PlayerbotAI* botAI) { return new LadyVashjMainTankPositionBossAction(botAI); }
static Action* lady_vashj_phase_1_spread_ranged_in_arc(
PlayerbotAI* botAI) { return new LadyVashjPhase1SpreadRangedInArcAction(botAI); }
static Action* lady_vashj_set_grounding_totem_in_main_tank_group(
PlayerbotAI* botAI) { return new LadyVashjSetGroundingTotemInMainTankGroupAction(botAI); }
static Action* lady_vashj_static_charge_move_away_from_group(
PlayerbotAI* botAI) { return new LadyVashjStaticChargeMoveAwayFromGroupAction(botAI); }
static Action* lady_vashj_misdirect_boss_to_main_tank(
PlayerbotAI* botAI) { return new LadyVashjMisdirectBossToMainTankAction(botAI); }
static Action* lady_vashj_assign_phase_2_and_phase_3_dps_priority(
PlayerbotAI* botAI) { return new LadyVashjAssignPhase2AndPhase3DpsPriorityAction(botAI); }
static Action* lady_vashj_misdirect_strider_to_first_assist_tank(
PlayerbotAI* botAI) { return new LadyVashjMisdirectStriderToFirstAssistTankAction(botAI); }
static Action* lady_vashj_tank_attack_and_move_away_strider(
PlayerbotAI* botAI) { return new LadyVashjTankAttackAndMoveAwayStriderAction(botAI); }
static Action* lady_vashj_teleport_to_tainted_elemental(
PlayerbotAI* botAI) { return new LadyVashjTeleportToTaintedElementalAction(botAI); }
static Action* lady_vashj_loot_tainted_core(
PlayerbotAI* botAI) { return new LadyVashjLootTaintedCoreAction(botAI); }
static Action* lady_vashj_pass_the_tainted_core(
PlayerbotAI* botAI) { return new LadyVashjPassTheTaintedCoreAction(botAI); }
static Action* lady_vashj_destroy_tainted_core(
PlayerbotAI* botAI) { return new LadyVashjDestroyTaintedCoreAction(botAI); }
static Action* lady_vashj_erase_core_passing_trackers(
PlayerbotAI* botAI) { return new LadyVashjEraseCorePassingTrackersAction(botAI); }
static Action* lady_vashj_avoid_toxic_spores(
PlayerbotAI* botAI) { return new LadyVashjAvoidToxicSporesAction(botAI); }
static Action* lady_vashj_use_free_action_abilities(
PlayerbotAI* botAI) { return new LadyVashjUseFreeActionAbilitiesAction(botAI); }
};
#endif

View File

@@ -0,0 +1,325 @@
#ifndef _PLAYERBOT_RAIDSSCTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDSSCTRIGGERCONTEXT_H
#include "RaidSSCTriggers.h"
#include "AiObjectContext.h"
class RaidSSCTriggerContext : public NamedObjectContext<Trigger>
{
public:
RaidSSCTriggerContext()
{
// General
creators["serpent shrine cavern bot is not in combat"] =
&RaidSSCTriggerContext::serpent_shrine_cavern_bot_is_not_in_combat;
// Trash
creators["underbog colossus spawned toxic pool after death"] =
&RaidSSCTriggerContext::underbog_colossus_spawned_toxic_pool_after_death;
creators["greyheart tidecaller water elemental totem spawned"] =
&RaidSSCTriggerContext::greyheart_tidecaller_water_elemental_totem_spawned;
// Hydross the Unstable <Duke of Currents>
creators["hydross the unstable bot is frost tank"] =
&RaidSSCTriggerContext::hydross_the_unstable_bot_is_frost_tank;
creators["hydross the unstable bot is nature tank"] =
&RaidSSCTriggerContext::hydross_the_unstable_bot_is_nature_tank;
creators["hydross the unstable elementals spawned"] =
&RaidSSCTriggerContext::hydross_the_unstable_elementals_spawned;
creators["hydross the unstable danger from water tombs"] =
&RaidSSCTriggerContext::hydross_the_unstable_danger_from_water_tombs;
creators["hydross the unstable tank needs aggro upon phase change"] =
&RaidSSCTriggerContext::hydross_the_unstable_tank_needs_aggro_upon_phase_change;
creators["hydross the unstable aggro resets upon phase change"] =
&RaidSSCTriggerContext::hydross_the_unstable_aggro_resets_upon_phase_change;
creators["hydross the unstable need to manage timers"] =
&RaidSSCTriggerContext::hydross_the_unstable_need_to_manage_timers;
// The Lurker Below
creators["the lurker below spout is active"] =
&RaidSSCTriggerContext::the_lurker_below_spout_is_active;
creators["the lurker below boss is active for main tank"] =
&RaidSSCTriggerContext::the_lurker_below_boss_is_active_for_main_tank;
creators["the lurker below boss casts geyser"] =
&RaidSSCTriggerContext::the_lurker_below_boss_casts_geyser;
creators["the lurker below boss is submerged"] =
&RaidSSCTriggerContext::the_lurker_below_boss_is_submerged;
creators["the lurker below need to prepare timer for spout"] =
&RaidSSCTriggerContext::the_lurker_below_need_to_prepare_timer_for_spout;
// Leotheras the Blind
creators["leotheras the blind boss is inactive"] =
&RaidSSCTriggerContext::leotheras_the_blind_boss_is_inactive;
creators["leotheras the blind boss transformed into demon form"] =
&RaidSSCTriggerContext::leotheras_the_blind_boss_transformed_into_demon_form;
creators["leotheras the blind only warlock should tank demon form"] =
&RaidSSCTriggerContext::leotheras_the_blind_only_warlock_should_tank_demon_form;
creators["leotheras the blind boss engaged by ranged"] =
&RaidSSCTriggerContext::leotheras_the_blind_boss_engaged_by_ranged;
creators["leotheras the blind boss channeling whirlwind"] =
&RaidSSCTriggerContext::leotheras_the_blind_boss_channeling_whirlwind;
creators["leotheras the blind bot has too many chaos blast stacks"] =
&RaidSSCTriggerContext::leotheras_the_blind_bot_has_too_many_chaos_blast_stacks;
creators["leotheras the blind inner demon has awakened"] =
&RaidSSCTriggerContext::leotheras_the_blind_inner_demon_has_awakened;
creators["leotheras the blind entered final phase"] =
&RaidSSCTriggerContext::leotheras_the_blind_entered_final_phase;
creators["leotheras the blind demon form tank needs aggro"] =
&RaidSSCTriggerContext::leotheras_the_blind_demon_form_tank_needs_aggro;
creators["leotheras the blind boss wipes aggro upon phase change"] =
&RaidSSCTriggerContext::leotheras_the_blind_boss_wipes_aggro_upon_phase_change;
// Fathom-Lord Karathress
creators["fathom-lord karathress boss engaged by main tank"] =
&RaidSSCTriggerContext::fathom_lord_karathress_boss_engaged_by_main_tank;
creators["fathom-lord karathress caribdis engaged by first assist tank"] =
&RaidSSCTriggerContext::fathom_lord_karathress_caribdis_engaged_by_first_assist_tank;
creators["fathom-lord karathress sharkkis engaged by second assist tank"] =
&RaidSSCTriggerContext::fathom_lord_karathress_sharkkis_engaged_by_second_assist_tank;
creators["fathom-lord karathress tidalvess engaged by third assist tank"] =
&RaidSSCTriggerContext::fathom_lord_karathress_tidalvess_engaged_by_third_assist_tank;
creators["fathom-lord karathress caribdis tank needs dedicated healer"] =
&RaidSSCTriggerContext::fathom_lord_karathress_caribdis_tank_needs_dedicated_healer;
creators["fathom-lord karathress pulling bosses"] =
&RaidSSCTriggerContext::fathom_lord_karathress_pulling_bosses;
creators["fathom-lord karathress determining kill order"] =
&RaidSSCTriggerContext::fathom_lord_karathress_determining_kill_order;
creators["fathom-lord karathress tanks need to establish aggro"] =
&RaidSSCTriggerContext::fathom_lord_karathress_tanks_need_to_establish_aggro;
// Morogrim Tidewalker
creators["morogrim tidewalker boss engaged by main tank"] =
&RaidSSCTriggerContext::morogrim_tidewalker_boss_engaged_by_main_tank;
creators["morogrim tidewalker pulling boss"] =
&RaidSSCTriggerContext::morogrim_tidewalker_pulling_boss;
creators["morogrim tidewalker water globules are incoming"] =
&RaidSSCTriggerContext::morogrim_tidewalker_water_globules_are_incoming;
// Lady Vashj <Coilfang Matron>
creators["lady vashj boss engaged by main tank"] =
&RaidSSCTriggerContext::lady_vashj_boss_engaged_by_main_tank;
creators["lady vashj boss engaged by ranged in phase 1"] =
&RaidSSCTriggerContext::lady_vashj_boss_engaged_by_ranged_in_phase_1;
creators["lady vashj casts shock blast on highest aggro"] =
&RaidSSCTriggerContext::lady_vashj_casts_shock_blast_on_highest_aggro;
creators["lady vashj bot has static charge"] =
&RaidSSCTriggerContext::lady_vashj_bot_has_static_charge;
creators["lady vashj pulling boss in phase 1 and phase 3"] =
&RaidSSCTriggerContext::lady_vashj_pulling_boss_in_phase_1_and_phase_3;
creators["lady vashj adds spawn in phase 2 and phase 3"] =
&RaidSSCTriggerContext::lady_vashj_adds_spawn_in_phase_2_and_phase_3;
creators["lady vashj coilfang strider is approaching"] =
&RaidSSCTriggerContext::lady_vashj_coilfang_strider_is_approaching;
creators["lady vashj tainted elemental cheat"] =
&RaidSSCTriggerContext::lady_vashj_tainted_elemental_cheat;
creators["lady vashj tainted core was looted"] =
&RaidSSCTriggerContext::lady_vashj_tainted_core_was_looted;
creators["lady vashj tainted core is unusable"] =
&RaidSSCTriggerContext::lady_vashj_tainted_core_is_unusable;
creators["lady vashj need to reset core passing trackers"] =
&RaidSSCTriggerContext::lady_vashj_need_to_reset_core_passing_trackers;
creators["lady vashj toxic sporebats are spewing poison clouds"] =
&RaidSSCTriggerContext::lady_vashj_toxic_sporebats_are_spewing_poison_clouds;
creators["lady vashj bot is entangled in toxic spores or static charge"] =
&RaidSSCTriggerContext::lady_vashj_bot_is_entangled_in_toxic_spores_or_static_charge;
}
private:
// General
static Trigger* serpent_shrine_cavern_bot_is_not_in_combat(
PlayerbotAI* botAI) { return new SerpentShrineCavernBotIsNotInCombatTrigger(botAI); }
// Trash
static Trigger* underbog_colossus_spawned_toxic_pool_after_death(
PlayerbotAI* botAI) { return new UnderbogColossusSpawnedToxicPoolAfterDeathTrigger(botAI); }
static Trigger* greyheart_tidecaller_water_elemental_totem_spawned(
PlayerbotAI* botAI) { return new GreyheartTidecallerWaterElementalTotemSpawnedTrigger(botAI); }
// Hydross the Unstable <Duke of Currents>
static Trigger* hydross_the_unstable_bot_is_frost_tank(
PlayerbotAI* botAI) { return new HydrossTheUnstableBotIsFrostTankTrigger(botAI); }
static Trigger* hydross_the_unstable_bot_is_nature_tank(
PlayerbotAI* botAI) { return new HydrossTheUnstableBotIsNatureTankTrigger(botAI); }
static Trigger* hydross_the_unstable_elementals_spawned(
PlayerbotAI* botAI) { return new HydrossTheUnstableElementalsSpawnedTrigger(botAI); }
static Trigger* hydross_the_unstable_danger_from_water_tombs(
PlayerbotAI* botAI) { return new HydrossTheUnstableDangerFromWaterTombsTrigger(botAI); }
static Trigger* hydross_the_unstable_tank_needs_aggro_upon_phase_change(
PlayerbotAI* botAI) { return new HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger(botAI); }
static Trigger* hydross_the_unstable_aggro_resets_upon_phase_change(
PlayerbotAI* botAI) { return new HydrossTheUnstableAggroResetsUponPhaseChangeTrigger(botAI); }
static Trigger* hydross_the_unstable_need_to_manage_timers(
PlayerbotAI* botAI) { return new HydrossTheUnstableNeedToManageTimersTrigger(botAI); }
// The Lurker Below
static Trigger* the_lurker_below_spout_is_active(
PlayerbotAI* botAI) { return new TheLurkerBelowSpoutIsActiveTrigger(botAI); }
static Trigger* the_lurker_below_boss_is_active_for_main_tank(
PlayerbotAI* botAI) { return new TheLurkerBelowBossIsActiveForMainTankTrigger(botAI); }
static Trigger* the_lurker_below_boss_casts_geyser(
PlayerbotAI* botAI) { return new TheLurkerBelowBossCastsGeyserTrigger(botAI); }
static Trigger* the_lurker_below_boss_is_submerged(
PlayerbotAI* botAI) { return new TheLurkerBelowBossIsSubmergedTrigger(botAI); }
static Trigger* the_lurker_below_need_to_prepare_timer_for_spout(
PlayerbotAI* botAI) { return new TheLurkerBelowNeedToPrepareTimerForSpoutTrigger(botAI); }
// Leotheras the Blind
static Trigger* leotheras_the_blind_boss_is_inactive(
PlayerbotAI* botAI) { return new LeotherasTheBlindBossIsInactiveTrigger(botAI); }
static Trigger* leotheras_the_blind_boss_transformed_into_demon_form(
PlayerbotAI* botAI) { return new LeotherasTheBlindBossTransformedIntoDemonFormTrigger(botAI); }
static Trigger* leotheras_the_blind_only_warlock_should_tank_demon_form(
PlayerbotAI* botAI) { return new LeotherasTheBlindOnlyWarlockShouldTankDemonFormTrigger(botAI); }
static Trigger* leotheras_the_blind_boss_engaged_by_ranged(
PlayerbotAI* botAI) { return new LeotherasTheBlindBossEngagedByRangedTrigger(botAI); }
static Trigger* leotheras_the_blind_boss_channeling_whirlwind(
PlayerbotAI* botAI) { return new LeotherasTheBlindBossChannelingWhirlwindTrigger(botAI); }
static Trigger* leotheras_the_blind_bot_has_too_many_chaos_blast_stacks(
PlayerbotAI* botAI) { return new LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger(botAI); }
static Trigger* leotheras_the_blind_inner_demon_has_awakened(
PlayerbotAI* botAI) { return new LeotherasTheBlindInnerDemonHasAwakenedTrigger(botAI); }
static Trigger* leotheras_the_blind_entered_final_phase(
PlayerbotAI* botAI) { return new LeotherasTheBlindEnteredFinalPhaseTrigger(botAI); }
static Trigger* leotheras_the_blind_demon_form_tank_needs_aggro(
PlayerbotAI* botAI) { return new LeotherasTheBlindDemonFormTankNeedsAggro(botAI); }
static Trigger* leotheras_the_blind_boss_wipes_aggro_upon_phase_change(
PlayerbotAI* botAI) { return new LeotherasTheBlindBossWipesAggroUponPhaseChangeTrigger(botAI); }
// Fathom-Lord Karathress
static Trigger* fathom_lord_karathress_boss_engaged_by_main_tank(
PlayerbotAI* botAI) { return new FathomLordKarathressBossEngagedByMainTankTrigger(botAI); }
static Trigger* fathom_lord_karathress_caribdis_engaged_by_first_assist_tank(
PlayerbotAI* botAI) { return new FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger(botAI); }
static Trigger* fathom_lord_karathress_sharkkis_engaged_by_second_assist_tank(
PlayerbotAI* botAI) { return new FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger(botAI); }
static Trigger* fathom_lord_karathress_tidalvess_engaged_by_third_assist_tank(
PlayerbotAI* botAI) { return new FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger(botAI); }
static Trigger* fathom_lord_karathress_caribdis_tank_needs_dedicated_healer(
PlayerbotAI* botAI) { return new FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger(botAI); }
static Trigger* fathom_lord_karathress_pulling_bosses(
PlayerbotAI* botAI) { return new FathomLordKarathressPullingBossesTrigger(botAI); }
static Trigger* fathom_lord_karathress_determining_kill_order(
PlayerbotAI* botAI) { return new FathomLordKarathressDeterminingKillOrderTrigger(botAI); }
static Trigger* fathom_lord_karathress_tanks_need_to_establish_aggro(
PlayerbotAI* botAI) { return new FathomLordKarathressTanksNeedToEstablishAggroTrigger(botAI); }
// Morogrim Tidewalker
static Trigger* morogrim_tidewalker_boss_engaged_by_main_tank(
PlayerbotAI* botAI) { return new MorogrimTidewalkerBossEngagedByMainTankTrigger(botAI); }
static Trigger* morogrim_tidewalker_pulling_boss(
PlayerbotAI* botAI) { return new MorogrimTidewalkerPullingBossTrigger(botAI); }
static Trigger* morogrim_tidewalker_water_globules_are_incoming(
PlayerbotAI* botAI) { return new MorogrimTidewalkerWaterGlobulesAreIncomingTrigger(botAI); }
// Lady Vashj <Coilfang Matron>
static Trigger* lady_vashj_boss_engaged_by_main_tank(
PlayerbotAI* botAI) { return new LadyVashjBossEngagedByMainTankTrigger(botAI); }
static Trigger* lady_vashj_boss_engaged_by_ranged_in_phase_1(
PlayerbotAI* botAI) { return new LadyVashjBossEngagedByRangedInPhase1Trigger(botAI); }
static Trigger* lady_vashj_casts_shock_blast_on_highest_aggro(
PlayerbotAI* botAI) { return new LadyVashjCastsShockBlastOnHighestAggroTrigger(botAI); }
static Trigger* lady_vashj_bot_has_static_charge(
PlayerbotAI* botAI) { return new LadyVashjBotHasStaticChargeTrigger(botAI); }
static Trigger* lady_vashj_pulling_boss_in_phase_1_and_phase_3(
PlayerbotAI* botAI) { return new LadyVashjPullingBossInPhase1AndPhase3Trigger(botAI); }
static Trigger* lady_vashj_adds_spawn_in_phase_2_and_phase_3(
PlayerbotAI* botAI) { return new LadyVashjAddsSpawnInPhase2AndPhase3Trigger(botAI); }
static Trigger* lady_vashj_coilfang_strider_is_approaching(
PlayerbotAI* botAI) { return new LadyVashjCoilfangStriderIsApproachingTrigger(botAI); }
static Trigger* lady_vashj_tainted_elemental_cheat(
PlayerbotAI* botAI) { return new LadyVashjTaintedElementalCheatTrigger(botAI); }
static Trigger* lady_vashj_tainted_core_was_looted(
PlayerbotAI* botAI) { return new LadyVashjTaintedCoreWasLootedTrigger(botAI); }
static Trigger* lady_vashj_tainted_core_is_unusable(
PlayerbotAI* botAI) { return new LadyVashjTaintedCoreIsUnusableTrigger(botAI); }
static Trigger* lady_vashj_need_to_reset_core_passing_trackers(
PlayerbotAI* botAI) { return new LadyVashjNeedToResetCorePassingTrackersTrigger(botAI); }
static Trigger* lady_vashj_toxic_sporebats_are_spewing_poison_clouds(
PlayerbotAI* botAI) { return new LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger(botAI); }
static Trigger* lady_vashj_bot_is_entangled_in_toxic_spores_or_static_charge(
PlayerbotAI* botAI) { return new LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger(botAI); }
};
#endif

View File

@@ -0,0 +1,206 @@
#include "RaidSSCStrategy.h"
#include "RaidSSCMultipliers.h"
void RaidSSCStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
// General
triggers.push_back(new TriggerNode("serpent shrine cavern bot is not in combat", {
NextAction("serpent shrine cavern erase timers and trackers", ACTION_EMERGENCY + 11) }));
// Trash Mobs
triggers.push_back(new TriggerNode("underbog colossus spawned toxic pool after death", {
NextAction("underbog colossus escape toxic pool", ACTION_EMERGENCY + 10) }));
triggers.push_back(new TriggerNode("greyheart tidecaller water elemental totem spawned", {
NextAction("greyheart tidecaller mark water elemental totem", ACTION_RAID + 1) }));
// Hydross the Unstable <Duke of Currents>
triggers.push_back(new TriggerNode("hydross the unstable bot is frost tank", {
NextAction("hydross the unstable position frost tank", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("hydross the unstable bot is nature tank", {
NextAction("hydross the unstable position nature tank", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("hydross the unstable elementals spawned", {
NextAction("hydross the unstable prioritize elemental adds", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("hydross the unstable danger from water tombs", {
NextAction("hydross the unstable frost phase spread out", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode("hydross the unstable tank needs aggro upon phase change", {
NextAction("hydross the unstable misdirect boss to tank", ACTION_EMERGENCY + 6) }));
triggers.push_back(new TriggerNode("hydross the unstable aggro resets upon phase change", {
NextAction("hydross the unstable stop dps upon phase change", ACTION_EMERGENCY + 9) }));
triggers.push_back(new TriggerNode("hydross the unstable need to manage timers", {
NextAction("hydross the unstable manage timers", ACTION_EMERGENCY + 10) }));
// The Lurker Below
triggers.push_back(new TriggerNode("the lurker below spout is active", {
NextAction("the lurker below run around behind boss", ACTION_EMERGENCY + 6) }));
triggers.push_back(new TriggerNode("the lurker below boss is active for main tank", {
NextAction("the lurker below position main tank", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("the lurker below boss casts geyser", {
NextAction("the lurker below spread ranged in arc", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("the lurker below boss is submerged", {
NextAction("the lurker below tanks pick up adds", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode("the lurker below need to prepare timer for spout", {
NextAction("the lurker below manage spout timer", ACTION_EMERGENCY + 10) }));
// Leotheras the Blind
triggers.push_back(new TriggerNode("leotheras the blind boss is inactive", {
NextAction("leotheras the blind target spellbinders", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("leotheras the blind boss transformed into demon form", {
NextAction("leotheras the blind demon form tank attack boss", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("leotheras the blind only warlock should tank demon form", {
NextAction("leotheras the blind melee tanks don't attack demon form", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("leotheras the blind boss engaged by ranged", {
NextAction("leotheras the blind position ranged", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("leotheras the blind boss channeling whirlwind", {
NextAction("leotheras the blind run away from whirlwind", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode("leotheras the blind bot has too many chaos blast stacks", {
NextAction("leotheras the blind melee dps run away from boss", ACTION_EMERGENCY + 6) }));
triggers.push_back(new TriggerNode("leotheras the blind inner demon has awakened", {
NextAction("leotheras the blind destroy inner demon", ACTION_EMERGENCY + 7) }));
triggers.push_back(new TriggerNode("leotheras the blind entered final phase", {
NextAction("leotheras the blind final phase assign dps priority", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("leotheras the blind demon form tank needs aggro", {
NextAction("leotheras the blind misdirect boss to demon form tank", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("leotheras the blind boss wipes aggro upon phase change", {
NextAction("leotheras the blind manage dps wait timers", ACTION_EMERGENCY + 10) }));
// Fathom-Lord Karathress
triggers.push_back(new TriggerNode("fathom-lord karathress boss engaged by main tank", {
NextAction("fathom-lord karathress main tank position boss", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("fathom-lord karathress caribdis engaged by first assist tank", {
NextAction("fathom-lord karathress first assist tank position caribdis", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("fathom-lord karathress sharkkis engaged by second assist tank", {
NextAction("fathom-lord karathress second assist tank position sharkkis", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("fathom-lord karathress tidalvess engaged by third assist tank", {
NextAction("fathom-lord karathress third assist tank position tidalvess", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("fathom-lord karathress caribdis tank needs dedicated healer", {
NextAction("fathom-lord karathress position caribdis tank healer", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("fathom-lord karathress pulling bosses", {
NextAction("fathom-lord karathress misdirect bosses to tanks", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("fathom-lord karathress determining kill order", {
NextAction("fathom-lord karathress assign dps priority", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("fathom-lord karathress tanks need to establish aggro", {
NextAction("fathom-lord karathress manage dps timer", ACTION_EMERGENCY + 10) }));
// Morogrim Tidewalker
triggers.push_back(new TriggerNode("morogrim tidewalker boss engaged by main tank", {
NextAction("morogrim tidewalker move boss to tank position", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("morogrim tidewalker water globules are incoming", {
NextAction("morogrim tidewalker phase 2 reposition ranged", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("morogrim tidewalker pulling boss", {
NextAction("morogrim tidewalker misdirect boss to main tank", ACTION_RAID + 1) }));
// Lady Vashj <Coilfang Matron>
triggers.push_back(new TriggerNode("lady vashj boss engaged by main tank", {
NextAction("lady vashj main tank position boss", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("lady vashj boss engaged by ranged in phase 1", {
NextAction("lady vashj phase 1 spread ranged in arc", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("lady vashj casts shock blast on highest aggro", {
NextAction("lady vashj set grounding totem in main tank group", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode("lady vashj bot has static charge", {
NextAction("lady vashj static charge move away from group", ACTION_EMERGENCY + 7) }));
triggers.push_back(new TriggerNode("lady vashj pulling boss in phase 1 and phase 3", {
NextAction("lady vashj misdirect boss to main tank", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("lady vashj tainted elemental cheat", {
NextAction("lady vashj teleport to tainted elemental", ACTION_EMERGENCY + 10),
NextAction("lady vashj loot tainted core", ACTION_EMERGENCY + 10) }));
triggers.push_back(new TriggerNode("lady vashj tainted core was looted", {
NextAction("lady vashj pass the tainted core", ACTION_EMERGENCY + 10) }));
triggers.push_back(new TriggerNode("lady vashj tainted core is unusable", {
NextAction("lady vashj destroy tainted core", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode("lady vashj need to reset core passing trackers", {
NextAction("lady vashj erase core passing trackers", ACTION_EMERGENCY + 10) }));
triggers.push_back(new TriggerNode("lady vashj adds spawn in phase 2 and phase 3", {
NextAction("lady vashj assign phase 2 and phase 3 dps priority", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("lady vashj coilfang strider is approaching", {
NextAction("lady vashj misdirect strider to first assist tank", ACTION_EMERGENCY + 2),
NextAction("lady vashj tank attack and move away strider", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode("lady vashj toxic sporebats are spewing poison clouds", {
NextAction("lady vashj avoid toxic spores", ACTION_EMERGENCY + 6) }));
triggers.push_back(new TriggerNode("lady vashj bot is entangled in toxic spores or static charge", {
NextAction("lady vashj use free action abilities", ACTION_EMERGENCY + 7) }));
}
void RaidSSCStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{
// Trash Mobs
multipliers.push_back(new UnderbogColossusEscapeToxicPoolMultiplier(botAI));
// Hydross the Unstable <Duke of Currents>
multipliers.push_back(new HydrossTheUnstableDisableTankActionsMultiplier(botAI));
multipliers.push_back(new HydrossTheUnstableWaitForDpsMultiplier(botAI));
multipliers.push_back(new HydrossTheUnstableControlMisdirectionMultiplier(botAI));
// The Lurker Below
multipliers.push_back(new TheLurkerBelowStayAwayFromSpoutMultiplier(botAI));
multipliers.push_back(new TheLurkerBelowMaintainRangedSpreadMultiplier(botAI));
multipliers.push_back(new TheLurkerBelowDisableTankAssistMultiplier(botAI));
// Leotheras the Blind
multipliers.push_back(new LeotherasTheBlindAvoidWhirlwindMultiplier(botAI));
multipliers.push_back(new LeotherasTheBlindDisableTankActionsMultiplier(botAI));
multipliers.push_back(new LeotherasTheBlindMeleeDpsAvoidChaosBlastMultiplier(botAI));
multipliers.push_back(new LeotherasTheBlindFocusOnInnerDemonMultiplier(botAI));
multipliers.push_back(new LeotherasTheBlindWaitForDpsMultiplier(botAI));
multipliers.push_back(new LeotherasTheBlindDelayBloodlustAndHeroismMultiplier(botAI));
// Fathom-Lord Karathress
multipliers.push_back(new FathomLordKarathressDisableTankActionsMultiplier(botAI));
multipliers.push_back(new FathomLordKarathressDisableAoeMultiplier(botAI));
multipliers.push_back(new FathomLordKarathressControlMisdirectionMultiplier(botAI));
multipliers.push_back(new FathomLordKarathressWaitForDpsMultiplier(botAI));
multipliers.push_back(new FathomLordKarathressCaribdisTankHealerMaintainPositionMultiplier(botAI));
// Morogrim Tidewalker
multipliers.push_back(new MorogrimTidewalkerDelayBloodlustAndHeroismMultiplier(botAI));
multipliers.push_back(new MorogrimTidewalkerDisableTankActionsMultiplier(botAI));
multipliers.push_back(new MorogrimTidewalkerMaintainPhase2StackingMultiplier(botAI));
// Lady Vashj <Coilfang Matron>
multipliers.push_back(new LadyVashjDelayCooldownsMultiplier(botAI));
multipliers.push_back(new LadyVashjMaintainPhase1RangedSpreadMultiplier(botAI));
multipliers.push_back(new LadyVashjStaticChargeStayAwayFromGroupMultiplier(botAI));
multipliers.push_back(new LadyVashjDoNotLootTheTaintedCoreMultiplier(botAI));
multipliers.push_back(new LadyVashjCorePassersPrioritizePositioningMultiplier(botAI));
multipliers.push_back(new LadyVashjDisableAutomaticTargetingAndMovementModifier(botAI));
}

View File

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

View File

@@ -0,0 +1,670 @@
#include "RaidSSCTriggers.h"
#include "RaidSSCHelpers.h"
#include "RaidSSCActions.h"
#include "AiFactory.h"
#include "Corpse.h"
#include "LootObjectStack.h"
#include "ObjectAccessor.h"
#include "Playerbots.h"
#include "RaidBossHelpers.h"
using namespace SerpentShrineCavernHelpers;
// General
bool SerpentShrineCavernBotIsNotInCombatTrigger::IsActive()
{
return !bot->IsInCombat();
}
// Trash Mobs
bool UnderbogColossusSpawnedToxicPoolAfterDeathTrigger::IsActive()
{
return bot->HasAura(SPELL_TOXIC_POOL);
}
bool GreyheartTidecallerWaterElementalTotemSpawnedTrigger::IsActive()
{
return botAI->IsDps(bot) &&
GetFirstAliveUnitByEntry(botAI, NPC_WATER_ELEMENTAL_TOTEM);
}
// Hydross the Unstable <Duke of Currents>
bool HydrossTheUnstableBotIsFrostTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "hydross the unstable") &&
botAI->IsMainTank(bot);
}
bool HydrossTheUnstableBotIsNatureTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "hydross the unstable") &&
botAI->IsAssistTankOfIndex(bot, 0, true);
}
bool HydrossTheUnstableElementalsSpawnedTrigger::IsActive()
{
Unit* hydross = AI_VALUE2(Unit*, "find target", "hydross the unstable");
if (hydross && hydross->GetHealthPct() < 10.0f)
return false;
if (!AI_VALUE2(Unit*, "find target", "pure spawn of hydross") &&
!AI_VALUE2(Unit*, "find target", "tainted spawn of hydross"))
return false;
return !botAI->IsHeal(bot) && !botAI->IsMainTank(bot) &&
!botAI->IsAssistTankOfIndex(bot, 0, true);
}
bool HydrossTheUnstableDangerFromWaterTombsTrigger::IsActive()
{
return botAI->IsRanged(bot) &&
AI_VALUE2(Unit*, "find target", "hydross the unstable");
}
bool HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger::IsActive()
{
return bot->getClass() == CLASS_HUNTER &&
AI_VALUE2(Unit*, "find target", "hydross the unstable");
}
bool HydrossTheUnstableAggroResetsUponPhaseChangeTrigger::IsActive()
{
if (!AI_VALUE2(Unit*, "find target", "hydross the unstable"))
return false;
return bot->getClass() != CLASS_HUNTER &&
!botAI->IsHeal(bot) &&
!botAI->IsMainTank(bot) &&
!botAI->IsAssistTankOfIndex(bot, 0, true);
}
bool HydrossTheUnstableNeedToManageTimersTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "hydross the unstable") &&
IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr);
}
// The Lurker Below
bool TheLurkerBelowSpoutIsActiveTrigger::IsActive()
{
Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below");
if (!lurker)
return false;
const time_t now = std::time(nullptr);
auto it = lurkerSpoutTimer.find(lurker->GetMap()->GetInstanceId());
return it != lurkerSpoutTimer.end() && it->second > now;
}
bool TheLurkerBelowBossIsActiveForMainTankTrigger::IsActive()
{
Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below");
if (!lurker)
return false;
if (!botAI->IsMainTank(bot))
return false;
const time_t now = std::time(nullptr);
auto it = lurkerSpoutTimer.find(lurker->GetMap()->GetInstanceId());
return lurker->getStandState() != UNIT_STAND_STATE_SUBMERGED &&
(it == lurkerSpoutTimer.end() || it->second <= now);
}
bool TheLurkerBelowBossCastsGeyserTrigger::IsActive()
{
if (!botAI->IsRanged(bot))
return false;
Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below");
if (!lurker)
return false;
const time_t now = std::time(nullptr);
auto it = lurkerSpoutTimer.find(lurker->GetMap()->GetInstanceId());
return lurker->getStandState() != UNIT_STAND_STATE_SUBMERGED &&
(it == lurkerSpoutTimer.end() || it->second <= now);
}
// Trigger will be active only if there are at least 3 tanks in the raid
bool TheLurkerBelowBossIsSubmergedTrigger::IsActive()
{
Unit* lurker = AI_VALUE2(Unit*, "find target", "the lurker below");
if (!lurker || lurker->getStandState() != UNIT_STAND_STATE_SUBMERGED)
return false;
Player* mainTank = nullptr;
Player* firstAssistTank = nullptr;
Player* secondAssistTank = nullptr;
Group* group = bot->GetGroup();
if (!group)
return false;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive())
continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (!memberAI)
continue;
if (!mainTank && memberAI->IsMainTank(member))
mainTank = member;
else if (!firstAssistTank && memberAI->IsAssistTankOfIndex(member, 0, true))
firstAssistTank = member;
else if (!secondAssistTank && memberAI->IsAssistTankOfIndex(member, 1, true))
secondAssistTank = member;
}
if (!mainTank || !firstAssistTank || !secondAssistTank)
return false;
return bot == mainTank || bot == firstAssistTank || bot == secondAssistTank;
}
bool TheLurkerBelowNeedToPrepareTimerForSpoutTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "the lurker below") &&
IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr);
}
// Leotheras the Blind
bool LeotherasTheBlindBossIsInactiveTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "greyheart spellbinder");
}
bool LeotherasTheBlindBossTransformedIntoDemonFormTrigger::IsActive()
{
if (!AI_VALUE2(Unit*, "find target", "leotheras the blind"))
return false;
if (GetLeotherasDemonFormTank(bot) != bot)
return false;
return GetActiveLeotherasDemon(botAI);
}
bool LeotherasTheBlindOnlyWarlockShouldTankDemonFormTrigger::IsActive()
{
if (botAI->IsRanged(bot) || !botAI->IsTank(bot))
return false;
if (!AI_VALUE2(Unit*, "find target", "leotheras the blind"))
return false;
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
if (!GetLeotherasDemonFormTank(bot))
return false;
return GetPhase2LeotherasDemon(botAI);
}
bool LeotherasTheBlindBossEngagedByRangedTrigger::IsActive()
{
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
if (!botAI->IsRanged(bot))
return false;
Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind");
if (!leotheras)
return false;
return !leotheras->HasAura(SPELL_LEOTHERAS_BANISHED) &&
!leotheras->HasAura(SPELL_WHIRLWIND) &&
!leotheras->HasAura(SPELL_WHIRLWIND_CHANNEL);
}
bool LeotherasTheBlindBossChannelingWhirlwindTrigger::IsActive()
{
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
if (botAI->IsTank(bot) && botAI->IsMelee(bot))
return false;
Unit* leotheras = AI_VALUE2(Unit*, "find target", "leotheras the blind");
if (!leotheras || leotheras->HasAura(SPELL_LEOTHERAS_BANISHED))
return false;
return leotheras->HasAura(SPELL_WHIRLWIND) ||
leotheras->HasAura(SPELL_WHIRLWIND_CHANNEL);
}
bool LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger::IsActive()
{
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
if (botAI->IsRanged(bot))
return false;
Aura* chaosBlast = bot->GetAura(SPELL_CHAOS_BLAST);
if (!chaosBlast || chaosBlast->GetStackAmount() < 5)
return false;
if (!GetLeotherasDemonFormTank(bot) && botAI->IsMainTank(bot))
return false;
return GetPhase2LeotherasDemon(botAI);
}
bool LeotherasTheBlindInnerDemonHasAwakenedTrigger::IsActive()
{
return bot->HasAura(SPELL_INSIDIOUS_WHISPER) &&
GetLeotherasDemonFormTank(bot) != bot;
}
bool LeotherasTheBlindEnteredFinalPhaseTrigger::IsActive()
{
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
if (botAI->IsHeal(bot))
return false;
if (GetLeotherasDemonFormTank(bot) == bot)
return false;
return GetPhase3LeotherasDemon(botAI) &&
GetLeotherasHuman(botAI);
}
bool LeotherasTheBlindDemonFormTankNeedsAggro::IsActive()
{
if (bot->HasAura(SPELL_INSIDIOUS_WHISPER))
return false;
if (bot->getClass() != CLASS_HUNTER)
return false;
return AI_VALUE2(Unit*, "find target", "leotheras the blind");
}
bool LeotherasTheBlindBossWipesAggroUponPhaseChangeTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "leotheras the blind") &&
IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr);
}
// Fathom-Lord Karathress
bool FathomLordKarathressBossEngagedByMainTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "fathom-lord karathress") &&
botAI->IsMainTank(bot);
}
bool FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "fathom-guard caribdis") &&
botAI->IsAssistTankOfIndex(bot, 0, false);
}
bool FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis") &&
botAI->IsAssistTankOfIndex(bot, 1, false);
}
bool FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess") &&
botAI->IsAssistTankOfIndex(bot, 2, false);
}
bool FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger::IsActive()
{
Unit* caribdis = AI_VALUE2(Unit*, "find target", "fathom-guard caribdis");
if (!caribdis)
return false;
if (!botAI->IsAssistHealOfIndex(bot, 0, true))
return false;
Player* firstAssistTank = nullptr;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive())
continue;
if (botAI->IsAssistTankOfIndex(member, 0, false))
{
firstAssistTank = member;
break;
}
}
}
return firstAssistTank;
}
bool FathomLordKarathressPullingBossesTrigger::IsActive()
{
if (bot->getClass() != CLASS_HUNTER)
return false;
Unit* karathress = AI_VALUE2(Unit*, "find target", "fathom-lord karathress");
return karathress && karathress->GetHealthPct() > 98.0f;
}
bool FathomLordKarathressDeterminingKillOrderTrigger::IsActive()
{
if (!AI_VALUE2(Unit*, "find target", "fathom-lord karathress"))
return false;
if (botAI->IsHeal(bot))
return false;
if (botAI->IsDps(bot))
return true;
else if (botAI->IsAssistTankOfIndex(bot, 0, false))
return !AI_VALUE2(Unit*, "find target", "fathom-guard caribdis");
else if (botAI->IsAssistTankOfIndex(bot, 1, false))
return !AI_VALUE2(Unit*, "find target", "fathom-guard sharkkis");
else if (botAI->IsAssistTankOfIndex(bot, 2, false))
return !AI_VALUE2(Unit*, "find target", "fathom-guard tidalvess");
else
return false;
}
bool FathomLordKarathressTanksNeedToEstablishAggroTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "fathom-lord karathress") &&
IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr);
}
// Morogrim Tidewalker
bool MorogrimTidewalkerPullingBossTrigger::IsActive()
{
if (bot->getClass() != CLASS_HUNTER)
return false;
Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker");
return tidewalker && tidewalker->GetHealthPct() > 95.0f;
}
bool MorogrimTidewalkerBossEngagedByMainTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "morogrim tidewalker") &&
botAI->IsMainTank(bot);
}
bool MorogrimTidewalkerWaterGlobulesAreIncomingTrigger::IsActive()
{
if (!botAI->IsRanged(bot))
return false;
Unit* tidewalker = AI_VALUE2(Unit*, "find target", "morogrim tidewalker");
return tidewalker && tidewalker->GetHealthPct() < 25.0f;
}
// Lady Vashj <Coilfang Matron>
bool LadyVashjBossEngagedByMainTankTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "lady vashj") &&
!IsLadyVashjInPhase2(botAI) && botAI->IsMainTank(bot);
}
bool LadyVashjBossEngagedByRangedInPhase1Trigger::IsActive()
{
return botAI->IsRanged(bot) && IsLadyVashjInPhase1(botAI);
}
bool LadyVashjCastsShockBlastOnHighestAggroTrigger::IsActive()
{
if (bot->getClass() != CLASS_SHAMAN)
return false;
if (!AI_VALUE2(Unit*, "find target", "lady vashj") ||
IsLadyVashjInPhase2(botAI))
return false;
if (!IsMainTankInSameSubgroup(bot))
return false;
return true;
}
bool LadyVashjBotHasStaticChargeTrigger::IsActive()
{
if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
return false;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->HasAura(SPELL_STATIC_CHARGE))
return true;
}
}
return false;
}
bool LadyVashjPullingBossInPhase1AndPhase3Trigger::IsActive()
{
if (bot->getClass() != CLASS_HUNTER)
return false;
Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj");
if (!vashj)
return false;
return (vashj->GetHealthPct() <= 100.0f && vashj->GetHealthPct() > 90.0f) ||
(!vashj->HasUnitState(UNIT_STATE_ROOT) && vashj->GetHealthPct() <= 50.0f &&
vashj->GetHealthPct() > 40.0f);
}
bool LadyVashjAddsSpawnInPhase2AndPhase3Trigger::IsActive()
{
if (botAI->IsHeal(bot))
return false;
return AI_VALUE2(Unit*, "find target", "lady vashj") &&
!IsLadyVashjInPhase1(botAI);
}
bool LadyVashjCoilfangStriderIsApproachingTrigger::IsActive()
{
return AI_VALUE2(Unit*, "find target", "coilfang strider");
}
bool LadyVashjTaintedElementalCheatTrigger::IsActive()
{
if (!botAI->HasCheat(BotCheatMask::raid))
return false;
if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
return false;
bool taintedPresent = false;
Unit* taintedUnit = AI_VALUE2(Unit*, "find target", "tainted elemental");
if (taintedUnit)
taintedPresent = true;
else
{
GuidVector corpses = AI_VALUE(GuidVector, "nearest corpses");
for (auto const& guid : corpses)
{
LootObject loot(bot, guid);
WorldObject* object = loot.GetWorldObject(bot);
if (!object)
continue;
if (Creature* creature = object->ToCreature())
{
if (creature->GetEntry() == NPC_TAINTED_ELEMENTAL && !creature->IsAlive())
{
taintedPresent = true;
break;
}
}
}
}
if (!taintedPresent)
return false;
Group* group = bot->GetGroup();
if (!group)
return false;
return (GetDesignatedCoreLooter(group, botAI) == bot &&
!bot->HasItemCount(ITEM_TAINTED_CORE, 1, false));
}
bool LadyVashjTaintedCoreWasLootedTrigger::IsActive()
{
if (!AI_VALUE2(Unit*, "find target", "lady vashj") || !IsLadyVashjInPhase2(botAI))
return false;
Group* group = bot->GetGroup();
if (!group)
return false;
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI);
Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI);
Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI);
Player* fourthCorePasser = GetFourthTaintedCorePasser(group, botAI);
auto hasCore = [](Player* player) -> bool
{
return player && player->HasItemCount(ITEM_TAINTED_CORE, 1, false);
};
if (bot == designatedLooter)
{
if (!hasCore(bot))
return false;
}
else if (bot == firstCorePasser)
{
if (hasCore(secondCorePasser) || hasCore(thirdCorePasser) ||
hasCore(fourthCorePasser))
return false;
}
else if (bot == secondCorePasser)
{
if (hasCore(thirdCorePasser) || hasCore(fourthCorePasser))
return false;
}
else if (bot == thirdCorePasser)
{
if (hasCore(fourthCorePasser))
return false;
}
else if (bot != fourthCorePasser)
return false;
if (AnyRecentCoreInInventory(group, botAI))
return true;
// First and second passers move to positions as soon as the elemental appears
if (AI_VALUE2(Unit*, "find target", "tainted elemental") &&
(bot == firstCorePasser || bot == secondCorePasser))
return true;
return false;
}
bool LadyVashjTaintedCoreIsUnusableTrigger::IsActive()
{
Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj");
if (!vashj)
return false;
if (!IsLadyVashjInPhase2(botAI))
return bot->HasItemCount(ITEM_TAINTED_CORE, 1, false);
Group* group = bot->GetGroup();
if (!group)
return false;
Player* coreHandlers[] =
{
GetDesignatedCoreLooter(group, botAI),
GetFirstTaintedCorePasser(group, botAI),
GetSecondTaintedCorePasser(group, botAI),
GetThirdTaintedCorePasser(group, botAI),
GetFourthTaintedCorePasser(group, botAI)
};
if (bot->HasItemCount(ITEM_TAINTED_CORE, 1, false))
{
for (Player* coreHandler : coreHandlers)
{
if (coreHandler && bot == coreHandler)
return false;
}
return true;
}
return false;
}
bool LadyVashjNeedToResetCorePassingTrackersTrigger::IsActive()
{
Unit* vashj = AI_VALUE2(Unit*, "find target", "lady vashj");
if (!vashj || IsLadyVashjInPhase2(botAI))
return false;
Group* group = bot->GetGroup();
if (!group)
return false;
return IsMechanicTrackerBot(botAI, bot, SSC_MAP_ID, nullptr) ||
GetDesignatedCoreLooter(group, botAI) == bot ||
GetFirstTaintedCorePasser(group, botAI) == bot ||
GetSecondTaintedCorePasser(group, botAI) == bot ||
GetThirdTaintedCorePasser(group, botAI) == bot ||
GetFourthTaintedCorePasser(group, botAI) == bot;
}
bool LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger::IsActive()
{
return IsLadyVashjInPhase3(botAI);
}
bool LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger::IsActive()
{
if (!AI_VALUE2(Unit*, "find target", "lady vashj"))
return false;
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->HasAura(SPELL_ENTANGLE))
continue;
if (botAI->IsMelee(member))
return true;
}
}
return false;
}

View File

@@ -0,0 +1,414 @@
#ifndef _PLAYERBOT_RAIDSSCTRIGGERS_H
#define _PLAYERBOT_RAIDSSCTRIGGERS_H
#include "Trigger.h"
// General
class SerpentShrineCavernBotIsNotInCombatTrigger : public Trigger
{
public:
SerpentShrineCavernBotIsNotInCombatTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "serpent shrine cavern bot is not in combat") {}
bool IsActive() override;
};
// Trash
class UnderbogColossusSpawnedToxicPoolAfterDeathTrigger : public Trigger
{
public:
UnderbogColossusSpawnedToxicPoolAfterDeathTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "underbog colossus spawned toxic pool after death") {}
bool IsActive() override;
};
class GreyheartTidecallerWaterElementalTotemSpawnedTrigger : public Trigger
{
public:
GreyheartTidecallerWaterElementalTotemSpawnedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "greyheart tidecaller water elemental totem spawned") {}
bool IsActive() override;
};
// Hydross the Unstable <Duke of Currents>
class HydrossTheUnstableBotIsFrostTankTrigger : public Trigger
{
public:
HydrossTheUnstableBotIsFrostTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable bot is frost tank") {}
bool IsActive() override;
};
class HydrossTheUnstableBotIsNatureTankTrigger : public Trigger
{
public:
HydrossTheUnstableBotIsNatureTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable bot is nature tank") {}
bool IsActive() override;
};
class HydrossTheUnstableElementalsSpawnedTrigger : public Trigger
{
public:
HydrossTheUnstableElementalsSpawnedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable elementals spawned") {}
bool IsActive() override;
};
class HydrossTheUnstableDangerFromWaterTombsTrigger : public Trigger
{
public:
HydrossTheUnstableDangerFromWaterTombsTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable danger from water tombs") {}
bool IsActive() override;
};
class HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger : public Trigger
{
public:
HydrossTheUnstableTankNeedsAggroUponPhaseChangeTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable tank needs aggro upon phase change") {}
bool IsActive() override;
};
class HydrossTheUnstableAggroResetsUponPhaseChangeTrigger : public Trigger
{
public:
HydrossTheUnstableAggroResetsUponPhaseChangeTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable aggro resets upon phase change") {}
bool IsActive() override;
};
class HydrossTheUnstableNeedToManageTimersTrigger : public Trigger
{
public:
HydrossTheUnstableNeedToManageTimersTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "hydross the unstable need to manage timers") {}
bool IsActive() override;
};
// The Lurker Below
class TheLurkerBelowSpoutIsActiveTrigger : public Trigger
{
public:
TheLurkerBelowSpoutIsActiveTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "the lurker below spout is active") {}
bool IsActive() override;
};
class TheLurkerBelowBossIsActiveForMainTankTrigger : public Trigger
{
public:
TheLurkerBelowBossIsActiveForMainTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "the lurker below boss is active for main tank") {}
bool IsActive() override;
};
class TheLurkerBelowBossCastsGeyserTrigger : public Trigger
{
public:
TheLurkerBelowBossCastsGeyserTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "the lurker below boss casts geyser") {}
bool IsActive() override;
};
class TheLurkerBelowBossIsSubmergedTrigger : public Trigger
{
public:
TheLurkerBelowBossIsSubmergedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "the lurker below boss is submerged") {}
bool IsActive() override;
};
class TheLurkerBelowNeedToPrepareTimerForSpoutTrigger : public Trigger
{
public:
TheLurkerBelowNeedToPrepareTimerForSpoutTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "the lurker below need to prepare timer for spout") {}
bool IsActive() override;
};
// Leotheras the Blind
class LeotherasTheBlindBossIsInactiveTrigger : public Trigger
{
public:
LeotherasTheBlindBossIsInactiveTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind boss is inactive") {}
bool IsActive() override;
};
class LeotherasTheBlindOnlyWarlockShouldTankDemonFormTrigger : public Trigger
{
public:
LeotherasTheBlindOnlyWarlockShouldTankDemonFormTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind only warlock should tank demon form") {}
bool IsActive() override;
};
class LeotherasTheBlindBossTransformedIntoDemonFormTrigger : public Trigger
{
public:
LeotherasTheBlindBossTransformedIntoDemonFormTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind boss transformed into demon form") {}
bool IsActive() override;
};
class LeotherasTheBlindBossEngagedByRangedTrigger : public Trigger
{
public:
LeotherasTheBlindBossEngagedByRangedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind boss engaged by ranged") {}
bool IsActive() override;
};
class LeotherasTheBlindBossChannelingWhirlwindTrigger : public Trigger
{
public:
LeotherasTheBlindBossChannelingWhirlwindTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind boss channeling whirlwind") {}
bool IsActive() override;
};
class LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger : public Trigger
{
public:
LeotherasTheBlindBotHasTooManyChaosBlastStacksTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind bot has too many chaos blast stacks") {}
bool IsActive() override;
};
class LeotherasTheBlindInnerDemonHasAwakenedTrigger : public Trigger
{
public:
LeotherasTheBlindInnerDemonHasAwakenedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind inner demon has awakened") {}
bool IsActive() override;
};
class LeotherasTheBlindEnteredFinalPhaseTrigger : public Trigger
{
public:
LeotherasTheBlindEnteredFinalPhaseTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind entered final phase") {}
bool IsActive() override;
};
class LeotherasTheBlindDemonFormTankNeedsAggro : public Trigger
{
public:
LeotherasTheBlindDemonFormTankNeedsAggro(
PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind demon form tank needs aggro") {}
bool IsActive() override;
};
class LeotherasTheBlindBossWipesAggroUponPhaseChangeTrigger : public Trigger
{
public:
LeotherasTheBlindBossWipesAggroUponPhaseChangeTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "leotheras the blind boss wipes aggro upon phase change") {}
bool IsActive() override;
};
// Fathom-Lord Karathress
class FathomLordKarathressBossEngagedByMainTankTrigger : public Trigger
{
public:
FathomLordKarathressBossEngagedByMainTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress boss engaged by main tank") {}
bool IsActive() override;
};
class FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger : public Trigger
{
public:
FathomLordKarathressCaribdisEngagedByFirstAssistTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress caribdis engaged by first assist tank") {}
bool IsActive() override;
};
class FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger : public Trigger
{
public:
FathomLordKarathressSharkkisEngagedBySecondAssistTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress sharkkis engaged by second assist tank") {}
bool IsActive() override;
};
class FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger : public Trigger
{
public:
FathomLordKarathressTidalvessEngagedByThirdAssistTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress tidalvess engaged by third assist tank") {}
bool IsActive() override;
};
class FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger : public Trigger
{
public:
FathomLordKarathressCaribdisTankNeedsDedicatedHealerTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress caribdis tank needs dedicated healer") {}
bool IsActive() override;
};
class FathomLordKarathressPullingBossesTrigger : public Trigger
{
public:
FathomLordKarathressPullingBossesTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress pulling bosses") {}
bool IsActive() override;
};
class FathomLordKarathressDeterminingKillOrderTrigger : public Trigger
{
public:
FathomLordKarathressDeterminingKillOrderTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress determining kill order") {}
bool IsActive() override;
};
class FathomLordKarathressTanksNeedToEstablishAggroTrigger : public Trigger
{
public:
FathomLordKarathressTanksNeedToEstablishAggroTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "fathom-lord karathress tanks need to establish aggro") {}
bool IsActive() override;
};
// Morogrim Tidewalker
class MorogrimTidewalkerPullingBossTrigger : public Trigger
{
public:
MorogrimTidewalkerPullingBossTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "morogrim tidewalker pulling boss") {}
bool IsActive() override;
};
class MorogrimTidewalkerBossEngagedByMainTankTrigger : public Trigger
{
public:
MorogrimTidewalkerBossEngagedByMainTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "morogrim tidewalker boss engaged by main tank") {}
bool IsActive() override;
};
class MorogrimTidewalkerWaterGlobulesAreIncomingTrigger : public Trigger
{
public:
MorogrimTidewalkerWaterGlobulesAreIncomingTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "morogrim tidewalker water globules are incoming") {}
bool IsActive() override;
};
// Lady Vashj <Coilfang Matron>
class LadyVashjBossEngagedByMainTankTrigger : public Trigger
{
public:
LadyVashjBossEngagedByMainTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "lady vashj boss engaged by main tank") {}
bool IsActive() override;
};
class LadyVashjBossEngagedByRangedInPhase1Trigger : public Trigger
{
public:
LadyVashjBossEngagedByRangedInPhase1Trigger(
PlayerbotAI* botAI) : Trigger(botAI, "lady vashj boss engaged by ranged in phase 1") {}
bool IsActive() override;
};
class LadyVashjCastsShockBlastOnHighestAggroTrigger : public Trigger
{
public:
LadyVashjCastsShockBlastOnHighestAggroTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "lady vashj casts shock blast on highest aggro") {}
bool IsActive() override;
};
class LadyVashjBotHasStaticChargeTrigger : public Trigger
{
public:
LadyVashjBotHasStaticChargeTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "lady vashj bot has static charge") {}
bool IsActive() override;
};
class LadyVashjPullingBossInPhase1AndPhase3Trigger : public Trigger
{
public:
LadyVashjPullingBossInPhase1AndPhase3Trigger(
PlayerbotAI* botAI) : Trigger(botAI, "lady vashj pulling boss in phase 1 and phase 3") {}
bool IsActive() override;
};
class LadyVashjAddsSpawnInPhase2AndPhase3Trigger : public Trigger
{
public:
LadyVashjAddsSpawnInPhase2AndPhase3Trigger(
PlayerbotAI* botAI) : Trigger(botAI, "lady vashj adds spawn in phase 2 and phase 3") {}
bool IsActive() override;
};
class LadyVashjCoilfangStriderIsApproachingTrigger : public Trigger
{
public:
LadyVashjCoilfangStriderIsApproachingTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "lady vashj coilfang strider is approaching") {}
bool IsActive() override;
};
class LadyVashjTaintedElementalCheatTrigger : public Trigger
{
public:
LadyVashjTaintedElementalCheatTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "lady vashj tainted elemental cheat") {}
bool IsActive() override;
};
class LadyVashjTaintedCoreWasLootedTrigger : public Trigger
{
public:
LadyVashjTaintedCoreWasLootedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "lady vashj tainted core was looted") {}
bool IsActive() override;
};
class LadyVashjTaintedCoreIsUnusableTrigger : public Trigger
{
public:
LadyVashjTaintedCoreIsUnusableTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "lady vashj tainted core is unusable") {}
bool IsActive() override;
};
class LadyVashjNeedToResetCorePassingTrackersTrigger : public Trigger
{
public:
LadyVashjNeedToResetCorePassingTrackersTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "lady vashj need to reset core passing trackers") {}
bool IsActive() override;
};
class LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger : public Trigger
{
public:
LadyVashjToxicSporebatsAreSpewingPoisonCloudsTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "lady vashj toxic sporebats are spewing poison clouds") {}
bool IsActive() override;
};
class LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger : public Trigger
{
public:
LadyVashjBotIsEntangledInToxicSporesOrStaticChargeTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "lady vashj bot is entangled in toxic spores or static charge") {}
bool IsActive() override;
};
#endif

View File

@@ -0,0 +1,583 @@
#include "RaidSSCHelpers.h"
#include "AiFactory.h"
#include "Creature.h"
#include "ObjectAccessor.h"
#include "Playerbots.h"
#include "RaidBossHelpers.h"
namespace SerpentShrineCavernHelpers
{
// Hydross the Unstable <Duke of Currents>
const Position HYDROSS_FROST_TANK_POSITION = { -236.669f, -358.352f, -0.828f };
const Position HYDROSS_NATURE_TANK_POSITION = { -225.471f, -327.790f, -3.682f };
std::unordered_map<uint32, time_t> hydrossFrostDpsWaitTimer;
std::unordered_map<uint32, time_t> hydrossNatureDpsWaitTimer;
std::unordered_map<uint32, time_t> hydrossChangeToFrostPhaseTimer;
std::unordered_map<uint32, time_t> hydrossChangeToNaturePhaseTimer;
bool HasMarkOfHydrossAt100Percent(Player* bot)
{
return bot->HasAura(SPELL_MARK_OF_HYDROSS_100) ||
bot->HasAura(SPELL_MARK_OF_HYDROSS_250) ||
bot->HasAura(SPELL_MARK_OF_HYDROSS_500);
}
bool HasNoMarkOfHydross(Player* bot)
{
return !bot->HasAura(SPELL_MARK_OF_HYDROSS_10) &&
!bot->HasAura(SPELL_MARK_OF_HYDROSS_25) &&
!bot->HasAura(SPELL_MARK_OF_HYDROSS_50) &&
!bot->HasAura(SPELL_MARK_OF_HYDROSS_100) &&
!bot->HasAura(SPELL_MARK_OF_HYDROSS_250) &&
!bot->HasAura(SPELL_MARK_OF_HYDROSS_500);
}
bool HasMarkOfCorruptionAt100Percent(Player* bot)
{
return bot->HasAura(SPELL_MARK_OF_CORRUPTION_100) ||
bot->HasAura(SPELL_MARK_OF_CORRUPTION_250) ||
bot->HasAura(SPELL_MARK_OF_CORRUPTION_500);
}
bool HasNoMarkOfCorruption(Player* bot)
{
return !bot->HasAura(SPELL_MARK_OF_CORRUPTION_10) &&
!bot->HasAura(SPELL_MARK_OF_CORRUPTION_25) &&
!bot->HasAura(SPELL_MARK_OF_CORRUPTION_50) &&
!bot->HasAura(SPELL_MARK_OF_CORRUPTION_100) &&
!bot->HasAura(SPELL_MARK_OF_CORRUPTION_250) &&
!bot->HasAura(SPELL_MARK_OF_CORRUPTION_500);
}
// The Lurker Below
const Position LURKER_MAIN_TANK_POSITION = { 23.706f, -406.038f, -19.686f };
std::unordered_map<uint32, time_t> lurkerSpoutTimer;
std::unordered_map<ObjectGuid, Position> lurkerRangedPositions;
bool IsLurkerCastingSpout(Unit* lurker)
{
if (!lurker || !lurker->HasUnitState(UNIT_STATE_CASTING))
return false;
Spell* currentSpell = lurker->GetCurrentSpell(CURRENT_GENERIC_SPELL);
if (!currentSpell)
return false;
uint32 spellId = currentSpell->m_spellInfo->Id;
bool isSpout = spellId == SPELL_SPOUT_VISUAL;
return isSpout;
}
// Leotheras the Blind
std::unordered_map<uint32, time_t> leotherasHumanFormDpsWaitTimer;
std::unordered_map<uint32, time_t> leotherasDemonFormDpsWaitTimer;
std::unordered_map<uint32, time_t> leotherasFinalPhaseDpsWaitTimer;
Unit* GetLeotherasHuman(PlayerbotAI* botAI)
{
auto const& npcs =
botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest hostile npcs")->Get();
for (auto const& guid : npcs)
{
Unit* unit = botAI->GetUnit(guid);
if (unit && unit->GetEntry() == NPC_LEOTHERAS_THE_BLIND &&
unit->IsInCombat() && !unit->HasAura(SPELL_METAMORPHOSIS))
return unit;
}
return nullptr;
}
Unit* GetPhase2LeotherasDemon(PlayerbotAI* botAI)
{
auto const& npcs =
botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest hostile npcs")->Get();
for (auto const& guid : npcs)
{
Unit* unit = botAI->GetUnit(guid);
if (unit && unit->GetEntry() == NPC_LEOTHERAS_THE_BLIND &&
unit->HasAura(SPELL_METAMORPHOSIS))
return unit;
}
return nullptr;
}
Unit* GetPhase3LeotherasDemon(PlayerbotAI* botAI)
{
auto const& npcs =
botAI->GetAiObjectContext()->GetValue<GuidVector>("nearest hostile npcs")->Get();
for (auto const& guid : npcs)
{
Unit* unit = botAI->GetUnit(guid);
if (unit && unit->GetEntry() == NPC_SHADOW_OF_LEOTHERAS)
return unit;
}
return nullptr;
}
Unit* GetActiveLeotherasDemon(PlayerbotAI* botAI)
{
Unit* phase2 = GetPhase2LeotherasDemon(botAI);
Unit* phase3 = GetPhase3LeotherasDemon(botAI);
return phase2 ? phase2 : phase3;
}
Player* GetLeotherasDemonFormTank(Player* bot)
{
Group* group = bot->GetGroup();
if (!group)
return nullptr;
// (1) First loop: Return the first assistant Warlock (real player or bot)
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;
}
// (2) Fall back to first found bot Warlock
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
member->getClass() != CLASS_WARLOCK)
continue;
return member;
}
// (3) Return nullptr if none found
return nullptr;
}
// Fathom-Lord Karathress
const Position KARATHRESS_TANK_POSITION = { 474.403f, -531.118f, -7.548f };
const Position TIDALVESS_TANK_POSITION = { 511.282f, -501.162f, -13.158f };
const Position SHARKKIS_TANK_POSITION = { 508.057f, -541.109f, -10.133f };
const Position CARIBDIS_TANK_POSITION = { 464.462f, -475.820f, -13.158f };
const Position CARIBDIS_HEALER_POSITION = { 466.203f, -503.201f, -13.158f };
const Position CARIBDIS_RANGED_DPS_POSITION = { 463.197f, -501.190f, -13.158f };
std::unordered_map<uint32, time_t> karathressDpsWaitTimer;
// Morogrim Tidewalker
const Position TIDEWALKER_PHASE_1_TANK_POSITION = { 410.925f, -741.916f, -7.146f };
const Position TIDEWALKER_PHASE_TRANSITION_WAYPOINT = { 407.035f, -759.479f, -7.168f };
const Position TIDEWALKER_PHASE_2_TANK_POSITION = { 446.571f, -767.155f, -7.144f };
const Position TIDEWALKER_PHASE_2_RANGED_POSITION = { 432.595f, -766.288f, -7.145f };
std::unordered_map<ObjectGuid, uint8> tidewalkerTankStep;
std::unordered_map<ObjectGuid, uint8> tidewalkerRangedStep;
// Lady Vashj <Coilfang Matron>
const Position VASHJ_PLATFORM_CENTER_POSITION = { 29.634f, -923.541f, 42.985f };
std::unordered_map<ObjectGuid, Position> vashjRangedPositions;
std::unordered_map<ObjectGuid, bool> hasReachedVashjRangedPosition;
std::unordered_map<uint32, ObjectGuid> nearestTriggerGuid;
std::unordered_map<ObjectGuid, Position> intendedLineup;
std::unordered_map<uint32, time_t> lastImbueAttempt;
std::unordered_map<uint32, time_t> lastCoreInInventoryTime;
bool IsMainTankInSameSubgroup(Player* bot)
{
Group* group = bot->GetGroup();
if (!group || !group->isRaidGroup())
return false;
uint8 botSubGroup = group->GetMemberGroup(bot->GetGUID());
if (botSubGroup >= MAX_RAID_SUBGROUPS)
return false;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || member == bot || !member->IsAlive())
continue;
if (group->GetMemberGroup(member->GetGUID()) != botSubGroup)
continue;
if (PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member))
{
if (memberAI->IsMainTank(member))
return true;
}
}
return false;
}
bool IsLadyVashjInPhase1(PlayerbotAI* botAI)
{
Unit* vashj =
botAI->GetAiObjectContext()->GetValue<Unit*>("find target", "lady vashj")->Get();
if (!vashj)
return false;
Creature* vashjCreature = vashj->ToCreature();
return vashjCreature && vashjCreature->GetHealthPct() > 70.0f &&
vashjCreature->GetReactState() != REACT_PASSIVE;
}
bool IsLadyVashjInPhase2(PlayerbotAI* botAI)
{
Unit* vashj =
botAI->GetAiObjectContext()->GetValue<Unit*>("find target", "lady vashj")->Get();
if (!vashj)
return false;
Creature* vashjCreature = vashj->ToCreature();
return vashjCreature && vashjCreature->GetReactState() == REACT_PASSIVE;
}
bool IsLadyVashjInPhase3(PlayerbotAI* botAI)
{
Unit* vashj =
botAI->GetAiObjectContext()->GetValue<Unit*>("find target", "lady vashj")->Get();
if (!vashj)
return false;
Creature* vashjCreature = vashj->ToCreature();
return vashjCreature && vashjCreature->GetHealthPct() <= 50.0f &&
vashjCreature->GetReactState() != REACT_PASSIVE;
}
bool IsValidLadyVashjCombatNpc(Unit* unit, PlayerbotAI* botAI)
{
if (!unit || !unit->IsAlive())
return false;
uint32 entry = unit->GetEntry();
if (IsLadyVashjInPhase2(botAI))
{
return entry == NPC_TAINTED_ELEMENTAL || entry == NPC_ENCHANTED_ELEMENTAL ||
entry == NPC_COILFANG_ELITE || entry == NPC_COILFANG_STRIDER;
}
else if (IsLadyVashjInPhase3(botAI))
{
return entry == NPC_TAINTED_ELEMENTAL || entry == NPC_ENCHANTED_ELEMENTAL ||
entry == NPC_COILFANG_ELITE || entry == NPC_COILFANG_STRIDER ||
entry == NPC_TOXIC_SPOREBAT || entry == NPC_LADY_VASHJ;
}
return false;
}
bool AnyRecentCoreInInventory(Group* group, PlayerbotAI* botAI, uint32 graceSeconds)
{
Unit* vashj =
botAI->GetAiObjectContext()->GetValue<Unit*>("find target", "lady vashj")->Get();
if (!vashj)
return false;
if (group)
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->HasItemCount(ITEM_TAINTED_CORE, 1, false))
return true;
}
}
const uint32 instanceId = vashj->GetMap()->GetInstanceId();
const time_t now = std::time(nullptr);
auto it = lastCoreInInventoryTime.find(instanceId);
if (it != lastCoreInInventoryTime.end())
{
if ((now - it->second) <= static_cast<time_t>(graceSeconds))
return true;
}
return false;
}
Player* GetDesignatedCoreLooter(Group* group, PlayerbotAI* botAI)
{
if (!group)
return nullptr;
Player* leader = nullptr;
ObjectGuid leaderGuid = group->GetLeaderGUID();
if (!leaderGuid.IsEmpty())
leader = ObjectAccessor::FindPlayer(leaderGuid);
if (!botAI->HasCheat(BotCheatMask::raid))
return leader;
Player* fallback = leader;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member == leader)
continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (!memberAI)
continue;
if (memberAI->IsMelee(member) && memberAI->IsDps(member))
return member;
if (!fallback && memberAI->IsRangedDps(member))
fallback = member;
}
return fallback ? fallback : leader;
}
Player* GetFirstTaintedCorePasser(Group* group, PlayerbotAI* botAI)
{
if (!group)
return nullptr;
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member == designatedLooter)
continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (!memberAI)
continue;
if (memberAI->IsAssistHealOfIndex(member, 0, true))
return member;
}
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
botAI->IsTank(member) || member == designatedLooter)
continue;
return member;
}
return nullptr;
}
Player* GetSecondTaintedCorePasser(Group* group, PlayerbotAI* botAI)
{
if (!group)
return nullptr;
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI);
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member == designatedLooter ||
member == firstCorePasser)
continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (!memberAI)
continue;
if (memberAI->IsAssistHealOfIndex(member, 1, true))
return member;
}
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
botAI->IsTank(member) || member == designatedLooter ||
member == firstCorePasser)
continue;
return member;
}
return nullptr;
}
Player* GetThirdTaintedCorePasser(Group* group, PlayerbotAI* botAI)
{
if (!group)
return nullptr;
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI);
Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI);
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member == designatedLooter ||
member == firstCorePasser || member == secondCorePasser)
continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (!memberAI)
continue;
if (memberAI->IsAssistHealOfIndex(member, 2, true))
return member;
}
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
botAI->IsTank(member) || member == designatedLooter ||
member == firstCorePasser || member == secondCorePasser)
continue;
return member;
}
return nullptr;
}
Player* GetFourthTaintedCorePasser(Group* group, PlayerbotAI* botAI)
{
if (!group)
return nullptr;
Player* designatedLooter = GetDesignatedCoreLooter(group, botAI);
Player* firstCorePasser = GetFirstTaintedCorePasser(group, botAI);
Player* secondCorePasser = GetSecondTaintedCorePasser(group, botAI);
Player* thirdCorePasser = GetThirdTaintedCorePasser(group, botAI);
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || member == designatedLooter ||
member == firstCorePasser || member == secondCorePasser ||
member == thirdCorePasser)
continue;
PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member);
if (!memberAI)
continue;
if (memberAI->IsAssistRangedDpsOfIndex(member, 0, true))
return member;
}
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive() || !GET_PLAYERBOT_AI(member) ||
botAI->IsTank(member) || member == designatedLooter ||
member == firstCorePasser || member == secondCorePasser ||
member == thirdCorePasser)
continue;
return member;
}
return nullptr;
}
const std::vector<uint32> SHIELD_GENERATOR_DB_GUIDS =
{
47482, // NW
47483, // NE
47484, // SE
47485 // SW
};
// Get the positions of all active Shield Generators by their database GUIDs
std::vector<GeneratorInfo> GetAllGeneratorInfosByDbGuids(
Map* map, const std::vector<uint32>& generatorDbGuids)
{
std::vector<GeneratorInfo> generators;
if (!map)
return generators;
for (uint32 dbGuid : generatorDbGuids)
{
auto bounds = map->GetGameObjectBySpawnIdStore().equal_range(dbGuid);
if (bounds.first == bounds.second)
continue;
GameObject* go = bounds.first->second;
if (!go)
continue;
if (go->GetGoState() != GO_STATE_READY)
continue;
GeneratorInfo info;
info.guid = go->GetGUID();
info.x = go->GetPositionX();
info.y = go->GetPositionY();
info.z = go->GetPositionZ();
generators.push_back(info);
}
return generators;
}
// Returns the nearest active Shield Generator to the bot
// Active generators are powered by NPC_WORLD_INVISIBLE_TRIGGER creatures,
// which depawn after use
Unit* GetNearestActiveShieldGeneratorTriggerByEntry(Unit* reference)
{
if (!reference)
return nullptr;
std::list<Creature*> triggers;
constexpr float searchRange = 150.0f;
reference->GetCreatureListWithEntryInGrid(
triggers, NPC_WORLD_INVISIBLE_TRIGGER, searchRange);
Creature* nearest = nullptr;
float minDist = std::numeric_limits<float>::max();
for (Creature* creature : triggers)
{
if (!creature->IsAlive())
continue;
float dist = reference->GetDistance(creature);
if (dist < minDist)
{
minDist = dist;
nearest = creature;
}
}
return nearest;
}
const GeneratorInfo* GetNearestGeneratorToBot(
Player* bot, const std::vector<GeneratorInfo>& generators)
{
if (!bot || generators.empty())
return nullptr;
const GeneratorInfo* nearest = nullptr;
float minDist = std::numeric_limits<float>::max();
for (auto const& gen : generators)
{
float dist = bot->GetExactDist(gen.x, gen.y, gen.z);
if (dist < minDist)
{
minDist = dist;
nearest = &gen;
}
}
return nearest;
}
}

View File

@@ -0,0 +1,189 @@
#ifndef _PLAYERBOT_RAIDSSCHELPERS_H_
#define _PLAYERBOT_RAIDSSCHELPERS_H_
#include <ctime>
#include <unordered_map>
#include "AiObject.h"
#include "Position.h"
#include "Unit.h"
namespace SerpentShrineCavernHelpers
{
enum SerpentShrineCavernSpells
{
// Trash Mobs
SPELL_TOXIC_POOL = 38718,
// Hydross the Unstable <Duke of Currents>
SPELL_MARK_OF_HYDROSS_10 = 38215,
SPELL_MARK_OF_HYDROSS_25 = 38216,
SPELL_MARK_OF_HYDROSS_50 = 38217,
SPELL_MARK_OF_HYDROSS_100 = 38218,
SPELL_MARK_OF_HYDROSS_250 = 38231,
SPELL_MARK_OF_HYDROSS_500 = 40584,
SPELL_MARK_OF_CORRUPTION_10 = 38219,
SPELL_MARK_OF_CORRUPTION_25 = 38220,
SPELL_MARK_OF_CORRUPTION_50 = 38221,
SPELL_MARK_OF_CORRUPTION_100 = 38222,
SPELL_MARK_OF_CORRUPTION_250 = 38230,
SPELL_MARK_OF_CORRUPTION_500 = 40583,
SPELL_CORRUPTION = 37961,
// The Lurker Below
SPELL_SPOUT_VISUAL = 37431,
// Leotheras the Blind
SPELL_LEOTHERAS_BANISHED = 37546,
SPELL_WHIRLWIND = 37640,
SPELL_WHIRLWIND_CHANNEL = 37641,
SPELL_METAMORPHOSIS = 37673,
SPELL_CHAOS_BLAST = 37675,
SPELL_INSIDIOUS_WHISPER = 37676,
// Lady Vashj <Coilfang Matron>
SPELL_FEAR_WARD = 6346,
SPELL_POISON_BOLT = 38253,
SPELL_STATIC_CHARGE = 38280,
SPELL_ENTANGLE = 38316,
// Druid
SPELL_CAT_FORM = 768,
SPELL_BEAR_FORM = 5487,
SPELL_DIRE_BEAR_FORM = 9634,
SPELL_TREE_OF_LIFE = 33891,
// Hunter
SPELL_MISDIRECTION = 35079,
// Mage
SPELL_SLOW = 31589,
// Shaman
SPELL_GROUNDING_TOTEM_EFFECT = 8178,
// Warlock
SPELL_CURSE_OF_EXHAUSTION = 18223,
// Item
SPELL_HEAVY_NETHERWEAVE_NET = 31368,
};
enum SerpentShrineCavernNPCs
{
// Trash Mobs
NPC_WATER_ELEMENTAL_TOTEM = 22236,
// Hydross the Unstable <Duke of Currents>
NPC_PURE_SPAWN_OF_HYDROSS = 22035,
NPC_TAINTED_SPAWN_OF_HYDROSS = 22036,
// The Lurker Below
NPC_COILFANG_GUARDIAN = 21873,
// Leotheras the Blind
NPC_LEOTHERAS_THE_BLIND = 21215,
NPC_GREYHEART_SPELLBINDER = 21806,
NPC_INNER_DEMON = 21857,
NPC_SHADOW_OF_LEOTHERAS = 21875,
// Fathom-Lord Karathress
NPC_SPITFIRE_TOTEM = 22091,
// Lady Vashj <Coilfang Matron>
NPC_WORLD_INVISIBLE_TRIGGER = 12999,
NPC_LADY_VASHJ = 21212,
NPC_ENCHANTED_ELEMENTAL = 21958,
NPC_TAINTED_ELEMENTAL = 22009,
NPC_COILFANG_ELITE = 22055,
NPC_COILFANG_STRIDER = 22056,
NPC_TOXIC_SPOREBAT = 22140,
NPC_SPORE_DROP_TRIGGER = 22207,
};
enum SerpentShrineCavernItems
{
// Lady Vashj <Coilfang Matron>
ITEM_TAINTED_CORE = 31088,
// Tailoring
ITEM_HEAVY_NETHERWEAVE_NET = 24269,
};
constexpr uint32 SSC_MAP_ID = 548;
// Hydross the Unstable <Duke of Currents>
extern const Position HYDROSS_FROST_TANK_POSITION;
extern const Position HYDROSS_NATURE_TANK_POSITION;
extern std::unordered_map<uint32, time_t> hydrossFrostDpsWaitTimer;
extern std::unordered_map<uint32, time_t> hydrossNatureDpsWaitTimer;
extern std::unordered_map<uint32, time_t> hydrossChangeToFrostPhaseTimer;
extern std::unordered_map<uint32, time_t> hydrossChangeToNaturePhaseTimer;
bool HasMarkOfHydrossAt100Percent(Player* bot);
bool HasNoMarkOfHydross(Player* bot);
bool HasMarkOfCorruptionAt100Percent(Player* bot);
bool HasNoMarkOfCorruption(Player* bot);
// The Lurker Below
extern const Position LURKER_MAIN_TANK_POSITION;
extern std::unordered_map<uint32, time_t> lurkerSpoutTimer;
extern std::unordered_map<ObjectGuid, Position> lurkerRangedPositions;
bool IsLurkerCastingSpout(Unit* lurker);
// Leotheras the Blind
extern std::unordered_map<uint32, time_t> leotherasHumanFormDpsWaitTimer;
extern std::unordered_map<uint32, time_t> leotherasDemonFormDpsWaitTimer;
extern std::unordered_map<uint32, time_t> leotherasFinalPhaseDpsWaitTimer;
Unit* GetLeotherasHuman(PlayerbotAI* botAI);
Unit* GetPhase2LeotherasDemon(PlayerbotAI* botAI);
Unit* GetPhase3LeotherasDemon(PlayerbotAI* botAI);
Unit* GetActiveLeotherasDemon(PlayerbotAI* botAI);
Player* GetLeotherasDemonFormTank(Player* bot);
// Fathom-Lord Karathress
extern const Position KARATHRESS_TANK_POSITION;
extern const Position TIDALVESS_TANK_POSITION;
extern const Position SHARKKIS_TANK_POSITION;
extern const Position CARIBDIS_TANK_POSITION;
extern const Position CARIBDIS_HEALER_POSITION;
extern const Position CARIBDIS_RANGED_DPS_POSITION;
extern std::unordered_map<uint32, time_t> karathressDpsWaitTimer;
// Morogrim Tidewalker
extern const Position TIDEWALKER_PHASE_1_TANK_POSITION;
extern const Position TIDEWALKER_PHASE_TRANSITION_WAYPOINT;
extern const Position TIDEWALKER_PHASE_2_TANK_POSITION;
extern const Position TIDEWALKER_PHASE_2_RANGED_POSITION;
extern std::unordered_map<ObjectGuid, uint8> tidewalkerTankStep;
extern std::unordered_map<ObjectGuid, uint8> tidewalkerRangedStep;
// Lady Vashj <Coilfang Matron>
constexpr float VASHJ_PLATFORM_Z = 42.985f;
extern const Position VASHJ_PLATFORM_CENTER_POSITION;
extern std::unordered_map<ObjectGuid, Position> vashjRangedPositions;
extern std::unordered_map<ObjectGuid, bool> hasReachedVashjRangedPosition;
extern std::unordered_map<uint32, ObjectGuid> nearestTriggerGuid;
extern std::unordered_map<ObjectGuid, Position> intendedLineup;
extern std::unordered_map<uint32, time_t> lastImbueAttempt;
extern std::unordered_map<uint32, time_t> lastCoreInInventoryTime;
bool IsMainTankInSameSubgroup(Player* bot);
bool IsLadyVashjInPhase1(PlayerbotAI* botAI);
bool IsLadyVashjInPhase2(PlayerbotAI* botAI);
bool IsLadyVashjInPhase3(PlayerbotAI* botAI);
bool IsValidLadyVashjCombatNpc(Unit* unit, PlayerbotAI* botAI);
bool AnyRecentCoreInInventory(Group* group, PlayerbotAI* botAI, uint32 graceSeconds = 3);
Player* GetDesignatedCoreLooter(Group* group, PlayerbotAI* botAI);
Player* GetFirstTaintedCorePasser(Group* group, PlayerbotAI* botAI);
Player* GetSecondTaintedCorePasser(Group* group, PlayerbotAI* botAI);
Player* GetThirdTaintedCorePasser(Group* group, PlayerbotAI* botAI);
Player* GetFourthTaintedCorePasser(Group* group, PlayerbotAI* botAI);
struct GeneratorInfo { ObjectGuid guid; float x, y, z; };
extern const std::vector<uint32> SHIELD_GENERATOR_DB_GUIDS;
std::vector<GeneratorInfo> GetAllGeneratorInfosByDbGuids(
Map* map, const std::vector<uint32>& generatorDbGuids);
Unit* GetNearestActiveShieldGeneratorTriggerByEntry(Unit* reference);
const GeneratorInfo* GetNearestGeneratorToBot(
Player* bot, const std::vector<GeneratorInfo>& generators);
}
#endif

View File

@@ -11,7 +11,6 @@
#include "GameObject.h" #include "GameObject.h"
#include "Group.h" #include "Group.h"
#include "LastMovementValue.h" #include "LastMovementValue.h"
#include "ObjectDefines.h"
#include "ObjectGuid.h" #include "ObjectGuid.h"
#include "PlayerbotAI.h" #include "PlayerbotAI.h"
#include "PlayerbotAIConfig.h" #include "PlayerbotAIConfig.h"
@@ -19,11 +18,9 @@
#include "Position.h" #include "Position.h"
#include "RaidUlduarBossHelper.h" #include "RaidUlduarBossHelper.h"
#include "RaidUlduarScripts.h" #include "RaidUlduarScripts.h"
#include "RaidUlduarStrategy.h"
#include "RtiValue.h" #include "RtiValue.h"
#include "ScriptedCreature.h" #include "ScriptedCreature.h"
#include "ServerFacade.h" #include "ServerFacade.h"
#include "SharedDefines.h"
#include "Unit.h" #include "Unit.h"
#include "Vehicle.h" #include "Vehicle.h"
#include <RtiTargetValue.h> #include <RtiTargetValue.h>

View File

@@ -1,28 +0,0 @@
#include "RaidUlduarMultipliers.h"
#include "ChooseTargetActions.h"
#include "DKActions.h"
#include "DruidActions.h"
#include "DruidBearActions.h"
#include "FollowActions.h"
#include "GenericActions.h"
#include "GenericSpellActions.h"
#include "HunterActions.h"
#include "MageActions.h"
#include "MovementActions.h"
#include "PaladinActions.h"
#include "PriestActions.h"
#include "RaidUlduarActions.h"
#include "ReachTargetActions.h"
#include "RogueActions.h"
#include "ScriptedCreature.h"
#include "ShamanActions.h"
#include "UseMeetingStoneAction.h"
#include "WarriorActions.h"
float FlameLeviathanMultiplier::GetValue(Action* action)
{
// if (dynamic_cast<FleeAction*>(action))
// return 0.0f;
return 1.0f;
}

View File

@@ -1,17 +0,0 @@
#ifndef _PLAYERRBOT_RAIDULDUARMULTIPLIERS_H_
#define _PLAYERRBOT_RAIDULDUARMULTIPLIERS_H_
#include "Multiplier.h"
#include "Ai/Raid/Ulduar/RaidUlduarBossHelper.h"
class FlameLeviathanMultiplier : public Multiplier
{
public:
FlameLeviathanMultiplier(PlayerbotAI* ai) : Multiplier(ai, "flame leviathan") {}
public:
virtual float GetValue(Action* action);
};
#endif

View File

@@ -1,169 +0,0 @@
#ifndef _PLAYERBOT_RAIDULDUARBOSSHELPER_H
#define _PLAYERBOT_RAIDULDUARBOSSHELPER_H
#include <string>
#include <unordered_map>
#include <vector>
#include <cmath>
#include <ctime>
#include <limits>
#include "AiObject.h"
#include "AiObjectContext.h"
#include "EventMap.h"
#include "Log.h"
#include "ObjectGuid.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "ScriptedCreature.h"
#include "SharedDefines.h"
const uint32 ULDUAR_MAP_ID = 603;
class RazorscaleBossHelper : public AiObject
{
public:
// Enums and constants specific to Razorscale
enum RazorscaleUnits : uint32
{
UNIT_RAZORSCALE = 33186,
UNIT_DARK_RUNE_SENTINEL = 33846,
UNIT_DARK_RUNE_WATCHER = 33453,
UNIT_DARK_RUNE_GUARDIAN = 33388,
UNIT_DEVOURING_FLAME = 34188,
};
enum RazorscaleGameObjects : uint32
{
GO_RAZORSCALE_HARPOON_1 = 194519,
GO_RAZORSCALE_HARPOON_2 = 194541,
GO_RAZORSCALE_HARPOON_3 = 194542,
GO_RAZORSCALE_HARPOON_4 = 194543,
};
enum RazorscaleSpells : uint32
{
SPELL_CHAIN_1 = 49679,
SPELL_CHAIN_2 = 49682,
SPELL_CHAIN_3 = 49683,
SPELL_CHAIN_4 = 49684,
SPELL_SENTINEL_WHIRLWIND = 63806,
SPELL_STUN_AURA = 62794,
SPELL_FUSEARMOR = 64771
};
static constexpr uint32 FUSEARMOR_THRESHOLD = 2;
// Constants for arena parameters
static constexpr float RAZORSCALE_FLYING_Z_THRESHOLD = 440.0f;
static constexpr float RAZORSCALE_ARENA_CENTER_X = 587.54f;
static constexpr float RAZORSCALE_ARENA_CENTER_Y = -175.04f;
static constexpr float RAZORSCALE_ARENA_RADIUS = 30.0f;
// Harpoon cooldown (seconds)
static constexpr time_t HARPOON_COOLDOWN_DURATION = 5;
// Structure for harpoon data
struct HarpoonData
{
uint32 gameObjectEntry;
uint32 chainSpellId;
};
explicit RazorscaleBossHelper(PlayerbotAI* botAI)
: AiObject(botAI), _boss(nullptr) {}
bool UpdateBossAI();
Unit* GetBoss() const;
bool IsGroundPhase() const;
bool IsFlyingPhase() const;
bool IsHarpoonFired(uint32 chainSpellId) const;
static bool IsHarpoonReady(GameObject* harpoonGO);
static void SetHarpoonOnCooldown(GameObject* harpoonGO);
GameObject* FindNearestHarpoon(float x, float y, float z) const;
static const std::vector<HarpoonData>& GetHarpoonData();
void AssignRolesBasedOnHealth();
bool AreRolesAssigned() const;
bool CanSwapRoles() const;
private:
Unit* _boss;
// A map to track the last role swap *per bot* by their GUID
static std::unordered_map<ObjectGuid, std::time_t> _lastRoleSwapTime;
// The cooldown that applies to every bot
static const std::time_t _roleSwapCooldown = 10;
static std::unordered_map<ObjectGuid, time_t> _harpoonCooldowns;
};
// template <class BossAiType>
// class GenericBossHelper : public AiObject
// {
// public:
// GenericBossHelper(PlayerbotAI* botAI, std::string name) : AiObject(botAI), _name(name) {}
// virtual bool UpdateBossAI()
// {
// if (!bot->IsInCombat())
// {
// _unit = nullptr;
// }
// if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive()))
// {
// _unit = nullptr;
// }
// if (!_unit)
// {
// _unit = AI_VALUE2(Unit*, "find target", _name);
// if (!_unit)
// {
// return false;
// }
// _target = _unit->ToCreature();
// if (!_target)
// {
// return false;
// }
// _ai = dynamic_cast<BossAiType*>(_target->GetAI());
// if (!_ai)
// {
// return false;
// }
// _event_map = &_ai->events;
// if (!_event_map)
// {
// return false;
// }
// }
// if (!_event_map)
// {
// return false;
// }
// _timer = _event_map->GetTimer();
// return true;
// }
// virtual void Reset()
// {
// _unit = nullptr;
// _target = nullptr;
// _ai = nullptr;
// _event_map = nullptr;
// _timer = 0;
// }
// protected:
// std::string _name;
// Unit* _unit = nullptr;
// Creature* _target = nullptr;
// BossAiType* _ai = nullptr;
// EventMap* _event_map = nullptr;
// uint32 _timer = 0;
// };
#endif

View File

@@ -1,7 +1,5 @@
#include "RaidUlduarStrategy.h" #include "RaidUlduarStrategy.h"
#include "RaidUlduarMultipliers.h"
void RaidUlduarStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void RaidUlduarStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
// //
@@ -316,8 +314,3 @@ void RaidUlduarStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
"yogg-saron phase 3 positioning trigger", "yogg-saron phase 3 positioning trigger",
{ NextAction("yogg-saron phase 3 positioning action", ACTION_RAID) })); { NextAction("yogg-saron phase 3 positioning action", ACTION_RAID) }));
} }
void RaidUlduarStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{
multipliers.push_back(new FlameLeviathanMultiplier(botAI));
}

View File

@@ -3,16 +3,14 @@
#define _PLAYERBOT_RAIDULDUARSTRATEGY_H #define _PLAYERBOT_RAIDULDUARSTRATEGY_H
#include "AiObjectContext.h" #include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h" #include "Strategy.h"
class RaidUlduarStrategy : public Strategy class RaidUlduarStrategy : public Strategy
{ {
public: public:
RaidUlduarStrategy(PlayerbotAI* ai) : Strategy(ai) {} RaidUlduarStrategy(PlayerbotAI* ai) : Strategy(ai) {}
virtual std::string const getName() override { return "uld"; } virtual std::string const getName() override { return "ulduar"; }
virtual void InitTriggers(std::vector<TriggerNode*>& triggers) override; virtual void InitTriggers(std::vector<TriggerNode*>& triggers) override;
virtual void InitMultipliers(std::vector<Multiplier*>& multipliers) override;
}; };
#endif #endif

View File

@@ -1634,7 +1634,7 @@ bool VezaxShadowCrashTrigger::IsActive()
return false; return false;
} }
return botAI->HasAura(SPELL_SHADOW_CRASH, bot); return botAI->HasAura(SPELL_VEZAX_SHADOW_CRASH, bot);
} }
bool VezaxMarkOfTheFacelessTrigger::IsActive() bool VezaxMarkOfTheFacelessTrigger::IsActive()

View File

@@ -3,187 +3,9 @@
#include "EventMap.h" #include "EventMap.h"
#include "GenericTriggers.h" #include "GenericTriggers.h"
#include "PlayerbotAIConfig.h"
#include "RaidUlduarBossHelper.h" #include "RaidUlduarBossHelper.h"
#include "Trigger.h" #include "Trigger.h"
enum UlduarIDs
{
// Iron Assembly
SPELL_LIGHTNING_TENDRILS_10_MAN = 61887,
SPELL_LIGHTNING_TENDRILS_25_MAN = 63486,
SPELL_OVERLOAD_10_MAN = 61869,
SPELL_OVERLOAD_25_MAN = 63481,
SPELL_OVERLOAD_10_MAN_2 = 63485,
SPELL_OVERLOAD_25_MAN_2 = 61886,
SPELL_RUNE_OF_POWER = 64320,
// Kologarn
NPC_RIGHT_ARM = 32934,
NPC_RUBBLE = 33768,
SPELL_CRUNCH_ARMOR = 64002,
SPELL_FOCUSED_EYEBEAM_10_2 = 63346,
SPELL_FOCUSED_EYEBEAM_10 = 63347,
SPELL_FOCUSED_EYEBEAM_25_2 = 63976,
SPELL_FOCUSED_EYEBEAM_25 = 63977,
// Hodir
NPC_SNOWPACKED_ICICLE = 33174,
NPC_TOASTY_FIRE = 33342,
SPELL_FLASH_FREEZE = 61968,
SPELL_BITING_COLD_PLAYER_AURA = 62039,
// Freya
NPC_SNAPLASHER = 32916,
NPC_STORM_LASHER = 32919,
NPC_DETONATING_LASHER = 32918,
NPC_ANCIENT_WATER_SPIRIT = 33202,
NPC_ANCIENT_CONSERVATOR = 33203,
NPC_HEALTHY_SPORE = 33215,
NPC_EONARS_GIFT = 33228,
GOBJECT_NATURE_BOMB = 194902,
// Thorim
NPC_DARK_RUNE_ACOLYTE_I = 32886,
NPC_CAPTURED_MERCENARY_SOLDIER_ALLY = 32885,
NPC_CAPTURED_MERCENARY_SOLDIER_HORDE = 32883,
NPC_CAPTURED_MERCENARY_CAPTAIN_ALLY = 32908,
NPC_CAPTURED_MERCENARY_CAPTAIN_HORDE = 32907,
NPC_JORMUNGAR_BEHEMOT = 32882,
NPC_DARK_RUNE_WARBRINGER = 32877,
NPC_DARK_RUNE_EVOKER = 32878,
NPC_DARK_RUNE_CHAMPION = 32876,
NPC_DARK_RUNE_COMMONER = 32904,
NPC_IRON_RING_GUARD = 32874,
NPC_RUNIC_COLOSSUS = 32872,
NPC_ANCIENT_RUNE_GIANT = 32873,
NPC_DARK_RUNE_ACOLYTE_G = 33110,
NPC_IRON_HONOR_GUARD = 32875,
SPELL_UNBALANCING_STRIKE = 62130,
// Mimiron
NPC_LEVIATHAN_MKII = 33432,
NPC_VX001 = 33651,
NPC_AERIAL_COMMAND_UNIT = 33670,
NPC_BOMB_BOT = 33836,
NPC_ROCKET_STRIKE_N = 34047,
NPC_ASSAULT_BOT = 34057,
NPC_PROXIMITY_MINE = 34362,
SPELL_P3WX2_LASER_BARRAGE_1 = 63293,
SPELL_P3WX2_LASER_BARRAGE_2 = 63297,
SPELL_SPINNING_UP = 63414,
SPELL_SHOCK_BLAST = 63631,
SPELL_P3WX2_LASER_BARRAGE_3 = 64042,
SPELL_P3WX2_LASER_BARRAGE_AURA_1 = 63274,
SPELL_P3WX2_LASER_BARRAGE_AURA_2 = 63300,
// General Vezax
SPELL_MARK_OF_THE_FACELESS = 63276,
SPELL_SHADOW_CRASH = 63277,
// Yogg-Saron
ACTION_ILLUSION_DRAGONS = 1,
ACTION_ILLUSION_ICECROWN = 2,
ACTION_ILLUSION_STORMWIND = 3,
NPC_GUARDIAN_OF_YS = 33136,
NPC_YOGG_SARON = 33288,
NPC_OMINOUS_CLOUD = 33292,
NPC_RUBY_CONSORT = 33716,
NPC_AZURE_CONSORT = 33717,
NPC_BRONZE_CONSORT = 33718,
NPC_EMERALD_CONSORT = 33719,
NPC_OBSIDIAN_CONSORT = 33720,
NPC_ALEXTRASZA = 33536,
NPC_MALYGOS_ILLUSION = 33535,
NPC_NELTHARION = 33523,
NPC_YSERA = 33495,
GO_DRAGON_SOUL = 194462,
NPC_SARA_PHASE_1 = 33134,
NPC_LICH_KING_ILLUSION = 33441,
NPC_IMMOLATED_CHAMPION = 33442,
NPC_SUIT_OF_ARMOR = 33433,
NPC_GARONA = 33436,
NPC_KING_LLANE = 33437,
NPC_DEATHSWORN_ZEALOT = 33567,
NPC_INFLUENCE_TENTACLE = 33943,
NPC_DEATH_ORB = 33882,
NPC_BRAIN = 33890,
NPC_CRUSHER_TENTACLE = 33966,
NPC_CONSTRICTOR_TENTACLE = 33983,
NPC_CORRUPTOR_TENTACLE = 33985,
NPC_IMMORTAL_GUARDIAN = 33988,
NPC_LAUGHING_SKULL = 33990,
NPC_SANITY_WELL = 33991,
NPC_DESCEND_INTO_MADNESS = 34072,
NPC_MARKED_IMMORTAL_GUARDIAN = 36064,
SPELL_SANITY = 63050,
SPELL_BRAIN_LINK = 63802,
SPELL_MALADY_OF_THE_MIND = 63830,
SPELL_SHADOW_BARRIER = 63894,
SPELL_TELEPORT_TO_CHAMBER = 63997,
SPELL_TELEPORT_TO_ICECROWN = 63998,
SPELL_TELEPORT_TO_STORMWIND = 63989,
SPELL_TELEPORT_BACK = 63992,
SPELL_CANCEL_ILLUSION_AURA = 63993,
SPELL_INDUCE_MADNESS = 64059,
SPELL_LUNATIC_GAZE_YS = 64163,
GO_FLEE_TO_THE_SURFACE_PORTAL = 194625,
// Buffs
SPELL_FROST_TRAP = 13809
};
const float ULDUAR_KOLOGARN_AXIS_Z_PATHING_ISSUE_DETECT = 420.0f;
const float ULDUAR_KOLOGARN_EYEBEAM_RADIUS = 3.0f;
const float ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD = 429.6094f;
const float ULDUAR_THORIM_AXIS_Z_PATHING_ISSUE_DETECT = 410.0f;
const float ULDUAR_AURIAYA_AXIS_Z_PATHING_ISSUE_DETECT = 410.0f;
const float ULDUAR_YOGG_SARON_BOSS_ROOM_AXIS_Z_PATHING_ISSUE_DETECT = 300.0f;
const float ULDUAR_YOGG_SARON_BRAIN_ROOM_AXIS_Z_PATHING_ISSUE_DETECT = 200.0f;
const float ULDUAR_YOGG_SARON_STORMWIND_KEEPER_RADIUS = 150.0f;
const float ULDUAR_YOGG_SARON_ICECROWN_CITADEL_RADIUS = 150.0f;
const float ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_RADIUS = 150.0f;
const float ULDUAR_YOGG_SARON_BRAIN_ROOM_RADIUS = 50.0f;
const Position ULDUAR_THORIM_NEAR_ARENA_CENTER = Position(2134.9854f, -263.11853f, 419.8465f);
const Position ULDUAR_THORIM_NEAR_ENTRANCE_POSITION = Position(2172.4355f, -258.27957f, 418.47162f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_6_YARDS_1 = Position(2237.6187f, -265.08844f, 412.17548f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_6_YARDS_2 = Position(2237.2498f, -275.81122f, 412.17548f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_5_YARDS_1 = Position(2236.895f, -294.62448f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_10_YARDS_1 = Position(2242.1162f, -310.15308f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_10_YARDS_2 = Position(2242.018f, -318.66003f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_10_YARDS_3 = Position(2242.1904f, -329.0533f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_6_YARDS_1 = Position(2219.5417f, -264.77167f, 412.17548f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_6_YARDS_2 = Position(2217.446f, -275.85248f, 412.17548f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_5_YARDS_1 = Position(2217.8877f, -295.01193f, 412.13434f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_10_YARDS_1 = Position(2212.193f, -307.44992f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_10_YARDS_2 = Position(2212.1353f, -318.20795f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_10_YARDS_3 = Position(2212.1956f, -328.0144f, 412.1348f);
const Position ULDUAR_THORIM_JUMP_END_POINT = Position(2137.8818f, -278.18942f, 419.66653f);
const Position ULDUAR_THORIM_PHASE2_TANK_SPOT = Position(2134.8572f, -287.0291f, 419.4935f);
const Position ULDUAR_THORIM_PHASE2_RANGE1_SPOT = Position(2112.8752f, -267.69305f, 419.52814f);
const Position ULDUAR_THORIM_PHASE2_RANGE2_SPOT = Position(2134.1296f, -257.3316f, 419.8462f);
const Position ULDUAR_THORIM_PHASE2_RANGE3_SPOT = Position(2156.798f, -267.57434f, 419.52722f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE1RANGE_SPOT = Position(2753.708f, 2583.9617f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE1MELEE_SPOT = Position(2746.9792f, 2573.6716f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE2RANGE_SPOT = Position(2727.7224f, 2569.527f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE2MELEE_SPOT = Position(2739.4746f, 2569.4106f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE3RANGE_SPOT = Position(2754.1294f, 2553.9954f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE3MELEE_SPOT = Position(2746.8513f, 2565.4263f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE4_TANK_SPOT = Position(2744.5754f, 2570.8657f, 364.3138f);
const Position ULDUAR_VEZAX_MARK_OF_THE_FACELESS_SPOT = Position(1913.6501f, 122.93989f, 342.38083f);
const Position ULDUAR_YOGG_SARON_MIDDLE = Position(1980.28f, -25.5868f, 329.397f);
const Position ULDUAR_YOGG_SARON_STORMWIND_KEEPER_MIDDLE = Position(1927.1511f, 68.507256f, 242.37657f);
const Position ULDUAR_YOGG_SARON_ICECROWN_CITADEL_MIDDLE = Position(1925.6553f, -121.59296f, 239.98965f);
const Position ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_MIDDLE = Position(2104.5667f, -25.509348f, 242.64679f);
const Position ULDUAR_YOGG_SARON_BRAIN_ROOM_MIDDLE = Position(1980.1971f, -27.854689f, 236.06789f);
const Position ULDUAR_YOGG_SARON_STORMWIND_KEEPER_ENTRANCE = Position(1954.06f, 21.66f, 239.71f);
const Position ULDUAR_YOGG_SARON_ICECROWN_CITADEL_ENTRANCE = Position(1950.11f, -79.284f, 239.98982f);
const Position ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_ENTRANCE = Position(2048.63f, -25.5f, 239.72f);
const Position ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT = Position(1998.5377f, -22.90317f, 324.8895f);
const Position ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT = Position(2018.7628f, -18.896868f, 327.07245f);
// //
// Flame Levi // Flame Levi
// //

View File

@@ -1,4 +1,3 @@
#include "ChatHelper.h"
#include "RaidUlduarBossHelper.h" #include "RaidUlduarBossHelper.h"
#include "ObjectAccessor.h" #include "ObjectAccessor.h"
#include "GameObject.h" #include "GameObject.h"
@@ -9,6 +8,44 @@
#include "Playerbots.h" #include "Playerbots.h"
#include "World.h" #include "World.h"
const Position ULDUAR_THORIM_NEAR_ARENA_CENTER = Position(2134.9854f, -263.11853f, 419.8465f);
const Position ULDUAR_THORIM_NEAR_ENTRANCE_POSITION = Position(2172.4355f, -258.27957f, 418.47162f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_6_YARDS_1 = Position(2237.6187f, -265.08844f, 412.17548f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_6_YARDS_2 = Position(2237.2498f, -275.81122f, 412.17548f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_5_YARDS_1 = Position(2236.895f, -294.62448f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_10_YARDS_1 = Position(2242.1162f, -310.15308f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_10_YARDS_2 = Position(2242.018f, -318.66003f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_10_YARDS_3 = Position(2242.1904f, -329.0533f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_6_YARDS_1 = Position(2219.5417f, -264.77167f, 412.17548f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_6_YARDS_2 = Position(2217.446f, -275.85248f, 412.17548f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_5_YARDS_1 = Position(2217.8877f, -295.01193f, 412.13434f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_10_YARDS_1 = Position(2212.193f, -307.44992f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_10_YARDS_2 = Position(2212.1353f, -318.20795f, 412.1348f);
const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_10_YARDS_3 = Position(2212.1956f, -328.0144f, 412.1348f);
const Position ULDUAR_THORIM_JUMP_END_POINT = Position(2137.8818f, -278.18942f, 419.66653f);
const Position ULDUAR_THORIM_PHASE2_TANK_SPOT = Position(2134.8572f, -287.0291f, 419.4935f);
const Position ULDUAR_THORIM_PHASE2_RANGE1_SPOT = Position(2112.8752f, -267.69305f, 419.52814f);
const Position ULDUAR_THORIM_PHASE2_RANGE2_SPOT = Position(2134.1296f, -257.3316f, 419.8462f);
const Position ULDUAR_THORIM_PHASE2_RANGE3_SPOT = Position(2156.798f, -267.57434f, 419.52722f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE1RANGE_SPOT = Position(2753.708f, 2583.9617f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE1MELEE_SPOT = Position(2746.9792f, 2573.6716f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE2RANGE_SPOT = Position(2727.7224f, 2569.527f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE2MELEE_SPOT = Position(2739.4746f, 2569.4106f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE3RANGE_SPOT = Position(2754.1294f, 2553.9954f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE2_SIDE3MELEE_SPOT = Position(2746.8513f, 2565.4263f, 364.31357f);
const Position ULDUAR_MIMIRON_PHASE4_TANK_SPOT = Position(2744.5754f, 2570.8657f, 364.3138f);
const Position ULDUAR_VEZAX_MARK_OF_THE_FACELESS_SPOT = Position(1913.6501f, 122.93989f, 342.38083f);
const Position ULDUAR_YOGG_SARON_MIDDLE = Position(1980.28f, -25.5868f, 329.397f);
const Position ULDUAR_YOGG_SARON_STORMWIND_KEEPER_MIDDLE = Position(1927.1511f, 68.507256f, 242.37657f);
const Position ULDUAR_YOGG_SARON_ICECROWN_CITADEL_MIDDLE = Position(1925.6553f, -121.59296f, 239.98965f);
const Position ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_MIDDLE = Position(2104.5667f, -25.509348f, 242.64679f);
const Position ULDUAR_YOGG_SARON_BRAIN_ROOM_MIDDLE = Position(1980.1971f, -27.854689f, 236.06789f);
const Position ULDUAR_YOGG_SARON_STORMWIND_KEEPER_ENTRANCE = Position(1954.06f, 21.66f, 239.71f);
const Position ULDUAR_YOGG_SARON_ICECROWN_CITADEL_ENTRANCE = Position(1950.11f, -79.284f, 239.98982f);
const Position ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_ENTRANCE = Position(2048.63f, -25.5f, 239.72f);
const Position ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT = Position(1998.5377f, -22.90317f, 324.8895f);
const Position ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT = Position(2018.7628f, -18.896868f, 327.07245f);
// Prevent harpoon spam // Prevent harpoon spam
std::unordered_map<ObjectGuid, time_t> RazorscaleBossHelper::_harpoonCooldowns; std::unordered_map<ObjectGuid, time_t> RazorscaleBossHelper::_harpoonCooldowns;
// Prevent role assignment spam // Prevent role assignment spam

View File

@@ -0,0 +1,341 @@
#ifndef _PLAYERBOT_RAIDULDUARBOSSHELPER_H
#define _PLAYERBOT_RAIDULDUARBOSSHELPER_H
#include <string>
#include <unordered_map>
#include <vector>
#include "AiObject.h"
#include "AiObjectContext.h"
#include "EventMap.h"
#include "ObjectGuid.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "ScriptedCreature.h"
constexpr uint32 ULDUAR_MAP_ID = 603;
enum UlduarIDs
{
// Iron Assembly
SPELL_LIGHTNING_TENDRILS_10_MAN = 61887,
SPELL_LIGHTNING_TENDRILS_25_MAN = 63486,
SPELL_OVERLOAD_10_MAN = 61869,
SPELL_OVERLOAD_25_MAN = 63481,
SPELL_OVERLOAD_10_MAN_2 = 63485,
SPELL_OVERLOAD_25_MAN_2 = 61886,
SPELL_RUNE_OF_POWER = 64320,
// Kologarn
NPC_RIGHT_ARM = 32934,
NPC_RUBBLE = 33768,
SPELL_CRUNCH_ARMOR = 64002,
SPELL_FOCUSED_EYEBEAM_10_2 = 63346,
SPELL_FOCUSED_EYEBEAM_10 = 63347,
SPELL_FOCUSED_EYEBEAM_25_2 = 63976,
SPELL_FOCUSED_EYEBEAM_25 = 63977,
// Hodir
NPC_SNOWPACKED_ICICLE = 33174,
NPC_TOASTY_FIRE = 33342,
SPELL_FLASH_FREEZE = 61968,
SPELL_BITING_COLD_PLAYER_AURA = 62039,
// Freya
NPC_SNAPLASHER = 32916,
NPC_STORM_LASHER = 32919,
NPC_DETONATING_LASHER = 32918,
NPC_ANCIENT_WATER_SPIRIT = 33202,
NPC_ANCIENT_CONSERVATOR = 33203,
NPC_HEALTHY_SPORE = 33215,
NPC_EONARS_GIFT = 33228,
GOBJECT_NATURE_BOMB = 194902,
// Thorim
NPC_DARK_RUNE_ACOLYTE_I = 32886,
NPC_CAPTURED_MERCENARY_SOLDIER_ALLY = 32885,
NPC_CAPTURED_MERCENARY_SOLDIER_HORDE = 32883,
NPC_CAPTURED_MERCENARY_CAPTAIN_ALLY = 32908,
NPC_CAPTURED_MERCENARY_CAPTAIN_HORDE = 32907,
NPC_JORMUNGAR_BEHEMOT = 32882,
NPC_DARK_RUNE_WARBRINGER = 32877,
NPC_DARK_RUNE_EVOKER = 32878,
NPC_DARK_RUNE_CHAMPION = 32876,
NPC_DARK_RUNE_COMMONER = 32904,
NPC_IRON_RING_GUARD = 32874,
NPC_RUNIC_COLOSSUS = 32872,
NPC_ANCIENT_RUNE_GIANT = 32873,
NPC_DARK_RUNE_ACOLYTE_G = 33110,
NPC_IRON_HONOR_GUARD = 32875,
SPELL_UNBALANCING_STRIKE = 62130,
// Mimiron
NPC_LEVIATHAN_MKII = 33432,
NPC_VX001 = 33651,
NPC_AERIAL_COMMAND_UNIT = 33670,
NPC_BOMB_BOT = 33836,
NPC_ROCKET_STRIKE_N = 34047,
NPC_ASSAULT_BOT = 34057,
NPC_PROXIMITY_MINE = 34362,
SPELL_P3WX2_LASER_BARRAGE_1 = 63293,
SPELL_P3WX2_LASER_BARRAGE_2 = 63297,
SPELL_SPINNING_UP = 63414,
SPELL_SHOCK_BLAST = 63631,
SPELL_P3WX2_LASER_BARRAGE_3 = 64042,
SPELL_P3WX2_LASER_BARRAGE_AURA_1 = 63274,
SPELL_P3WX2_LASER_BARRAGE_AURA_2 = 63300,
// General Vezax
SPELL_MARK_OF_THE_FACELESS = 63276,
SPELL_VEZAX_SHADOW_CRASH = 63277,
// Yogg-Saron
ACTION_ILLUSION_DRAGONS = 1,
ACTION_ILLUSION_ICECROWN = 2,
ACTION_ILLUSION_STORMWIND = 3,
NPC_GUARDIAN_OF_YS = 33136,
NPC_YOGG_SARON = 33288,
NPC_OMINOUS_CLOUD = 33292,
NPC_RUBY_CONSORT = 33716,
NPC_AZURE_CONSORT = 33717,
NPC_BRONZE_CONSORT = 33718,
NPC_EMERALD_CONSORT = 33719,
NPC_OBSIDIAN_CONSORT = 33720,
NPC_ALEXTRASZA = 33536,
NPC_MALYGOS_ILLUSION = 33535,
NPC_NELTHARION = 33523,
NPC_YSERA = 33495,
GO_DRAGON_SOUL = 194462,
NPC_SARA_PHASE_1 = 33134,
NPC_LICH_KING_ILLUSION = 33441,
NPC_IMMOLATED_CHAMPION = 33442,
NPC_SUIT_OF_ARMOR = 33433,
NPC_GARONA = 33436,
NPC_KING_LLANE = 33437,
NPC_DEATHSWORN_ZEALOT = 33567,
NPC_INFLUENCE_TENTACLE = 33943,
NPC_DEATH_ORB = 33882,
NPC_BRAIN = 33890,
NPC_CRUSHER_TENTACLE = 33966,
NPC_CONSTRICTOR_TENTACLE = 33983,
NPC_CORRUPTOR_TENTACLE = 33985,
NPC_IMMORTAL_GUARDIAN = 33988,
NPC_LAUGHING_SKULL = 33990,
NPC_SANITY_WELL = 33991,
NPC_DESCEND_INTO_MADNESS = 34072,
NPC_MARKED_IMMORTAL_GUARDIAN = 36064,
SPELL_SANITY = 63050,
SPELL_BRAIN_LINK = 63802,
SPELL_MALADY_OF_THE_MIND = 63830,
SPELL_SHADOW_BARRIER = 63894,
SPELL_TELEPORT_TO_CHAMBER = 63997,
SPELL_TELEPORT_TO_ICECROWN = 63998,
SPELL_TELEPORT_TO_STORMWIND = 63989,
SPELL_TELEPORT_BACK = 63992,
SPELL_CANCEL_ILLUSION_AURA = 63993,
SPELL_INDUCE_MADNESS = 64059,
SPELL_LUNATIC_GAZE_YS = 64163,
GO_FLEE_TO_THE_SURFACE_PORTAL = 194625,
// Buffs
SPELL_FROST_TRAP = 13809
};
constexpr float ULDUAR_KOLOGARN_AXIS_Z_PATHING_ISSUE_DETECT = 420.0f;
constexpr float ULDUAR_KOLOGARN_EYEBEAM_RADIUS = 3.0f;
constexpr float ULDUAR_THORIM_AXIS_Z_FLOOR_THRESHOLD = 429.6094f;
constexpr float ULDUAR_THORIM_AXIS_Z_PATHING_ISSUE_DETECT = 410.0f;
constexpr float ULDUAR_AURIAYA_AXIS_Z_PATHING_ISSUE_DETECT = 410.0f;
constexpr float ULDUAR_YOGG_SARON_BOSS_ROOM_AXIS_Z_PATHING_ISSUE_DETECT = 300.0f;
constexpr float ULDUAR_YOGG_SARON_BRAIN_ROOM_AXIS_Z_PATHING_ISSUE_DETECT = 200.0f;
constexpr float ULDUAR_YOGG_SARON_STORMWIND_KEEPER_RADIUS = 150.0f;
constexpr float ULDUAR_YOGG_SARON_ICECROWN_CITADEL_RADIUS = 150.0f;
constexpr float ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_RADIUS = 150.0f;
constexpr float ULDUAR_YOGG_SARON_BRAIN_ROOM_RADIUS = 50.0f;
extern const Position ULDUAR_THORIM_NEAR_ARENA_CENTER;
extern const Position ULDUAR_THORIM_NEAR_ENTRANCE_POSITION;
extern const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_6_YARDS_1;
extern const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_6_YARDS_2;
extern const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_5_YARDS_1;
extern const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_10_YARDS_1;
extern const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_10_YARDS_2;
extern const Position ULDUAR_THORIM_GAUNTLET_LEFT_SIDE_10_YARDS_3;
extern const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_6_YARDS_1;
extern const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_6_YARDS_2;
extern const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_5_YARDS_1;
extern const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_10_YARDS_1;
extern const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_10_YARDS_2;
extern const Position ULDUAR_THORIM_GAUNTLET_RIGHT_SIDE_10_YARDS_3;
extern const Position ULDUAR_THORIM_JUMP_END_POINT;
extern const Position ULDUAR_THORIM_PHASE2_TANK_SPOT;
extern const Position ULDUAR_THORIM_PHASE2_RANGE1_SPOT;
extern const Position ULDUAR_THORIM_PHASE2_RANGE2_SPOT;
extern const Position ULDUAR_THORIM_PHASE2_RANGE3_SPOT;
extern const Position ULDUAR_MIMIRON_PHASE2_SIDE1RANGE_SPOT;
extern const Position ULDUAR_MIMIRON_PHASE2_SIDE1MELEE_SPOT;
extern const Position ULDUAR_MIMIRON_PHASE2_SIDE2RANGE_SPOT;
extern const Position ULDUAR_MIMIRON_PHASE2_SIDE2MELEE_SPOT;
extern const Position ULDUAR_MIMIRON_PHASE2_SIDE3RANGE_SPOT;
extern const Position ULDUAR_MIMIRON_PHASE2_SIDE3MELEE_SPOT;
extern const Position ULDUAR_MIMIRON_PHASE4_TANK_SPOT;
extern const Position ULDUAR_VEZAX_MARK_OF_THE_FACELESS_SPOT;
extern const Position ULDUAR_YOGG_SARON_MIDDLE;
extern const Position ULDUAR_YOGG_SARON_STORMWIND_KEEPER_MIDDLE;
extern const Position ULDUAR_YOGG_SARON_ICECROWN_CITADEL_MIDDLE;
extern const Position ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_MIDDLE;
extern const Position ULDUAR_YOGG_SARON_BRAIN_ROOM_MIDDLE;
extern const Position ULDUAR_YOGG_SARON_STORMWIND_KEEPER_ENTRANCE;
extern const Position ULDUAR_YOGG_SARON_ICECROWN_CITADEL_ENTRANCE;
extern const Position ULDUAR_YOGG_SARON_CHAMBER_OF_ASPECTS_ENTRANCE;
extern const Position ULDUAR_YOGG_SARON_PHASE_3_MELEE_SPOT;
extern const Position ULDUAR_YOGG_SARON_PHASE_3_RANGED_SPOT;
class RazorscaleBossHelper : public AiObject
{
public:
// Enums and constants specific to Razorscale
enum RazorscaleUnits : uint32
{
UNIT_RAZORSCALE = 33186,
UNIT_DARK_RUNE_SENTINEL = 33846,
UNIT_DARK_RUNE_WATCHER = 33453,
UNIT_DARK_RUNE_GUARDIAN = 33388,
UNIT_DEVOURING_FLAME = 34188,
};
enum RazorscaleGameObjects : uint32
{
GO_RAZORSCALE_HARPOON_1 = 194519,
GO_RAZORSCALE_HARPOON_2 = 194541,
GO_RAZORSCALE_HARPOON_3 = 194542,
GO_RAZORSCALE_HARPOON_4 = 194543,
};
enum RazorscaleSpells : uint32
{
SPELL_CHAIN_1 = 49679,
SPELL_CHAIN_2 = 49682,
SPELL_CHAIN_3 = 49683,
SPELL_CHAIN_4 = 49684,
SPELL_SENTINEL_WHIRLWIND = 63806,
SPELL_STUN_AURA = 62794,
SPELL_FUSEARMOR = 64771
};
static constexpr uint32 FUSEARMOR_THRESHOLD = 2;
// Constants for arena parameters
static constexpr float RAZORSCALE_FLYING_Z_THRESHOLD = 440.0f;
static constexpr float RAZORSCALE_ARENA_CENTER_X = 587.54f;
static constexpr float RAZORSCALE_ARENA_CENTER_Y = -175.04f;
static constexpr float RAZORSCALE_ARENA_RADIUS = 30.0f;
// Harpoon cooldown (seconds)
static constexpr time_t HARPOON_COOLDOWN_DURATION = 5;
// Structure for harpoon data
struct HarpoonData
{
uint32 gameObjectEntry;
uint32 chainSpellId;
};
explicit RazorscaleBossHelper(PlayerbotAI* botAI)
: AiObject(botAI), _boss(nullptr) {}
bool UpdateBossAI();
Unit* GetBoss() const;
bool IsGroundPhase() const;
bool IsFlyingPhase() const;
bool IsHarpoonFired(uint32 chainSpellId) const;
static bool IsHarpoonReady(GameObject* harpoonGO);
static void SetHarpoonOnCooldown(GameObject* harpoonGO);
GameObject* FindNearestHarpoon(float x, float y, float z) const;
static const std::vector<HarpoonData>& GetHarpoonData();
void AssignRolesBasedOnHealth();
bool AreRolesAssigned() const;
bool CanSwapRoles() const;
private:
Unit* _boss;
// A map to track the last role swap *per bot* by their GUID
static std::unordered_map<ObjectGuid, std::time_t> _lastRoleSwapTime;
// The cooldown that applies to every bot
static const std::time_t _roleSwapCooldown = 10;
static std::unordered_map<ObjectGuid, time_t> _harpoonCooldowns;
};
// template <class BossAiType>
// class GenericBossHelper : public AiObject
// {
// public:
// GenericBossHelper(PlayerbotAI* botAI, std::string name) : AiObject(botAI), _name(name) {}
// virtual bool UpdateBossAI()
// {
// if (!bot->IsInCombat())
// {
// _unit = nullptr;
// }
// if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive()))
// {
// _unit = nullptr;
// }
// if (!_unit)
// {
// _unit = AI_VALUE2(Unit*, "find target", _name);
// if (!_unit)
// {
// return false;
// }
// _target = _unit->ToCreature();
// if (!_target)
// {
// return false;
// }
// _ai = dynamic_cast<BossAiType*>(_target->GetAI());
// if (!_ai)
// {
// return false;
// }
// _event_map = &_ai->events;
// if (!_event_map)
// {
// return false;
// }
// }
// if (!_event_map)
// {
// return false;
// }
// _timer = _event_map->GetTimer();
// return true;
// }
// virtual void Reset()
// {
// _unit = nullptr;
// _target = nullptr;
// _ai = nullptr;
// _event_map = nullptr;
// _timer = 0;
// }
// protected:
// std::string _name;
// Unit* _unit = nullptr;
// Creature* _target = nullptr;
// BossAiType* _ai = nullptr;
// EventMap* _event_map = nullptr;
// uint32 _timer = 0;
// };
#endif

View File

@@ -7,6 +7,7 @@
#define _PLAYERBOT_RAIDVOAACTIONCONTEXT_H #define _PLAYERBOT_RAIDVOAACTIONCONTEXT_H
#include "Action.h" #include "Action.h"
#include "BossAuraActions.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "RaidVoAActions.h" #include "RaidVoAActions.h"
#include "PlayerbotAI.h" #include "PlayerbotAI.h"

View File

@@ -7,6 +7,7 @@
#define _PLAYERBOT_RAIDVOATRIGGERCONTEXT_H #define _PLAYERBOT_RAIDVOATRIGGERCONTEXT_H
#include "AiObjectContext.h" #include "AiObjectContext.h"
#include "BossAuraTriggers.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "RaidVoATriggers.h" #include "RaidVoATriggers.h"

View File

@@ -15,8 +15,6 @@
#include "PaladinAiObjectContext.h" #include "PaladinAiObjectContext.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "PriestAiObjectContext.h" #include "PriestAiObjectContext.h"
#include "RaidUlduarActionContext.h"
#include "RaidUlduarTriggerContext.h"
#include "RogueAiObjectContext.h" #include "RogueAiObjectContext.h"
#include "ShamanAiObjectContext.h" #include "ShamanAiObjectContext.h"
#include "SharedValueContext.h" #include "SharedValueContext.h"
@@ -43,12 +41,16 @@
#include "Ai/Raid/Magtheridon/RaidMagtheridonTriggerContext.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/SerpentshrineCavern/RaidSSCActionContext.h"
#include "Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.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"
#include "Ai/Raid/VaultOfArchavon/RaidVoATriggerContext.h" #include "Ai/Raid/VaultOfArchavon/RaidVoATriggerContext.h"
#include "Ai/Raid/ObsidianSanctum/RaidOsActionContext.h" #include "Ai/Raid/ObsidianSanctum/RaidOsActionContext.h"
#include "Ai/Raid/ObsidianSanctum/RaidOsTriggerContext.h" #include "Ai/Raid/ObsidianSanctum/RaidOsTriggerContext.h"
#include "Ai/Raid/Ulduar/RaidUlduarActionContext.h"
#include "Ai/Raid/Ulduar/RaidUlduarTriggerContext.h"
#include "Ai/Raid/Onyxia/RaidOnyxiaActionContext.h" #include "Ai/Raid/Onyxia/RaidOnyxiaActionContext.h"
#include "Ai/Raid/Onyxia/RaidOnyxiaTriggerContext.h" #include "Ai/Raid/Onyxia/RaidOnyxiaTriggerContext.h"
#include "Ai/Raid/Icecrown/RaidIccActionContext.h" #include "Ai/Raid/Icecrown/RaidIccActionContext.h"
@@ -115,6 +117,7 @@ void AiObjectContext::BuildSharedActionContexts(SharedNamedObjectContextList<Act
actionContexts.Add(new RaidKarazhanActionContext()); actionContexts.Add(new RaidKarazhanActionContext());
actionContexts.Add(new RaidMagtheridonActionContext()); actionContexts.Add(new RaidMagtheridonActionContext());
actionContexts.Add(new RaidGruulsLairActionContext()); actionContexts.Add(new RaidGruulsLairActionContext());
actionContexts.Add(new RaidSSCActionContext());
actionContexts.Add(new RaidOsActionContext()); actionContexts.Add(new RaidOsActionContext());
actionContexts.Add(new RaidEoEActionContext()); actionContexts.Add(new RaidEoEActionContext());
actionContexts.Add(new RaidVoAActionContext()); actionContexts.Add(new RaidVoAActionContext());
@@ -149,6 +152,7 @@ void AiObjectContext::BuildSharedTriggerContexts(SharedNamedObjectContextList<Tr
triggerContexts.Add(new RaidKarazhanTriggerContext()); triggerContexts.Add(new RaidKarazhanTriggerContext());
triggerContexts.Add(new RaidMagtheridonTriggerContext()); triggerContexts.Add(new RaidMagtheridonTriggerContext());
triggerContexts.Add(new RaidGruulsLairTriggerContext()); triggerContexts.Add(new RaidGruulsLairTriggerContext());
triggerContexts.Add(new RaidSSCTriggerContext());
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

@@ -1799,10 +1799,6 @@ void PlayerbotFactory::InitEquipment(bool incremental, bool second_chance)
{ {
for (uint32 itemId : sRandomItemMgr.GetCachedEquipments(requiredLevel, inventoryType)) for (uint32 itemId : sRandomItemMgr.GetCachedEquipments(requiredLevel, inventoryType))
{ {
if (itemId == 46978) // shaman earth ring totem
{
continue;
}
uint32 skipProb = 25; uint32 skipProb = 25;
if (urand(1, 100) <= skipProb) if (urand(1, 100) <= skipProb)
continue; continue;

View File

@@ -185,6 +185,7 @@ PlayerbotAI::PlayerbotAI(Player* bot)
botOutgoingPacketHandlers.AddHandler(SMSG_TRADE_STATUS_EXTENDED, "trade status extended"); botOutgoingPacketHandlers.AddHandler(SMSG_TRADE_STATUS_EXTENDED, "trade status extended");
botOutgoingPacketHandlers.AddHandler(SMSG_LOOT_RESPONSE, "loot response"); botOutgoingPacketHandlers.AddHandler(SMSG_LOOT_RESPONSE, "loot response");
botOutgoingPacketHandlers.AddHandler(SMSG_ITEM_PUSH_RESULT, "item push result"); botOutgoingPacketHandlers.AddHandler(SMSG_ITEM_PUSH_RESULT, "item push result");
botOutgoingPacketHandlers.AddHandler(SMSG_LOOT_ROLL_WON, "loot roll won");
botOutgoingPacketHandlers.AddHandler(SMSG_PARTY_COMMAND_RESULT, "party command"); botOutgoingPacketHandlers.AddHandler(SMSG_PARTY_COMMAND_RESULT, "party command");
botOutgoingPacketHandlers.AddHandler(SMSG_LEVELUP_INFO, "levelup"); botOutgoingPacketHandlers.AddHandler(SMSG_LEVELUP_INFO, "levelup");
botOutgoingPacketHandlers.AddHandler(SMSG_LOG_XPGAIN, "xpgain"); botOutgoingPacketHandlers.AddHandler(SMSG_LOG_XPGAIN, "xpgain");
@@ -892,6 +893,7 @@ bool PlayerbotAI::IsAllowedCommand(std::string const text)
unsecuredCommands.insert("invite"); unsecuredCommands.insert("invite");
unsecuredCommands.insert("leave"); unsecuredCommands.insert("leave");
unsecuredCommands.insert("lfg"); unsecuredCommands.insert("lfg");
unsecuredCommands.insert("pvp stats");
unsecuredCommands.insert("rpg status"); unsecuredCommands.insert("rpg status");
} }
@@ -1553,6 +1555,9 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
case 544: case 544:
strategyName = "magtheridon"; // Magtheridon's Lair strategyName = "magtheridon"; // Magtheridon's Lair
break; break;
case 548:
strategyName = "ssc"; // Serpentshrine Cavern
break;
case 565: case 565:
strategyName = "gruulslair"; // Gruul's Lair strategyName = "gruulslair"; // Gruul's Lair
break; break;
@@ -1584,7 +1589,7 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
strategyName = "wotlk-hol"; // Halls of Lightning strategyName = "wotlk-hol"; // Halls of Lightning
break; break;
case 603: case 603:
strategyName = "uld"; // Ulduar strategyName = "ulduar"; // Ulduar
break; break;
case 604: case 604:
strategyName = "wotlk-gd"; // Gundrak strategyName = "wotlk-gd"; // Gundrak
@@ -2579,7 +2584,7 @@ std::string PlayerbotAI::GetLocalizedGameObjectName(uint32 entry)
return name; return name;
} }
std::vector<Player*> PlayerbotAI::GetPlayersInGroup() std::vector<Player*> PlayerbotAI::GetRealPlayersInGroup()
{ {
std::vector<Player*> members; std::vector<Player*> members;
@@ -2606,6 +2611,30 @@ std::vector<Player*> PlayerbotAI::GetPlayersInGroup()
return members; return members;
} }
std::vector<Player*> PlayerbotAI::GetAllPlayersInGroup()
{
std::vector<Player*> members;
Group* group = bot->GetGroup();
if (!group)
return members;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member)
{
continue;
}
members.push_back(ref->GetSource());
}
return members;
}
bool PlayerbotAI::SayToGuild(const std::string& msg) bool PlayerbotAI::SayToGuild(const std::string& msg)
{ {
if (msg.empty()) if (msg.empty())
@@ -2714,9 +2743,9 @@ bool PlayerbotAI::SayToParty(const std::string& msg)
ChatHandler::BuildChatPacket(data, CHAT_MSG_PARTY, msg.c_str(), LANG_UNIVERSAL, CHAT_TAG_NONE, bot->GetGUID(), ChatHandler::BuildChatPacket(data, CHAT_MSG_PARTY, msg.c_str(), LANG_UNIVERSAL, CHAT_TAG_NONE, bot->GetGUID(),
bot->GetName()); bot->GetName());
for (auto reciever : GetPlayersInGroup()) for (auto receiver : GetRealPlayersInGroup())
{ {
ServerFacade::instance().SendPacket(reciever, &data); ServerFacade::instance().SendPacket(receiver, &data);
} }
return true; return true;
@@ -2731,9 +2760,9 @@ bool PlayerbotAI::SayToRaid(const std::string& msg)
ChatHandler::BuildChatPacket(data, CHAT_MSG_RAID, msg.c_str(), LANG_UNIVERSAL, CHAT_TAG_NONE, bot->GetGUID(), ChatHandler::BuildChatPacket(data, CHAT_MSG_RAID, msg.c_str(), LANG_UNIVERSAL, CHAT_TAG_NONE, bot->GetGUID(),
bot->GetName()); bot->GetName());
for (auto reciever : GetPlayersInGroup()) for (auto receiver : GetRealPlayersInGroup())
{ {
ServerFacade::instance().SendPacket(reciever, &data); ServerFacade::instance().SendPacket(receiver, &data);
} }
return true; return true;

View File

@@ -446,7 +446,8 @@ public:
GameObject* GetGameObject(ObjectGuid guid); GameObject* GetGameObject(ObjectGuid guid);
// static GameObject* GetGameObject(GameObjectData const* gameObjectData); // static GameObject* GetGameObject(GameObjectData const* gameObjectData);
WorldObject* GetWorldObject(ObjectGuid guid); WorldObject* GetWorldObject(ObjectGuid guid);
std::vector<Player*> GetPlayersInGroup(); std::vector<Player*> GetAllPlayersInGroup();
std::vector<Player*> GetRealPlayersInGroup();
const AreaTableEntry* GetCurrentArea(); const AreaTableEntry* GetCurrentArea();
const AreaTableEntry* GetCurrentZone(); const AreaTableEntry* GetCurrentZone();
static std::string GetLocalizedAreaName(const AreaTableEntry* entry); static std::string GetLocalizedAreaName(const AreaTableEntry* entry);

View File

@@ -1784,7 +1784,7 @@ void RandomPlayerbotMgr::PrepareZone2LevelBracket()
zone2LevelBracket[3525] = {10, 21}; // Bloodmyst Isle zone2LevelBracket[3525] = {10, 21}; // Bloodmyst Isle
// Classic WoW - High - level zones // Classic WoW - High - level zones
zone2LevelBracket[10] = {19, 33}; // Deadwind Pass zone2LevelBracket[10] = {19, 33}; // Duskwood
zone2LevelBracket[11] = {21, 30}; // Wetlands zone2LevelBracket[11] = {21, 30}; // Wetlands
zone2LevelBracket[44] = {16, 28}; // Redridge Mountains zone2LevelBracket[44] = {16, 28}; // Redridge Mountains
zone2LevelBracket[267] = {20, 34}; // Hillsbrad Foothills zone2LevelBracket[267] = {20, 34}; // Hillsbrad Foothills

View File

@@ -2256,10 +2256,13 @@ void RandomItemMgr::BuildEquipCacheNew()
{ {
continue; continue;
} }
if (itemId == 22784)
{ // Sunwell Orb // Unobtainable or unusable items
if (itemId == 12468 || // Chilton Wand
itemId == 22784 || // Sunwell Orb
itemId == 46978) // Totem of the Earthen Ring
continue; continue;
}
equipCacheNew[proto->RequiredLevel][proto->InventoryType].push_back(itemId); equipCacheNew[proto->RequiredLevel][proto->InventoryType].push_back(itemId);
} }
} }

View File

@@ -881,107 +881,6 @@ std::vector<GameObjectData const*> WorldPosition::getGameObjectsNear(float radiu
return worker.GetResult(); return worker.GetResult();
} }
Creature* GuidPosition::GetCreature()
{
if (!*this)
return nullptr;
if (loadedFromDB)
{
auto creatureBounds = getMap()->GetCreatureBySpawnIdStore().equal_range(GetCounter());
if (creatureBounds.first != creatureBounds.second)
return creatureBounds.second->second;
return nullptr;
}
return getMap()->GetCreature(*this);
}
Unit* GuidPosition::GetUnit()
{
if (!*this)
return nullptr;
if (loadedFromDB)
{
auto creatureBounds = getMap()->GetCreatureBySpawnIdStore().equal_range(GetCounter());
if (creatureBounds.first != creatureBounds.second)
return creatureBounds.second->second;
return nullptr;
}
if (IsPlayer())
return ObjectAccessor::FindPlayer(*this);
if (IsPet())
return getMap()->GetPet(*this);
return GetCreature();
}
GameObject* GuidPosition::GetGameObject()
{
if (!*this)
return nullptr;
if (loadedFromDB)
{
auto gameobjectBounds = getMap()->GetGameObjectBySpawnIdStore().equal_range(GetCounter());
if (gameobjectBounds.first != gameobjectBounds.second)
return gameobjectBounds.second->second;
return nullptr;
}
return getMap()->GetGameObject(*this);
}
Player* GuidPosition::GetPlayer()
{
if (!*this)
return nullptr;
if (IsPlayer())
return ObjectAccessor::FindPlayer(*this);
return nullptr;
}
bool GuidPosition::isDead()
{
if (!getMap())
return false;
if (!getMap()->IsGridLoaded(getX(), getY()))
return false;
if (IsUnit() && GetUnit() && GetUnit()->IsInWorld() && GetUnit()->IsAlive())
return false;
if (IsGameObject() && GetGameObject() && GetGameObject()->IsInWorld())
return false;
return true;
}
GuidPosition::GuidPosition(WorldObject* wo) : ObjectGuid(wo->GetGUID()), WorldPosition(wo), loadedFromDB(false) {}
GuidPosition::GuidPosition(CreatureData const& creData)
: ObjectGuid(HighGuid::Unit, creData.id1, creData.spawnId),
WorldPosition(creData.mapid, creData.posX, creData.posY, creData.posZ, creData.orientation)
{
loadedFromDB = true;
}
GuidPosition::GuidPosition(GameObjectData const& goData)
: ObjectGuid(HighGuid::GameObject, goData.id),
WorldPosition(goData.mapid, goData.posX, goData.posY, goData.posZ, goData.orientation)
{
loadedFromDB = true;
}
CreatureTemplate const* GuidPosition::GetCreatureTemplate() CreatureTemplate const* GuidPosition::GetCreatureTemplate()
{ {
return IsCreature() ? sObjectMgr->GetCreatureTemplate(GetEntry()) : nullptr; return IsCreature() ? sObjectMgr->GetCreatureTemplate(GetEntry()) : nullptr;
@@ -1000,7 +899,7 @@ WorldObject* GuidPosition::GetWorldObject()
switch (GetHigh()) switch (GetHigh())
{ {
case HighGuid::Player: case HighGuid::Player:
return ObjectAccessor::FindPlayer(*this); return GetPlayer();
case HighGuid::Transport: case HighGuid::Transport:
case HighGuid::Mo_Transport: case HighGuid::Mo_Transport:
case HighGuid::GameObject: case HighGuid::GameObject:
@@ -1021,8 +920,93 @@ WorldObject* GuidPosition::GetWorldObject()
return nullptr; return nullptr;
} }
GameObject* GuidPosition::GetGameObject()
{
if (!*this)
return nullptr;
if (loadedFromDB)
return ObjectAccessor::GetSpawnedGameObjectByDBGUID(GetMapId(), GetCounter());
return getMap()->GetGameObject(*this); // fallback
}
Unit* GuidPosition::GetUnit()
{
if (!*this)
return nullptr;
if (IsPlayer())
return GetPlayer();
if (IsPet())
return getMap()->GetPet(*this);
return GetCreature();
}
Creature* GuidPosition::GetCreature()
{
if (!*this)
return nullptr;
if (loadedFromDB)
return ObjectAccessor::GetSpawnedCreatureByDBGUID(GetMapId(), GetCounter());
return getMap()->GetCreature(*this); // fallback
}
Player* GuidPosition::GetPlayer()
{
if (!*this)
return nullptr;
if (IsPlayer())
return ObjectAccessor::FindPlayer(*this);
return nullptr;
}
bool GuidPosition::HasNpcFlag(NPCFlags flag) { return IsCreature() && GetCreatureTemplate()->npcflag & flag; } bool GuidPosition::HasNpcFlag(NPCFlags flag) { return IsCreature() && GetCreatureTemplate()->npcflag & flag; }
bool GuidPosition::IsCreatureOrGOAccessible()
{
Map* map = getMap();
if (!map || !map->IsGridLoaded(GetPositionX(), GetPositionY()))
return false;
if (IsCreature())
{
Creature* creature = GetCreature();
if (creature && creature->IsInWorld() && creature->IsAlive())
return true;
}
else if (IsGameObject())
{
GameObject* go = GetGameObject();
if (go && go->IsInWorld())
return true;
}
return false;
}
GuidPosition::GuidPosition(WorldObject* wo) : ObjectGuid(wo->GetGUID()), WorldPosition(wo), loadedFromDB(false) {}
GuidPosition::GuidPosition(CreatureData const& creData)
: ObjectGuid(HighGuid::Unit, creData.id1, creData.spawnId),
WorldPosition(creData.mapid, creData.posX, creData.posY, creData.posZ, creData.orientation)
{
loadedFromDB = true;
}
GuidPosition::GuidPosition(GameObjectData const& goData)
: ObjectGuid(HighGuid::GameObject, goData.id),
WorldPosition(goData.mapid, goData.posX, goData.posY, goData.posZ, goData.orientation)
{
loadedFromDB = true;
}
std::vector<WorldPosition*> TravelDestination::getPoints(bool ignoreFull) std::vector<WorldPosition*> TravelDestination::getPoints(bool ignoreFull)
{ {
if (ignoreFull) if (ignoreFull)

View File

@@ -416,14 +416,13 @@ public:
GameObjectTemplate const* GetGameObjectTemplate(); GameObjectTemplate const* GetGameObjectTemplate();
WorldObject* GetWorldObject(); WorldObject* GetWorldObject();
Creature* GetCreature();
Unit* GetUnit();
GameObject* GetGameObject(); GameObject* GetGameObject();
Unit* GetUnit();
Creature* GetCreature();
Player* GetPlayer(); Player* GetPlayer();
bool HasNpcFlag(NPCFlags flag); bool HasNpcFlag(NPCFlags flag);
bool IsCreatureOrGOAccessible(); // For loaded grids check if the creature/gameobject is in world + alive
bool isDead(); // For loaded grids check if the unit/object is unloaded/dead.
operator bool() const { return !IsEmpty(); } operator bool() const { return !IsEmpty(); }
bool operator==(ObjectGuid const& guid) const { return GetRawValue() == guid.GetRawValue(); } bool operator==(ObjectGuid const& guid) const { return GetRawValue() == guid.GetRawValue(); }

View File

@@ -14,9 +14,11 @@
#include "PlayerbotOperation.h" #include "PlayerbotOperation.h"
#include "Player.h" #include "Player.h"
#include "PlayerbotAI.h" #include "PlayerbotAI.h"
#include "PlayerbotAIConfig.h"
#include "PlayerbotMgr.h" #include "PlayerbotMgr.h"
#include "PlayerbotRepository.h" #include "PlayerbotRepository.h"
#include "RandomPlayerbotMgr.h" #include "RandomPlayerbotMgr.h"
#include "UseMeetingStoneAction.h"
#include "WorldSession.h" #include "WorldSession.h"
#include "WorldSessionMgr.h" #include "WorldSessionMgr.h"
@@ -74,6 +76,15 @@ public:
if (group->AddMember(target)) if (group->AddMember(target))
{ {
LOG_DEBUG("playerbots", "GroupInviteOperation: Successfully added {} to group", target->GetName()); LOG_DEBUG("playerbots", "GroupInviteOperation: Successfully added {} to group", target->GetName());
if (sPlayerbotAIConfig.summonWhenGroup && target->GetDistance(bot) > sPlayerbotAIConfig.sightDistance)
{
PlayerbotAI* targetAI = sPlayerbotsMgr.GetPlayerbotAI(target);
if (targetAI)
{
SummonAction summonAction(targetAI, "group summon");
summonAction.Teleport(bot, target, true);
}
}
return true; return true;
} }
else else