Compare commits

..

21 Commits

Author SHA1 Message Date
bash
b8adda0a90 fix(Core/Travel): Drop AddExcludeFlag call (core handles via IsBot filter) 2026-05-10 20:50:24 +02:00
bash
cfcc29b86f Merge remote-tracking branch 'origin/test-staging' into feature/new_rpg_and_nav_2
# Conflicts:
#	src/Ai/World/Rpg/Action/NewRpgAction.cpp
#	src/Mgr/Travel/TravelMgr.cpp
2026-05-10 20:42:36 +02:00
Keleborn
9118c9671a
Fix/travelValType (#2376)
<!--
Thank you for contributing to mod-playerbots, please make sure that
you...
1. Submit your PR to the test-staging branch, not master.
2. Read the guidelines below before submitting.
3. Don't delete parts of this template.

DESIGN PHILOSOPHY: We prioritize STABILITY, PERFORMANCE, AND
PREDICTABILITY over behavioral realism.

Every action and decision executes PER BOT AND PER TRIGGER. Small
increases in logic complexity scale
poorly across thousands of bots and negatively affect all. We prioritize
a stable system over a smarter
one. Bots don't need to behave perfectly; believable behavior is the
goal, not human simulation.
Default behavior must be cheap in processing; expensive behavior must be
opt-in.

Before submitting, make sure your changes aligns with these principles.
-->

## Pull Request Description
<!-- Describe what this change does and why it is needed -->
Incorrect var types when I refactored away from SQL lookup. 


## Feature Evaluation
<!--
If your PR is very minimal (comment typo, wrong ID reference, etc), and
it is very obvious it will not have
any impact on performance, you may skip these question. If necessary, a
maintainer may ask you for them later.
-->

<!-- Please answer the following: -->
- Describe the **minimum logic** required to achieve the intended
behavior.
- Describe the **processing 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, number of bots, specific
configuration).
- Expected behavior and how to verify it.
-->



## Impact Assessment
<!-- As a generic test, before and after measure of pmon (playerbot pmon
tick) can help you here. -->
- Does this change increase per-bot/per-tick processing or risk scaling
poorly with thousands of bots?
    - - [x] No, not at all
    - - [ ] Minimal impact (**explain below**)
    - - [ ] Moderate impact (**explain below**)



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



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



## AI Assistance
<!--
AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
We expect contributors to be honest about what they do and do not
understand.
-->
Was AI assistance used while working on this change?
- - [x] No
- - [ ] Yes (**explain below**)
<!--
If yes, please specify:
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation).
- Which parts of the change were influenced or generated, and whether it
was thoroughly reviewed.
-->



<!--
TRANSLATIONS:
Anything new that the bots say in chat must be in a translatable format.
This is done using GetBotTextOrDefault,
which you can search for in the codebase to find examples. Your code
needs to have English as the default fallback,
while the full translations need to be in an SQL update file. The
languages in the file are the nine language
options supported by AzerothCore: English, Korean, French, German,
Chinese, Taiwanese, Spanish, Spanish Mexico, and
Russian. See
data/sql/playerbots/updates/2025_12_27_ai_playerbot_fishing_text.sql as
an example of a translation SQL
update, whose content are called within the codebase at
src/strategy/actions/FishingAction.cpp
-->

## Final Checklist

- - [x] Stability is not compromised.
- - [x] Performance impact is understood, tested, and acceptable.
- - [x] Added logic complexity is justified and explained.
- - [x] Any new bot dialogue lines are translated.
- - [x] Documentation updated if needed (Conf comments, WiKi commands).

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
2026-05-09 19:14:05 +02:00
Keleborn
8cb847db5d
Fix location cache. (#2374)
<!--
Thank you for contributing to mod-playerbots, please make sure that
you...
1. Submit your PR to the test-staging branch, not master.
2. Read the guidelines below before submitting.
3. Don't delete parts of this template.

DESIGN PHILOSOPHY: We prioritize STABILITY, PERFORMANCE, AND
PREDICTABILITY over behavioral realism.

Every action and decision executes PER BOT AND PER TRIGGER. Small
increases in logic complexity scale
poorly across thousands of bots and negatively affect all. We prioritize
a stable system over a smarter
one. Bots don't need to behave perfectly; believable behavior is the
goal, not human simulation.
Default behavior must be cheap in processing; expensive behavior must be
opt-in.

Before submitting, make sure your changes aligns with these principles.
-->

## Pull Request Description
<!-- Describe what this change does and why it is needed -->
Bot locations were not correctly registered, so they werent picking it
as often as they should.
In part related to #2369


## Feature Evaluation
<!--
If your PR is very minimal (comment typo, wrong ID reference, etc), and
it is very obvious it will not have
any impact on performance, you may skip these question. If necessary, a
maintainer may ask you for them later.
-->

<!-- Please answer the following: -->
- Describe the **minimum logic** required to achieve the intended
behavior.
- Describe the **processing 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, number of bots, specific
configuration).
- Expected behavior and how to verify it.
-->



## Impact Assessment
<!-- As a generic test, before and after measure of pmon (playerbot pmon
tick) can help you here. -->
- Does this change increase per-bot/per-tick processing or risk scaling
poorly with thousands of bots?
    - - [ ] No, not at all
    - - [ ] Minimal impact (**explain below**)
    - - [ ] Moderate impact (**explain below**)



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



- Does this change add new decision branches or increase maintenance
complexity?
    - - [ ] No
    - - [ ] Yes (**explain below**)



## AI Assistance
<!--
AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
We expect contributors to be honest about what they do and do not
understand.
-->
Was AI assistance used while working on this change?
- - [ ] No
- - [ ] Yes (**explain below**)
<!--
If yes, please specify:
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation).
- Which parts of the change were influenced or generated, and whether it
was thoroughly reviewed.
-->



<!--
TRANSLATIONS:
Anything new that the bots say in chat must be in a translatable format.
This is done using GetBotTextOrDefault,
which you can search for in the codebase to find examples. Your code
needs to have English as the default fallback,
while the full translations need to be in an SQL update file. The
languages in the file are the nine language
options supported by AzerothCore: English, Korean, French, German,
Chinese, Taiwanese, Spanish, Spanish Mexico, and
Russian. See
data/sql/playerbots/updates/2025_12_27_ai_playerbot_fishing_text.sql as
an example of a translation SQL
update, whose content are called within the codebase at
src/strategy/actions/FishingAction.cpp
-->

## Final Checklist

- - [ ] Stability is not compromised.
- - [ ] Performance impact is understood, tested, and acceptable.
- - [ ] Added logic complexity is justified and explained.
- - [ ] Any new bot dialogue lines are translated.
- - [ ] Documentation updated if needed (Conf comments, WiKi commands).

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
2026-05-09 09:48:59 +02:00
NoxMax
66d41e1d79
Feat: Selective reset to default of combat or non-combat strategies (#2365)
<!--
Thank you for contributing to mod-playerbots, please make sure that
you...
1. Submit your PR to the test-staging branch, not master.
2. Read the guidelines below before submitting.
3. Don't delete parts of this template.

DESIGN PHILOSOPHY: We prioritize STABILITY, PERFORMANCE, AND
PREDICTABILITY over behavioral realism.

Every action and decision executes PER BOT AND PER TRIGGER. Small
increases in logic complexity scale
poorly across thousands of bots and negatively affect all. We prioritize
a stable system over a smarter
one. Bots don't need to behave perfectly; believable behavior is the
goal, not human simulation.
Default behavior must be cheap in processing; expensive behavior must be
opt-in.

Before submitting, make sure your changes aligns with these principles.
-->

## Pull Request Description
<!-- Describe what this change does and why it is needed -->
Adds the commands `co !` and `nc !`, which would reset either the combat
or non-combat strategies of a follower bot, without affecting the other
strategies or any other values.

Also ChangeStrategyAction.cpp was refactored for duplicate code by
introducing the helper function `HandleStrategyCommon`, that gets called
by `ChangeCombatStrategyAction` and `ChangeNonCombatStrategyAction`


## Feature Evaluation
<!--
If your PR is very minimal (comment typo, wrong ID reference, etc), and
it is very obvious it will not have
any impact on performance, you may skip these question. If necessary, a
maintainer may ask you for them later.
-->

<!-- Please answer the following: -->
- Describe the **minimum logic** required to achieve the intended
behavior.
- Describe the **processing cost** when this logic executes across many
bots.

`reset botAI` already resets strategies back to default, but it resets
ALL strategies and wipes values such as formations, stances, and
everything else under the `value` key in playerbots_db_store>value. The
new commands don't run across many bots, only on the bot the command is
run on.


## How to Test the Changes
<!--
- Step-by-step instructions to test the change.
- Any required setup (e.g. multiple players, number of bots, specific
configuration).
- Expected behavior and how to verify it.
-->
1. Run either `co ?` and `nc ?` to see current list of combat and
non-combat strategies the bot has.
2. Add and remove strategies to both `co` and `nc`.
3. Confirm your changes with `co ?` and `nc ?`.
4. Run `co !` only.
5. Run `co ?` to confirm combat strategies have been reset to default,
and `nc ?` to confirm it has not been affected. Then run `nc !` to reset
it as well.
6. Do another test [inside an
instance](https://github.com/mod-playerbots/mod-playerbots/wiki/Playerbot-Commands#raid-specific-strategies).
Remove a bunch of `nc` strategies, including the strategy for the raid
itself.
7. Run `nc !` and check that the defaults have been reset, but that the
instance strategy has been re-added as well.



## Impact Assessment
<!-- As a generic test, before and after measure of pmon (playerbot pmon
tick) can help you here. -->
- Does this change increase per-bot/per-tick processing or risk scaling
poorly with thousands of bots?
    - - [x] No, not at all
    - - [ ] Minimal impact (**explain below**)
    - - [ ] Moderate impact (**explain below**)



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



- Does this change add new decision branches or increase maintenance
complexity?
    - - [ ] No
    - - [x] Yes (**explain below**)
Technically it adds a new case to ChangeCombatStrategyAction, but it's
straightforward.


## AI Assistance
<!--
AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
We expect contributors to be honest about what they do and do not
understand.
-->
Was AI assistance used while working on this change?
- - [ ] No
- - [x] Yes (**explain below**)
<!--
If yes, please specify:
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation).
- Which parts of the change were influenced or generated, and whether it
was thoroughly reviewed.
-->
Review only in case I was missing something, and then to easily refactor
duplicate code with HandleStrategyCommon.



<!--
TRANSLATIONS:
Anything new that the bots say in chat must be in a translatable format.
This is done using GetBotTextOrDefault,
which you can search for in the codebase to find examples. Your code
needs to have English as the default fallback,
while the full translations need to be in an SQL update file. The
languages in the file are the nine language
options supported by AzerothCore: English, Korean, French, German,
Chinese, Taiwanese, Spanish, Spanish Mexico, and
Russian. See
data/sql/playerbots/updates/2025_12_27_ai_playerbot_fishing_text.sql as
an example of a translation SQL
update, whose content are called within the codebase at
src/strategy/actions/FishingAction.cpp
-->

## Final Checklist

- - [x] Stability is not compromised.
- - [x] Performance impact is understood, tested, and acceptable.
- - [x] Added logic complexity is justified and explained.
- - [x] Any new bot dialogue lines are translated.
- - [x] Documentation updated if needed (Conf comments, WiKi commands).

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->

[Commands
wiki](https://github.com/mod-playerbots/mod-playerbots/wiki/Playerbot-Commands#strategies)
need to be modified to read:

---
You can query the bot to report what strategies are currently being
used:

```
co ?
nc ?
```
You can reset either of the bot's strategies back to defaults:

```
co !
nc !
```
---

Tangentially I also recommend [this
section](https://github.com/mod-playerbots/mod-playerbots/wiki/Playerbot-Commands#non-combat-strategies)
to be edit to this for more accuracy:

---
General
strategy | description
:---|:---
``food`` | enable bot's ability to eat/drink
``pvp`` | enable bot's ability to engage in PVP combat. Note: PVP mode
wouldn't appear active until the bot starts combat
``loot`` | enable bot's ability to loot. Note: adding or removing that
strategy for randombots requires GM level
2026-05-08 22:42:18 -07:00
Alex Dcnh
d2e5443109
Fix contradictory leader bot check in `LeaveLargeGuildTrigger::IsActi… (#2361)
<!--
Thank you for contributing to mod-playerbots, please make sure that
you...
1. Submit your PR to the test-staging branch, not master.
2. Read the guidelines below before submitting.
3. Don't delete parts of this template.

DESIGN PHILOSOPHY: We prioritize STABILITY, PERFORMANCE, AND
PREDICTABILITY over behavioral realism.

Every action and decision executes PER BOT AND PER TRIGGER. Small
increases in logic complexity scale
poorly across thousands of bots and negatively affect all. We prioritize
a stable system over a smarter
one. Bots don't need to behave perfectly; believable behavior is the
goal, not human simulation.
Default behavior must be cheap in processing; expensive behavior must be
opt-in.

Before submitting, make sure your changes aligns with these principles.
-->

## Pull Request Description
The previous logic contained two contradictory guards back-to-back:

```
// First check: passes only if IsRealPlayer() == true
if (!leader || !GET_PLAYERBOT_AI(leader) || !GET_PLAYERBOT_AI(leader)->IsRealPlayer())
    return false;

// Second check: returns false if IsRealPlayer() == true
PlayerbotAI* leaderBotAI = GET_PLAYERBOT_AI(leader);
if (!leaderBotAI || leaderBotAI->IsRealPlayer())
    return false;
```

The first guard (due to the erroneous `!` before `IsRealPlayer()`) only
passes when the leader **is** a real player. The second guard then
immediately returns `false` for the same reason, making the function
incapable of ever returning `true`.



## Feature Evaluation
<!--
If your PR is very minimal (comment typo, wrong ID reference, etc), and
it is very obvious it will not have
any impact on performance, you may skip these question. If necessary, a
maintainer may ask you for them later.
-->

<!-- Please answer the following: -->
- Describe the **minimum logic** required to achieve the intended
behavior.
- Describe the **processing 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, number of bots, specific
configuration).
- Expected behavior and how to verify it.
-->



## Impact Assessment
<!-- As a generic test, before and after measure of pmon (playerbot pmon
tick) can help you here. -->
- Does this change increase per-bot/per-tick processing or risk scaling
poorly with thousands of bots?
    - - [x] No, not at all
    - - [ ] Minimal impact (**explain below**)
    - - [ ] Moderate impact (**explain below**)



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



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



## AI Assistance
<!--
AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
We expect contributors to be honest about what they do and do not
understand.
-->
Was AI assistance used while working on this change?
- - [x] No
- - [ ] Yes (**explain below**)
<!--
If yes, please specify:
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation).
- Which parts of the change were influenced or generated, and whether it
was thoroughly reviewed.
-->



<!--
TRANSLATIONS:
Anything new that the bots say in chat must be in a translatable format.
This is done using GetBotTextOrDefault,
which you can search for in the codebase to find examples. Your code
needs to have English as the default fallback,
while the full translations need to be in an SQL update file. The
languages in the file are the nine language
options supported by AzerothCore: English, Korean, French, German,
Chinese, Taiwanese, Spanish, Spanish Mexico, and
Russian. See
data/sql/playerbots/updates/2025_12_27_ai_playerbot_fishing_text.sql as
an example of a translation SQL
update, whose content are called within the codebase at
src/strategy/actions/FishingAction.cpp
-->

## Final Checklist

- - [x] Stability is not compromised.
- - [x] Performance impact is understood, tested, and acceptable.
- - [x] Added logic complexity is justified and explained.
- - [x] Any new bot dialogue lines are translated.
- - [x] Documentation updated if needed (Conf comments, WiKi commands).

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
2026-05-08 22:42:07 -07:00
Crow
6b0df4ff6c
Fix ambiguous item parsing in bot text (#2356)
## Pull Request Description

This change fixes two cases of broken bot text with respect to inventory
items.

1. Reserved inventory qualifiers such as "mount," "food," "drink," etc.
no longer also trigger generic item name matching. I first noticed this
problem when my resto Shaman who had the "Mounting Vengeance" weapon in
her inventory would repeatedly give error messages of failing to use it
while mounting (because mounting also causes bots to use items that fit
the reserved "mount," which due to this bug, also caused bots to try to
use any item with "mount" in its name).

2. Custom cast output text no longer reports an inferred bag item as the
spell target for normal unit-targeted casts such as "cast chain heal on
Keleborn." There was a bug where the action would first parse the actual
target and then parse the spell text and then try to match the last word
of the string to a bag item (so the bot would say it was casting chain
heal on a healing potion, even though the heal was in fact cast
correctly on a player).

## Feature Evaluation

- Describe the **minimum logic** required to achieve the intended
behavior.

- Add a reserved-qualifier check in InventoryAction::parseItems() so
reserved selectors do not also run through FindNamedItemVisitor.
- In custom cast output text, choose the displayed target based on the
actual target type already resolved for the cast.
  - It does not change mount selection behavior itself.
  - It does not add new spell-target parsing rules.
 
- Describe the **processing cost** when this logic executes across many
bots.
  - None.

## How to Test the Changes

Reserved qualifiers:
1. Give a bot in your party the "Mounting Vengeance" weapon.
2. Mount up, and the bot should mount too without saying anything
(before the fix, the bot would say it is using the weapon and that the
item was not found).

Spell cast text:
1. Give a bot an inventory item whose name overlaps with part of a spell
name, such as a healing potion.
2. Command a bot to cast some heal on a player.
3. The bot should cast the spell on the intended player (as was the case
previously), and the status text names the player instead of the
inventory item.

## Impact Assessment

- Does this change increase per-bot/per-tick processing or risk scaling
poorly with thousands of bots?
    - [x] No, not at all
    - [ ] Minimal impact (**explain below**)
   
- Does this change modify default bot behavior?
    - [x] No
    - [ ] Yes (**explain why**)

- Does this change add new decision branches or increase maintenance
complexity?
    - [ ] No
    - [x] Yes (**explain below**)
    
Very minor changes. InventoryAction gets one explicit reserved-qualifier
guard. Custom cast text selection becomes more explicit about which
target type should be displayed.

## AI Assistance

Was AI assistance used while working on this change?
- [ ] No
- [x] Yes (**explain below**)

GPT-5.4 was used to trace the relevant code paths for the errors and
propose the changes.

## Final Checklist

- [x] Stability is not compromised.
- [x] Performance impact is understood, tested, and acceptable.
- [x] Added logic complexity is justified and explained.
- [x] Any new bot dialogue lines are translated.
- [x] Documentation updated if needed (Conf comments, WiKi commands).

## Notes for Reviewers

---------

Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com>
Co-authored-by: bash <hermensb@gmail.com>
Co-authored-by: Revision <tkn963@gmail.com>
Co-authored-by: kadeshar <kadeshar@gmail.com>
2026-05-08 22:41:54 -07:00
Keleborn
b8ff5996f8
Flying mount fixes and self-bot (#2351)
<!--
Thank you for contributing to mod-playerbots, please make sure that
you...
1. Submit your PR to the test-staging branch, not master.
2. Read the guidelines below before submitting.
3. Don't delete parts of this template.

DESIGN PHILOSOPHY: We prioritize STABILITY, PERFORMANCE, AND
PREDICTABILITY over behavioral realism.

Every action and decision executes PER BOT AND PER TRIGGER. Small
increases in logic complexity scale
poorly across thousands of bots and negatively affect all. We prioritize
a stable system over a smarter
one. Bots don't need to behave perfectly; believable behavior is the
goal, not human simulation.
Default behavior must be cheap in processing; expensive behavior must be
opt-in.

Before submitting, make sure your changes aligns with these principles.
-->

## Pull Request Description
<!-- Describe what this change does and why it is needed -->
This PR does a few things.
1. Enable Selfbots to mount up. Because they have masters, but are their
own masters, they would never mount up because their master never
mounted.
2. Fix flag state handling after processing the aura change. 
3. Add in the Dismount packet handler. This is intended to implement
fall animations and have bots touch the ground when dismounting instead
of floating off the ground. (It was cleared anyway after the first move,
but this should make it more seamless.)



## Feature Evaluation
<!--
If your PR is very minimal (comment typo, wrong ID reference, etc), and
it is very obvious it will not have
any impact on performance, you may skip these question. If necessary, a
maintainer may ask you for them later.
-->

<!-- Please answer the following: -->
- Describe the **minimum logic** required to achieve the intended
behavior.
- Describe the **processing 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, number of bots, specific
configuration).
- Expected behavior and how to verify it.
-->
self bot should mount up, and select area appropriate mounts. 

Bots in your team should mount up, and on your dismount properly snap to
the ground.

should test at low Z (<1.0 off the ground) and higher z (> 1.0 off the
ground)




## Impact Assessment
<!-- As a generic test, before and after measure of pmon (playerbot pmon
tick) can help you here. -->
- Does this change increase per-bot/per-tick processing or risk scaling
poorly with thousands of bots?
    - - [ ] No, not at all
    - - [x] Minimal impact (**explain below**)
    - - [ ] Moderate impact (**explain below**)
The processing of a fall path has some impact, but I dont think itll be
too much.


- Does this change modify default bot behavior?
    - - [ ] No
    - - [x] Yes (**explain why**)
Add natural falling when dismounting. May incurr fall damange.... 


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



## AI Assistance
<!--
AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
We expect contributors to be honest about what they do and do not
understand.
-->
Was AI assistance used while working on this change?
- - [ ] No
- - [x] Yes (**explain below**)
<!--
If yes, please specify:
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation).
- Which parts of the change were influenced or generated, and whether it
was thoroughly reviewed.
-->
Comparison of code bases, searching for flags, adding diagnostic
logging, and processing of said logging.
iterating and brainstorming. 
Code was also written, but fully reviewed by me, and fixed where
appropriate.


<!--
TRANSLATIONS:
Anything new that the bots say in chat must be in a translatable format.
This is done using GetBotTextOrDefault,
which you can search for in the codebase to find examples. Your code
needs to have English as the default fallback,
while the full translations need to be in an SQL update file. The
languages in the file are the nine language
options supported by AzerothCore: English, Korean, French, German,
Chinese, Taiwanese, Spanish, Spanish Mexico, and
Russian. See
data/sql/playerbots/updates/2025_12_27_ai_playerbot_fishing_text.sql as
an example of a translation SQL
update, whose content are called within the codebase at
src/strategy/actions/FishingAction.cpp
-->

## Final Checklist

- - [x] Stability is not compromised.
- - [x] Performance impact is understood, tested, and acceptable.
- - [x] Added logic complexity is justified and explained.
- - [x] Any new bot dialogue lines are translated.
- - [x] Documentation updated if needed (Conf comments, WiKi commands).

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 22:41:13 -07:00
Crow
826887133d
Exclude Invalid Weapons from Shaman Enchants & Refactor Temporary Enchant Spellcasting (#2345)
<!--
Thank you for contributing to mod-playerbots, please make sure that
you...
1. Submit your PR to the test-staging branch, not master.
2. Read the guidelines below before submitting.
3. Don't delete parts of this template.

DESIGN PHILOSOPHY: We prioritize STABILITY, PERFORMANCE, AND
PREDICTABILITY over behavioral realism.

Every action and decision executes PER BOT AND PER TRIGGER. Small
increases in logic complexity scale
poorly across thousands of bots and negatively affect all. We prioritize
a stable system over a smarter
one. Bots don't need to behave perfectly; believable behavior is the
goal, not human simulation.
Default behavior must be cheap in processing; expensive behavior must be
opt-in.

Before submitting, make sure your changes aligns with these principles.
-->

## Pull Request Description
<!-- Describe what this change does and why it is needed -->
Fix for issue #2343 

I excluded the MISC and FISHING POLE weapon subclasses from weapon
enchants. MISC includes the entry profession "weapons" (skinning knife,
mining pick, blacksmithing hammer, arclight spanner) and some other crap
that I suspect is not enchantable, but even if it is there's no good
reason to do so (like Brewfest steins). The subclass doesn't include
weapons that can be used for professions but you might actually want to
use for fighting (like Finkle's Skinner).

To clean things up overall, I removed the intermediate class
CastEnchantItemAction between CastSpellAction and
CastEnchantItemMainHandAction and CastEnchantItemOffHandAction.
CastEnchantItemAction is not doing anything helpful that can't easily be
replicated in the MH/OH classes, and I can't think of any future reason
for keeping CastEnchantItemAction. I also brought the CanCastSpell check
into the MH/OH classes--previously it just wasn't run for the weapon
enchant spells, and I can't think of any good reason why it shouldn't
be.

I also added Execute functions to both CastEnchantItemMainHandAction and
CastEnchantItemOffHandAction so they actually directly cast the enchant
on the specified hand instead of running through CastSpellAction's
Execute (and thus going through item for spell). I wasn't having
problems with the wrong hand being applied under the prior approach, but
this is a more direct and better approach anyway.

Other changes are just formatting.

## Feature Evaluation
<!--
If your PR is very minimal (comment typo, wrong ID reference, etc), and
it is very obvious it will not have
any impact on performance, you may skip these question. If necessary, a
maintainer may ask you for them later.
-->

<!-- Please answer the following: -->
- Describe the **minimum logic** required to achieve the intended
behavior.
- Describe the **processing cost** when this logic executes across many
bots.

The new path is very similar to the old one but just adds a check that
is common to all spells and early returns to avoid invalid results.

## How to Test the Changes
<!--
- Step-by-step instructions to test the change.
- Any required setup (e.g. multiple players, number of bots, specific
configuration).
- Expected behavior and how to verify it.
-->

1. Log into a Shaman and activate selfbot
2. Check to make sure the correct enchantments are applied (e.g., MH
Windfury and OH Flametongue for a dual-wielding Enhancement Shaman)
3. Equip a profession weapon such as a skinning knife and make sure the
Shaman does not attempt to enchant it

## Impact Assessment
<!-- As a generic test, before and after measure of pmon (playerbot pmon
tick) can help you here. -->
- Does this change increase per-bot/per-tick processing or risk scaling
poorly with thousands of bots?
    - - [x] No, not at all
    - - [ ] Minimal impact (**explain below**)
    - - [ ] Moderate impact (**explain below**)



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

This just stops Shamans from trying to enchant stuff that they can't.

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



## AI Assistance
<!--
AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
We expect contributors to be honest about what they do and do not
understand.
-->
Was AI assistance used while working on this change?
- - [ ] No
- - [x] Yes (**explain below**)
<!--
If yes, please specify:
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation).
- Which parts of the change were influenced or generated, and whether it
was thoroughly reviewed.
-->

I kicked around some ideas with GPT-5.4 with respect to the refactoring
aspect of the PR after I had fixed the bug.
<!--
TRANSLATIONS:
Anything new that the bots say in chat must be in a translatable format.
This is done using GetBotTextOrDefault,
which you can search for in the codebase to find examples. Your code
needs to have English as the default fallback,
while the full translations need to be in an SQL update file. The
languages in the file are the nine language
options supported by AzerothCore: English, Korean, French, German,
Chinese, Taiwanese, Spanish, Spanish Mexico, and
Russian. See
data/sql/playerbots/updates/2025_12_27_ai_playerbot_fishing_text.sql as
an example of a translation SQL
update, whose content are called within the codebase at
src/strategy/actions/FishingAction.cpp
-->

## Final Checklist

- - [x] Stability is not compromised.
- - [x] Performance impact is understood, tested, and acceptable.
- - [x] Added logic complexity is justified and explained.
- - [x] Any new bot dialogue lines are translated.
- - [x] Documentation updated if needed (Conf comments, WiKi commands).

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
2026-05-08 22:40:35 -07:00
Ivan Novokhatski
7af675e712
Respect worldserver's PreventAFKLogout value (#2328)
## Pull Request Description
This adds checks to prevent bots from logging out when the master isn't
actually logging out, respecting the `PreventAFKLogout` setting in
`worldserver.conf`. Otherwise, returning to the game after a long pause
means your bots are offline and you have to re-add them, which is
annoying.



## Feature Evaluation
N/A



## How to Test the Changes
1. Set `PreventAFKLogout` to 1 or 2 in `worldserver.conf`.
2. Start the server, log in, add some bots to your party.
3. Go to a sanctuary if you set `PreventAFKLogout` to 1 or just start
idling anywhere otherwise.
4. Both you and the bots will stay in-game no matter how much time has
passed.



## Impact Assessment
- Does this change increase per-bot/per-tick processing or risk scaling
poorly with thousands of bots?
    - - [x] No, not at all
    - - [ ] Minimal impact (**explain below**)
    - - [ ] Moderate impact (**explain below**)



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



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



## AI Assistance
Was AI assistance used while working on this change?
- - [ ] No
- - [x] Yes (**explain below**)
Researching the issue and determining what checks need to be
implemented.



## Final Checklist

- - [x] Stability is not compromised.
- - [x] Performance impact is understood, tested, and acceptable.
- - [x] Added logic complexity is justified and explained.
- - [x] Any new bot dialogue lines are translated.
- - [x] Documentation updated if needed (Conf comments, WiKi commands).

## Notes for Reviewers
N/A
2026-05-08 22:40:16 -07:00
Ivan Novokhatski
8caf37af97
Add EnableAutoTradeOnItemMention config option (#2323)
## Pull Request Description
This PR adds a config parameter
`AiPlayerbot.EnableAutoTradeOnItemMention` that controls whether trade
dialogues and inventory listings will be triggered for messages that
contain keywords anywhere in their text (for example "got some food?").
The default value is `1/true`, so for existing installs there will be no
change.

This is useful for other mods that could utilise game chats for other
purposes, specifically my
[mod-playerbots-characters](https://github.com/deseven/mod-playerbots-characters)
and @DustinHendrickson 's
[mod-ollama-chat](https://github.com/DustinHendrickson/mod-ollama-chat).
Individual users might also benefit from the ability to disable this
functionality.



## Feature Evaluation
N/A



## How to Test the Changes
1. Start the server with default config and join the game.
2. Get into a party with one or more bots.
3. Write `got some food?` to the party chat.
4. A trade dialogue along with the whispers from the bots should pop up.
5. Stop the server, change `AiPlayerbot.EnableAutoTradeOnItemMention` to
`0`.
6. Start the server, join the game.
7. Get into a party with one or more bots.
8. Write `got some food?` to the party chat.
9. Nothing should happen.

> [!NOTE]
> In both cases the commands `t something` and `c something` should
still work.

## Impact Assessment
- Does this change increase per-bot/per-tick processing or risk scaling
poorly with thousands of bots?
    - - [x] No, not at all
    - - [ ] Minimal impact (**explain below**)
    - - [ ] Moderate impact (**explain below**)



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



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



## AI Assistance
Was AI assistance 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] Any new bot dialogue lines are translated.
- - [x] Documentation updated if needed (Conf comments, WiKi commands).

## Notes for Reviewers
N/A
2026-05-08 22:39:55 -07:00
Crow
5d9761c9e8
Implement Battle for Mount Hyjal Strategies (#2258)
<!--
Thank you for contributing to mod-playerbots, please make sure that
you...
1. Submit your PR to the test-staging branch, not master.
2. Read the guidelines below before submitting.
3. Don't delete parts of this template.

DESIGN PHILOSOPHY: We prioritize STABILITY, PERFORMANCE, AND
PREDICTABILITY over behavioral realism.

Every action and decision executes PER BOT AND PER TRIGGER. Small
increases in logic complexity scale
poorly across thousands of bots and negatively affect all. We prioritize
a stable system over a smarter
one. Bots don't need to behave perfectly; believable behavior is the
goal, not human simulation.
Default behavior must be cheap in processing; expensive behavior must be
opt-in.

Before submitting, make sure your changes aligns with these principles.
-->

### Contingent on
https://github.com/mod-playerbots/mod-playerbots/pull/2295/

## Pull Request Description
<!-- Describe what this change does and why it is needed -->
This PR implements raid strategies for all bosses in everybody's
favorite TBC instance, the Battle for Mount Hyjal. As before, I have
designed these all to work with IP with 50% damage and healing. I also
did not merge the 1.88x buff to Vanilla & TBC healing items that IP
recently implemented.

The next post will outline all implemented strategies.

Note: Set to draft for now as I may tweak Archimonde some more, but I
generally consider Hyjal complete, subject to comments.

## Feature Evaluation
<!--
If your PR is very minimal (comment typo, wrong ID reference, etc), and
it is very obvious it will not have
any impact on performance, you may skip these question. If necessary, a
maintainer may ask you for them later.
-->

<!-- Please answer the following: -->
- Describe the **minimum logic** required to achieve the intended
behavior.
- Describe the **processing cost** when this logic executes across many
bots.

As with previous strategies, I've worked within the existing context of
actions/multipliers/triggers. This strategy does implement new spell
hooks, but I don't think that is problematic for performance, and I'll
explain why they are necessary in the next post.


## How to Test the Changes
<!--
- Step-by-step instructions to test the change.
- Any required setup (e.g. multiple players, number of bots, specific
configuration).
- Expected behavior and how to verify it.
-->



## Impact Assessment
<!-- As a generic test, before and after measure of pmon (playerbot pmon
tick) can help you here. -->
- Does this change increase per-bot/per-tick processing or risk scaling
poorly with thousands of bots?
    - - [ ] No, not at all
    - - [x] Minimal impact (**explain below**)
    - - [ ] Moderate impact (**explain below**)

There are many new triggers, multipliers, and actions, but they will be
evaluated only if the "hyjal" strategy is added. Additionally, I've
attempted to order and implement checks in a manner to limit performance
impact and have tested with .pmon active. In general, I consider
performance to be highly important so am always working on ways to limit
the impact (e.g., trying to use the most targeted grid searches
available when needed).


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

Only in the Hyjal instance, for obvious reasons.


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

New decision branches apply only in the new Hyjal instance, for obvious
reasons. Maintenance complexity should not be increased as code outside
of the new Hyjal files is not impacted, except to the extent needed to
register and implement the strategy in the same manner as all existing
strategies. Exception: As noted, I did add new spell hooks, but if they
become problematic, they can easily be removed.


## Messages to Translate
<!--
Bot messages have to be translatable, but you don't need to do the
translations here. You only need to make sure
the message is in a translatable format, and list in the table the
message_key and the default English message.
Search for GetBotTextOrDefault in the codebase for examples.
-->
- Does this change add bot messages to translate?
    - - [x] No
    - - [ ] Yes (**list messages in the table**)

| Message key  | Default message |
| --------------- | ------------------ |
|			 |			      |
|			 |			      |

## AI Assistance
<!--
AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
We expect contributors to be honest about what they do and do not
understand.
-->
- Was AI assistance used while working on this change?
    - - [ ] No
    - - [x] Yes (**explain below**)
<!--
If yes, please specify:
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation).
- Which parts of the change were influenced or generated, and whether it
was thoroughly reviewed.
-->

Claude Sonnet 4.6 was used for more complex implementation and
occasionally GPT-5 mini was used for simple questions. I do not use AI
for brainstorming or developing strategies, only for implementation and
review of code. Most of this was written by me directly, but most
notably I needed AI support to implement the spell hooks and
triggers/actions that relied on them. Everything was reviewed and tested
many times.


## Final Checklist

- - [x] Stability is not compromised.
- - [x] Performance impact is understood, tested, and acceptable.
Caveat: The full impact of implementing the spell hooks on a broad scale
is beyond my knowledge to evaluate.
- - [x] Added logic complexity is justified and explained.
- - [x] Documentation updated if needed (Conf comments, WiKi commands).

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->

---------

Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com>
Co-authored-by: bash <hermensb@gmail.com>
Co-authored-by: Revision <tkn963@gmail.com>
Co-authored-by: kadeshar <kadeshar@gmail.com>
2026-05-08 22:39:32 -07:00
Keleborn
38caa1daa7
Randombots respect realm PVP setting (#2342)
<!--
Thank you for contributing to mod-playerbots, please make sure that
you...
1. Submit your PR to the test-staging branch, not master.
2. Read the guidelines below before submitting.
3. Don't delete parts of this template.

DESIGN PHILOSOPHY: We prioritize STABILITY, PERFORMANCE, AND
PREDICTABILITY over behavioral realism.

Every action and decision executes PER BOT AND PER TRIGGER. Small
increases in logic complexity scale
poorly across thousands of bots and negatively affect all. We prioritize
a stable system over a smarter
one. Bots don't need to behave perfectly; believable behavior is the
goal, not human simulation.
Default behavior must be cheap in processing; expensive behavior must be
opt-in.

Before submitting, make sure your changes aligns with these principles.
-->

## Pull Request Description
<!-- Describe what this change does and why it is needed -->
Fix an issue where bots would eventually have pvp set by reset. THis
ensures bot pvp states are consistent with realm type.


## Feature Evaluation
<!--
If your PR is very minimal (comment typo, wrong ID reference, etc), and
it is very obvious it will not have
any impact on performance, you may skip these question. If necessary, a
maintainer may ask you for them later.
-->

<!-- Please answer the following: -->
- Describe the **minimum logic** required to achieve the intended
behavior.
- Describe the **processing 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, number of bots, specific
configuration).
- Expected behavior and how to verify it.
-->



## Impact Assessment
<!-- As a generic test, before and after measure of pmon (playerbot pmon
tick) can help you here. -->
- Does this change increase per-bot/per-tick processing or risk scaling
poorly with thousands of bots?
    - - [x] No, not at all
    - - [ ] Minimal impact (**explain below**)
    - - [ ] Moderate impact (**explain below**)



- Does this change modify default bot behavior?
    - - [ ] No
    - - [x] Yes (**explain why**)
Corrects behavior to match server intent. 


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



## AI Assistance
<!--
AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
We expect contributors to be honest about what they do and do not
understand.
-->
Was AI assistance used while working on this change?
- - [ ] No
- - [x] Yes (**explain below**)
<!--
If yes, please specify:
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation).
- Which parts of the change were influenced or generated, and whether it
was thoroughly reviewed.
-->
searching code, writing it. 


<!--
TRANSLATIONS:
Anything new that the bots say in chat must be in a translatable format.
This is done using GetBotTextOrDefault,
which you can search for in the codebase to find examples. Your code
needs to have English as the default fallback,
while the full translations need to be in an SQL update file. The
languages in the file are the nine language
options supported by AzerothCore: English, Korean, French, German,
Chinese, Taiwanese, Spanish, Spanish Mexico, and
Russian. See
data/sql/playerbots/updates/2025_12_27_ai_playerbot_fishing_text.sql as
an example of a translation SQL
update, whose content are called within the codebase at
src/strategy/actions/FishingAction.cpp
-->

## Final Checklist

- - [x] Stability is not compromised.
- - [x] Performance impact is understood, tested, and acceptable.
- - [x] Added logic complexity is justified and explained.
- - [x] Any new bot dialogue lines are translated.
- - [x] Documentation updated if needed (Conf comments, WiKi commands).

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
2026-05-03 21:41:45 +02:00
Keleborn
ccce14238e
Core Update, change to DeserterCheck and signature (#2354)
<!--
Thank you for contributing to mod-playerbots, please make sure that
you...
1. Submit your PR to the test-staging branch, not master.
2. Read the guidelines below before submitting.
3. Don't delete parts of this template.

DESIGN PHILOSOPHY: We prioritize STABILITY, PERFORMANCE, AND
PREDICTABILITY over behavioral realism.

Every action and decision executes PER BOT AND PER TRIGGER. Small
increases in logic complexity scale
poorly across thousands of bots and negatively affect all. We prioritize
a stable system over a smarter
one. Bots don't need to behave perfectly; believable behavior is the
goal, not human simulation.
Default behavior must be cheap in processing; expensive behavior must be
opt-in.

Before submitting, make sure your changes aligns with these principles.
-->

## Pull Request Description
<!-- Describe what this change does and why it is needed -->
Required change for
https://github.com/azerothcore/azerothcore-wotlk/pull/24641


## Feature Evaluation
<!--
If your PR is very minimal (comment typo, wrong ID reference, etc), and
it is very obvious it will not have
any impact on performance, you may skip these question. If necessary, a
maintainer may ask you for them later.
-->

<!-- Please answer the following: -->
- Describe the **minimum logic** required to achieve the intended
behavior.
- Describe the **processing 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, number of bots, specific
configuration).
- Expected behavior and how to verify it.
-->



## Impact Assessment
<!-- As a generic test, before and after measure of pmon (playerbot pmon
tick) can help you here. -->
- Does this change increase per-bot/per-tick processing or risk scaling
poorly with thousands of bots?
    - - [ ] No, not at all
    - - [ ] Minimal impact (**explain below**)
    - - [ ] Moderate impact (**explain below**)



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



- Does this change add new decision branches or increase maintenance
complexity?
    - - [ ] No
    - - [ ] Yes (**explain below**)



## AI Assistance
<!--
AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
We expect contributors to be honest about what they do and do not
understand.
-->
Was AI assistance used while working on this change?
- - [ ] No
- - [ ] Yes (**explain below**)
<!--
If yes, please specify:
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation).
- Which parts of the change were influenced or generated, and whether it
was thoroughly reviewed.
-->



<!--
TRANSLATIONS:
Anything new that the bots say in chat must be in a translatable format.
This is done using GetBotTextOrDefault,
which you can search for in the codebase to find examples. Your code
needs to have English as the default fallback,
while the full translations need to be in an SQL update file. The
languages in the file are the nine language
options supported by AzerothCore: English, Korean, French, German,
Chinese, Taiwanese, Spanish, Spanish Mexico, and
Russian. See
data/sql/playerbots/updates/2025_12_27_ai_playerbot_fishing_text.sql as
an example of a translation SQL
update, whose content are called within the codebase at
src/strategy/actions/FishingAction.cpp
-->

## Final Checklist

- - [ ] Stability is not compromised.
- - [ ] Performance impact is understood, tested, and acceptable.
- - [ ] Added logic complexity is justified and explained.
- - [ ] Any new bot dialogue lines are translated.
- - [ ] Documentation updated if needed (Conf comments, WiKi commands).

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
2026-05-03 07:16:34 -07:00
Crow
104a1b9ee1
Clean up unnecessary includes in raid strategy and trigger-context headers (#2347)
<!--
Thank you for contributing to mod-playerbots, please make sure that
you...
1. Submit your PR to the test-staging branch, not master.
2. Read the guidelines below before submitting.
3. Don't delete parts of this template.

DESIGN PHILOSOPHY: We prioritize STABILITY, PERFORMANCE, AND
PREDICTABILITY over behavioral realism.

Every action and decision executes PER BOT AND PER TRIGGER. Small
increases in logic complexity scale
poorly across thousands of bots and negatively affect all. We prioritize
a stable system over a smarter
one. Bots don't need to behave perfectly; believable behavior is the
goal, not human simulation.
Default behavior must be cheap in processing; expensive behavior must be
opt-in.

Before submitting, make sure your changes aligns with these principles.
-->

## Pull Request Description
<!-- Describe what this change does and why it is needed -->
This PR trims redundant includes from raid Strategy.h and
TriggerContext.h headers. I noticed a consistent pattern of including
Multiplier.h when it was not needed in Strategy.h and including
AiObjectContext.h in TriggerContext.h when only the narrower
NamedObjectContext.h is needed (both of which I was guilty of also).
Since we make new raid strategies based on existing raid strategies, I
figure let's go for the low-hanging fruit and just fix this so we stop
doing it wrong going forward.

While I was at it, I removed other unnecessary includes but in those two
files only (across dungeon and raid strategies).

Edit: Made a couple of other minor code cleanups I'd been intending to
do. Notably, we shouldn't be including a .cpp in PlayerbotAI.cpp.

## Feature Evaluation
<!--
If your PR is very minimal (comment typo, wrong ID reference, etc), and
it is very obvious it will not have
any impact on performance, you may skip these question. If necessary, a
maintainer may ask you for them later.
-->

<!-- Please answer the following: -->
- Describe the **minimum logic** required to achieve the intended
behavior.
- Describe the **processing 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, number of bots, specific
configuration).
- Expected behavior and how to verify it.
-->



## Impact Assessment
<!-- As a generic test, before and after measure of pmon (playerbot pmon
tick) can help you here. -->
- Does this change increase per-bot/per-tick processing or risk scaling
poorly with thousands of bots?
    - - [x] No, not at all
    - - [ ] Minimal impact (**explain below**)
    - - [ ] Moderate impact (**explain below**)



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



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



## AI Assistance
<!--
AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
We expect contributors to be honest about what they do and do not
understand.
-->
Was AI assistance used while working on this change?
- - [ ] No
- - [x] Yes (**explain below**)
<!--
If yes, please specify:
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation).
- Which parts of the change were influenced or generated, and whether it
was thoroughly reviewed.
-->

I had GPT-5.4 do the actual work because doing it myself file-by-file
would've been such a snoozefest.

<!--
TRANSLATIONS:
Anything new that the bots say in chat must be in a translatable format.
This is done using GetBotTextOrDefault,
which you can search for in the codebase to find examples. Your code
needs to have English as the default fallback,
while the full translations need to be in an SQL update file. The
languages in the file are the nine language
options supported by AzerothCore: English, Korean, French, German,
Chinese, Taiwanese, Spanish, Spanish Mexico, and
Russian. See
data/sql/playerbots/updates/2025_12_27_ai_playerbot_fishing_text.sql as
an example of a translation SQL
update, whose content are called within the codebase at
src/strategy/actions/FishingAction.cpp
-->

## Final Checklist

- - [x] Stability is not compromised.
- - [x] Performance impact is understood, tested, and acceptable.
- - [x] Added logic complexity is justified and explained.
- - [x] Any new bot dialogue lines are translated.
- - [x] Documentation updated if needed (Conf comments, WiKi commands).

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
2026-05-02 12:20:18 -07:00
Crow
94195c3b9b
Bots Don't Autoequip Tools & Other Misc Weapons (#2346)
<!--
Thank you for contributing to mod-playerbots, please make sure that
you...
1. Submit your PR to the test-staging branch, not master.
2. Read the guidelines below before submitting.
3. Don't delete parts of this template.

DESIGN PHILOSOPHY: We prioritize STABILITY, PERFORMANCE, AND
PREDICTABILITY over behavioral realism.

Every action and decision executes PER BOT AND PER TRIGGER. Small
increases in logic complexity scale
poorly across thousands of bots and negatively affect all. We prioritize
a stable system over a smarter
one. Bots don't need to behave perfectly; believable behavior is the
goal, not human simulation.
Default behavior must be cheap in processing; expensive behavior must be
opt-in.

Before submitting, make sure your changes aligns with these principles.
-->

## Pull Request Description
<!-- Describe what this change does and why it is needed -->
Solve the rest of #2344

Now, bots won't autoequip any weapon from ITEM_SUBCLASS_WEAPON_MISC,
which includes all of the basic tools and some other crap that they have
no need to autoequip, either. Bots are still eligible to equip those
weapons (such as through the "e" command).

Note that MISC includes the Argent Tournament lances. I've not played
WotLK, but I assume those might be relevant for a strategy. It shouldn't
be a problem though because I've intentionally not made bots ineligible
for MISC weapons; they just won't consider them upgrades on their own.

I also cleaned up ItemUsageValue::QueryItemUsageForEquip to consolidate
checks and so on. None of that should be functional, or I screwed up.
The check for MISC is on lines 219 through 221.

## Feature Evaluation
<!--
If your PR is very minimal (comment typo, wrong ID reference, etc), and
it is very obvious it will not have
any impact on performance, you may skip these question. If necessary, a
maintainer may ask you for them later.
-->

<!-- Please answer the following: -->
- Describe the **minimum logic** required to achieve the intended
behavior.
- Describe the **processing 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, number of bots, specific
configuration).
- Expected behavior and how to verify it.
-->
Activate selfbot. Unequip all weapons and have nothing in the inventory
except for a MISC weapon such as a skinning knife. Whisper self "equip
upgrade"--nothing should happen. Whisper self "e [LINK TO WEAPON]"--the
bot should equip the weapon.


## Impact Assessment
<!-- As a generic test, before and after measure of pmon (playerbot pmon
tick) can help you here. -->
- Does this change increase per-bot/per-tick processing or risk scaling
poorly with thousands of bots?
    - - [ ] No, not at all
    - - [x] Minimal impact (**explain below**)
    - - [ ] Moderate impact (**explain below**)

There's an extra check but totally meaningless with respect to
performance.

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

They won't auto-equip crap that will prevent them from using abilities.

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



## AI Assistance
<!--
AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
We expect contributors to be honest about what they do and do not
understand.
-->
Was AI assistance used while working on this change?
- - [ ] No
- - [x] Yes (**explain below**)
<!--
If yes, please specify:
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation).
- Which parts of the change were influenced or generated, and whether it
was thoroughly reviewed.
-->

I had GPT-5.4 evaluate different spots where I thought an exclusion
could be added before settling on this one.

<!--
TRANSLATIONS:
Anything new that the bots say in chat must be in a translatable format.
This is done using GetBotTextOrDefault,
which you can search for in the codebase to find examples. Your code
needs to have English as the default fallback,
while the full translations need to be in an SQL update file. The
languages in the file are the nine language
options supported by AzerothCore: English, Korean, French, German,
Chinese, Taiwanese, Spanish, Spanish Mexico, and
Russian. See
data/sql/playerbots/updates/2025_12_27_ai_playerbot_fishing_text.sql as
an example of a translation SQL
update, whose content are called within the codebase at
src/strategy/actions/FishingAction.cpp
-->

## Final Checklist

- - [x] Stability is not compromised.
- - [x] Performance impact is understood, tested, and acceptable.
- - [x] Added logic complexity is justified and explained.
- - [x] Any new bot dialogue lines are translated.
- - [x] Documentation updated if needed (Conf comments, WiKi commands).

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
2026-05-02 12:20:03 -07:00
kadeshar
063eabc16e
Spam guild fix (#2341)
<!--
Thank you for contributing to mod-playerbots, please make sure that
you...
1. Submit your PR to the test-staging branch, not master.
2. Read the guidelines below before submitting.
3. Don't delete parts of this template.

DESIGN PHILOSOPHY: We prioritize STABILITY, PERFORMANCE, AND
PREDICTABILITY over behavioral realism.

Every action and decision executes PER BOT AND PER TRIGGER. Small
increases in logic complexity scale
poorly across thousands of bots and negatively affect all. We prioritize
a stable system over a smarter
one. Bots don't need to behave perfectly; believable behavior is the
goal, not human simulation.
Default behavior must be cheap in processing; expensive behavior must be
opt-in.

Before submitting, make sure your changes aligns with these principles.
-->

## Pull Request Description
<!-- Describe what this change does and why it is needed -->

Removed messages in failed attempts of buying tabard.
Related with: #1885 

## How to Test the Changes
<!--
- Step-by-step instructions to test the change.
- Any required setup (e.g. multiple players, number of bots, specific
configuration).
- Expected behavior and how to verify it.
-->

Invite bot with guild strategy. Spam should not appear.

## Impact Assessment
<!-- As a generic test, before and after measure of pmon (playerbot pmon
tick) can help you here. -->
- Does this change increase per-bot/per-tick processing or risk scaling
poorly with thousands of bots?
    - - [x] No, not at all
    - - [ ] Minimal impact (**explain below**)
    - - [ ] Moderate impact (**explain below**)



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



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



## AI Assistance
<!--
AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
We expect contributors to be honest about what they do and do not
understand.
-->
Was AI assistance used while working on this change?
- - [x] No
- - [ ] Yes (**explain below**)
<!--
If yes, please specify:
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation).
- Which parts of the change were influenced or generated, and whether it
was thoroughly reviewed.
-->



<!--
TRANSLATIONS:
Anything new that the bots say in chat must be in a translatable format.
This is done using GetBotTextOrDefault,
which you can search for in the codebase to find examples. Your code
needs to have English as the default fallback,
while the full translations need to be in an SQL update file. The
languages in the file are the nine language
options supported by AzerothCore: English, Korean, French, German,
Chinese, Taiwanese, Spanish, Spanish Mexico, and
Russian. See
data/sql/playerbots/updates/2025_12_27_ai_playerbot_fishing_text.sql as
an example of a translation SQL
update, whose content are called within the codebase at
src/strategy/actions/FishingAction.cpp
-->

## Final Checklist

- - [x] Stability is not compromised.
- - [x] Performance impact is understood, tested, and acceptable.
- - [x] Added logic complexity is justified and explained.
- - [x] Any new bot dialogue lines are translated.
- - [x] Documentation updated if needed (Conf comments, WiKi commands).

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
2026-05-02 12:19:51 -07:00
kadeshar
cc6f6c2c3a
Thorns reapply fix (#2338)
<!--
Thank you for contributing to mod-playerbots, please make sure that
you...
1. Submit your PR to the test-staging branch, not master.
2. Read the guidelines below before submitting.
3. Don't delete parts of this template.

DESIGN PHILOSOPHY: We prioritize STABILITY, PERFORMANCE, AND
PREDICTABILITY over behavioral realism.

Every action and decision executes PER BOT AND PER TRIGGER. Small
increases in logic complexity scale
poorly across thousands of bots and negatively affect all. We prioritize
a stable system over a smarter
one. Bots don't need to behave perfectly; believable behavior is the
goal, not human simulation.
Default behavior must be cheap in processing; expensive behavior must be
opt-in.

Before submitting, make sure your changes aligns with these principles.
-->

## Pull Request Description
<!-- Describe what this change does and why it is needed -->

Allowed druid cast Thorns on target which already have Thorns on him to
extend duration.
Related with: #2290 

## How to Test the Changes
<!--
- Step-by-step instructions to test the change.
- Any required setup (e.g. multiple players, number of bots, specific
configuration).
- Expected behavior and how to verify it.
-->

1. Invite 2 bot (one of them must be druid which can cast thorns)
2. Select second bot and use commad `cast thorns`
3. Wait until buff timer decrease
4. Use again same command
5. Druid should cast spell

## Impact Assessment
<!-- As a generic test, before and after measure of pmon (playerbot pmon
tick) can help you here. -->
- Does this change increase per-bot/per-tick processing or risk scaling
poorly with thousands of bots?
    - - [x] No, not at all
    - - [ ] Minimal impact (**explain below**)
    - - [ ] Moderate impact (**explain below**)



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



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



## AI Assistance
<!--
AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
We expect contributors to be honest about what they do and do not
understand.
-->
Was AI assistance used while working on this change?
- - [ ] No
- - [x] Yes (**explain below**)
<!--
If yes, please specify:
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation).
- Which parts of the change were influenced or generated, and whether it
was thoroughly reviewed.
-->

To understand reason.

<!--
TRANSLATIONS:
Anything new that the bots say in chat must be in a translatable format.
This is done using GetBotTextOrDefault,
which you can search for in the codebase to find examples. Your code
needs to have English as the default fallback,
while the full translations need to be in an SQL update file. The
languages in the file are the nine language
options supported by AzerothCore: English, Korean, French, German,
Chinese, Taiwanese, Spanish, Spanish Mexico, and
Russian. See
data/sql/playerbots/updates/2025_12_27_ai_playerbot_fishing_text.sql as
an example of a translation SQL
update, whose content are called within the codebase at
src/strategy/actions/FishingAction.cpp
-->

## Final Checklist

- - [x] Stability is not compromised.
- - [x] Performance impact is understood, tested, and acceptable.
- - [x] Added logic complexity is justified and explained.
- - [x] Any new bot dialogue lines are translated.
- - [x] Documentation updated if needed (Conf comments, WiKi commands).

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
2026-05-02 12:19:37 -07:00
Keleborn
c819516325
Fix rpg travel flying (#2324)
<!--
Thank you for contributing to mod-playerbots, please make sure that
you...
1. Submit your PR to the test-staging branch, not master.
2. Read the guidelines below before submitting.
3. Don't delete parts of this template.

DESIGN PHILOSOPHY: We prioritize STABILITY, PERFORMANCE, AND
PREDICTABILITY over behavioral realism.

Every action and decision executes PER BOT AND PER TRIGGER. Small
increases in logic complexity scale
poorly across thousands of bots and negatively affect all. We prioritize
a stable system over a smarter
one. Bots don't need to behave perfectly; believable behavior is the
goal, not human simulation.
Default behavior must be cheap in processing; expensive behavior must be
opt-in.

Before submitting, make sure your changes aligns with these principles.
-->

## Pull Request Description
<!-- Describe what this change does and why it is needed -->
Clean up values that were incorrectly translated from the sql search
into the dbc search.
Refactors structure for cities in TravelMgr to try to resolve some
duplication issues.
Change to position based search, so that bots dont get stuck if they
fail to resolve the flightmaster game object when it hasnt spawned.
TravelFlight state now stores flight master entry + world position
instead of ObjectGuid, so the bot can move back into range and
re-resolve the NPC locally via FindNearestCreature
Bundles reliability cleanup in NewRpgTravelFlightAction: uses
info.ChangeToIdle() consistently and adds the missing return true after
a failed taxi path

## Feature Evaluation
<!--
If your PR is very minimal (comment typo, wrong ID reference, etc), and
it is very obvious it will not have
any impact on performance, you may skip these question. If necessary, a
maintainer may ask you for them later.
-->

<!-- Please answer the following: -->
- Describe the **minimum logic** required to achieve the intended
behavior.
- Describe the **processing cost** when this logic executes across many
bots.

No expected shanges. 

## How to Test the Changes
<!--
- Step-by-step instructions to test the change.
- Any required setup (e.g. multiple players, number of bots, specific
configuration).
- Expected behavior and how to verify it.
-->

Run the server and check if zones are getting populated well. 

## Impact Assessment
<!-- As a generic test, before and after measure of pmon (playerbot pmon
tick) can help you here. -->
- Does this change increase per-bot/per-tick processing or risk scaling
poorly with thousands of bots?
    - - [x] No, not at all
    - - [ ] Minimal impact (**explain below**)
    - - [ ] Moderate impact (**explain below**)

Run with 4k bots, no issues. 

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

It should correctly send bots to the areas appropriate for their level
in an equally weighted manner.


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



## AI Assistance
<!--
AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
We expect contributors to be honest about what they do and do not
understand.
-->
Was AI assistance used while working on this change?
- - [ ] No
- - x ] Yes (**explain below**)
<!--
If yes, please specify:
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation).
- Which parts of the change were influenced or generated, and whether it
was thoroughly reviewed.
-->
Refactoring the data structure based on my instruction. 
All parts reviewed. 


<!--
TRANSLATIONS:
Anything new that the bots say in chat must be in a translatable format.
This is done using GetBotTextOrDefault,
which you can search for in the codebase to find examples. Your code
needs to have English as the default fallback,
while the full translations need to be in an SQL update file. The
languages in the file are the nine language
options supported by AzerothCore: English, Korean, French, German,
Chinese, Taiwanese, Spanish, Spanish Mexico, and
Russian. See
data/sql/playerbots/updates/2025_12_27_ai_playerbot_fishing_text.sql as
an example of a translation SQL
update, whose content are called within the codebase at
src/strategy/actions/FishingAction.cpp
-->

## Final Checklist

- - [x] Stability is not compromised.
- - [x] Performance impact is understood, tested, and acceptable.
- - [x] Added logic complexity is justified and explained.
- - [x] Any new bot dialogue lines are translated.
- - [x] Documentation updated if needed (Conf comments, WiKi commands).

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
2026-05-02 12:19:23 -07:00
HennyWilly
410ce134fe
Fix Deep Breath issues during Onyxia encounter (#2318)
<!--
Thank you for contributing to mod-playerbots, please make sure that
you...
1. Submit your PR to the test-staging branch, not master.
2. Read the guidelines below before submitting.
3. Don't delete parts of this template.

DESIGN PHILOSOPHY: We prioritize STABILITY, PERFORMANCE, AND
PREDICTABILITY over behavioral realism.

Every action and decision executes PER BOT AND PER TRIGGER. Small
increases in logic complexity scale
poorly across thousands of bots and negatively affect all. We prioritize
a stable system over a smarter
one. Bots don't need to behave perfectly; believable behavior is the
goal, not human simulation.
Default behavior must be cheap in processing; expensive behavior must be
opt-in.

Before submitting, make sure your changes aligns with these principles.
-->

## Pull Request Description
<!-- Describe what this change does and why it is needed -->
The current strategy for Onyxia causes bots to get hit by her breath
attack relatively consistently during phase 2.
The problem was that the safe zone coordinates always use the bot's
z-coordinate. If the bots are standing at the lower altitude part of the
arena, `SearchForBestPath` inside `MoveTo` causes `INVALID_HEIGHT`,
resulting in the bot not moving at all and getting hit by the breath
attack.

This PR fixes this behavior by using the actual terrain z-coordinates
for the predefined safe zones instead of always using the bot's
z-coordinate. These values are chosen to ensure valid pathfinding
regardless of the bot's current elevation.
Additionally, bots now interrupt their spells if they are not inside a
safe zone during the breath. This causes the bots to immediately start
running instead of finishing their casts first.

## Feature Evaluation
<!--
If your PR is very minimal (comment typo, wrong ID reference, etc), and
it is very obvious it will not have
any impact on performance, you may skip these question. If necessary, a
maintainer may ask you for them later.
-->

<!-- Please answer the following: -->
- Describe the **minimum logic** required to achieve the intended
behavior.

Replaced the use of `bot->GetPositionZ()` in `GetSafeZonesForBreath`
with predefined safe zone z-coordinates to ensure valid pathfinding.
Added `AttackStop` and `InterruptNonMeleeSpells` to guarantee immediate
movement when outside safe zones.
No additional condition checks or branching logic were introduced.

- Describe the **processing cost** when this logic executes across many
bots.

Minimal. The logic only runs within the Onyxia encounter script and
calling `AttackStop` and `InterruptNonMeleeSpells` should be negligible.


## How to Test the Changes
<!--
- Step-by-step instructions to test the change.
- Any required setup (e.g. multiple players, number of bots, specific
configuration).
- Expected behavior and how to verify it.
-->
Enter Onyxia's Lair (10, 25 or 40 (mod-individual-progression)) and
engage Onyxia with the appropriate number of bots.
During phase 2 (Onyxia takes off), check if the bots move to the safe
zones during the breath attack.
Tip: Mark Onyxia as moon (RTI), so that phase 2 doesn't end too quickly.

## Impact Assessment
<!-- As a generic test, before and after measure of pmon (playerbot pmon
tick) can help you here. -->
- Does this change increase per-bot/per-tick processing or risk scaling
poorly with thousands of bots?
    - - [ ] No, not at all
    - - [x] Minimal impact (**explain below**)
    - - [ ] Moderate impact (**explain below**)

The calls of `AttackStop` and `InterruptNonMeleeSpells` cause minimal
overhead compared to the original strategy. This should be negligible.

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

Yes (encounter-specific). Bots will now interrupt casts earlier during
Onyxia phase 2 to prioritize movement to safe zones.

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

## AI Assistance
<!--
AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
We expect contributors to be honest about what they do and do not
understand.
-->
Was AI assistance used while working on this change?
- - [x] No
- - [ ] Yes (**explain below**)
<!--
If yes, please specify:
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation).
- Which parts of the change were influenced or generated, and whether it
was thoroughly reviewed.
-->



<!--
TRANSLATIONS:
Anything new that the bots say in chat must be in a translatable format.
This is done using GetBotTextOrDefault,
which you can search for in the codebase to find examples. Your code
needs to have English as the default fallback,
while the full translations need to be in an SQL update file. The
languages in the file are the nine language
options supported by AzerothCore: English, Korean, French, German,
Chinese, Taiwanese, Spanish, Spanish Mexico, and
Russian. See
data/sql/playerbots/updates/2025_12_27_ai_playerbot_fishing_text.sql as
an example of a translation SQL
update, whose content are called within the codebase at
src/strategy/actions/FishingAction.cpp
-->

## Final Checklist

- - [x] Stability is not compromised.
- - [x] Performance impact is understood, tested, and acceptable.
- - [x] Added logic complexity is justified and explained.
- - [x] Any new bot dialogue lines are translated.
- - [x] Documentation updated if needed (Conf comments, WiKi commands).

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->

The strategy for Onyxia might need additional work:
For example, the Onyxian Lair Guards are completely ignored while whelps
are alive and their Blast Nova doesn't get handled at all.
This PR focuses on fixing the Deep Breath behavior. Handling of Onyxian
Lair Guards is not included and should be implemented in a separate PR.
2026-05-02 12:19:11 -07:00
Alex Dcnh
4a79a46da5
Add argument "all" to "rep" command and new "emblems" command (#2035)
## Summary
- restrict `reputation all` to a curated list of WotLK/BC/Classic
faction IDs (filtered by team)
- reuse a shared formatter for reputation lines
- add an `emblems` chat command to report emblem counts

### Multibot will need a update

<img width="420" height="131" alt="image"
src="https://github.com/user-attachments/assets/bedf9dd8-e8de-465f-96d0-f9c2f1dacfc1"
/>

<img width="601" height="623" alt="image"
src="https://github.com/user-attachments/assets/1edde264-baed-4cfb-a401-208bea189139"
/>

<img width="670" height="661" alt="image"
src="https://github.com/user-attachments/assets/a70e2174-dd1d-4e14-b6e4-2938c26ccb29"
/>

<img width="650" height="48" alt="image"
src="https://github.com/user-attachments/assets/241e332a-23ce-4d81-be53-4d83e10d246a"
/>

---------

Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com>
Co-authored-by: bash <hermensb@gmail.com>
Co-authored-by: Revision <tkn963@gmail.com>
Co-authored-by: kadeshar <kadeshar@gmail.com>
2026-05-02 12:18:54 -07:00
82 changed files with 4249 additions and 263 deletions

View File

@ -32,7 +32,7 @@
# LEVELS
# GEAR
# QUESTS
# ACTIVITIES
# ACTIVITY
# SPELLS
# STRATEGIES
# RPG STRATEGY
@ -2242,6 +2242,14 @@ AiPlayerbot.CommandPrefix = ""
# Separator for bot chat commands
AiPlayerbot.CommandSeparator = "\\\\"
# Enable automatic item count/trade trigger when a chat message contains
# item-related keywords. When enabled (1), mentioning items in chat
# (e.g. "food", "potion", "ammo") will automatically show inventory and
# open a trade window with the bot. Explicit "c" and "t" commands still
# work regardless of this setting.
# Default: 1 (enabled)
AiPlayerbot.EnableAutoTradeOnItemMention = 1
# Enable bots talking (say / yell / general chatting / lfg)
AiPlayerbot.RandomBotTalk = 1
# Enable bots emoting

View File

@ -343,7 +343,7 @@ bool BGJoinAction::isUseful()
return false;
// check Deserter debuff
if (!bot->CanJoinToBattleground())
if (bot->IsDeserter())
return false;
// check if has free queue slots (pointless as already making sure not in queue)

View File

@ -213,13 +213,7 @@ bool BuyAction::Execute(Event event)
}
}
if (!vendored)
{
botAI->TellError("There are no vendors nearby");
return false;
}
return true;
return vendored;
}
bool BuyAction::BuyItem(VendorItemData const* tItems, ObjectGuid vendorguid, ItemTemplate const* proto)

View File

@ -143,14 +143,17 @@ bool CastCustomSpellAction::Execute(Event event)
std::ostringstream spellName;
spellName << ChatHelper::FormatSpell(spellInfo) << " on ";
bool const hasItemTarget = itemTarget &&
(spellInfo->Targets & TARGET_FLAG_ITEM || spellInfo->Targets & TARGET_FLAG_GAMEOBJECT_ITEM);
if (bot->GetTrader())
spellName << "trade item";
else if (itemTarget)
else if (hasItemTarget)
spellName << chat->FormatItem(itemTarget->GetTemplate());
else if (target == bot)
spellName << "self";
else
else if (target != bot)
spellName << target->GetName();
else
spellName << "self";
if (!bot->GetTrader() && !botAI->CanCastSpell(spell, target, true, itemTarget))
{

View File

@ -9,11 +9,8 @@
#include "PlayerbotRepository.h"
#include "Playerbots.h"
bool ChangeCombatStrategyAction::Execute(Event event)
{
std::string const text = event.getParam();
botAI->ChangeStrategy(text.empty() ? getName() : text, BOT_STATE_COMBAT);
if (event.GetSource() == "co")
// Helper function for prefixes used by combat and non-combat strategy commands.
static void HandleStrategyCommon(PlayerbotAI* botAI, std::string const& text, BotState state)
{
std::vector<std::string> splitted = split(text, ',');
for (std::vector<std::string>::iterator i = splitted.begin(); i != splitted.end(); i++)
@ -26,12 +23,23 @@ bool ChangeCombatStrategyAction::Execute(Event event)
case '~':
PlayerbotRepository::instance().Save(botAI);
break;
case '!':
botAI->SelectiveResetStrategies(state);
PlayerbotRepository::instance().Save(botAI);
break;
case '?':
break;
}
}
}
bool ChangeCombatStrategyAction::Execute(Event event)
{
std::string const text = event.getParam();
botAI->ChangeStrategy(text.empty() ? getName() : text, BOT_STATE_COMBAT);
if (event.GetSource() == "co")
HandleStrategyCommon(botAI, text, BOT_STATE_COMBAT);
return true;
}
@ -52,23 +60,7 @@ bool ChangeNonCombatStrategyAction::Execute(Event event)
botAI->ChangeStrategy(text, BOT_STATE_NON_COMBAT);
if (event.GetSource() == "nc")
{
std::vector<std::string> splitted = split(text, ',');
for (std::vector<std::string>::iterator i = splitted.begin(); i != splitted.end(); i++)
{
const char* name = i->c_str();
switch (name[0])
{
case '+':
case '-':
case '~':
PlayerbotRepository::instance().Save(botAI);
break;
case '?':
break;
}
}
}
HandleStrategyCommon(botAI, text, BOT_STATE_NON_COMBAT);
return true;
}

View File

@ -4,9 +4,11 @@
*/
#include "CheckMountStateAction.h"
#include "AreaDefines.h"
#include "BattleGroundTactics.h"
#include "BattlegroundEY.h"
#include "BattlegroundWS.h"
#include "DBCStores.h"
#include "Event.h"
#include "PlayerbotAI.h"
#include "PlayerbotAIConfig.h"
@ -14,6 +16,8 @@
#include "ServerFacade.h"
#include "SpellAuraEffects.h"
static constexpr uint32 SPELL_COLD_WEATHER_FLYING = 54197;
// Define the static map / init bool for caching bot preferred mount data globally
std::unordered_map<uint32, PreferredMountCache> CheckMountStateAction::mountCache;
bool CheckMountStateAction::preferredMountTableChecked = false;
@ -94,9 +98,10 @@ bool CheckMountStateAction::Execute(Event /*event*/)
}
bool inBattleground = bot->InBattleground();
bool const noRealMaster = (!master || master == bot);
// If there is a master and bot not in BG, follow master's mount state regardless of group leader
if (master && !inBattleground)
if (!noRealMaster && !inBattleground)
{
if (ShouldFollowMasterMountState(master, noAttackers, shouldMount))
return Mount();
@ -110,8 +115,8 @@ bool CheckMountStateAction::Execute(Event /*event*/)
return false;
}
// If there is no master or bot in BG
if ((!master || inBattleground) && !bot->IsMounted() &&
// No real master (random bot or self-bot) OR bot in BG
if ((noRealMaster || inBattleground) && !bot->IsMounted() &&
noAttackers && shouldMount && !bot->IsInCombat())
return Mount();
@ -228,6 +233,39 @@ void CheckMountStateAction::Dismount()
WorldPacket emptyPacket;
bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket);
bool const wantsFly = bot->HasIncreaseMountedFlightSpeedAura() || bot->HasFlyAura();
bool const isWaterWalking = bot->HasUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
bool const isFlying = bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING);
bool const hasGravityDisabled = bot->HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
if (!wantsFly && !isWaterWalking && (isFlying || hasGravityDisabled))
{
bot->RemoveUnitMovementFlag(
MOVEMENTFLAG_FLYING | MOVEMENTFLAG_CAN_FLY | MOVEMENTFLAG_DISABLE_GRAVITY);
if (!bot->IsRooted())
bot->SendMovementFlagUpdate();
}
}
void CheckMountStateAction::CompleteDismount(Player* bot)
{
if (!bot || !bot->IsInWorld())
return;
float const x = bot->GetPositionX();
float const y = bot->GetPositionY();
float const startZ = bot->GetPositionZ();
float groundZ = startZ;
bot->UpdateAllowedPositionZ(x, y, groundZ);
bot->GetMotionMaster()->MoveFall();
MovementInfo fallInfo = bot->m_movementInfo;
// Need to set the start of the fall, otherwise the fall may start from too high of a Z and kill the bot.
bot->SetFallInformation(0, startZ);
fallInfo.pos.Relocate(x, y, groundZ);
bot->HandleFall(fallInfo);
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_FALLING | MOVEMENTFLAG_FALLING_FAR);
}
bool CheckMountStateAction::TryForms(Player* master, int32 masterMountType, int32 masterSpeed) const
@ -434,6 +472,24 @@ bool CheckMountStateAction::ShouldDismountForMaster(Player* master) const
return !isMasterMounted && bot->IsMounted();
}
static bool BotCanUseFlyingMount(Player const* bot)
{
if (bot->GetPureSkillValue(SKILL_RIDING) < 225)
return false;
AreaTableEntry const* area = sAreaTableStore.LookupEntry(bot->GetAreaId());
if (!area || !area->IsFlyable())
return false;
if (area->flags & AREA_FLAG_NO_FLY_ZONE)
return false;
uint32 const vmap = GetVirtualMapForMapAndZone(bot->GetMapId(), bot->GetZoneId());
if (vmap == MAP_NORTHREND && !bot->HasSpell(SPELL_COLD_WEATHER_FLYING))
return false;
return true;
}
int32 CheckMountStateAction::CalculateMasterMountSpeed(Player* master, const MountData& mountData) const
{
// Check riding skill and level requirements
@ -443,8 +499,10 @@ int32 CheckMountStateAction::CalculateMasterMountSpeed(Player* master, const Mou
if (ridingSkill <= 75 && botLevel < static_cast<int32>(sPlayerbotAIConfig.useFastGroundMountAtMinLevel))
return 59;
// If there is a master and bot not in BG, use master's aura effects.
if (master && !bot->InBattleground())
// check if bot has master and if master is self
bool const noRealMaster = (!master || master == bot);
if (!noRealMaster && !bot->InBattleground())
{
auto auraEffects = master->GetAuraEffectsByType(SPELL_AURA_MOUNTED);
if (!auraEffects.empty())
@ -458,27 +516,27 @@ int32 CheckMountStateAction::CalculateMasterMountSpeed(Player* master, const Mou
return 279;
else if (masterInShapeshiftForm == FORM_FLIGHT)
return 149;
}
else
{
// Bots on their own.
int32 speed = mountData.maxSpeed;
if (bot->InBattleground() && speed > 99)
return 99;
return speed;
return 59; // walk pace
}
return 59;
// No real master OR battleground: pick speed by skill tier.
if (!bot->InBattleground() && BotCanUseFlyingMount(bot))
return (ridingSkill >= 300) ? 279 : 149;
int32 maxGround = (ridingSkill >= 150) ? 99 : 59;
if (bot->InBattleground() && maxGround > 99)
maxGround = 99;
return maxGround;
}
uint32 CheckMountStateAction::GetMountType(Player* master) const
{
if (!master)
return 0;
bool const noRealMaster = (!master || master == bot);
if (noRealMaster)
return (!bot->InBattleground() && BotCanUseFlyingMount(bot)) ? 1 : 0;
auto auraEffects = master->GetAuraEffectsByType(SPELL_AURA_MOUNTED);
if (!auraEffects.empty())
{
SpellInfo const* masterSpell = auraEffects.front()->GetSpellInfo();

View File

@ -42,6 +42,8 @@ public:
bool isPossible() override { return true; }
bool Mount();
static void CompleteDismount(Player* bot);
private:
Player* master;
ShapeshiftForm masterInShapeshiftForm;

View File

@ -17,7 +17,7 @@
#include "WorldPacket.h"
#include "Group.h"
#include "Chat.h"
#include "Ai/Base/Util/GenericBuffUtils.h"
#include "GenericBuffUtils.h"
#include "PlayerbotAI.h"
using ai::buff::MakeAuraQualifierForBuff;
@ -134,7 +134,8 @@ bool CastSpellAction::isPossible()
return botAI->CanCastSpell(spell, GetTarget());
}
CastMeleeSpellAction::CastMeleeSpellAction(PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell)
CastMeleeSpellAction::CastMeleeSpellAction(
PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell)
{
range = ATTACK_DISTANCE;
}
@ -182,56 +183,47 @@ bool CastAuraSpellAction::isUseful()
return false;
}
CastEnchantItemAction::CastEnchantItemAction(PlayerbotAI* botAI, std::string const spell)
: CastSpellAction(botAI, spell)
CastEnchantItemMainHandAction::CastEnchantItemMainHandAction(
PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell) {}
bool CastEnchantItemMainHandAction::Execute(Event /*event*/)
{
range = botAI->GetRange("spell");
Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
return item && botAI->CastSpell(spell, bot, item);
}
bool CastEnchantItemAction::isPossible()
{
// if (!CastSpellAction::isPossible())
// {
// botAI->TellMasterNoFacing("Impossible: " + spell);
// return false;
// }
uint32 spellId = AI_VALUE2(uint32, "spell id", spell);
// bool ok = AI_VALUE2(Item*, "item for spell", spellId);
// Item* item = AI_VALUE2(Item*, "item for spell", spellId);
// botAI->TellMasterNoFacing("spell: " + spell + ", spell id: " + std::to_string(spellId) + " item for spell: " +
// std::to_string(ok));
return spellId && AI_VALUE2(Item*, "item for spell", spellId);
}
CastEnchantItemMainHandAction::CastEnchantItemMainHandAction(PlayerbotAI* botAI, std::string const spell)
: CastEnchantItemAction(botAI, spell) {}
bool CastEnchantItemMainHandAction::isPossible()
{
if (!CastEnchantItemAction::isPossible())
return false;
Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
return item && !item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) &&
item->GetTemplate()->Class == ITEM_CLASS_WEAPON;
if (!item || item->GetTemplate()->SubClass == ITEM_SUBCLASS_WEAPON_MISC ||
item->GetTemplate()->SubClass == ITEM_SUBCLASS_WEAPON_FISHING_POLE ||
item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT))
{
return false;
}
CastEnchantItemOffHandAction::CastEnchantItemOffHandAction(PlayerbotAI* botAI, std::string const spell)
: CastEnchantItemAction(botAI, spell) {}
return botAI->CanCastSpell(spell, bot, item);
}
CastEnchantItemOffHandAction::CastEnchantItemOffHandAction(
PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell) {}
bool CastEnchantItemOffHandAction::Execute(Event /*event*/)
{
Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
return item && botAI->CastSpell(spell, bot, item);
}
bool CastEnchantItemOffHandAction::isPossible()
{
if (!CastEnchantItemAction::isPossible())
return false;
Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
if (!item || item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT))
if (!item || item->GetTemplate()->SubClass == ITEM_SUBCLASS_WEAPON_MISC ||
item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT))
{
return false;
}
uint32 invType = item->GetTemplate()->InventoryType;
return invType == INVTYPE_WEAPON || invType == INVTYPE_WEAPONOFFHAND;
return botAI->CanCastSpell(spell, bot, item);
}
CastHealingSpellAction::CastHealingSpellAction(PlayerbotAI* botAI, std::string const spell, uint8 estAmount,
@ -245,7 +237,8 @@ bool CastHealingSpellAction::isUseful() { return CastAuraSpellAction::isUseful()
bool CastAoeHealSpellAction::isUseful() { return CastSpellAction::isUseful(); }
CastCureSpellAction::CastCureSpellAction(PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell)
CastCureSpellAction::CastCureSpellAction(
PlayerbotAI* botAI, std::string const spell) : CastSpellAction(botAI, spell)
{
range = botAI->GetRange("heal");
}
@ -267,13 +260,15 @@ bool BuffOnPartyAction::Execute(Event /*event*/)
std::string castName = spell; // default = mono
auto SendGroupRP = ai::chat::MakeGroupAnnouncer(bot);
castName = ai::buff::UpgradeToGroupIfAppropriate(bot, botAI, castName, /*announceOnMissing=*/true, SendGroupRP);
castName = ai::buff::UpgradeToGroupIfAppropriate(
bot, botAI, castName, /*announceOnMissing=*/true, SendGroupRP);
return botAI->CastSpell(castName, GetTarget());
}
// End greater buff fix
CastShootAction::CastShootAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "shoot"), shootSpellId(0)
CastShootAction::CastShootAction(
PlayerbotAI* botAI) : CastSpellAction(botAI, "shoot"), shootSpellId(0)
{
if (Item* const pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED))
{
@ -327,7 +322,8 @@ Value<Unit*>* CastDebuffSpellOnMeleeAttackerAction::GetTargetValue()
return context->GetValue<Unit*>("melee attacker without aura", spell);
}
CastBuffSpellAction::CastBuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool checkIsOwner, uint32 beforeDuration)
CastBuffSpellAction::CastBuffSpellAction(
PlayerbotAI* botAI, std::string const spell, bool checkIsOwner, uint32 beforeDuration)
: CastAuraSpellAction(botAI, spell, checkIsOwner, false, beforeDuration)
{
range = botAI->GetRange("spell");
@ -448,7 +444,8 @@ bool UseTrinketAction::UseTrinket(Item* item)
uint32 spellId = 0;
for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
{
if (item->GetTemplate()->Spells[i].SpellId > 0 && item->GetTemplate()->Spells[i].SpellTrigger == ITEM_SPELLTRIGGER_ON_USE)
if (item->GetTemplate()->Spells[i].SpellId > 0 &&
item->GetTemplate()->Spells[i].SpellTrigger == ITEM_SPELLTRIGGER_ON_USE)
{
spellId = item->GetTemplate()->Spells[i].SpellId;
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);

View File

@ -121,26 +121,21 @@ public:
std::string const GetTargetName() override { return "self target"; }
};
class CastEnchantItemAction : public CastSpellAction
{
public:
CastEnchantItemAction(PlayerbotAI* botAI, std::string const spell);
bool isPossible() override;
std::string const GetTargetName() override { return "self target"; }
};
class CastEnchantItemMainHandAction : public CastEnchantItemAction
class CastEnchantItemMainHandAction : public CastSpellAction
{
public:
CastEnchantItemMainHandAction(PlayerbotAI* botAI, std::string const spell);
std::string const GetTargetName() override { return "self target"; }
bool Execute(Event event) override;
bool isPossible() override;
};
class CastEnchantItemOffHandAction : public CastEnchantItemAction
class CastEnchantItemOffHandAction : public CastSpellAction
{
public:
CastEnchantItemOffHandAction(PlayerbotAI* botAI, std::string const spell);
std::string const GetTargetName() override { return "self target"; }
bool Execute(Event event) override;
bool isPossible() override;
};

View File

@ -296,7 +296,7 @@ bool PetitionTurnInAction::isUseful()
bool BuyTabardAction::Execute(Event /*event*/)
{
bool canBuy = botAI->DoSpecificAction("buy", Event("buy tabard", "Hitem:5976:"));
bool canBuy = botAI->DoSpecificAction("buy", Event("buy tabard", "Hitem:5976:"), true);
if (canBuy && AI_VALUE2(uint32, "item count", chat->FormatQItem(5976)))
return true;

View File

@ -10,6 +10,31 @@
#include "ItemVisitors.h"
#include "Playerbots.h"
namespace
{
bool isReservedQualifier(std::string const& text)
{
static std::array<std::string_view, 13> const exactQualifiers = {
"ammo",
"conjured drink",
"conjured food",
"conjured water",
"drink",
"food",
"healing potion",
"mount",
"mana potion",
"pet",
"quest",
"recipe",
"water"
};
return std::find(exactQualifiers.begin(), exactQualifiers.end(), text) != exactQualifiers.end() ||
text.rfind("usage ", 0) == 0;
}
}
void InventoryAction::IterateItems(IterateItemsVisitor* visitor, IterateItemsMask mask)
{
if (mask & ITERATE_ITEMS_IN_BAGS)
@ -292,9 +317,12 @@ std::vector<Item*> InventoryAction::parseItems(std::string const text, IterateIt
found.insert(visitor.GetResult().begin(), visitor.GetResult().end());
}
if (!isReservedQualifier(text))
{
FindNamedItemVisitor visitor(bot, text);
IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS);
found.insert(visitor.GetResult().begin(), visitor.GetResult().end());
}
uint32 quality = chat->parseItemQuality(text);
if (quality != MAX_ITEM_QUALITY)

View File

@ -0,0 +1,39 @@
/*
* 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 "TellEmblemsAction.h"
#include <array>
#include "Event.h"
#include "Playerbots.h"
bool TellEmblemsAction::Execute(Event /*event*/)
{
static std::array<uint32, 6> const emblemIds = {
29434, // Badge of Justice
40752, // Emblem of Heroism
40753, // Emblem of Valor
45624, // Emblem of Conquest
47241, // Emblem of Triumph
49426 // Emblem of Frost
};
botAI->TellMaster("=== Emblems ===");
for (uint32 itemId : emblemIds)
{
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);
if (!proto)
continue;
uint32 count = bot->GetItemCount(itemId, false);
std::ostringstream out;
out << chat->FormatItem(proto, count);
botAI->TellMaster(out);
}
return true;
}

View File

@ -0,0 +1,21 @@
/*
* 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_TELLEMBLEMSACTION_H
#define _PLAYERBOT_TELLEMBLEMSACTION_H
#include "InventoryAction.h"
class PlayerbotAI;
class TellEmblemsAction : public InventoryAction
{
public:
TellEmblemsAction(PlayerbotAI* botAI) : InventoryAction(botAI, "emblems") {}
bool Execute(Event event) override;
};
#endif

View File

@ -5,34 +5,23 @@
#include "TellReputationAction.h"
#include <algorithm>
#include "Event.h"
#include "PlayerbotAI.h"
#include "ReputationMgr.h"
bool TellReputationAction::Execute(Event /*event*/)
#include "SharedDefines.h"
std::string TellReputationAction::BuildReputationLine(FactionEntry const* entry)
{
Player* master = GetMaster();
if (!master)
return false;
ObjectGuid selection = master->GetTarget();
if (selection.IsEmpty())
return false;
Unit* unit = ObjectAccessor::GetUnit(*master, selection);
if (!unit)
return false;
FactionTemplateEntry const* factionTemplate = unit->GetFactionTemplateEntry();
uint32 faction = factionTemplate->faction;
FactionEntry const* entry = sFactionStore.LookupEntry(faction);
int32 reputation = bot->GetReputationMgr().GetReputation(faction);
ReputationMgr& repMgr = bot->GetReputationMgr();
ReputationRank rank = repMgr.GetRank(entry);
int32 reputation = repMgr.GetReputation(entry->ID);
std::ostringstream out;
out << entry->name[0] << ": ";
out << "|cff";
out << entry->name[0] << ": |cff";
ReputationRank rank = bot->GetReputationMgr().GetRank(entry);
switch (rank)
{
case REP_HATED:
@ -71,7 +60,65 @@ bool TellReputationAction::Execute(Event /*event*/)
base -= ReputationMgr::PointsInRank[i];
out << " (" << (reputation - base) << "/" << ReputationMgr::PointsInRank[rank] << ")";
botAI->TellMaster(out);
return out.str();
}
bool TellReputationAction::Execute(Event event)
{
std::string const param = event.getParam();
if (param == "all")
{
ReputationMgr& repMgr = bot->GetReputationMgr();
std::vector<std::string> lines;
FactionStateList const& stateList = repMgr.GetStateList();
lines.reserve(stateList.size());
for (auto const& itr : stateList)
{
FactionState const& faction = itr.second;
if (!(faction.Flags & FACTION_FLAG_VISIBLE))
continue;
if (faction.Flags & (FACTION_FLAG_HIDDEN | FACTION_FLAG_INVISIBLE_FORCED) &&
!(faction.Flags & FACTION_FLAG_SPECIAL))
continue;
FactionEntry const* entry = sFactionStore.LookupEntry(faction.ID);
if (!entry)
continue;
lines.push_back(BuildReputationLine(entry));
}
std::sort(lines.begin(), lines.end());
botAI->TellMaster("=== Reputations ===");
for (auto const& line : lines)
botAI->TellMaster(line);
return true;
}
Player* master = GetMaster();
if (!master)
return false;
ObjectGuid selection = master->GetTarget();
if (selection.IsEmpty())
return false;
Unit* unit = ObjectAccessor::GetUnit(*master, selection);
if (!unit)
return false;
FactionTemplateEntry const* factionTemplate = unit->GetFactionTemplateEntry();
FactionEntry const* entry = sFactionStore.LookupEntry(factionTemplate->faction);
if (!entry)
return false;
botAI->TellMaster(BuildReputationLine(entry));
return true;
}

View File

@ -6,8 +6,11 @@
#ifndef _PLAYERBOT_TELLREPUTATIONACTION_H
#define _PLAYERBOT_TELLREPUTATIONACTION_H
#include <string>
#include "Action.h"
struct FactionEntry;
class PlayerbotAI;
class TellReputationAction : public Action
@ -16,6 +19,9 @@ public:
TellReputationAction(PlayerbotAI* botAI) : Action(botAI, "reputation") {}
bool Execute(Event event) override;
private:
std::string BuildReputationLine(FactionEntry const* entry);
};
#endif

View File

@ -66,6 +66,7 @@
#include "TaxiAction.h"
#include "TeleportAction.h"
#include "TellCastFailedAction.h"
#include "TellEmblemsAction.h"
#include "TellItemCountAction.h"
#include "TellLosAction.h"
#include "TellReputationAction.h"
@ -120,6 +121,7 @@ public:
creators["teleport"] = &ChatActionContext::teleport;
creators["taxi"] = &ChatActionContext::taxi;
creators["repair"] = &ChatActionContext::repair;
creators["emblems"] = &ChatActionContext::emblems;
creators["use"] = &ChatActionContext::use;
creators["item count"] = &ChatActionContext::item_count;
creators["equip"] = &ChatActionContext::equip;
@ -276,6 +278,7 @@ private:
static Action* item_count(PlayerbotAI* botAI) { return new TellItemCountAction(botAI); }
static Action* use(PlayerbotAI* botAI) { return new UseItemAction(botAI); }
static Action* repair(PlayerbotAI* botAI) { return new RepairAllAction(botAI); }
static Action* emblems(PlayerbotAI* botAI) { return new TellEmblemsAction(botAI); }
static Action* taxi(PlayerbotAI* botAI) { return new TaxiAction(botAI); }
static Action* teleport(PlayerbotAI* botAI) { return new TeleportAction(botAI); }
static Action* release(PlayerbotAI* botAI) { return new ReleaseSpiritAction(botAI); }

View File

@ -41,6 +41,7 @@ public:
creators["teleport"] = &ChatTriggerContext::teleport;
creators["taxi"] = &ChatTriggerContext::taxi;
creators["repair"] = &ChatTriggerContext::repair;
creators["emblems"] = &ChatTriggerContext::emblems;
creators["u"] = &ChatTriggerContext::use;
creators["use"] = &ChatTriggerContext::use;
creators["c"] = &ChatTriggerContext::item_count;
@ -235,6 +236,7 @@ private:
static Trigger* item_count(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "c"); }
static Trigger* use(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "use"); }
static Trigger* repair(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "repair"); }
static Trigger* emblems(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "emblems"); }
static Trigger* taxi(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "taxi"); }
static Trigger* teleport(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "teleport"); }
static Trigger* q(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "q"); }

View File

@ -114,6 +114,7 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
triggers.push_back(new TriggerNode("pet attack", { NextAction("pet attack", relevance) }));
triggers.push_back(new TriggerNode("roll", { NextAction("roll", relevance) }));
triggers.push_back(new TriggerNode("focus heal", { NextAction("focus heal targets", relevance) }));
triggers.push_back(new TriggerNode("emblems", { NextAction("emblems", relevance) }));
}
ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI)
@ -138,6 +139,7 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas
supported.push_back("teleport");
supported.push_back("taxi");
supported.push_back("repair");
supported.push_back("emblems");
supported.push_back("talents");
supported.push_back("spells");
supported.push_back("co");
@ -202,8 +204,8 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas
supported.push_back("unlock items");
supported.push_back("unlock traded item");
supported.push_back("tame");
supported.push_back("glyphs"); // Added for custom Glyphs
supported.push_back("glyph equip"); // Added for custom Glyphs
supported.push_back("glyphs");
supported.push_back("glyph equip");
supported.push_back("pet");
supported.push_back("pet attack");
supported.push_back("wait for attack time");

View File

@ -39,11 +39,8 @@ bool LeaveLargeGuildTrigger::IsActive()
Player* leader = ObjectAccessor::FindPlayer(guild->GetLeaderGUID());
// Only leave the guild if we know the leader is not a real player.
if (!leader || !GET_PLAYERBOT_AI(leader) || !GET_PLAYERBOT_AI(leader)->IsRealPlayer())
return false;
PlayerbotAI* leaderBotAI = GET_PLAYERBOT_AI(leader);
// Only leave the guild if the leader is an online bot (not a real player).
PlayerbotAI* leaderBotAI = leader ? GET_PLAYERBOT_AI(leader) : nullptr;
if (!leaderBotAI || leaderBotAI->IsRealPlayer())
return false;

View File

@ -297,7 +297,7 @@ bool PlayerWantsInBattlegroundTrigger::IsActive()
if (bot->GetBattleground() && bot->GetBattleground()->GetStatus() == STATUS_IN_PROGRESS)
return false;
if (!bot->CanJoinToBattleground())
if (bot->IsDeserter())
return false;
return true;

View File

@ -180,19 +180,11 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
delete pItem;
if (result != EQUIP_ERR_OK && result != EQUIP_ERR_CANT_CARRY_MORE_OF_THIS)
{
return ITEM_USAGE_NONE;
}
// Check is unique items are equipped or not
bool needToCheckUnique = false;
if (result == EQUIP_ERR_CANT_CARRY_MORE_OF_THIS)
{
needToCheckUnique = true;
}
else if (itemProto->HasFlag(ITEM_FLAG_UNIQUE_EQUIPPABLE))
{
needToCheckUnique = true;
}
// Check if unique items are equipped or not
bool needToCheckUnique = result == EQUIP_ERR_CANT_CARRY_MORE_OF_THIS ||
itemProto->HasFlag(ITEM_FLAG_UNIQUE_EQUIPPABLE);
if (needToCheckUnique)
{
@ -206,14 +198,11 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
bool isEquipped = (totalItemCount > bagItemCount);
if (isEquipped)
{
return ITEM_USAGE_NONE; // Item is already equipped
}
// If not equipped, continue processing
}
if (itemProto->Class == ITEM_CLASS_QUIVER)
if (bot->getClass() != CLASS_HUNTER)
if (itemProto->Class == ITEM_CLASS_QUIVER && bot->getClass() != CLASS_HUNTER)
return ITEM_USAGE_NONE;
if (itemProto->Class == ITEM_CLASS_CONTAINER)
@ -221,13 +210,15 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
if (itemProto->SubClass != ITEM_SUBCLASS_CONTAINER)
return ITEM_USAGE_NONE; // Todo add logic for non-bag containers. We want to look at professions/class and
// only replace if non-bag is larger than bag.
if (GetSmallestBagSize() >= itemProto->ContainerSlots)
return ITEM_USAGE_NONE;
return ITEM_USAGE_EQUIP;
}
if (itemProto->Class == ITEM_CLASS_WEAPON && itemProto->SubClass == ITEM_SUBCLASS_WEAPON_MISC)
return ITEM_USAGE_NONE;
bool shouldEquip = false;
// uint32 statWeight = sRandomItemMgr.GetLiveStatWeight(bot, itemProto->ItemId);
StatsWeightCalculator calculator(bot);
@ -254,19 +245,14 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
uint8 dstSlot = botAI->FindEquipSlot(itemProto, NULL_SLOT, true);
// Check if dest wasn't set correctly by CanEquipItem and use FindEquipSlot instead
// This occurs with unique items that are already in the bots bags when CanEquipItem is called
if (dest == 0)
{
if (dstSlot != NULL_SLOT)
if (dest == 0 && dstSlot != NULL_SLOT)
{
// Construct dest from dstSlot
dest = (INVENTORY_SLOT_BAG_0 << 8) | dstSlot;
}
}
if (dstSlot == EQUIPMENT_SLOT_FINGER1 || dstSlot == EQUIPMENT_SLOT_TRINKET1)
{
possibleSlots = 2;
}
// Check weapon case separately to keep things a bit cleaner
bool have2HWeapon = false;
@ -283,14 +269,9 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
itemProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2);
// If the bot can Titan Grip, ignore any 2H weapon that isn't a 2H sword, mace, or axe.
if (bot->CanTitanGrip())
{
// If this weapon is 2H but not one of the valid TG weapon types, do not equip it at all.
if (itemProto->InventoryType == INVTYPE_2HWEAPON && !isValidTGWeapon)
{
if (bot->CanTitanGrip() && itemProto->InventoryType == INVTYPE_2HWEAPON && !isValidTGWeapon)
return ITEM_USAGE_NONE;
}
}
// Now handle the logic for equipping and possible offhand slots
// If the bot can Dual Wield and:
@ -317,10 +298,8 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
if (shouldEquipInSlot)
return ITEM_USAGE_EQUIP;
else
{
return ITEM_USAGE_BAD_EQUIP;
}
}
ItemTemplate const* oldItemProto = oldItem->GetTemplate();
float oldScore = calculator.CalculateItem(oldItemProto->ItemId, oldItem->GetInt32Value(ITEM_FIELD_RANDOM_PROPERTIES_ID));
@ -328,23 +307,17 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
{
// uint32 oldStatWeight = sRandomItemMgr.GetLiveStatWeight(bot, oldItemProto->ItemId);
if (itemScore || oldScore)
{
shouldEquipInSlot = itemScore > oldScore * sPlayerbotAIConfig.equipUpgradeThreshold;
}
}
// Bigger quiver
if (itemProto->Class == ITEM_CLASS_QUIVER)
{
if (!oldItem || oldItemProto->ContainerSlots < itemProto->ContainerSlots)
{
return ITEM_USAGE_EQUIP;
}
else
{
return ITEM_USAGE_NONE;
}
}
bool existingShouldEquip = true;
if (oldItemProto->Class == ITEM_CLASS_WEAPON && !sRandomItemMgr.CanEquipWeapon(bot->getClass(), oldItemProto))

View File

@ -11,6 +11,22 @@
#include "AoeValues.h"
#include "TargetValue.h"
namespace
{
bool PrepareThornsTarget(PlayerbotAI* botAI, Unit* target)
{
if (!target)
return false;
Aura* existingThorns = botAI->GetAura("thorns", target, true);
if (!existingThorns)
return true;
target->RemoveOwnedAura(existingThorns, AURA_REMOVE_BY_CANCEL);
return true;
}
}
std::vector<NextAction> CastAbolishPoisonAction::getAlternatives()
{
return NextAction::merge({ NextAction("cure poison") },
@ -33,6 +49,21 @@ bool CastLifebloomOnMainTankAction::isUseful()
return !lifebloom || lifebloom->GetStackAmount() < 3 || lifebloom->GetDuration() < 2000;
}
bool CastThornsAction::Execute(Event event)
{
return PrepareThornsTarget(botAI, GetTarget()) && CastBuffSpellAction::Execute(event);
}
bool CastThornsOnPartyAction::Execute(Event event)
{
return PrepareThornsTarget(botAI, GetTarget()) && BuffOnPartyAction::Execute(event);
}
bool CastThornsOnMainTankAction::Execute(Event event)
{
return PrepareThornsTarget(botAI, GetTarget()) && BuffOnMainTankAction::Execute(event);
}
Value<Unit*>* CastEntanglingRootsCcAction::GetTargetValue()
{
return context->GetValue<Unit*>("cc target", "entangling roots");

View File

@ -114,18 +114,24 @@ class CastThornsAction : public CastBuffSpellAction
{
public:
CastThornsAction(PlayerbotAI* botAI) : CastBuffSpellAction(botAI, "thorns") {}
bool Execute(Event event) override;
};
class CastThornsOnPartyAction : public BuffOnPartyAction
{
public:
CastThornsOnPartyAction(PlayerbotAI* botAI) : BuffOnPartyAction(botAI, "thorns") {}
bool Execute(Event event) override;
};
class CastThornsOnMainTankAction : public BuffOnMainTankAction
{
public:
CastThornsOnMainTankAction(PlayerbotAI* botAI) : BuffOnMainTankAction(botAI, "thorns", false) {}
bool Execute(Event event) override;
};
class CastLifebloomOnMainTankAction : public BuffOnMainTankAction

View File

@ -1,7 +1,6 @@
#ifndef _PLAYERBOT_RAIDAQ20TRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDAQ20TRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h"
#include "RaidAq20Triggers.h"

View File

@ -1,8 +1,6 @@
#ifndef _PLAYERBOT_RAIDAQ20STRATEGY_H
#define _PLAYERBOT_RAIDAQ20STRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h"
class RaidAq20Strategy : public Strategy

View File

@ -1,7 +1,6 @@
#ifndef _PLAYERBOT_RAIDBWLTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDBWLTRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h"
#include "RaidBwlTriggers.h"

View File

@ -2,8 +2,6 @@
#ifndef _PLAYERBOT_RAIDBWLSTRATEGY_H
#define _PLAYERBOT_RAIDBWLSTRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h"
class RaidBwlStrategy : public Strategy

View File

@ -1,7 +1,6 @@
#ifndef _PLAYERBOT_RAIDEOETRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDEOETRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h"
#include "RaidEoETriggers.h"

View File

@ -1,8 +1,6 @@
#ifndef _PLAYERBOT_RAIDEOESTRATEGY_H
#define _PLAYERBOT_RAIDEOESTRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h"
class RaidEoEStrategy : public Strategy

View File

@ -2,7 +2,7 @@
#define _PLAYERBOT_RAIDGRUULSLAIRTRIGGERCONTEXT_H
#include "RaidGruulsLairTriggers.h"
#include "AiObjectContext.h"
#include "NamedObjectContext.h"
class RaidGruulsLairTriggerContext : public NamedObjectContext<Trigger>
{

View File

@ -2,7 +2,6 @@
#define _PLAYERBOT_RAIDGRUULSLAIRSTRATEGY_H
#include "Strategy.h"
#include "Multiplier.h"
class RaidGruulsLairStrategy : public Strategy
{

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,277 @@
/*
* 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_RAIDHYJALSUMMITACTIONS_H
#define _PLAYERBOT_RAIDHYJALSUMMITACTIONS_H
#include "Action.h"
#include "AttackAction.h"
#include "MovementActions.h"
// General
class HyjalSummitEraseTrackersAction : public Action
{
public:
HyjalSummitEraseTrackersAction(
PlayerbotAI* botAI) : Action(botAI, "hyjal summit erase trackers") {}
bool Execute(Event event) override;
};
// Rage Winterchill
class RageWinterchillMisdirectBossToMainTankAction : public AttackAction
{
public:
RageWinterchillMisdirectBossToMainTankAction(
PlayerbotAI* botAI) : AttackAction(botAI, "rage winterchill misdirect boss to main tank") {}
bool Execute(Event event) override;
};
class RageWinterchillMainTankPositionBossAction : public AttackAction
{
public:
RageWinterchillMainTankPositionBossAction(
PlayerbotAI* botAI) : AttackAction(botAI, "rage winterchill main tank position boss") {}
bool Execute(Event event) override;
};
class RageWinterchillSpreadRangedInCircleAction : public MovementAction
{
public:
RageWinterchillSpreadRangedInCircleAction(
PlayerbotAI* botAI) : MovementAction(botAI, "rage winterchill spread ranged in circle") {}
bool Execute(Event event) override;
};
class RageWinterchillMeleeGetOutOfDeathAndDecayAction : public AttackAction
{
public:
RageWinterchillMeleeGetOutOfDeathAndDecayAction(
PlayerbotAI* botAI) : AttackAction(botAI, "rage winterchill melee get out of death and decay") {}
bool Execute(Event event) override;
};
// Anetheron
class AnetheronMisdirectBossAndInfernalsToTanksAction : public AttackAction
{
public:
AnetheronMisdirectBossAndInfernalsToTanksAction(
PlayerbotAI* botAI) : AttackAction(botAI, "anetheron misdirect boss and infernals to tanks") {}
bool Execute(Event event) override;
};
class AnetheronMainTankPositionBossAction : public AttackAction
{
public:
AnetheronMainTankPositionBossAction(
PlayerbotAI* botAI) : AttackAction(botAI, "anetheron main tank position boss") {}
bool Execute(Event event) override;
};
class AnetheronSpreadRangedInCircleAction : public MovementAction
{
public:
AnetheronSpreadRangedInCircleAction(
PlayerbotAI* botAI) : MovementAction(botAI, "anetheron spread ranged in circle") {}
bool Execute(Event event) override;
};
class AnetheronBringInfernalToInfernalTankAction : public MovementAction
{
public:
AnetheronBringInfernalToInfernalTankAction(
PlayerbotAI* botAI) : MovementAction(botAI, "anetheron bring infernal to infernal tank") {}
bool Execute(Event event) override;
};
class AnetheronFirstAssistTankPickUpInfernalsAction : public AttackAction
{
public:
AnetheronFirstAssistTankPickUpInfernalsAction(
PlayerbotAI* botAI) : AttackAction(botAI, "anetheron first assist tank pick up infernals") {}
bool Execute(Event event) override;
};
class AnetheronAssignDpsPriorityAction : public AttackAction
{
public:
AnetheronAssignDpsPriorityAction(
PlayerbotAI* botAI) : AttackAction(botAI, "anetheron assign dps priority") {}
bool Execute(Event event) override;
};
// Kaz'rogal
class KazrogalMisdirectBossToMainTankAction : public AttackAction
{
public:
KazrogalMisdirectBossToMainTankAction(
PlayerbotAI* botAI) : AttackAction(botAI, "kaz'rogal misdirect boss to main tank") {}
bool Execute(Event event) override;
};
class KazrogalMainTankPositionBossAction : public AttackAction
{
public:
KazrogalMainTankPositionBossAction(
PlayerbotAI* botAI) : AttackAction(botAI, "kaz'rogal main tank position boss") {}
bool Execute(Event event) override;
};
class KazrogalAssistTanksMoveInFrontOfBossAction : public AttackAction
{
public:
KazrogalAssistTanksMoveInFrontOfBossAction(
PlayerbotAI* botAI) : AttackAction(botAI, "kaz'rogal assist tanks move in front of boss") {}
bool Execute(Event event) override;
};
class KazrogalSpreadRangedInArcAction : public MovementAction
{
public:
KazrogalSpreadRangedInArcAction(
PlayerbotAI* botAI) : MovementAction(botAI, "kaz'rogal spread ranged in arc") {}
bool Execute(Event event) override;
};
class KazrogalLowManaBotTakeDefensiveMeasuresAction : public MovementAction
{
public:
KazrogalLowManaBotTakeDefensiveMeasuresAction(
PlayerbotAI* botAI) : MovementAction(botAI, "kaz'rogal low mana bot take defensive measures") {}
bool Execute(Event event) override;
};
class KazrogalCastShadowProtectionSpellAction : public Action
{
public:
KazrogalCastShadowProtectionSpellAction(
PlayerbotAI* botAI) : Action(botAI, "kaz'rogal cast shadow protection spell") {}
bool Execute(Event event) override;
};
// Azgalor
class AzgalorMisdirectBossToMainTankAction : public AttackAction
{
public:
AzgalorMisdirectBossToMainTankAction(
PlayerbotAI* botAI) : AttackAction(botAI, "azgalor misdirect boss to main tank") {}
bool Execute(Event event) override;
};
class AzgalorMainTankPositionBossAction : public AttackAction
{
public:
AzgalorMainTankPositionBossAction(
PlayerbotAI* botAI) : AttackAction(botAI, "azgalor main tank position boss") {}
bool Execute(Event event) override;
};
class AzgalorWaitAtSafePositionAction : public MovementAction
{
public:
AzgalorWaitAtSafePositionAction(
PlayerbotAI* botAI) : MovementAction(botAI, "azgalor wait at safe position") {}
bool Execute(Event event) override;
};
class AzgalorDisperseRangedAction : public MovementAction
{
public:
AzgalorDisperseRangedAction(
PlayerbotAI* botAI) : MovementAction(botAI, "azgalor disperse ranged") {}
bool Execute(Event event) override;
};
class AzgalorMeleeGetOutOfFireAndSwapTargetsAction : public AttackAction
{
public:
AzgalorMeleeGetOutOfFireAndSwapTargetsAction(
PlayerbotAI* botAI) : AttackAction(botAI, "azgalor melee get out of fire and swap targets") {}
bool Execute(Event event) override;
};
class AzgalorMoveToDoomguardTankAction : public MovementAction
{
public:
AzgalorMoveToDoomguardTankAction(
PlayerbotAI* botAI) : MovementAction(botAI, "azgalor move to doomguard tank") {}
bool Execute(Event event) override;
};
class AzgalorFirstAssistTankPositionDoomguardAction : public AttackAction
{
public:
AzgalorFirstAssistTankPositionDoomguardAction(
PlayerbotAI* botAI) : AttackAction(botAI, "azgalor first assist tank position doomguard") {}
bool Execute(Event event) override;
};
class AzgalorRangedDpsPrioritizeDoomguardsAction : public AttackAction
{
public:
AzgalorRangedDpsPrioritizeDoomguardsAction(
PlayerbotAI* botAI) : AttackAction(botAI, "azgalor ranged dps prioritize doomguards") {}
bool Execute(Event event) override;
};
// Archimonde
class ArchimondeMisdirectBossToMainTankAction : public AttackAction
{
public:
ArchimondeMisdirectBossToMainTankAction(
PlayerbotAI* botAI) : AttackAction(botAI, "archimonde misdirect boss to main tank") {}
bool Execute(Event event) override;
};
class ArchimondeMoveBossToInitialPositionAction : public AttackAction
{
public:
ArchimondeMoveBossToInitialPositionAction(
PlayerbotAI* botAI) : AttackAction(botAI, "archimonde move boss to initial position") {}
bool Execute(Event event) override;
};
class ArchimondeCastFearImmunitySpellAction : public Action
{
public:
ArchimondeCastFearImmunitySpellAction(
PlayerbotAI* botAI) : Action(botAI, "archimonde cast fear immunity spell") {}
bool Execute(Event event) override;
private:
bool CastFearWardOnMainTank();
bool UseTremorTotemStrategy();
};
class ArchimondeSpreadToAvoidAirBurstAction : public MovementAction
{
public:
ArchimondeSpreadToAvoidAirBurstAction(
PlayerbotAI* botAI) : MovementAction(botAI, "archimonde spread to avoid air burst") {}
bool Execute(Event event) override;
};
class ArchimondeAvoidDoomfireAction : public MovementAction
{
public:
ArchimondeAvoidDoomfireAction(
PlayerbotAI* botAI) : MovementAction(botAI, "archimonde avoid doomfire") {}
bool Execute(Event event) override;
};
class ArchimondeRemoveDoomfireDotAction : public Action
{
public:
ArchimondeRemoveDoomfireDotAction(
PlayerbotAI* botAI) : Action(botAI, "archimonde remove doomfire dot") {}
bool Execute(Event event) override;
};
#endif

View File

@ -0,0 +1,295 @@
/*
* 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 "RaidHyjalSummitMultipliers.h"
#include "RaidHyjalSummitActions.h"
#include "RaidHyjalSummitHelpers.h"
#include "AiFactory.h"
#include "ChooseTargetActions.h"
#include "DKActions.h"
#include "DruidBearActions.h"
#include "HunterActions.h"
#include "PaladinActions.h"
#include "RaidBossHelpers.h"
#include "ReachTargetActions.h"
#include "ShamanActions.h"
#include "WarriorActions.h"
using namespace HyjalSummitHelpers;
// Without this multiplier, Bloodlust/Heroism will not be available for
// bosses because it will be used on cooldown during trash waves
float HyjalSummitTimeBloodlustAndHeroismMultiplier::GetValue(Action* action)
{
if (bot->getClass() != CLASS_SHAMAN)
return 1.0f;
if (dynamic_cast<CastBloodlustAction*>(action) ||
dynamic_cast<CastHeroismAction*>(action))
{
Unit* archimonde = AI_VALUE2(Unit*, "find target", "archimonde");
if (archimonde && archimonde->GetHealthPct() < 90.0f)
return 1.0f;
Unit* azgalor = AI_VALUE2(Unit*, "find target", "azgalor");
if (azgalor && azgalor->GetHealthPct() < 90.0f)
return 1.0f;
Unit* kazrogal = AI_VALUE2(Unit*, "find target", "kaz'rogal");
if (kazrogal && kazrogal->GetHealthPct() < 90.0f)
return 1.0f;
Unit* anetheron = AI_VALUE2(Unit*, "find target", "anetheron");
if (anetheron && anetheron->GetHealthPct() < 85.0f)
return 1.0f;
Unit* winterchill = AI_VALUE2(Unit*, "find target", "rage winterchill");
if (winterchill && winterchill->GetHealthPct() < 90.0f)
return 1.0f;
return 0.0f;
}
return 1.0f;
}
// Rage Winterchill
float RageWinterchillDisableCombatFormationMoveMultiplier::GetValue(Action* action)
{
if (!AI_VALUE2(Unit*, "find target", "rage winterchill"))
return 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action) &&
!dynamic_cast<SetBehindTargetAction*>(action))
return 0.0f;
return 1.0f;
}
float RageWinterchillMeleeControlAvoidanceMultiplier::GetValue(Action* action)
{
if (botAI->IsRanged(bot))
return 1.0f;
Unit* winterchill = AI_VALUE2(Unit*, "find target", "rage winterchill");
if (!winterchill)
return 1.0f;
if (IsInDeathAndDecay(bot, DEATH_AND_DECAY_SAFE_RADIUS + 2.0f))
{
if (dynamic_cast<AvoidAoeAction*>(action))
return 0.0f;
if (botAI->IsMainTank(bot) || winterchill->GetVictim() == bot)
return 1.0f;
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<RageWinterchillMeleeGetOutOfDeathAndDecayAction*>(action))
return 0.0f;
if (dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
}
return 1.0f;
}
// Anetheron
float AnetheronDisableTankActionsMultiplier::GetValue(Action* action)
{
if (!botAI->IsTank(bot) || !AI_VALUE2(Unit*, "find target", "anetheron"))
return 1.0f;
if (dynamic_cast<AvoidAoeAction*>(action))
return 0.0f;
if (bot->GetVictim() != nullptr &&
dynamic_cast<TankAssistAction*>(action))
return 0.0f;
return 1.0f;
}
float AnetheronDisableCombatFormationMoveMultiplier::GetValue(Action* action)
{
if (!AI_VALUE2(Unit*, "find target", "anetheron"))
return 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action) &&
!dynamic_cast<SetBehindTargetAction*>(action))
return 0.0f;
return 1.0f;
}
float AnetheronControlMisdirectionMultiplier::GetValue(Action* action)
{
if (bot->getClass() != CLASS_HUNTER ||
!AI_VALUE2(Unit*, "find target", "anetheron"))
return 1.0f;
if (dynamic_cast<CastMisdirectionOnMainTankAction*>(action))
return 0.0f;
return 1.0f;
}
// Kaz'rogal
float KazrogalLowManaBotStayAwayFromGroupMultiplier::GetValue(Action* action)
{
if (bot->getClass() == CLASS_WARRIOR || bot->getClass() == CLASS_ROGUE ||
bot->getClass() == CLASS_DEATH_KNIGHT || bot->getClass() == CLASS_HUNTER)
return 1.0f;
uint8 tab = AiFactory::GetPlayerSpecTab(bot);
if (bot->getClass() == CLASS_DRUID && tab == DRUID_TAB_FERAL)
return 1.0f;
if (!AI_VALUE2(Unit*, "find target", "kaz'rogal"))
return 1.0f;
if (!isBelowManaThreshold.count(bot->GetGUID()))
return 1.0f;
if (dynamic_cast<CastReachTargetSpellAction*>(action) ||
(dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<AttackAction*>(action) &&
!dynamic_cast<KazrogalLowManaBotTakeDefensiveMeasuresAction*>(action)))
return 0.0f;
return 1.0f;
}
float KazrogalKeepAspectOfTheViperActiveMultiplier::GetValue(Action* action)
{
if (bot->getClass() != CLASS_HUNTER || bot->GetPower(POWER_MANA) > 4000 ||
!AI_VALUE2(Unit*, "find target", "kaz'rogal"))
return 1.0f;
if (dynamic_cast<CastAspectOfTheHawkAction*>(action) ||
dynamic_cast<CastAspectOfTheWildAction*>(action) ||
dynamic_cast<CastAspectOfTheDragonhawkAction*>(action) ||
dynamic_cast<CastAspectOfTheCheetahAction*>(action) ||
dynamic_cast<CastAspectOfThePackAction*>(action) ||
dynamic_cast<CastAspectOfTheMonkeyAction*>(action))
return 0.0f;
return 1.0f;
}
float KazrogalControlMovementMultiplier::GetValue(Action* action)
{
if (!AI_VALUE2(Unit*, "find target", "kaz'rogal"))
return 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action) &&
!dynamic_cast<SetBehindTargetAction*>(action))
return 0.0f;
if (dynamic_cast<FleeAction*>(action))
return 0.0f;
if (botAI->IsRanged(bot) && dynamic_cast<ReachTargetAction*>(action))
return 0.0f;
return 1.0f;
}
// Azgalor
float AzgalorDisableTankActionsMultiplier::GetValue(Action* action)
{
if (bot->GetVictim() == nullptr)
return 1.0f;
if (!botAI->IsTank(bot) || !AI_VALUE2(Unit*, "find target", "azgalor"))
return 1.0f;
if (dynamic_cast<TankFaceAction*>(action))
return 0.0f;
if (dynamic_cast<TankAssistAction*>(action) || dynamic_cast<AvoidAoeAction*>(action))
{
if (botAI->IsMainTank(bot))
{
return 0.0f;
}
else if (botAI->IsAssistTank(bot) && (AnyGroupMemberHasDoom(bot) ||
AI_VALUE2(Unit*, "find target", "lesser doomguard")))
{
return 0.0f;
}
}
return 1.0f;
}
float AzgalorDoomedBotPrioritizePositioningMultiplier::GetValue(Action* action)
{
if (!bot->HasAura(static_cast<uint32>(HyjalSummitSpells::SPELL_DOOM)))
return 1.0f;
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<AttackAction*>(action) &&
!dynamic_cast<AvoidAoeAction*>(action) &&
!dynamic_cast<AzgalorMoveToDoomguardTankAction*>(action))
return 0.0f;
return 1.0f;
}
float AzgalorMeleeDpsControlAvoidanceMultiplier::GetValue(Action* action)
{
if (botAI->IsRanged(bot) || botAI->IsTank(bot))
return 1.0f;
Unit* azgalor = AI_VALUE2(Unit*, "find target", "azgalor");
if (!azgalor)
return 1.0f;
constexpr float singleTickMoveAwayDist = 6.0f;
if (IsInRainOfFire(bot, RAIN_OF_FIRE_RADIUS + singleTickMoveAwayDist))
{
if (dynamic_cast<AvoidAoeAction*>(action) ||
dynamic_cast<CastReachTargetSpellAction*>(action))
return 0.0f;
if (dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<AzgalorMeleeGetOutOfFireAndSwapTargetsAction*>(action))
return 0.0f;
}
Player* mainTank = GetGroupMainTank(botAI, bot);
if (!mainTank || !GET_PLAYERBOT_AI(mainTank))
return 1.0f;
TankPositionState tankState = GetAzgalorTankPositionState(botAI, bot);
if ((tankState == TankPositionState::Unknown ||
tankState == TankPositionState::MovingToTransition) &&
dynamic_cast<MovementAction*>(action) &&
!dynamic_cast<AzgalorWaitAtSafePositionAction*>(action))
{
return 0.0f;
}
return 1.0f;
}
// Archimonde
float ArchimondeDisableCombatFormationMoveMultiplier::GetValue(Action* action)
{
if (!AI_VALUE2(Unit*, "find target", "archimonde"))
return 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action) &&
!dynamic_cast<SetBehindTargetAction*>(action))
return 0.0f;
return 1.0f;
}

View File

@ -0,0 +1,125 @@
/*
* 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_RAIDHYJALSUMMITMULTIPLIERS_H
#define _PLAYERBOT_RAIDHYJALSUMMITMULTIPLIERS_H
#include "Multiplier.h"
class HyjalSummitTimeBloodlustAndHeroismMultiplier : public Multiplier
{
public:
HyjalSummitTimeBloodlustAndHeroismMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "hyjal summit time bloodlust and heroism multiplier") {}
virtual float GetValue(Action* action);
};
// Rage Winterchill
class RageWinterchillDisableCombatFormationMoveMultiplier : public Multiplier
{
public:
RageWinterchillDisableCombatFormationMoveMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "rage winterchill disable combat formation move multiplier") {}
virtual float GetValue(Action* action);
};
class RageWinterchillMeleeControlAvoidanceMultiplier : public Multiplier
{
public:
RageWinterchillMeleeControlAvoidanceMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "rage winterchill melee control avoidance multiplier") {}
virtual float GetValue(Action* action);
};
// Anetheron
class AnetheronDisableTankActionsMultiplier : public Multiplier
{
public:
AnetheronDisableTankActionsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "anetheron disable tank actions multiplier") {}
virtual float GetValue(Action* action);
};
class AnetheronDisableCombatFormationMoveMultiplier : public Multiplier
{
public:
AnetheronDisableCombatFormationMoveMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "anetheron disable combat formation move multiplier") {}
virtual float GetValue(Action* action);
};
class AnetheronControlMisdirectionMultiplier : public Multiplier
{
public:
AnetheronControlMisdirectionMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "anetheron control misdirection multiplier") {}
virtual float GetValue(Action* action);
};
// Kaz'rogal
class KazrogalLowManaBotStayAwayFromGroupMultiplier : public Multiplier
{
public:
KazrogalLowManaBotStayAwayFromGroupMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "kaz'rogal low mana bot stay away from group multiplier") {}
virtual float GetValue(Action* action);
};
class KazrogalKeepAspectOfTheViperActiveMultiplier : public Multiplier
{
public:
KazrogalKeepAspectOfTheViperActiveMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "kaz'rogal keep aspect of the viper active multiplier") {}
virtual float GetValue(Action* action);
};
class KazrogalControlMovementMultiplier : public Multiplier
{
public:
KazrogalControlMovementMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "kaz'rogal control movement multiplier") {}
virtual float GetValue(Action* action);
};
// Azgalor
class AzgalorDisableTankActionsMultiplier : public Multiplier
{
public:
AzgalorDisableTankActionsMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "azgalor disable tank actions multiplier") {}
virtual float GetValue(Action* action);
};
class AzgalorDoomedBotPrioritizePositioningMultiplier : public Multiplier
{
public:
AzgalorDoomedBotPrioritizePositioningMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "azgalor doomed bot prioritize positioning multiplier") {}
virtual float GetValue(Action* action);
};
class AzgalorMeleeDpsControlAvoidanceMultiplier : public Multiplier
{
public:
AzgalorMeleeDpsControlAvoidanceMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "azgalor melee dps control avoidance multiplier") {}
virtual float GetValue(Action* action);
};
// Archimonde
class ArchimondeDisableCombatFormationMoveMultiplier : public Multiplier
{
public:
ArchimondeDisableCombatFormationMoveMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "archimonde disable combat formation move multiplier") {}
virtual float GetValue(Action* action);
};
#endif

View File

@ -0,0 +1,218 @@
/*
* 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_RAIDHYJALSUMMITACTIONCONTEXT_H
#define _PLAYERBOT_RAIDHYJALSUMMITACTIONCONTEXT_H
#include "RaidHyjalSummitActions.h"
#include "NamedObjectContext.h"
class RaidHyjalSummitActionContext : public NamedObjectContext<Action>
{
public:
RaidHyjalSummitActionContext()
{
// General
creators["hyjal summit erase trackers"] =
&RaidHyjalSummitActionContext::hyjal_summit_erase_trackers;
// Rage Winterchill
creators["rage winterchill misdirect boss to main tank"] =
&RaidHyjalSummitActionContext::rage_winterchill_misdirect_boss_to_main_tank;
creators["rage winterchill main tank position boss"] =
&RaidHyjalSummitActionContext::rage_winterchill_main_tank_position_boss;
creators["rage winterchill spread ranged in circle"] =
&RaidHyjalSummitActionContext::rage_winterchill_spread_ranged_in_circle;
creators["rage winterchill melee get out of death and decay"] =
&RaidHyjalSummitActionContext::rage_winterchill_melee_get_out_of_death_and_decay;
// Anetheron
creators["anetheron misdirect boss and infernals to tanks"] =
&RaidHyjalSummitActionContext::anetheron_misdirect_boss_and_infernals_to_tanks;
creators["anetheron main tank position boss"] =
&RaidHyjalSummitActionContext::anetheron_main_tank_position_boss;
creators["anetheron spread ranged in circle"] =
&RaidHyjalSummitActionContext::anetheron_spread_ranged_in_circle;
creators["anetheron bring infernal to infernal tank"] =
&RaidHyjalSummitActionContext::anetheron_bring_infernal_to_infernal_tank;
creators["anetheron first assist tank pick up infernals"] =
&RaidHyjalSummitActionContext::anetheron_first_assist_tank_pick_up_infernals;
creators["anetheron assign dps priority"] =
&RaidHyjalSummitActionContext::anetheron_assign_dps_priority;
// Kaz'rogal
creators["kaz'rogal misdirect boss to main tank"] =
&RaidHyjalSummitActionContext::kazrogal_misdirect_boss_to_main_tank;
creators["kaz'rogal main tank position boss"] =
&RaidHyjalSummitActionContext::kazrogal_main_tank_position_boss;
creators["kaz'rogal assist tanks move in front of boss"] =
&RaidHyjalSummitActionContext::kazrogal_assist_tanks_move_in_front_of_boss;
creators["kaz'rogal spread ranged in arc"] =
&RaidHyjalSummitActionContext::kazrogal_spread_ranged_in_arc;
creators["kaz'rogal low mana bot take defensive measures"] =
&RaidHyjalSummitActionContext::kazrogal_low_mana_bot_take_defensive_measures;
creators["kaz'rogal cast shadow protection spell"] =
&RaidHyjalSummitActionContext::kazrogal_cast_shadow_protection_spell;
// Azgalor
creators["azgalor misdirect boss to main tank"] =
&RaidHyjalSummitActionContext::azgalor_misdirect_boss_to_main_tank;
creators["azgalor main tank position boss"] =
&RaidHyjalSummitActionContext::azgalor_main_tank_position_boss;
creators["azgalor wait at safe position"] =
&RaidHyjalSummitActionContext::azgalor_wait_at_safe_position;
creators["azgalor disperse ranged"] =
&RaidHyjalSummitActionContext::azgalor_disperse_ranged;
creators["azgalor melee get out of fire and swap targets"] =
&RaidHyjalSummitActionContext::azgalor_melee_get_out_of_fire_and_swap_targets;
creators["azgalor move to doomguard tank"] =
&RaidHyjalSummitActionContext::azgalor_move_to_doomguard_tank;
creators["azgalor first assist tank position doomguard"] =
&RaidHyjalSummitActionContext::azgalor_first_assist_tank_position_doomguard;
creators["azgalor ranged dps prioritize doomguards"] =
&RaidHyjalSummitActionContext::azgalor_ranged_dps_prioritize_doomguards;
// Archimonde
creators["archimonde misdirect boss to main tank"] =
&RaidHyjalSummitActionContext::archimonde_misdirect_boss_to_main_tank;
creators["archimonde move boss to initial position"] =
&RaidHyjalSummitActionContext::archimonde_move_boss_to_initial_position;
creators["archimonde cast fear immunity spell"] =
&RaidHyjalSummitActionContext::archimonde_cast_fear_immunity_spell;
creators["archimonde spread to avoid air burst"] =
&RaidHyjalSummitActionContext::archimonde_spread_to_avoid_air_burst;
creators["archimonde avoid doomfire"] =
&RaidHyjalSummitActionContext::archimonde_avoid_doomfire;
creators["archimonde remove doomfire dot"] =
&RaidHyjalSummitActionContext::archimonde_remove_doomfire_dot;
}
private:
// General
static Action* hyjal_summit_erase_trackers(
PlayerbotAI* botAI) { return new HyjalSummitEraseTrackersAction(botAI); }
// Rage Winterchill
static Action* rage_winterchill_misdirect_boss_to_main_tank(
PlayerbotAI* botAI) { return new RageWinterchillMisdirectBossToMainTankAction(botAI); }
static Action* rage_winterchill_main_tank_position_boss(
PlayerbotAI* botAI) { return new RageWinterchillMainTankPositionBossAction(botAI); }
static Action* rage_winterchill_spread_ranged_in_circle(
PlayerbotAI* botAI) { return new RageWinterchillSpreadRangedInCircleAction(botAI); }
static Action* rage_winterchill_melee_get_out_of_death_and_decay(
PlayerbotAI* botAI) { return new RageWinterchillMeleeGetOutOfDeathAndDecayAction(botAI); }
// Anetheron
static Action* anetheron_misdirect_boss_and_infernals_to_tanks(
PlayerbotAI* botAI) { return new AnetheronMisdirectBossAndInfernalsToTanksAction(botAI); }
static Action* anetheron_main_tank_position_boss(
PlayerbotAI* botAI) { return new AnetheronMainTankPositionBossAction(botAI); }
static Action* anetheron_spread_ranged_in_circle(
PlayerbotAI* botAI) { return new AnetheronSpreadRangedInCircleAction(botAI); }
static Action* anetheron_bring_infernal_to_infernal_tank(
PlayerbotAI* botAI) { return new AnetheronBringInfernalToInfernalTankAction(botAI); }
static Action* anetheron_first_assist_tank_pick_up_infernals(
PlayerbotAI* botAI) { return new AnetheronFirstAssistTankPickUpInfernalsAction(botAI); }
static Action* anetheron_assign_dps_priority(
PlayerbotAI* botAI) { return new AnetheronAssignDpsPriorityAction(botAI); }
// Kaz'rogal
static Action* kazrogal_misdirect_boss_to_main_tank(
PlayerbotAI* botAI) { return new KazrogalMisdirectBossToMainTankAction(botAI); }
static Action* kazrogal_main_tank_position_boss(
PlayerbotAI* botAI) { return new KazrogalMainTankPositionBossAction(botAI); }
static Action* kazrogal_assist_tanks_move_in_front_of_boss(
PlayerbotAI* botAI) { return new KazrogalAssistTanksMoveInFrontOfBossAction(botAI); }
static Action* kazrogal_spread_ranged_in_arc(
PlayerbotAI* botAI) { return new KazrogalSpreadRangedInArcAction(botAI); }
static Action* kazrogal_low_mana_bot_take_defensive_measures(
PlayerbotAI* botAI) { return new KazrogalLowManaBotTakeDefensiveMeasuresAction(botAI); }
static Action* kazrogal_cast_shadow_protection_spell(
PlayerbotAI* botAI) { return new KazrogalCastShadowProtectionSpellAction(botAI); }
// Azgalor
static Action* azgalor_misdirect_boss_to_main_tank(
PlayerbotAI* botAI) { return new AzgalorMisdirectBossToMainTankAction(botAI); }
static Action* azgalor_main_tank_position_boss(
PlayerbotAI* botAI) { return new AzgalorMainTankPositionBossAction(botAI); }
static Action* azgalor_wait_at_safe_position(
PlayerbotAI* botAI) { return new AzgalorWaitAtSafePositionAction(botAI); }
static Action* azgalor_disperse_ranged(
PlayerbotAI* botAI) { return new AzgalorDisperseRangedAction(botAI); }
static Action* azgalor_melee_get_out_of_fire_and_swap_targets(
PlayerbotAI* botAI) { return new AzgalorMeleeGetOutOfFireAndSwapTargetsAction(botAI); }
static Action* azgalor_move_to_doomguard_tank(
PlayerbotAI* botAI) { return new AzgalorMoveToDoomguardTankAction(botAI); }
static Action* azgalor_first_assist_tank_position_doomguard(
PlayerbotAI* botAI) { return new AzgalorFirstAssistTankPositionDoomguardAction(botAI); }
static Action* azgalor_ranged_dps_prioritize_doomguards(
PlayerbotAI* botAI) { return new AzgalorRangedDpsPrioritizeDoomguardsAction(botAI); }
// Archimonde
static Action* archimonde_misdirect_boss_to_main_tank(
PlayerbotAI* botAI) { return new ArchimondeMisdirectBossToMainTankAction(botAI); }
static Action* archimonde_move_boss_to_initial_position(
PlayerbotAI* botAI) { return new ArchimondeMoveBossToInitialPositionAction(botAI); }
static Action* archimonde_cast_fear_immunity_spell(
PlayerbotAI* botAI) { return new ArchimondeCastFearImmunitySpellAction(botAI); }
static Action* archimonde_spread_to_avoid_air_burst(
PlayerbotAI* botAI) { return new ArchimondeSpreadToAvoidAirBurstAction(botAI); }
static Action* archimonde_avoid_doomfire(
PlayerbotAI* botAI) { return new ArchimondeAvoidDoomfireAction(botAI); }
static Action* archimonde_remove_doomfire_dot(
PlayerbotAI* botAI) { return new ArchimondeRemoveDoomfireDotAction(botAI); }
};
#endif

View File

@ -0,0 +1,218 @@
/*
* 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_RAIDHYJALSUMMITTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDHYJALSUMMITTRIGGERCONTEXT_H
#include "RaidHyjalSummitTriggers.h"
#include "NamedObjectContext.h"
class RaidHyjalSummitTriggerContext : public NamedObjectContext<Trigger>
{
public:
RaidHyjalSummitTriggerContext()
{
// General
creators["hyjal summit bot is not in combat"] =
&RaidHyjalSummitTriggerContext::hyjal_summit_bot_is_not_in_combat;
// Rage Winterchill
creators["rage winterchill pulling boss"] =
&RaidHyjalSummitTriggerContext::rage_winterchill_pulling_boss;
creators["rage winterchill boss engaged by main tank"] =
&RaidHyjalSummitTriggerContext::rage_winterchill_boss_engaged_by_main_tank;
creators["rage winterchill boss casts death and decay on ranged"] =
&RaidHyjalSummitTriggerContext::rage_winterchill_boss_casts_death_and_decay_on_ranged;
creators["rage winterchill melee is standing in death and decay"] =
&RaidHyjalSummitTriggerContext::rage_winterchill_melee_is_standing_in_death_and_decay;
// Anetheron
creators["anetheron pulling boss or infernal"] =
&RaidHyjalSummitTriggerContext::anetheron_pulling_boss_or_infernal;
creators["anetheron boss engaged by main tank"] =
&RaidHyjalSummitTriggerContext::anetheron_boss_engaged_by_main_tank;
creators["anetheron boss casts carrion swarm"] =
&RaidHyjalSummitTriggerContext::anetheron_boss_casts_carrion_swarm;
creators["anetheron bot is targeted by infernal"] =
&RaidHyjalSummitTriggerContext::anetheron_bot_is_targeted_by_infernal;
creators["anetheron infernals need to be kept away from raid"] =
&RaidHyjalSummitTriggerContext::anetheron_infernals_need_to_be_kept_away_from_raid;
creators["anetheron infernals continue to spawn"] =
&RaidHyjalSummitTriggerContext::anetheron_infernals_continue_to_spawn;
// Kaz'rogal
creators["kaz'rogal pulling boss"] =
&RaidHyjalSummitTriggerContext::kazrogal_pulling_boss;
creators["kaz'rogal boss engaged by main tank"] =
&RaidHyjalSummitTriggerContext::kazrogal_boss_engaged_by_main_tank;
creators["kaz'rogal boss engaged by assist tanks"] =
&RaidHyjalSummitTriggerContext::kazrogal_boss_engaged_by_assist_tanks;
creators["kaz'rogal bot is low on mana"] =
&RaidHyjalSummitTriggerContext::kazrogal_bot_is_low_on_mana;
creators["kaz'rogal low mana bots need escape path"] =
&RaidHyjalSummitTriggerContext::kazrogal_low_mana_bots_need_escape_path;
creators["kaz'rogal mark deals shadow damage"] =
&RaidHyjalSummitTriggerContext::kazrogal_mark_deals_shadow_damage;
// Azgalor
creators["azgalor pulling boss"] =
&RaidHyjalSummitTriggerContext::azgalor_pulling_boss;
creators["azgalor boss engaged by main tank"] =
&RaidHyjalSummitTriggerContext::azgalor_boss_engaged_by_main_tank;
creators["azgalor main tank is positioning boss"] =
&RaidHyjalSummitTriggerContext::azgalor_main_tank_is_positioning_boss;
creators["azgalor boss engaged by ranged"] =
&RaidHyjalSummitTriggerContext::azgalor_boss_engaged_by_ranged;
creators["azgalor boss casts rain of fire on melee"] =
&RaidHyjalSummitTriggerContext::azgalor_boss_casts_rain_of_fire_on_melee;
creators["azgalor bot is doomed"] =
&RaidHyjalSummitTriggerContext::azgalor_bot_is_doomed;
creators["azgalor doomguards must be controlled"] =
&RaidHyjalSummitTriggerContext::azgalor_doomguards_must_be_controlled;
creators["azgalor doomguards must die"] =
&RaidHyjalSummitTriggerContext::azgalor_doomguards_must_die;
// Archimonde
creators["archimonde pulling boss"] =
&RaidHyjalSummitTriggerContext::archimonde_pulling_boss;
creators["archimonde boss engaged by main tank"] =
&RaidHyjalSummitTriggerContext::archimonde_boss_engaged_by_main_tank;
creators["archimonde boss casts fear"] =
&RaidHyjalSummitTriggerContext::archimonde_boss_casts_fear;
creators["archimonde boss casts air burst"] =
&RaidHyjalSummitTriggerContext::archimonde_boss_casts_air_burst;
creators["archimonde boss summoned doomfire"] =
&RaidHyjalSummitTriggerContext::archimonde_boss_summoned_doomfire;
creators["archimonde bot stood in doomfire"] =
&RaidHyjalSummitTriggerContext::archimonde_bot_stood_in_doomfire;
}
private:
// General
static Trigger* hyjal_summit_bot_is_not_in_combat(
PlayerbotAI* botAI) { return new HyjalSummitBotIsNotInCombatTrigger(botAI); }
// Rage Winterchill
static Trigger* rage_winterchill_pulling_boss(
PlayerbotAI* botAI) { return new RageWinterchillPullingBossTrigger(botAI); }
static Trigger* rage_winterchill_boss_engaged_by_main_tank(
PlayerbotAI* botAI) { return new RageWinterchillBossEngagedByMainTankTrigger(botAI); }
static Trigger* rage_winterchill_boss_casts_death_and_decay_on_ranged(
PlayerbotAI* botAI) { return new RageWinterchillBossCastsDeathAndDecayOnRangedTrigger(botAI); }
static Trigger* rage_winterchill_melee_is_standing_in_death_and_decay(
PlayerbotAI* botAI) { return new RageWinterchillMeleeIsStandingInDeathAndDecayTrigger(botAI); }
// Anetheron
static Trigger* anetheron_pulling_boss_or_infernal(
PlayerbotAI* botAI) { return new AnetheronPullingBossOrInfernalTrigger(botAI); }
static Trigger* anetheron_boss_engaged_by_main_tank(
PlayerbotAI* botAI) { return new AnetheronBossEngagedByMainTankTrigger(botAI); }
static Trigger* anetheron_boss_casts_carrion_swarm(
PlayerbotAI* botAI) { return new AnetheronBossCastsCarrionSwarmTrigger(botAI); }
static Trigger* anetheron_bot_is_targeted_by_infernal(
PlayerbotAI* botAI) { return new AnetheronBotIsTargetedByInfernalTrigger(botAI); }
static Trigger* anetheron_infernals_need_to_be_kept_away_from_raid(
PlayerbotAI* botAI) { return new AnetheronInfernalsNeedToBeKeptAwayFromRaidTrigger(botAI); }
static Trigger* anetheron_infernals_continue_to_spawn(
PlayerbotAI* botAI) { return new AnetheronInfernalsContinueToSpawnTrigger(botAI); }
// Kaz'rogal
static Trigger* kazrogal_pulling_boss(
PlayerbotAI* botAI) { return new KazrogalPullingBossTrigger(botAI); }
static Trigger* kazrogal_boss_engaged_by_main_tank(
PlayerbotAI* botAI) { return new KazrogalBossEngagedByMainTankTrigger(botAI); }
static Trigger* kazrogal_boss_engaged_by_assist_tanks(
PlayerbotAI* botAI) { return new KazrogalBossEngagedByAssistTanksTrigger(botAI); }
static Trigger* kazrogal_low_mana_bots_need_escape_path(
PlayerbotAI* botAI) { return new KazrogalLowManaBotsNeedEscapePathTrigger(botAI); }
static Trigger* kazrogal_bot_is_low_on_mana(
PlayerbotAI* botAI) { return new KazrogalBotIsLowOnManaTrigger(botAI); }
static Trigger* kazrogal_mark_deals_shadow_damage(
PlayerbotAI* botAI) { return new KazrogalMarkDealsShadowDamageTrigger(botAI); }
// Azgalor
static Trigger* azgalor_pulling_boss(
PlayerbotAI* botAI) { return new AzgalorPullingBossTrigger(botAI); }
static Trigger* azgalor_boss_engaged_by_main_tank(
PlayerbotAI* botAI) { return new AzgalorBossEngagedByMainTankTrigger(botAI); }
static Trigger* azgalor_main_tank_is_positioning_boss(
PlayerbotAI* botAI) { return new AzgalorMainTankIsPositioningBossTrigger(botAI); }
static Trigger* azgalor_boss_engaged_by_ranged(
PlayerbotAI* botAI) { return new AzgalorBossEngagedByRangedTrigger(botAI); }
static Trigger* azgalor_boss_casts_rain_of_fire_on_melee(
PlayerbotAI* botAI) { return new AzgalorBossCastsRainOfFireOnMeleeTrigger(botAI); }
static Trigger* azgalor_bot_is_doomed(
PlayerbotAI* botAI) { return new AzgalorBotIsDoomedTrigger(botAI); }
static Trigger* azgalor_doomguards_must_be_controlled(
PlayerbotAI* botAI) { return new AzgalorDoomguardsMustBeControlledTrigger(botAI); }
static Trigger* azgalor_doomguards_must_die(
PlayerbotAI* botAI) { return new AzgalorDoomguardsMustDieTrigger(botAI); }
// Archimonde
static Trigger* archimonde_pulling_boss(
PlayerbotAI* botAI) { return new ArchimondePullingBossTrigger(botAI); }
static Trigger* archimonde_boss_engaged_by_main_tank(
PlayerbotAI* botAI) { return new ArchimondeBossEngagedByMainTankTrigger(botAI); }
static Trigger* archimonde_boss_casts_fear(
PlayerbotAI* botAI) { return new ArchimondeBossCastsFearTrigger(botAI); }
static Trigger* archimonde_boss_casts_air_burst(
PlayerbotAI* botAI) { return new ArchimondeBossCastsAirBurstTrigger(botAI); }
static Trigger* archimonde_boss_summoned_doomfire(
PlayerbotAI* botAI) { return new ArchimondeBossSummonedDoomfireTrigger(botAI); }
static Trigger* archimonde_bot_stood_in_doomfire(
PlayerbotAI* botAI) { return new ArchimondeBotStoodInDoomfireTrigger(botAI); }
};
#endif

View File

@ -0,0 +1,137 @@
/*
* 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 "RaidHyjalSummitStrategy.h"
#include "RaidHyjalSummitMultipliers.h"
void RaidHyjalSummitStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
// General
triggers.push_back(new TriggerNode("hyjal summit bot is not in combat", {
NextAction("hyjal summit erase trackers", ACTION_EMERGENCY + 11) }));
// Rage Winterchill
triggers.push_back(new TriggerNode("rage winterchill pulling boss", {
NextAction("rage winterchill misdirect boss to main tank", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("rage winterchill boss engaged by main tank", {
NextAction("rage winterchill main tank position boss", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("rage winterchill boss casts death and decay on ranged", {
NextAction("rage winterchill spread ranged in circle", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("rage winterchill melee is standing in death and decay", {
NextAction("rage winterchill melee get out of death and decay", ACTION_EMERGENCY + 1) }));
// Anetheron
triggers.push_back(new TriggerNode("anetheron pulling boss or infernal", {
NextAction("anetheron misdirect boss and infernals to tanks", ACTION_RAID + 3) }));
triggers.push_back(new TriggerNode("anetheron boss engaged by main tank", {
NextAction("anetheron main tank position boss", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("anetheron boss casts carrion swarm", {
NextAction("anetheron spread ranged in circle", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("anetheron bot is targeted by infernal", {
NextAction("anetheron bring infernal to infernal tank", ACTION_EMERGENCY + 2) }));
triggers.push_back(new TriggerNode("anetheron infernals need to be kept away from raid", {
NextAction("anetheron first assist tank pick up infernals", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode("anetheron infernals continue to spawn", {
NextAction("anetheron assign dps priority", ACTION_RAID + 1) }));
// Kaz'rogal
triggers.push_back(new TriggerNode("kaz'rogal pulling boss", {
NextAction("kaz'rogal misdirect boss to main tank", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("kaz'rogal boss engaged by main tank", {
NextAction("kaz'rogal main tank position boss", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("kaz'rogal boss engaged by assist tanks", {
NextAction("kaz'rogal assist tanks move in front of boss", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("kaz'rogal low mana bots need escape path", {
NextAction("kaz'rogal spread ranged in arc", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("kaz'rogal bot is low on mana", {
NextAction("kaz'rogal low mana bot take defensive measures", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode("kaz'rogal mark deals shadow damage", {
NextAction("kaz'rogal cast shadow protection spell", ACTION_EMERGENCY + 6) }));
// Azgalor
triggers.push_back(new TriggerNode("azgalor pulling boss", {
NextAction("azgalor misdirect boss to main tank", ACTION_RAID + 3) }));
triggers.push_back(new TriggerNode("azgalor boss engaged by main tank", {
NextAction("azgalor main tank position boss", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("azgalor main tank is positioning boss", {
NextAction("azgalor wait at safe position", ACTION_EMERGENCY + 1) }));
triggers.push_back(new TriggerNode("azgalor boss engaged by ranged", {
NextAction("azgalor disperse ranged", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("azgalor boss casts rain of fire on melee", {
NextAction("azgalor melee get out of fire and swap targets", ACTION_EMERGENCY + 2) }));
triggers.push_back(new TriggerNode("azgalor bot is doomed", {
NextAction("azgalor move to doomguard tank", ACTION_EMERGENCY + 3) }));
triggers.push_back(new TriggerNode("azgalor doomguards must be controlled", {
NextAction("azgalor first assist tank position doomguard", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("azgalor doomguards must die", {
NextAction("azgalor ranged dps prioritize doomguards", ACTION_RAID + 1) }));
// Archimonde
triggers.push_back(new TriggerNode("archimonde pulling boss", {
NextAction("archimonde misdirect boss to main tank", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("archimonde boss engaged by main tank", {
NextAction("archimonde move boss to initial position", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("archimonde boss casts fear", {
NextAction("archimonde cast fear immunity spell", ACTION_RAID + 2) }));
triggers.push_back(new TriggerNode("archimonde boss casts air burst", {
NextAction("archimonde spread to avoid air burst", ACTION_RAID + 1) }));
triggers.push_back(new TriggerNode("archimonde boss summoned doomfire", {
NextAction("archimonde avoid doomfire", ACTION_EMERGENCY + 6) }));
triggers.push_back(new TriggerNode("archimonde bot stood in doomfire", {
NextAction("archimonde remove doomfire dot", ACTION_EMERGENCY + 7) }));
}
void RaidHyjalSummitStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{
// Trash
multipliers.push_back(new HyjalSummitTimeBloodlustAndHeroismMultiplier(botAI));
// Rage Winterchill
multipliers.push_back(new RageWinterchillDisableCombatFormationMoveMultiplier(botAI));
multipliers.push_back(new RageWinterchillMeleeControlAvoidanceMultiplier(botAI));
// Anetheron
multipliers.push_back(new AnetheronDisableTankActionsMultiplier(botAI));
multipliers.push_back(new AnetheronDisableCombatFormationMoveMultiplier(botAI));
multipliers.push_back(new AnetheronControlMisdirectionMultiplier(botAI));
// Kaz'rogal
multipliers.push_back(new KazrogalLowManaBotStayAwayFromGroupMultiplier(botAI));
multipliers.push_back(new KazrogalKeepAspectOfTheViperActiveMultiplier(botAI));
multipliers.push_back(new KazrogalControlMovementMultiplier(botAI));
// Azgalor
multipliers.push_back(new AzgalorDisableTankActionsMultiplier(botAI));
multipliers.push_back(new AzgalorDoomedBotPrioritizePositioningMultiplier(botAI));
multipliers.push_back(new AzgalorMeleeDpsControlAvoidanceMultiplier(botAI));
// Archimonde
multipliers.push_back(new ArchimondeDisableCombatFormationMoveMultiplier(botAI));
}

View File

@ -0,0 +1,22 @@
/*
* 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_RAIDHYJALSUMMITSTRATEGY_H_
#define _PLAYERBOT_RAIDHYJALSUMMITSTRATEGY_H_
#include "Strategy.h"
class RaidHyjalSummitStrategy : public Strategy
{
public:
RaidHyjalSummitStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
std::string const getName() override { return "hyjal"; }
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
void InitMultipliers(std::vector<Multiplier*>& multipliers) override;
};
#endif

View File

@ -0,0 +1,357 @@
/*
* 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 "RaidHyjalSummitTriggers.h"
#include "RaidHyjalSummitHelpers.h"
#include "RaidHyjalSummitActions.h"
#include "AiFactory.h"
#include "Playerbots.h"
#include "RaidBossHelpers.h"
using namespace HyjalSummitHelpers;
// General
bool HyjalSummitBotIsNotInCombatTrigger::IsActive()
{
return !bot->IsInCombat() && bot->GetMapId() == HYJAL_SUMMIT_MAP_ID;
}
// Rage Winterchill
bool RageWinterchillPullingBossTrigger::IsActive()
{
if (bot->getClass() != CLASS_HUNTER)
return false;
Unit* winterchill = AI_VALUE2(Unit*, "find target", "rage winterchill");
return winterchill && winterchill->GetHealthPct() > 95.0f;
}
bool RageWinterchillBossEngagedByMainTankTrigger::IsActive()
{
return botAI->IsMainTank(bot) &&
AI_VALUE2(Unit*, "find target", "rage winterchill");
}
bool RageWinterchillBossCastsDeathAndDecayOnRangedTrigger::IsActive()
{
return botAI->IsRanged(bot) &&
AI_VALUE2(Unit*, "find target", "rage winterchill");
}
bool RageWinterchillMeleeIsStandingInDeathAndDecayTrigger::IsActive()
{
if (botAI->IsRanged(bot))
return false;
Unit* winterchill = AI_VALUE2(Unit*, "find target", "rage winterchill");
if (!winterchill || winterchill->GetVictim() == bot)
return false;
if (botAI->IsMainTank(bot))
return false;
return IsInDeathAndDecay(bot, DEATH_AND_DECAY_SAFE_RADIUS);
}
// Anetheron
bool AnetheronPullingBossOrInfernalTrigger::IsActive()
{
return bot->getClass() == CLASS_HUNTER &&
AI_VALUE2(Unit*, "find target", "anetheron");
}
bool AnetheronBossEngagedByMainTankTrigger::IsActive()
{
return botAI->IsMainTank(bot) && AI_VALUE2(Unit*, "find target", "anetheron");
}
bool AnetheronBossCastsCarrionSwarmTrigger::IsActive()
{
if (botAI->IsMelee(bot))
return false;
Unit* anetheron = AI_VALUE2(Unit*, "find target", "anetheron");
if (!anetheron)
return false;
return GetInfernoTarget(anetheron) != bot;
}
bool AnetheronBotIsTargetedByInfernalTrigger::IsActive()
{
Unit* anetheron = AI_VALUE2(Unit*, "find target", "anetheron");
if (!anetheron || botAI->IsMainTank(bot))
return false;
return GetInfernoTarget(anetheron) == bot;
}
bool AnetheronInfernalsNeedToBeKeptAwayFromRaidTrigger::IsActive()
{
return botAI->IsAssistTankOfIndex(bot, 0, true) &&
AI_VALUE2(Unit*, "find target", "towering infernal");
}
bool AnetheronInfernalsContinueToSpawnTrigger::IsActive()
{
return !botAI->IsTank(bot) && AI_VALUE2(Unit*, "find target", "anetheron");
}
// Kaz'rogal
bool KazrogalPullingBossTrigger::IsActive()
{
if (bot->getClass() != CLASS_HUNTER)
return false;
Unit* kazrogal = AI_VALUE2(Unit*, "find target", "kaz'rogal");
return kazrogal && kazrogal->GetHealthPct() > 95.0f;
}
bool KazrogalBossEngagedByMainTankTrigger::IsActive()
{
return botAI->IsMainTank(bot) && AI_VALUE2(Unit*, "find target", "kaz'rogal");
}
bool KazrogalBossEngagedByAssistTanksTrigger::IsActive()
{
if (!botAI->IsAssistTank(bot))
return false;
if (!AI_VALUE2(Unit*, "find target", "kaz'rogal"))
return false;
return bot->GetPower(POWER_MANA) > 3000;
}
bool KazrogalLowManaBotsNeedEscapePathTrigger::IsActive()
{
if (bot->getClass() == CLASS_WARRIOR || bot->getClass() == CLASS_ROGUE ||
bot->getClass() == CLASS_DEATH_KNIGHT)
return false;
uint8 tab = AiFactory::GetPlayerSpecTab(bot);
if (bot->getClass() == CLASS_DRUID && tab == DRUID_TAB_FERAL)
return false;
if (!AI_VALUE2(Unit*, "find target", "kaz'rogal"))
return false;
if (bot->getClass() == CLASS_HUNTER)
{
return true;
}
else if (bot->GetPower(POWER_MANA) > 4000)
{
isBelowManaThreshold.erase(bot->GetGUID());
if (botAI->IsMelee(bot))
return false;
else
return true;
}
return false;
}
bool KazrogalBotIsLowOnManaTrigger::IsActive()
{
if (bot->getClass() == CLASS_WARRIOR || bot->getClass() == CLASS_ROGUE ||
bot->getClass() == CLASS_DEATH_KNIGHT)
return false;
uint8 tab = AiFactory::GetPlayerSpecTab(bot);
if (bot->getClass() == CLASS_DRUID && tab == DRUID_TAB_FERAL)
return false;
if (!AI_VALUE2(Unit*, "find target", "kaz'rogal"))
return false;
if (botAI->HasAnyAuraOf(bot, "ice block", "divine shield", nullptr))
return false;
if (isBelowManaThreshold.count(bot->GetGUID()) ||
bot->GetPower(POWER_MANA) <= 3200)
return true;
return false;
}
bool KazrogalMarkDealsShadowDamageTrigger::IsActive()
{
if (bot->getClass() != CLASS_PALADIN && bot->getClass() != CLASS_WARLOCK)
return false;
if (!AI_VALUE2(Unit*, "find target", "kaz'rogal"))
return false;
if (bot->getClass() == CLASS_PALADIN &&
(botAI->HasAura("shadow resistance aura", bot) ||
botAI->HasAura("prayer of shadow protection", bot) ||
botAI->HasAura("shadow protection", bot)))
return false;
return bot->HasAura(
static_cast<uint32>(HyjalSummitSpells::SPELL_MARK_OF_KAZROGAL));
}
// Azgalor
bool AzgalorPullingBossTrigger::IsActive()
{
if (bot->getClass() != CLASS_HUNTER)
return false;
Unit* azgalor = AI_VALUE2(Unit*, "find target", "azgalor");
return azgalor && azgalor->GetHealthPct() > 95.0f;
}
bool AzgalorBossEngagedByMainTankTrigger::IsActive()
{
return botAI->IsMainTank(bot) && AI_VALUE2(Unit*, "find target", "azgalor");
}
bool AzgalorMainTankIsPositioningBossTrigger::IsActive()
{
if (botAI->IsRanged(bot))
return false;
Unit* azgalor = AI_VALUE2(Unit*, "find target", "azgalor");
if (!azgalor || azgalor->GetVictim() == bot)
return false;
Player* mainTank = GetGroupMainTank(botAI, bot);
if (!mainTank || !GET_PLAYERBOT_AI(mainTank) || botAI->IsMainTank(bot))
return false;
TankPositionState tankState = GetAzgalorTankPositionState(botAI, bot);
return tankState == TankPositionState::Unknown ||
tankState == TankPositionState::MovingToTransition;
}
bool AzgalorBossEngagedByRangedTrigger::IsActive()
{
if (botAI->IsMelee(bot))
return false;
Unit* azgalor = AI_VALUE2(Unit*, "find target", "azgalor");
return azgalor && azgalor->GetVictim() != bot &&
!bot->HasAura(static_cast<uint32>(HyjalSummitSpells::SPELL_DOOM));
}
bool AzgalorBossCastsRainOfFireOnMeleeTrigger::IsActive()
{
if (botAI->IsRanged(bot) || botAI->IsTank(bot))
return false;
Unit* azgalor = AI_VALUE2(Unit*, "find target", "azgalor");
if (!azgalor || azgalor->GetVictim() == bot ||
bot->HasAura(static_cast<uint32>(HyjalSummitSpells::SPELL_DOOM)))
return false;
return IsInRainOfFire(bot, RAIN_OF_FIRE_RADIUS);
}
bool AzgalorBotIsDoomedTrigger::IsActive()
{
return bot->HasAura(static_cast<uint32>(HyjalSummitSpells::SPELL_DOOM));
}
bool AzgalorDoomguardsMustBeControlledTrigger::IsActive()
{
if (!botAI->IsAssistTank(bot) ||
!AI_VALUE2(Unit*, "find target", "azgalor"))
return false;
if (botAI->IsAssistTankOfIndex(bot, 0, true))
{
return AI_VALUE2(Unit*, "find target", "lesser doomguard") ||
AnyGroupMemberHasDoom(bot);
}
if (botAI->IsAssistTankOfIndex(bot, 1, true))
{
// Trigger for second assist tank only if first assist tank has Doom
Player* firstAssistTank = GetGroupAssistTank(botAI, bot, 0);
if (firstAssistTank &&
!firstAssistTank->HasAura(static_cast<uint32>(HyjalSummitSpells::SPELL_DOOM)))
return false;
return AI_VALUE2(Unit*, "find target", "lesser doomguard") ||
AnyGroupMemberHasDoom(bot);
}
return false;
}
bool AzgalorDoomguardsMustDieTrigger::IsActive()
{
return botAI->IsRangedDps(bot) && AI_VALUE2(Unit*, "find target", "azgalor");
}
// Archimonde
bool ArchimondePullingBossTrigger::IsActive()
{
if (bot->getClass() != CLASS_HUNTER)
return false;
Unit* archimonde = AI_VALUE2(Unit*, "find target", "archimonde");
return archimonde && archimonde->GetHealthPct() > 95.0f;
}
bool ArchimondeBossEngagedByMainTankTrigger::IsActive()
{
if (!botAI->IsMainTank(bot))
return false;
Unit* archimonde = AI_VALUE2(Unit*, "find target", "archimonde");
return archimonde && archimonde->GetHealthPct() > 95.0f;
}
bool ArchimondeBossCastsFearTrigger::IsActive()
{
if (bot->getClass() != CLASS_PRIEST &&
bot->getClass() != CLASS_SHAMAN)
return false;
Unit* archimonde = AI_VALUE2(Unit*, "find target", "archimonde");
return archimonde && archimonde->GetHealthPct() > 10.0f;
}
bool ArchimondeBossCastsAirBurstTrigger::IsActive()
{
Unit* archimonde = AI_VALUE2(Unit*, "find target", "archimonde");
if (!archimonde || archimonde->GetHealthPct() <= 10.0f ||
archimonde->GetVictim() == bot)
return false;
return !botAI->IsMainTank(bot);
}
bool ArchimondeBossSummonedDoomfireTrigger::IsActive()
{
Unit* archimonde = AI_VALUE2(Unit*, "find target", "archimonde");
if (!archimonde || archimonde->GetHealthPct() <= 10.0f)
return false;
// If I don't make an exception, bots actually refuse to enter the
// Doomfire even when feared
return !bot->HasAura(
static_cast<uint32>(HyjalSummitSpells::SPELL_ARCHIMONDE_FEAR));
}
bool ArchimondeBotStoodInDoomfireTrigger::IsActive()
{
if (bot->getClass() != CLASS_MAGE && bot->getClass() != CLASS_ROGUE &&
bot->getClass() != CLASS_PALADIN)
return false;
return bot->GetHealthPct() < 40.0f &&
(bot->HasAura(static_cast<uint32>(HyjalSummitSpells::SPELL_DOOMFIRE)) ||
bot->HasAura(static_cast<uint32>(HyjalSummitSpells::SPELL_DOOMFIRE_DOT)));
}

View File

@ -0,0 +1,271 @@
/*
* 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_RAIDHYJALSUMMITTRIGGERS_H
#define _PLAYERBOT_RAIDHYJALSUMMITTRIGGERS_H
#include "Trigger.h"
// General
class HyjalSummitBotIsNotInCombatTrigger : public Trigger
{
public:
HyjalSummitBotIsNotInCombatTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "hyjal summit bot is not in combat") {}
bool IsActive() override;
};
// Rage Winterchill
class RageWinterchillPullingBossTrigger : public Trigger
{
public:
RageWinterchillPullingBossTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "rage winterchill pulling boss") {}
bool IsActive() override;
};
class RageWinterchillBossEngagedByMainTankTrigger : public Trigger
{
public:
RageWinterchillBossEngagedByMainTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "rage winterchill boss engaged by main tank") {}
bool IsActive() override;
};
class RageWinterchillBossCastsDeathAndDecayOnRangedTrigger : public Trigger
{
public:
RageWinterchillBossCastsDeathAndDecayOnRangedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "rage winterchill boss casts death and decay on ranged") {}
bool IsActive() override;
};
class RageWinterchillMeleeIsStandingInDeathAndDecayTrigger : public Trigger
{
public:
RageWinterchillMeleeIsStandingInDeathAndDecayTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "rage winterchill melee is standing in death and decay") {}
bool IsActive() override;
};
// Anetheron
class AnetheronPullingBossOrInfernalTrigger : public Trigger
{
public:
AnetheronPullingBossOrInfernalTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "anetheron pulling boss or infernal") {}
bool IsActive() override;
};
class AnetheronBossEngagedByMainTankTrigger : public Trigger
{
public:
AnetheronBossEngagedByMainTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "anetheron boss engaged by main tank") {}
bool IsActive() override;
};
class AnetheronBossCastsCarrionSwarmTrigger : public Trigger
{
public:
AnetheronBossCastsCarrionSwarmTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "anetheron boss casts carrion swarm") {}
bool IsActive() override;
};
class AnetheronBotIsTargetedByInfernalTrigger : public Trigger
{
public:
AnetheronBotIsTargetedByInfernalTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "anetheron bot is targeted by infernal") {}
bool IsActive() override;
};
class AnetheronInfernalsNeedToBeKeptAwayFromRaidTrigger : public Trigger
{
public:
AnetheronInfernalsNeedToBeKeptAwayFromRaidTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "anetheron infernals need to be kept away from raid") {}
bool IsActive() override;
};
class AnetheronInfernalsContinueToSpawnTrigger : public Trigger
{
public:
AnetheronInfernalsContinueToSpawnTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "anetheron infernals continue to spawn") {}
bool IsActive() override;
};
// Kaz'rogal
class KazrogalPullingBossTrigger : public Trigger
{
public:
KazrogalPullingBossTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kaz'rogal pulling boss") {}
bool IsActive() override;
};
class KazrogalBossEngagedByMainTankTrigger : public Trigger
{
public:
KazrogalBossEngagedByMainTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kaz'rogal boss engaged by main tank") {}
bool IsActive() override;
};
class KazrogalBossEngagedByAssistTanksTrigger : public Trigger
{
public:
KazrogalBossEngagedByAssistTanksTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kaz'rogal boss engaged by assist tanks") {}
bool IsActive() override;
};
class KazrogalLowManaBotsNeedEscapePathTrigger : public Trigger
{
public:
KazrogalLowManaBotsNeedEscapePathTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kaz'rogal low mana bots need escape path") {}
bool IsActive() override;
};
class KazrogalBotIsLowOnManaTrigger : public Trigger
{
public:
KazrogalBotIsLowOnManaTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kaz'rogal bot is low on mana") {}
bool IsActive() override;
};
class KazrogalMarkDealsShadowDamageTrigger : public Trigger
{
public:
KazrogalMarkDealsShadowDamageTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "kaz'rogal mark deals shadow damage") {}
bool IsActive() override;
};
// Azgalor
class AzgalorPullingBossTrigger : public Trigger
{
public:
AzgalorPullingBossTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "azgalor pulling boss") {}
bool IsActive() override;
};
class AzgalorBossEngagedByMainTankTrigger : public Trigger
{
public:
AzgalorBossEngagedByMainTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "azgalor boss engaged by main tank") {}
bool IsActive() override;
};
class AzgalorMainTankIsPositioningBossTrigger : public Trigger
{
public:
AzgalorMainTankIsPositioningBossTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "azgalor main tank is positioning boss") {}
bool IsActive() override;
};
class AzgalorBossEngagedByRangedTrigger : public Trigger
{
public:
AzgalorBossEngagedByRangedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "azgalor boss engaged by ranged") {}
bool IsActive() override;
};
class AzgalorBossCastsRainOfFireOnMeleeTrigger : public Trigger
{
public:
AzgalorBossCastsRainOfFireOnMeleeTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "azgalor boss casts rain of fire on melee") {}
bool IsActive() override;
};
class AzgalorBotIsDoomedTrigger : public Trigger
{
public:
AzgalorBotIsDoomedTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "azgalor bot is doomed") {}
bool IsActive() override;
};
class AzgalorDoomguardsMustBeControlledTrigger : public Trigger
{
public:
AzgalorDoomguardsMustBeControlledTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "azgalor doomguards must be controlled") {}
bool IsActive() override;
};
class AzgalorDoomguardsMustDieTrigger : public Trigger
{
public:
AzgalorDoomguardsMustDieTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "azgalor doomguards must die") {}
bool IsActive() override;
};
// Archimonde
class ArchimondePullingBossTrigger : public Trigger
{
public:
ArchimondePullingBossTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "archimonde pulling boss") {}
bool IsActive() override;
};
class ArchimondeBossEngagedByMainTankTrigger : public Trigger
{
public:
ArchimondeBossEngagedByMainTankTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "archimonde boss engaged by main tank") {}
bool IsActive() override;
};
class ArchimondeBossCastsFearTrigger : public Trigger
{
public:
ArchimondeBossCastsFearTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "archimonde boss casts fear") {}
bool IsActive() override;
};
class ArchimondeBossCastsAirBurstTrigger : public Trigger
{
public:
ArchimondeBossCastsAirBurstTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "archimonde boss casts air burst") {}
bool IsActive() override;
};
class ArchimondeBossSummonedDoomfireTrigger : public Trigger
{
public:
ArchimondeBossSummonedDoomfireTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "archimonde boss summoned doomfire") {}
bool IsActive() override;
};
class ArchimondeBotStoodInDoomfireTrigger : public Trigger
{
public:
ArchimondeBotStoodInDoomfireTrigger(
PlayerbotAI* botAI) : Trigger(botAI, "archimonde bot stood in doomfire") {}
bool IsActive() override;
};
#endif

View File

@ -0,0 +1,268 @@
/*
* 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 "RaidHyjalSummitHelpers.h"
#include <algorithm>
#include "Playerbots.h"
#include "RaidBossHelpers.h"
#include "Timer.h"
namespace HyjalSummitHelpers
{
// General
bool GetGroundedStepPosition(
Player* bot, float destinationX, float destinationY, float moveDist,
float& stepX, float& stepY, float& stepZ)
{
const float distance = bot->GetExactDist2d(destinationX, destinationY);
if (distance <= 0.0f)
return false;
const float stepDistance = std::min(moveDist, distance);
const float deltaX = destinationX - bot->GetPositionX();
const float deltaY = destinationY - bot->GetPositionY();
stepX = bot->GetPositionX() + (deltaX / distance) * stepDistance;
stepY = bot->GetPositionY() + (deltaY / distance) * stepDistance;
stepZ = bot->GetMapWaterOrGroundLevel(stepX, stepY, bot->GetPositionZ());
if (stepZ <= INVALID_HEIGHT)
stepZ = bot->GetPositionZ();
bot->GetMap()->CheckCollisionAndGetValidCoords(
bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
stepX, stepY, stepZ, false);
return true;
}
RangedGroups GetRangedGroups(PlayerbotAI* botAI, Player* bot)
{
RangedGroups result;
Group* group = bot->GetGroup();
if (!group)
return result;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !botAI->IsRanged(member))
continue;
if (botAI->IsHeal(member))
result.healers.push_back(member);
else
result.rangedDps.push_back(member);
}
return result;
}
std::pair<size_t, size_t> GetBotCircleIndexAndCount(PlayerbotAI* botAI, Player* bot,
const RangedGroups& groups)
{
const std::vector<Player*>& vec = botAI->IsHeal(bot) ? groups.healers : groups.rangedDps;
auto it = std::find(vec.begin(), vec.end(), bot);
size_t index = (it != vec.end()) ? std::distance(vec.begin(), it) : 0;
return {index, vec.size()};
}
// Rage Winterchill
const Position WINTERCHILL_TANK_POSITION = { 5031.061f, -1784.521f, 1321.626f };
std::unordered_map<ObjectGuid, bool> hasReachedWinterchillPosition;
std::unordered_map<uint32, DeathAndDecayData> deathAndDecayPosition;
DeathAndDecayData* GetActiveWinterchillDeathAndDecay(uint32 instanceId)
{
auto instanceIt = deathAndDecayPosition.find(instanceId);
if (instanceIt == deathAndDecayPosition.end())
return nullptr;
const uint32 now = getMSTime();
const uint32 elapsed = getMSTimeDiff(instanceIt->second.spawnTime, now);
if (elapsed >= DEATH_AND_DECAY_REACQUIRE_DELAY)
{
deathAndDecayPosition.erase(instanceIt);
return nullptr;
}
if (elapsed >= DEATH_AND_DECAY_DURATION)
return nullptr;
return &instanceIt->second;
}
bool IsInDeathAndDecay(Player* bot, float radius)
{
const uint32 instanceId = bot->GetMap()->GetInstanceId();
Aura* aura = bot->GetAura(static_cast<uint32>(HyjalSummitSpells::SPELL_DEATH_AND_DECAY));
if (aura)
{
DynamicObject* dynObj = aura->GetDynobjOwner();
if (dynObj && dynObj->IsInWorld())
{
const uint32 now = getMSTime();
auto instanceIt = deathAndDecayPosition.find(instanceId);
if (instanceIt == deathAndDecayPosition.end() ||
getMSTimeDiff(instanceIt->second.spawnTime, now) >= DEATH_AND_DECAY_REACQUIRE_DELAY)
{
deathAndDecayPosition[instanceId] =
DeathAndDecayData{ dynObj->GetPosition(), now };
}
}
}
DeathAndDecayData* data = GetActiveWinterchillDeathAndDecay(instanceId);
if (!data)
return false;
return bot->GetExactDist2d(data->position) < radius;
}
// Anetheron
const Position ANETHERON_TANK_POSITION = { 5033.177f, -1765.996f, 1324.195f };
const Position ANETHERON_E_INFERNAL_POSITION = { 5016.578f, -1800.233f, 1323.070f };
const Position ANETHERON_W_INFERNAL_POSITION = { 5048.911f, -1722.164f, 1321.408f };
std::unordered_map<ObjectGuid, bool> hasReachedAnetheronPosition;
Player* GetInfernoTarget(Unit* anetheron)
{
if (!anetheron)
return nullptr;
Spell* spell = anetheron->GetCurrentSpell(CURRENT_GENERIC_SPELL);
if (spell && spell->m_spellInfo->Id ==
static_cast<uint32>(HyjalSummitSpells::SPELL_INFERNO))
{
Unit* spellTarget = spell->m_targets.GetUnitTarget();
if (spellTarget && spellTarget->IsPlayer())
return spellTarget->ToPlayer();
}
return nullptr;
}
const Position& GetClosestInfernalTankPosition(Player* bot)
{
const Position& east = ANETHERON_E_INFERNAL_POSITION;
const Position& west = ANETHERON_W_INFERNAL_POSITION;
return (bot->GetExactDist2d(east.GetPositionX(), east.GetPositionY()) <=
bot->GetExactDist2d(west.GetPositionX(), west.GetPositionY())) ? east : west;
}
// Kaz'rogal
const Position KAZROGAL_TANK_TRANSITION_POSITION = { 5528.792f, -2636.486f, 1481.293f };
const Position KAZROGAL_TANK_FINAL_POSITION = { 5511.514f, -2662.466f, 1480.288f };
std::unordered_map<ObjectGuid, TankPositionState> kazrogalTankStep;
std::unordered_map<ObjectGuid, bool> isBelowManaThreshold;
TankPositionState GetKazrogalTankPositionState(PlayerbotAI* botAI, Player* bot)
{
Player* mainTank = GetGroupMainTank(botAI, bot);
if (!mainTank)
return TankPositionState::Unknown;
auto it = kazrogalTankStep.find(mainTank->GetGUID());
if (it != kazrogalTankStep.end())
return it->second;
return TankPositionState::Unknown;
}
// Azgalor
const Position AZGALOR_TANK_TRANSITION_POSITION = { 5486.787f, -2696.215f, 1482.007f };
const Position AZGALOR_TANK_FINAL_POSITION = { 5496.379f, -2675.265f, 1481.053f };
const Position AZGALOR_DOOMGUARD_POSITION = { 5485.555f, -2731.659f, 1485.555f };
std::unordered_map<ObjectGuid, TankPositionState> azgalorTankStep;
std::unordered_map<uint32, RainOfFireData> rainOfFirePosition;
RainOfFireData* GetActiveAzgalorRainOfFire(uint32 instanceId)
{
auto instanceIt = rainOfFirePosition.find(instanceId);
if (instanceIt == rainOfFirePosition.end())
return nullptr;
const uint32 now = getMSTime();
const uint32 elapsed = getMSTimeDiff(instanceIt->second.spawnTime, now);
if (elapsed >= RAIN_OF_FIRE_REACQUIRE_DELAY)
{
rainOfFirePosition.erase(instanceIt);
return nullptr;
}
if (elapsed >= RAIN_OF_FIRE_DURATION)
return nullptr;
return &instanceIt->second;
}
TankPositionState GetAzgalorTankPositionState(PlayerbotAI* botAI, Player* bot)
{
Player* mainTank = GetGroupMainTank(botAI, bot);
if (!mainTank)
return TankPositionState::Unknown;
auto it = azgalorTankStep.find(mainTank->GetGUID());
if (it != azgalorTankStep.end())
return it->second;
return TankPositionState::Unknown;
}
bool IsInRainOfFire(Player* bot, float radius)
{
RainOfFireData* data = GetActiveAzgalorRainOfFire(bot->GetMap()->GetInstanceId());
if (!data)
return false;
return bot->GetExactDist2d(data->position) < radius;
}
bool AnyGroupMemberHasDoom(Player* bot)
{
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member &&
member->HasAura(static_cast<uint32>(HyjalSummitSpells::SPELL_DOOM)))
return true;
}
}
return false;
}
// Archimonde
const Position ARCHIMONDE_INITIAL_POSITION = { 5640.502f, -3421.238f, 1587.453f };
std::unordered_map<uint32, AirBurstData> archimondeAirBurstTargets;
std::unordered_map<uint32, std::vector<DoomfireTrailData>> doomfireTrails;
std::unordered_map<ObjectGuid, uint32> doomfireLastSampleTime;
AirBurstData* GetRecentArchimondeAirBurst(uint32 instanceId)
{
auto instanceIt = archimondeAirBurstTargets.find(instanceId);
if (instanceIt == archimondeAirBurstTargets.end())
return nullptr;
constexpr uint32 airBurstReactionWindow = 2000;
const uint32 now = getMSTime();
if (getMSTimeDiff(instanceIt->second.castTime, now) >= airBurstReactionWindow)
{
archimondeAirBurstTargets.erase(instanceIt);
return nullptr;
}
return &instanceIt->second;
}
}

View File

@ -0,0 +1,143 @@
/*
* 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_RAIDHYJALSUMMITHELPERS_H_
#define _PLAYERBOT_RAIDHYJALSUMMITHELPERS_H_
#include <unordered_map>
#include <utility>
#include <vector>
#include "AiObject.h"
#include "Position.h"
#include "Unit.h"
namespace HyjalSummitHelpers
{
enum class HyjalSummitSpells : uint32
{
// Rage Winterchill
SPELL_DEATH_AND_DECAY = 31258,
// Anetheron
SPELL_INFERNO = 31299,
// Kaz'rogal
SPELL_MARK_OF_KAZROGAL = 31447,
// Azgalor
SPELL_RAIN_OF_FIRE = 31340,
SPELL_DOOM = 31347,
// Archimonde
SPELL_DOOMFIRE = 31944, // Damaging part of trail
SPELL_DOOMFIRE_DOT = 31969, // DoT after exiting trail
SPELL_ARCHIMONDE_FEAR = 31970,
SPELL_AIR_BURST = 32014,
// Hunter
SPELL_MISDIRECTION = 35079,
// Priest
SPELL_FEAR_WARD = 6346,
};
enum class HyjalSummitNpcs : uint32
{
// Archimonde
NPC_DOOMFIRE = 18095,
};
enum class TankPositionState : uint8
{
MovingToTransition = 0,
MovingToFinal = 1,
Positioned = 2,
Unknown = 255,
};
// General
constexpr uint32 HYJAL_SUMMIT_MAP_ID = 534;
struct RangedGroups
{
std::vector<Player*> healers;
std::vector<Player*> rangedDps;
};
bool GetGroundedStepPosition(
Player* bot, float destinationX, float destinationY, float moveDist,
float& stepX, float& stepY, float& stepZ);
RangedGroups GetRangedGroups(PlayerbotAI* botAI, Player* bot);
std::pair<size_t, size_t> GetBotCircleIndexAndCount(PlayerbotAI* botAI, Player* bot,
const RangedGroups& groups);
// Rage Winterchill
extern const Position WINTERCHILL_TANK_POSITION;
extern std::unordered_map<ObjectGuid, bool> hasReachedWinterchillPosition;
constexpr uint32 DEATH_AND_DECAY_DURATION = 15000;
constexpr uint32 DEATH_AND_DECAY_REACQUIRE_DELAY = 20000;
constexpr float DEATH_AND_DECAY_SAFE_RADIUS = 22.0f; // 20y radius + 1.5y player hitbox + 0.5y buffer
struct DeathAndDecayData
{
Position position;
uint32 spawnTime;
};
extern std::unordered_map<uint32, DeathAndDecayData> deathAndDecayPosition;
DeathAndDecayData* GetActiveWinterchillDeathAndDecay(uint32 instanceId);
bool IsInDeathAndDecay(Player* bot, float radius);
// Anetheron
extern const Position ANETHERON_TANK_POSITION;
extern const Position ANETHERON_E_INFERNAL_POSITION;
extern const Position ANETHERON_W_INFERNAL_POSITION;
extern std::unordered_map<ObjectGuid, bool> hasReachedAnetheronPosition;
Player* GetInfernoTarget(Unit* anetheron);
const Position& GetClosestInfernalTankPosition(Player* bot);
// Kaz'rogal
extern const Position KAZROGAL_TANK_TRANSITION_POSITION;
extern const Position KAZROGAL_TANK_FINAL_POSITION;
extern std::unordered_map<ObjectGuid, TankPositionState> kazrogalTankStep;
extern std::unordered_map<ObjectGuid, bool> isBelowManaThreshold;
TankPositionState GetKazrogalTankPositionState(PlayerbotAI* botAI, Player* bot);
// Azgalor
extern const Position AZGALOR_TANK_TRANSITION_POSITION;
extern const Position AZGALOR_TANK_FINAL_POSITION;
extern const Position AZGALOR_DOOMGUARD_POSITION;
extern std::unordered_map<ObjectGuid, TankPositionState> azgalorTankStep;
constexpr uint32 RAIN_OF_FIRE_DURATION = 10000;
constexpr uint32 RAIN_OF_FIRE_REACQUIRE_DELAY = 15000;
constexpr float RAIN_OF_FIRE_RADIUS = 17.0f; // 15y radius + 1.5y player hitbox + 0.5y buffer
struct RainOfFireData
{
Position position;
uint32 spawnTime;
};
extern std::unordered_map<uint32, RainOfFireData> rainOfFirePosition;
TankPositionState GetAzgalorTankPositionState(PlayerbotAI* botAI, Player* bot);
RainOfFireData* GetActiveAzgalorRainOfFire(uint32 instanceId);
bool IsInRainOfFire(Player* bot, float radius);
bool AnyGroupMemberHasDoom(Player* bot);
// Archimonde
constexpr float AIR_BURST_SAFE_DISTANCE = 15.0f;
struct AirBurstData
{
ObjectGuid targetGuid;
uint32 castTime;
};
struct DoomfireTrailData
{
Position position;
uint32 recordTime;
};
extern const Position ARCHIMONDE_INITIAL_POSITION;
extern std::unordered_map<uint32, AirBurstData> archimondeAirBurstTargets;
extern std::unordered_map<uint32, std::vector<DoomfireTrailData>> doomfireTrails;
extern std::unordered_map<ObjectGuid, uint32> doomfireLastSampleTime;
AirBurstData* GetRecentArchimondeAirBurst(uint32 instanceId);
}
#endif

View File

@ -0,0 +1,211 @@
/*
* 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 "RaidHyjalSummitHelpers.h"
#include "AllCreatureScript.h"
#include "ObjectAccessor.h"
#include "Player.h"
#include "RaidBossHelpers.h"
#include "DynamicObjectScript.h"
#include "Playerbots.h"
#include "ScriptMgr.h"
#include "Spell.h"
#include "Timer.h"
using namespace HyjalSummitHelpers;
static Player* GetFirstPlayerSpellTarget(Spell* spell, Unit* caster)
{
if (!spell || !caster)
return nullptr;
if (Unit* unitTarget = spell->m_targets.GetUnitTarget())
return unitTarget->ToPlayer();
std::list<TargetInfo> const& targets = *spell->GetUniqueTargetInfo();
for (TargetInfo const& targetInfo : targets)
{
if (Player* target = ObjectAccessor::GetPlayer(*caster, targetInfo.targetGUID))
return target;
}
return nullptr;
}
static bool ShouldInterruptForArchimondeAirBurst(PlayerbotAI* botAI, Player* bot, Player* target)
{
if (!target)
return false;
Player* mainTank = GetGroupMainTank(botAI, bot);
if (!mainTank || bot == mainTank)
return false;
float distanceToMainTank = bot->GetExactDist2d(mainTank);
return (target == mainTank || target == bot) &&
distanceToMainTank < AIR_BURST_SAFE_DISTANCE;
}
// Records the active Rain of Fire dynamic object so that melee bots can avoid it by running
// away from Azgalor or swapping to a Doomguard; the standard FleePosition() logic to avoid aoe
// can take melee in front of Azgalor, resulting in them getting cleaved
class AzgalorRainOfFireScript : public DynamicObjectScript
{
public:
AzgalorRainOfFireScript() : DynamicObjectScript("AzgalorRainOfFireScript") {}
void OnUpdate(DynamicObject* dynobj, uint32 /*diff*/) override
{
if (dynobj->GetSpellId() != static_cast<uint32>(HyjalSummitSpells::SPELL_RAIN_OF_FIRE))
return;
uint32 instanceId = dynobj->GetMap()->GetInstanceId();
if (GetActiveAzgalorRainOfFire(instanceId))
return;
uint32 now = getMSTime();
auto instanceIt = rainOfFirePosition.find(instanceId);
if (instanceIt != rainOfFirePosition.end() &&
getMSTimeDiff(instanceIt->second.spawnTime, now) < RAIN_OF_FIRE_REACQUIRE_DELAY)
{
return;
}
bool shouldTrackRainOfFire = false;
Map::PlayerList const& players = dynobj->GetMap()->GetPlayers();
for (Map::PlayerList::const_iterator it = players.begin(); it != players.end(); ++it)
{
Player* player = it->GetSource();
if (!player || !player->IsAlive())
continue;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(player);
if (!botAI || !botAI->HasStrategy("hyjal", BOT_STATE_COMBAT))
continue;
shouldTrackRainOfFire = true;
break;
}
if (!shouldTrackRainOfFire)
return;
rainOfFirePosition[instanceId] = RainOfFireData{ dynobj->GetPosition(), now };
}
};
// Records the position of each Doomfire NPC at regular intervals so that bots can avoid
// the persistent fire trail it leaves behind. Each sample is tagged with a timestamp and
// expires after TRAIL_DURATION ms, matching the lifetime of a Doomfire DynamicObject (18s)
class ArchimondeDoomfireTrailScript : public AllCreatureScript
{
public:
ArchimondeDoomfireTrailScript() : AllCreatureScript("ArchimondeDoomfireTrailScript") {}
void OnAllCreatureUpdate(Creature* creature, uint32 /*diff*/) override
{
if (creature->GetEntry() != static_cast<uint32>(HyjalSummitNpcs::NPC_DOOMFIRE))
return;
uint32 now = getMSTime();
ObjectGuid guid = creature->GetGUID();
auto& lastSample = doomfireLastSampleTime[guid];
if (getMSTimeDiff(lastSample, now) < 500)
return;
lastSample = now;
uint32 instanceId = creature->GetMap()->GetInstanceId();
auto& trail = doomfireTrails[instanceId];
DoomfireTrailData data;
data.position = creature->GetPosition();
data.recordTime = now;
trail.push_back(data);
constexpr uint32 TRAIL_DURATION = 18000;
trail.erase(std::remove_if(trail.begin(), trail.end(),
[now](const DoomfireTrailData& d)
{
return getMSTimeDiff(d.recordTime, now) > TRAIL_DURATION;
}), trail.end());
constexpr float DOOMFIRE_DANGER_RANGE = 10.0f;
Map::PlayerList const& players = creature->GetMap()->GetPlayers();
for (Map::PlayerList::const_iterator it = players.begin(); it != players.end(); ++it)
{
Player* player = it->GetSource();
if (!player || !player->IsAlive())
continue;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(player);
if (!botAI || !botAI->HasStrategy("hyjal", BOT_STATE_COMBAT) ||
creature->GetDistance(player) > DOOMFIRE_DANGER_RANGE)
{
continue;
}
botAI->RequestSpellInterrupt();
}
}
void OnCreatureRemoveWorld(Creature* creature) override
{
if (creature->GetEntry() != static_cast<uint32>(HyjalSummitNpcs::NPC_DOOMFIRE))
return;
doomfireLastSampleTime.erase(creature->GetGUID());
}
};
class ArchimondeAirBurstSpellListenerScript : public AllSpellScript
{
public:
ArchimondeAirBurstSpellListenerScript() :
AllSpellScript("ArchimondeAirBurstSpellListenerScript") {}
void OnSpellCast(
Spell* spell, Unit* caster, SpellInfo const* spellInfo, bool /*skipCheck*/) override
{
if (!spell || !caster || !spellInfo)
return;
if (spellInfo->Id != static_cast<uint32>(HyjalSummitSpells::SPELL_AIR_BURST))
return;
Player* target = GetFirstPlayerSpellTarget(spell, caster);
if (!target)
return;
archimondeAirBurstTargets[caster->GetMap()->GetInstanceId()] =
AirBurstData{ target->GetGUID(), getMSTime() };
Map::PlayerList const& players = caster->GetMap()->GetPlayers();
for (Map::PlayerList::const_iterator it = players.begin(); it != players.end(); ++it)
{
Player* player = it->GetSource();
if (!player || !player->IsAlive())
continue;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(player);
if (!botAI || !botAI->HasStrategy("hyjal", BOT_STATE_COMBAT) ||
!ShouldInterruptForArchimondeAirBurst(botAI, player, target))
{
continue;
}
botAI->RequestSpellInterrupt();
}
}
};
void AddSC_HyjalSummitBotScripts()
{
new AzgalorRainOfFireScript();
new ArchimondeDoomfireTrailScript();
new ArchimondeAirBurstSpellListenerScript();
}

View File

@ -1,7 +1,6 @@
#ifndef _PLAYERBOT_RAIDICCTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDICCTRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h"
#include "RaidIccTriggers.h"

View File

@ -1,10 +1,7 @@
#ifndef _PLAYERBOT_RAIDICCSTRATEGY_H
#define _PLAYERBOT_RAIDICCSTRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h"
#include "RaidIccMultipliers.h"
class RaidIccStrategy : public Strategy
{

View File

@ -2,7 +2,7 @@
#define _PLAYERBOT_RAIDKARAZHANTRIGGERCONTEXT_H
#include "RaidKarazhanTriggers.h"
#include "AiObjectContext.h"
#include "NamedObjectContext.h"
class RaidKarazhanTriggerContext : public NamedObjectContext<Trigger>
{

View File

@ -2,7 +2,6 @@
#define _PLAYERBOT_RAIDKARAZHANSTRATEGY_H_
#include "Strategy.h"
#include "Multiplier.h"
class RaidKarazhanStrategy : public Strategy
{

View File

@ -2,7 +2,7 @@
#define _PLAYERBOT_RAIDMAGTHERIDONTRIGGERCONTEXT_H
#include "RaidMagtheridonTriggers.h"
#include "AiObjectContext.h"
#include "NamedObjectContext.h"
class RaidMagtheridonTriggerContext : public NamedObjectContext<Trigger>
{

View File

@ -2,7 +2,6 @@
#define _PLAYERBOT_RAIDMAGTHERIDONSTRATEGY_H
#include "Strategy.h"
#include "Multiplier.h"
class RaidMagtheridonStrategy : public Strategy
{

View File

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

View File

@ -1,8 +1,6 @@
#ifndef _PLAYERBOT_RAIDMCSTRATEGY_H
#define _PLAYERBOT_RAIDMCSTRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h"
class RaidMcStrategy : public Strategy

View File

@ -6,7 +6,6 @@
#ifndef _PLAYERBOT_RAIDNAXXTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDNAXXTRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h"
#include "RaidNaxxTriggers.h"

View File

@ -2,8 +2,6 @@
#ifndef _PLAYERBOT_RAIDNAXXSTRATEGY_H
#define _PLAYERBOT_RAIDNAXXSTRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h"
class RaidNaxxStrategy : public Strategy

View File

@ -1,7 +1,6 @@
#ifndef _PLAYERBOT_RAIDOSTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDOSTRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h"
#include "RaidOsTriggers.h"

View File

@ -1,8 +1,6 @@
#ifndef _PLAYERBOT_RAIDOSSTRATEGY_H
#define _PLAYERBOT_RAIDOSSTRATEGY_H
#include "AiObjectContext.h"
#include "Multiplier.h"
#include "Strategy.h"
class RaidOsStrategy : public Strategy

View File

@ -99,8 +99,12 @@ bool RaidOnyxiaMoveToSafeZoneAction::Execute(Event /*event*/)
if (bot->IsWithinDist2d(bestZone->pos.GetPositionX(), bestZone->pos.GetPositionY(), bestZone->radius))
return false; // Already safe
// Stop current spell first
bot->AttackStop();
bot->InterruptNonMeleeSpells(false);
// bot->Yell("Moving to Safe Zone!", LANG_UNIVERSAL);
return MoveTo(bot->GetMapId(), bestZone->pos.GetPositionX(), bestZone->pos.GetPositionY(), bot->GetPositionZ(),
return MoveTo(bot->GetMapId(), bestZone->pos.GetPositionX(), bestZone->pos.GetPositionY(), bestZone->pos.GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}

View File

@ -2,7 +2,6 @@
#ifndef _PLAYERBOT_RAIDONYXIAACTIONS_H_
#define _PLAYERBOT_RAIDONYXIAACTIONS_H_
#include "Action.h"
#include "AttackAction.h"
#include "GenericSpellActions.h"
#include "MovementActions.h"
@ -45,42 +44,45 @@ public:
bool Execute(Event event) override;
private:
std::vector<SafeZone> GetSafeZonesForBreath(uint32 spellId)
static std::vector<SafeZone> GetSafeZonesForBreath(uint32 spellId)
{
// Define your safe zone coordinates based on the map
// Example assumes Onyxia's lair map coordinates
float z = bot->GetPositionZ(); // Stay at current height
// Safe zone coordinates based on the map
// Assumes Onyxia's lair map coordinates
switch (spellId)
{
case 17086: // N to S
case 18351: // S to N
return {SafeZone{Position(-10.0f, -180.0f, z), 5.0f},
SafeZone{Position(-20.0f, -250.0f, z), 5.0f}}; // Bottom Safe Zone
return {
SafeZone{Position(-10.0f, -180.0f, -87.0f), 5.0f},
SafeZone{Position(-20.0f, -250.0f, -88.0f), 5.0f}
}; // Bottom Safe Zone
case 18576: // E to W
case 18609: // W to E
return {
SafeZone{Position(20.0f, -210.0f, z), 5.0f},
SafeZone{Position(-75.0f, -210.0f, z), 5.0f},
SafeZone{Position(20.0f, -210.0f, -85.5f), 5.0f},
SafeZone{Position(-75.0f, -210.0f, -83.4f), 5.0f},
}; // Left Safe Zone
case 18564: // SE to NW
case 18584: // NW to SE
return {
SafeZone{Position(-60.0f, -195.0f, z), 5.0f},
SafeZone{Position(10.0f, -240.0f, z), 5.0f},
SafeZone{Position(-60.0f, -195.0f, -85.0f), 5.0f},
SafeZone{Position(10.0f, -240.0f, -85.9f), 5.0f},
}; // NW Safe Zone
case 18596: // SW to NE
case 18617: // NE to SW
return {
SafeZone{Position(7.0f, -185.0f, z), 5.0f},
SafeZone{Position(-60.0f, -240.0f, z), 5.0f},
SafeZone{Position(7.0f, -185.0f, -86.2f), 5.0f},
SafeZone{Position(-60.0f, -240.0f, -85.2f), 5.0f},
}; // NE Safe Zone
default:
return {SafeZone{Position(0.0f, 0.0f, z), 5.0f}}; // Fallback center - shouldn't ever happen
return {
SafeZone{Position(-40.0f, -214.0f, -86.6f), 5.0f}
}; // Fallback center - shouldn't ever happen
}
}
};

View File

@ -1,7 +1,6 @@
#ifndef _PLAYERBOT_RAIDONYXIATRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDONYXIATRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h"
#include "RaidOnyxiaTriggers.h"

View File

@ -11,6 +11,7 @@
#include "RaidNaxxStrategy.h"
#include "RaidSSCStrategy.h"
#include "RaidTempestKeepStrategy.h"
#include "RaidHyjalSummitStrategy.h"
#include "RaidZulAmanStrategy.h"
#include "RaidOsStrategy.h"
#include "RaidEoEStrategy.h"
@ -33,6 +34,7 @@ public:
creators["naxx"] = &RaidStrategyContext::naxx;
creators["ssc"] = &RaidStrategyContext::ssc;
creators["tempestkeep"] = &RaidStrategyContext::tempestkeep;
creators["hyjal"] = &RaidStrategyContext::hyjal;
creators["zulaman"] = &RaidStrategyContext::zulaman;
creators["wotlk-os"] = &RaidStrategyContext::wotlk_os;
creators["wotlk-eoe"] = &RaidStrategyContext::wotlk_eoe;
@ -52,6 +54,7 @@ private:
static Strategy* naxx(PlayerbotAI* botAI) { return new RaidNaxxStrategy(botAI); }
static Strategy* ssc(PlayerbotAI* botAI) { return new RaidSSCStrategy(botAI); }
static Strategy* tempestkeep(PlayerbotAI* botAI) { return new RaidTempestKeepStrategy(botAI); }
static Strategy* hyjal(PlayerbotAI* botAI) { return new RaidHyjalSummitStrategy(botAI); }
static Strategy* zulaman(PlayerbotAI* botAI) { return new RaidZulAmanStrategy(botAI); }
static Strategy* wotlk_os(PlayerbotAI* botAI) { return new RaidOsStrategy(botAI); }
static Strategy* wotlk_eoe(PlayerbotAI* botAI) { return new RaidEoEStrategy(botAI); }

View File

@ -7,7 +7,7 @@
#define _PLAYERBOT_RAIDSSCTRIGGERCONTEXT_H
#include "RaidSSCTriggers.h"
#include "AiObjectContext.h"
#include "NamedObjectContext.h"
class RaidSSCTriggerContext : public NamedObjectContext<Trigger>
{

View File

@ -7,7 +7,6 @@
#define _PLAYERBOT_RAIDSSCSTRATEGY_H_
#include "Strategy.h"
#include "Multiplier.h"
class RaidSSCStrategy : public Strategy
{

View File

@ -2,7 +2,7 @@
#define _PLAYERBOT_RAIDTEMPESTKEEPTRIGGERCONTEXT_H
#include "RaidTempestKeepTriggers.h"
#include "AiObjectContext.h"
#include "NamedObjectContext.h"
class RaidTempestKeepTriggerContext : public NamedObjectContext<Trigger>
{

View File

@ -2,7 +2,6 @@
#define _PLAYERBOT_RAIDTEMPESTKEEPSTRATEGY_H_
#include "Strategy.h"
#include "Multiplier.h"
class RaidTempestKeepStrategy : public Strategy
{

View File

@ -6,7 +6,6 @@
#ifndef _PLAYERBOT_RAIDULDUARTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDULDUARTRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h"
#include "RaidUlduarTriggers.h"
#include "BossAuraTriggers.h"

View File

@ -2,7 +2,6 @@
#ifndef _PLAYERBOT_RAIDULDUARSTRATEGY_H
#define _PLAYERBOT_RAIDULDUARSTRATEGY_H
#include "AiObjectContext.h"
#include "Strategy.h"
class RaidUlduarStrategy : public Strategy

View File

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

View File

@ -3,10 +3,6 @@
#define _PLAYERBOT_RAIDVOASTRATEGY_H
#include "Strategy.h"
#include "PlayerbotAI.h"
#include "string"
#include "Trigger.h"
#include "vector"
class RaidVoAStrategy : public Strategy
{

View File

@ -7,7 +7,7 @@
#define _PLAYERBOT_RAIDZULAMANTRIGGERCONTEXT_H
#include "RaidZulAmanTriggers.h"
#include "AiObjectContext.h"
#include "NamedObjectContext.h"
class RaidZulAmanTriggerContext : public NamedObjectContext<Trigger>
{

View File

@ -7,7 +7,6 @@
#define _PLAYERBOT_RAIDZULAMANSTRATEGY_H_
#include "Strategy.h"
#include "Multiplier.h"
class RaidZulAmanStrategy : public Strategy
{

View File

@ -11,6 +11,7 @@
#include "Ai/Raid/Magtheridon/RaidMagtheridonActionContext.h"
#include "Ai/Raid/SerpentshrineCavern/RaidSSCActionContext.h"
#include "Ai/Raid/TempestKeep/RaidTempestKeepActionContext.h"
#include "Ai/Raid/HyjalSummit/RaidHyjalSummitActionContext.h"
#include "Ai/Raid/ZulAman/RaidZulAmanActionContext.h"
#include "Ai/Raid/ObsidianSanctum/RaidOsActionContext.h"
#include "Ai/Raid/EyeOfEternity/RaidEoEActionContext.h"
@ -34,6 +35,7 @@ void AiObjectContext::BuildSharedActionContexts(SharedNamedObjectContextList<Act
actionContexts.Add(new RaidMagtheridonActionContext());
actionContexts.Add(new RaidSSCActionContext());
actionContexts.Add(new RaidTempestKeepActionContext());
actionContexts.Add(new RaidHyjalSummitActionContext());
actionContexts.Add(new RaidZulAmanActionContext());
actionContexts.Add(new RaidNaxxActionContext());
actionContexts.Add(new RaidOsActionContext());

View File

@ -11,6 +11,7 @@
#include "Ai/Raid/Naxxramas/RaidNaxxTriggerContext.h"
#include "Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h"
#include "Ai/Raid/TempestKeep/RaidTempestKeepTriggerContext.h"
#include "Ai/Raid/HyjalSummit/RaidHyjalSummitTriggerContext.h"
#include "Ai/Raid/ZulAman/RaidZulAmanTriggerContext.h"
#include "Ai/Raid/ObsidianSanctum/RaidOsTriggerContext.h"
#include "Ai/Raid/EyeOfEternity/RaidEoETriggerContext.h"
@ -35,6 +36,7 @@ void AiObjectContext::BuildSharedTriggerContexts(SharedNamedObjectContextList<Tr
triggerContexts.Add(new RaidNaxxTriggerContext());
triggerContexts.Add(new RaidSSCTriggerContext());
triggerContexts.Add(new RaidTempestKeepTriggerContext());
triggerContexts.Add(new RaidHyjalSummitTriggerContext());
triggerContexts.Add(new RaidZulAmanTriggerContext());
triggerContexts.Add(new RaidOsTriggerContext());
triggerContexts.Add(new RaidEoETriggerContext());

View File

@ -33,8 +33,11 @@ bool ExternalEventHelper::ParseChatCommand(std::string const command, Player* ow
if (!ChatHelper::parseableItem(command))
return false;
if (sPlayerbotAIConfig.enableAutoTradeOnItemMention)
{
HandleCommand("c", command, owner);
HandleCommand("t", command, owner);
}
return true;
}

View File

@ -619,7 +619,7 @@ void RandomPlayerbotFactory::CreateRandomBots()
else
password = accountName;
AccountMgr::CreateAccount(accountName, password);
sAccountMgr->CreateAccount(accountName, password);
LOG_DEBUG("playerbots", "Account {} created for random bots", accountName.c_str());
}

View File

@ -15,6 +15,7 @@
#include "ChannelMgr.h"
#include "CharacterPackets.h"
#include "ChatHelper.h"
#include "CheckMountStateAction.h"
#include "Common.h"
#include "CreatureData.h"
#include "EmoteAction.h"
@ -54,9 +55,9 @@
#include "Unit.h"
#include "UpdateTime.h"
#include "Vehicle.h"
#include "../../../../src/server/scripts/Spells/spell_dk.cpp"
const int SPELL_TITAN_GRIP = 49152;
constexpr uint32 SPELL_TITAN_GRIP = 49152;
constexpr uint32 SPELL_DK_FROST_PRESENCE = 48263;
std::vector<std::string> PlayerbotAI::dispel_whitelist = {
"mutating injection",
@ -1380,6 +1381,17 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet)
// */
return;
}
case SMSG_DISMOUNT:
{
WorldPacket p(packet);
p.rpos(0);
ObjectGuid guid;
p >> guid.ReadAsPacked();
if (guid != bot->GetGUID())
return;
CheckMountStateAction::CompleteDismount(bot);
return;
}
default:
botOutgoingPacketHandlers.AddPacket(packet);
}
@ -1588,6 +1600,27 @@ void PlayerbotAI::ClearStrategies(BotState type)
e->removeAllStrategies();
}
// Resets only the combat or non-combat engine: wipe strategies, repopulate with class/spec defaults,
// re-apply current map's instance strategy (if any), and call Init() to rebuild trigger/action lists.
void PlayerbotAI::SelectiveResetStrategies(BotState type)
{
Engine* e = engines[type];
if (!e)
return;
e->removeAllStrategies();
if (type == BOT_STATE_COMBAT)
AiFactory::AddDefaultCombatStrategies(bot, this, e);
else if (type == BOT_STATE_NON_COMBAT)
AiFactory::AddDefaultNonCombatStrategies(bot, this, e);
if (sPlayerbotAIConfig.applyInstanceStrategies)
ApplyInstanceStrategies(bot->GetMapId());
e->Init();
}
std::vector<std::string> PlayerbotAI::GetStrategies(BotState type)
{
Engine* e = engines[type];
@ -1601,11 +1634,12 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
{
static const std::vector<std::string> allInstanceStrategies =
{
"aq20", "bwl", "karazhan", "gruulslair", "icc", "magtheridon", "moltencore",
"naxx", "onyxia", "ssc", "tbc-ac", "tempestkeep", "ulduar", "voa", "wotlk-an", "wotlk-cos",
"wotlk-dtk", "wotlk-eoe", "wotlk-fos", "wotlk-gd", "wotlk-hol", "wotlk-hor",
"wotlk-hos", "wotlk-nex", "wotlk-occ", "wotlk-ok", "wotlk-os", "wotlk-pos",
"wotlk-toc", "wotlk-uk", "wotlk-up", "wotlk-vh", "zulaman"
"aq20", "bwl", "karazhan", "gruulslair", "hyjal", "icc", "magtheridon",
"moltencore", "naxx", "onyxia", "ssc", "tbc-ac", "tempestkeep", "ulduar",
"voa", "wotlk-an", "wotlk-cos", "wotlk-dtk", "wotlk-eoe", "wotlk-fos",
"wotlk-gd", "wotlk-hol", "wotlk-hor", "wotlk-hos", "wotlk-nex", "wotlk-occ",
"wotlk-ok", "wotlk-os", "wotlk-pos", "wotlk-toc", "wotlk-uk", "wotlk-up",
"wotlk-vh", "zulaman"
};
for (const std::string& strat : allInstanceStrategies)
@ -1635,6 +1669,9 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
case 533:
strategyName = "naxx"; // Naxxramas
break;
case 534:
strategyName = "hyjal"; // The Battle for Mount Hyjal (Hyjal Summit)
break;
case 544:
strategyName = "magtheridon"; // Magtheridon's Lair
break;

View File

@ -405,6 +405,7 @@ public:
std::string const qualifier = "");
void ChangeStrategy(std::string const name, BotState type);
void ClearStrategies(BotState type);
void SelectiveResetStrategies(BotState type);
std::vector<std::string> GetStrategies(BotState type);
Strategy* GetStrategy(std::string const name, BotState type);
void ApplyInstanceStrategies(uint32 mapId, bool tellMaster = false);

View File

@ -1542,6 +1542,24 @@ void PlayerbotMgr::HandleMasterIncomingPacket(WorldPacket const& packet)
// if master is logging out, log out all bots
case CMSG_LOGOUT_REQUEST:
{
Player* master = GetMaster();
if (master)
{
// Replicate the AFK logout prevention checks from WorldSession::HandleLogoutRequestOpcode
// so bots are not logged out when the master's own logout is going to be prevented.
AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(master->GetAreaId());
bool preventAfkSanctuaryLogout = sWorld->getIntConfig(CONFIG_AFK_PREVENT_LOGOUT) == 1
&& master->isAFK() && areaEntry && areaEntry->IsSanctuary();
bool preventAfkLogout = sWorld->getIntConfig(CONFIG_AFK_PREVENT_LOGOUT) == 2
&& master->isAFK();
if (preventAfkSanctuaryLogout || preventAfkLogout)
{
break;
}
}
LogoutAllBots();
break;
}

View File

@ -2070,7 +2070,7 @@ void RandomPlayerbotMgr::Refresh(Player* bot)
bot->DurabilityRepairAll(false, 1.0f, false);
bot->SetFullHealth();
bot->SetPvP(true);
bot->SetPvP(sWorld->IsPvPRealm());
PlayerbotFactory factory(bot, bot->GetLevel());
factory.Refresh();
@ -2635,6 +2635,7 @@ void RandomPlayerbotMgr::OnPlayerLogin(Player* player)
{
// ObjectGuid::LowType guid = player->GetGUID().GetCounter(); //not used, conditional could be rewritten for
// simplicity. line marked for removal.
player->SetPvP(sWorld->IsPvPRealm());
}
else
{

View File

@ -730,7 +730,6 @@ std::vector<WorldPosition> WorldPosition::getPathStepFrom(WorldPosition startPos
// the previous step's endpoint, giving the 40-attempt walker
// its intended multi-tile reach.
PathGenerator path(pathUnit);
path.AddExcludeFlag(NAV_GROUND_STEEP);
path.CalculatePath(startPos.GetPositionX(), startPos.GetPositionY(), startPos.GetPositionZ(),
GetPositionX(), GetPositionY(), GetPositionZ(), false);

View File

@ -518,6 +518,7 @@ bool PlayerbotAIConfig::Initialize()
LoadListString<std::vector<std::string>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.AllowedLogFiles", ""),
allowedLogFiles);
enableAutoTradeOnItemMention = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableAutoTradeOnItemMention", true);
LoadListString<std::vector<std::string>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.TradeActionExcludedPrefixes", ""),
tradeActionExcludedPrefixes);

View File

@ -306,6 +306,7 @@ public:
uint32 iterationsPerTick;
std::mutex m_logMtx;
bool enableAutoTradeOnItemMention;
std::vector<std::string> tradeActionExcludedPrefixes;
std::vector<std::string> allowedLogFiles;
std::unordered_map<std::string, std::pair<FILE*, bool>> logFiles;

View File

@ -526,6 +526,7 @@ public:
void AddPlayerbotsSecureLoginScripts();
void AddSC_TempestKeepBotScripts();
void AddSC_HyjalSummitBotScripts();
void AddPlayerbotsScripts()
{
@ -541,4 +542,5 @@ void AddPlayerbotsScripts()
AddPlayerbotsCommandscripts();
PlayerBotsGuildValidationScript();
AddSC_TempestKeepBotScripts();
AddSC_HyjalSummitBotScripts();
}