Compare commits

..

63 Commits

Author SHA1 Message Date
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
Keleborn
4bd5a9b89c
Crash Fix. Queue arena packet instead of handle directly. (#2331)
<!--
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 -->
Have arenas follow the same path as battlegrounds when queueing .
Intended to to resolve discord user crash. 


## 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-04-26 19:14:17 +02:00
kadeshar
ed5791eabf
Pull target overlap fix (#2335)
<!--
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 -->

Fixed "pull target" value which was overlap with new pull strategy.
Related with #2334 

## 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 tank bot to party
2. Use `nc +debug`
3. Use command `do attack my target`
4. In debug shouldnt be `reach pull` or similar

## 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 analyze problem with value

<!--
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-04-25 14:40:53 -07:00
ThePenguinMan96
605f1d7aaa
PvP Gear, Autogear Tuning, and Stat Weight Corrections (#2322)
<!--
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 -->

Hello playerbots community! I have been working diligently whilst on
vacation to help get pvp gear up and running for pvp specs. Throughout
this process, I have looked at our current autogear system, tested it
through and through, and made some changes to make gearing more
appropriate per spec. _I am going to have my description of the changes
in italics_, **and the AI description overview will be bolded.** Let's
begin!

**This PR makes some improvements to the bot autogear system across item
scoring, spec tracking(pvp specs and gear), and stat weights. Changes
are split between those that are always active and those controlled by
new config options.**

**Mandatory Changes:**

**PvP Spec Detection (IsSpecPvp)
A new method RandomPlayerbotMgr::IsSpecPvp(botGuid, cls) checks the
bot's stored specNo against the spec name string defined in config. If
the name contains "pvp", the bot is treated as a PvP spec throughout the
entire gear pipeline. This is the single source of truth used by both
InitEquipment() and ItemUsageValue. In the future this detection can be
expanded to drive bot behavior decisions — such as prioritizing dueling
players in the world, joining Wintergrasp, or preferring BG and Arena
queues over PvE content.**

_This is scalable, so if someone were to create their own pvp spec in
the config, it would still be tracked if the name contains "pvp". I like
the idea of pvp specced random bots having an identifier for pvp
events._

**PvP Weights Applied During Loot Evaluation
ItemUsageValue::QueryItemUsageForEquip() now calls IsSpecPvp() before
scoring a looted item. If the bot is on a PvP spec, it passes
SetPvpSpec(true) to the StatsWeightCalculator, ensuring looted items are
evaluated with PvP stat priorities (including resilience weighting)
rather than PvE weights. Previously, a PvP-specced bot would score loot
identically to a PvE bot.**

_So, during autogear and upgrade equips, pvp specced bots will now
heavily prioritize resilience. On the flip side, pve bots really don't
want resilience gear, so a negative weight modifier (penalty for
resilience items) has been applied to pve autogearing and upgrade
equips. This is important, because you can switch a bot from a pve spec
to a pvp spec, and it will automatically consider resilience items in
it's inventory as upgrades, and equip them. Same for when you switch a
bot from a pvp spec back to a pve spec - the resilience penalty will
encourage the bot to switch back to the best available pve gear._

**Resilience Weighting
After all per-spec weights are generated in GenerateBasicWeights(), a
global resilience modifier is applied unconditionally:**

**PvP specs: +7.0 resilience weight — strongly prioritizes resilience
gear
Non-PvP specs: −3.0 resilience weight — actively discourages resilience
gear
Resilience is additionally excluded entirely from trinket slot scoring
via SetExcludeResilience(true), preventing the PvP resilience bonus from
inflating the scores of non-CC trinkets.**

_I tried several different numbers here - as high as 10 and as low as 3
for resilience. I ended up with 7 so nearly all specs will slot
resilience in every slot EXCEPT for trinkets. I stopped weighing
resilience on trinkets because they ended up being garbage trinkets for
the most part - other endgame pve trinkets were way more impactful. In
my testing, the only class/specs that wont use 100% resilience gears are
the tanks, since defense rating/parry/block/dodge weights are so high._

**CC-Break Trinket Cache
At server startup, PlayerbotFactory::BuildCcBreakTrinketCache() queries
the world database for all trinkets (InventoryType=12, Quality≥2) whose
spell IDs include spell 42292 — the CC-break / PvP trinket effect shared
by items like Medallion of the Alliance/Horde. Results are sorted by
item level descending and cached in a static vector, ready for fast
lookup during gearing.**

_This creates a cache of cc trinkets on startup, for this:_

**CC-Break Trinket Force-Equip
During InitEquipment(), PvP-specced bots at level 50 or higher (level
minimum for autogear to apply trinkets) run a pre-selection pass over
ccBreakTrinketCache to find the best CC-break trinket they meet the
level requirement and quality limit for. Human and Undead bots are
excluded from this — they have racial abilities (Every Man for Himself,
Will of the Forsaken) that share the PvP trinket cooldown, making a
dedicated trinket redundant.**

**If a suitable trinket is found, it is stored as pvpTrinket1 and
force-equipped into TRINKET1 before the main gear loop runs. If an item
already occupies the slot, it is moved to bags first. The second-chance
pass also skips TRINKET1 when pvpTrinket1 is set, so the CC trinket is
never overwritten.**

_This is the catch-all forced pvp trinket for trinket slot 1. In my
testing, I really found out how few cc trinkets there are - most of them
are epic, and blue ones start showing up super late in the game. An
heirloom patch would really help the lower levels, being able to equip a
pvp trinket at level 10 or something. Keep in mind, that if your bot
isn't getting a pvp trinket with autogear, make sure they aren't human
or undead, and check your config for what quality items are allowed with
autogear. NOTE - PVP TRINKET STRATEGIES ARE NOT CURRENTLY CODED, SAME
WITH CC RACIALS. They will not break out of stun/cc currently. This is
for future updates if/when I make a trinketstrategy._

**Enhancement Shaman Dual Wield Fix
Classes like Rogues, Frost DKs, and Fury Warriors have their dual wield
capability established through class initialization code in the core.
Enhancement Shamans acquire Dual Wield only through a specific talent
(spell 30798, learned around level 40), and the bot factory had no code
to detect and apply this. The result was that Enhancement Shaman bots
would sometimes have their offhand weapon unequipped — despite having
the talent. After talents are applied in both InitTalentsTree() and
InitTalentsBySpecNo(), the code now checks for spell 30798 and
explicitly grants SKILL_DUAL_WIELD and SetCanDualWield(true) when
present.**

_When testing the weapon speed preferences, I noticed that randombot
enhancement shamans were unequipping their offhand randomly. They would
just walk around with a single 1-hand weapon. This is because they were
not considered in the system as dual wielding, so when initequipment or
autoequipupgrades was ran, it would unequip the offhand through a
function, despite having the dual wield talent. Looking at the code, the
other classes already have this flag (warriors, rogues, dks, hunters)
because they didn't acquire it through talents._

**CalculateItem() Slot Awareness
StatsWeightCalculator::CalculateItem() now accepts an optional slot
parameter (default -1). When provided and the item is a weapon,
ApplyWeaponSpeedGovernance() can be called. Both item scoring calls
inside InitEquipment() — the candidate scoring loop and the incremental
old-item comparison — now pass the current equipment slot.**

_This change allows the calculate item function to know what slot it's
working with, and that's how it modifies it's decision making for some
of the optional features below._

**Holy Paladin Weapon Scoring Fix
Prior to this change, Holy Paladin could end up equipping 2H weapons
because haste and crit sticks (2H weapons) were outscoring appropriate
1H caster weapons — the item type penalty was not catching them
correctly. Holy Paladin is now explicitly added to the dual-wield
penalty group (preventing 2H weapons from being viable), excluded from
the generic caster 1H penalty (since they use 1H + shield rather than a
staff), and given a 0.8x soft preference for 1H weapons.**

_In autogear testing, sometimes 2h weps with high crit/haste would win
over caster gear - this is especially noticeable at lower levels, with
shallower item pools (greens only). You'd hit autogear and the holy
paladin would equip a 2h axe with crit :( So this makes it so holy
paladins only use 1h weapons. They can use either a shield or an
offhand, depending on stat weights._

**PvP Spec Slots Added for All Classes
The existing RandomClassSpecProb / RandomClassSpecIndex config entries
control what percentage of random bots in the world are assigned each
spec. Previously only PvE specs (indices 0–2, or 0–3 for Druids) were
defined, giving server operators no way to introduce PvP-specced random
bots into the world population. This PR adds PvP spec slots for every
class (indices 3–6 depending on class), all defaulting to 0 probability.
Server operators can raise these values to spawn PvP-specced random bots
— e.g., setting RandomClassSpecProb.1.3 = 20 would make 20% of Warrior
bots run Arms PvP.
Two additional PvE specs have also been added:
Death Knight index 3: Double-aura Blood (a hybrid Blood/Frost PvE tank
variant)
Mage index 3: Frostfire (a PvE hybrid spec)
All existing spec entries have been annotated with comments identifying
each one (e.g., # arms pve, # holy pve) for readability.**

_This change was actually added at the start - I realized that there was
no way for pvp-specced randombots to spawn naturally, so I added
optional probabilities to the config. They are currently set at 0% by
default, but giving the user the option I feel is necessary. Also, it
would have been impossible for me to test the init on randombots with
pvp gear otherwise. Also, I noticed that the frostfire mage and the
dual-aura dk didn't have an option, so I added them in as well, as well
as names above each option for quality of life._

**Stat Weight Corrections
The following per-spec stat weights were adjusted to better reflect
actual WotLK priorities. Entries marked NEW did not previously exist;
unmarked rows show old → new values.**

<img width="795" height="268" alt="arms warrior"
src="https://github.com/user-attachments/assets/cb0deb00-a985-432d-81a1-133fc953088b"
/>

_Arms warriors would prefer leather/ap gear about half of the time - the
combined weights of both would often beat strength gear, especially at
lower levels, or where the item pool was shallow. Also, they continued
to spawn with spell power gear and defense gear occasionally, especially
on gear with resilience (resilience, spell power, crit, haste, stam
items)._

<img width="796" height="301" alt="fury warrior"
src="https://github.com/user-attachments/assets/715ff3a3-3d20-4e0e-a953-7ed6fd9386db"
/>

_Fury warriors had the same issues as arms warrior, but really can't
afford to lose a strength item - beserker stance increases strength by
20%. Also had to reduce haste here because haste really isn't nearly as
important as strength, crit, arp. Haste items would win often over
strength/crit/arp gear._

<img width="796" height="300" alt="prot tanks"
src="https://github.com/user-attachments/assets/624de09a-2506-4aee-95aa-c49cbc5b85d3"
/>

_So, prot paladins and prot warriors currently are weighed identically
fyi. Look at that whopping 2.0 agility - twice as important as strength?
I noticed that my prot paladins/warriors were equipping
agility/haste/crit items instead of defense gear on their neck, rings,
trinkets, and back. This adjustment pretty much ensures that defense
gear takes those slots if it's available. Removed the crit/haste
weightings, because realistically if a tank wants more damage, it will
just get strength. Lastly added the spell power penalty because prot
paladins would spawn in fully holy gear if they were pvp specced
(resilience is weighted so high, resilience/spellpower/stam/haste gear
would often win). This aims to prevent that._

<img width="802" height="421" alt="dps dks"
src="https://github.com/user-attachments/assets/371d1344-2382-4460-b3a7-f38b33025b73"
/>

_Same issue with plate dps as the warriors had. Spell power gear would
occasionally spawn on crit/hit items in pve, and a ton of spell power
resilience gear would spawn. There is no scenario where a DK wants spell
power, this isn't patch 3.0.1..._

<img width="796" height="447" alt="blood dk"
src="https://github.com/user-attachments/assets/c84a2bbf-7daa-4805-acf3-cd3bf815eda4"
/>

_Similar issues to prot paladin/warrior. I was really tired of seeing
block rating/value gear as a result of getting gear with defense/stam.
This results in a lot more defense rating/expertise/hit/dodge/parry
gear, and basically makes shield stats nearly non-existent (unless the
upgrade is good enough, it could still win)_

<img width="794" height="226" alt="ret paladin"
src="https://github.com/user-attachments/assets/b09f40ed-b25f-4945-940c-2ce92f81c7c4"
/>

_Prior to this PR, the positive spellpower and int weights were enough
for ret paladins to spawn with spellpower/int/haste/crit gear. This is
unlikely now. And agility/ap was reduced to favor more strength gear._

<img width="795" height="306" alt="Enhancement Shaman"
src="https://github.com/user-attachments/assets/1e5231fd-36ea-4b7e-a546-cf0075d17bd4"
/>

_While spell power is a decent stat on enhancment shamans, it was
appearing on too much gear, especially on items that were
haste/crit/spell power. And for elemental shamans, they were getting
agi/haste/crit gear, so this aims to get rid of those items entirely
without reducing haste/crit._

<img width="800" height="119" alt="shaman pally"
src="https://github.com/user-attachments/assets/6ea3300a-effd-4a03-8f6f-4ae13c5383a5"
/>

_Holy paladins and resto shamans are scored the same, but this prevents
attack power/haste/crit gear, since haste and crit are weighted high._

<img width="1025" height="385" alt="mage"
src="https://github.com/user-attachments/assets/03191dfd-dc09-477d-8424-8fd56f3e0d71"
/>

_Prevents mages from equipping/autogearing items with attack power, some
attack power/crit/haste/hit items were winning with shallow item pools._

<img width="1022" height="502" alt="hunter rogue"
src="https://github.com/user-attachments/assets/06fa67c9-7709-4ee8-a0e2-34de18594018"
/>

_Prevents hunters and rogues from getting spell power leather gear with
hit/crit. Crit is very heavy for hunters so this was decently common._

**Optional Changes (Config-Controlled)**

**AiPlayerbot.PreferClassArmorType (default: 0)
Applies a 3x score multiplier to armor matching the bot's
class-appropriate type (plate/mail/leather/cloth). A significantly
better off-type item can still win — this is a soft preference, not a
hard filter.**

_Are you tired of your fury warrior being a leather daddy? Are you tired
of your holy paladin running around in cloth lingerie? This will fix
that. For mail classes (hunters/shamans) and plate classes, this only
kicks in after level 40. But it really helps adhere to the highest armor
class available. This would be the perfect solution to the quarterly
question "Why is my paladin wearing leather?". This definitely should
remain optional, as quite a few BIS lists would disagree with it.
Leather at certain stages is great for hunters/shamans/warriors/dks._

**AiPlayerbot.AutogearAllowsQuestRewards (default: 0)
Builds a cache of equippable armor and weapon quest rewards at startup.
Bots can then equip these items during autogear, using the quest's
minimum level as the effective required level gate.**

_So, I noticed that autogear didn't allow items without a level
requirement (quest rewards), because it didn't know how to handle that
when giving out gear. It would previously just flat out reject all quest
rewards, as they wouldn't be a part of the item pool. This option
enables quest rewards to be considered in the item pool, and the level
correlates to the lowest level you could get the quest. I have tested
this for about 3 hours across all specs and using blue/green gear, it
seems like a really nice bonus. Keep in mind that I do 0 quests on my
way to 80, so players like me could still benefit from those items. I
think this should remain optional._

**AiPlayerbot.EquipAllSlotsAtAnyLevel (default: 0)
Bypasses the low-level slot restrictions in InitEquipment():
Trinkets normally locked until level 50
Head/Neck until level 30
Rings until level 20
All other non-weapon slots until level 5**

_Autogear currently has level floors for slots - they will not ever give
items below the above thresholds. This config option bypasses that. I
have not tested this as much as I should have, so as people test this,
they could let us know of items that should be blacklisted._

**AiPlayerbot.WeaponSpeedGovernance (default: 0)
When enabled, ApplyWeaponSpeedGovernance() applies a 3x score multiplier
to weapons matching the spec's ideal attack speed profile. Applies to
mainhand, offhand, and ranged slots only. Per-spec preferences:
Arms Warrior: Slow 2H (>=3400ms) in mainhand; poleaxes and axes
preferred (Axe Specialization)
Ret Paladin / Blood & Unholy DK:  Slow 2H (>=3400ms) in mainhand
Prot Warrior & Paladin: Slow 1H (>=2600ms) in mainhand
Fury Warrior dual wield:  Slow 1H (>=2600ms) in both hands
Fury Warrior titan's grip: Slow 2H (>=3400ms) in both hands
Frost DK: Slow 1H (>=2600ms) in both hands; 2H excluded
Enhancement Shaman (dual wield): Slow 1H (>=2600ms) in both hands;
synchronized MH/OH speeds for flurry procs
Enhancement Shaman (pre-dual wield): Slow 2H (>=3400ms) in mainhand
Combat Rogue: Slow MH (>=2600ms) + Fast OH (<=1500ms)
Assassination / Subtlety Rogue: Slow dagger MH (>=1700ms) + Fast dagger
OH (<=1500ms)
Hunter: Slow ranged (>=2600ms); melee is a stat stick, speed ignored
Feral Druid: No preference (forms normalize attack speed)**

_Besides pvp gearing for pvp specs, I feel like this is one of the
nicest additions. It was really frustrating to see an enhancement shaman
put windfury on a 1.5 dagger. Without this, weights for melee dps are
calculated on dps alone, not weapon speed. You'll see 2h specs use fast
2h weapons (3.0), rogues use 2 fast weapons or slow weapons, frost dks
occasionally using 2h weapons while having dual wield talents. I tested
this for about 6 hours across all mentioned specs at levels 20, 30, 40,
50, 60, 65, 70, 75, and 80, with 3 quality types (greens, blues,
purples). I would actually consider making this mandatory, simply
because of the impact I saw in the dps charts. Super happy and proud of
this._

<img width="1021" height="470" alt="files changes"
src="https://github.com/user-attachments/assets/f55d955c-8760-4adf-b4d9-84797da2dc65"
/>


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

_Two caches are built upon startup - the pvp trinket cache and the quest
reward cache. From there, this directly modifies the stat weight
calculations involving initequipement (autogear) and autoequipupgrades,
as both go off of stat weight calculations. I tried to implement these
changes with as little custom functions and coding as possible, and
relied as much as I could on the pre-existing framework._

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

_Fortunately most of the gates are boolean so it shouldn't impact
performance much at all. I ran these changes on my local server with
stock 500 bots, noticed no pmon difference from the main branch. Did a
24h stress test on my server yesterday, stats looked consistent with the
stress test I did prior to making any changes on 3-31-26._

_It helps that it uses pre-existing functions such as initequipment and
autoequipupgrades, and it really just modifies them with slightly more
logic. That being said, autogear didn't lag my server at all, nor did
the bots equipping upgrades._

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

_So, with the basic stock playerbots config (do not forget to copy the
new config!), the only thing that should change is the pvp gear
appearing on pvp specs, and the classes preferring more appropriate
stats across the board. You can load into the game, level a bot to 20,
autogear, and notice the difference. Same at level 40, 60, 75, or
whatever. You could add in the optional config settings to further
streamline the gear you want. I currently run with all 4 enabled, 2 of
which increase the item pool, and 2 of which help guide them to more
appropriate gear (armor/weps)._

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

_The code is only used on startup (cache generation) and when
autogear/autoequipupgrades is called. Not all the time, and not per
tick. I noticed no performance impact after these changes._

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

_It modifies the decision making as far as equipment goes, but as far as
priority/strategies, this does not affect that._

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

_Not to my knowledge, but I'll rely on testers and the community to let
me know if it does._

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

_AI was used in the research of the initequipment system, stat weights,
and cache building. As far as generating the code, using AI was 2 steps
forward, 1 step back. I used Claude Code with Sonnet 4.6 (high) and had
gemini/copilot review the work. **AI did generate a large portion of the
code being used.** I have personally reviewed every line, and a lot was
removed out of being obsolete/new system that copied an old one/too many
comments. I don't think anything else can be trimmed, though. I also
used AI in the PR description, and made my own comments in italics below
each entry. I hate explaining/writing._

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

_I would like atleast 5-10 people to review this over the next 1-6
months. The big problem I used to have with my PRs was I was acting like
they were a sprint, when it's really a marathon - good changes take
time, and I was too quick to bust out new content. The old PRs I made
introduced just as many new bugs as they did features. I learned my
lesson, and have tested this extensively (code was pretty much complete
on 4-10-26, been testing alone for the last 11 days) and it's ready for
the test realm for others to try out. I think it's going to be a good
step forward when it comes to gear decision making for bots as a whole.
PvPers have come and gone too much from this project due to the lack of
options, and this helps captivate that audience. Please reach out to me
on discord at Zhur#4391, I am happy to hear results/suggestions there as
well as here._

---------

Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com>
2026-04-24 23:22:52 +02:00
Keleborn
ad8e8444d1
clean up for DropQuestAction (#2326)
<!--
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 -->
I was getting annoyed by the constant "No event owner detected" message
after I enabled bot debugging messaging.
And then I figured that there is no reason that maintenance should
trigger this action every 5 seconds. So I swapped it to seldom, so it
runs every 5 mins. More Performance!


## 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-04-24 23:22:13 +02:00
Keleborn
eb268c7507
Init guilds on login. (#2325)
<!--
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 -->
Guild solution to #2148


## 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
    - - [x] Minimal impact (**explain below**)
    - - [ ] Moderate impact (**explain below**)

Minimal cost on bot login. 

- 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-04-24 23:05:35 +02:00
kadeshar
ed0a21eefa
GetGrave fix (#2320)
<!--
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 -->

Added missing races in GetGrave method
Related with: #2220 

## 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. Group with bot in starting zone for dranei or blood elf
2. Kill bot.
3. Use command `release` and `revive`
4. Watch which graveyard will be used

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

Stability test:
<img width="1014" height="191" alt="obraz"
src="https://github.com/user-attachments/assets/036a836f-c611-4cc3-832f-7813b91754e8"
/>
2026-04-24 23:04:59 +02:00
NoxMax
b6408ca602
Fix: Prevent infantry auto attack when IsInVehicle (#2319)
<!--
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 -->
While testing vehicle combat in Wintergrasp, I was near two opposing
vehicles. They were right on top of each other, and I was hearing the
sounds of infantry melee attack. It looks like their auto-attack was on.

I had thought the check in
[GenericActions](0c205b8cef/src/Ai/Base/Actions/GenericActions.cpp (L46))
would prevent that, but I guess we need the extra defence.

Note that IsInVehicle first three parameters are
canControl/canCast/canAttack


## 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.
-->
There isn't vehicle combat scenarios (particularly vehicle on vehicle)
in Playerbots right now that allow for obvious testing. I only learned
about this through testing my unpublished Wintergrasp implementation.
Regardless, the change is simple and the effect on code should be clear.


## 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**)
No more infantry combat of any kind for drivers.


- 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-04-24 23:04:40 +02:00
Crow
1967b63bc1
Cleanups for Shaman weapon enchant refactor (#2315)
<!--
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 -->
I forgot to include some clean-ups relating to the recent commit to
refactor Shaman weapon enchants. This is just deleting some now unneeded
code and cleaning up a bit of other code.


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

---------

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-04-24 23:04:21 +02:00
kadeshar
04f8b0dd13
Stat weights fix (#2313)
<!--
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 -->

Added support for Warlock stat weights when he dont have Fel Armor.
Fixed Mage weights when he dont have Molten Armor

## 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 mage which dont have Molten Armor (level < 62) or warlock
which dont have Fel Armor (level < 62)
2. Give him 2 items for same slot one with spirit one with intellect and
unequip item on this slot and destroy
3. Bot should equip this with intellect (if other stats are same)

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

Mage and Warlock before getting Molten Armor/Fel Armor dont prioritize
Spirit before Intellect

- 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-04-24 23:03:57 +02:00
Keleborn
866a73dfbf
Clean up unused variables (#2268)
<!--
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 a bunch of additional unused variable warnings. 


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



## 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 reviewed the warnings log from a build and suggested a series of
changes. I focused just on these warnings for now. Every line was
reviewed. Some sections need to be reviewed by author for intent.



## Final Checklist

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

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
2026-04-24 23:03:36 +02:00
Keleborn
a4b37c9fbc
Revert "Feat: Reintroduce timed logouts" (#2329)
Reverts mod-playerbots/mod-playerbots#2289
2026-04-24 12:27:06 -07:00
kadeshar
52273b4971
Pull multiplier fix (#2317)
<!--
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 pull strategy multiplier and ending pull command

## 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 tank bot
2. Order him to attack mob (not pull)

## 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-04-21 10:37:54 -07:00
kadeshar
9c5b1d0027
Pull strategy GetTarget fix (#2316)
<!--
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 -->

Added  safe-guard condition for PullStrategy.GetTarget

## 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 bot tank
2. Use `reset boAI` or `nc +pull,+pull back` + `co +pull,+pull back`
3. Order bot to pull using command `pull my target` or `pull rti target`
on dead creature
4. Bot should run to mob, use ranged skill and back to point where he
started pull
Without `pull back` strategy bot run to mob, use ranged skill and wait
on mob until he come to bot

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

Fix proposed by @brighton-chi
2026-04-19 09:49:09 -07:00
kadeshar
19249e90a0
Pull strategy migration (#2310)
<!--
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 -->

Pull strategy migration from cmangos for tank specializations

## 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 bot tank
2. Use `reset boAI` or `nc +pull,+pull back` + `co +pull,+pull back`
3. Order bot to pull using command `pull my target` or `pull rti target`
4. Bot should run to mob, use ranged skill and back to point where he
started pull
Without `pull back` strategy bot run to mob, use ranged skill and wait
on mob until he come to bot

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

Help with migration and solving some problems

<!--
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.
- - [ ] Documentation updated if needed (Conf comments, WiKi commands).

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

Stability test after randomize new bots
<img width="465" height="172" alt="obraz"
src="https://github.com/user-attachments/assets/6e39a8c0-f23b-47cc-852a-71fa98044a31"
/>

---------

Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com>
2026-04-17 14:07:47 -07:00
kadeshar
665a702a65
Worldbuff classic support (#2311)
<!--
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 -->

Added support for flask and food for vanilla and TBC
## Vanilla

| Class | Spec | Buff IDs | Buffs |
| --- | --- | --- | --- |
| Warrior | Arms | `17538`, `24799` | Elixir of the Mongoose; Smoked
Desert Dumplings |
| Warrior | Fury | `17538`, `24799` | Elixir of the Mongoose; Smoked
Desert Dumplings |
| Warrior | Protection | `17626`, `25661` | Flask of the Titans; Dirge's
Kickin' Chimaerok Chops |
| Paladin | Holy | `17627`, `18194` | Flask of Distilled Wisdom;
Nightfin Soup |
| Paladin | Protection | `17626`, `25661` | Flask of the Titans; Dirge's
Kickin' Chimaerok Chops |
| Paladin | Retribution | `17628`, `24799` | Flask of Supreme Power;
Smoked Desert Dumplings |
| Hunter | Beast Mastery | `17538`, `18192` | Elixir of the Mongoose;
Grilled Squid |
| Hunter | Marksmanship | `17538`, `18192` | Elixir of the Mongoose;
Grilled Squid |
| Hunter | Survival | `17538`, `18192` | Elixir of the Mongoose; Grilled
Squid |
| Rogue | Assassination | `17538`, `18192` | Elixir of the Mongoose;
Grilled Squid |
| Rogue | Combat | `17538`, `18192` | Elixir of the Mongoose; Grilled
Squid |
| Rogue | Subtlety | `17538`, `18192` | Elixir of the Mongoose; Grilled
Squid |
| Priest | Discipline | `17628`, `18194` | Flask of Supreme Power;
Nightfin Soup |
| Priest | Holy | `17627`, `18194` | Flask of Distilled Wisdom; Nightfin
Soup |
| Priest | Shadow | `17628`, `18194` | Flask of Supreme Power; Nightfin
Soup |
| Shaman | Elemental | `17628`, `18194` | Flask of Supreme Power;
Nightfin Soup |
| Shaman | Enhancement | `17538`, `24799` | Elixir of the Mongoose;
Smoked Desert Dumplings |
| Shaman | Restoration | `17627`, `18194` | Flask of Distilled Wisdom;
Nightfin Soup |
| Mage | Arcane | `17628`, `18194` | Flask of Supreme Power; Nightfin
Soup |
| Mage | Fire | `17628`, `18194` | Flask of Supreme Power; Nightfin Soup
|
| Mage | Frost | `17628`, `18194` | Flask of Supreme Power; Nightfin
Soup |
| Warlock | Affliction | `17628`, `25661` | Flask of Supreme Power;
Dirge's Kickin' Chimaerok Chops |
| Warlock | Demonology | `17628`, `25661` | Flask of Supreme Power;
Dirge's Kickin' Chimaerok Chops |
| Warlock | Destruction | `17628`, `25661` | Flask of Supreme Power;
Dirge's Kickin' Chimaerok Chops |
| Druid | Balance | `17628`, `18194` | Flask of Supreme Power; Nightfin
Soup |
| Druid | Feral Bear | `17626`, `25661` | Flask of the Titans; Dirge's
Kickin' Chimaerok Chops |
| Druid | Restoration | `17627`, `18194` | Flask of Distilled Wisdom;
Nightfin Soup |
| Druid | Feral Cat | `17538`, `24799` | Elixir of the Mongoose; Smoked
Desert Dumplings |

## TBC

| Class | Spec | Buff IDs | Buffs |
| --- | --- | --- | --- |
| Warrior | Arms | `28520`, `33256` | Flask of Relentless Assault;
Roasted Clefthoof |
| Warrior | Fury | `28520`, `33256` | Flask of Relentless Assault;
Roasted Clefthoof |
| Warrior | Protection | `28518`, `33257` | Flask of Fortification;
Fisherman's Feast |
| Paladin | Holy | `28491`, `39627`, `33263` | Elixir of Healing Power;
Elixir of Draenic Wisdom; Blackened Basilisk |
| Paladin | Protection | `28518`, `33257` | Flask of Fortification;
Fisherman's Feast |
| Paladin | Retribution | `28520`, `33256` | Flask of Relentless
Assault; Roasted Clefthoof |
| Hunter | Beast Mastery | `28520`, `33261` | Flask of Relentless
Assault; Warp Burger |
| Hunter | Marksmanship | `28520`, `33261` | Flask of Relentless
Assault; Warp Burger |
| Hunter | Survival | `28520`, `33261` | Flask of Relentless Assault;
Warp Burger |
| Rogue | Assassination | `28520`, `33261` | Flask of Relentless
Assault; Warp Burger |
| Rogue | Combat | `28520`, `33261` | Flask of Relentless Assault; Warp
Burger |
| Rogue | Subtlety | `28520`, `33261` | Flask of Relentless Assault;
Warp Burger |
| Priest | Discipline | `28491`, `39627`, `33263` | Elixir of Healing
Power; Elixir of Draenic Wisdom; Blackened Basilisk |
| Priest | Holy | `28491`, `39627`, `33263` | Elixir of Healing Power;
Elixir of Draenic Wisdom; Blackened Basilisk |
| Priest | Shadow | `28540`, `33263` | Flask of Pure Death; Blackened
Basilisk |
| Shaman | Elemental | `28521`, `33263` | Flask of Blinding Light;
Blackened Basilisk |
| Shaman | Enhancement | `28520`, `33261` | Flask of Relentless Assault;
Warp Burger |
| Shaman | Restoration | `28491`, `39627`, `33263` | Elixir of Healing
Power; Elixir of Draenic Wisdom; Blackened Basilisk |
| Mage | Arcane | `28521`, `33263` | Flask of Blinding Light; Blackened
Basilisk |
| Mage | Fire | `28540`, `33263` | Flask of Pure Death; Blackened
Basilisk |
| Mage | Frost | `28540`, `33263` | Flask of Pure Death; Blackened
Basilisk |
| Warlock | Affliction | `28540`, `33263` | Flask of Pure Death;
Blackened Basilisk |
| Warlock | Demonology | `28540`, `33263` | Flask of Pure Death;
Blackened Basilisk |
| Warlock | Destruction | `28540`, `33263` | Flask of Pure Death;
Blackened Basilisk |
| Druid | Balance | `28521`, `33263` | Flask of Blinding Light;
Blackened Basilisk |
| Druid | Feral Bear | `28518`, `33257` | Flask of Fortification;
Fisherman's Feast |
| Druid | Restoration | `28491`, `39627`, `33263` | Elixir of Healing
Power; Elixir of Draenic Wisdom; Blackened Basilisk |
| Druid | Feral Cat | `28520`, `33261` | Flask of Relentless Assault;
Warp Burger |

## 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 bot with 60-79 level
2. Add him strategy `nc +worldbuff`
3. Bot should apply buffs


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

Prepare list of buffs based on website and apply them to matrix.
Randomly reviewed ids with guides and in-game

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



[VANILLA_CONSUMABLE_WORLD_BUFFS.md](https://github.com/user-attachments/files/26761020/VANILLA_CONSUMABLE_WORLD_BUFFS.md)

[TBC_CONSUMABLE_WORLD_BUFFS.md](https://github.com/user-attachments/files/26761021/TBC_CONSUMABLE_WORLD_BUFFS.md)
2026-04-17 13:54:52 -07:00
Scarecr0w12
c0c2b6ab5b
feat(Core/Playerbots): Initialize bot professions and specializations (#2287)
## Pull Request Description

Initialize random bot professions from the factory using class-matching
or weighted-random profession pairs, respect the active primary
profession cap, and restore required profession tools during bot
init/refresh.

This PR also initializes profession specializations for eligible bots so
crafted professions are not left in an unspecialized state after
profession assignment. Supported specialization families include:

- Alchemy: Transmute / Elixir / Potion
- Engineering: Goblin / Gnomish
- Leatherworking: Dragonscale / Elemental / Tribal
- Tailoring: Spellfire / Mooncloth / Shadoweave
- Blacksmithing: Armorsmith / Weaponsmith, plus Hammersmith / Axesmith /
Swordsmith for eligible Weaponsmith bots

Specialization choices are stored in bot values so they remain stable
across later refreshes. Required tool items are also restored for
relevant professions during maintenance.

## Feature Evaluation

- Describe the **minimum logic** required to achieve the intended
behavior.
- Select one or two professions during factory initialization from a
small weighted list.
- Clamp the assigned professions to the configured primary profession
limit.
- Learn the profession starter spell and set skill to the bot’s
profession cap.
- For professions with supported specialization branches, assign exactly
one valid specialization when the bot meets the same level/skill gates
used by AzerothCore profession scripts.
- Persist the specialization selection in stored bot values so the
choice is stable and does not need to be recalculated repeatedly.
- Restore missing profession tools only when the bot has the related
profession and the tool is absent.

- Describe the **processing cost** when this logic executes across many
bots.
- The added logic executes only during bot init/refresh, not as part of
per-tick combat or trigger evaluation.
- Runtime cost is limited to a few small switch statements, stored value
lookups, spell checks, and item presence checks.
- No expensive repeated searches, map scans, or per-trigger decision
trees were added.
- The design keeps specialization selection deterministic after first
assignment by storing the result, avoiding repeated random branching
later.

## How to Test the Changes

1. Build and restart the server with this branch.
2. Trigger random bot creation, refresh, or level-based reroll for
multiple bots.
3. Verify in `Playerbots.log` that bots receive profession pairs and,
when eligible, profession specializations.
4. Check that low-level bots do not receive specializations before the
required thresholds.
5. Check that eligible bots do receive one specialization for supported
profession families.
6. Verify that specialization choices remain stable across subsequent
refreshes.
7. Verify that profession tools are restored when missing:
   - Mining Pick
   - Blacksmith Hammer
   - Arclight Spanner
   - Runed Arcanite Rod
   - Skinning Knife
8. For a few bots, inspect in game or via debug tooling that profession
spells/specialization spells are present as expected.

Expected behavior:
- Bots receive professions that respect the configured primary
profession limit.
- Profession skill values are initialized to the level-based cap.
- Eligible bots receive exactly one valid specialization for supported
profession families.
- Specialization assignments are logged and persist across refreshes.
- Profession tools are restored only when required.

## Impact Assessment

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

  Explanation:
- The added work runs during initialization/refresh rather than normal
per-tick behavior.
- Logic is bounded, data-local, and based on direct skill/spell/value
checks.

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

  Explanation:
- Bots can now start with initialized professions, required tools, and
eligible profession specializations instead of remaining partially
configured or unspecialized.

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

  Explanation:
- The factory now contains specialization assignment branches for
supported profession families.
- Complexity is intentionally limited to init-time switch-based logic
with stored specialization values to preserve predictability.

## AI Assistance

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

AI assistance was used for:
- code generation and refactoring in `PlayerbotFactory`
- drafting and refining profession/specialization initialization logic
- PR description preparation

All generated and suggested code was reviewed, adjusted, built locally,
and validated before submission.

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

- Target branch is `test-staging`.
- Profession/specialization logic is intentionally limited to
init/refresh paths to avoid per-tick cost.
- Specialization selections are stored to keep bot behavior stable
across later refreshes.
- Recent changes also add debug logging for assigned specializations and
save the bot after specialization learning so assignments are visible
and persisted.

---------

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-04-17 13:54:36 -07:00
NoxMax
6c517eb9d1
Feat: Reintroduce timed logouts (#2289)
<!--
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 -->
Timed logouts was enabled in the past, but was disabled due to a
misdiagnosis on an issue that caused crashes. That issue was better
understood and resolved in #2131. Now timed logouts are reintroduced for
alt-bots and addclass-bots. As before, random-bots do not and should not
accept logout commands.

Note that if the bot's master has instant logout privileges according to
`InstantLogout` in worldserver.conf, so would the bot. If not, neither
would the bot.

## 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.
Feature at minimum logic needed to implement. No measurable processing
cost.


## 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. Have your account gmlevel set to 2 in acore_auth>account_access just
to test how it works with security parameters.
2. Set `InstantLogout = 3` in worldserver.conf.
3. Invite an addclass or alt-bot.
4. Bot should logout if whispered "logout" to or if you logout.
5. If not in a resting place, neither you nor the bot should be able to
instant logout according to the security setting.
6. After the bot logout, log it back in and whisper "logout" again, but
right after whisper "cancel logout" or "logout cancel". That should
cancel the logout.
7. Have your account gmlevel set to 3 in acore_auth>account_access.
(when you change your gmlevel, you need to log out of your account for
the change to take effect)
8. You are now at admin gmlevel, and with `InstantLogout = 3`, you and
any bot under your command should be able to logout instantly.


## 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**)
Bots now trigger instant or timed logout under the same circumstances
that would apply to their master.


- Does this change add new decision branches or increase maintenance
complexity?
    - - [ ] No
    - - [x] Yes (**explain below**)
Checks if bot is eligible for instant logout or not. If not, timed
logout applies to them.


## 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.
-->
While testing this I was having some odd issues with the command
`account set gmlevel`. Not sure what's going on there, but for purposes
of testing this and changing your account security level, doing it
directly in the DB at "acore_auth>account_access" is going to be most
reliable.
2026-04-17 11:27:44 -07:00
Crow
a34681bd7e
Modify UpdateAI to Allow Future Methods to Interrupt Bot Spells (#2295)
Note: Resubmitted because the prior PR had master for the source

<!--
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 adds to PlayerbotAI::UpdateAI a boolean variable,
pendingCastInterrupt, and a public function, RequestCastInterrupt(),
that toggles the variable and would then call InterruptSpells in
UpdateAI. This lets an external script hook into UpdateAI to interrupt a
spell that is in the process of being cast. This should allow
raid/dungeon strategies to actually interrupt spells, such as for
avoiding hazards, as you cannot do so by calling InterruptSpells() or
Reset() or anything else with a raid/dungeon action method.

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

There is no processing cost from this PR itself since it just adds a
simple boolean check that will always return false unless other methods
are implemented to call RequestCastInterrupt().

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

This PR doesn't do anything by itself, but confirmation of no
performance impact can be tested by just playing with the PR merged. I
tested this by introducing scripts that called RequestCastInterrupt()
for Archimonde (included in the Hyjal PR now) and for Auchenai Crypts
(built on the strategy from flashtate's PR just for test purposes), and
spells were interrupted as intended.

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

I'm not sure if it counts as "yes" here, but this PR opens up the
ability for scripts to be added that would add real checks to UpdateAI.
The impact of such scripts will depend on how they are implemented,
however. It is possible (and intended) for the external calls to be
highly limited in scope (e.g., only bots in a particular circumstance in
a particular boss fight).

## 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 to brainstorm different possibilities to
allow external scripts to interrupt spells. I considered different
options before settling on this one due to it not requiring core
changes, being easily usable in boss strategies, and not having any
performance impact on its own.

<!--
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: 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-04-17 11:27:11 -07:00
Crow
d01316fe64
Exclude Isle of Quel'danas Areas From PvP (#2304)
<!--
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 vendor/quest hub areas of the Isle of Quel'danas to excluded
PvP areas (Shattered Sun Staging Area, Sun's Reach Sanctum, Sun's Reach
Harbor, Sun's Reach Armory). Otherwise, bots attack each other and piss
off all the Shattered Sun Offensive guards and NPCs.


## 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.
-->
Go to one of the above-mentioned areas while PvP flagged (or on a PvP
server, like me). See if bots attack.


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

---------

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-04-17 11:26:53 -07:00
Hokken
ce1adebc78
fix(Core): scope AddPlayerBot loading count to master account (#2307)
## Problem

`AddPlayerBot()` falsely rejects player bot additions with *"You have
added too many bots (more than 40)"* even when the player has zero
personal bots.

This happens because the `MaxAddedBots` check at `PlayerbotMgr.cpp:124`
adds `botLoading.size()` to the player's personal bot count:

```cpp
uint32 count = mgr->GetPlayerbotsCount() + botLoading.size();
```

`botLoading` is a `static std::unordered_set<ObjectGuid>` on
`PlayerbotHolder` — shared by both `PlayerbotMgr` (per-player) and
`RandomPlayerbotMgr` (singleton). When `RandomPlayerbotMgr` loads random
bots at startup (up to 60 per interval via `RandomBotsPerInterval`),
their GUIDs go into the same global set. During the startup loading
window, `botLoading.size()` can easily reach 100–300, far exceeding the
default `MaxAddedBots = 40` limit.

The result: any player who logs in during the random bot loading window
and tries `.playerbot add <name>` gets blocked, even though the limit is
intended to be per-player.

### How to reproduce

1. Set `AiPlayerbot.RandomBotAutologin = 1` (default) with 500 random
bots
2. Start the server
3. Log in immediately while random bots are still loading
4. Run `.playerbot add <character_name>` for an offline character on
your account
5. Get *"You have added too many bots (more than 40)"* despite having 0
personal bots
6. Wait 1–2 minutes for random bot loading to finish, try again — works

### Root cause

- `PlayerbotHolder::botLoading` is declared `static` at
`PlayerbotMgr.h:60`, so both `PlayerbotMgr` and `RandomPlayerbotMgr`
share the same set
- `AddPlayerBot()` inserts into `botLoading` at line 147 for ALL callers
— both player-initiated adds (`masterAccountId > 0`) and random bot
spawns (`masterAccountId = 0`)
- The count check at line 124 uses `botLoading.size()` (the entire
global set) instead of filtering to bots being loaded for the requesting
player
- The config comment confirms the intended scope: *"The maximum number
of bots that a player can control simultaneously"*

## Fix

Change `botLoading` from `unordered_set<ObjectGuid>` to
`unordered_map<ObjectGuid, uint32>` where the value is the
`masterAccountId` passed to `AddPlayerBot()`. Random bots are loaded
with `masterAccountId = 0`.

The count check now iterates the map and only counts entries matching
the current player's `masterAccountId`:

```cpp
uint32 loadingForMaster = 0;
for (auto const& [guid, acctId] : botLoading)
{
    if (acctId == masterAccountId)
        ++loadingForMaster;
}
uint32 count = mgr->GetPlayerbotsCount() + loadingForMaster;
```

### Callsite compatibility

All 10 existing `botLoading` callsites were audited:

| Callsite | Operation | Compatible |
|----------|-----------|-----------|
| `PlayerbotMgr.cpp:85` | `find()` by key | Yes |
| `PlayerbotMgr.cpp:153` | `emplace()` (was `insert()`) | Changed |
| `PlayerbotMgr.cpp:174` | `erase()` by key | Yes |
| `PlayerbotMgr.cpp:209` | `erase()` by key | Yes |
| `PlayerbotMgr.cpp:229` | `erase()` by key | Yes |
| `PlayerbotMgr.cpp:1163` | `find()` by key | Yes |
| `RandomPlayerbotMgr.cpp:429` | `empty()` | Yes |

The six unchanged callsites use `find()`, `erase()`, and `empty()` which
operate on keys identically for both `unordered_set` and
`unordered_map`.

## Files changed

| File | Change |
|------|--------|
| `src/Bot/PlayerbotMgr.h` | `botLoading` type:
`unordered_set<ObjectGuid>` → `unordered_map<ObjectGuid, uint32>` |
| `src/Bot/PlayerbotMgr.cpp` | Definition type updated, `insert` →
`emplace` with `masterAccountId`, count check filters by
`masterAccountId` |

## What is NOT changed

- `MaxAddedBots` config key and default value (40) — unchanged
- Random bot loading behavior — unchanged
- The `botLoading.empty()` throttle in `RandomPlayerbotMgr` — unchanged
- In-game group invite flow — unaffected (does not go through
`AddPlayerBot`)
- No new config keys, no schema changes, no API changes

---------

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>
Co-authored-by: Hokken <Hokken@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 11:26:42 -07:00
Crow
937b4903bb
Fix Potential Dereference in AttackAction (#2308)
<!--
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 -->
AttackAction::Attack() uses target before checking it. This has never
historically been a problem for me, but yesterday it was somehow causing
me to crash every time I ordered a bot to attack. Rebuilding didn't
solve the issue so it didn't seem to be a bad build. The problem was
fixed by moving the target check to the beginning of the function.

I restored the function to its existing ordering today and tested again,
and somehow I don't crash anymore regardless. I'm confused as hell, but
regardless, this is a fix that should be made.


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

Order bots to "attack" a target.


## 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 try to help me identify the source of the crash. I
couldn't trace it to any particular PR, but it did identify the issue
that is the subject of this PR.


<!--
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: 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-04-17 11:26:29 -07:00
Flashtate98
8f7d352f7e
Implement Auchenai Crypts Strategies and TBC Dungeon Contexts (#2229)
<!--
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 aims to add bot strategies to the Auchenai Crypts dungeon,
specifically for the boss Shirrak the Dead Watcher, as his focus fire
mechanic is really annoying to deal with.

Additionally I have added TbcDungeonActionContext.h and
TbcDungeonTriggerContext.h for future reference as I add more strategies
to TBC dungeons that need it.

A HUGE thank you to @brighton-chi for all his help. This has been a fun
learning experience!

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

Trigger: A 20 yard radius grid search is performed to locate the focus
fire trigger NPC

Action: Bots will flee from the trigger NPC using MoveAway with a 5 yard
safety buffer

Multiplier: A for loop that prevents bots from running back into the
focus fire mechanic while its active.

Tanking: Tanks have a set coordinate to drag the boss to for the
duration of the fight.

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

Minimal, these scripts only execute when Shirrak has been engaged.

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

Go into Auchenai Crypts (Normal or Heroic) and engage Shirrak the Dead
Watcher

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

This logic is only applied to Auchenai Crypts and activates during
Shirrak's encounter.


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

Behavior only applies when in the instance and when engaged with
Shirrak.

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

New dungeon contexts were added (TbcDungeonActionContext and
TbcDungeonTriggerContext) to provide a clean and dedicated structure for
future TBC dungeon strategies that will be developed. This was done to
be in alignment with the existing WOTLK contexts that already exist.

## 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.
-->
Gemini was used to help me understand existing code in the module I was
referencing and reusing to develop this strategy as I am not a
programmer and hardly know anything about C++.


## Final Checklist

- - [X] Stability is not compromised.
- - [ ] Performance impact is understood, tested, and acceptable.
- - [ ] Added logic complexity is justified and explained.
- - [ ] Documentation updated if needed (Conf comments, WiKi commands).

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

If there are any better alternatives that could be used to help improve
the strategy in any way, please suggest it. I am very new to this and
want to learn and improve.

---------

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-04-17 11:26:08 -07:00
kadeshar
5d7fb9e97d
Druid - lifebloom (#2292)
<!--
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 -->

Restoration Druid now use lifebloom when get Clearcasting proc.

## 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 to group restoration druid and tank
2. Attack target (for example dummy)
3. When druid heals and get clearcasting should cast lifebloom on tank

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

Restoration druid by default use lifebloom on tank when got clearcasting
proc.

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

Copilot CLI to review.


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

<img width="728" height="268" alt="obraz"
src="https://github.com/user-attachments/assets/25bf2b22-d8e5-4755-9f44-d24246cceae2"
/>
<img width="506" height="196" alt="obraz"
src="https://github.com/user-attachments/assets/f4e4897c-496a-412f-bde7-a88f15d9099c"
/>
2026-04-10 22:24:38 -07:00
bashermens
e177901402
Bug(self-bot): Self-bot should face target when using ranged or melee attack (#2300)
## Pull Request Description

Currently when using self-bot and that self-bot is using ranged attacks
its not facing the target, this happens because the client doesnt
receive the instruction in Player::SendMessageToSet(WorldPacket const*
data, bool self);

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

Create new character with uses range attack as base attack (hunter,
mage, druid etc) and observe. Apply patch and observe your self-bot
character is now always facing towards the target.


## 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-04-10 22:18:13 -07:00
Keleborn
53a607e147
Enable bots to do Outdoor pvp (#2217)
## Pull Request Description
Bots will now engage with outdoor pvp targets when in an area with them.
I carved this out of the guildrpg system Im working on since it should
work just fine as a standalone. Note this requires a core update
https://github.com/azerothcore/azerothcore-wotlk/pull/25103

## Feature Evaluation

Its not expensive. the status checks are fairly light and simple. Should
be on par with current rpg system actions

## How to Test the Changes

You can try to use selfbot to enable this while in EPL, or set the
probability of all other rpg actions to 0.


## 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 is some impact, but should be minimal overall. 

- Does this change modify default bot behavior?
    - [ ] No
    - [x] Yes (**explain why**)
It will activate automatically based on default config. 


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

## AI Assistance
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.
-->
Nothing beyond search functionality and autocomplete. 


## Final Checklist

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

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

---------

Co-authored-by: bash <hermensb@gmail.com>
Co-authored-by: Revision <tkn963@gmail.com>
Co-authored-by: kadeshar <kadeshar@gmail.com>
2026-04-10 22:16:58 -07:00
kadeshar
74ccc6fbe9
Mage additional stat weight (#2299)
<!--
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 -->

Mage before getting Molten Armor dont prioritize Spirit before Intellect


## 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 mage which dont have Molten Armor (level < 62)
2. Give him 2 items for same slot one with spirit one with intellect and
unequip item on this slot and destroy
3. Bot should equip this with intellect (if other stats are same)


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

Mage before getting Molten Armor dont prioritize Spirit before Intellect

- 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 find best spot to change stat weights depending of class and
HasSpell.

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

Example stat weights when using Molten Armor (Fire P1 Preset)
<img width="777" height="340" alt="obraz"
src="https://github.com/user-attachments/assets/1e90e0e2-502c-4e3c-80ff-42c73589fccb"
/>
Example stat weight when using Mage Armor (Fire P1 Preset)
<img width="781" height="348" alt="obraz"
src="https://github.com/user-attachments/assets/3f5ece3b-daed-41a0-ab7d-f6615be5f9b4"
/>
2026-04-10 22:16:28 -07:00
Boidl
ae9b76aaa5
Fix Undead/Draenei bots stuck in starting zones (#2298)
## Pull Request Description

Undead and Draenei bots get stuck in an idle/rest loop in their starting
zones because the default NPC scan range (150f) and quest giver filter
(80f) are too small and not enough. NPCs fall within range for
`WANDER_NPC` to activate (requires >= 3).

This adds a configurable area-based override that increases both ranges
to 200f only in affected areas. All other zones remain at default
values.

  ## Feature Evaluation

- **Minimum logic**: One `std::set::count()` lookup per `Calculate()`
call to check if the bot's current area is in the override list. If yes,
scan range is 200f instead of 150f.
- **Processing cost**: `GetAreaId()` is a cached uint32 read (~1ns).
`std::set::count()` on a 2-element set is O(log n) ≈ 1 comparison.
Negligible compared to the grid scan itself (~33,000-53,000ns).

  ## How to Test the Changes

  1. Create Undead or Draenei bots (level 1)
2. Observe that they pick up quests and start moving in Deathknell/Ammen
Vale
  3. Without this fix, they sit in REST status indefinitely
4. Optionally add/remove area IDs via
`AiPlayerbot.RpgScanRangeOverrideAreaIds` in playerbots.conf

  ## Impact Assessment

- Does this change increase per-bot/per-tick processing or risk scaling
poorly with thousands of bots?
      - [x] Minimal impact
      - pmon data (500 bots, extended run):
        - Default 150f: 0.033ms avg
        - Global 200f: 0.053ms avg
        - Area check (this PR): 0.042ms avg
- The 0.009ms increase over default is caused by bots currently in
starting zones scanning at 200f. Bots outside override areas are
unaffected.

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

- Does this change add new decision branches or increase maintenance
complexity?
      - [x] No
- Uses the existing `LoadSet`/`std::set` config pattern already used
throughout the codebase.

  ## AI Assistance

  - [x] Yes
- Used AI to speed up understanding the codebase, locate relevant
functions, and compare with the cmangos playerbots implementation. All
code was reviewed and tested manually.

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

The root cause is that `WANDER_NPC` requires `possibleTargets.size() >=
3`, but sparse starting zones have fewer than 3 NPC-flagged units within
150f. Increasing the scan range to 200f brings enough NPCs into range
for the status check to pass. The override is configurable via
`AiPlayerbot.RpgScanRangeOverrideAreaIds` so server admins can add more
areas without code changes.
2026-04-10 22:16:15 -07:00
kadeshar
e13aa7d2f6
IsTank fix for Death Knight (#2296)
<!--
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 -->

Death Knights with Frost Presents are recognized by bots as tank

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. Create Death Knight and apply Frost Presence 
2. Invite Restoration Druid bot to party
3. Bot should apply "Thorns" on you

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

Bots now identify Death Knight with Frost Presence as tank


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

Example of non Blood Death Knight tank build
https://forum.warmane.com/showthread.php?325582
2026-04-10 22:15:57 -07:00
kadeshar
7cc00e6283
Focus heal targets strategy migration (#2254)
<!--
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 -->

Migration for "focus heal targets" strategy which order healer to focus
only on specified targets

Commands:
`nc +focus heal targets`
`co +focus heal targets`
`focus heal +botName`
`focus heal -botName`
`focus heal ?`

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

- create bots party with at least one healer
- apply to healer strategy
- order healer focus on single bot
- start fight (for example with dummy)
- use `.damage 10000` to bots a watch which are healed directly and
which one only via aoe healing

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



## 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?
    - - [ ] No
    - - [x] Yes (**list messages in the table**)

| Message key  | Default message |
| --------------- | ------------------ |
focus_heal_not_healer | I''m not a healer or offhealer (please change my
strats to heal or offheal)
focus_heal_provide_names | Please provide one or more player names
focus_heal_no_targets | I don''t have any focus heal targets
focus_heal_current_targets | My focus heal targets are %targets
focus_heal_cleared | Removed focus heal targets
focus_heal_add_remove_syntax | Please specify a + for add or - to remove
a target
focus_heal_not_in_group | I''m not in a group
focus_heal_not_in_group_with | I''m not in a group with %player_name
focus_heal_added | Added %player_name to focus heal targets
focus_heal_removed | Removed %player_name from focus heal targets

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

OpenCode, to help migrate strategy

## Final Checklist

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

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

From tests:
<img width="515" height="471" alt="obraz"
src="https://github.com/user-attachments/assets/ed6a2bce-e3b1-4fce-ba6f-1a2b10673c61"
/>

Its optional strategy mostly for raiders then performance is not a
issue.

---------

Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com>
Co-authored-by: bash <hermensb@gmail.com>
Co-authored-by: Revision <tkn963@gmail.com>
2026-04-10 22:15:47 -07:00
kadeshar
51a0d643b6
Crashfix for wait for attack (#2303)
<!--
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 -->

Fixed crash related with setting height for new best safe spot.

## 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. Create raid group
2. Go to Molten Core
3. Add wait for attack strategy to bot and set time
4. Attack mob
5. If bot/bots will wait set time and server dont crash then is ok

## 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 find existing method which safetly get height for specific point.

<!--
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-04-10 15:17:13 -07:00
Keleborn
5e2f2823ec
fix(Core/Paladin): Remove duplicate trigger registrations (#2301)
<!--
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 "lay on hands", "lay on hands on party", "blessing of protection on
party", and "divine plea" triggers were registered twice with
conflicting priorities, causing double-firing. Removes the old block
while preserving the new "hand of freedom on party" trigger.
@kadeshar Can you confirm this matches your intent? 

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



## 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 spotted the error on review and ran ahead with a fix, but I
confirmed that it is infact duplicate.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>


## Final Checklist

- - [X] Stability is not compromised.
- - [X] Performance impact is understood, tested, and acceptable.
- - [X] Added logic complexity is justified and explained.
- - [X] Documentation updated if needed (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.6 (1M context) <noreply@anthropic.com>
2026-04-10 15:17:03 -07:00
bash
03db0c34b2 improving RPG traveland minimize wierd path selections but still happen 2026-04-10 12:49:49 +02:00
bash
cd16f6baf1 z-axe clamping to prevent clipping throught the map 2026-04-10 12:47:49 +02:00
bashermens
4bcf8fd2c4
Performance(Core): Some activity sec to ms init fixes, global activity loop check and some additional minor fixes (#2288)
<!--
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

1. Corrected the init activity times; Since ive changed (previous) the
calc from seconds to ms to increase more scope for the offset execute
jitter (removed timer(), 0 is correct way todo now with MS) (also broke
self-bot)
2. Global loop checks in activityAllowed instead vs multiple loops, this
function is called very often so we better optimize it.
3 Fixed the broken 'HasManyPlayersNearby' function and then deleted it
:O To fragile due various edge cases and rather expensive call, besides
lets activeAlone deal with this situation which is way more controlled.
4. Some additional small fixes that where unnoticed overtime.
5. Added/Changed inline comments which makes more sense and explains
what it does.
6. Removed dead code freeze bots during init.
7. self-bot fix

## 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**)
    - - [x] Moderate impact (**explain below**)

When playing with larger amount of real players makes the allowed
activity perform better.

- 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-04-08 19:35:18 +02:00
bash
9ebccc23a2 clearified the ac 2026-04-05 11:25:53 +02:00
kadeshar
a4a3a3d964
Fix for github action translation labeling (#2285)
Maintenance PR
2026-04-04 22:10:14 +02:00
Benjamin Jackson
7b04c56956
Add default case to mount initialization for bots. (#2276)
## Pull Request Description
<!-- Describe what this change does and why it is needed -->
This PR adds a default case for the mount initialization function for
player bots, allowing custom race additions to not crash when added to
an AzerothCore server (such as
[`mod-worgoblin`](https://github.com/heyitsbench/mod-worgoblin)).

## Feature Evaluation
This feature ideally does not get touched in standard `mod-playerbots`
usage, as it only adds a case to a switch statement, and would not be
getting taken with Blizzlike data (which the vast majority of users
likely use).

## How to Test the Changes
1. Add custom races to your AzerothCore instance (most easily
accomplished with the above linked module).
2. Start the server and configure it to create random player bots (that
fall under the added custom races).
3. Observe no crash.

## Impact Assessment
- Does this change increase per-bot/per-tick processing or risk scaling
poorly with thousands of bots?
    - - [X] No, not at all

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

- Does this change add new decision branches or increase maintenance
complexity?
    - - [X] Yes (**explain below**)
This does add another path in a switch statement. It also creates
another set of data for that case. This could have been avoided by
consolidating the human/orc cases into the default case, but I elected
not to do that in case players make their own changes. If preferred, I
can definitely consolidate the cases to not have those redundant data
points.

## Messages to Translate
- Does this change add bot messages to translate?
    - - [X] No

## AI Assistance
- Was AI assistance used while working on this change?
    - - [X] No

## Final Checklist
- - [X] Stability is not compromised.
- - [X] Performance impact is understood, tested, and acceptable.
- - [X] Added logic complexity is justified and explained.
- - [ ] Documentation updated if needed (Conf comments, WiKi commands).

## Notes for Reviewers
Pretty please? 🥹

---------

Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com>
2026-04-04 13:22:27 +02:00
kadeshar
4c9b0adb72
Fire mage cc (#2281)
<!--
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 -->

Added support for fire mage cc spells like: Dragon's Breath (disorient)
and Blast Wave (knockback)

## 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 fire mage to party
2. Add strategy `nc +duel`
3. Start duel and go near bot
4. Bot should use frost nova/dragon's breath/blast wave depends of
cooldowns

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

Mages cc by default

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



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

OpenCode, for research differences between CcStrategy implementation
between cmangos and ac playerbots

## Final Checklist

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

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
2026-04-04 13:00:51 +02:00
dillyns
4ba05962c9
Add fireball fallback for frostfire bolt for frost mage (#2271)
<!--
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 -->
Low level frost mages who do not yet have Frostfire Bolt never use Brain
Freeze procs, since it is trying to use Frostfire Bolt. This adds
Fireball as the fallback for Frostfire Bolt, so frost mages without
frostfire bolt will still use Fireball when they get Brain Freeze


## 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.
-->
Get a frost mage without Frostfire Bolt who has the Brain Freeze talent.
A level 60 frost mage works for example.
Have them attack something until Brain Freeze procs. They should now use
Fireball instead of ignoring 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**)



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



## Final Checklist

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

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
2026-04-04 13:00:26 +02:00
Keleborn
ca54cff6f5
Bug fix. Edge case where bots would get stuck in cities. (#2269)
<!--
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 -->
When I refactored flight destinations, I wanted to make where bots go
more intentional. so I made it dependent on the
allianceHubsPerLevelCache and hodeHubsPerLevelCache. This system relied
on there being an innkeeper in each area that the bots would fly to.
However, not every zone has an innkeeper, and so there was an odd
situation where bots had nowhere to fly to. (Most notably at level 53.)
This solves that by hardcoding the flightmasters in those areas into the
cache.

I also put back in the city teleport probability check which was forcing
every bot to teleport to a city on level up.


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



## 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.
-->
Debugging and comments.



## Final Checklist

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

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
2026-04-04 13:00:01 +02:00
Crow
f5363c9465
Fix Warrior Battle Shout Spam (#2259)
<!--
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 -->
There is a known problem that Warriors will repeatedly spam Battle Shout
(BS) if there is a stronger Blessing of Might (BoM) present that they
cannot override. It has been long known that the issue arises from the
lack of a BS trigger. It was not a problem in the past because BS and
BoM used to (erroneously) stack, and that was eliminated when AC ported
TC's stacking rules.

This PR adds a trigger for BS that prevents the bot from attempting to
cast it if a stronger BoM (or Greater BoM) is present. To do this, it
compares the AP bonus of any BoM/GBoM applied to the Warrior bot, and
then will attempt BS only if the Warrior has a stronger one.

I also did some minor reformatting of the other Warrior triggers, mostly
just deleting comments and extra braces.

## 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 trigger is fairly complicated because it uses an AP comparison and
takes into account spell ranks and the talent Commanding Presence (which
increases the strength of BS). The bug could be solved by a very simple
aura check for BoM/GBoM. That's suboptimal though because BS will be
stronger if the Paladin has a lower rank of BoM or the Warrior has
Commanding Presence and the Paladin does not have Improved BoM.

At the end of the day, this is a new trigger that runs every tick, but
it's not really more expensive than generic BuffTriggers, which all do
this. And it is needed to solve a very annoying bug that essentially
renders Warriors useless in any optimized raid unless BS is disabled.

I don't believe this more robust trigger (as opposed to just calling
BuffTrigger and additionally checking for any BoM/GBoM aura) has any
further impact on performance--any added logic is practically free AFAIK
(e.g., checking if the bot has a particular talent is just an integer
lookup).


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

Form a group with a Warrior and Paladin bot, with the Warrior bot having
a weaker BS than the Paladin's BoM (this is naturally the case at max
level, unless the Warrior has Commanding Presence and the Paladin does
not have Improved BoM). Have the Paladin cast BoM on the Warrior if it
has not done so automatically. Enter combat and see if the Warrior uses
BS. Try again with the Warrior having a stronger BS.



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

As noted above, this is a new BuffTrigger that runs every tick.

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

Default behavior was broken.

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

This is, with respect to the code, a pretty complex trigger. But that
should not impact other logic, and if need be, there is a very simple
version that could be implemented that would at least fix the bug, as
noted above.

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

I used Claude Sonnet 4.6. This is essentially the first PR that I
entirely vibe coded. I knew what the problem was and how to fix it,
including the conditions I wanted in the trigger, and the LLM did the
rest. I can tell you what each block does and why that's needed, but I
can't tell you how they all work technically.

I tested this ingame in scenarios where the available BS was stronger
and not as strong, and in each case, the correct behavior was observed.
I've since run several raids without BS disabled on my Warriors, and
they are correctly not using it (as my Ret Paladin is applying a
stronger BoM). The trigger does not show up as an expensive one with
pmon (unlike, say, VIgilance, which is probably the most expensive class
trigger in the mod (excluding any DK triggers just because I've never
used one so I have no idea)).

## Final Checklist

- - [x] Stability is not compromised.
- - [x] Performance impact is understood, tested, and acceptable.
- - [x] Added logic complexity is justified and explained.
- - [x] Documentation updated if needed (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-04-04 12:59:45 +02:00
kadeshar
a87999bef5
Berserker Rage support (#2261)
<!--
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 -->

Added Berserker Rage usage (in combat and outside combat) for Warrior
(all specs)

Related with: #1755 

## 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 warrior to party
- [optional] start combat (for example with dummy)
- use command `.aura 6215`
- bot should cast Berserker Rage

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

Warriors use Berserker Rage when they got fear, sleep or sap

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



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

Copilot CLI to review

## Final Checklist

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

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

<img width="324" height="125" alt="obraz"
src="https://github.com/user-attachments/assets/e502f1c1-88f4-42a1-b07b-fc9ef3fd318b"
/>

[Berserker Rage performance
test.txt](https://github.com/user-attachments/files/26333365/Berserker.Rage.performance.test.txt)
2026-04-03 22:32:32 -07:00
kadeshar
30c142aaca
Challenging Roar support (#2238)
<!--
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 -->
Added Challenging Roar support to druid taunt spell
Related with: #2002 

## 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 dungeon with druid tank and monitor spell usage


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

Druid also use Challenging Roar as taunt spell


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



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



## Final Checklist

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

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
2026-04-03 22:31:42 -07:00
kadeshar
d07ddb14d0
Paladin use bubble heal strategy (#2244)
<!--
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 -->

Added support for bubble heal strategy.
Adjusted emergency action order

## 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 paladin bot to party
- check that bot dont have `healer dps` strategy
- start combat (with for example dummy)
- use `.damage 20000` where 20000 is near max health of bot
- use `.unaura 25771` until bot use Divine Shield
- bot should heal himself 

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

Paladin bot heal himself without `healer dps` while Divine Shield


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



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



## Final Checklist

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

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

<img width="297" height="142" alt="obraz"
src="https://github.com/user-attachments/assets/5e35341b-2b23-4d34-b0c6-dc228823850f"
/>
2026-04-03 22:31:17 -07:00
bashermens
c0390a24fd
feat(Performance): BotActiveAlone activity interval fixes and default settings for avg player (#2250)
<!--
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

- Bugfix the jittering on/off of botAlone activity
- BotActiveAlone activity duration configurable
- Updated the default config values for general user for a smoother
experience
- Added offset jittering for the check allowedActivity and check next AI
delay to prevent cpu spikes

(disabled WhenIsFriend can cause race conditions)

## 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**)
    - - [x] Moderate impact (**explain below**)

In a positive way, bots in your zone and 150 radius will always be
active, meanwhile other bots will be active 40%
of the time with intervals for 60 seconds per bot. With much lower
latencies. All configurable without say.

40% and 60 seconds for more balance for those who seek bots create world
feel more natural and live vs bots leveling without killing the server
performance. Why not 50 due activity of bots itself 40% will result more
into 45-50% like behavior and 50% prolly more 55%-60%. This it not
something we want incorporate when calculating the value since it
depends on various config and situation. But 40% is good base with
default config.

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



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



## Final Checklist

- - [x] Stability is not compromised.
- - [x] Performance impact is understood, tested, and acceptable.
- - [x] Added logic complexity is justified and explained.
- - [x] Documentation updated if needed (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: Revision <tkn963@gmail.com>
Co-authored-by: kadeshar <kadeshar@gmail.com>
2026-04-03 14:54:46 -07:00
Crow
f76c286353
Fix Destruction Warlock Glyphs, Take Two (#2278)
<!--
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 -->
When I previously "fixed" the default glyphs for destro pve, I
accidentally put Life Tap twice. This PR replaces the second Life Tap
with Incinerate (which is what I intended).

I also fixed a typo in the config.


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

Use maintenance on a level 80 Warlock with destro pve spec. Their major
glyphs should be, in order, Life Tap, Conflagrate, and Incinerate.

## 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**)
The default glyphs are wrong.


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



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



## Final Checklist

- - [x] Stability is not compromised.
- - [x] Performance impact is understood, tested, and acceptable.
- - [x] Added logic complexity is justified and explained.
- - [x] Documentation updated if needed (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-04-03 13:31:30 -07:00
Chris Lacy
579f972666
fix: invert NoRtiTrigger condition so mark rti strategy works (#2256)
## Summary
- `NoRtiTrigger::IsActive()` returns `target != nullptr`, meaning it
only fires when a raid icon is **already** assigned — creating a
chicken-and-egg problem where the bot never places the first mark
- Invert to `target == nullptr` so the trigger fires when **no** mark
exists, prompting `MarkRtiAction` to mark the lowest-HP unmarked
attacker
- Once a mark is placed, the trigger stops firing until the marked
target dies

## Test plan
- [ ] Add `mark rti` strategy to a tank bot via `co +mark rti`
- [ ] Enter combat with multiple mobs — bot should auto-mark lowest HP
target with skull
- [ ] Verify mark persists until target dies, then bot marks next target
- [ ] Verify no marking occurs out of combat

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

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>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:29:23 -07:00
Crow
15bf0ab427
Fix Shaman Weapon Enchants & Cure Toxins/Cleanse Spirit (#2234)
<!--
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 -->

1. I've been having persistent issues with Enhancement Shamans sometimes
applying Rockbiter to both weapons instead of MH Windfury and OH
Flametongue. Rockbiter is the alternative for Flametongue and, through
Flametongue, the alternative for Windfury. But there seemed to be no
obvious reason why a Shaman that had all three abilities would ever use
Rockbiter, which costs more mana than Windfury and Flametongue. Claude's
take on it is that there is instability from ItemForSpellValue related
to its poor way of distinguishing handedness, in addition to it having a
1-second cache, which can cause in some scenarios stale caches for the
action running on each hand back-to-back. I still can't say I fully
understand why the issue exists, but the most straightforward fix that
should prevent this from happening is to just have separate mainhand and
offhand actions for each enchant. So that's what this PR does. The
relevant ActionNodes are now:
 
- The MH-specific chain for Enhancement is WF -> FT -> RB. In practice,
Enhancement should never apply RB because all Shamans under level 10
(when FT is learned) are considered Elemental. The FT -> RB node is just
for Elemental.
- The MH-specific Resto chain (not that Resto can dual-wield) is EL ->
FT -> RB. Againt, FT -> RB is just for Elemental.
- OH for Enhancement is only FT. You cannot be Enhancement before level
10, nor can Enhancement dual-wield before level 40, so no alternative is
needed.

3. I commented out Frostbrand Weapon actions/triggers because the
ability is not included in any strategy. I didn't delete the code
because in the future somebody might want to implement it as I
understand it can be useful for Enhancement Shamans in PvP.
4. Shamans are coded to use "cure poison" and "cure disease", which do
not exist in WotLK, having been combined into Cure Toxins. Wishmaster
has PR #1844 that has been open on this for a long time, but I decided
to correct the abilities here anyway as he was going for a more limited
approach, and I decided to rename all the actions and redo the structure
to rely on ActionNode alternatives, which is pretty much the exact
framework that should be used for this type of situation w/r/t bots.
Now, Shamans prefer Cleanse Spirit (Resto talent, which costs the same
as Cure Toxins and also dispels curses), with an alternative of Cure
Toxins (for poisons and disease only). I tested this and it seems to
work well.
5. I deleted empty ActionNodes.
6. I did some cleanup of formatting and such, but this is not intended
to be a comprehensive refactor.


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

I've followed the general intended structure of class strategies with
triggers and actions. The same triggers exist, just different actions
are called based on the trigger that fires, so I don't think there
should be any impact on performance.

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

The best way to get a grasp on if things work is probably to just do
group play for a while with Shamans and make sure they apply the right
enchants and properly cast Cleanse Spirit and Cure Toxins.

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

Shamans previously did not cure poisons or disease at all, and now they
do with the default "cure" strategy applied.

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

One might say having separate actions per hand for enchants is somewhat
more complex, but ultimately I think it is less confusing to keep those
paths separate.

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

I had Claude try to diagnose the weapon enchant issue. It proposed and
provided the separate MH/OH WF/FT actions. The other things were easy
enough for me to do.

## Final Checklist

- - [x] Stability is not compromised.
- - [x] Performance impact is understood, tested, and acceptable.
- - [x] Added logic complexity is justified and explained.
- - [x] Documentation updated if needed (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-04-03 13:25:40 -07:00
kadeshar
76dd91c4fa
Hand of Freedom support (#2233)
<!--
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
Added Hand of Freedom action for paladin.
Related with: #2002 

## 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 paladin bot to party
- start fight (can use dummy target)
- apply some snare effect to bot or yourself (for example `.aura 1715`)
- bot should use hand of freedom

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

Yes, paladin bots start using Hand of Freedom

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



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

OpenCode, as helper to create and review code

## Final Checklist

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

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
<img width="424" height="93" alt="obraz"
src="https://github.com/user-attachments/assets/3cac4454-35af-474d-8ea0-67c462973c79"
/>
2026-04-03 13:25:29 -07:00
dillyns
496d6c9e4c
Blood dk rotation update (#2227)
<!--
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 -->
Improve the blood DK rotation, add hysteria use, improve blood pact use.
Basic Blood priority should be:
1. Keep diseases up
2. Use rune strike whenever possible
3. Use frost and death runes on Icy Touch (highest threat ability)
4. Use unholy runes on death strike to trigger more death runes
5. use blood runes on heart/blood strike

Hysteria should be used on a physical dps, or a tank if no physical dps
is available. Never a caster or healer.
Summon ghoul should be saved for death pact usage. They are honestly a
liability that aggros everything in range without the unholy talent that
turns them into a pet anyways.

## 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.
Removed plague strike from the general rotation. It should only be used
for disease application.
Added death strike on "high unholy rune" trigger instead.
Remove raise dead from generic dk. Only cast it right before death pact.
Add hysteria to bloods default actions.
- Describe the **processing cost** when this logic executes across many
bots.
Minimal change



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

To test rotation changes:
Use a combat parsing addon such as Details! or watch the combat log of a
Blood DK bot.
They should now use death strike instead of using unholy runes on plague
strike.

To test hysteria:
Ungrouped blood dk bots can be observed using it on themselves.
Create a group with the blood dk + casters/healers only. Observe that
they never use hysteria on the casters.
Create a group with blood dk + physical dps. Observe that they use
hysteria on a dps and not themselves.

To test blood pact:
I used GM command .damage to get my Blood DK bot into "critical health"
trigger. They will use raise dead then blood pact.

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



## 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.
-->
Used for brainstorming and exploring the codebase to find similar
patterns.


## Final Checklist

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

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
2026-04-03 13:24:55 -07:00
Crow
79562be2e5
Fix Hunter Aspect Switching + Trigger Cleanups (#2203)
# Pull Request

Note: When I reference Aspect of the Hawk below, it also means Aspect of
the Dragonhawk (the code will use Dragonhawk if the Hunter has it, Hawk
if not, they share actions, triggers, and strategies).

Hunter Aspects are currently bugged. All Hunters, regardless of spec or
strategy, are hardcoded to use Aspect of the Hawk when mana is at 70%+
and Aspect of the Viper when mana drops to "lowMana" from the config
(default is 15%), divided by 2.

This means the following:

- Hawk (bdps) and Viper (bmana) strategies are useless
- Pack (bspeed) and Wild (rnature) strategies are applied, but bots will
rapidly switch back and forth between Pack/Wild and Hawk/Viper,
depending on strategy and mana level.

This PR addresses the issues by doing the following:

- Global Hawk strategy is removed. Now you need to set bdps for Hunters
to use Hawk, but bdps remains the default Aspect strategy for all
Hunters.
- Dedicated Viper strategy is removed, leaving the global strategy.
However, Viper will be used (when lowMana/2) ONLY if the bot is set to
bdps. If the bot has the Wild or Pack strategy, they will not switch to
Viper at all. I did this because I am assuming if you are using Wild or
Pack, you need them for reasons other than to pump DPS.
- The threshold to switch back to Hawk is lowered from 70% to 60%. The
gap between lowMana/2 and 60% is now filled--if bdps is on, Hunters will
switch to Hawk whenever above the Viper threshold, _except_ for when
they have the Viper aura, in which case they will not switch to Hawk
until 60% mana. This lets the Hunter build back mana before swapping
back to Hawk (more like general player behavior) while still letting
them swap from other Aspects to Hawk without needing to be all the way
at 60% mana.
- Gets rid of a weird condition in the Hawk trigger that would make it
so that Hunters would switch to Hawk when at exactly 0 mana. I'm not
sure what the point of that is.

Also, I refactored the triggers a bit because I noticed there was some
dead code in there. I didn't do a comprehensive refactor, but there was
a lot of stuff that clearly didn't make sense even to my eyes, like
back-to-back returns. I think there's more unnecessary code even just in
the triggers, but I didn't want to get too into the weeds with this PR.

---

## Design Philosophy

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

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

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

Principles:

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

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

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

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

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

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

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

---

## Feature Evaluation

Please answer the following:

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

I don't expect there to be any impact on costs, and if anything this PR
removes some unneeded checks from triggers.

---

## How to Test the Changes

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

The easiest way is to go shoot a dummy with Volley until low on mana and
then toggle on selfbot. You can do this with various Aspects active to
test.

## Complexity & Impact

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

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

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

## Defaults & Configuration

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

Described above. Default behavior is broken.

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

## AI Assistance

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

I asked Claude some questions about the triggers to make sure I didn't
screw anything up.

If yes, please specify:

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

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

---

## Final Checklist

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

---

## Notes for Reviewers

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

---------

Co-authored-by: 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-04-03 13:24:37 -07:00
Keleborn
6d2ee70831
ci: Use test-staging branch of azerothcore-wotlk for test-staging PRs (#2280)
<!--
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 -->
Should sync the test staging branch compile checks. 


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



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



## 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.
-->
Wrote the changes. I tested them. Claude Opus.


## Final Checklist

- - [x] Stability is not compromised.
- - [x] Performance impact is understood, tested, and acceptable.
- - [x] Added logic complexity is justified and explained.
- - [x] Documentation updated if needed (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.6 (1M context) <noreply@anthropic.com>
2026-04-03 11:36:19 -07:00
Keleborn
7fa9ab7d34
Module update for CollisionChange (#2242)
<!--
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 -->
Some code changes due to core update change from MMapFactory.h
->MapCollisionData.h


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



## 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.
-->
Identifying problematic areas generated by the change



## Final Checklist

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

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
2026-03-30 11:55:33 -07:00
kadeshar
4dbcd2544c
Auto label PR - github action (#2257)
Maintenance PR
2026-03-29 16:59:18 +02:00
274 changed files with 6480 additions and 2318 deletions

View File

@ -39,7 +39,7 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
repository: 'mod-playerbots/azerothcore-wotlk' repository: 'mod-playerbots/azerothcore-wotlk'
ref: 'Playerbot' ref: ${{ (github.base_ref || github.ref_name) == 'test-staging' && 'test-staging' || 'Playerbot' }}
- name: Set reusable strings - name: Set reusable strings
id: strings id: strings

View File

@ -0,0 +1,39 @@
name: Label translation PRs
on:
pull_request_target:
types: [opened, synchronize, reopened]
permissions:
contents: read
pull-requests: write
jobs:
label-translation:
runs-on: ubuntu-latest
steps:
- name: Fetch PR diff
env:
GH_TOKEN: ${{ github.token }}
run: |
gh api \
repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }} \
--header "Accept: application/vnd.github.v3.diff" > pr.diff
- name: Detect ai_playerbot_texts inserts
id: detect
run: |
if grep -E '^\+.*INSERT[[:space:]]+INTO[[:space:]]+`?ai_playerbot_texts`?' pr.diff; then
echo "has_translation=true" >> "$GITHUB_OUTPUT"
else
echo "has_translation=false" >> "$GITHUB_OUTPUT"
fi
- name: Add label
if: steps.detect.outputs.has_translation == 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
gh pr edit ${{ github.event.pull_request.number }} \
--repo ${{ github.repository }} \
--add-label "Added translation"

View File

@ -23,7 +23,7 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
repository: 'mod-playerbots/azerothcore-wotlk' repository: 'mod-playerbots/azerothcore-wotlk'
ref: 'Playerbot' ref: ${{ (github.base_ref || github.ref_name) == 'test-staging' && 'test-staging' || 'Playerbot' }}
- name: Checkout Playerbot Module - name: Checkout Playerbot Module
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:

View File

@ -24,7 +24,7 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
repository: 'mod-playerbots/azerothcore-wotlk' repository: 'mod-playerbots/azerothcore-wotlk'
ref: 'Playerbot' ref: ${{ (github.base_ref || github.ref_name) == 'test-staging' && 'test-staging' || 'Playerbot' }}
path: 'ac' path: 'ac'
- name: Checkout Playerbot Module - name: Checkout Playerbot Module
uses: actions/checkout@v3 uses: actions/checkout@v3

View File

@ -32,7 +32,7 @@
# LEVELS # LEVELS
# GEAR # GEAR
# QUESTS # QUESTS
# ACTIVITIES # ACTIVITY
# SPELLS # SPELLS
# STRATEGIES # STRATEGIES
# RPG STRATEGY # RPG STRATEGY
@ -581,10 +581,10 @@ AiPlayerbot.AutoGearScoreLimit = 0
# Default: food, taxi, and raid are enabled # Default: food, taxi, and raid are enabled
AiPlayerbot.BotCheats = "food,taxi,raid" AiPlayerbot.BotCheats = "food,taxi,raid"
# List of attunement quests (comma-separated list of quest IDs) that are automatically completed for all bots. # List of attunement quests (comma-separated list of quest IDs) that are automatically completed for all bots.
# While mod-playerbots does not restore removed attunement requirements, although other mods, such as mod-individual-progression, may do so. # While mod-playerbots does not restore removed attunement requirements, other mods, such as mod-individual-progression, may do so.
# This is meant to exclude bots from such requirements. # This is meant to exclude bots from such requirements.
# #
# Default: # Default:
# Caverns of Time - Part 1 # Caverns of Time - Part 1
# - 10279, To The Master's Lair # - 10279, To The Master's Lair
@ -651,9 +651,14 @@ AiPlayerbot.BotTaxiGapJitterMs = 100
#################################################################################################### ####################################################################################################
# PROFESSIONS # PROFESSIONS
# Note: Random bots currently do not get professions
# #
# Percentage of randombots in each class bucket that receive a class-matching
# weighted profession combination. The remaining randombots use the weighted
# random sane-pair profession pool.
# Default: 30
AiPlayerbot.ClassMatchingProfessionChance = 30
# Automatically adds the 'master fishing' strategy to bots that have the fishing skill when the bots master fishes. # Automatically adds the 'master fishing' strategy to bots that have the fishing skill when the bots master fishes.
# Default: 1 (Enabled) # Default: 1 (Enabled)
AiPlayerbot.EnableFishingWithMaster = 1 AiPlayerbot.EnableFishingWithMaster = 1
@ -801,6 +806,27 @@ AiPlayerbot.RandomGearQualityLimit = 3
# Default: 0 (no limit) # Default: 0 (no limit)
AiPlayerbot.RandomGearScoreLimit = 0 AiPlayerbot.RandomGearScoreLimit = 0
# Prefer armor of the class's ideal type: apply 3x score multiplier to class-appropriate armor.
# When enabled, Warriors strongly prefer plate, Shamans prefer mail, etc.
# A truly superior item can still win (no hard filtering), but same-quality
# armor of the preferred type will score 3x higher and be equipped instead.
#
# ARMOR TYPE PREFERENCES:
# Plate: Warriors, Paladins, Death Knights
# Mail: Hunters, Shamans
# Leather: Rogues, Druids
# Cloth: Priests, Mages, Warlocks
#
# Default: 0 (disabled)
AiPlayerbot.PreferClassArmorType = 0
# When enabled, bots prefer spec-appropriate weapons based on speed and weapon type during autogear.
# Examples: Arms Warriors favor slow 2H axes/polearms (Axe Specialization), Combat Rogues
# favor a slow MH with a fast OH, and Enhancement Shamans favor synchronized slow 1H weapons.
# Default: 0 (disabled)
AiPlayerbot.PreferredSpecWeapons = 0
# If disabled, random bots can only upgrade equipment through looting and quests # If disabled, random bots can only upgrade equipment through looting and quests
# Default: 1 (enabled) # Default: 1 (enabled)
AiPlayerbot.IncrementalGearInit = 1 AiPlayerbot.IncrementalGearInit = 1
@ -865,34 +891,65 @@ AiPlayerbot.ExcludedHunterPetFamilies = ""
# #
# #
#################################################################################################### ####################################################################################################
#################################################################################################### ####################################################################################################
# ACTIVITIES # ACTIVITY
# #
# BotActiveAlone
# - Controls how many bots are active when no real players are nearby.
# - Think of it as a rough percentage: 10 means approximately 10% of bots will be active.
# Not exact — the actual number may vary slightly per rotation cycle.
# - The active bots rotate: every <DurationSeconds> a different set of bots takes a turn.
# - The real number of active bots will always be higher than this value, because bots in
# combat, dungeons, battlegrounds, LFG queue, groups with real players, etc. are always
# forced active on top of this (see force rules below).
# - Set to 100 (with SmartScale off) = all bots always active. Maximum server load.
# - Set to 0 = only bots that match a force rule below will be active.
# #
# Specify percent of active bots # BotActiveAloneDurationSeconds
# The default is 100% but will be automatically adjusted if botActiveAloneSmartScale # - How often the active roster rotates (in seconds). A different group of bots wakes up
# is enabled. Regardless, this value is only applied to inactive areas where no real players # and the previous group may go idle.
# are detected. When real players are nearby, the value is always enforced to 100% # - This is a minimum, not exact. If a bot is in combat or meets any force rule when the
AiPlayerbot.BotActiveAlone = 100 # rotation happens, it stays active until those conditions end — it won't be cut off
# mid-fight just because its turn expired.
#
AiPlayerbot.BotActiveAlone = 10
AiPlayerbot.BotActiveAloneDurationSeconds = 30
# Force botActiveAlone when bot is within the specified distance of a real player #
# Force-active rules (1 = on, 0 = off)
# These override the percentage above. If any of these conditions is true, the bot stays active.
#
# InRadius - A real player is within this many yards (set to 0 to disable).
# InZone - A real player is in the same zone (e.g. Elwynn Forest).
# InMap - A real player is on the same continent (e.g. Eastern Kingdoms).
# IsFriend - A real player has this bot on their friends list.
# InGuild - This bot is in a guild that has a real player in it.
#
# Bots are also always forced active (not configurable) when:
# in combat, inside a dungeon/raid/BG, in a BG or LFG queue,
# grouped with a real player, or controlled by a real player.
#
AiPlayerbot.BotActiveAloneForceWhenInRadius = 150 AiPlayerbot.BotActiveAloneForceWhenInRadius = 150
AiPlayerbot.BotActiveAloneForceWhenInZone = 1 AiPlayerbot.BotActiveAloneForceWhenInZone = 1
AiPlayerbot.BotActiveAloneForceWhenInMap = 0 AiPlayerbot.BotActiveAloneForceWhenInMap = 0
AiPlayerbot.BotActiveAloneForceWhenIsFriend = 1 AiPlayerbot.BotActiveAloneForceWhenIsFriend = 0
AiPlayerbot.BotActiveAloneForceWhenInGuild = 1 AiPlayerbot.BotActiveAloneForceWhenInGuild = 1
# SmartScale (automatic scaling of percentage of active bots based on latency) # SmartScale — automatically reduces active bots when the server is struggling.
# The default is 1. When enabled (smart) scales the 'BotActiveAlone' value. # Monitors the server's update time (how long each server tick takes in milliseconds).
# (The scaling will be overruled by the BotActiveAloneForceWhen...rules) # When the server slows down, fewer bots are kept active to reduce load.
# #
# Limitfloor - when DIFF (latency) is above floor, activity scaling begins # Floor (default 50ms) - Below this, no reduction. Server is running fine.
# LimitCeiling - when DIFF (latency) is above ceiling, activity is 0% # Ceiling (default 200ms) - At or above this, all non-forced bots are paused.
# Between floor and ceiling, activity scales down gradually.
# Example: BotActiveAlone=10, floor=50, ceiling=200
# Server at 50ms → ~10% active (no reduction)
# Server at 125ms → ~5% active (half reduction)
# Server at 200ms → 0% active (only forced bots remain)
# #
# MinLevel - only apply scaling when level is above or equal to min(bot)Level # MinLevel/MaxLevel — only bots within this level range are affected by SmartScale.
# MaxLevel - only apply scaling when level is lower or equal of max(bot)Level # Bots outside the range always use the full BotActiveAlone value.
# Force rules always win over SmartScale.
# #
AiPlayerbot.botActiveAloneSmartScale = 1 AiPlayerbot.botActiveAloneSmartScale = 1
AiPlayerbot.botActiveAloneSmartScaleDiffLimitfloor = 50 AiPlayerbot.botActiveAloneSmartScaleDiffLimitfloor = 50
@ -1007,6 +1064,7 @@ AiPlayerbot.EnableNewRpgStrategy = 1
# DoQuest (Default: 60 Select quest from the quest log and head to the location to attempt completion) # DoQuest (Default: 60 Select quest from the quest log and head to the location to attempt completion)
# TravelFlight (Default: 15 Go to the nearest flightmaster and fly to a level-appropriate area) # TravelFlight (Default: 15 Go to the nearest flightmaster and fly to a level-appropriate area)
# Rest (Default: 5 Take a break for a while and do nothing) # Rest (Default: 5 Take a break for a while and do nothing)
# OutdoorPvp (Default: 10 Participate in outdoor PvP capture points if already in an outdoor PvP zone)
AiPlayerbot.RpgStatusProbWeight.WanderRandom = 15 AiPlayerbot.RpgStatusProbWeight.WanderRandom = 15
AiPlayerbot.RpgStatusProbWeight.WanderNpc = 20 AiPlayerbot.RpgStatusProbWeight.WanderNpc = 20
AiPlayerbot.RpgStatusProbWeight.GoGrind = 15 AiPlayerbot.RpgStatusProbWeight.GoGrind = 15
@ -1014,6 +1072,7 @@ AiPlayerbot.RpgStatusProbWeight.GoCamp = 10
AiPlayerbot.RpgStatusProbWeight.DoQuest = 60 AiPlayerbot.RpgStatusProbWeight.DoQuest = 60
AiPlayerbot.RpgStatusProbWeight.TravelFlight = 15 AiPlayerbot.RpgStatusProbWeight.TravelFlight = 15
AiPlayerbot.RpgStatusProbWeight.Rest = 5 AiPlayerbot.RpgStatusProbWeight.Rest = 5
AiPlayerbot.RpgStatusProbWeight.OutdoorPvp = 10
# Bots' minimum and maximum level when teleporting in and out of a zone, according to the new RPG strategy # Bots' minimum and maximum level when teleporting in and out of a zone, according to the new RPG strategy
# Format: AiPlayerbot.ZoneBracket.zoneID = minLevel,maxLevel # Format: AiPlayerbot.ZoneBracket.zoneID = minLevel,maxLevel
@ -1286,7 +1345,7 @@ AiPlayerbot.DeleteRandomBotArenaTeams = 0
AiPlayerbot.PvpProhibitedZoneIds = "2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,3951" AiPlayerbot.PvpProhibitedZoneIds = "2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,3951"
# PvP Restricted Areas (bots don't pvp) # PvP Restricted Areas (bots don't pvp)
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754,3786,3973" AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754,3786,3973,4085,4086,4087,4088"
# Improve reaction speeds in battlegrounds and arenas (may cause lag) # Improve reaction speeds in battlegrounds and arenas (may cause lag)
AiPlayerbot.FastReactInBG = 1 AiPlayerbot.FastReactInBG = 1
@ -1687,7 +1746,7 @@ AiPlayerbot.PremadeSpecLink.9.1.60 = -003203301135112530135201051
AiPlayerbot.PremadeSpecLink.9.1.70 = -003203301135112530135201051-55 AiPlayerbot.PremadeSpecLink.9.1.70 = -003203301135112530135201051-55
AiPlayerbot.PremadeSpecLink.9.1.80 = -003203301135112530135221351-55000005 AiPlayerbot.PremadeSpecLink.9.1.80 = -003203301135112530135221351-55000005
AiPlayerbot.PremadeSpecName.9.2 = destro pve AiPlayerbot.PremadeSpecName.9.2 = destro pve
AiPlayerbot.PremadeSpecGlyph.9.2 = 45785,43390,42454,43394,43393,45785 AiPlayerbot.PremadeSpecGlyph.9.2 = 45785,43390,42454,43394,43393,42453
AiPlayerbot.PremadeSpecLink.9.2.60 = --05203215200231051305031151 AiPlayerbot.PremadeSpecLink.9.2.60 = --05203215200231051305031151
AiPlayerbot.PremadeSpecLink.9.2.80 = 23-0302-05203215220331051335231351 AiPlayerbot.PremadeSpecLink.9.2.80 = 23-0302-05203215220331051335231351
AiPlayerbot.PremadeSpecName.9.3 = affli pvp AiPlayerbot.PremadeSpecName.9.3 = affli pvp
@ -1764,10 +1823,10 @@ AiPlayerbot.PremadeSpecLink.11.6.80 = 05320021--230033312031500531353013251
# Requires sending the command "nc +worldbuff" in chat to a bot (or a group of bots) to enable # Requires sending the command "nc +worldbuff" in chat to a bot (or a group of bots) to enable
# Each entry in the matrix should be formatted as follows: Entry:FactionID,ClassID,SpecID,MinimumLevel,MaximumLevel:SpellID1,SpellID2,etc.; # Each entry in the matrix should be formatted as follows: Entry:FactionID,ClassID,SpecID,MinimumLevel,MaximumLevel:SpellID1,SpellID2,etc.;
# FactionID may be set to 0 for the entry to apply buffs to bots of either faction # FactionID may be set to 0 for the entry to apply buffs to bots of either faction
# The default entries create a cross-faction list of level 80 buffs for each implemented pve spec from the "Premade Specs" section # The default entries create a cross-faction level 60-69 Vanilla buffs, level 70-79 TBC buffs, and level 80 buffs for each implemented pve spec from the "Premade Specs" section
# The default entries may be deleted or modified, and new custom entries may be added # The default entries may be deleted or modified, and new custom entries may be added
AiPlayerbot.WorldBuffMatrix = # WARRIOR ARMS 1:0,1,0,80,80:53760,57358; # WARRIOR FURY 2:0,1,1,80,80:53760,57358; # WARRIOR PROTECTION 3:0,1,2,80,80:53758,57356; # PALADIN HOLY 4:0,2,0,80,80:53749,57332,60347; # PALADIN PROTECTION 5:0,2,1,80,80:53758,57356; # PALADIN RETRIBUTION 6:0,2,2,80,80:53760,57371; # HUNTER BEAST 7:0,3,0,80,80:53760,57325; # HUNTER MARKSMANSHIP 8:0,3,1,80,80:53760,57358; # HUNTER SURVIVAL 9:0,3,2,80,80:53760,57367; # ROGUE ASSASSINATION 10:0,4,0,80,80:53760,57325; # ROGUE COMBAT 11:0,4,1,80,80:53760,57358; # ROGUE SUBTLETY 12:0,4,2,80,80:53760,57367; # PRIEST DISCIPLINE 13:0,5,0,80,80:53755,57327; # PRIEST HOLY 14:0,5,1,80,80:53755,57327; # PRIEST SHADOW 15:0,5,2,80,80:53755,57327; # DEATH KNIGHT BLOOD 16:0,6,0,80,80:53758,57356; # DEATH KNIGHT FROST 17:0,6,1,80,80:53760,57358; # DEATH KNIGHT UNHOLY 18:0,6,2,80,80:53760,57358; # DEATH KNIGHT BLOOD DPS 19:0,6,3,80,80:53760,57371; # SHAMAN ELEMENTAL 20:0,7,0,80,80:53755,57327; # SHAMAN ENHANCEMENT 21:0,7,1,80,80:53760,57325; # SHAMAN RESTORATION 22:0,7,2,80,80:53755,57327; # MAGE ARCANE 23:0,8,0,80,80:53755,57327; # MAGE FIRE 24:0,8,1,80,80:53755,57327; # MAGE FROST 25:0,8,2,80,80:53755,57327; # WARLOCK AFFLICTION 26:0,9,0,80,80:53755,57327; # WARLOCK DEMONOLOGY 27:0,9,1,80,80:53755,57327; # WARLOCK DESTRUCTION 28:0,9,2,80,80:53755,57327; # DRUID BALANCE 29:0,11,0,80,80:53755,57327; # DRUID FERAL BEAR 30:0,11,1,80,80:53749,53763,57367; # DRUID RESTORATION 31:0,11,2,80,80:54212,57334; # DRUID FERAL CAT 32:0,11,3,80,80:53760,57358 AiPlayerbot.WorldBuffMatrix = # WARRIOR ARMS 1:0,1,0,80,80:53760,57358; # WARRIOR FURY 2:0,1,1,80,80:53760,57358; # WARRIOR PROTECTION 3:0,1,2,80,80:53758,57356; # PALADIN HOLY 4:0,2,0,80,80:53749,57332,60347; # PALADIN PROTECTION 5:0,2,1,80,80:53758,57356; # PALADIN RETRIBUTION 6:0,2,2,80,80:53760,57371; # HUNTER BEAST 7:0,3,0,80,80:53760,57325; # HUNTER MARKSMANSHIP 8:0,3,1,80,80:53760,57358; # HUNTER SURVIVAL 9:0,3,2,80,80:53760,57367; # ROGUE ASSASSINATION 10:0,4,0,80,80:53760,57325; # ROGUE COMBAT 11:0,4,1,80,80:53760,57358; # ROGUE SUBTLETY 12:0,4,2,80,80:53760,57367; # PRIEST DISCIPLINE 13:0,5,0,80,80:53755,57327; # PRIEST HOLY 14:0,5,1,80,80:53755,57327; # PRIEST SHADOW 15:0,5,2,80,80:53755,57327; # DEATH KNIGHT BLOOD 16:0,6,0,80,80:53758,57356; # DEATH KNIGHT FROST 17:0,6,1,80,80:53760,57358; # DEATH KNIGHT UNHOLY 18:0,6,2,80,80:53760,57358; # DEATH KNIGHT BLOOD DPS 19:0,6,3,80,80:53760,57371; # SHAMAN ELEMENTAL 20:0,7,0,80,80:53755,57327; # SHAMAN ENHANCEMENT 21:0,7,1,80,80:53760,57325; # SHAMAN RESTORATION 22:0,7,2,80,80:53755,57327; # MAGE ARCANE 23:0,8,0,80,80:53755,57327; # MAGE FIRE 24:0,8,1,80,80:53755,57327; # MAGE FROST 25:0,8,2,80,80:53755,57327; # WARLOCK AFFLICTION 26:0,9,0,80,80:53755,57327; # WARLOCK DEMONOLOGY 27:0,9,1,80,80:53755,57327; # WARLOCK DESTRUCTION 28:0,9,2,80,80:53755,57327; # DRUID BALANCE 29:0,11,0,80,80:53755,57327; # DRUID FERAL BEAR 30:0,11,1,80,80:53749,53763,57367; # DRUID RESTORATION 31:0,11,2,80,80:54212,57334; # DRUID FERAL CAT 32:0,11,3,80,80:53760,57358; # WARRIOR ARMS TBC 33:0,1,0,70,79:28520,33256; # WARRIOR FURY TBC 34:0,1,1,70,79:28520,33256; # WARRIOR PROTECTION TBC 35:0,1,2,70,79:28518,33257; # PALADIN HOLY TBC 36:0,2,0,70,79:28491,39627,33263; # PALADIN PROTECTION TBC 37:0,2,1,70,79:28518,33257; # PALADIN RETRIBUTION TBC 38:0,2,2,70,79:28520,33256; # HUNTER BEAST TBC 39:0,3,0,70,79:28520,33261; # HUNTER MARKSMANSHIP TBC 40:0,3,1,70,79:28520,33261; # HUNTER SURVIVAL TBC 41:0,3,2,70,79:28520,33261; # ROGUE ASSASSINATION TBC 42:0,4,0,70,79:28520,33261; # ROGUE COMBAT TBC 43:0,4,1,70,79:28520,33261; # ROGUE SUBTLETY TBC 44:0,4,2,70,79:28520,33261; # PRIEST DISCIPLINE TBC 45:0,5,0,70,79:28491,39627,33263; # PRIEST HOLY TBC 46:0,5,1,70,79:28491,39627,33263; # PRIEST SHADOW TBC 47:0,5,2,70,79:28540,33263; # SHAMAN ELEMENTAL TBC 48:0,7,0,70,79:28521,33263; # SHAMAN ENHANCEMENT TBC 49:0,7,1,70,79:28520,33261; # SHAMAN RESTORATION TBC 50:0,7,2,70,79:28491,39627,33263; # MAGE ARCANE TBC 51:0,8,0,70,79:28521,33263; # MAGE FIRE TBC 52:0,8,1,70,79:28540,33263; # MAGE FROST TBC 53:0,8,2,70,79:28540,33263; # WARLOCK AFFLICTION TBC 54:0,9,0,70,79:28540,33263; # WARLOCK DEMONOLOGY TBC 55:0,9,1,70,79:28540,33263; # WARLOCK DESTRUCTION TBC 56:0,9,2,70,79:28540,33263; # DRUID BALANCE TBC 57:0,11,0,70,79:28521,33263; # DRUID FERAL BEAR TBC 58:0,11,1,70,79:28518,33257; # DRUID RESTORATION TBC 59:0,11,2,70,79:28491,39627,33263; # DRUID FERAL CAT TBC 60:0,11,3,70,79:28520,33261; # WARRIOR ARMS VANILLA 61:0,1,0,60,69:17538,24799; # WARRIOR FURY VANILLA 62:0,1,1,60,69:17538,24799; # WARRIOR PROTECTION VANILLA 63:0,1,2,60,69:17626,25661; # PALADIN HOLY VANILLA 64:0,2,0,60,69:17627,18194; # PALADIN PROTECTION VANILLA 65:0,2,1,60,69:17626,25661; # PALADIN RETRIBUTION VANILLA 66:0,2,2,60,69:17628,24799; # HUNTER BEAST VANILLA 67:0,3,0,60,69:17538,18192; # HUNTER MARKSMANSHIP VANILLA 68:0,3,1,60,69:17538,18192; # HUNTER SURVIVAL VANILLA 69:0,3,2,60,69:17538,18192; # ROGUE ASSASSINATION VANILLA 70:0,4,0,60,69:17538,18192; # ROGUE COMBAT VANILLA 71:0,4,1,60,69:17538,18192; # ROGUE SUBTLETY VANILLA 72:0,4,2,60,69:17538,18192; # PRIEST DISCIPLINE VANILLA 73:0,5,0,60,69:17628,18194; # PRIEST HOLY VANILLA 74:0,5,1,60,69:17627,18194; # PRIEST SHADOW VANILLA 75:0,5,2,60,69:17628,18194; # SHAMAN ELEMENTAL VANILLA 76:0,7,0,60,69:17628,18194; # SHAMAN ENHANCEMENT VANILLA 77:0,7,1,60,69:17538,24799; # SHAMAN RESTORATION VANILLA 78:0,7,2,60,69:17627,18194; # MAGE ARCANE VANILLA 79:0,8,0,60,69:17628,18194; # MAGE FIRE VANILLA 80:0,8,1,60,69:17628,18194; # MAGE FROST VANILLA 81:0,8,2,60,69:17628,18194; # WARLOCK AFFLICTION VANILLA 82:0,9,0,60,69:17628,25661; # WARLOCK DEMONOLOGY VANILLA 83:0,9,1,60,69:17628,25661; # WARLOCK DESTRUCTION VANILLA 84:0,9,2,60,69:17628,25661; # DRUID BALANCE VANILLA 85:0,11,0,60,69:17628,18194; # DRUID FERAL BEAR VANILLA 86:0,11,1,60,69:17626,25661; # DRUID RESTORATION VANILLA 87:0,11,2,60,69:17627,18194; # DRUID FERAL CAT VANILLA 88:0,11,3,60,69:17538,24799
# #
# #
@ -1798,12 +1857,24 @@ AiPlayerbot.WorldBuffMatrix = # WARRIOR ARMS 1:0,1,0,80,80:53760,57358; # WARRIO
# #
# #
# arms pve
AiPlayerbot.RandomClassSpecProb.1.0 = 20 AiPlayerbot.RandomClassSpecProb.1.0 = 20
AiPlayerbot.RandomClassSpecIndex.1.0 = 0 AiPlayerbot.RandomClassSpecIndex.1.0 = 0
# fury pve
AiPlayerbot.RandomClassSpecProb.1.1 = 40 AiPlayerbot.RandomClassSpecProb.1.1 = 40
AiPlayerbot.RandomClassSpecIndex.1.1 = 1 AiPlayerbot.RandomClassSpecIndex.1.1 = 1
# prot pve
AiPlayerbot.RandomClassSpecProb.1.2 = 40 AiPlayerbot.RandomClassSpecProb.1.2 = 40
AiPlayerbot.RandomClassSpecIndex.1.2 = 2 AiPlayerbot.RandomClassSpecIndex.1.2 = 2
# arms pvp
AiPlayerbot.RandomClassSpecProb.1.3 = 0
AiPlayerbot.RandomClassSpecIndex.1.3 = 3
# fury pvp
AiPlayerbot.RandomClassSpecProb.1.4 = 0
AiPlayerbot.RandomClassSpecIndex.1.4 = 4
# prot pvp
AiPlayerbot.RandomClassSpecProb.1.5 = 0
AiPlayerbot.RandomClassSpecIndex.1.5 = 5
# #
# #
@ -1815,12 +1886,24 @@ AiPlayerbot.RandomClassSpecIndex.1.2 = 2
# #
# #
# holy pve
AiPlayerbot.RandomClassSpecProb.2.0 = 30 AiPlayerbot.RandomClassSpecProb.2.0 = 30
AiPlayerbot.RandomClassSpecIndex.2.0 = 0 AiPlayerbot.RandomClassSpecIndex.2.0 = 0
# prot pve
AiPlayerbot.RandomClassSpecProb.2.1 = 40 AiPlayerbot.RandomClassSpecProb.2.1 = 40
AiPlayerbot.RandomClassSpecIndex.2.1 = 1 AiPlayerbot.RandomClassSpecIndex.2.1 = 1
# ret pve
AiPlayerbot.RandomClassSpecProb.2.2 = 30 AiPlayerbot.RandomClassSpecProb.2.2 = 30
AiPlayerbot.RandomClassSpecIndex.2.2 = 2 AiPlayerbot.RandomClassSpecIndex.2.2 = 2
# holy pvp
AiPlayerbot.RandomClassSpecProb.2.3 = 0
AiPlayerbot.RandomClassSpecIndex.2.3 = 3
# prot pvp
AiPlayerbot.RandomClassSpecProb.2.4 = 0
AiPlayerbot.RandomClassSpecIndex.2.4 = 4
# ret pvp
AiPlayerbot.RandomClassSpecProb.2.5 = 0
AiPlayerbot.RandomClassSpecIndex.2.5 = 5
# #
# #
@ -1832,12 +1915,24 @@ AiPlayerbot.RandomClassSpecIndex.2.2 = 2
# #
# #
# bm pve
AiPlayerbot.RandomClassSpecProb.3.0 = 33 AiPlayerbot.RandomClassSpecProb.3.0 = 33
AiPlayerbot.RandomClassSpecIndex.3.0 = 0 AiPlayerbot.RandomClassSpecIndex.3.0 = 0
# mm pve
AiPlayerbot.RandomClassSpecProb.3.1 = 33 AiPlayerbot.RandomClassSpecProb.3.1 = 33
AiPlayerbot.RandomClassSpecIndex.3.1 = 1 AiPlayerbot.RandomClassSpecIndex.3.1 = 1
# surv pve
AiPlayerbot.RandomClassSpecProb.3.2 = 33 AiPlayerbot.RandomClassSpecProb.3.2 = 33
AiPlayerbot.RandomClassSpecIndex.3.2 = 2 AiPlayerbot.RandomClassSpecIndex.3.2 = 2
# bm pvp
AiPlayerbot.RandomClassSpecProb.3.3 = 0
AiPlayerbot.RandomClassSpecIndex.3.3 = 3
# mm pvp
AiPlayerbot.RandomClassSpecProb.3.4 = 0
AiPlayerbot.RandomClassSpecIndex.3.4 = 4
# surv pvp
AiPlayerbot.RandomClassSpecProb.3.5 = 0
AiPlayerbot.RandomClassSpecIndex.3.5 = 5
# #
# #
@ -1849,12 +1944,24 @@ AiPlayerbot.RandomClassSpecIndex.3.2 = 2
# #
# #
# as pve
AiPlayerbot.RandomClassSpecProb.4.0 = 45 AiPlayerbot.RandomClassSpecProb.4.0 = 45
AiPlayerbot.RandomClassSpecIndex.4.0 = 0 AiPlayerbot.RandomClassSpecIndex.4.0 = 0
# combat pve
AiPlayerbot.RandomClassSpecProb.4.1 = 45 AiPlayerbot.RandomClassSpecProb.4.1 = 45
AiPlayerbot.RandomClassSpecIndex.4.1 = 1 AiPlayerbot.RandomClassSpecIndex.4.1 = 1
# subtlety pve
AiPlayerbot.RandomClassSpecProb.4.2 = 10 AiPlayerbot.RandomClassSpecProb.4.2 = 10
AiPlayerbot.RandomClassSpecIndex.4.2 = 2 AiPlayerbot.RandomClassSpecIndex.4.2 = 2
# as pvp
AiPlayerbot.RandomClassSpecProb.4.3 = 0
AiPlayerbot.RandomClassSpecIndex.4.3 = 3
# combat pvp
AiPlayerbot.RandomClassSpecProb.4.4 = 0
AiPlayerbot.RandomClassSpecIndex.4.4 = 4
# subtlety pvp
AiPlayerbot.RandomClassSpecProb.4.5 = 0
AiPlayerbot.RandomClassSpecIndex.4.5 = 5
# #
# #
@ -1866,12 +1973,24 @@ AiPlayerbot.RandomClassSpecIndex.4.2 = 2
# #
# #
# disc pve
AiPlayerbot.RandomClassSpecProb.5.0 = 40 AiPlayerbot.RandomClassSpecProb.5.0 = 40
AiPlayerbot.RandomClassSpecIndex.5.0 = 0 AiPlayerbot.RandomClassSpecIndex.5.0 = 0
# holy pve
AiPlayerbot.RandomClassSpecProb.5.1 = 35 AiPlayerbot.RandomClassSpecProb.5.1 = 35
AiPlayerbot.RandomClassSpecIndex.5.1 = 1 AiPlayerbot.RandomClassSpecIndex.5.1 = 1
# shadow pve
AiPlayerbot.RandomClassSpecProb.5.2 = 25 AiPlayerbot.RandomClassSpecProb.5.2 = 25
AiPlayerbot.RandomClassSpecIndex.5.2 = 2 AiPlayerbot.RandomClassSpecIndex.5.2 = 2
# disc pvp
AiPlayerbot.RandomClassSpecProb.5.3 = 0
AiPlayerbot.RandomClassSpecIndex.5.3 = 3
# holy pvp
AiPlayerbot.RandomClassSpecProb.5.4 = 0
AiPlayerbot.RandomClassSpecIndex.5.4 = 4
# shadow pvp
AiPlayerbot.RandomClassSpecProb.5.5 = 0
AiPlayerbot.RandomClassSpecIndex.5.5 = 5
# #
# #
@ -1883,12 +2002,27 @@ AiPlayerbot.RandomClassSpecIndex.5.2 = 2
# #
# #
# blood pve
AiPlayerbot.RandomClassSpecProb.6.0 = 30 AiPlayerbot.RandomClassSpecProb.6.0 = 30
AiPlayerbot.RandomClassSpecIndex.6.0 = 0 AiPlayerbot.RandomClassSpecIndex.6.0 = 0
# frost pve
AiPlayerbot.RandomClassSpecProb.6.1 = 40 AiPlayerbot.RandomClassSpecProb.6.1 = 40
AiPlayerbot.RandomClassSpecIndex.6.1 = 1 AiPlayerbot.RandomClassSpecIndex.6.1 = 1
# unholy pve
AiPlayerbot.RandomClassSpecProb.6.2 = 30 AiPlayerbot.RandomClassSpecProb.6.2 = 30
AiPlayerbot.RandomClassSpecIndex.6.2 = 2 AiPlayerbot.RandomClassSpecIndex.6.2 = 2
# double aura blood pve
AiPlayerbot.RandomClassSpecProb.6.3 = 0
AiPlayerbot.RandomClassSpecIndex.6.3 = 3
# blood pvp
AiPlayerbot.RandomClassSpecProb.6.4 = 0
AiPlayerbot.RandomClassSpecIndex.6.4 = 4
# frost pvp
AiPlayerbot.RandomClassSpecProb.6.5 = 0
AiPlayerbot.RandomClassSpecIndex.6.5 = 5
# unholy pvp
AiPlayerbot.RandomClassSpecProb.6.6 = 0
AiPlayerbot.RandomClassSpecIndex.6.6 = 6
# #
# #
@ -1900,12 +2034,24 @@ AiPlayerbot.RandomClassSpecIndex.6.2 = 2
# #
# #
# ele pve
AiPlayerbot.RandomClassSpecProb.7.0 = 33 AiPlayerbot.RandomClassSpecProb.7.0 = 33
AiPlayerbot.RandomClassSpecIndex.7.0 = 0 AiPlayerbot.RandomClassSpecIndex.7.0 = 0
# enh pve
AiPlayerbot.RandomClassSpecProb.7.1 = 33 AiPlayerbot.RandomClassSpecProb.7.1 = 33
AiPlayerbot.RandomClassSpecIndex.7.1 = 1 AiPlayerbot.RandomClassSpecIndex.7.1 = 1
# resto pve
AiPlayerbot.RandomClassSpecProb.7.2 = 33 AiPlayerbot.RandomClassSpecProb.7.2 = 33
AiPlayerbot.RandomClassSpecIndex.7.2 = 2 AiPlayerbot.RandomClassSpecIndex.7.2 = 2
# ele pvp
AiPlayerbot.RandomClassSpecProb.7.3 = 0
AiPlayerbot.RandomClassSpecIndex.7.3 = 3
# enh pvp
AiPlayerbot.RandomClassSpecProb.7.4 = 0
AiPlayerbot.RandomClassSpecIndex.7.4 = 4
# resto pvp
AiPlayerbot.RandomClassSpecProb.7.5 = 0
AiPlayerbot.RandomClassSpecIndex.7.5 = 5
# #
# #
@ -1917,12 +2063,27 @@ AiPlayerbot.RandomClassSpecIndex.7.2 = 2
# #
# #
# arcane pve
AiPlayerbot.RandomClassSpecProb.8.0 = 30 AiPlayerbot.RandomClassSpecProb.8.0 = 30
AiPlayerbot.RandomClassSpecIndex.8.0 = 0 AiPlayerbot.RandomClassSpecIndex.8.0 = 0
# fire pve
AiPlayerbot.RandomClassSpecProb.8.1 = 30 AiPlayerbot.RandomClassSpecProb.8.1 = 30
AiPlayerbot.RandomClassSpecIndex.8.1 = 1 AiPlayerbot.RandomClassSpecIndex.8.1 = 1
# frost pve
AiPlayerbot.RandomClassSpecProb.8.2 = 40 AiPlayerbot.RandomClassSpecProb.8.2 = 40
AiPlayerbot.RandomClassSpecIndex.8.2 = 2 AiPlayerbot.RandomClassSpecIndex.8.2 = 2
# frostfire pve
AiPlayerbot.RandomClassSpecProb.8.3 = 0
AiPlayerbot.RandomClassSpecIndex.8.3 = 3
# arcane pvp
AiPlayerbot.RandomClassSpecProb.8.4 = 0
AiPlayerbot.RandomClassSpecIndex.8.4 = 4
# fire pvp
AiPlayerbot.RandomClassSpecProb.8.5 = 0
AiPlayerbot.RandomClassSpecIndex.8.5 = 5
# frost pvp
AiPlayerbot.RandomClassSpecProb.8.6 = 0
AiPlayerbot.RandomClassSpecIndex.8.6 = 6
# #
# #
@ -1934,12 +2095,24 @@ AiPlayerbot.RandomClassSpecIndex.8.2 = 2
# #
# #
# affli pve
AiPlayerbot.RandomClassSpecProb.9.0 = 33 AiPlayerbot.RandomClassSpecProb.9.0 = 33
AiPlayerbot.RandomClassSpecIndex.9.0 = 0 AiPlayerbot.RandomClassSpecIndex.9.0 = 0
# demo pve
AiPlayerbot.RandomClassSpecProb.9.1 = 34 AiPlayerbot.RandomClassSpecProb.9.1 = 34
AiPlayerbot.RandomClassSpecIndex.9.1 = 1 AiPlayerbot.RandomClassSpecIndex.9.1 = 1
# destro pve
AiPlayerbot.RandomClassSpecProb.9.2 = 33 AiPlayerbot.RandomClassSpecProb.9.2 = 33
AiPlayerbot.RandomClassSpecIndex.9.2 = 2 AiPlayerbot.RandomClassSpecIndex.9.2 = 2
# affli pvp
AiPlayerbot.RandomClassSpecProb.9.3 = 0
AiPlayerbot.RandomClassSpecIndex.9.3 = 3
# demo pvp
AiPlayerbot.RandomClassSpecProb.9.4 = 0
AiPlayerbot.RandomClassSpecIndex.9.4 = 4
# destro pvp
AiPlayerbot.RandomClassSpecProb.9.5 = 0
AiPlayerbot.RandomClassSpecIndex.9.5 = 5
# #
# #
@ -1951,14 +2124,27 @@ AiPlayerbot.RandomClassSpecIndex.9.2 = 2
# #
# #
# balance pve
AiPlayerbot.RandomClassSpecProb.11.0 = 20 AiPlayerbot.RandomClassSpecProb.11.0 = 20
AiPlayerbot.RandomClassSpecIndex.11.0 = 0 AiPlayerbot.RandomClassSpecIndex.11.0 = 0
# bear pve
AiPlayerbot.RandomClassSpecProb.11.1 = 25 AiPlayerbot.RandomClassSpecProb.11.1 = 25
AiPlayerbot.RandomClassSpecIndex.11.1 = 1 AiPlayerbot.RandomClassSpecIndex.11.1 = 1
# resto pve
AiPlayerbot.RandomClassSpecProb.11.2 = 35 AiPlayerbot.RandomClassSpecProb.11.2 = 35
AiPlayerbot.RandomClassSpecIndex.11.2 = 2 AiPlayerbot.RandomClassSpecIndex.11.2 = 2
# cat pve
AiPlayerbot.RandomClassSpecProb.11.3 = 20 AiPlayerbot.RandomClassSpecProb.11.3 = 20
AiPlayerbot.RandomClassSpecIndex.11.3 = 3 AiPlayerbot.RandomClassSpecIndex.11.3 = 3
# balance pvp
AiPlayerbot.RandomClassSpecProb.11.4 = 0
AiPlayerbot.RandomClassSpecIndex.11.4 = 4
# cat pvp
AiPlayerbot.RandomClassSpecProb.11.5 = 0
AiPlayerbot.RandomClassSpecIndex.11.5 = 5
# resto pvp
AiPlayerbot.RandomClassSpecProb.11.6 = 0
AiPlayerbot.RandomClassSpecIndex.11.6 = 6
# #
# #

View File

@ -0,0 +1,240 @@
-- #########################################################
-- Playerbots - Add focus heal command texts
-- Localized for all WotLK locales (koKR, frFR, deDE, zhCN,
-- zhTW, esES, esMX, ruRU)
-- #########################################################
DELETE FROM ai_playerbot_texts WHERE name IN (
'focus_heal_not_healer',
'focus_heal_provide_names',
'focus_heal_no_targets',
'focus_heal_current_targets',
'focus_heal_cleared',
'focus_heal_add_remove_syntax',
'focus_heal_not_in_group',
'focus_heal_not_in_group_with',
'focus_heal_added',
'focus_heal_removed'
);
DELETE FROM ai_playerbot_texts_chance WHERE name IN (
'focus_heal_not_healer',
'focus_heal_provide_names',
'focus_heal_no_targets',
'focus_heal_current_targets',
'focus_heal_cleared',
'focus_heal_add_remove_syntax',
'focus_heal_not_in_group',
'focus_heal_not_in_group_with',
'focus_heal_added',
'focus_heal_removed'
);
-- focus_heal_not_healer
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1745,
'focus_heal_not_healer',
'I''m not a healer or offhealer (please change my strats to heal or offheal)',
0, 0,
'저는 힐러나 오프힐러가 아닙니다 (전략을 heal 또는 offheal로 변경해주세요)',
'Je ne suis pas un soigneur ou un soigneur secondaire (veuillez changer mes strats en heal ou offheal)',
'Ich bin kein Heiler oder Nebenheiler (bitte ändere meine Strategien auf heal oder offheal)',
'我不是治疗者或副治疗者(请将我的策略更改为 heal 或 offheal',
'我不是治療者或副治療者(請將我的策略更改為 heal 或 offheal',
'No soy un sanador ni un sanador secundario (por favor cambia mis estrategias a heal o offheal)',
'No soy un sanador ni un sanador secundario (por favor cambia mis estrategias a heal o offheal)',
'Я не лекарь и не побочный лекарь (пожалуйста, измените мои стратегии на heal или offheal)');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_not_healer', 100);
-- focus_heal_provide_names
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1746,
'focus_heal_provide_names',
'Please provide one or more player names',
0, 0,
'하나 이상의 플레이어 이름을 제공해주세요',
'Veuillez fournir un ou plusieurs noms de joueurs',
'Bitte geben Sie einen oder mehrere Spielernamen an',
'请提供一个或多个玩家名称',
'請提供一個或多個玩家名稱',
'Por favor proporciona uno o más nombres de jugadores',
'Por favor proporciona uno o más nombres de jugadores',
'Пожалуйста, укажите одно или несколько имён игроков');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_provide_names', 100);
-- focus_heal_no_targets
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1747,
'focus_heal_no_targets',
'I don''t have any focus heal targets',
0, 0,
'지정된 집중 치유 대상이 없습니다',
'Je n''ai aucune cible de soin prioritaire',
'Ich habe keine fokussierten Heilziele',
'我没有任何集中治疗目标',
'我沒有任何集中治療目標',
'No tengo ningún objetivo de sanación prioritario',
'No tengo ningún objetivo de sanación prioritario',
'У меня нет целей приоритетного лечения');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_no_targets', 100);
-- focus_heal_current_targets: %targets is replaced with comma-separated player names
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1748,
'focus_heal_current_targets',
'My focus heal targets are %targets',
0, 0,
'나의 집중 치유 대상: %targets',
'Mes cibles de soin prioritaire sont %targets',
'Meine fokussierten Heilziele sind %targets',
'我的集中治疗目标是 %targets',
'我的集中治療目標是 %targets',
'Mis objetivos de sanación prioritarios son %targets',
'Mis objetivos de sanación prioritarios son %targets',
'Мои цели приоритетного лечения: %targets');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_current_targets', 100);
-- focus_heal_cleared
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1749,
'focus_heal_cleared',
'Removed focus heal targets',
0, 0,
'집중 치유 대상을 제거했습니다',
'Cibles de soin prioritaire supprimées',
'Fokussierte Heilziele entfernt',
'已移除集中治疗目标',
'已移除集中治療目標',
'Objetivos de sanación prioritarios eliminados',
'Objetivos de sanación prioritarios eliminados',
'Цели приоритетного лечения удалены');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_cleared', 100);
-- focus_heal_add_remove_syntax
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1750,
'focus_heal_add_remove_syntax',
'Please specify a + for add or - to remove a target',
0, 0,
'대상을 추가하려면 +, 제거하려면 -를 지정해주세요',
'Veuillez spécifier + pour ajouter ou - pour retirer une cible',
'Bitte geben Sie + zum Hinzufügen oder - zum Entfernen eines Ziels an',
'请指定 + 添加或 - 移除目标',
'請指定 + 添加或 - 移除目標',
'Por favor especifica + para agregar o - para eliminar un objetivo',
'Por favor especifica + para agregar o - para eliminar un objetivo',
'Пожалуйста, укажите + для добавления или - для удаления цели');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_add_remove_syntax', 100);
-- focus_heal_not_in_group
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1751,
'focus_heal_not_in_group',
'I''m not in a group',
0, 0,
'저는 파티에 속해있지 않습니다',
'Je ne suis pas dans un groupe',
'Ich bin in keiner Gruppe',
'我不在队伍中',
'我不在隊伍中',
'No estoy en un grupo',
'No estoy en un grupo',
'Я не в группе');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_not_in_group', 100);
-- focus_heal_not_in_group_with: %player_name is replaced with the target player's name
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1752,
'focus_heal_not_in_group_with',
'I''m not in a group with %player_name',
0, 0,
'%player_name 와(과) 같은 파티에 없습니다',
'Je ne suis pas dans un groupe avec %player_name',
'Ich bin nicht in einer Gruppe mit %player_name',
'我与 %player_name 不在同一队伍中',
'我與 %player_name 不在同一隊伍中',
'No estoy en un grupo con %player_name',
'No estoy en un grupo con %player_name',
'Я не в группе с %player_name');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_not_in_group_with', 100);
-- focus_heal_added: %player_name is replaced with the added player's name
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1753,
'focus_heal_added',
'Added %player_name to focus heal targets',
0, 0,
'%player_name 을(를) 집중 치유 대상에 추가했습니다',
'%player_name ajouté aux cibles de soin prioritaire',
'%player_name zu den fokussierten Heilzielen hinzugefügt',
'已将 %player_name 添加到集中治疗目标',
'已將 %player_name 添加到集中治療目標',
'%player_name agregado a los objetivos de sanación prioritarios',
'%player_name agregado a los objetivos de sanación prioritarios',
'%player_name добавлен в цели приоритетного лечения');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_added', 100);
-- focus_heal_removed: %player_name is replaced with the removed player's name
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1754,
'focus_heal_removed',
'Removed %player_name from focus heal targets',
0, 0,
'%player_name 을(를) 집중 치유 대상에서 제거했습니다',
'%player_name retiré des cibles de soin prioritaire',
'%player_name aus den fokussierten Heilzielen entfernt',
'已将 %player_name 从集中治疗目标中移除',
'已將 %player_name 從集中治療目標中移除',
'%player_name eliminado de los objetivos de sanación prioritarios',
'%player_name eliminado de los objetivos de sanación prioritarios',
'%player_name удалён из целей приоритетного лечения');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('focus_heal_removed', 100);

View File

@ -0,0 +1,102 @@
-- #########################################################
-- Playerbots - Add pull command texts
-- Localized for all WotLK locales (koKR, frFR, deDE, zhCN,
-- zhTW, esES, esMX, ruRU)
-- #########################################################
DELETE FROM ai_playerbot_texts WHERE name IN (
'pull_no_target_error',
'pull_target_too_far_error',
'pull_invalid_target_error',
'pull_action_unavailable_error'
);
DELETE FROM ai_playerbot_texts_chance WHERE name IN (
'pull_no_target_error',
'pull_target_too_far_error',
'pull_invalid_target_error',
'pull_action_unavailable_error'
);
-- pull_no_target_error
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1755,
'pull_no_target_error',
'You have no target',
0, 0,
'대상이 없습니다',
'Vous n''avez pas de cible',
'Du hast kein Ziel',
'你没有目标',
'你沒有目標',
'No tienes objetivo',
'No tienes objetivo',
'У вас нет цели');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('pull_no_target_error', 100);
-- pull_target_too_far_error
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1756,
'pull_target_too_far_error',
'The target is too far away',
0, 0,
'대상이 너무 멀리 있습니다',
'La cible est trop loin',
'Das Ziel ist zu weit entfernt',
'目标太远了',
'目標太遠了',
'El objetivo está demasiado lejos',
'El objetivo está demasiado lejos',
'Цель слишком далеко');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('pull_target_too_far_error', 100);
-- pull_invalid_target_error
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1757,
'pull_invalid_target_error',
'The target can''t be pulled',
0, 0,
'해당 대상은 풀링할 수 없습니다',
'La cible ne peut pas être attirée',
'Das Ziel kann nicht gepullt werden',
'该目标无法被拉怪',
'該目標無法被拉怪',
'No se puede hacer pull al objetivo',
'No se puede hacer pull al objetivo',
'Эту цель нельзя пуллить');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('pull_invalid_target_error', 100);
-- pull_action_unavailable_error: %action_name is replaced with the configured pull action
INSERT INTO `ai_playerbot_texts`
(`id`, `name`, `text`, `say_type`, `reply_type`,
`text_loc1`, `text_loc2`, `text_loc3`, `text_loc4`,
`text_loc5`, `text_loc6`, `text_loc7`, `text_loc8`)
VALUES (
1758,
'pull_action_unavailable_error',
'Can''t perform pull action ''%action_name''',
0, 0,
'''%action_name'' 풀 액션을 수행할 수 없습니다',
'Impossible d''effectuer l''action d''engagement ''%action_name''',
'Die Pull-Aktion ''%action_name'' kann nicht ausgeführt werden',
'无法执行拉怪动作“%action_name”',
'無法執行拉怪動作「%action_name」',
'No se puede realizar la acción de pull ''%action_name''',
'No se puede realizar la acción de pull ''%action_name''',
'Невозможно выполнить действие пула ''%action_name''');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('pull_action_unavailable_error', 100);

View File

@ -45,6 +45,7 @@
#include "NonCombatActions.h" #include "NonCombatActions.h"
#include "OutfitAction.h" #include "OutfitAction.h"
#include "PositionAction.h" #include "PositionAction.h"
#include "PullActions.h"
#include "DropQuestAction.h" #include "DropQuestAction.h"
#include "RandomBotUpdateAction.h" #include "RandomBotUpdateAction.h"
#include "ReachTargetActions.h" #include "ReachTargetActions.h"
@ -63,6 +64,7 @@
#include "WorldBuffAction.h" #include "WorldBuffAction.h"
#include "XpGainAction.h" #include "XpGainAction.h"
#include "NewRpgAction.h" #include "NewRpgAction.h"
#include "NewRpgOutdoorPvP.h"
#include "FishingAction.h" #include "FishingAction.h"
#include "CancelChannelAction.h" #include "CancelChannelAction.h"
#include "WaitForAttackAction.h" #include "WaitForAttackAction.h"
@ -104,6 +106,13 @@ public:
creators["shoot"] = &ActionContext::shoot; creators["shoot"] = &ActionContext::shoot;
creators["lifeblood"] = &ActionContext::lifeblood; creators["lifeblood"] = &ActionContext::lifeblood;
creators["arcane torrent"] = &ActionContext::arcane_torrent; creators["arcane torrent"] = &ActionContext::arcane_torrent;
creators["pull my target"] = &ActionContext::pull_my_target;
creators["pull rti target"] = &ActionContext::pull_rti_target;
creators["pull start"] = &ActionContext::pull_start;
creators["pull action"] = &ActionContext::pull_action;
creators["pull end"] = &ActionContext::pull_end;
creators["return to pull position"] = &ActionContext::return_to_pull_position;
creators["reach pull"] = &ActionContext::reach_pull;
creators["end pull"] = &ActionContext::end_pull; creators["end pull"] = &ActionContext::end_pull;
creators["healthstone"] = &ActionContext::healthstone; creators["healthstone"] = &ActionContext::healthstone;
creators["healing potion"] = &ActionContext::healing_potion; creators["healing potion"] = &ActionContext::healing_potion;
@ -265,6 +274,7 @@ public:
creators["new rpg wander npc"] = &ActionContext::new_rpg_wander_npc; creators["new rpg wander npc"] = &ActionContext::new_rpg_wander_npc;
creators["new rpg do quest"] = &ActionContext::new_rpg_do_quest; creators["new rpg do quest"] = &ActionContext::new_rpg_do_quest;
creators["new rpg travel flight"] = &ActionContext::new_rpg_travel_flight; creators["new rpg travel flight"] = &ActionContext::new_rpg_travel_flight;
creators["new rpg outdoor pvp"] = &ActionContext::new_rpg_outdoor_pvp;
creators["wait for attack keep safe distance"] = &ActionContext::wait_for_attack_keep_safe_distance; creators["wait for attack keep safe distance"] = &ActionContext::wait_for_attack_keep_safe_distance;
} }
@ -311,6 +321,13 @@ private:
static Action* gift_of_the_naaru(PlayerbotAI* botAI) { return new CastGiftOfTheNaaruAction(botAI); } static Action* gift_of_the_naaru(PlayerbotAI* botAI) { return new CastGiftOfTheNaaruAction(botAI); }
static Action* lifeblood(PlayerbotAI* botAI) { return new CastLifeBloodAction(botAI); } static Action* lifeblood(PlayerbotAI* botAI) { return new CastLifeBloodAction(botAI); }
static Action* arcane_torrent(PlayerbotAI* botAI) { return new CastArcaneTorrentAction(botAI); } static Action* arcane_torrent(PlayerbotAI* botAI) { return new CastArcaneTorrentAction(botAI); }
static Action* pull_my_target(PlayerbotAI* botAI) { return new PullMyTargetAction(botAI); }
static Action* pull_rti_target(PlayerbotAI* botAI) { return new PullRtiTargetAction(botAI); }
static Action* pull_start(PlayerbotAI* botAI) { return new PullStartAction(botAI); }
static Action* pull_action(PlayerbotAI* botAI) { return new PullAction(botAI); }
static Action* pull_end(PlayerbotAI* botAI) { return new PullEndAction(botAI); }
static Action* return_to_pull_position(PlayerbotAI* botAI) { return new ReturnToPullPositionAction(botAI); }
static Action* reach_pull(PlayerbotAI* botAI) { return new ReachPullAction(botAI); }
static Action* mana_tap(PlayerbotAI* botAI) { return new CastManaTapAction(botAI); } static Action* mana_tap(PlayerbotAI* botAI) { return new CastManaTapAction(botAI); }
static Action* end_pull(PlayerbotAI* botAI) { return new ChangeCombatStrategyAction(botAI, "-pull"); } static Action* end_pull(PlayerbotAI* botAI) { return new ChangeCombatStrategyAction(botAI, "-pull"); }
static Action* cancel_channel(PlayerbotAI* botAI) { return new CancelChannelAction(botAI); } static Action* cancel_channel(PlayerbotAI* botAI) { return new CancelChannelAction(botAI); }
@ -462,6 +479,7 @@ private:
static Action* new_rpg_wander_npc(PlayerbotAI* ai) { return new NewRpgWanderNpcAction(ai); } static Action* new_rpg_wander_npc(PlayerbotAI* ai) { return new NewRpgWanderNpcAction(ai); }
static Action* new_rpg_do_quest(PlayerbotAI* ai) { return new NewRpgDoQuestAction(ai); } static Action* new_rpg_do_quest(PlayerbotAI* ai) { return new NewRpgDoQuestAction(ai); }
static Action* new_rpg_travel_flight(PlayerbotAI* ai) { return new NewRpgTravelFlightAction(ai); } static Action* new_rpg_travel_flight(PlayerbotAI* ai) { return new NewRpgTravelFlightAction(ai); }
static Action* new_rpg_outdoor_pvp(PlayerbotAI* ai) { return new NewRpgOutdoorPvpAction(ai); }
static Action* wait_for_attack_keep_safe_distance(PlayerbotAI* ai) { return new WaitForAttackKeepSafeDistanceAction(ai); } static Action* wait_for_attack_keep_safe_distance(PlayerbotAI* ai) { return new WaitForAttackKeepSafeDistanceAction(ai); }
}; };

View File

@ -53,22 +53,6 @@ bool AttackMyTargetAction::Execute(Event /*event*/)
bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/) bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
{ {
Unit* oldTarget = context->GetValue<Unit*>("current target")->Get();
bool shouldMelee = bot->IsWithinMeleeRange(target) || botAI->IsMelee(bot);
bool sameTarget = oldTarget == target && bot->GetVictim() == target;
bool inCombat = botAI->GetState() == BOT_STATE_COMBAT;
bool sameAttackMode = bot->HasUnitState(UNIT_STATE_MELEE_ATTACKING) == shouldMelee;
if (bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE ||
bot->HasUnitState(UNIT_STATE_IN_FLIGHT))
{
if (verbose)
botAI->TellError("I cannot attack in flight");
return false;
}
if (!target) if (!target)
{ {
if (verbose) if (verbose)
@ -85,6 +69,15 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
return false; return false;
} }
if (bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE ||
bot->HasUnitState(UNIT_STATE_IN_FLIGHT))
{
if (verbose)
botAI->TellError("I cannot attack in flight");
return false;
}
// Check if bot OR target is in prohibited zone/area (skip for duels) // Check if bot OR target is in prohibited zone/area (skip for duels)
if ((target->IsPlayer() || target->IsPet()) && if ((target->IsPlayer() || target->IsPet()) &&
(!bot->duel || bot->duel->Opponent != target) && (!bot->duel || bot->duel->Opponent != target) &&
@ -121,6 +114,18 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
return false; return false;
} }
// Infantry attacks are not allowed from vehicles drivers.
// Check is needed to stop some auto-attack situations.
if (botAI->IsInVehicle() && !botAI->IsInVehicle(false, false, true))
return false;
Unit* oldTarget = context->GetValue<Unit*>("current target")->Get();
bool shouldMelee = bot->IsWithinMeleeRange(target) || botAI->IsMelee(bot);
bool sameTarget = oldTarget == target && bot->GetVictim() == target;
bool inCombat = botAI->GetState() == BOT_STATE_COMBAT;
bool sameAttackMode = bot->HasUnitState(UNIT_STATE_MELEE_ATTACKING) == shouldMelee;
if (sameTarget && inCombat && sameAttackMode) if (sameTarget && inCombat && sameAttackMode)
{ {
if (verbose) if (verbose)
@ -146,8 +151,7 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
ObjectGuid guid = target->GetGUID(); ObjectGuid guid = target->GetGUID();
bot->SetSelection(target->GetGUID()); bot->SetSelection(target->GetGUID());
context->GetValue<Unit*>("old target")->Set(oldTarget); context->GetValue<Unit*>("old target")->Set(oldTarget);
context->GetValue<Unit*>("current target")->Set(target); context->GetValue<Unit*>("current target")->Set(target);
context->GetValue<LootObjectStack*>("available loot")->Get()->Add(guid); context->GetValue<LootObjectStack*>("available loot")->Get()->Add(guid);

View File

@ -73,7 +73,7 @@ void AutoMaintenanceOnLevelupAction::LearnSpells(std::ostringstream* out)
LearnQuestSpells(out); LearnQuestSpells(out);
} }
void AutoMaintenanceOnLevelupAction::LearnTrainerSpells(std::ostringstream* out) void AutoMaintenanceOnLevelupAction::LearnTrainerSpells(std::ostringstream* /*out*/)
{ {
PlayerbotFactory factory(bot, bot->GetLevel()); PlayerbotFactory factory(bot, bot->GetLevel());
factory.InitSkills(); factory.InitSkills();

View File

@ -27,7 +27,7 @@ bool BankAction::Execute(Event event)
return false; return false;
} }
bool BankAction::ExecuteBank(std::string const text, Unit* bank) bool BankAction::ExecuteBank(std::string const text, Unit* /*bank*/)
{ {
if (text.empty() || text == "?") if (text.empty() || text == "?")
{ {

View File

@ -343,7 +343,7 @@ bool BGJoinAction::isUseful()
return false; return false;
// check Deserter debuff // check Deserter debuff
if (!bot->CanJoinToBattleground()) if (bot->IsDeserter())
return false; return false;
// check if has free queue slots (pointless as already making sure not in queue) // check if has free queue slots (pointless as already making sure not in queue)
@ -534,21 +534,18 @@ bool BGJoinAction::JoinQueue(uint32 type)
botAI->GetAiObjectContext()->GetValue<uint32>("bg type")->Set(0); botAI->GetAiObjectContext()->GetValue<uint32>("bg type")->Set(0);
WorldPacket* packet = nullptr;
if (!isArena) if (!isArena)
{ {
WorldPacket* packet = new WorldPacket(CMSG_BATTLEMASTER_JOIN, 20); packet = new WorldPacket(CMSG_BATTLEMASTER_JOIN, 20);
*packet << bot->GetGUID() << bgTypeId_ << instanceId << joinAsGroup; *packet << bot->GetGUID() << bgTypeId_ << instanceId << joinAsGroup;
/// FIX race condition
// bot->GetSession()->HandleBattlemasterJoinOpcode(packet);
bot->GetSession()->QueuePacket(packet);
} }
else else
{ {
WorldPacket arena_packet(CMSG_BATTLEMASTER_JOIN_ARENA, 20); packet = new WorldPacket(CMSG_BATTLEMASTER_JOIN_ARENA, 20);
arena_packet << unit->GetGUID() << arenaslot << asGroup << uint8(isRated); *packet << unit->GetGUID() << arenaslot << asGroup << uint8(isRated);
bot->GetSession()->HandleBattlemasterJoinArena(arena_packet);
} }
bot->GetSession()->QueuePacket(packet);
return true; return true;
} }

View File

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

View File

@ -21,7 +21,7 @@ public:
} }
bool Execute(Event event) override; bool Execute(Event event) override;
virtual std::string const castString(WorldObject* target) { return "cast"; } virtual std::string const castString(WorldObject* /*target*/) { return "cast"; }
protected: protected:
bool ncCast = false; bool ncCast = false;
@ -49,7 +49,7 @@ public:
bool isUseful() override { return false; } bool isUseful() override { return false; }
virtual bool AcceptSpell(SpellInfo const* spellInfo); virtual bool AcceptSpell(SpellInfo const* spellInfo);
virtual uint32 GetSpellPriority(SpellInfo const* spellInfo) { return 1; } virtual uint32 GetSpellPriority(SpellInfo const* /*spellInfo*/) { return 1; }
virtual bool castSpell(uint32 spellId, WorldObject* wo); virtual bool castSpell(uint32 spellId, WorldObject* wo);
bool Execute(Event event) override; bool Execute(Event event) override;

View File

@ -80,7 +80,7 @@ bool FollowChatShortcutAction::Execute(Event /*event*/)
true, priority); true, priority);
} }
if (Pet* pet = bot->GetPet()) if (bot->GetPet())
botAI->PetFollow(); botAI->PetFollow();
if (moved) if (moved)

View File

@ -116,6 +116,7 @@ bool ChooseRpgTargetAction::Execute(Event /*event*/)
GuidPosition masterRpgTarget; GuidPosition masterRpgTarget;
if (master && master != bot && GET_PLAYERBOT_AI(master) && master->GetMapId() == bot->GetMapId() && !master->IsBeingTeleported()) if (master && master != bot && GET_PLAYERBOT_AI(master) && master->GetMapId() == bot->GetMapId() && !master->IsBeingTeleported())
{ {
//TODO Implement
Player* player = botAI->GetMaster(); Player* player = botAI->GetMaster();
//GuidPosition masterRpgTarget = PAI_VALUE(GuidPosition, "rpg target"); //not used, line marked for removal. //GuidPosition masterRpgTarget = PAI_VALUE(GuidPosition, "rpg target"); //not used, line marked for removal.
} }

View File

@ -62,31 +62,16 @@ bool CleanQuestLogAction::Execute(Event event)
{ {
Player* requester = event.getOwner() ? event.getOwner() : GetMaster(); Player* requester = event.getOwner() ? event.getOwner() : GetMaster();
if (!requester) if (!requester)
{
botAI->TellMaster("No event owner detected");
return false; return false;
}
if (!sPlayerbotAIConfig.dropObsoleteQuests) if (!sPlayerbotAIConfig.dropObsoleteQuests)
{
return false; return false;
}
// Only output this message if "debug rpg" strategy is enabled // Only output this message if "debug rpg" strategy is enabled
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT)) if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
{
botAI->TellMaster("Clean Quest Log command received, removing grey/trivial quests..."); botAI->TellMaster("Clean Quest Log command received, removing grey/trivial quests...");
}
uint8 botLevel = bot->GetLevel(); // Get bot's level uint8 botLevel = bot->GetLevel(); // Get bot's level
uint8 numQuest = 0;
for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
{
if (bot->GetQuestSlotQuestId(slot))
{
numQuest++;
}
}
for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot) for (uint8 slot = 0; slot < MAX_QUEST_LOG_SIZE; ++slot)
{ {
@ -101,34 +86,24 @@ bool CleanQuestLogAction::Execute(Event event)
// Determine if quest is trivial by comparing levels // Determine if quest is trivial by comparing levels
int32 questLevel = quest->GetQuestLevel(); int32 questLevel = quest->GetQuestLevel();
if (questLevel == -1) // For scaling quests, default to bot level if (questLevel == -1) // For scaling quests, default to bot level
{
questLevel = botLevel; questLevel = botLevel;
}
// Set the level difference for when a quest becomes trivial // Set the level difference for when a quest becomes trivial
// This was determined by using the Lua code the client uses // This was determined by using the Lua code the client uses
int32 trivialLevel = 5; int32 trivialLevel = 5;
if (botLevel >= 40) if (botLevel >= 40)
{
trivialLevel = 8; trivialLevel = 8;
}
else if (botLevel >= 30) else if (botLevel >= 30)
{
trivialLevel = 7; trivialLevel = 7;
}
else if (botLevel >= 20) else if (botLevel >= 20)
{
trivialLevel = 6; trivialLevel = 6;
}
// Check if the quest is trivial (grey) for the bot // Check if the quest is trivial (grey) for the bot
if ((botLevel - questLevel) > trivialLevel) if ((botLevel - questLevel) > trivialLevel)
{ {
// Output only if "debug rpg" strategy is enabled // Output only if "debug rpg" strategy is enabled
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT)) if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
{
botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] will be removed because it is trivial (grey)."); botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] will be removed because it is trivial (grey).");
}
// Remove quest // Remove quest
botAI->rpgStatistic.questDropped++; botAI->rpgStatistic.questDropped++;
@ -137,8 +112,6 @@ bool CleanQuestLogAction::Execute(Event event)
bot->SetQuestStatus(questId, QUEST_STATUS_NONE); bot->SetQuestStatus(questId, QUEST_STATUS_NONE);
bot->RemoveRewardedQuest(questId); bot->RemoveRewardedQuest(questId);
numQuest--;
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT)) if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
{ {
const std::string text_quest = ChatHelper::FormatQuest(quest); const std::string text_quest = ChatHelper::FormatQuest(quest);
@ -147,17 +120,13 @@ bool CleanQuestLogAction::Execute(Event event)
} }
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT)) if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
{
botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] has been removed."); botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] has been removed.");
}
} }
else else
{ {
// Only output if "debug rpg" strategy is enabled // Only output if "debug rpg" strategy is enabled
if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT)) if (botAI->HasStrategy("debug rpg", BotState::BOT_STATE_COMBAT))
{
botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] is not trivial and will be kept."); botAI->TellMaster("Quest [ " + quest->GetTitle() + " ] is not trivial and will be kept.");
}
} }
} }
@ -174,7 +143,6 @@ void CleanQuestLogAction::DropQuestType(uint8& numQuest, uint8 wantNum, bool isG
{ {
std::random_device rd; std::random_device rd;
std::mt19937 g(rd()); std::mt19937 g(rd());
std::shuffle(slots.begin(), slots.end(), g); std::shuffle(slots.begin(), slots.end(), g);
} }
@ -200,8 +168,10 @@ void CleanQuestLogAction::DropQuestType(uint8& numQuest, uint8 wantNum, bool isG
bot->GetLevel() <= bot->GetQuestLevel(quest) + uint32(lowLevelDiff)) // Quest is not gray bot->GetLevel() <= bot->GetQuestLevel(quest) + uint32(lowLevelDiff)) // Quest is not gray
{ {
if (bot->GetLevel() + 5 > bot->GetQuestLevel(quest)) // Quest is not red if (bot->GetLevel() + 5 > bot->GetQuestLevel(quest)) // Quest is not red
{
if (!isGreen) if (!isGreen)
continue; continue;
}
} }
else // Quest is gray else // Quest is gray
{ {

View File

@ -168,8 +168,8 @@ bool FollowAction::Execute(Event /*event*/)
? MovementPriority::MOVEMENT_COMBAT ? MovementPriority::MOVEMENT_COMBAT
: MovementPriority::MOVEMENT_NORMAL; : MovementPriority::MOVEMENT_NORMAL;
bool const movingAllowed = IsMovingAllowed(mapId, destX, destY, destZ); bool const movingAllowed = IsMovingAllowed();
bool const dupMove = IsDuplicateMove(mapId, destX, destY, destZ); bool const dupMove = IsDuplicateMove(destX, destY, destZ);
bool const waiting = IsWaitingForLastMove(priority); bool const waiting = IsWaitingForLastMove(priority);
if (movingAllowed && !dupMove && !waiting) if (movingAllowed && !dupMove && !waiting)

View File

@ -151,7 +151,9 @@ bool CastMeleeSpellAction::isUseful()
return CastSpellAction::isUseful(); return CastSpellAction::isUseful();
} }
CastMeleeDebuffSpellAction::CastMeleeDebuffSpellAction(PlayerbotAI* botAI, std::string const spell, bool isOwner, float needLifeTime) : CastDebuffSpellAction(botAI, spell, isOwner, needLifeTime) CastMeleeDebuffSpellAction::CastMeleeDebuffSpellAction(
PlayerbotAI* botAI, std::string const spell, bool isOwner, float needLifeTime) :
CastDebuffSpellAction(botAI, spell, isOwner, needLifeTime)
{ {
range = ATTACK_DISTANCE; range = ATTACK_DISTANCE;
} }
@ -203,6 +205,35 @@ bool CastEnchantItemAction::isPossible()
return spellId && AI_VALUE2(Item*, "item for spell", spellId); 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;
}
CastEnchantItemOffHandAction::CastEnchantItemOffHandAction(PlayerbotAI* botAI, std::string const spell)
: CastEnchantItemAction(botAI, spell) {}
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))
return false;
uint32 invType = item->GetTemplate()->InventoryType;
return invType == INVTYPE_WEAPON || invType == INVTYPE_WEAPONOFFHAND;
}
CastHealingSpellAction::CastHealingSpellAction(PlayerbotAI* botAI, std::string const spell, uint8 estAmount, CastHealingSpellAction::CastHealingSpellAction(PlayerbotAI* botAI, std::string const spell, uint8 estAmount,
HealingManaEfficiency manaEfficiency, bool isOwner) HealingManaEfficiency manaEfficiency, bool isOwner)
: CastAuraSpellAction(botAI, spell, isOwner), estAmount(estAmount), manaEfficiency(manaEfficiency) : CastAuraSpellAction(botAI, spell, isOwner), estAmount(estAmount), manaEfficiency(manaEfficiency)
@ -242,7 +273,7 @@ bool BuffOnPartyAction::Execute(Event /*event*/)
} }
// End greater buff fix // End greater buff fix
CastShootAction::CastShootAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "shoot") CastShootAction::CastShootAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "shoot"), shootSpellId(0)
{ {
if (Item* const pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED)) if (Item* const pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED))
{ {
@ -252,17 +283,40 @@ CastShootAction::CastShootAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "s
{ {
case ITEM_SUBCLASS_WEAPON_GUN: case ITEM_SUBCLASS_WEAPON_GUN:
spell += " gun"; spell += " gun";
shootSpellId = 3018;
break; break;
case ITEM_SUBCLASS_WEAPON_BOW: case ITEM_SUBCLASS_WEAPON_BOW:
spell += " bow"; spell += " bow";
shootSpellId = 3018;
break; break;
case ITEM_SUBCLASS_WEAPON_CROSSBOW: case ITEM_SUBCLASS_WEAPON_CROSSBOW:
spell += " crossbow"; spell += " crossbow";
shootSpellId = 3018;
break;
case ITEM_SUBCLASS_WEAPON_THROWN:
spell = "throw";
shootSpellId = 2764;
break; break;
} }
} }
} }
bool CastShootAction::isPossible()
{
if (shootSpellId)
return botAI->CanCastSpell(shootSpellId, GetTarget(), false);
return CastSpellAction::isPossible();
}
bool CastShootAction::Execute(Event /*event*/)
{
if (shootSpellId)
return botAI->CastSpell(shootSpellId, GetTarget());
return botAI->CastSpell(spell, GetTarget());
}
Value<Unit*>* CastDebuffSpellOnAttackerAction::GetTargetValue() Value<Unit*>* CastDebuffSpellOnAttackerAction::GetTargetValue()
{ {
return context->GetValue<Unit*>("attacker without aura", spell); return context->GetValue<Unit*>("attacker without aura", spell);

View File

@ -130,6 +130,20 @@ public:
std::string const GetTargetName() override { return "self target"; } std::string const GetTargetName() override { return "self target"; }
}; };
class CastEnchantItemMainHandAction : public CastEnchantItemAction
{
public:
CastEnchantItemMainHandAction(PlayerbotAI* botAI, std::string const spell);
bool isPossible() override;
};
class CastEnchantItemOffHandAction : public CastEnchantItemAction
{
public:
CastEnchantItemOffHandAction(PlayerbotAI* botAI, std::string const spell);
bool isPossible() override;
};
class CastHealingSpellAction : public CastAuraSpellAction class CastHealingSpellAction : public CastAuraSpellAction
{ {
public: public:
@ -239,7 +253,12 @@ class CastShootAction : public CastSpellAction
public: public:
CastShootAction(PlayerbotAI* botAI); CastShootAction(PlayerbotAI* botAI);
bool isPossible() override;
bool Execute(Event event) override;
ActionThreatType getThreatType() override { return ActionThreatType::None; } ActionThreatType getThreatType() override { return ActionThreatType::None; }
private:
uint32 shootSpellId;
}; };
class CastLifeBloodAction : public CastHealingSpellAction class CastLifeBloodAction : public CastHealingSpellAction

View File

@ -53,7 +53,7 @@ bool GuildBankAction::Execute(std::string const text, GameObject* bank)
return result; return result;
} }
bool GuildBankAction::MoveFromCharToBank(Item* item, GameObject* bank) bool GuildBankAction::MoveFromCharToBank(Item* item, GameObject* /*bank*/)
{ {
uint32 playerSlot = item->GetSlot(); uint32 playerSlot = item->GetSlot();
uint32 playerBag = item->GetBagSlot(); uint32 playerBag = item->GetBagSlot();

View File

@ -296,7 +296,7 @@ bool PetitionTurnInAction::isUseful()
bool BuyTabardAction::Execute(Event /*event*/) 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))) if (canBuy && AI_VALUE2(uint32, "item count", chat->FormatQItem(5976)))
return true; return true;

View File

@ -78,7 +78,7 @@ private:
class TakeMailProcessor : public MailProcessor class TakeMailProcessor : public MailProcessor
{ {
public: public:
bool Process(uint32 index, Mail* mail, PlayerbotAI* botAI) override bool Process(uint32 /*index*/, Mail* mail, PlayerbotAI* botAI) override
{ {
Player* bot = botAI->GetBot(); Player* bot = botAI->GetBot();
if (!CheckBagSpace(bot)) if (!CheckBagSpace(bot))
@ -104,7 +104,7 @@ public:
{ {
std::vector<uint32> guids; std::vector<uint32> guids;
for (MailItemInfoVec::iterator i = mail->items.begin(); i != mail->items.end(); ++i) for (MailItemInfoVec::iterator i = mail->items.begin(); i != mail->items.end(); ++i)
if (ItemTemplate const* proto = sObjectMgr->GetItemTemplate(i->item_template)) if (sObjectMgr->GetItemTemplate(i->item_template))
guids.push_back(i->item_guid); guids.push_back(i->item_guid);
for (std::vector<uint32>::iterator i = guids.begin(); i != guids.end(); ++i) for (std::vector<uint32>::iterator i = guids.begin(); i != guids.end(); ++i)
@ -157,7 +157,7 @@ private:
class DeleteMailProcessor : public MailProcessor class DeleteMailProcessor : public MailProcessor
{ {
public: public:
bool Process(uint32 index, Mail* mail, PlayerbotAI* botAI) override bool Process(uint32 /*index*/, Mail* mail, PlayerbotAI* botAI) override
{ {
std::ostringstream out; std::ostringstream out;
out << "|cffffffff" << mail->subject << "|cffff0000 deleted"; out << "|cffffffff" << mail->subject << "|cffff0000 deleted";
@ -172,7 +172,7 @@ public:
class ReadMailProcessor : public MailProcessor class ReadMailProcessor : public MailProcessor
{ {
public: public:
bool Process(uint32 index, Mail* mail, PlayerbotAI* botAI) override bool Process(uint32 /*index*/, Mail* mail, PlayerbotAI* botAI) override
{ {
std::ostringstream out, body; std::ostringstream out, body;
out << "|cffffffff" << mail->subject; out << "|cffffffff" << mail->subject;

View File

@ -74,8 +74,18 @@ bool MoveToTravelTargetAction::Execute(Event /*event*/)
float maxDistance = target->getDestination()->getRadiusMin(); float maxDistance = target->getDestination()->getRadiusMin();
// Evenly distribute around the target. // Spread bots around the target but keep the offset stable per
float angle = 2 * M_PI * urand(0, 100) / 100.0; // (bot, destination) pair. Previously the angle and radius were
// re-rolled every time the action re-entered (i.e. every tick the
// bot wasn't already moving), which made bots oscillate between
// two random points around the same quest POI instead of
// committing to one approach.
uint32 botLow = bot->GetGUID().GetCounter();
int32 destSeed = static_cast<int32>(location.GetPositionX()) * 73856093 ^
static_cast<int32>(location.GetPositionY()) * 19349663;
uint32 seed = botLow ^ static_cast<uint32>(destSeed);
float angle = 2.0f * static_cast<float>(M_PI) * static_cast<float>(seed % 1000) / 1000.0f;
float mod = 0.5f + static_cast<float>((seed / 1000) % 1000) / 2000.0f; // [0.5, 1.0]
if (target->getMaxTravelTime() > target->getTimeLeft()) // The bot is late. Speed it up. if (target->getMaxTravelTime() > target->getTimeLeft()) // The bot is late. Speed it up.
{ {
@ -89,9 +99,6 @@ bool MoveToTravelTargetAction::Execute(Event /*event*/)
float z = location.GetPositionZ(); float z = location.GetPositionZ();
float mapId = location.GetMapId(); float mapId = location.GetMapId();
// Move between 0.5 and 1.0 times the maxDistance.
float mod = frand(50.f, 100.f) / 100.0f;
x += cos(angle) * maxDistance * mod; x += cos(angle) * maxDistance * mod;
y += sin(angle) * maxDistance * mod; y += sin(angle) * maxDistance * mod;

View File

@ -63,10 +63,10 @@ void MovementAction::CreateWp(Player* wpOwner, float x, float y, float z, float
bool MovementAction::JumpTo(uint32 mapId, float x, float y, float z, MovementPriority priority) bool MovementAction::JumpTo(uint32 mapId, float x, float y, float z, MovementPriority priority)
{ {
UpdateMovementState(); UpdateMovementState();
if (!IsMovingAllowed(mapId, x, y, z)) if (!IsMovingAllowed())
return false; return false;
if (IsDuplicateMove(mapId, x, y, z)) if (IsDuplicateMove(x, y, z))
return false; return false;
if (IsWaitingForLastMove(priority)) if (IsWaitingForLastMove(priority))
@ -101,6 +101,11 @@ bool MovementAction::MoveNear(WorldObject* target, float distance, MovementPrior
float x = target->GetPositionX() + cos(angle) * distance; float x = target->GetPositionX() + cos(angle) * distance;
float y = target->GetPositionY() + sin(angle) * distance; float y = target->GetPositionY() + sin(angle) * distance;
float z = target->GetPositionZ(); float z = target->GetPositionZ();
// Clamp Z to the terrain under the offset point so we don't
// hand PointMovementGenerator a Z that matches the target's
// floor but not the sampled (x,y) — avoids straight-line
// fallbacks through geometry.
bot->UpdateAllowedPositionZ(x, y, z);
if (!bot->IsWithinLOS(x, y, z)) if (!bot->IsWithinLOS(x, y, z))
continue; continue;
@ -166,11 +171,11 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
bool exact_waypoint, MovementPriority priority, bool lessDelay, bool backwards) bool exact_waypoint, MovementPriority priority, bool lessDelay, bool backwards)
{ {
UpdateMovementState(); UpdateMovementState();
if (!IsMovingAllowed(mapId, x, y, z)) if (!IsMovingAllowed())
{ {
return false; return false;
} }
if (IsDuplicateMove(mapId, x, y, z)) if (IsDuplicateMove(x, y, z))
{ {
return false; return false;
} }
@ -250,7 +255,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
// bot->CastStop(); // bot->CastStop();
// botAI->InterruptSpell(); // botAI->InterruptSpell();
// } // }
DoMovePoint(bot, x, y, z, generatePath, backwards); DoMovePoint(bot, x, y, modifiedZ, generatePath, backwards);
float delay = 1000.0f * MoveDelay(distance, backwards); float delay = 1000.0f * MoveDelay(distance, backwards);
if (lessDelay) if (lessDelay)
{ {
@ -258,7 +263,8 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
} }
delay = std::max(.0f, delay); delay = std::max(.0f, delay);
delay = std::min((float)sPlayerbotAIConfig.maxWaitForMove, delay); delay = std::min((float)sPlayerbotAIConfig.maxWaitForMove, delay);
AI_VALUE(LastMovement&, "last movement").Set(mapId, x, y, z, bot->GetOrientation(), delay, priority); AI_VALUE(LastMovement&, "last movement")
.Set(mapId, x, y, modifiedZ, bot->GetOrientation(), delay, priority);
return true; return true;
} }
} }
@ -778,15 +784,17 @@ bool MovementAction::MoveTo(WorldObject* target, float distance, MovementPriorit
float dx = cos(angle) * needToGo + bx; float dx = cos(angle) * needToGo + bx;
float dy = sin(angle) * needToGo + by; float dy = sin(angle) * needToGo + by;
float dz; // = std::max(bz, tz); // calc accurate z position to avoid stuck // Start from a seed Z between bot and target, then clamp to the
// terrain under (dx,dy). Linear interpolation alone ignores hills
// between the two units and fed PointMovementGenerator a Z that
// could be well above/below ground, triggering straight-line
// fallbacks through walls.
float dz;
if (distanceToTarget > CONTACT_DISTANCE) if (distanceToTarget > CONTACT_DISTANCE)
{
dz = bz + (tz - bz) * (needToGo / distanceToTarget); dz = bz + (tz - bz) * (needToGo / distanceToTarget);
}
else else
{
dz = tz; dz = tz;
} bot->UpdateAllowedPositionZ(dx, dy, dz);
return MoveTo(target->GetMapId(), dx, dy, dz, false, false, false, false, priority); return MoveTo(target->GetMapId(), dx, dy, dz, false, false, false, false, priority);
} }
@ -889,20 +897,7 @@ bool MovementAction::IsMovingAllowed(WorldObject* target)
return IsMovingAllowed(); return IsMovingAllowed();
} }
bool MovementAction::IsMovingAllowed(uint32 mapId, float x, float y, float z) bool MovementAction::IsDuplicateMove(float x, float y, float z)
{
// removed sqrt as means distance limit was effectively 22500 (ReactDistance<63>)
// leaving it commented incase we find ReactDistance limit causes problems
// float distance = sqrt(bot->GetDistance(x, y, z));
// Remove react distance limit
// if (!bot->InBattleground())
// return false;
return IsMovingAllowed();
}
bool MovementAction::IsDuplicateMove(uint32 mapId, float x, float y, float z)
{ {
LastMovement& lastMove = *context->GetValue<LastMovement&>("last movement"); LastMovement& lastMove = *context->GetValue<LastMovement&>("last movement");
@ -1278,7 +1273,7 @@ bool MovementAction::Follow(Unit* target, float distance, float angle)
return true; return true;
} }
bool MovementAction::ChaseTo(WorldObject* obj, float distance, float angle) bool MovementAction::ChaseTo(WorldObject* obj, float distance)
{ {
if (!IsMovingAllowed()) if (!IsMovingAllowed())
{ {
@ -1851,7 +1846,7 @@ bool FleeAction::isUseful()
bool FleeWithPetAction::Execute(Event /*event*/) bool FleeWithPetAction::Execute(Event /*event*/)
{ {
if (Pet* pet = bot->GetPet()) if (bot->GetPet())
botAI->PetFollow(); botAI->PetFollow();
return Flee(AI_VALUE(Unit*, "current target")); return Flee(AI_VALUE(Unit*, "current target"));

View File

@ -43,14 +43,13 @@ protected:
float GetFollowAngle(); float GetFollowAngle();
bool Follow(Unit* target, float distance = sPlayerbotAIConfig.followDistance); bool Follow(Unit* target, float distance = sPlayerbotAIConfig.followDistance);
bool Follow(Unit* target, float distance, float angle); bool Follow(Unit* target, float distance, float angle);
bool ChaseTo(WorldObject* obj, float distance = 0.0f, float angle = 0.0f); bool ChaseTo(WorldObject* obj, float distance = 0.0f);
bool ReachCombatTo(Unit* target, float distance = 0.0f); bool ReachCombatTo(Unit* target, float distance = 0.0f);
float MoveDelay(float distance, bool backwards = false); float MoveDelay(float distance, bool backwards = false);
void WaitForReach(float distance); void WaitForReach(float distance);
void SetNextMovementDelay(float delayMillis); void SetNextMovementDelay(float delayMillis);
bool IsMovingAllowed(WorldObject* target); bool IsMovingAllowed(WorldObject* target);
bool IsMovingAllowed(uint32 mapId, float x, float y, float z); bool IsDuplicateMove(float x, float y, float z);
bool IsDuplicateMove(uint32 mapId, float x, float y, float z);
bool IsWaitingForLastMove(MovementPriority priority); bool IsWaitingForLastMove(MovementPriority priority);
bool IsMovingAllowed(); bool IsMovingAllowed();
bool Flee(Unit* target); bool Flee(Unit* target);

View File

@ -0,0 +1,321 @@
/*
* 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 "AttackersValue.h"
#include "CreatureAI.h"
#include "Playerbots.h"
#include "PlayerbotTextMgr.h"
#include "PositionValue.h"
#include "PullActions.h"
#include "PullStrategy.h"
#include "RtiTargetValue.h"
#include <algorithm>
namespace
{
float GetPullReachDistance(Player* bot, Unit* target, PullStrategy const* strategy)
{
if (!bot || !target || !strategy)
return 0.0f;
float const combatDistance = bot->GetCombatReach() + target->GetCombatReach();
return std::max(0.0f, strategy->GetRange() - combatDistance);
}
bool IsWithinPullRange(Player* bot, Unit* target, PullStrategy const* strategy)
{
return bot && target && strategy && bot->GetExactDist(target) <= strategy->GetRange();
}
}
bool PullRequestAction::Execute(Event event)
{
PullStrategy* strategy = PullStrategy::Get(botAI);
if (!strategy)
return false;
if (!botAI->IsTank(bot))
return false;
Unit* target = GetPullTarget(event);
if (!target || !target->IsInWorld())
{
std::string const text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pull_no_target_error", "You have no target", {});
botAI->TellError(text);
return false;
}
float const maxPullDistance = sPlayerbotAIConfig.reactDistance * 3.0f;
if (target->GetMapId() != bot->GetMapId() || bot->GetDistance(target) > maxPullDistance)
{
std::string const text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pull_target_too_far_error", "The target is too far away", {});
botAI->TellError(text);
return false;
}
if (!AttackersValue::IsPossibleTarget(target, bot))
{
std::string const text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pull_invalid_target_error", "The target can't be pulled", {});
botAI->TellError(text);
return false;
}
if (!strategy->CanDoPullAction(target))
{
std::string const actionName = strategy->GetPullActionName();
std::string const text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"pull_action_unavailable_error",
"Can't perform pull action '%action_name'",
{{"%action_name", actionName}});
botAI->TellError(text);
return false;
}
PositionMap& posMap = AI_VALUE(PositionMap&, "position");
PositionInfo pullPosition = posMap["pull"];
pullPosition.Set(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetMapId());
posMap["pull"] = pullPosition;
strategy->RequestPull(target);
context->GetValue<Unit*>("current target")->Set(target);
botAI->ChangeEngine(BOT_STATE_COMBAT);
botAI->SetNextCheckDelay(sPlayerbotAIConfig.reactDelay);
return true;
}
Unit* PullMyTargetAction::GetPullTarget(Event event)
{
Player* requester = event.getOwner() ? event.getOwner() : GetMaster();
if (event.GetSource() == "attack anything")
return botAI->GetCreature(event.getObject());
return requester ? requester->GetSelectedUnit() : nullptr;
}
Unit* PullRtiTargetAction::GetPullTarget(Event /*event*/)
{
Unit* rtiTarget = AI_VALUE(Unit*, "rti target");
if (rtiTarget)
return rtiTarget;
Group* group = bot->GetGroup();
if (!group)
return nullptr;
std::string const rti = AI_VALUE(std::string, "rti");
int32 const index = RtiTargetValue::GetRtiIndex(rti);
if (index < 0)
return nullptr;
ObjectGuid const guid = group->GetTargetIcon(index);
return guid.IsEmpty() ? nullptr : botAI->GetUnit(guid);
}
bool PullStartAction::Execute(Event event)
{
PullStrategy* strategy = PullStrategy::Get(botAI);
if (!strategy)
return false;
Unit* target = strategy->GetTarget();
if (!target)
return false;
std::string const preActionName = strategy->GetPreActionName();
if (!preActionName.empty() && !botAI->DoSpecificAction(preActionName, event, true))
return false;
if (Pet* pet = bot->GetPet())
{
Creature* creature = pet->ToCreature();
if (creature)
{
strategy->SetPetReactState(creature->GetReactState());
creature->SetReactState(REACT_PASSIVE);
}
}
strategy->OnPullStarted();
return true;
}
PullAction::PullAction(PlayerbotAI* botAI, std::string const name) : CastSpellAction(botAI, name) { InitPullAction(); }
Unit* PullAction::GetTarget()
{
PullStrategy* strategy = PullStrategy::Get(botAI);
if (!strategy)
return nullptr;
return strategy->GetTarget();
}
std::vector<NextAction> PullAction::getPrerequisites()
{
PullStrategy* strategy = PullStrategy::Get(botAI);
Unit* target = strategy ? strategy->GetTarget() : nullptr;
if (!strategy || !target)
return {};
return IsWithinPullRange(bot, target, strategy) ? std::vector<NextAction>{}
: std::vector<NextAction>{ NextAction("reach pull", ACTION_MOVE) };
}
bool PullAction::Execute(Event event)
{
InitPullAction();
PullStrategy* strategy = PullStrategy::Get(botAI);
if (!strategy)
return false;
Unit* target = strategy->GetTarget();
if (!target || !target->IsInWorld())
return false;
if (target->IsInCombat())
return false;
if (!IsWithinPullRange(bot, target, strategy))
{
strategy->RequestPull(target, false);
return false;
}
if (bot->isMoving())
{
bot->StopMoving();
strategy->RequestPull(target, false);
return false;
}
context->GetValue<Unit*>("current target")->Set(target);
if (!botAI->DoSpecificAction(strategy->GetPullActionName(), event, true))
return false;
return true;
}
bool PullAction::isPossible()
{
InitPullAction();
PullStrategy* strategy = PullStrategy::Get(botAI);
if (!strategy)
return false;
Unit* target = strategy->GetTarget();
std::string const spellName = strategy->GetSpellName();
if (!target || !target->IsInWorld() || target->GetMapId() != bot->GetMapId() || spellName.empty())
return false;
return true;
}
void PullAction::InitPullAction()
{
PullStrategy* strategy = PullStrategy::Get(botAI);
if (!strategy)
return;
std::string const spellName = strategy->GetSpellName();
if (spellName.empty())
return;
spell = spellName;
bool isShoot = (spellName == "shoot" || spellName == "shoot bow" ||
spellName == "shoot gun" || spellName == "shoot crossbow" ||
spellName == "throw");
range = botAI->GetRange(isShoot ? "shoot" : "spell");
}
bool PullEndAction::Execute(Event /*event*/)
{
PullStrategy* strategy = PullStrategy::Get(botAI);
if (!strategy)
return false;
Unit* pullTarget = strategy->GetTarget();
if (!strategy->HasPullStarted() && !strategy->IsPullPendingToStart() && !strategy->HasTarget())
return false;
if (Pet* pet = bot->GetPet())
{
Creature* creature = pet->ToCreature();
if (creature)
creature->SetReactState(strategy->GetPetReactState());
}
PositionMap& posMap = AI_VALUE(PositionMap&, "position");
PositionInfo pullPosition = posMap["pull"];
if (pullPosition.isSet())
posMap.erase("pull");
if (pullTarget && context->GetValue<Unit*>("current target")->Get() == pullTarget)
context->GetValue<Unit*>("current target")->Set(nullptr);
strategy->OnPullEnded();
return true;
}
bool ReturnToPullPositionAction::Execute(Event /*event*/)
{
PositionInfo pullPosition = AI_VALUE(PositionMap&, "position")["pull"];
if (!pullPosition.isSet() || pullPosition.mapId != bot->GetMapId())
return false;
return MoveTo(pullPosition.mapId, pullPosition.x, pullPosition.y, pullPosition.z,
false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true);
}
bool ReturnToPullPositionAction::isUseful()
{
PullStrategy* strategy = PullStrategy::Get(botAI);
Unit* target = strategy ? strategy->GetTarget() : nullptr;
if (!strategy || !target || !target->IsInCombat())
return false;
PositionInfo pullPosition = AI_VALUE(PositionMap&, "position")["pull"];
return pullPosition.isSet() && pullPosition.mapId == bot->GetMapId() &&
bot->GetDistance(pullPosition.x, pullPosition.y, pullPosition.z) > sPlayerbotAIConfig.followDistance;
}
bool ReachPullAction::Execute(Event /*event*/)
{
Unit* target = GetTarget();
PullStrategy* strategy = PullStrategy::Get(botAI);
if (!target || !strategy)
return false;
float const reachDistance = GetPullReachDistance(bot, target, strategy);
return ReachCombatTo(target, reachDistance);
}
bool ReachPullAction::isUseful()
{
if (botAI->HasStrategy("stay", botAI->GetState()))
return false;
if (bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL) != nullptr)
return false;
PullStrategy* strategy = PullStrategy::Get(botAI);
Unit* target = strategy ? strategy->GetTarget() : nullptr;
return target && !IsWithinPullRange(bot, target, strategy);
}
Unit* ReachPullAction::GetTarget()
{
PullStrategy* strategy = PullStrategy::Get(botAI);
if (!strategy)
return nullptr;
return strategy->GetTarget();
}

View File

@ -0,0 +1,90 @@
/*
* 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_PULLACTIONS_H
#define _PLAYERBOT_PULLACTIONS_H
#include "GenericSpellActions.h"
#include "ReachTargetActions.h"
class PullRequestAction : public Action
{
public:
PullRequestAction(PlayerbotAI* botAI, std::string const name) : Action(botAI, name) {}
bool Execute(Event event) override;
protected:
virtual Unit* GetPullTarget(Event event) = 0;
};
class PullMyTargetAction : public PullRequestAction
{
public:
PullMyTargetAction(PlayerbotAI* botAI) : PullRequestAction(botAI, "pull my target") {}
private:
Unit* GetPullTarget(Event event) override;
};
class PullRtiTargetAction : public PullRequestAction
{
public:
PullRtiTargetAction(PlayerbotAI* botAI) : PullRequestAction(botAI, "pull rti target") {}
private:
Unit* GetPullTarget(Event event) override;
};
class PullStartAction : public Action
{
public:
PullStartAction(PlayerbotAI* botAI, std::string const name = "pull start") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class PullAction : public CastSpellAction
{
public:
PullAction(PlayerbotAI* botAI, std::string const name = "pull action");
bool Execute(Event event) override;
bool isPossible() override;
std::vector<NextAction> getPrerequisites() override;
Unit* GetTarget() override;
private:
void InitPullAction();
};
class PullEndAction : public Action
{
public:
PullEndAction(PlayerbotAI* botAI, std::string const name = "pull end") : Action(botAI, name) {}
bool Execute(Event event) override;
};
class ReachPullAction : public ReachTargetAction
{
public:
ReachPullAction(PlayerbotAI* botAI) : ReachTargetAction(botAI, "reach pull", botAI->GetRange("spell")) {}
bool Execute(Event event) override;
bool isUseful() override;
Unit* GetTarget() override;
};
class ReturnToPullPositionAction : public MovementAction
{
public:
ReturnToPullPositionAction(PlayerbotAI* botAI) : MovementAction(botAI, "return to pull position") {}
bool Execute(Event event) override;
bool isUseful() override;
};
#endif

View File

@ -45,7 +45,7 @@ std::once_flag ReadyChecker::initFlag;
class HealthChecker : public ReadyChecker class HealthChecker : public ReadyChecker
{ {
public: public:
bool Check(PlayerbotAI* botAI, AiObjectContext* context) override bool Check(PlayerbotAI* /*botAI*/, AiObjectContext* context) override
{ {
return AI_VALUE2(uint8, "health", "self target") > sPlayerbotAIConfig.almostFullHealth; return AI_VALUE2(uint8, "health", "self target") > sPlayerbotAIConfig.almostFullHealth;
} }
@ -56,7 +56,7 @@ public:
class ManaChecker : public ReadyChecker class ManaChecker : public ReadyChecker
{ {
public: public:
bool Check(PlayerbotAI* botAI, AiObjectContext* context) override bool Check(PlayerbotAI* /*botAI*/, AiObjectContext* context) override
{ {
return !AI_VALUE2(bool, "has mana", "self target") || return !AI_VALUE2(bool, "has mana", "self target") ||
AI_VALUE2(uint8, "mana", "self target") > sPlayerbotAIConfig.mediumHealth; AI_VALUE2(uint8, "mana", "self target") > sPlayerbotAIConfig.mediumHealth;
@ -68,7 +68,7 @@ public:
class DistanceChecker : public ReadyChecker class DistanceChecker : public ReadyChecker
{ {
public: public:
bool Check(PlayerbotAI* botAI, AiObjectContext* context) override bool Check(PlayerbotAI* botAI, AiObjectContext* /*context*/) override
{ {
Player* bot = botAI->GetBot(); Player* bot = botAI->GetBot();
if (Player* master = botAI->GetMaster()) if (Player* master = botAI->GetMaster())
@ -90,7 +90,7 @@ public:
class HunterChecker : public ReadyChecker class HunterChecker : public ReadyChecker
{ {
public: public:
bool Check(PlayerbotAI* botAI, AiObjectContext* context) override bool Check(PlayerbotAI* botAI, AiObjectContext* /*context*/) override
{ {
Player* bot = botAI->GetBot(); Player* bot = botAI->GetBot();
if (bot->getClass() == CLASS_HUNTER) if (bot->getClass() == CLASS_HUNTER)
@ -126,7 +126,7 @@ class ItemCountChecker : public ReadyChecker
public: public:
ItemCountChecker(std::string const item, std::string const name) : item(item), name(name) {} ItemCountChecker(std::string const item, std::string const name) : item(item), name(name) {}
bool Check(PlayerbotAI* botAI, AiObjectContext* context) override bool Check(PlayerbotAI* /*botAI*/, AiObjectContext* context) override
{ {
return AI_VALUE2(uint32, "item count", item) > 0; return AI_VALUE2(uint32, "item count", item) > 0;
} }
@ -225,4 +225,4 @@ bool ReadyCheckAction::ReadyCheck()
return true; return true;
} }
bool FinishReadyCheckAction::Execute(Event event) { return ReadyCheck(); } bool FinishReadyCheckAction::Execute(Event /*event*/) { return ReadyCheck(); }

View File

@ -65,7 +65,7 @@ void ReleaseSpiritAction::IncrementDeathCount() const
} }
} }
void ReleaseSpiritAction::LogRelease(const std::string& releaseMsg, bool isAutoRelease) const void ReleaseSpiritAction::LogRelease(const std::string& releaseMsg) const
{ {
const std::string teamPrefix = bot->GetTeamId() == TEAM_ALLIANCE ? "A" : "H"; const std::string teamPrefix = bot->GetTeamId() == TEAM_ALLIANCE ? "A" : "H";
@ -82,13 +82,13 @@ bool AutoReleaseSpiritAction::Execute(Event /*event*/)
{ {
IncrementDeathCount(); IncrementDeathCount();
bot->DurabilityRepairAll(false, 1.0f, false); bot->DurabilityRepairAll(false, 1.0f, false);
LogRelease("auto released", true); LogRelease("auto released");
WorldPacket packet(CMSG_REPOP_REQUEST); WorldPacket packet(CMSG_REPOP_REQUEST);
packet << uint8(0); packet << uint8(0);
bot->GetSession()->HandleRepopRequestOpcode(packet); bot->GetSession()->HandleRepopRequestOpcode(packet);
LogRelease("releases spirit", true); LogRelease("releases spirit");
if (bot->InBattleground()) if (bot->InBattleground())
{ {

View File

@ -18,7 +18,7 @@ public:
: Action(botAI, name) {} : Action(botAI, name) {}
bool Execute(Event event) override; bool Execute(Event event) override;
void LogRelease(const std::string& releaseType, bool isAutoRelease = false) const; void LogRelease(const std::string& releaseType) const;
protected: protected:
void IncrementDeathCount() const; void IncrementDeathCount() const;

View File

@ -251,9 +251,9 @@ GraveyardStruct const* SpiritHealerAction::GetGrave(bool startZone)
std::vector<uint32> races; std::vector<uint32> races;
if (bot->GetTeamId() == TEAM_ALLIANCE) if (bot->GetTeamId() == TEAM_ALLIANCE)
races = {RACE_HUMAN, RACE_DWARF, RACE_GNOME, RACE_NIGHTELF}; races = {RACE_HUMAN, RACE_DWARF, RACE_GNOME, RACE_NIGHTELF, RACE_DRAENEI};
else else
races = {RACE_ORC, RACE_TROLL, RACE_TAUREN, RACE_UNDEAD_PLAYER}; races = {RACE_ORC, RACE_TROLL, RACE_TAUREN, RACE_UNDEAD_PLAYER, RACE_BLOODELF};
float graveDistance = -1; float graveDistance = -1;

View File

@ -154,7 +154,7 @@ bool SayAction::isUseful()
return (time(nullptr) - lastSaid) > 30; return (time(nullptr) - lastSaid) > 30;
} }
void ChatReplyAction::ChatReplyDo(Player* bot, uint32& type, uint32& guid1, uint32& guid2, std::string& msg, std::string& chanName, std::string& name) void ChatReplyAction::ChatReplyDo(Player* bot, uint32& type, uint32& guid1, std::string& msg, std::string& chanName, std::string& name)
{ {
std::string respondsText = ""; std::string respondsText = "";
@ -205,14 +205,14 @@ void ChatReplyAction::ChatReplyDo(Player* bot, uint32& type, uint32& guid1, uint
if (msg.starts_with(sPlayerbotAIConfig.toxicLinksPrefix) if (msg.starts_with(sPlayerbotAIConfig.toxicLinksPrefix)
&& (GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllItemIds(msg).size() > 0 || GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllQuestIds(msg).size() > 0)) && (GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllItemIds(msg).size() > 0 || GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllQuestIds(msg).size() > 0))
{ {
HandleToxicLinksReply(bot, chatChannelSource, msg, name); HandleToxicLinksReply(bot, chatChannelSource);
return; return;
} }
//thunderfury //thunderfury
if (GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllItemIds(msg).count(19019)) if (GET_PLAYERBOT_AI(bot)->GetChatHelper()->ExtractAllItemIds(msg).count(19019))
{ {
HandleThunderfuryReply(bot, chatChannelSource, msg, name); HandleThunderfuryReply(bot, chatChannelSource);
return; return;
} }
@ -220,7 +220,7 @@ void ChatReplyAction::ChatReplyDo(Player* bot, uint32& type, uint32& guid1, uint
SendGeneralResponse(bot, chatChannelSource, messageRepy, name); SendGeneralResponse(bot, chatChannelSource, messageRepy, name);
} }
bool ChatReplyAction::HandleThunderfuryReply(Player* bot, ChatChannelSource chatChannelSource, std::string& msg, std::string& name) bool ChatReplyAction::HandleThunderfuryReply(Player* bot, ChatChannelSource chatChannelSource)
{ {
std::map<std::string, std::string> placeholders; std::map<std::string, std::string> placeholders;
const auto thunderfury = sObjectMgr->GetItemTemplate(19019); const auto thunderfury = sObjectMgr->GetItemTemplate(19019);
@ -248,7 +248,7 @@ bool ChatReplyAction::HandleThunderfuryReply(Player* bot, ChatChannelSource chat
return true; return true;
} }
bool ChatReplyAction::HandleToxicLinksReply(Player* bot, ChatChannelSource chatChannelSource, std::string& msg, std::string& name) bool ChatReplyAction::HandleToxicLinksReply(Player* bot, ChatChannelSource chatChannelSource)
{ {
//quests //quests
std::vector<uint32> incompleteQuests; std::vector<uint32> incompleteQuests;

View File

@ -29,12 +29,12 @@ class ChatReplyAction : public Action
{ {
public: public:
ChatReplyAction(PlayerbotAI* ai) : Action(ai, "chat message") {} ChatReplyAction(PlayerbotAI* ai) : Action(ai, "chat message") {}
virtual bool Execute(Event event) { return true; } virtual bool Execute(Event /*event*/) { return true; }
bool isUseful() { return true; } bool isUseful() { return true; }
static void ChatReplyDo(Player* bot, uint32& type, uint32& guid1, uint32& guid2, std::string& msg, std::string& chanName, std::string& name); static void ChatReplyDo(Player* bot, uint32& type, uint32& guid1, std::string& msg, std::string& chanName, std::string& name);
static bool HandleThunderfuryReply(Player* bot, ChatChannelSource chatChannelSource, std::string& msg, std::string& name); static bool HandleThunderfuryReply(Player* bot, ChatChannelSource chatChannelSource);
static bool HandleToxicLinksReply(Player* bot, ChatChannelSource chatChannelSource, std::string& msg, std::string& name); static bool HandleToxicLinksReply(Player* bot, ChatChannelSource chatChannelSource);
static bool HandleWTBItemsReply(Player* bot, ChatChannelSource chatChannelSource, std::string& msg, std::string& name); static bool HandleWTBItemsReply(Player* bot, ChatChannelSource chatChannelSource, std::string& msg, std::string& name);
static bool HandleLFGQuestsReply(Player* bot, ChatChannelSource chatChannelSource, std::string& msg, std::string& name); static bool HandleLFGQuestsReply(Player* bot, ChatChannelSource chatChannelSource, std::string& msg, std::string& name);
static bool SendGeneralResponse(Player* bot, ChatChannelSource chatChannelSource, std::string& responseMessage, std::string& name); static bool SendGeneralResponse(Player* bot, ChatChannelSource chatChannelSource, std::string& responseMessage, std::string& name);

View File

@ -15,7 +15,7 @@
std::set<uint32> const FISHING_SPELLS = {7620, 7731, 7732, 18248, 33095, 51294}; std::set<uint32> const FISHING_SPELLS = {7620, 7731, 7732, 18248, 33095, 51294};
Creature* SeeSpellAction::CreateWps(Player* wpOwner, float x, float y, float z, float o, uint32 entry, Creature* lastWp, Creature* SeeSpellAction::CreateWps(Player* wpOwner, float x, float y, float z, float o, uint32 entry, Creature* /*lastWp*/,
bool important) bool important)
{ {
float dist = wpOwner->GetDistance(x, y, z); float dist = wpOwner->GetDistance(x, y, z);

View File

@ -0,0 +1,219 @@
/*
* 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 "SetFocusHealTargetsAction.h"
#include "ObjectAccessor.h"
#include "Playerbots.h"
#include "PlayerbotTextMgr.h"
#include <algorithm>
#include <cctype>
static std::string LowercaseString(std::string const& str)
{
std::string result = str;
std::transform(result.begin(), result.end(), result.begin(),
[](unsigned char c) { return std::tolower(c); });
return result;
}
static Player* FindGroupPlayerByName(Player* player, std::string const& playerName)
{
if (!player)
return nullptr;
Group* group = player->GetGroup();
if (!group)
return nullptr;
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
if (member)
{
std::string memberName = member->GetName();
if (LowercaseString(memberName) == playerName)
return member;
}
}
return nullptr;
}
bool SetFocusHealTargetsAction::Execute(Event event)
{
if (!botAI->IsHeal(bot) && !botAI->HasStrategy("offheal", BOT_STATE_COMBAT))
{
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"focus_heal_not_healer",
"I'm not a healer or offhealer (please change my strats to heal or offheal)",
{});
botAI->TellMasterNoFacing(text);
return false;
}
std::string const param = LowercaseString(event.getParam());
if (param.empty())
{
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"focus_heal_provide_names",
"Please provide one or more player names",
{});
botAI->TellMasterNoFacing(text);
return false;
}
std::list<ObjectGuid> focusHealTargets =
AI_VALUE(std::list<ObjectGuid>, "focus heal targets");
// Query current focus targets
if (param.find('?') != std::string::npos)
{
if (focusHealTargets.empty())
{
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"focus_heal_no_targets",
"I don't have any focus heal targets",
{});
botAI->TellMasterNoFacing(text);
}
else
{
std::stringstream targetNames;
for (auto it = focusHealTargets.begin(); it != focusHealTargets.end(); ++it)
{
Unit* target = botAI->GetUnit(*it);
if (target)
{
if (it != focusHealTargets.begin())
targetNames << ", ";
targetNames << target->GetName();
}
}
std::map<std::string, std::string> placeholders;
placeholders["%targets"] = targetNames.str();
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"focus_heal_current_targets",
"My focus heal targets are %targets",
placeholders);
botAI->TellMasterNoFacing(text);
}
return true;
}
// Clear all targets
if (param == "none" || param == "unset" || param == "clear")
{
focusHealTargets.clear();
SET_AI_VALUE(std::list<ObjectGuid>, "focus heal targets", focusHealTargets);
botAI->ChangeStrategy("-focus heal targets", BOT_STATE_COMBAT);
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"focus_heal_cleared",
"Removed focus heal targets",
{});
botAI->TellMasterNoFacing(text);
return true;
}
// Parse multiple targets separated by commas
std::vector<std::string> targetNames;
if (param.find(',') != std::string::npos)
{
std::string targetName;
std::stringstream ss(param);
while (std::getline(ss, targetName, ','))
targetNames.push_back(targetName);
}
else
targetNames.push_back(param);
if (targetNames.empty())
{
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"focus_heal_provide_names",
"Please provide one or more player names",
{});
botAI->TellMasterNoFacing(text);
return false;
}
if (!bot->GetGroup())
{
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"focus_heal_not_in_group",
"I'm not in a group",
{});
botAI->TellMasterNoFacing(text);
return false;
}
for (std::string const& targetName : targetNames)
{
bool const add = targetName.find("+") != std::string::npos;
bool const remove = targetName.find("-") != std::string::npos;
if (!add && !remove)
{
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"focus_heal_add_remove_syntax",
"Please specify a + for add or - to remove a target",
{});
botAI->TellMasterNoFacing(text);
continue;
}
std::string const playerName = targetName.substr(1);
Player* target = FindGroupPlayerByName(bot, playerName);
if (!target)
{
std::map<std::string, std::string> placeholders;
placeholders["%player_name"] = playerName;
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"focus_heal_not_in_group_with",
"I'm not in a group with %player_name",
placeholders);
botAI->TellMasterNoFacing(text);
continue;
}
ObjectGuid const& targetGuid = target->GetGUID();
if (add)
{
if (std::find(focusHealTargets.begin(), focusHealTargets.end(), targetGuid) ==
focusHealTargets.end())
focusHealTargets.push_back(targetGuid);
std::map<std::string, std::string> placeholders;
placeholders["%player_name"] = playerName;
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"focus_heal_added",
"Added %player_name to focus heal targets",
placeholders);
botAI->TellMasterNoFacing(text);
}
else
{
focusHealTargets.remove(targetGuid);
std::map<std::string, std::string> placeholders;
placeholders["%player_name"] = playerName;
std::string text = PlayerbotTextMgr::instance().GetBotTextOrDefault(
"focus_heal_removed",
"Removed %player_name from focus heal targets",
placeholders);
botAI->TellMasterNoFacing(text);
}
}
SET_AI_VALUE(std::list<ObjectGuid>, "focus heal targets", focusHealTargets);
if (focusHealTargets.empty())
botAI->ChangeStrategy("-focus heal targets", BOT_STATE_COMBAT);
else
botAI->ChangeStrategy("+focus heal targets", BOT_STATE_COMBAT);
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_SETFOCUSHEALTARGETSACTION_H
#define _PLAYERBOT_SETFOCUSHEALTARGETSACTION_H
#include "Action.h"
class PlayerbotAI;
class SetFocusHealTargetsAction : public Action
{
public:
SetFocusHealTargetsAction(PlayerbotAI* botAI) : Action(botAI, "focus heal targets") {}
bool Execute(Event event) override;
};
#endif

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 "TellReputationAction.h"
#include <algorithm>
#include "Event.h" #include "Event.h"
#include "PlayerbotAI.h" #include "PlayerbotAI.h"
#include "ReputationMgr.h" #include "ReputationMgr.h"
bool TellReputationAction::Execute(Event /*event*/) #include "SharedDefines.h"
std::string TellReputationAction::BuildReputationLine(FactionEntry const* entry)
{ {
Player* master = GetMaster(); ReputationMgr& repMgr = bot->GetReputationMgr();
if (!master) ReputationRank rank = repMgr.GetRank(entry);
return false; int32 reputation = repMgr.GetReputation(entry->ID);
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);
std::ostringstream out; std::ostringstream out;
out << entry->name[0] << ": "; out << entry->name[0] << ": |cff";
out << "|cff";
ReputationRank rank = bot->GetReputationMgr().GetRank(entry);
switch (rank) switch (rank)
{ {
case REP_HATED: case REP_HATED:
@ -71,7 +60,65 @@ bool TellReputationAction::Execute(Event /*event*/)
base -= ReputationMgr::PointsInRank[i]; base -= ReputationMgr::PointsInRank[i];
out << " (" << (reputation - base) << "/" << ReputationMgr::PointsInRank[rank] << ")"; 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; return true;
} }

View File

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

View File

@ -61,7 +61,7 @@ bool SummonAction::Execute(Event /*event*/)
if (!master) if (!master)
return false; return false;
if (Pet* pet = bot->GetPet()) if (bot->GetPet())
botAI->PetFollow(); botAI->PetFollow();
if (master->GetSession()->GetSecurity() >= SEC_PLAYER) if (master->GetSession()->GetSecurity() >= SEC_PLAYER)

View File

@ -44,7 +44,12 @@ WorldPosition GetBestPoint(AiObjectContext* context, Player* bot, Unit* target,
float z = targetPosition.GetPositionZ() + 1.0f; float z = targetPosition.GetPositionZ() + 1.0f;
WorldPosition point(targetPosition.GetMapId(), x, y, z); WorldPosition point(targetPosition.GetMapId(), x, y, z);
point.setZ(point.getHeight());
float groundZ = bot->GetMapHeight(x, y, z);
if (groundZ == INVALID_HEIGHT || groundZ == VMAP_INVALID_HEIGHT_VALUE)
continue;
point.setZ(groundZ);
// Check line of sight to target // Check line of sight to target
if (!target->IsWithinLOS(point.GetPositionX(), point.GetPositionY(), if (!target->IsWithinLOS(point.GetPositionX(), point.GetPositionY(),
@ -88,8 +93,11 @@ bool WaitForAttackKeepSafeDistanceAction::Execute(Event /*event*/)
{ {
Unit* target = AI_VALUE(Unit*, "current target"); Unit* target = AI_VALUE(Unit*, "current target");
if (!target)
return false;
// If our target is moving towards a stationary unit, use that unit as anchor // If our target is moving towards a stationary unit, use that unit as anchor
if (target && !target->IsStopped()) if (!target->IsStopped())
{ {
ObjectGuid targetGuid = target->GetTarget(); ObjectGuid targetGuid = target->GetTarget();
if (targetGuid) if (targetGuid)
@ -100,7 +108,7 @@ bool WaitForAttackKeepSafeDistanceAction::Execute(Event /*event*/)
} }
} }
if (target && target->IsAlive()) if (target->IsAlive())
{ {
float safeDistance = std::max( float safeDistance = std::max(
target->GetCombatReach() + ATTACK_DISTANCE, target->GetCombatReach() + ATTACK_DISTANCE,

View File

@ -37,11 +37,13 @@
#include "LogLevelAction.h" #include "LogLevelAction.h"
#include "LootStrategyAction.h" #include "LootStrategyAction.h"
#include "LootRollAction.h" #include "LootRollAction.h"
#include "SetFocusHealTargetsAction.h"
#include "MailAction.h" #include "MailAction.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "NewRpgAction.h" #include "NewRpgAction.h"
#include "PassLeadershipToMasterAction.h" #include "PassLeadershipToMasterAction.h"
#include "PositionAction.h" #include "PositionAction.h"
#include "PullActions.h"
#include "QueryItemUsageAction.h" #include "QueryItemUsageAction.h"
#include "QueryQuestAction.h" #include "QueryQuestAction.h"
#include "RangeAction.h" #include "RangeAction.h"
@ -64,6 +66,7 @@
#include "TaxiAction.h" #include "TaxiAction.h"
#include "TeleportAction.h" #include "TeleportAction.h"
#include "TellCastFailedAction.h" #include "TellCastFailedAction.h"
#include "TellEmblemsAction.h"
#include "TellItemCountAction.h" #include "TellItemCountAction.h"
#include "TellLosAction.h" #include "TellLosAction.h"
#include "TellReputationAction.h" #include "TellReputationAction.h"
@ -118,6 +121,7 @@ public:
creators["teleport"] = &ChatActionContext::teleport; creators["teleport"] = &ChatActionContext::teleport;
creators["taxi"] = &ChatActionContext::taxi; creators["taxi"] = &ChatActionContext::taxi;
creators["repair"] = &ChatActionContext::repair; creators["repair"] = &ChatActionContext::repair;
creators["emblems"] = &ChatActionContext::emblems;
creators["use"] = &ChatActionContext::use; creators["use"] = &ChatActionContext::use;
creators["item count"] = &ChatActionContext::item_count; creators["item count"] = &ChatActionContext::item_count;
creators["equip"] = &ChatActionContext::equip; creators["equip"] = &ChatActionContext::equip;
@ -137,6 +141,8 @@ public:
creators["autogear"] = &ChatActionContext::autogear; creators["autogear"] = &ChatActionContext::autogear;
creators["equip upgrade"] = &ChatActionContext::equip_upgrade; creators["equip upgrade"] = &ChatActionContext::equip_upgrade;
creators["attack my target"] = &ChatActionContext::attack_my_target; creators["attack my target"] = &ChatActionContext::attack_my_target;
creators["pull my target"] = &ChatActionContext::pull_my_target;
creators["pull rti target"] = &ChatActionContext::pull_rti_target;
creators["chat"] = &ChatActionContext::chat; creators["chat"] = &ChatActionContext::chat;
creators["home"] = &ChatActionContext::home; creators["home"] = &ChatActionContext::home;
creators["destroy"] = &ChatActionContext::destroy; creators["destroy"] = &ChatActionContext::destroy;
@ -201,6 +207,7 @@ public:
creators["pet attack"] = &ChatActionContext::pet_attack; creators["pet attack"] = &ChatActionContext::pet_attack;
creators["roll"] = &ChatActionContext::roll_action; creators["roll"] = &ChatActionContext::roll_action;
creators["wait for attack time"] = &ChatActionContext::wait_for_attack_time; creators["wait for attack time"] = &ChatActionContext::wait_for_attack_time;
creators["focus heal targets"] = &ChatActionContext::focus_heal_targets;
} }
private: private:
@ -248,6 +255,8 @@ private:
static Action* home(PlayerbotAI* botAI) { return new SetHomeAction(botAI); } static Action* home(PlayerbotAI* botAI) { return new SetHomeAction(botAI); }
static Action* chat(PlayerbotAI* botAI) { return new ChangeChatAction(botAI); } static Action* chat(PlayerbotAI* botAI) { return new ChangeChatAction(botAI); }
static Action* attack_my_target(PlayerbotAI* botAI) { return new AttackMyTargetAction(botAI); } static Action* attack_my_target(PlayerbotAI* botAI) { return new AttackMyTargetAction(botAI); }
static Action* pull_my_target(PlayerbotAI* botAI) { return new PullMyTargetAction(botAI); }
static Action* pull_rti_target(PlayerbotAI* botAI) { return new PullRtiTargetAction(botAI); }
static Action* trainer(PlayerbotAI* botAI) { return new TrainerAction(botAI); } static Action* trainer(PlayerbotAI* botAI) { return new TrainerAction(botAI); }
static Action* maintenance(PlayerbotAI* botAI) { return new MaintenanceAction(botAI); } static Action* maintenance(PlayerbotAI* botAI) { return new MaintenanceAction(botAI); }
static Action* remove_glyph(PlayerbotAI* botAI) { return new RemoveGlyphAction(botAI); } static Action* remove_glyph(PlayerbotAI* botAI) { return new RemoveGlyphAction(botAI); }
@ -269,6 +278,7 @@ private:
static Action* item_count(PlayerbotAI* botAI) { return new TellItemCountAction(botAI); } static Action* item_count(PlayerbotAI* botAI) { return new TellItemCountAction(botAI); }
static Action* use(PlayerbotAI* botAI) { return new UseItemAction(botAI); } static Action* use(PlayerbotAI* botAI) { return new UseItemAction(botAI); }
static Action* repair(PlayerbotAI* botAI) { return new RepairAllAction(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* taxi(PlayerbotAI* botAI) { return new TaxiAction(botAI); }
static Action* teleport(PlayerbotAI* botAI) { return new TeleportAction(botAI); } static Action* teleport(PlayerbotAI* botAI) { return new TeleportAction(botAI); }
static Action* release(PlayerbotAI* botAI) { return new ReleaseSpiritAction(botAI); } static Action* release(PlayerbotAI* botAI) { return new ReleaseSpiritAction(botAI); }
@ -314,6 +324,7 @@ private:
static Action* pet_attack(PlayerbotAI* botAI) { return new PetsAction(botAI, "attack"); } static Action* pet_attack(PlayerbotAI* botAI) { return new PetsAction(botAI, "attack"); }
static Action* roll_action(PlayerbotAI* botAI) { return new RollAction(botAI); } static Action* roll_action(PlayerbotAI* botAI) { return new RollAction(botAI); }
static Action* wait_for_attack_time(PlayerbotAI* botAI) { return new SetWaitForAttackTimeAction(botAI); } static Action* wait_for_attack_time(PlayerbotAI* botAI) { return new SetWaitForAttackTimeAction(botAI); }
static Action* focus_heal_targets(PlayerbotAI* botAI) { return new SetFocusHealTargetsAction(botAI); }
}; };
#endif #endif

View File

@ -41,6 +41,7 @@ public:
creators["teleport"] = &ChatTriggerContext::teleport; creators["teleport"] = &ChatTriggerContext::teleport;
creators["taxi"] = &ChatTriggerContext::taxi; creators["taxi"] = &ChatTriggerContext::taxi;
creators["repair"] = &ChatTriggerContext::repair; creators["repair"] = &ChatTriggerContext::repair;
creators["emblems"] = &ChatTriggerContext::emblems;
creators["u"] = &ChatTriggerContext::use; creators["u"] = &ChatTriggerContext::use;
creators["use"] = &ChatTriggerContext::use; creators["use"] = &ChatTriggerContext::use;
creators["c"] = &ChatTriggerContext::item_count; creators["c"] = &ChatTriggerContext::item_count;
@ -66,6 +67,9 @@ public:
creators["autogear"] = &ChatTriggerContext::autogear; creators["autogear"] = &ChatTriggerContext::autogear;
creators["equip upgrade"] = &ChatTriggerContext::equip_upgrade; creators["equip upgrade"] = &ChatTriggerContext::equip_upgrade;
creators["attack"] = &ChatTriggerContext::attack; creators["attack"] = &ChatTriggerContext::attack;
creators["pull"] = &ChatTriggerContext::pull;
creators["pull back"] = &ChatTriggerContext::pull_back;
creators["pull rti"] = &ChatTriggerContext::pull_rti;
creators["chat"] = &ChatTriggerContext::chat; creators["chat"] = &ChatTriggerContext::chat;
creators["accept"] = &ChatTriggerContext::accept; creators["accept"] = &ChatTriggerContext::accept;
creators["home"] = &ChatTriggerContext::home; creators["home"] = &ChatTriggerContext::home;
@ -146,6 +150,7 @@ public:
creators["pet attack"] = &ChatTriggerContext::pet_attack; creators["pet attack"] = &ChatTriggerContext::pet_attack;
creators["roll"] = &ChatTriggerContext::roll_action; creators["roll"] = &ChatTriggerContext::roll_action;
creators["wait for attack time"] = &ChatTriggerContext::wait_for_attack_time; creators["wait for attack time"] = &ChatTriggerContext::wait_for_attack_time;
creators["focus heal"] = &ChatTriggerContext::focus_heal;
} }
private: private:
@ -208,6 +213,9 @@ private:
static Trigger* accept(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "accept"); } static Trigger* accept(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "accept"); }
static Trigger* chat(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "chat"); } static Trigger* chat(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "chat"); }
static Trigger* attack(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "attack"); } static Trigger* attack(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "attack"); }
static Trigger* pull(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "pull"); }
static Trigger* pull_back(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "pull back"); }
static Trigger* pull_rti(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "pull rti"); }
static Trigger* trainer(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "trainer"); } static Trigger* trainer(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "trainer"); }
static Trigger* maintenance(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "maintenance"); } static Trigger* maintenance(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "maintenance"); }
static Trigger* remove_glyph(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "remove glyph"); } static Trigger* remove_glyph(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "remove glyph"); }
@ -228,6 +236,7 @@ private:
static Trigger* item_count(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "c"); } static Trigger* item_count(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "c"); }
static Trigger* use(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "use"); } static Trigger* use(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "use"); }
static Trigger* repair(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "repair"); } 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* taxi(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "taxi"); }
static Trigger* teleport(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "teleport"); } static Trigger* teleport(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "teleport"); }
static Trigger* q(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "q"); } static Trigger* q(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "q"); }
@ -271,6 +280,7 @@ private:
static Trigger* pet_attack(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "pet attack"); } static Trigger* pet_attack(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "pet attack"); }
static Trigger* roll_action(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "roll"); } static Trigger* roll_action(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "roll"); }
static Trigger* wait_for_attack_time(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "wait for attack time"); } static Trigger* wait_for_attack_time(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "wait for attack time"); }
static Trigger* focus_heal(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "focus heal"); }
}; };
#endif #endif

View File

@ -11,7 +11,7 @@ public:
ChatCommandActionNodeFactoryInternal() { creators["tank attack chat shortcut"] = &tank_attack_chat_shortcut; } ChatCommandActionNodeFactoryInternal() { creators["tank attack chat shortcut"] = &tank_attack_chat_shortcut; }
private: private:
static ActionNode* tank_attack_chat_shortcut(PlayerbotAI* botAI) static ActionNode* tank_attack_chat_shortcut(PlayerbotAI* /*botAI*/)
{ {
return new ActionNode("tank attack chat shortcut", return new ActionNode("tank attack chat shortcut",
/*P*/ {}, /*P*/ {},
@ -81,6 +81,12 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
new TriggerNode("attackers", { NextAction("tell attackers", relevance) })); new TriggerNode("attackers", { NextAction("tell attackers", relevance) }));
triggers.push_back( triggers.push_back(
new TriggerNode("target", { NextAction("tell target", relevance) })); new TriggerNode("target", { NextAction("tell target", relevance) }));
triggers.push_back(
new TriggerNode("pull", { NextAction("pull my target", relevance) }));
triggers.push_back(
new TriggerNode("pull back", { NextAction("pull my target", relevance) }));
triggers.push_back(
new TriggerNode("pull rti", { NextAction("pull rti target", relevance) }));
triggers.push_back( triggers.push_back(
new TriggerNode("ready", { NextAction("ready check", relevance) })); new TriggerNode("ready", { NextAction("ready check", relevance) }));
triggers.push_back( triggers.push_back(
@ -107,6 +113,8 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
triggers.push_back(new TriggerNode("pet", { NextAction("pet", relevance) })); triggers.push_back(new TriggerNode("pet", { NextAction("pet", relevance) }));
triggers.push_back(new TriggerNode("pet attack", { NextAction("pet attack", relevance) })); 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("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) ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI)
@ -131,6 +139,7 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas
supported.push_back("teleport"); supported.push_back("teleport");
supported.push_back("taxi"); supported.push_back("taxi");
supported.push_back("repair"); supported.push_back("repair");
supported.push_back("emblems");
supported.push_back("talents"); supported.push_back("talents");
supported.push_back("spells"); supported.push_back("spells");
supported.push_back("co"); supported.push_back("co");
@ -195,9 +204,10 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas
supported.push_back("unlock items"); supported.push_back("unlock items");
supported.push_back("unlock traded item"); supported.push_back("unlock traded item");
supported.push_back("tame"); supported.push_back("tame");
supported.push_back("glyphs"); // Added for custom Glyphs supported.push_back("glyphs");
supported.push_back("glyph equip"); // Added for custom Glyphs supported.push_back("glyph equip");
supported.push_back("pet"); supported.push_back("pet");
supported.push_back("pet attack"); supported.push_back("pet attack");
supported.push_back("wait for attack time"); supported.push_back("wait for attack time");
supported.push_back("focus heal");
} }

View File

@ -64,11 +64,11 @@ std::vector<NextAction> AvoidAoeStrategy::getDefaultActions()
}; };
} }
void AvoidAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void AvoidAoeStrategy::InitTriggers(std::vector<TriggerNode*>& /*triggers*/)
{ {
} }
void AvoidAoeStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers) void AvoidAoeStrategy::InitMultipliers(std::vector<Multiplier*>& /*multipliers*/)
{ {
} }
@ -81,7 +81,7 @@ std::vector<NextAction> TankFaceStrategy::getDefaultActions()
}; };
} }
void TankFaceStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void TankFaceStrategy::InitTriggers(std::vector<TriggerNode*>& /*triggers*/)
{ {
} }

View File

@ -17,6 +17,6 @@ void DuelStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
DuelStrategy::DuelStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI) {} DuelStrategy::DuelStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI) {}
void StartDuelStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) {} void StartDuelStrategy::InitTriggers(std::vector<TriggerNode*>& /*triggers*/) {}
StartDuelStrategy::StartDuelStrategy(PlayerbotAI* botAI) : Strategy(botAI) {} StartDuelStrategy::StartDuelStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}

View File

@ -0,0 +1,20 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_FOCUSTARGETSTRATEGY_H
#define _PLAYERBOT_FOCUSTARGETSTRATEGY_H
#include "Strategy.h"
class PlayerbotAI;
class FocusHealTargetsStrategy : public Strategy
{
public:
FocusHealTargetsStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
std::string const getName() override { return "focus heal targets"; }
};
#endif

View File

@ -12,6 +12,6 @@ std::vector<NextAction> FollowMasterStrategy::getDefaultActions()
}; };
} }
void FollowMasterStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void FollowMasterStrategy::InitTriggers(std::vector<TriggerNode*>& /*triggers*/)
{ {
} }

View File

@ -12,4 +12,4 @@ std::vector<NextAction> GuardStrategy::getDefaultActions()
}; };
} }
void GuardStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) {} void GuardStrategy::InitTriggers(std::vector<TriggerNode*>& /*triggers*/) {}

View File

@ -13,7 +13,7 @@ void MaintenanceStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
triggers.push_back( triggers.push_back(
new TriggerNode( new TriggerNode(
"random", "seldom",
{ {
NextAction("clean quest log", 6.0f) NextAction("clean quest log", 6.0f)
} }

View File

@ -17,7 +17,7 @@ void CollisionStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
new TriggerNode("collision", { NextAction("move out of collision", 2.0f) })); new TriggerNode("collision", { NextAction("move out of collision", 2.0f) }));
} }
void MountStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void MountStrategy::InitTriggers(std::vector<TriggerNode*>& /*triggers*/)
{ {
} }

View File

@ -5,8 +5,188 @@
#include "PullStrategy.h" #include "PullStrategy.h"
#include "AiObjectContext.h"
#include "PassiveMultiplier.h" #include "PassiveMultiplier.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "SpellMgr.h"
class PullStrategyActionNodeFactory : public NamedObjectFactory<ActionNode>
{
public:
PullStrategyActionNodeFactory()
{
creators["pull start"] = &pull_start;
}
private:
static ActionNode* pull_start(PlayerbotAI* /*botAI*/)
{
return new ActionNode("pull start", {}, {}, { NextAction("pull action", ACTION_NORMAL) });
}
};
PullStrategy::PullStrategy(PlayerbotAI* botAI, std::string const action, std::string const preAction)
: Strategy(botAI), action(action), preAction(preAction)
{
actionNodeFactories.Add(new PullStrategyActionNodeFactory());
}
PullStrategy* PullStrategy::Get(PlayerbotAI* botAI)
{
if (!botAI)
return nullptr;
if (PullStrategy* strategy = dynamic_cast<PullStrategy*>(botAI->GetStrategy("pull", BOT_STATE_NON_COMBAT)))
{
if (strategy->IsPullPendingToStart() || strategy->HasPullStarted() || strategy->HasTarget())
return strategy;
}
return dynamic_cast<PullStrategy*>(botAI->GetStrategy("pull", BOT_STATE_COMBAT));
}
Unit* PullStrategy::GetTarget() const
{
ObjectGuid const guid = botAI->GetAiObjectContext()->GetValue<ObjectGuid>("pull strategy target")->Get();
if (guid.IsEmpty())
return nullptr;
Unit* target = botAI->GetUnit(guid);
Player* bot = botAI->GetBot();
if (!bot || !target || !target->IsAlive() || !target->IsInWorld() ||
target->GetMapId() != bot->GetMapId())
return nullptr;
return target;
}
bool PullStrategy::HasTarget() const { return GetTarget() != nullptr; }
void PullStrategy::SetTarget(Unit* target)
{
botAI->GetAiObjectContext()->GetValue<ObjectGuid>("pull strategy target")->Set(target ? target->GetGUID() : ObjectGuid::Empty);
}
std::string PullStrategy::GetPullActionName() const
{
return action;
}
std::string PullStrategy::GetSpellName() const
{
Player* bot = botAI->GetBot();
std::string spellName = GetPullActionName();
if (!bot || spellName != "shoot")
return spellName;
Item* equippedWeapon = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED);
if (!equippedWeapon)
return spellName;
ItemTemplate const* itemTemplate = equippedWeapon->GetTemplate();
if (!itemTemplate)
return spellName;
switch (itemTemplate->SubClass)
{
case ITEM_SUBCLASS_WEAPON_THROWN:
return "throw";
case ITEM_SUBCLASS_WEAPON_GUN:
return "shoot gun";
case ITEM_SUBCLASS_WEAPON_BOW:
return "shoot bow";
case ITEM_SUBCLASS_WEAPON_CROSSBOW:
return "shoot crossbow";
default:
return spellName;
}
}
float PullStrategy::GetRange() const
{
Player* bot = botAI->GetBot();
std::string const spellName = GetSpellName();
if (bot && !spellName.empty())
{
uint32 const spellId = botAI->GetAiObjectContext()->GetValue<uint32>("spell id", spellName)->Get();
if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId))
return bot->GetSpellMaxRangeForTarget(GetTarget(), spellInfo) - CONTACT_DISTANCE;
}
return (action == "shoot" ? botAI->GetRange("shoot") : botAI->GetRange("spell")) - CONTACT_DISTANCE;
}
std::string PullStrategy::GetPreActionName() const
{
return preAction;
}
bool PullStrategy::CanDoPullAction(Unit* target)
{
Player* bot = botAI->GetBot();
if (!bot || !target)
return false;
if (!target->IsInWorld() || target->GetMapId() != bot->GetMapId())
return false;
if (bot->getClass() != CLASS_DRUID && bot->getClass() != CLASS_PALADIN &&
GetPullActionName() == "shoot" && !bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED))
{
return false;
}
std::string const spellName = GetSpellName();
if (spellName.empty())
return false;
return true;
}
void PullStrategy::RequestPull(Unit* target, bool resetTime)
{
SetTarget(target);
pendingToStart = true;
if (resetTime)
pullStartTime = time(nullptr);
}
void PullStrategy::OnPullStarted() { pendingToStart = false; }
void PullStrategy::OnPullEnded()
{
pullStartTime = 0;
pendingToStart = false;
SetTarget(nullptr);
}
PullMultiplier::PullMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "pull") {}
float PullMultiplier::GetValue(Action* action)
{
PullStrategy const* strategy = PullStrategy::Get(botAI);
if (!strategy || !strategy->HasTarget() || !action)
return 1.0f;
if (!strategy->IsPullPendingToStart() && !strategy->HasPullStarted())
return 1.0f;
std::string const actionName = action->getName();
if (actionName == "pull my target" ||
actionName == "pull rti target" ||
actionName == "reach pull" ||
actionName == "pull start" ||
actionName == "pull action" ||
actionName == "return to pull position" ||
actionName == "pull end" ||
actionName == "follow" ||
actionName == "set facing")
return 1.0f;
return 0.0f;
}
class MagePullMultiplier : public PassiveMultiplier class MagePullMultiplier : public PassiveMultiplier
{ {
@ -24,8 +204,16 @@ float MagePullMultiplier::GetValue(Action* action)
if (!action) if (!action)
return 1.0f; return 1.0f;
PullStrategy const* strategy = PullStrategy::Get(botAI);
if (!strategy || !strategy->HasTarget())
return 1.0f;
std::string const name = action->getName(); std::string const name = action->getName();
if (actionName == name || name == "reach spell" || name == "change strategy") if (actionName == name || name == "pull action" || name == "pull start" || name == "pull end" ||
name == "pull my target" || name == "pull rti target" ||
name == "reach spell" || name == "reach pull" ||
name == "return to pull position" || name == "follow" ||
name == "set facing" || name == "change strategy")
return 1.0f; return 1.0f;
return PassiveMultiplier::GetValue(action); return PassiveMultiplier::GetValue(action);
@ -34,18 +222,32 @@ float MagePullMultiplier::GetValue(Action* action)
std::vector<NextAction> PullStrategy::getDefaultActions() std::vector<NextAction> PullStrategy::getDefaultActions()
{ {
return { return {
NextAction(action, 105.0f), NextAction("pull action", 105.0f),
NextAction("follow", 104.0f),
NextAction("end pull", 103.0f),
}; };
} }
void PullStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) { CombatStrategy::InitTriggers(triggers); } void PullStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
triggers.push_back(new TriggerNode(
"pull start",
{
NextAction("pull start", 106.0f),
NextAction("pull action", ACTION_MOVE)
}
));
triggers.push_back(new TriggerNode(
"pull end",
{
NextAction("pull end", 107.0f)
}
));
}
void PullStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers) void PullStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{ {
multipliers.push_back(new PullMultiplier(botAI));
multipliers.push_back(new MagePullMultiplier(botAI, action)); multipliers.push_back(new MagePullMultiplier(botAI, action));
CombatStrategy::InitMultipliers(multipliers);
} }
void PossibleAddsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void PossibleAddsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
@ -61,3 +263,15 @@ void PossibleAddsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
) )
); );
} }
void PullBackStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
Strategy::InitTriggers(triggers);
triggers.push_back(new TriggerNode(
"return to pull position",
{
NextAction("return to pull position", ACTION_MOVE + 5.0f)
}
));
}

View File

@ -6,22 +6,65 @@
#ifndef _PLAYERBOT_PULLSTRATEGY_H #ifndef _PLAYERBOT_PULLSTRATEGY_H
#define _PLAYERBOT_PULLSTRATEGY_H #define _PLAYERBOT_PULLSTRATEGY_H
#include "CombatStrategy.h" #include "Strategy.h"
class Action;
class Multiplier;
class Unit;
class PlayerbotAI; class PlayerbotAI;
class PullStrategy : public CombatStrategy class PullStrategy : public Strategy
{ {
public: public:
PullStrategy(PlayerbotAI* botAI, std::string const action) : CombatStrategy(botAI), action(action) {} PullStrategy(PlayerbotAI* botAI, std::string const action, std::string const preAction = "");
void InitTriggers(std::vector<TriggerNode*>& triggers) override; void InitTriggers(std::vector<TriggerNode*>& triggers) override;
void InitMultipliers(std::vector<Multiplier*>& multipliers) override; void InitMultipliers(std::vector<Multiplier*>& multipliers) override;
std::string const getName() override { return "pull"; } std::string const getName() override { return "pull"; }
std::vector<NextAction> getDefaultActions() override; std::vector<NextAction> getDefaultActions() override;
uint32 GetType() const override { return STRATEGY_TYPE_COMBAT | STRATEGY_TYPE_NONCOMBAT; }
static PullStrategy* Get(PlayerbotAI* botAI);
static uint8 GetMaxPullTime() { return 15; }
time_t GetPullStartTime() const { return pullStartTime; }
bool IsPullPendingToStart() const { return pendingToStart; }
bool HasPullStarted() const { return pullStartTime > 0; }
bool CanDoPullAction(Unit* target);
Unit* GetTarget() const;
bool HasTarget() const;
virtual std::string GetPullActionName() const;
std::string GetSpellName() const;
float GetRange() const;
virtual std::string GetPreActionName() const;
void RequestPull(Unit* target, bool resetTime = true);
void OnPullStarted();
void OnPullEnded();
ReactStates GetPetReactState() const { return petReactState; }
void SetPetReactState(ReactStates reactState) { petReactState = reactState; }
private:
void SetTarget(Unit* target);
private: private:
std::string const action; std::string const action;
std::string const preAction;
bool pendingToStart = false;
time_t pullStartTime = 0;
ReactStates petReactState = REACT_DEFENSIVE;
};
class PullMultiplier : public Multiplier
{
public:
PullMultiplier(PlayerbotAI* botAI);
float GetValue(Action* action) override;
}; };
class PossibleAddsStrategy : public Strategy class PossibleAddsStrategy : public Strategy
@ -33,4 +76,13 @@ public:
std::string const getName() override { return "adds"; } std::string const getName() override { return "adds"; }
}; };
class PullBackStrategy : public Strategy
{
public:
PullBackStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
void InitTriggers(std::vector<TriggerNode*>& triggers) override;
std::string const getName() override { return "pull back"; }
};
#endif #endif

View File

@ -7,4 +7,4 @@
RTSCStrategy::RTSCStrategy(PlayerbotAI* botAI) : Strategy(botAI) {} RTSCStrategy::RTSCStrategy(PlayerbotAI* botAI) : Strategy(botAI) {}
void RTSCStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) {} void RTSCStrategy::InitTriggers(std::vector<TriggerNode*>& /*triggers*/) {}

View File

@ -11,7 +11,7 @@ public:
RacialsStrategyActionNodeFactory() { creators["lifeblood"] = &lifeblood; } RacialsStrategyActionNodeFactory() { creators["lifeblood"] = &lifeblood; }
private: private:
static ActionNode* lifeblood(PlayerbotAI* botAI) static ActionNode* lifeblood(PlayerbotAI* /*botAI*/)
{ {
return new ActionNode("lifeblood", return new ActionNode("lifeblood",
/*P*/ {}, /*P*/ {},

View File

@ -11,7 +11,7 @@ public:
UsePotionsStrategyActionNodeFactory() { creators["healthstone"] = &healthstone; } UsePotionsStrategyActionNodeFactory() { creators["healthstone"] = &healthstone; }
private: private:
static ActionNode* healthstone(PlayerbotAI* botAI) static ActionNode* healthstone(PlayerbotAI* /*botAI*/)
{ {
return new ActionNode("healthstone", return new ActionNode("healthstone",
/*P*/ {}, /*P*/ {},

View File

@ -82,6 +82,7 @@ float WaitForAttackMultiplier::GetValue(Action* action)
actionName != "set facing" && actionName != "set facing" &&
actionName != "pull my target" && actionName != "pull my target" &&
actionName != "pull rti target" && actionName != "pull rti target" &&
actionName != "reach pull" &&
actionName != "pull start" && actionName != "pull start" &&
actionName != "pull action" && actionName != "pull action" &&
actionName != "pull end") actionName != "pull end")

View File

@ -19,6 +19,7 @@
#include "DuelStrategy.h" #include "DuelStrategy.h"
#include "EmoteStrategy.h" #include "EmoteStrategy.h"
#include "FleeStrategy.h" #include "FleeStrategy.h"
#include "FocusTargetStrategy.h"
#include "FollowMasterStrategy.h" #include "FollowMasterStrategy.h"
#include "GrindingStrategy.h" #include "GrindingStrategy.h"
#include "GroupStrategy.h" #include "GroupStrategy.h"
@ -94,6 +95,7 @@ public:
creators["sit"] = &StrategyContext::sit; creators["sit"] = &StrategyContext::sit;
creators["mark rti"] = &StrategyContext::mark_rti; creators["mark rti"] = &StrategyContext::mark_rti;
creators["adds"] = &StrategyContext::possible_adds; creators["adds"] = &StrategyContext::possible_adds;
creators["pull back"] = &StrategyContext::pull_back;
creators["close"] = &StrategyContext::close; creators["close"] = &StrategyContext::close;
creators["ranged"] = &StrategyContext::ranged; creators["ranged"] = &StrategyContext::ranged;
creators["behind"] = &StrategyContext::behind; creators["behind"] = &StrategyContext::behind;
@ -126,6 +128,7 @@ public:
creators["use bobber"] = &StrategyContext::bobber_strategy; creators["use bobber"] = &StrategyContext::bobber_strategy;
creators["master fishing"] = &StrategyContext::master_fishing; creators["master fishing"] = &StrategyContext::master_fishing;
creators["wait for attack"] = &StrategyContext::wait_for_attack; creators["wait for attack"] = &StrategyContext::wait_for_attack;
creators["focus heal targets"] = &StrategyContext::focus_heal_targets;
} }
private: private:
@ -169,6 +172,7 @@ private:
static Strategy* map_full(PlayerbotAI* botAI) { return new MapFullStrategy(botAI); } static Strategy* map_full(PlayerbotAI* botAI) { return new MapFullStrategy(botAI); }
static Strategy* sit(PlayerbotAI* botAI) { return new SitStrategy(botAI); } static Strategy* sit(PlayerbotAI* botAI) { return new SitStrategy(botAI); }
static Strategy* possible_adds(PlayerbotAI* botAI) { return new PossibleAddsStrategy(botAI); } static Strategy* possible_adds(PlayerbotAI* botAI) { return new PossibleAddsStrategy(botAI); }
static Strategy* pull_back(PlayerbotAI* botAI) { return new PullBackStrategy(botAI); }
static Strategy* mount(PlayerbotAI* botAI) { return new MountStrategy(botAI); } static Strategy* mount(PlayerbotAI* botAI) { return new MountStrategy(botAI); }
static Strategy* bg(PlayerbotAI* botAI) { return new BGStrategy(botAI); } static Strategy* bg(PlayerbotAI* botAI) { return new BGStrategy(botAI); }
static Strategy* battleground(PlayerbotAI* botAI) { return new BattlegroundStrategy(botAI); } static Strategy* battleground(PlayerbotAI* botAI) { return new BattlegroundStrategy(botAI); }
@ -198,6 +202,7 @@ private:
static Strategy* bobber_strategy(PlayerbotAI* botAI) { return new UseBobberStrategy(botAI); } static Strategy* bobber_strategy(PlayerbotAI* botAI) { return new UseBobberStrategy(botAI); }
static Strategy* master_fishing(PlayerbotAI* botAI) { return new MasterFishingStrategy(botAI); } static Strategy* master_fishing(PlayerbotAI* botAI) { return new MasterFishingStrategy(botAI); }
static Strategy* wait_for_attack(PlayerbotAI* botAI) { return new WaitForAttackStrategy(botAI); } static Strategy* wait_for_attack(PlayerbotAI* botAI) { return new WaitForAttackStrategy(botAI); }
static Strategy* focus_heal_targets(PlayerbotAI* botAI) { return new FocusHealTargetsStrategy(botAI); }
}; };
class MovementStrategyContext : public NamedObjectContext<Strategy> class MovementStrategyContext : public NamedObjectContext<Strategy>

View File

@ -481,6 +481,13 @@ bool FearCharmSleepTrigger::IsActive()
bot->HasAuraWithMechanic(1 << MECHANIC_SLEEP); bot->HasAuraWithMechanic(1 << MECHANIC_SLEEP);
} }
bool FearSleepSapTrigger::IsActive()
{
return bot->HasAuraType(SPELL_AURA_MOD_FEAR) ||
bot->HasAuraWithMechanic(1 << MECHANIC_SLEEP) ||
bot->HasAuraWithMechanic(1 << MECHANIC_SAPPED);
}
bool HasAuraStackTrigger::IsActive() bool HasAuraStackTrigger::IsActive()
{ {
Aura* aura = botAI->GetAura(getName(), GetTarget(), false, true, stack); Aura* aura = botAI->GetAura(getName(), GetTarget(), false, true, stack);

View File

@ -762,6 +762,14 @@ public:
bool IsActive() override; bool IsActive() override;
}; };
class FearSleepSapTrigger : public Trigger
{
public:
FearSleepSapTrigger(PlayerbotAI* botAI) : Trigger(botAI, "fear sleep sap", 1) {}
bool IsActive() override;
};
class IsSwimmingTrigger : public Trigger class IsSwimmingTrigger : public Trigger
{ {
public: public:

View File

@ -0,0 +1,62 @@
/*
* 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 "PullTriggers.h"
#include "PositionValue.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "PullStrategy.h"
bool PullStartTrigger::IsActive()
{
PullStrategy const* strategy = PullStrategy::Get(botAI);
return strategy && strategy->IsPullPendingToStart();
}
bool PullEndTrigger::IsActive()
{
PullStrategy const* strategy = PullStrategy::Get(botAI);
if (!strategy || !strategy->HasPullStarted())
return false;
Unit* target = strategy->GetTarget();
if (!target || !target->IsInWorld() || !target->IsAlive())
return true;
time_t const secondsSincePullStarted = time(nullptr) - strategy->GetPullStartTime();
if (secondsSincePullStarted >= PullStrategy::GetMaxPullTime())
return true;
float distanceToPullTarget = bot->GetDistance(target);
if (distanceToPullTarget > ATTACK_DISTANCE && !target->IsNonMeleeSpellCast(false, false, true) &&
(!botAI->IsRanged(bot) || distanceToPullTarget > botAI->GetRange("spell")))
return false;
if (!botAI->HasStrategy("pull back", BOT_STATE_COMBAT))
return true;
PositionInfo pullPosition = AI_VALUE(PositionMap&, "position")["pull"];
if (!pullPosition.isSet() || pullPosition.mapId != bot->GetMapId())
return true;
return bot->GetDistance(pullPosition.x, pullPosition.y, pullPosition.z) <= botAI->GetRange("follow");
}
bool ReturnToPullPositionTrigger::IsActive()
{
PullStrategy const* strategy = PullStrategy::Get(botAI);
Unit* target = strategy ? strategy->GetTarget() : nullptr;
if (!strategy || !strategy->HasPullStarted() || !target || !target->IsInCombat() ||
!botAI->HasStrategy("pull back", BOT_STATE_COMBAT))
return false;
PositionInfo pullPosition = AI_VALUE(PositionMap&, "position")["pull"];
return pullPosition.isSet() && pullPosition.mapId == bot->GetMapId() &&
bot->GetDistance(pullPosition.x, pullPosition.y, pullPosition.z) > sPlayerbotAIConfig.followDistance;
}

View File

@ -0,0 +1,35 @@
/*
* 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_PULLTRIGGERS_H
#define _PLAYERBOT_PULLTRIGGERS_H
#include "Trigger.h"
class PullStartTrigger : public Trigger
{
public:
PullStartTrigger(PlayerbotAI* botAI, std::string const name = "pull start") : Trigger(botAI, name) {}
bool IsActive() override;
};
class PullEndTrigger : public Trigger
{
public:
PullEndTrigger(PlayerbotAI* botAI, std::string const name = "pull end") : Trigger(botAI, name) {}
bool IsActive() override;
};
class ReturnToPullPositionTrigger : public Trigger
{
public:
ReturnToPullPositionTrigger(PlayerbotAI* botAI) : Trigger(botAI, "return to pull position") {}
bool IsActive() override;
};
#endif

View File

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

View File

@ -16,5 +16,5 @@ bool NoRtiTrigger::IsActive()
return false; return false;
Unit* target = AI_VALUE(Unit*, "rti target"); Unit* target = AI_VALUE(Unit*, "rti target");
return target != nullptr; return target == nullptr;
} }

View File

@ -8,7 +8,7 @@
#include "CellImpl.h" #include "CellImpl.h"
#include "PathGenerator.h" #include "PathGenerator.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "MMapFactory.h" #include "MapCollisionData.h"
bool MoveStuckTrigger::IsActive() bool MoveStuckTrigger::IsActive()
{ {
@ -89,8 +89,7 @@ bool MoveLongStuckTrigger::IsActive()
return true; return true;
} }
if (cell.GridX() > 0 && cell.GridY() > 0 && if (bot->GetMap()->IsGridCreated(GridCoord(cell.GridX(), cell.GridY())))
!MMAP::MMapFactory::createOrGetMMapMgr()->loadMap(botPos.GetMapId(), cell.GridX(), cell.GridY()))
{ {
// LOG_INFO("playerbots", "Bot {} {}:{} <{}> was in unloaded grid {},{} on map {}", // LOG_INFO("playerbots", "Bot {} {}:{} <{}> was in unloaded grid {},{} on map {}",
// bot->GetGUID().ToString().c_str(), bot->GetTeamId() == TEAM_ALLIANCE ? "A" : "H", bot->GetLevel(), // bot->GetGUID().ToString().c_str(), bot->GetTeamId() == TEAM_ALLIANCE ? "A" : "H", bot->GetLevel(),

View File

@ -16,6 +16,7 @@
#include "NewRpgStrategy.h" #include "NewRpgStrategy.h"
#include "NewRpgTriggers.h" #include "NewRpgTriggers.h"
#include "PvpTriggers.h" #include "PvpTriggers.h"
#include "PullTriggers.h"
#include "RpgTriggers.h" #include "RpgTriggers.h"
#include "RtiTriggers.h" #include "RtiTriggers.h"
#include "StuckTriggers.h" #include "StuckTriggers.h"
@ -62,6 +63,7 @@ public:
creators["generic boost"] = &TriggerContext::generic_boost; creators["generic boost"] = &TriggerContext::generic_boost;
creators["loss of control"] = &TriggerContext::loss_of_control; creators["loss of control"] = &TriggerContext::loss_of_control;
creators["fear charm sleep"] = &TriggerContext::fear_charm_sleep; creators["fear charm sleep"] = &TriggerContext::fear_charm_sleep;
creators["fear sleep sap"] = &TriggerContext::fear_sleep_sap;
creators["protect party member"] = &TriggerContext::protect_party_member; creators["protect party member"] = &TriggerContext::protect_party_member;
@ -128,6 +130,9 @@ public:
creators["has attackers"] = &TriggerContext::has_attackers; creators["has attackers"] = &TriggerContext::has_attackers;
creators["no possible targets"] = &TriggerContext::no_possible_targets; creators["no possible targets"] = &TriggerContext::no_possible_targets;
creators["possible adds"] = &TriggerContext::possible_adds; creators["possible adds"] = &TriggerContext::possible_adds;
creators["pull start"] = &TriggerContext::pull_start;
creators["pull end"] = &TriggerContext::pull_end;
creators["return to pull position"] = &TriggerContext::return_to_pull_position;
creators["no drink"] = &TriggerContext::no_drink; creators["no drink"] = &TriggerContext::no_drink;
creators["no food"] = &TriggerContext::no_food; creators["no food"] = &TriggerContext::no_food;
@ -229,6 +234,7 @@ public:
creators["wander npc status"] = &TriggerContext::wander_npc_status; creators["wander npc status"] = &TriggerContext::wander_npc_status;
creators["do quest status"] = &TriggerContext::do_quest_status; creators["do quest status"] = &TriggerContext::do_quest_status;
creators["travel flight status"] = &TriggerContext::travel_flight_status; creators["travel flight status"] = &TriggerContext::travel_flight_status;
creators["outdoor pvp status"] = &TriggerContext::outdoor_pvp_status;
creators["can self resurrect"] = &TriggerContext::can_self_resurrect; creators["can self resurrect"] = &TriggerContext::can_self_resurrect;
creators["can fish"] = &TriggerContext::can_fish; creators["can fish"] = &TriggerContext::can_fish;
creators["can use fishing bobber"] = &TriggerContext::can_use_fishing_bobber; creators["can use fishing bobber"] = &TriggerContext::can_use_fishing_bobber;
@ -278,6 +284,9 @@ private:
static Trigger* swimming(PlayerbotAI* botAI) { return new IsSwimmingTrigger(botAI); } static Trigger* swimming(PlayerbotAI* botAI) { return new IsSwimmingTrigger(botAI); }
static Trigger* no_possible_targets(PlayerbotAI* botAI) { return new NoPossibleTargetsTrigger(botAI); } static Trigger* no_possible_targets(PlayerbotAI* botAI) { return new NoPossibleTargetsTrigger(botAI); }
static Trigger* possible_adds(PlayerbotAI* botAI) { return new PossibleAddsTrigger(botAI); } static Trigger* possible_adds(PlayerbotAI* botAI) { return new PossibleAddsTrigger(botAI); }
static Trigger* pull_start(PlayerbotAI* botAI) { return new PullStartTrigger(botAI); }
static Trigger* pull_end(PlayerbotAI* botAI) { return new PullEndTrigger(botAI); }
static Trigger* return_to_pull_position(PlayerbotAI* botAI) { return new ReturnToPullPositionTrigger(botAI); }
static Trigger* can_loot(PlayerbotAI* botAI) { return new CanLootTrigger(botAI); } static Trigger* can_loot(PlayerbotAI* botAI) { return new CanLootTrigger(botAI); }
static Trigger* far_from_loot_target(PlayerbotAI* botAI) { return new FarFromCurrentLootTrigger(botAI); } static Trigger* far_from_loot_target(PlayerbotAI* botAI) { return new FarFromCurrentLootTrigger(botAI); }
static Trigger* far_from_master(PlayerbotAI* botAI) { return new FarFromMasterTrigger(botAI); } static Trigger* far_from_master(PlayerbotAI* botAI) { return new FarFromMasterTrigger(botAI); }
@ -369,6 +378,7 @@ private:
static Trigger* generic_boost(PlayerbotAI* botAI) { return new GenericBoostTrigger(botAI); } static Trigger* generic_boost(PlayerbotAI* botAI) { return new GenericBoostTrigger(botAI); }
static Trigger* loss_of_control(PlayerbotAI* botAI) { return new LossOfControlTrigger(botAI); } static Trigger* loss_of_control(PlayerbotAI* botAI) { return new LossOfControlTrigger(botAI); }
static Trigger* fear_charm_sleep(PlayerbotAI* botAI) { return new FearCharmSleepTrigger(botAI); } static Trigger* fear_charm_sleep(PlayerbotAI* botAI) { return new FearCharmSleepTrigger(botAI); }
static Trigger* fear_sleep_sap(PlayerbotAI* botAI) { return new FearSleepSapTrigger(botAI); }
static Trigger* PartyMemberCriticalHealth(PlayerbotAI* botAI) static Trigger* PartyMemberCriticalHealth(PlayerbotAI* botAI)
{ {
return new PartyMemberCriticalHealthTrigger(botAI); return new PartyMemberCriticalHealthTrigger(botAI);
@ -434,6 +444,7 @@ private:
static Trigger* wander_npc_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_WANDER_NPC); } static Trigger* wander_npc_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_WANDER_NPC); }
static Trigger* do_quest_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_DO_QUEST); } static Trigger* do_quest_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_DO_QUEST); }
static Trigger* travel_flight_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_TRAVEL_FLIGHT); } static Trigger* travel_flight_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_TRAVEL_FLIGHT); }
static Trigger* outdoor_pvp_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_OUTDOOR_PVP); }
static Trigger* can_self_resurrect(PlayerbotAI* ai) { return new SelfResurrectTrigger(ai); } static Trigger* can_self_resurrect(PlayerbotAI* ai) { return new SelfResurrectTrigger(ai); }
static Trigger* can_fish(PlayerbotAI* ai) { return new CanFishTrigger(ai); } static Trigger* can_fish(PlayerbotAI* ai) { return new CanFishTrigger(ai); }
static Trigger* can_use_fishing_bobber(PlayerbotAI* ai) { return new CanUseFishingBobberTrigger(ai); } static Trigger* can_use_fishing_bobber(PlayerbotAI* ai) { return new CanUseFishingBobberTrigger(ai); }

View File

@ -19,6 +19,7 @@ WorldLocation ArrowFormation::GetLocationInternal()
uint32 tankLines = 1 + tanks.Size() / 6; uint32 tankLines = 1 + tanks.Size() / 6;
uint32 meleeLines = 1 + melee.Size() / 6; uint32 meleeLines = 1 + melee.Size() / 6;
uint32 rangedLines = 1 + ranged.Size() / 6; uint32 rangedLines = 1 + ranged.Size() / 6;
//TODO Implement Healer Lines
uint32 healerLines = 1 + healers.Size() / 6; uint32 healerLines = 1 + healers.Size() / 6;
float offset = 0.f; float offset = 0.f;
@ -147,7 +148,7 @@ UnitPosition MultiLineUnitPlacer::Place(FormationUnit* unit, uint32 index, uint3
return placer.Place(unit, indexInLine, lineSize); return placer.Place(unit, indexInLine, lineSize);
} }
UnitPosition SingleLineUnitPlacer::Place(FormationUnit* unit, uint32 index, uint32 count) UnitPosition SingleLineUnitPlacer::Place(FormationUnit* /*unit*/, uint32 index, uint32 count)
{ {
float angle = orientation - M_PI / 2.0f; float angle = orientation - M_PI / 2.0f;
float x = cos(angle) * sPlayerbotAIConfig.followDistance * ((float)index - (float)count / 2); float x = cos(angle) * sPlayerbotAIConfig.followDistance * ((float)index - (float)count / 2);

View File

@ -20,7 +20,7 @@ public:
} }
public: public:
void CheckAttacker(Unit* creature, ThreatManager* threatMgr) override void CheckAttacker(Unit* creature, ThreatManager* /*threatMgr*/) override
{ {
Player* bot = botAI->GetBot(); Player* bot = botAI->GetBot();
if (!botAI->CanCastSpell(spell, creature)) if (!botAI->CanCastSpell(spell, creature))

View File

@ -13,7 +13,7 @@ public:
{ {
} }
void CheckAttacker(Unit* attacker, ThreatManager* threatMgr) override void CheckAttacker(Unit* attacker, ThreatManager* /*threatMgr*/) override
{ {
if (botAI->HasAura(spell, attacker)) if (botAI->HasAura(spell, attacker))
result = attacker; result = attacker;

View File

@ -50,7 +50,7 @@ public:
result = nullptr; result = nullptr;
} }
void CheckAttacker(Unit* attacker, ThreatManager* threatMgr) override void CheckAttacker(Unit* attacker, ThreatManager* /*threatMgr*/) override
{ {
if (Group* group = botAI->GetBot()->GetGroup()) if (Group* group = botAI->GetBot()->GetGroup())
{ {

View File

@ -11,8 +11,6 @@ std::vector<Item*> InventoryItemValueBase::Find(std::string const qualifier)
{ {
std::vector<Item*> result; std::vector<Item*> result;
Player* bot = InventoryAction::botAI->GetBot();
std::vector<Item*> items = InventoryAction::parseItems(qualifier); std::vector<Item*> items = InventoryAction::parseItems(qualifier);
for (Item* item : items) for (Item* item : items)
result.push_back(item); result.push_back(item);

View File

@ -17,7 +17,7 @@ class InventoryItemValueBase : public InventoryAction
public: public:
InventoryItemValueBase(PlayerbotAI* botAI) : InventoryAction(botAI, "empty") {} InventoryItemValueBase(PlayerbotAI* botAI) : InventoryAction(botAI, "empty") {}
bool Execute(Event event) override { return false; } bool Execute(Event /*event*/) override { return false; }
protected: protected:
std::vector<Item*> Find(std::string const qualifier); std::vector<Item*> Find(std::string const qualifier);

View File

@ -48,23 +48,6 @@ Item* ItemForSpellValue::Calculate()
} }
} }
// Workaround as some spells have no item mask (e.g. shaman weapon enhancements)
if (!strcmpi(spellInfo->SpellName[0], "rockbiter weapon") ||
!strcmpi(spellInfo->SpellName[0], "flametongue weapon") ||
!strcmpi(spellInfo->SpellName[0], "earthliving weapon") ||
!strcmpi(spellInfo->SpellName[0], "frostbrand weapon") || !strcmpi(spellInfo->SpellName[0], "windfury weapon"))
{
itemForSpell = GetItemFitsToSpellRequirements(EQUIPMENT_SLOT_MAINHAND, spellInfo);
if (itemForSpell && itemForSpell->GetTemplate()->Class == ITEM_CLASS_WEAPON)
return itemForSpell;
itemForSpell = GetItemFitsToSpellRequirements(EQUIPMENT_SLOT_OFFHAND, spellInfo);
if (itemForSpell && itemForSpell->GetTemplate()->Class == ITEM_CLASS_WEAPON)
return itemForSpell;
return nullptr;
}
if (!(spellInfo->Targets & TARGET_FLAG_ITEM)) if (!(spellInfo->Targets & TARGET_FLAG_ITEM))
return nullptr; return nullptr;

View File

@ -180,19 +180,11 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
delete pItem; delete pItem;
if (result != EQUIP_ERR_OK && result != EQUIP_ERR_CANT_CARRY_MORE_OF_THIS) if (result != EQUIP_ERR_OK && result != EQUIP_ERR_CANT_CARRY_MORE_OF_THIS)
{
return ITEM_USAGE_NONE; return ITEM_USAGE_NONE;
}
// Check is unique items are equipped or not // Check if unique items are equipped or not
bool needToCheckUnique = false; bool needToCheckUnique = result == EQUIP_ERR_CANT_CARRY_MORE_OF_THIS ||
if (result == EQUIP_ERR_CANT_CARRY_MORE_OF_THIS) itemProto->HasFlag(ITEM_FLAG_UNIQUE_EQUIPPABLE);
{
needToCheckUnique = true;
}
else if (itemProto->HasFlag(ITEM_FLAG_UNIQUE_EQUIPPABLE))
{
needToCheckUnique = true;
}
if (needToCheckUnique) if (needToCheckUnique)
{ {
@ -206,34 +198,38 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
bool isEquipped = (totalItemCount > bagItemCount); bool isEquipped = (totalItemCount > bagItemCount);
if (isEquipped) if (isEquipped)
{
return ITEM_USAGE_NONE; // Item is already equipped return ITEM_USAGE_NONE; // Item is already equipped
}
// If not equipped, continue processing // If not equipped, continue processing
} }
if (itemProto->Class == ITEM_CLASS_QUIVER) if (itemProto->Class == ITEM_CLASS_QUIVER && bot->getClass() != CLASS_HUNTER)
if (bot->getClass() != CLASS_HUNTER) return ITEM_USAGE_NONE;
return ITEM_USAGE_NONE;
if (itemProto->Class == ITEM_CLASS_CONTAINER) if (itemProto->Class == ITEM_CLASS_CONTAINER)
{ {
if (itemProto->SubClass != ITEM_SUBCLASS_CONTAINER) 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 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. // only replace if non-bag is larger than bag.
if (GetSmallestBagSize() >= itemProto->ContainerSlots) if (GetSmallestBagSize() >= itemProto->ContainerSlots)
return ITEM_USAGE_NONE; return ITEM_USAGE_NONE;
return ITEM_USAGE_EQUIP; return ITEM_USAGE_EQUIP;
} }
if (itemProto->Class == ITEM_CLASS_WEAPON && itemProto->SubClass == ITEM_SUBCLASS_WEAPON_MISC)
return ITEM_USAGE_NONE;
bool shouldEquip = false; bool shouldEquip = false;
// uint32 statWeight = sRandomItemMgr.GetLiveStatWeight(bot, itemProto->ItemId); // uint32 statWeight = sRandomItemMgr.GetLiveStatWeight(bot, itemProto->ItemId);
StatsWeightCalculator calculator(bot); StatsWeightCalculator calculator(bot);
calculator.SetItemSetBonus(false); calculator.SetItemSetBonus(false);
calculator.SetOverflowPenalty(false); calculator.SetOverflowPenalty(false);
// Apply PvP weights if the bot is specced for PvP
bool isPvp = sRandomPlayerbotMgr.IsSpecPvp(bot->GetGUID().GetCounter(), bot->getClass());
if (isPvp)
calculator.SetPvpSpec(true);
float itemScore = calculator.CalculateItem(itemProto->ItemId, randomPropertyId); float itemScore = calculator.CalculateItem(itemProto->ItemId, randomPropertyId);
if (itemScore) if (itemScore)
@ -249,19 +245,14 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
uint8 dstSlot = botAI->FindEquipSlot(itemProto, NULL_SLOT, true); uint8 dstSlot = botAI->FindEquipSlot(itemProto, NULL_SLOT, true);
// Check if dest wasn't set correctly by CanEquipItem and use FindEquipSlot instead // 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 // This occurs with unique items that are already in the bots bags when CanEquipItem is called
if (dest == 0) if (dest == 0 && dstSlot != NULL_SLOT)
{ {
if (dstSlot != NULL_SLOT) // Construct dest from dstSlot
{ dest = (INVENTORY_SLOT_BAG_0 << 8) | dstSlot;
// Construct dest from dstSlot
dest = (INVENTORY_SLOT_BAG_0 << 8) | dstSlot;
}
} }
if (dstSlot == EQUIPMENT_SLOT_FINGER1 || dstSlot == EQUIPMENT_SLOT_TRINKET1) if (dstSlot == EQUIPMENT_SLOT_FINGER1 || dstSlot == EQUIPMENT_SLOT_TRINKET1)
{
possibleSlots = 2; possibleSlots = 2;
}
// Check weapon case separately to keep things a bit cleaner // Check weapon case separately to keep things a bit cleaner
bool have2HWeapon = false; bool have2HWeapon = false;
@ -278,14 +269,9 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
itemProto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD2); 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 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 (bot->CanTitanGrip() && itemProto->InventoryType == INVTYPE_2HWEAPON && !isValidTGWeapon)
// If this weapon is 2H but not one of the valid TG weapon types, do not equip it at all. return ITEM_USAGE_NONE;
if (itemProto->InventoryType == INVTYPE_2HWEAPON && !isValidTGWeapon)
{
return ITEM_USAGE_NONE;
}
}
// Now handle the logic for equipping and possible offhand slots // Now handle the logic for equipping and possible offhand slots
// If the bot can Dual Wield and: // If the bot can Dual Wield and:
@ -312,9 +298,7 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
if (shouldEquipInSlot) if (shouldEquipInSlot)
return ITEM_USAGE_EQUIP; return ITEM_USAGE_EQUIP;
else else
{
return ITEM_USAGE_BAD_EQUIP; return ITEM_USAGE_BAD_EQUIP;
}
} }
ItemTemplate const* oldItemProto = oldItem->GetTemplate(); ItemTemplate const* oldItemProto = oldItem->GetTemplate();
@ -323,22 +307,16 @@ ItemUsage ItemUsageValue::QueryItemUsageForEquip(ItemTemplate const* itemProto,
{ {
// uint32 oldStatWeight = sRandomItemMgr.GetLiveStatWeight(bot, oldItemProto->ItemId); // uint32 oldStatWeight = sRandomItemMgr.GetLiveStatWeight(bot, oldItemProto->ItemId);
if (itemScore || oldScore) if (itemScore || oldScore)
{
shouldEquipInSlot = itemScore > oldScore * sPlayerbotAIConfig.equipUpgradeThreshold; shouldEquipInSlot = itemScore > oldScore * sPlayerbotAIConfig.equipUpgradeThreshold;
}
} }
// Bigger quiver // Bigger quiver
if (itemProto->Class == ITEM_CLASS_QUIVER) if (itemProto->Class == ITEM_CLASS_QUIVER)
{ {
if (!oldItem || oldItemProto->ContainerSlots < itemProto->ContainerSlots) if (!oldItem || oldItemProto->ContainerSlots < itemProto->ContainerSlots)
{
return ITEM_USAGE_EQUIP; return ITEM_USAGE_EQUIP;
}
else else
{
return ITEM_USAGE_NONE; return ITEM_USAGE_NONE;
}
} }
bool existingShouldEquip = true; bool existingShouldEquip = true;
@ -864,8 +842,6 @@ bool ItemUsageValue::SpellGivesSkillUp(uint32 spellId, Player* bot)
{ {
uint32 SkillValue = bot->GetPureSkillValue(skill->SkillLine); uint32 SkillValue = bot->GetPureSkillValue(skill->SkillLine);
uint32 craft_skill_gain = sWorld->getIntConfig(CONFIG_SKILL_GAIN_CRAFTING);
if (SkillGainChance(SkillValue, skill->TrivialSkillLineRankHigh, if (SkillGainChance(SkillValue, skill->TrivialSkillLineRankHigh,
(skill->TrivialSkillLineRankHigh + skill->TrivialSkillLineRankLow) / 2, (skill->TrivialSkillLineRankHigh + skill->TrivialSkillLineRankLow) / 2,
skill->TrivialSkillLineRankLow) > 0) skill->TrivialSkillLineRankLow) > 0)

View File

@ -13,7 +13,7 @@ class FindLeastHpTargetStrategy : public FindNonCcTargetStrategy
public: public:
FindLeastHpTargetStrategy(PlayerbotAI* botAI) : FindNonCcTargetStrategy(botAI), minHealth(0) {} FindLeastHpTargetStrategy(PlayerbotAI* botAI) : FindNonCcTargetStrategy(botAI), minHealth(0) {}
void CheckAttacker(Unit* attacker, ThreatManager* threatMgr) override void CheckAttacker(Unit* attacker, ThreatManager* /*threatMgr*/) override
{ {
if (IsCcTarget(attacker)) if (IsCcTarget(attacker))
return; return;

View File

@ -60,7 +60,7 @@ public:
class AllLootStrategy : public LootStrategy class AllLootStrategy : public LootStrategy
{ {
public: public:
bool CanLoot(ItemTemplate const* proto, AiObjectContext* context) override { return true; } bool CanLoot(ItemTemplate const* /*proto*/, AiObjectContext* /*context*/) override { return true; }
std::string const GetName() override { return "all"; } std::string const GetName() override { return "all"; }
}; };

View File

@ -28,4 +28,4 @@ void NearestCorpsesValue::FindUnits(std::list<Unit*>& targets)
Cell::VisitObjects(bot, searcher, range); Cell::VisitObjects(bot, searcher, range);
} }
bool NearestCorpsesValue::AcceptUnit(Unit* unit) { return true; } bool NearestCorpsesValue::AcceptUnit(Unit* /*unit*/) { return true; }

View File

@ -0,0 +1,64 @@
/*
* 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 "PartyMemberSnaredTargetValue.h"
#include <limits>
#include "PlayerbotAIAware.h"
#include "Playerbots.h"
class PartyMemberSnaredTargetPredicate : public FindPlayerPredicate, public PlayerbotAIAware
{
public:
PartyMemberSnaredTargetPredicate(PlayerbotAI* botAI)
: PlayerbotAIAware(botAI)
{
}
bool Check(Unit* unit) override
{
if (!unit || !unit->IsAlive() || !unit->IsInWorld() || unit == botAI->GetBot())
return false;
if (unit->GetMapId() != botAI->GetBot()->GetMapId())
return false;
if (!botAI->GetBot()->IsWithinLOSInMap(unit))
return false;
return botAI->IsMovementImpaired(unit);
}
};
Unit* PartyMemberSnaredTargetValue::Calculate()
{
Group* group = bot->GetGroup();
if (!group)
return nullptr;
PartyMemberSnaredTargetPredicate predicate(botAI);
Player* bestTarget = nullptr;
float closestDistanceSq = std::numeric_limits<float>::max();
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
if (!member)
continue;
if (!predicate.Check(member))
continue;
float const distanceSq = bot->GetExactDist2dSq(member->GetPositionX(), member->GetPositionY());
if (distanceSq < closestDistanceSq)
{
closestDistanceSq = distanceSq;
bestTarget = member;
}
}
return bestTarget;
}

View File

@ -0,0 +1,24 @@
/*
* 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_PARTYMEMBERSNAREDTARGETVALUE_H
#define _PLAYERBOT_PARTYMEMBERSNAREDTARGETVALUE_H
#include "NamedObjectContext.h"
#include "PartyMemberValue.h"
class PartyMemberSnaredTargetValue : public PartyMemberValue
{
public:
PartyMemberSnaredTargetValue(PlayerbotAI* botAI, std::string const name = "party member snared target")
: PartyMemberValue(botAI, name)
{
}
protected:
Unit* Calculate() override;
};
#endif

View File

@ -38,6 +38,36 @@ Unit* PartyMemberToHeal::Calculate()
bool isRaid = bot->GetGroup()->isRaidGroup(); bool isRaid = bot->GetGroup()->isRaidGroup();
MinValueCalculator calc(100); MinValueCalculator calc(100);
// If focus heal targets strategy is active, only heal those targets
if (botAI->HasStrategy("focus heal targets", BOT_STATE_COMBAT))
{
std::list<ObjectGuid> const focusHealTargets =
AI_VALUE(std::list<ObjectGuid>, "focus heal targets");
for (ObjectGuid const& focusHealTarget : focusHealTargets)
{
Player* player = ObjectAccessor::FindPlayer(focusHealTarget);
if (!player || !player->IsInWorld() || !player->IsAlive() || !player->IsInSameGroupWith(bot))
continue;
float health = player->GetHealthPct();
if (isRaid || health < sPlayerbotAIConfig.mediumHealth ||
!IsTargetOfSpellCast(player, predicate))
{
float probeValue = 100.0f;
if (player->GetDistance2d(bot) > sPlayerbotAIConfig.healDistance)
probeValue = health + 30.0f;
else
probeValue = health + player->GetDistance2d(bot) / 10.0f;
if (probeValue < calc.minValue && Check(player))
calc.probe(probeValue, player);
}
}
return (Unit*)calc.param;
}
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{ {
Player* player = gref->GetSource(); Player* player = gref->GetSource();
@ -45,17 +75,17 @@ Unit* PartyMemberToHeal::Calculate()
continue; continue;
if (player && player->IsAlive()) if (player && player->IsAlive())
{ {
uint8 health = player->GetHealthPct(); float health = player->GetHealthPct();
if (isRaid || health < sPlayerbotAIConfig.mediumHealth || !IsTargetOfSpellCast(player, predicate)) if (isRaid || health < sPlayerbotAIConfig.mediumHealth || !IsTargetOfSpellCast(player, predicate))
{ {
uint32 probeValue = 100; float probeValue = 100.0f;
if (player->GetDistance2d(bot) > sPlayerbotAIConfig.healDistance) if (player->GetDistance2d(bot) > sPlayerbotAIConfig.healDistance)
{ {
probeValue = health + 30; probeValue = health + 30.0f;
} }
else else
{ {
probeValue = health + player->GetDistance2d(bot) / 10; probeValue = health + player->GetDistance2d(bot) / 10.0f;
} }
// delay Check player to here for better performance // delay Check player to here for better performance
if (probeValue < calc.minValue && Check(player)) if (probeValue < calc.minValue && Check(player))
@ -68,10 +98,10 @@ Unit* PartyMemberToHeal::Calculate()
Pet* pet = player->GetPet(); Pet* pet = player->GetPet();
if (pet && pet->IsAlive()) if (pet && pet->IsAlive())
{ {
uint8 health = ((Unit*)pet)->GetHealthPct(); float health = ((Unit*)pet)->GetHealthPct();
uint32 probeValue = 100; float probeValue = 100.0f;
if (isRaid || health < sPlayerbotAIConfig.mediumHealth) if (isRaid || health < sPlayerbotAIConfig.mediumHealth)
probeValue = health + 30; probeValue = health + 30.0f;
// delay Check pet to here for better performance // delay Check pet to here for better performance
if (probeValue < calc.minValue && Check(pet)) if (probeValue < calc.minValue && Check(pet))
{ {
@ -82,10 +112,10 @@ Unit* PartyMemberToHeal::Calculate()
Unit* charm = player->GetCharm(); Unit* charm = player->GetCharm();
if (charm && charm->IsAlive()) if (charm && charm->IsAlive())
{ {
uint8 health = charm->GetHealthPct(); float health = charm->GetHealthPct();
uint32 probeValue = 100; float probeValue = 100.0f;
if (isRaid || health < sPlayerbotAIConfig.mediumHealth) if (isRaid || health < sPlayerbotAIConfig.mediumHealth)
probeValue = health + 30; probeValue = health + 30.0f;
// delay Check charm to here for better performance // delay Check charm to here for better performance
if (probeValue < calc.minValue && Check(charm)) if (probeValue < calc.minValue && Check(charm))
{ {

View File

@ -27,7 +27,7 @@ Unit* PartyMemberValue::FindPartyMember(std::vector<Player*>* party, FindPlayerP
return nullptr; return nullptr;
} }
Unit* PartyMemberValue::FindPartyMember(FindPlayerPredicate& predicate, bool ignoreOutOfGroup) Unit* PartyMemberValue::FindPartyMember(FindPlayerPredicate& predicate, bool /*ignoreOutOfGroup*/)
{ {
Player* master = GetMaster(); Player* master = GetMaster();
// GuidVector nearestPlayers; // GuidVector nearestPlayers;

View File

@ -13,6 +13,7 @@
#include "ServerFacade.h" #include "ServerFacade.h"
#include "SharedDefines.h" #include "SharedDefines.h"
#include "NearestGameObjects.h" #include "NearestGameObjects.h"
#include <unordered_set>
std::vector<uint32> PossibleRpgTargetsValue::allowedNpcFlags; std::vector<uint32> PossibleRpgTargetsValue::allowedNpcFlags;
@ -88,8 +89,11 @@ bool PossibleRpgTargetsValue::AcceptUnit(Unit* unit)
std::vector<uint32> PossibleNewRpgTargetsValue::allowedNpcFlags; std::vector<uint32> PossibleNewRpgTargetsValue::allowedNpcFlags;
// Sparse starting zones where the default scan range is insufficient for WANDER_NPC (requires >= 3 NPCs)
static const std::unordered_set<uint32> rpgRangeOverrideAreaIds = { 3526 /* Ammen Vale */, 2117 /* Deathknell */ };
PossibleNewRpgTargetsValue::PossibleNewRpgTargetsValue(PlayerbotAI* botAI, float range) PossibleNewRpgTargetsValue::PossibleNewRpgTargetsValue(PlayerbotAI* botAI, float range)
: NearestUnitsValue(botAI, "possible new rpg targets", range, true) : NearestUnitsValue(botAI, "possible new rpg targets", range, true), defaultRange(range)
{ {
if (allowedNpcFlags.empty()) if (allowedNpcFlags.empty())
{ {
@ -119,6 +123,11 @@ PossibleNewRpgTargetsValue::PossibleNewRpgTargetsValue(PlayerbotAI* botAI, float
GuidVector PossibleNewRpgTargetsValue::Calculate() GuidVector PossibleNewRpgTargetsValue::Calculate()
{ {
if (rpgRangeOverrideAreaIds.count(bot->GetAreaId()) && defaultRange < 200.0f)
range = 200.0f;
else
range = defaultRange;
std::list<Unit*> targets; std::list<Unit*> targets;
FindUnits(targets); FindUnits(targets);

View File

@ -35,6 +35,8 @@ public:
protected: protected:
void FindUnits(std::list<Unit*>& targets) override; void FindUnits(std::list<Unit*>& targets) override;
bool AcceptUnit(Unit* unit) override; bool AcceptUnit(Unit* unit) override;
private:
float defaultRange;
}; };
class PossibleNewRpgGameObjectsValue : public ObjectGuidListCalculatedValue class PossibleNewRpgGameObjectsValue : public ObjectGuidListCalculatedValue

View File

@ -40,13 +40,13 @@ bool SpellCastUsefulValue::Calculate()
return false; return false;
} }
// TODO: workaround if (qualifier == "windfury weapon" || qualifier == "flametongue weapon" ||
if (qualifier == "windfury weapon" || qualifier == "flametongue weapon" || qualifier == "frostbrand weapon" || qualifier == "frostbrand weapon" || qualifier == "rockbiter weapon" ||
qualifier == "rockbiter weapon" || qualifier == "earthliving weapon" || qualifier == "spellstone") qualifier == "earthliving weapon" || qualifier == "spellstone")
{ {
if (Item* item = AI_VALUE2(Item*, "item for spell", spellid)) if (Item* item = AI_VALUE2(Item*, "item for spell", spellid);
if (item->IsInWorld() && item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT)) item && item->IsInWorld() && item->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT))
return false; return false;
} }
std::set<uint32>& skipSpells = AI_VALUE(std::set<uint32>&, "skip spells list"); std::set<uint32>& skipSpells = AI_VALUE(std::set<uint32>&, "skip spells list");

View File

@ -49,7 +49,7 @@ class FindTankTargetSmartStrategy : public FindTargetStrategy
public: public:
FindTankTargetSmartStrategy(PlayerbotAI* botAI) : FindTargetStrategy(botAI) {} FindTankTargetSmartStrategy(PlayerbotAI* botAI) : FindTargetStrategy(botAI) {}
void CheckAttacker(Unit* attacker, ThreatManager* threatMgr) override void CheckAttacker(Unit* attacker, ThreatManager* /*threatMgr*/) override
{ {
if (Group* group = botAI->GetBot()->GetGroup()) if (Group* group = botAI->GetBot()->GetGroup())
{ {

View File

@ -161,7 +161,7 @@ Unit* FindTargetValue::Calculate()
return nullptr; return nullptr;
} }
void FindBossTargetStrategy::CheckAttacker(Unit* attacker, ThreatManager* threatManager) void FindBossTargetStrategy::CheckAttacker(Unit* attacker, ThreatManager* /*threatManager*/)
{ {
UnitAI* unitAI = attacker->GetAI(); UnitAI* unitAI = attacker->GetAI();
BossAI* bossAI = dynamic_cast<BossAI*>(unitAI); BossAI* bossAI = dynamic_cast<BossAI*>(unitAI);

View File

@ -116,6 +116,15 @@ public:
} }
}; };
class PullStrategyTargetValue : public ManualSetValue<ObjectGuid>
{
public:
PullStrategyTargetValue(PlayerbotAI* botAI, std::string const name = "pull strategy target")
: ManualSetValue<ObjectGuid>(botAI, ObjectGuid::Empty, name)
{
}
};
class FindTargetValue : public UnitCalculatedValue, public Qualified class FindTargetValue : public UnitCalculatedValue, public Qualified
{ {
public: public:
@ -140,4 +149,11 @@ public:
public: public:
Unit* Calculate(); Unit* Calculate();
}; };
class FocusHealTargetValue : public ManualSetValue<std::list<ObjectGuid>>
{
public:
FocusHealTargetValue(PlayerbotAI* botAI) : ManualSetValue<std::list<ObjectGuid>>(botAI, {}, "focus heal targets") {}
};
#endif #endif

View File

@ -66,6 +66,7 @@
#include "PartyMemberToDispel.h" #include "PartyMemberToDispel.h"
#include "PartyMemberToHeal.h" #include "PartyMemberToHeal.h"
#include "PartyMemberToResurrect.h" #include "PartyMemberToResurrect.h"
#include "PartyMemberSnaredTargetValue.h"
#include "PartyMemberWithoutAuraValue.h" #include "PartyMemberWithoutAuraValue.h"
#include "PartyMemberWithoutItemValue.h" #include "PartyMemberWithoutItemValue.h"
#include "PetTargetValue.h" #include "PetTargetValue.h"
@ -152,6 +153,7 @@ public:
creators["duel target"] = &ValueContext::duel_target; creators["duel target"] = &ValueContext::duel_target;
creators["party member to dispel"] = &ValueContext::party_member_to_dispel; creators["party member to dispel"] = &ValueContext::party_member_to_dispel;
creators["party member to protect"] = &ValueContext::party_member_to_protect; creators["party member to protect"] = &ValueContext::party_member_to_protect;
creators["party member snared target"] = &ValueContext::party_member_snared_target;
creators["health"] = &ValueContext::health; creators["health"] = &ValueContext::health;
creators["rage"] = &ValueContext::rage; creators["rage"] = &ValueContext::rage;
creators["energy"] = &ValueContext::energy; creators["energy"] = &ValueContext::energy;
@ -239,6 +241,8 @@ public:
creators["travel target"] = &ValueContext::travel_target; creators["travel target"] = &ValueContext::travel_target;
creators["talk target"] = &ValueContext::talk_target; creators["talk target"] = &ValueContext::talk_target;
creators["pull target"] = &ValueContext::pull_target; creators["pull target"] = &ValueContext::pull_target;
creators["pull strategy target"] = &ValueContext::pull_strategy_target;
creators["focus heal targets"] = &ValueContext::focus_heal_targets;
creators["group"] = &ValueContext::group; creators["group"] = &ValueContext::group;
creators["range"] = &ValueContext::range; creators["range"] = &ValueContext::range;
creators["inside target"] = &ValueContext::inside_target; creators["inside target"] = &ValueContext::inside_target;
@ -450,6 +454,7 @@ private:
static UntypedValue* party_member_to_resurrect(PlayerbotAI* botAI) { return new PartyMemberToResurrect(botAI); } static UntypedValue* party_member_to_resurrect(PlayerbotAI* botAI) { return new PartyMemberToResurrect(botAI); }
static UntypedValue* party_member_to_dispel(PlayerbotAI* botAI) { return new PartyMemberToDispel(botAI); } static UntypedValue* party_member_to_dispel(PlayerbotAI* botAI) { return new PartyMemberToDispel(botAI); }
static UntypedValue* party_member_to_protect(PlayerbotAI* botAI) { return new PartyMemberToProtect(botAI); } static UntypedValue* party_member_to_protect(PlayerbotAI* botAI) { return new PartyMemberToProtect(botAI); }
static UntypedValue* party_member_snared_target(PlayerbotAI* botAI) { return new PartyMemberSnaredTargetValue(botAI); }
static UntypedValue* current_target(PlayerbotAI* botAI) { return new CurrentTargetValue(botAI); } static UntypedValue* current_target(PlayerbotAI* botAI) { return new CurrentTargetValue(botAI); }
static UntypedValue* old_target(PlayerbotAI* botAI) { return new CurrentTargetValue(botAI); } static UntypedValue* old_target(PlayerbotAI* botAI) { return new CurrentTargetValue(botAI); }
static UntypedValue* self_target(PlayerbotAI* botAI) { return new SelfTargetValue(botAI); } static UntypedValue* self_target(PlayerbotAI* botAI) { return new SelfTargetValue(botAI); }
@ -494,6 +499,8 @@ private:
static UntypedValue* next_rpg_action(PlayerbotAI* botAI) { return new NextRpgActionValue(botAI); } static UntypedValue* next_rpg_action(PlayerbotAI* botAI) { return new NextRpgActionValue(botAI); }
static UntypedValue* travel_target(PlayerbotAI* botAI) { return new TravelTargetValue(botAI); } static UntypedValue* travel_target(PlayerbotAI* botAI) { return new TravelTargetValue(botAI); }
static UntypedValue* pull_target(PlayerbotAI* botAI) { return new PullTargetValue(botAI); } static UntypedValue* pull_target(PlayerbotAI* botAI) { return new PullTargetValue(botAI); }
static UntypedValue* pull_strategy_target(PlayerbotAI* botAI) { return new PullStrategyTargetValue(botAI); }
static UntypedValue* focus_heal_targets(PlayerbotAI* botAI) { return new FocusHealTargetValue(botAI); }
static UntypedValue* bg_master(PlayerbotAI* botAI) { return new BgMasterValue(botAI); } static UntypedValue* bg_master(PlayerbotAI* botAI) { return new BgMasterValue(botAI); }
static UntypedValue* bg_role(PlayerbotAI* botAI) { return new BgRoleValue(botAI); } static UntypedValue* bg_role(PlayerbotAI* botAI) { return new BgRoleValue(botAI); }

View File

@ -48,3 +48,55 @@ bool CastRaiseDeadAction::Execute(Event event)
return true; return true;
} }
Unit* CastHysteriaAction::GetTarget()
{
Group* group = bot->GetGroup();
if (!group)
{
if (!bot->HasAura(49016))
return bot;
return nullptr;
}
Unit* rangedDps = nullptr;
Unit* tank = nullptr;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || !member->IsAlive())
continue;
if (member->GetMap() != bot->GetMap() || bot->GetDistance(member) > sPlayerbotAIConfig.spellDistance)
continue;
// Skip if already has hysteria
if (member->HasAura(49016))
continue;
// Priority 1: Melee DPS
if (botAI->IsMelee(member) && botAI->IsDps(member))
return member;
// Priority 2: Ranged DPS (physical, not casters)
if (!rangedDps && botAI->IsRanged(member) && botAI->IsDps(member) && !botAI->IsCaster(member))
rangedDps = member;
// Priority 3: Tank
if (!tank && botAI->IsTank(member))
tank = member;
}
if (rangedDps)
return rangedDps;
if (tank)
return tank;
// Fallback to self if no hysteria
if (!bot->HasAura(49016))
return bot;
return nullptr;
}

View File

@ -340,4 +340,11 @@ public:
CastBloodTapAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "blood tap") {} CastBloodTapAction(PlayerbotAI* botAI) : CastMeleeSpellAction(botAI, "blood tap") {}
}; };
class CastHysteriaAction : public CastSpellAction
{
public:
CastHysteriaAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "hysteria") {}
Unit* GetTarget() override;
};
#endif #endif

View File

@ -8,11 +8,11 @@
#include "BloodDKStrategy.h" #include "BloodDKStrategy.h"
#include "DKActions.h" #include "DKActions.h"
#include "DKTriggers.h" #include "DKTriggers.h"
#include "DeathKnightPullStrategy.h"
#include "FrostDKStrategy.h" #include "FrostDKStrategy.h"
#include "GenericDKNonCombatStrategy.h" #include "GenericDKNonCombatStrategy.h"
#include "GenericTriggers.h" #include "GenericTriggers.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "PullStrategy.h"
#include "UnholyDKStrategy.h" #include "UnholyDKStrategy.h"
class DeathKnightStrategyFactoryInternal : public NamedObjectContext<Strategy> class DeathKnightStrategyFactoryInternal : public NamedObjectContext<Strategy>
@ -28,7 +28,7 @@ public:
private: private:
static Strategy* nc(PlayerbotAI* botAI) { return new GenericDKNonCombatStrategy(botAI); } static Strategy* nc(PlayerbotAI* botAI) { return new GenericDKNonCombatStrategy(botAI); }
static Strategy* pull(PlayerbotAI* botAI) { return new PullStrategy(botAI, "icy touch"); } static Strategy* pull(PlayerbotAI* botAI) { return new DeathKnightPullStrategy(botAI); }
static Strategy* frost_aoe(PlayerbotAI* botAI) { return new FrostDKAoeStrategy(botAI); } static Strategy* frost_aoe(PlayerbotAI* botAI) { return new FrostDKAoeStrategy(botAI); }
static Strategy* unholy_aoe(PlayerbotAI* botAI) { return new UnholyDKAoeStrategy(botAI); } static Strategy* unholy_aoe(PlayerbotAI* botAI) { return new UnholyDKAoeStrategy(botAI); }
}; };
@ -100,6 +100,7 @@ public:
creators["dd cd and no desolation"] = &DeathKnightTriggerFactoryInternal::dd_cd_and_no_desolation; creators["dd cd and no desolation"] = &DeathKnightTriggerFactoryInternal::dd_cd_and_no_desolation;
creators["death and decay cooldown"] = &DeathKnightTriggerFactoryInternal::death_and_decay_cooldown; creators["death and decay cooldown"] = &DeathKnightTriggerFactoryInternal::death_and_decay_cooldown;
creators["army of the dead"] = &DeathKnightTriggerFactoryInternal::army_of_the_dead; creators["army of the dead"] = &DeathKnightTriggerFactoryInternal::army_of_the_dead;
creators["hysteria no cd"] = &DeathKnightTriggerFactoryInternal::hysteria_no_cd;
} }
private: private:
@ -152,6 +153,7 @@ private:
} }
static Trigger* death_and_decay_cooldown(PlayerbotAI* botAI) { return new DeathAndDecayCooldownTrigger(botAI); } static Trigger* death_and_decay_cooldown(PlayerbotAI* botAI) { return new DeathAndDecayCooldownTrigger(botAI); }
static Trigger* army_of_the_dead(PlayerbotAI* botAI) { return new ArmyOfTheDeadTrigger(botAI); } static Trigger* army_of_the_dead(PlayerbotAI* botAI) { return new ArmyOfTheDeadTrigger(botAI); }
static Trigger* hysteria_no_cd(PlayerbotAI* botAI) { return new HysteriaNoCooldownTrigger(botAI); }
}; };
class DeathKnightAiObjectContextInternal : public NamedObjectContext<Action> class DeathKnightAiObjectContextInternal : public NamedObjectContext<Action>
@ -209,7 +211,7 @@ public:
creators["vampiric blood"] = &DeathKnightAiObjectContextInternal::vampiric_blood; creators["vampiric blood"] = &DeathKnightAiObjectContextInternal::vampiric_blood;
creators["death pact"] = &DeathKnightAiObjectContextInternal::death_pact; creators["death pact"] = &DeathKnightAiObjectContextInternal::death_pact;
creators["death rune_mastery"] = &DeathKnightAiObjectContextInternal::death_rune_mastery; creators["death rune_mastery"] = &DeathKnightAiObjectContextInternal::death_rune_mastery;
// creators["hysteria"] = &DeathKnightAiObjectContextInternal::hysteria; creators["hysteria"] = &DeathKnightAiObjectContextInternal::hysteria;
creators["dancing rune weapon"] = &DeathKnightAiObjectContextInternal::dancing_rune_weapon; creators["dancing rune weapon"] = &DeathKnightAiObjectContextInternal::dancing_rune_weapon;
creators["dark command"] = &DeathKnightAiObjectContextInternal::dark_command; creators["dark command"] = &DeathKnightAiObjectContextInternal::dark_command;
} }
@ -265,7 +267,7 @@ private:
static Action* vampiric_blood(PlayerbotAI* botAI) { return new CastVampiricBloodAction(botAI); } static Action* vampiric_blood(PlayerbotAI* botAI) { return new CastVampiricBloodAction(botAI); }
static Action* death_pact(PlayerbotAI* botAI) { return new CastDeathPactAction(botAI); } static Action* death_pact(PlayerbotAI* botAI) { return new CastDeathPactAction(botAI); }
static Action* death_rune_mastery(PlayerbotAI* botAI) { return new CastDeathRuneMasteryAction(botAI); } static Action* death_rune_mastery(PlayerbotAI* botAI) { return new CastDeathRuneMasteryAction(botAI); }
// static Action* hysteria(PlayerbotAI* botAI) { return new CastHysteriaAction(botAI); } static Action* hysteria(PlayerbotAI* botAI) { return new CastHysteriaAction(botAI); }
static Action* dancing_rune_weapon(PlayerbotAI* botAI) { return new CastDancingRuneWeaponAction(botAI); } static Action* dancing_rune_weapon(PlayerbotAI* botAI) { return new CastDancingRuneWeaponAction(botAI); }
static Action* dark_command(PlayerbotAI* botAI) { return new CastDarkCommandAction(botAI); } static Action* dark_command(PlayerbotAI* botAI) { return new CastDarkCommandAction(botAI); }
static Action* mind_freeze_on_enemy_healer(PlayerbotAI* botAI) static Action* mind_freeze_on_enemy_healer(PlayerbotAI* botAI)

View File

@ -50,7 +50,9 @@ private:
{ {
NextAction("frost presence") NextAction("frost presence")
}, },
/*A*/ {}, /*A*/ {
NextAction("blood strike")
},
/*C*/ {} /*C*/ {}
); );
} }
@ -89,13 +91,11 @@ BloodDKStrategy::BloodDKStrategy(PlayerbotAI* botAI) : GenericDKStrategy(botAI)
std::vector<NextAction> BloodDKStrategy::getDefaultActions() std::vector<NextAction> BloodDKStrategy::getDefaultActions()
{ {
return { return {
NextAction("rune strike", ACTION_DEFAULT + 0.8f), NextAction("rune strike", ACTION_DEFAULT + 0.6f),
NextAction("icy touch", ACTION_DEFAULT + 0.7f), NextAction("icy touch", ACTION_DEFAULT + 0.5f),
NextAction("heart strike", ACTION_DEFAULT + 0.6f), NextAction("heart strike", ACTION_DEFAULT + 0.4f),
NextAction("blood strike", ACTION_DEFAULT + 0.5f), NextAction("dancing rune weapon", ACTION_DEFAULT + 0.3f),
NextAction("dancing rune weapon", ACTION_DEFAULT + 0.4f), NextAction("death coil", ACTION_DEFAULT + 0.2f),
NextAction("death coil", ACTION_DEFAULT + 0.3f),
NextAction("plague strike", ACTION_DEFAULT + 0.2f),
NextAction("horn of winter", ACTION_DEFAULT + 0.1f), NextAction("horn of winter", ACTION_DEFAULT + 0.1f),
NextAction("melee", ACTION_DEFAULT) NextAction("melee", ACTION_DEFAULT)
}; };
@ -105,6 +105,14 @@ void BloodDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
GenericDKStrategy::InitTriggers(triggers); GenericDKStrategy::InitTriggers(triggers);
triggers.push_back(
new TriggerNode(
"hysteria no cd",
{
NextAction("hysteria", ACTION_NORMAL + 4)
}
)
);
triggers.push_back( triggers.push_back(
new TriggerNode( new TriggerNode(
"rune strike", "rune strike",
@ -162,4 +170,12 @@ void BloodDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
} }
) )
); );
triggers.push_back(
new TriggerNode(
"high unholy rune",
{
NextAction("death strike", ACTION_HIGH + 1)
}
)
);
} }

View File

@ -0,0 +1,43 @@
/*
* 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 "DeathKnightPullStrategy.h"
#include "AiObjectContext.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
std::string DeathKnightPullStrategy::GetPullActionName() const
{
Player* bot = botAI->GetBot();
Unit* target = GetTarget();
if (!bot || !target ||
(!botAI->HasStrategy("blood", BOT_STATE_COMBAT) && !botAI->HasStrategy("blood", BOT_STATE_NON_COMBAT)))
{
return PullStrategy::GetPullActionName();
}
uint32 const deathGripSpellId = botAI->GetAiObjectContext()->GetValue<uint32>("spell id", "death grip")->Get();
if (deathGripSpellId && bot->HasSpell(deathGripSpellId) &&
botAI->CanCastSpell(deathGripSpellId, target))
{
return "death grip";
}
uint32 const icyTouchSpellId = botAI->GetAiObjectContext()->GetValue<uint32>("spell id", "icy touch")->Get();
if (!icyTouchSpellId || !bot->HasSpell(icyTouchSpellId) ||
!botAI->CanCastSpell(icyTouchSpellId, target))
{
uint32 const darkCommandSpellId = botAI->GetAiObjectContext()->GetValue<uint32>("spell id", "dark command")->Get();
if (darkCommandSpellId && bot->HasSpell(darkCommandSpellId) &&
botAI->CanCastSpell(darkCommandSpellId, target))
{
return "dark command";
}
}
return PullStrategy::GetPullActionName();
}

View File

@ -0,0 +1,19 @@
/*
* 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_DEATH_KNIGHT_PULL_STRATEGY_H
#define _PLAYERBOT_DEATH_KNIGHT_PULL_STRATEGY_H
#include "PullStrategy.h"
class DeathKnightPullStrategy : public PullStrategy
{
public:
DeathKnightPullStrategy(PlayerbotAI* botAI) : PullStrategy(botAI, "icy touch") {}
std::string GetPullActionName() const override;
};
#endif

View File

@ -91,7 +91,6 @@ std::vector<NextAction> FrostDKStrategy::getDefaultActions()
return { return {
NextAction("obliterate", ACTION_DEFAULT + 0.7f), NextAction("obliterate", ACTION_DEFAULT + 0.7f),
NextAction("frost strike", ACTION_DEFAULT + 0.4f), NextAction("frost strike", ACTION_DEFAULT + 0.4f),
NextAction("empower rune weapon", ACTION_DEFAULT + 0.3f),
NextAction("horn of winter", ACTION_DEFAULT + 0.1f), NextAction("horn of winter", ACTION_DEFAULT + 0.1f),
NextAction("melee", ACTION_DEFAULT) NextAction("melee", ACTION_DEFAULT)
}; };

View File

@ -41,19 +41,13 @@ void GenericDKNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
{ {
NonCombatStrategy::InitTriggers(triggers); NonCombatStrategy::InitTriggers(triggers);
triggers.push_back(
new TriggerNode("no pet", { NextAction("raise dead", ACTION_NORMAL + 1) }));
triggers.push_back( triggers.push_back(
new TriggerNode("horn of winter", { NextAction("horn of winter", 21.0f) })); new TriggerNode("horn of winter", { NextAction("horn of winter", 21.0f) }));
triggers.push_back( triggers.push_back(
new TriggerNode("bone shield", { NextAction("bone shield", 21.0f) })); new TriggerNode("bone shield", { NextAction("bone shield", 21.0f) }));
triggers.push_back(
new TriggerNode("has pet", { NextAction("toggle pet spell", 60.0f) }));
triggers.push_back(
new TriggerNode("new pet", { NextAction("set pet stance", 60.0f) }));
} }
void DKBuffDpsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void DKBuffDpsStrategy::InitTriggers(std::vector<TriggerNode*>& /*triggers*/)
{ {
} }

View File

@ -165,12 +165,6 @@ void GenericDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
MeleeCombatStrategy::InitTriggers(triggers); MeleeCombatStrategy::InitTriggers(triggers);
triggers.push_back(
new TriggerNode("no pet", { NextAction("raise dead", ACTION_NORMAL + 5) }));
triggers.push_back(
new TriggerNode("has pet", { NextAction("toggle pet spell", 60.0f) }));
triggers.push_back(
new TriggerNode("new pet", { NextAction("set pet stance", 60.0f) }));
triggers.push_back( triggers.push_back(
new TriggerNode("mind freeze", { NextAction("mind freeze", ACTION_HIGH + 1) })); new TriggerNode("mind freeze", { NextAction("mind freeze", ACTION_HIGH + 1) }));
triggers.push_back( triggers.push_back(
@ -179,7 +173,8 @@ void GenericDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode( triggers.push_back(new TriggerNode(
"horn of winter", { NextAction("horn of winter", ACTION_NORMAL + 1) })); "horn of winter", { NextAction("horn of winter", ACTION_NORMAL + 1) }));
triggers.push_back(new TriggerNode("critical health", triggers.push_back(new TriggerNode("critical health",
{ NextAction("death pact", ACTION_HIGH + 5) })); { NextAction("raise dead", ACTION_HIGH + 6),
NextAction("death pact", ACTION_HIGH + 5) }));
triggers.push_back( triggers.push_back(
new TriggerNode("low health", { NextAction("icebound fortitude", ACTION_HIGH + 5), new TriggerNode("low health", { NextAction("icebound fortitude", ACTION_HIGH + 5),
@ -190,4 +185,11 @@ void GenericDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
NextAction("blood boil", ACTION_NORMAL + 3) })); NextAction("blood boil", ACTION_NORMAL + 3) }));
triggers.push_back( triggers.push_back(
new TriggerNode("pestilence glyph", { NextAction("pestilence", ACTION_HIGH + 9) })); new TriggerNode("pestilence glyph", { NextAction("pestilence", ACTION_HIGH + 9) }));
triggers.push_back(
new TriggerNode("no rune",
{
NextAction("empower rune weapon", ACTION_HIGH + 1)
}
)
);
} }

View File

@ -87,6 +87,13 @@ void UnholyDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {
GenericDKStrategy::InitTriggers(triggers); GenericDKStrategy::InitTriggers(triggers);
triggers.push_back(
new TriggerNode("no pet", { NextAction("raise dead", ACTION_NORMAL + 5) }));
triggers.push_back(
new TriggerNode("has pet", { NextAction("toggle pet spell", 60.0f) }));
triggers.push_back(
new TriggerNode("new pet", { NextAction("set pet stance", 60.0f) }));
triggers.push_back( triggers.push_back(
new TriggerNode( new TriggerNode(
"death and decay cooldown", "death and decay cooldown",
@ -146,13 +153,6 @@ void UnholyDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
} }
) )
); );
triggers.push_back(
new TriggerNode("no rune",
{
NextAction("empower rune weapon", ACTION_HIGH + 1)
}
)
);
triggers.push_back( triggers.push_back(
new TriggerNode( new TriggerNode(
"army of the dead", "army of the dead",

Some files were not shown because too many files have changed in this diff Show More