Compare commits

...

14 Commits

Author SHA1 Message Date
kadeshar
299e4398da
Merge pull request #2191 from mod-playerbots/test-staging
Test staging to master
2026-03-13 22:20:09 +01:00
kadeshar
2925f248a6
Workaround for crash related with uninitalized script (#2196)
# Pull Request

Workaround for crashes related with uninitialized new script introduced
in
e74adf550e

---

## Feature Evaluation

---

## How to Test the Changes

- run server with default bots amount without changes and should crashed
- run server with default bots amount with changes and should run
normally

## Complexity & Impact

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

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

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

## Defaults & Configuration

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

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

## AI Assistance

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

Used Copilot Cli to find crash reason.

---

## Final Checklist

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

---
2026-03-08 08:50:01 +01:00
kadeshar
a8dda550ad
Requirement fix to use bigobj parameter to compile (#2176)
# Pull Request
Compilation fix which making possible compiling without bigobj parameter

---

## How to Test the Changes

- compile using Visual Studio without bigobj parameter

## Complexity & Impact

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

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

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

## Defaults & Configuration

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

## AI Assistance

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

Automate file creation

---

## Final Checklist

- - [x] Stability is not compromised
- - [x] Performance impact is understood, tested, and acceptable
- - [x] Added logic complexity is justified and explained
- - [x] Documentation updated if needed
2026-03-08 08:49:45 +01:00
NoxMax
1a3468368d
PR template proposal, using the proposed template itself (#2170)
<!--
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 addition of a PR template last month was a great idea. We had
nothing before and just let people type whatever they thought was
relevant. Some wrote a whole article with too many details, and some
just wrote the title and didn't explain any of the important details.

So the addition of the PR template makes sure contributors know what's
most important to this project. However, several people thought the
template was... a lot. A lot of the information it showed, while useful
to the contributor, made it a bit confusing to reviewer to know what the
contributor wrote, and what is part of the PR template, so a lot of
these guidelines have now been put behind `<!-- -->`.

Moreover, even what has been hidden, has been truncated. The main
message of the guidelines is stability is our top priority. It is a
critical message, but it was repeated several more times than it
should've been. Less is more here, and if an important message is
repeated in a verbose manner, people would gloss over it like scrolling
down long terms and conditions. The questions were also made more
concise and explicit. We don't want contributors to question the
questions themselves and how do they even apply to their code.

The process of PR submission itself should not feel like submitting a
long bureaucratic form. Think of it like a scientific paper abstract: It
gives a reasonably short summary explaining the work, as clearly as
possible.


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

These are the core questions that are important to know, but even then,
not always relevant. So a note was added to the contributor that they
can obviously skip these if their PR is something like a comment edit or
whatever else that clearly doesn't add processing.

## 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.
-->
You are already testing whether or not this template is effective by
looking at it.

1. See
the"[Preview](https://github.com/NoxMax/mod-playerbots/blob/PR-template-proposal/PULL_REQUEST_TEMPLATE.md)"
of the file just so it's clear what the template is like without any of
the comments I made here.
2. See the
"[Code](https://github.com/NoxMax/mod-playerbots/blob/PR-template-proposal/PULL_REQUEST_TEMPLATE.md?plain=1)"
section of the template to see how it would actually look to a
contributor. The only difference is the alignment of the translation
table; it looks weird in the .md file, but it would look properly
aligned to the contributor when submitting.


## 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**)
    
Broke down processing impact into minimal and moderate. It is not
uncommon that we have changes that add some minimal processing, and yes,
collectively they can become an issue, but we also need to distinguish
them from the rare changes that have a moderate impact, and how critical
those changes are.

- 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**)
    
This question merges two previous one, because it's really asking the
same thing: Will your change be a headache to maintain down the line?

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

This is a new section, based on an idea that was discuss to not have
everyone add their SQL translation files to their PR, and figure out
file date name based on merge order, and coordinate who's using which
message key. No. The hidden instruction instead tell the contributor to
prerp the code to be translatable, by looking up GetBotTextOrDefault in
the codebase for examples, and leave it that.
When merged it would just use the default English fallback, then a
monthly PR can be made containing translations for all the recently
merged commits that have bot messages. The code would then automatically
pickup the translated lines for that.
This section of the template would remain if there's a consensus that
this is how the translation workflow should be.

## 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 (Code comments, Conf comments,
Commands in the Wiki).

Final checklist remains the same, only clarifies to the contributors
what sort of documentations that need updating.

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
This is a literal draft of of what the template should be, in that I
look forward to your ideas to any ways that can further improve this.
2026-03-06 20:04:37 +01:00
St0ny
55708f397a
add sql update (#2137)
Correction of a spelling mistake in the German chatter-texts.

# Pull Request

There is an error in the German translation of the chatter text.

This will be fixed 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?

---

## How to Test the Changes

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

## Complexity & Impact

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

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

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

## Defaults & Configuration

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

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

## AI Assistance

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

If yes, please specify:

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

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

---

## Final Checklist

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

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.
2026-03-06 20:03:44 +01:00
killerzwelch
660a5c0543
make playerbots compatible with 515aeca (#2181)
# Pull Request

needed changes for
515aeca570

---

## Design Philosophy

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

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

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

Principles:

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

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

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

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

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

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

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

---

## Feature Evaluation

Please answer the following:

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

---

## How to Test the Changes

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

## Complexity & Impact

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

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

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

## Defaults & Configuration

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

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

## AI Assistance

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

If yes, please specify:

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

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

---

## Final Checklist

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

---

## Notes for Reviewers

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

---------

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-03-06 20:03:32 +01:00
Crow
14c77b1e7b
Remove instance strategies when leaving map (#2163)
# Pull Request

Currently, dungeon and raid strategies, which are automatically added
when entering the applicable instance (unless disabled in config), will
persist until manually removed or until a different instance strategy is
applied. This is pretty bad because then bots will continue to check
triggers for the instance when outside of it.

This has been discussed for a long time, but after finally considering
it today, I think the solution is pretty simple because the existing
framework is already there. PlayerbotAI::ApplyInstanceStrategies() is
the function for enabling strategies when entering an instance, and it's
called whenever a bot changes maps. So all we need to do is to remove
all instance strategies first when calling it. I tested these changes,
and they worked for me, but obviously others should test too, and
especially the code should be examined since that is not my area of
expertise.

---

## Design Philosophy

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

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

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

Principles:

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

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

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

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

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

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

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

---

## Feature Evaluation

Please answer the following:

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

---

## How to Test the Changes

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

## Complexity & Impact

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

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

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

## Defaults & Configuration

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

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

## AI Assistance

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

If yes, please specify:

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

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

I used Gemini to verify that my idea would work and had it put together
the actual code for me.

---

## 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-03-06 10:50:00 -08:00
Hokken
788c7b025b
Fix quest links triggering trade window (#2155)
## Summary

`ChatHelper::parseable()` matched any hyperlink containing `|H`,
including quest links (`|Hquest:`), achievement links, spell links, etc.
This caused bots to interpret quest links shared in party chat as item
trade requests, opening the trade window instead of ignoring them.

Narrowed the check from `"|H"` to `"|Hitem:"` so only actual item links
trigger the parseable/trade logic.

**One-line change** in `src/Bot/Cmd/ChatHelper.cpp:603`

## Root Cause

The WoW client uses `|H<type>:<id>|h[Name]|h` hyperlinks for many object
types:
- `|Hitem:12345|h[Item Name]|h` — items
- `|Hquest:678|h[Quest Name]|h` — quests  
- `|Hspell:890|h[Spell Name]|h` — spells
- `|Hachievement:...|h` — achievements

The old check `text.find("|H")` matched ALL of these, so sharing a quest
link in party chat would cause the bot to enter the item parsing/trade
flow.

## Test Scenarios

| Scenario | Before | After |
|----------|--------|-------|
| Share `[Quest Name]` in party chat | Trade window opens | No reaction
(correct) |
| Share `[Item Name]` in party chat | Trade window opens | Trade window
opens (unchanged) |
| Say "questitem" in chat | Parsed correctly | Parsed correctly
(unchanged) |
| Share `[Spell Name]` in party chat | Trade window opens | No reaction
(correct) |

Tested on AzerothCore 3.3.5a with mod-playerbots, confirmed fix resolves
the issue.

---------

Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com>
Co-authored-by: bash <hermensb@gmail.com>
Co-authored-by: Hokken <Hokken@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 09:41:40 -08:00
NoxMax
ca9f23a8e3
Fix/Defensive: Prevent division by zero in MovementActions (#2185)
Added a check to prevent division by zero for orphaned raid groups.

# Pull Request

If a bots somehow ends up alone in a raid group, this can divide by zero
and freeze the server.

---

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

This is the simplest and cheapest way to implement this fix.

---

## 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 fix is a self-evident defensive measure.

## Complexity & Impact

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

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

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

## Defaults & Configuration

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

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

## AI Assistance

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

Core dump logs analysis to find this problem.

If yes, please specify:

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

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

---

## Final Checklist

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

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.
2026-03-06 07:59:36 -08:00
kadeshar
935252fcfe
Action trigger fix (#2180)
Maintenance PR unrelated with module itself
Modified action trigger to cover branch change
2026-03-06 07:58:47 -08:00
Rikus Louw
ed81a43403
Added all TBC attunement quests (#2179)
# Pull Request

Added all TBC attunement quests to conf

---

## Design Philosophy

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

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

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

Principles:

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

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

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

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

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

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

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

---

## Feature Evaluation

Please answer the following:

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

---

## How to Test the Changes

- Step-by-step instructions to test the change
Run maintenance on bots
- Any required setup (e.g. multiple players, bots, specific
configuration)
This only applies to Individual Progression mod, since attunements
aren't required in base AC
- Expected behavior and how to verify it
Bots should be able to enter:
- The Eye (Tempest Keep)
- Mount Hyjal
- Black Temple

## 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**)
All attunements for TBC are now added on 'maintenance' command

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

## AI Assistance

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

If yes, please specify:

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

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

---

## Final Checklist

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

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.
2026-03-06 07:58:32 -08:00
Crow
80860d72a3
Some simple improvements to Karazhan strategies (#2173)
# Pull Request

I've made a few simple changes to the Karazhan strategies that should
result in notable improvements in game.

- **Attumen**: I was using a GetExactDist2d() check for phase 2 when
bots stack behind him. That resulted in ranged bots being too close to
attack. It's now switched to the correct GetDistance2d() check to
account for the hitbox.
- **Maiden of Virtue**: The tank continuously ran side-to-side when
trying to tank her because it was trying to turn the boss with
TankFaceAction but not being able to due to being required to be within
a short distance of a set waypoint. I didn't understand the cause when I
was originally working on Karazhan. To fix this, a new multiplier
disables CombatFormationMoveAction (the "co+ disperse" strategy) and its
inherited classes, except for SetBehindTargetAction. The only other
class that inherits from CombatFormationMoveAction is TankFaceAction. I
disabled the parent class also because the ranged bots have a coded
positioning strategy and should not observe the co+ disperse strategy.
- **The Curator**: Same deal as Maiden with a new multiplier.
- **Nightbane**: Same deal as Maiden with a new multiplier.
- **Malchezaar**: Infernal avoidance for non-enfeebled bots had movement
priority set to MOVEMENT_FORCED. This was not good because it made bots
refuse to cross Hellfire so if you got unlucky, they could be stuck on
the other side of an Infernal from the boss and completely out of the
fight. MOVEMENT_FORCED needs to be reserved for situations in which the
bot absolutely cannot step in the AoE at all, and that's not the case
for non-Enfeebled bots here. Priority is now changed to MOVEMENT_COMBAT.

---

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

No additional complication in logic from these changes, and additional
performance impact is exceedingly small (just a few more multipliers
with inexpensive checks that would apply only in Karazhan).

---

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

Should be straightforward. Engage the above-mentioned bosses in Karazhan
and observe the mechanics. I did test all of them.

## Complexity & Impact

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

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

Barely due to the additional multipliers.

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

## Defaults & Configuration

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

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

## AI Assistance

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

---

## Final Checklist

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

---

## 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>
2026-03-06 07:58:02 -08:00
Alex Dcnh
18bd655869
Restore Naxx Strategies without core dependencies (#2031)
### Summary
This PR restores the Naxxramas raid strategies that were removed in
commit 686fe513b25bbb20ccfcc89f08ee8c4b498a263e .
The reintroduced logic is core‑friendly (no AzerothCore script headers
or internal boss AI/EventMap dependencies), and the Naxxramas actions
have been refactored into per‑boss files for better maintainability.

### Motivation
The previous removal was meant to avoid core modifications and unblock
upstreaming.
This PR brings the strategies back while adhering to that requirement,
using only observable state and mod‑playerbots helpers.

### What’s included

- Re‑enabled the Naxxramas strategies previously removed.
- Replaced core script header dependencies with observable checks
(auras, casts, unit flags, flight state, etc.).
- Split the Naxxramas action logic into per‑boss source files to avoid a
“god file” and ease future maintenance.
- Minor, non‑intrusive behavior improvements aligned with existing
helpers.

### Future work
Some strategies may still require refinement or more advanced handling
later.
This PR focuses on restoring the baseline logic without core
dependencies, while keeping changes minimal and safe.

**Any contributions are welcome to further improve and fine‑tune the
Naxxramas strategies.**

### Testing
Tested in some Naxx boxx.
No server crash and boss killed :D

Note: I'll make another PR with revised scripts when this one are merged

---------

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-03-06 07:57:21 -08:00
kadeshar
28a888b6e0
Added unobtainable items to config (#2133)
# Pull Request

Moving hardcoded values to config

## How to Test the Changes

- use maintenance command
- unequip and destroy item get from this command
- turn off server
- add item to config
- turn on server
- use maintenace command
- check that different item was provided

## Complexity & Impact

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

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

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

## Defaults & Configuration

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

## AI Assistance

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

---

## Final Checklist

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

---

## Notes for Reviewers
2026-03-06 07:56:53 -08:00
60 changed files with 3821 additions and 261 deletions

View File

@ -2,6 +2,7 @@ name: Enforce test-staging → master
on:
pull_request:
types: [opened, synchronize, reopened, edited]
branches:
- master
- test-staging

View File

@ -1,124 +1,103 @@
# Pull Request
<!--
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.
Describe what this change does and why it is needed...
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.
## Design Philosophy
Before submitting, make sure your changes aligns with these principles.
-->
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.
## Pull Request Description
<!-- Describe what this change does and why it is needed -->
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
<!--
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:
<!-- 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.
- Describe the **minimum logic** required to achieve the intended behavior?
- Describe the **cheapest implementation** that produces an acceptable result?
- Describe the **runtime cost** when this logic executes across many bots?
---
## How to Test the Changes
<!--
- Step-by-step instructions to test the change.
- Any required setup (e.g. multiple players, number of bots, specific configuration).
- Expected behavior and how to verify it.
-->
- Step-by-step instructions to test the change
- Any required setup (e.g. multiple players, bots, specific configuration)
- Expected behavior and how to verify it
## Complexity & Impact
Does this change add new decision branches?
- - [ ] No
- - [ ] Yes (**explain below**)
## 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 increase per-bot or per-tick processing?
- - [ ] No
- - [ ] Yes (**describe and justify impact**)
Could this logic scale poorly under load?
- - [ ] No
- - [ ] Yes (**explain why**)
---
## Defaults & Configuration
- Does this change modify default bot behavior?
- [ ] No
- [ ] Yes (**explain why**)
Does this change modify default bot behavior?
- - [ ] No
- - [ ] Yes (**explain why**)
If this introduces more advanced or AI-heavy logic:
- - [ ] Lightweight mode remains the default
- - [ ] More complex behavior is optional and thereby configurable
---
- Does this change add new decision branches or increase maintenance complexity?
- [ ] 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
- [ ] Yes (**list messages in the table**)
| Message key | Default message |
| --------------- | ------------------ |
| | |
| | |
## AI Assistance
Was AI assistance (e.g. ChatGPT or similar tools) used while working on this change?
- - [ ] No
- - [ ] Yes (**explain below**)
If yes, please specify:
- AI tool or model used (e.g. ChatGPT, GPT-4, Claude, etc.)
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code generation)
- Which parts of the change were influenced or generated
- Whether the result was manually reviewed and adapted
<!--
AI assistance is allowed, but all submitted code must be fully understood, reviewed, and owned by the contributor.
Any AI-influenced changes must be verified against existing CORE and PB logic. We expect contributors to be honest
about what they do and do not understand.
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.
-->
---
## Final Checklist
- - [ ] Stability is not compromised
- - [ ] Performance impact is understood, tested, and acceptable
- - [ ] Added logic complexity is justified and explained
- - [ ] Documentation updated if needed
---
- [ ] 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 that significantly improves realism at the cost of stability or performance should be carefully discussed
before merging.
<!-- Anything else that's helpful to review or test your pull request. -->

View File

@ -566,7 +566,10 @@ AiPlayerbot.AutoGearScoreLimit = 0
# Default: food, taxi, and raid are enabled
AiPlayerbot.BotCheats = "food,taxi,raid"
# Attunement quests (comma-separated list of quest IDs)
# 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.
# This is meant to exclude bots from such requirements.
#
# Default:
# Caverns of Time - Part 1
# - 10279, To The Master's Lair
@ -592,7 +595,17 @@ AiPlayerbot.BotCheats = "food,taxi,raid"
#
# Serpentshrine Cavern
# - 10901, The Cudgel of Kar'desh
AiPlayerbot.AttunementQuests = 10279,10277,10282,10283,10284,10285,10296,10297,10298,11481,11482,11488,11490,11492,10901
#
# The Eye
# - 10888, Trial of the Naaru: Magtheridon
#
# Mount Hyjal
# - 10445, The Vials of Eternity
#
# Black Temple
# - 10985, A Distraction for Akama
#
AiPlayerbot.AttunementQuests = 10279,10277,10282,10283,10284,10285,10296,10297,10298,11481,11482,11488,11490,11492,10901,10888,10445,10985
#
#
@ -796,6 +809,10 @@ AiPlayerbot.LimitGearExpansion = 1
# Set between 0 (0%) and 1 (100%)
AiPlayerbot.RandomGearLoweringChance = 0
# Unobtainable or unusable items (comma-separated list of item IDs)
# Default: Chilton Wand (12468), Totem of the Earthen Ring (46978)
AiPlayerbot.UnobtainableItems = 12468,46978
# Randombots check player's gearscore level and deny the group invitation if it's too low
# Default: 0 (disabled)
AiPlayerbot.GearScoreCheck = 0

View File

@ -0,0 +1 @@
UPDATE `ai_playerbot_texts` SET `text_loc3`='%s, du hörst den triefenden Sarkasmus in meinem text nicht' WHERE `id`=1353;

View File

@ -237,6 +237,20 @@ bool MaxDpsChatShortcutAction::Execute(Event /*event*/)
return true;
}
bool NaxxChatShortcutAction::Execute(Event /*event*/)
{
Player* master = GetMaster();
if (!master)
return false;
botAI->Reset();
botAI->ChangeStrategy("+naxx", BOT_STATE_NON_COMBAT);
botAI->ChangeStrategy("+naxx", BOT_STATE_COMBAT);
botAI->TellMasterNoFacing("Add Naxx Strategies!");
// bot->Say("Add Naxx Strategies!", LANG_UNIVERSAL);
return true;
}
bool BwlChatShortcutAction::Execute(Event /*event*/)
{
Player* master = GetMaster();

View File

@ -85,6 +85,13 @@ public:
bool Execute(Event event) override;
};
class NaxxChatShortcutAction : public Action
{
public:
NaxxChatShortcutAction(PlayerbotAI* ai) : Action(ai, "naxx chat shortcut") {}
virtual bool Execute(Event event);
};
class BwlChatShortcutAction : public Action
{
public:

View File

@ -854,6 +854,11 @@ float MovementAction::GetFollowAngle()
if (!group)
return 0.0f;
// Prevent bots with orphaned raid groups from dividing by 0, which freezes the server.
uint32 memberCount = group->GetMembersCount();
if (memberCount <= 1)
return 0.0f;
uint32 index = 1;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
@ -861,7 +866,7 @@ float MovementAction::GetFollowAngle()
continue;
if (ref->GetSource() == bot)
return 2 * M_PI / (group->GetMembersCount() - 1) * index;
return 2 * M_PI / (memberCount - 1) * index;
++index;
}

View File

@ -187,6 +187,7 @@ public:
creators["guild leave"] = &ChatActionContext::guild_leave;
creators["rtsc"] = &ChatActionContext::rtsc;
creators["bwl chat shortcut"] = &ChatActionContext::bwl_chat_shortcut;
creators["naxx chat shortcut"] = &ChatActionContext::naxx_chat_shortcut;
creators["tell estimated dps"] = &ChatActionContext::tell_estimated_dps;
creators["join"] = &ChatActionContext::join;
creators["lfg"] = &ChatActionContext::lfg;
@ -298,6 +299,7 @@ private:
static Action* guild_remove(PlayerbotAI* botAI) { return new GuildRemoveAction(botAI); }
static Action* guild_leave(PlayerbotAI* botAI) { return new GuildLeaveAction(botAI); }
static Action* rtsc(PlayerbotAI* botAI) { return new RTSCAction(botAI); }
static Action* naxx_chat_shortcut(PlayerbotAI* ai) { return new NaxxChatShortcutAction(ai); }
static Action* bwl_chat_shortcut(PlayerbotAI* ai) { return new BwlChatShortcutAction(ai); }
static Action* tell_estimated_dps(PlayerbotAI* ai) { return new TellEstimatedDpsAction(ai); }
static Action* join(PlayerbotAI* ai) { return new JoinGroupAction(ai); }

View File

@ -127,7 +127,6 @@ public:
creators["guild leave"] = &ChatTriggerContext::guild_leave;
creators["rtsc"] = &ChatTriggerContext::rtsc;
creators["drink"] = &ChatTriggerContext::drink;
// creators["bwl"] = &ChatTriggerContext::bwl;
creators["dps"] = &ChatTriggerContext::dps;
creators["disperse"] = &ChatTriggerContext::disperse;
creators["calc"] = &ChatTriggerContext::calc;
@ -245,7 +244,6 @@ private:
static Trigger* guild_leave(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "guild leave"); }
static Trigger* rtsc(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "rtsc"); }
static Trigger* drink(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "drink"); }
// static Trigger* bwl(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "bwl"); }
static Trigger* dps(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "dps"); }
static Trigger* disperse(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "disperse"); }
static Trigger* calc(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "calc"); }

View File

@ -83,6 +83,8 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
new TriggerNode("target", { NextAction("tell target", relevance) }));
triggers.push_back(
new TriggerNode("ready", { NextAction("ready check", relevance) }));
triggers.push_back(
new TriggerNode("naxx", {NextAction("naxx chat shortcut", relevance)}));
triggers.push_back(
new TriggerNode("bwl", { NextAction("bwl chat shortcut", relevance) }));
triggers.push_back(

View File

@ -116,9 +116,9 @@ bool AttumenTheHuntsmanStackBehindAction::Execute(Event /*event*/)
float rearX = attumenMounted->GetPositionX() + std::cos(orientation) * distanceBehind;
float rearY = attumenMounted->GetPositionY() + std::sin(orientation) * distanceBehind;
if (bot->GetExactDist2d(rearX, rearY) > 1.0f)
if (bot->GetDistance2d(rearX, rearY) > 1.0f)
{
return MoveTo(KARAZHAN_MAP_ID, rearX, rearY, attumenMounted->GetPositionZ(), false, false, false, false,
return MoveTo(KARAZHAN_MAP_ID, rearX, rearY, bot->GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_FORCED, true, false);
}
@ -1178,7 +1178,7 @@ bool PrinceMalchezaarNonTankAvoidInfernalAction::Execute(Event /*event*/)
bot->AttackStop();
bot->InterruptNonMeleeSpells(true);
return MoveTo(KARAZHAN_MAP_ID, bestDestX, bestDestY, bestDestZ, false, false, false, false,
MovementPriority::MOVEMENT_FORCED, true, false);
MovementPriority::MOVEMENT_COMBAT, true, false);
}
}
@ -1244,7 +1244,7 @@ bool PrinceMalchezaarMainTankMovementAction::Execute(Event /*event*/)
{
bot->AttackStop();
return MoveTo(KARAZHAN_MAP_ID, bestDestX, bestDestY, bestDestZ, false, false, false, false,
MovementPriority::MOVEMENT_FORCED, true, false);
MovementPriority::MOVEMENT_COMBAT, true, true);
}
}

View File

@ -80,6 +80,19 @@ float AttumenTheHuntsmanWaitForDpsMultiplier::GetValue(Action* action)
return 1.0f;
}
// Disables co +disperse and co +tank face
float MaidenOfVirtueDisableCombatFormationMoveMultiplier::GetValue(Action* action)
{
if (!AI_VALUE2(Unit*, "find target", "maiden of virtue"))
return 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action) &&
!dynamic_cast<SetBehindTargetAction*>(action))
return 0.0f;
return 1.0f;
}
// The assist tank should stay on the boss to be 2nd on aggro and tank Hateful Bolts
float TheCuratorDisableTankAssistMultiplier::GetValue(Action* action)
{
@ -93,6 +106,19 @@ float TheCuratorDisableTankAssistMultiplier::GetValue(Action* action)
return 1.0f;
}
// Disables co +disperse and co +tank face
float TheCuratorDisableCombatFormationMoveMultiplier::GetValue(Action* action)
{
if (!AI_VALUE2(Unit*, "find target", "the curator"))
return 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action) &&
!dynamic_cast<SetBehindTargetAction*>(action))
return 0.0f;
return 1.0f;
}
// Save Bloodlust/Heroism for Evocation (100% increased damage)
float TheCuratorDelayBloodlustAndHeroismMultiplier::GetValue(Action* action)
{
@ -350,16 +376,10 @@ float NightbaneDisableMovementMultiplier::GetValue(Action* action)
if (dynamic_cast<CastBlinkBackAction*>(action) ||
dynamic_cast<CastDisengageAction*>(action) ||
dynamic_cast<FleeAction*>(action))
return 0.0f;
// Disable CombatFormationMoveAction for all bots except:
// (1) main tank and (2) only during the ground phase, other melee
if (botAI->IsRanged(bot) ||
(botAI->IsMelee(bot) && !botAI->IsMainTank(bot) &&
nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z))
dynamic_cast<FleeAction*>(action) ||
(dynamic_cast<CombatFormationMoveAction*>(action) &&
!dynamic_cast<SetBehindTargetAction*>(action)))
{
if (dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
}

View File

@ -27,6 +27,14 @@ public:
virtual float GetValue(Action* action);
};
class MaidenOfVirtueDisableCombatFormationMoveMultiplier : public Multiplier
{
public:
MaidenOfVirtueDisableCombatFormationMoveMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "maiden of virtue disable combat formation move multiplier") {}
virtual float GetValue(Action* action);
};
class TheCuratorDisableTankAssistMultiplier : public Multiplier
{
public:
@ -35,6 +43,14 @@ public:
virtual float GetValue(Action* action);
};
class TheCuratorDisableCombatFormationMoveMultiplier : public Multiplier
{
public:
TheCuratorDisableCombatFormationMoveMultiplier(
PlayerbotAI* botAI) : Multiplier(botAI, "the curator disable combat formation move multiplier") {}
virtual float GetValue(Action* action);
};
class TheCuratorDelayBloodlustAndHeroismMultiplier : public Multiplier
{
public:

View File

@ -146,7 +146,9 @@ void RaidKarazhanStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers
multipliers.push_back(new AttumenTheHuntsmanDisableTankAssistMultiplier(botAI));
multipliers.push_back(new AttumenTheHuntsmanStayStackedMultiplier(botAI));
multipliers.push_back(new AttumenTheHuntsmanWaitForDpsMultiplier(botAI));
multipliers.push_back(new MaidenOfVirtueDisableCombatFormationMoveMultiplier(botAI));
multipliers.push_back(new TheCuratorDisableTankAssistMultiplier(botAI));
multipliers.push_back(new TheCuratorDisableCombatFormationMoveMultiplier(botAI));
multipliers.push_back(new TheCuratorDelayBloodlustAndHeroismMultiplier(botAI));
multipliers.push_back(new ShadeOfAranArcaneExplosionDisableChargeMultiplier(botAI));
multipliers.push_back(new ShadeOfAranFlameWreathDisableMovementMultiplier(botAI));

View File

@ -62,7 +62,6 @@ namespace KarazhanHelpers
NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED = 16152,
// Terestian Illhoof
NPC_TERESTIAN_ILLHOOF = 15688,
NPC_DEMON_CHAINS = 17248,
NPC_KILREK = 17229,

View File

@ -0,0 +1,326 @@
#ifndef _PLAYERBOT_RAIDNAXXACTIONS_H
#define _PLAYERBOT_RAIDNAXXACTIONS_H
#include "Action.h"
#include "AttackAction.h"
#include "GenericActions.h"
#include "MovementActions.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "RaidNaxxBossHelper.h"
class GrobbulusGoBehindAction : public MovementAction
{
public:
GrobbulusGoBehindAction(PlayerbotAI* ai, float distance = 24.0f, float delta_angle = M_PI / 8)
: MovementAction(ai, "grobbulus go behind")
{
this->distance = distance;
this->delta_angle = delta_angle;
}
virtual bool Execute(Event event);
protected:
float distance, delta_angle;
};
class GrobbulusRotateAction : public RotateAroundTheCenterPointAction
{
public:
GrobbulusRotateAction(PlayerbotAI* botAI)
: RotateAroundTheCenterPointAction(botAI, "rotate grobbulus", 3281.23f, -3310.38f, 35.0f, 8, true, M_PI) {}
virtual bool isUseful() override
{
return RotateAroundTheCenterPointAction::isUseful() && botAI->IsMainTank(bot) &&
AI_VALUE2(bool, "has aggro", "boss target");
}
uint32 GetCurrWaypoint() override;
};
class GrobblulusMoveCenterAction : public MoveInsideAction
{
public:
GrobblulusMoveCenterAction(PlayerbotAI* ai) : MoveInsideAction(ai, 3281.23f, -3310.38f, 5.0f) {}
};
class GrobbulusMoveAwayAction : public MovementAction
{
public:
GrobbulusMoveAwayAction(PlayerbotAI* ai, float distance = 18.0f)
: MovementAction(ai, "grobbulus move away"), distance(distance)
{
}
bool Execute(Event event) override;
private:
float distance;
};
//class HeiganDanceAction : public MovementAction
//{
//public:
// HeiganDanceAction(PlayerbotAI* ai) : MovementAction(ai, "heigan dance")
// {
// this->last_eruption_ms = 0;
// this->platform_phase = false;
// ResetSafe();
// waypoints.push_back(std::make_pair(2794.88f, -3668.12f));
// waypoints.push_back(std::make_pair(2775.49f, -3674.43f));
// waypoints.push_back(std::make_pair(2762.30f, -3684.59f));
// waypoints.push_back(std::make_pair(2755.99f, -3703.96f));
// platform = std::make_pair(2794.26f, -3706.67f);
// }
//
//protected:
// bool CalculateSafe();
// void ResetSafe()
// {
// curr_safe = 0;
// curr_dir = 1;
// }
// void NextSafe()
// {
// curr_safe += curr_dir;
// if (curr_safe == 3 || curr_safe == 0)
// {
// curr_dir = -curr_dir;
// }
// }
// uint32 last_eruption_ms;
// bool platform_phase;
// uint32 curr_safe, curr_dir;
// std::vector<std::pair<float, float>> waypoints;
// std::pair<float, float> platform;
//};
//
//class HeiganDanceMeleeAction : public HeiganDanceAction
//{
//public:
// HeiganDanceMeleeAction(PlayerbotAI* ai) : HeiganDanceAction(ai) {}
// virtual bool Execute(Event event);
//};
//
//class HeiganDanceRangedAction : public HeiganDanceAction
//{
//public:
// HeiganDanceRangedAction(PlayerbotAI* ai) : HeiganDanceAction(ai) {}
// virtual bool Execute(Event event);
//};
class ThaddiusAttackNearestPetAction : public AttackAction
{
public:
ThaddiusAttackNearestPetAction(PlayerbotAI* ai) : AttackAction(ai, "thaddius attack nearest pet"), helper(ai) {}
virtual bool Execute(Event event);
virtual bool isUseful();
private:
ThaddiusBossHelper helper;
};
// class ThaddiusMeleeToPlaceAction : public MovementAction
// {
// public:
// ThaddiusMeleeToPlaceAction(PlayerbotAI* ai) : MovementAction(ai, "thaddius melee to place") {}
// virtual bool Execute(Event event);
// virtual bool isUseful();
// };
// class ThaddiusRangedToPlaceAction : public MovementAction
// {
// public:
// ThaddiusRangedToPlaceAction(PlayerbotAI* ai) : MovementAction(ai, "thaddius ranged to place") {}
// virtual bool Execute(Event event);
// virtual bool isUseful();
// };
class ThaddiusMoveToPlatformAction : public MovementAction
{
public:
ThaddiusMoveToPlatformAction(PlayerbotAI* ai) : MovementAction(ai, "thaddius move to platform") {}
virtual bool Execute(Event event);
virtual bool isUseful();
};
class ThaddiusMovePolarityAction : public MovementAction
{
public:
ThaddiusMovePolarityAction(PlayerbotAI* ai) : MovementAction(ai, "thaddius move polarity") {}
virtual bool Execute(Event event);
virtual bool isUseful();
};
class RazuviousUseObedienceCrystalAction : public MovementAction
{
public:
RazuviousUseObedienceCrystalAction(PlayerbotAI* ai)
: MovementAction(ai, "razuvious use obedience crystal"), helper(ai)
{
}
bool Execute(Event event) override;
private:
RazuviousBossHelper helper;
};
class RazuviousTargetAction : public AttackAction
{
public:
RazuviousTargetAction(PlayerbotAI* ai) : AttackAction(ai, "razuvious target"), helper(ai) {}
bool Execute(Event event) override;
private:
RazuviousBossHelper helper;
};
class HorsemanAttractAlternativelyAction : public AttackAction
{
public:
HorsemanAttractAlternativelyAction(PlayerbotAI* ai) : AttackAction(ai, "horseman attract alternatively"), helper(ai)
{
}
bool Execute(Event event) override;
protected:
FourhorsemanBossHelper helper;
};
class HorsemanAttactInOrderAction : public AttackAction
{
public:
HorsemanAttactInOrderAction(PlayerbotAI* ai) : AttackAction(ai, "horseman attact in order"), helper(ai) {}
bool Execute(Event event) override;
protected:
FourhorsemanBossHelper helper;
};
// class SapphironGroundMainTankPositionAction : public MovementAction
// {
// public:
// SapphironGroundMainTankPositionAction(PlayerbotAI* ai) : MovementAction(ai, "sapphiron ground main tank
// position") {} virtual bool Execute(Event event);
// };
class SapphironGroundPositionAction : public MovementAction
{
public:
SapphironGroundPositionAction(PlayerbotAI* ai) : MovementAction(ai, "sapphiron ground position"), helper(ai) {}
bool Execute(Event event) override;
protected:
SapphironBossHelper helper;
};
class SapphironFlightPositionAction : public MovementAction
{
public:
SapphironFlightPositionAction(PlayerbotAI* ai) : MovementAction(ai, "sapphiron flight position"), helper(ai) {}
bool Execute(Event event) override;
protected:
SapphironBossHelper helper;
bool MoveToNearestIcebolt();
};
// class SapphironAvoidChillAction : public MovementAction
// {
// public:
// SapphironAvoidChillAction(PlayerbotAI* ai) : MovementAction(ai, "sapphiron avoid chill") {}
// virtual bool Execute(Event event);
// };
class KelthuzadChooseTargetAction : public AttackAction
{
public:
KelthuzadChooseTargetAction(PlayerbotAI* ai) : AttackAction(ai, "kel'thuzad choose target"), helper(ai) {}
virtual bool Execute(Event event);
private:
KelthuzadBossHelper helper;
};
class KelthuzadPositionAction : public MovementAction
{
public:
KelthuzadPositionAction(PlayerbotAI* ai) : MovementAction(ai, "kel'thuzad position"), helper(ai) {}
virtual bool Execute(Event event);
private:
KelthuzadBossHelper helper;
};
class AnubrekhanChooseTargetAction : public AttackAction
{
public:
AnubrekhanChooseTargetAction(PlayerbotAI* ai) : AttackAction(ai, "anub'rekhan choose target") {}
bool Execute(Event event) override;
};
class AnubrekhanPositionAction : public RotateAroundTheCenterPointAction
{
public:
AnubrekhanPositionAction(PlayerbotAI* ai)
: RotateAroundTheCenterPointAction(ai, "anub'rekhan position", 3272.49f, -3476.27f, 45.0f, 16) {}
bool Execute(Event event) override;
};
class GluthChooseTargetAction : public AttackAction
{
public:
GluthChooseTargetAction(PlayerbotAI* ai) : AttackAction(ai, "gluth choose target"), helper(ai) {}
bool Execute(Event event) override;
private:
GluthBossHelper helper;
};
class GluthPositionAction : public RotateAroundTheCenterPointAction
{
public:
GluthPositionAction(PlayerbotAI* ai)
: RotateAroundTheCenterPointAction(ai, "gluth position", 3293.61f, -3149.01f, 12.0f, 12), helper(ai) {}
bool Execute(Event event) override;
private:
GluthBossHelper helper;
};
class GluthSlowdownAction : public Action
{
public:
GluthSlowdownAction(PlayerbotAI* ai) : Action(ai, "gluth slowdown"), helper(ai) {}
bool Execute(Event event) override;
private:
GluthBossHelper helper;
};
class LoathebPositionAction : public MovementAction
{
public:
LoathebPositionAction(PlayerbotAI* ai) : MovementAction(ai, "loatheb position"), helper(ai) {}
virtual bool Execute(Event event);
private:
LoathebBossHelper helper;
};
class LoathebChooseTargetAction : public AttackAction
{
public:
LoathebChooseTargetAction(PlayerbotAI* ai) : AttackAction(ai, "loatheb choose target"), helper(ai) {}
virtual bool Execute(Event event);
private:
LoathebBossHelper helper;
};
//class PatchwerkRangedPositionAction : public MovementAction
//{
//public:
// PatchwerkRangedPositionAction(PlayerbotAI* ai) : MovementAction(ai, "patchwerk ranged position") {}
// bool Execute(Event event) override;
//};
#endif

View File

@ -0,0 +1,81 @@
#include "RaidNaxxActions.h"
#include "ObjectGuid.h"
#include "Playerbots.h"
bool AnubrekhanChooseTargetAction::Execute(Event /*event*/)
{
GuidVector attackers = context->GetValue<GuidVector>("attackers")->Get();
Unit* target = nullptr;
Unit* target_boss = nullptr;
std::vector<Unit*> target_guards;
for (ObjectGuid const guid : attackers)
{
Unit* unit = botAI->GetUnit(guid);
if (!unit)
continue;
if (botAI->EqualLowercaseName(unit->GetName(), "crypt guard"))
target_guards.push_back(unit);
if (botAI->EqualLowercaseName(unit->GetName(), "anub'rekhan"))
target_boss = unit;
}
if (botAI->IsMainTank(bot))
target = target_boss;
else
{
if (target_guards.size() == 0)
target = target_boss;
else
{
if (botAI->IsAssistTank(bot))
{
for (Unit* t : target_guards)
{
if (target == nullptr || (target->GetVictim() && target->GetVictim()->ToPlayer() &&
botAI->IsTank(target->GetVictim()->ToPlayer())))
target = t;
}
}
else
{
for (Unit* t : target_guards)
{
if (target == nullptr || target->GetHealthPct() > t->GetHealthPct())
target = t;
}
}
}
}
if (context->GetValue<Unit*>("current target")->Get() == target)
return false;
return Attack(target);
}
bool AnubrekhanPositionAction::Execute(Event /*event*/)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "anub'rekhan");
if (!boss)
return false;
bool inPhase = botAI->HasAura("locust swarm", boss) || boss->GetCurrentSpell(CURRENT_GENERIC_SPELL);
if (inPhase)
{
if (botAI->IsMainTank(bot))
{
uint32 nearest = FindNearestWaypoint();
uint32 next_point;
if (inPhase)
next_point = (nearest + 1) % intervals;
else
next_point = nearest;
return MoveTo(bot->GetMapId(), waypoints[next_point].first, waypoints[next_point].second, bot->GetPositionZ(), false, false,
false, false, MovementPriority::MOVEMENT_COMBAT);
}
else
return MoveInside(533, 3272.49f, -3476.27f, bot->GetPositionZ(), 3.0f, MovementPriority::MOVEMENT_COMBAT);
}
return false;
}

View File

@ -0,0 +1,3 @@
#include "RaidNaxxActions.h"
// Reserved for Faerlina-specific actions.

View File

@ -0,0 +1,59 @@
#include "RaidNaxxActions.h"
#include "Playerbots.h"
bool HorsemanAttractAlternativelyAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
helper.CalculatePosToGo(bot);
auto [posX, posY] = helper.CurrentAttractPos();
if (MoveTo(bot->GetMapId(), posX, posY, helper.posZ, false, false, false, false, MovementPriority::MOVEMENT_COMBAT))
return true;
Unit* attackTarget = helper.CurrentAttackTarget();
if (context->GetValue<Unit*>("current target")->Get() != attackTarget)
return Attack(attackTarget);
return false;
}
bool HorsemanAttactInOrderAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
Unit* target = nullptr;
Unit* thane = AI_VALUE2(Unit*, "find target", "thane korth'azz");
Unit* lady = AI_VALUE2(Unit*, "find target", "lady blaumeux");
Unit* sir = AI_VALUE2(Unit*, "find target", "sir zeliek");
Unit* fourth = AI_VALUE2(Unit*, "find target", "baron rivendare");
if (!fourth)
fourth = AI_VALUE2(Unit*, "find target", "highlord mograine");
std::vector<Unit*> attack_order;
if (botAI->IsAssistTank(bot))
attack_order = {fourth, thane, lady, sir};
else
attack_order = {thane, fourth, lady, sir};
for (Unit* t : attack_order)
{
if (t && t->IsAlive())
{
target = t;
break;
}
}
if (target)
{
if (context->GetValue<Unit*>("current target")->Get() == target && botAI->GetState() == BOT_STATE_COMBAT)
return false;
if (!bot->IsWithinLOSInMap(target))
return MoveNear(target, 22.0f, MovementPriority::MOVEMENT_COMBAT);
return Attack(target);
}
return false;
}

View File

@ -0,0 +1,178 @@
#include "RaidNaxxActions.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
#include "SharedDefines.h"
bool GluthChooseTargetAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
GuidVector attackers = context->GetValue<GuidVector>("possible targets")->Get();
Unit* target = nullptr;
Unit* target_boss = nullptr;
std::vector<Unit*> target_zombies;
for (GuidVector::iterator i = attackers.begin(); i != attackers.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (!unit)
continue;
if (!unit->IsAlive())
continue;
if (botAI->EqualLowercaseName(unit->GetName(), "zombie chow"))
target_zombies.push_back(unit);
if (botAI->EqualLowercaseName(unit->GetName(), "gluth"))
target_boss = unit;
}
if (botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0))
target = target_boss;
else if (botAI->IsAssistTankOfIndex(bot, 1))
{
for (Unit* t : target_zombies)
{
if (t->GetHealthPct() > helper.decimatedZombiePct && t->GetVictim() != bot && t->GetDistance2d(bot) <= 10.0f)
{
if (!target || t->GetDistance2d(bot) < target->GetDistance2d(bot))
target = t;
}
}
}
else if (botAI->GetClassIndex(bot, CLASS_HUNTER) == 0 || botAI->GetClassIndex(bot, CLASS_HUNTER) == 1)
{
// prevent zombie go straight to gluth
for (Unit* t : target_zombies)
{
if (t->GetHealthPct() > helper.decimatedZombiePct && t->GetVictim() == target_boss &&
t->GetDistance2d(bot) <= sPlayerbotAIConfig.spellDistance)
{
if (!target || t->GetDistance2d(bot) < target->GetDistance2d(bot))
target = t;
}
}
if (!target)
target = target_boss;
}
else
{
for (Unit* t : target_zombies)
{
if (t->GetHealthPct() <= helper.decimatedZombiePct)
{
if (target == nullptr ||
target->GetDistance2d(helper.mainTankPos25.first, helper.mainTankPos25.second) >
t->GetDistance2d(helper.mainTankPos25.first, helper.mainTankPos25.second))
target = t;
}
}
if (target == nullptr)
target = target_boss;
}
if (!target || context->GetValue<Unit*>("current target")->Get() == target)
return false;
if (target_boss && target == target_boss)
return Attack(target, true);
return Attack(target, false);
// return Attack(target);
}
bool GluthPositionAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
bool raid25 = bot->GetRaidDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL;
if (botAI->IsMainTank(bot) || botAI->IsAssistTankOfIndex(bot, 0))
{
if (AI_VALUE2(bool, "has aggro", "boss target"))
{
if (raid25)
{
if (MoveTo(NAXX_MAP_ID, helper.mainTankPos25.first, helper.mainTankPos25.second, bot->GetPositionZ(), false, false, false,
false, MovementPriority::MOVEMENT_COMBAT))
return true;
return MoveInside(NAXX_MAP_ID, helper.mainTankPos25.first, helper.mainTankPos25.second, bot->GetPositionZ(), 2.0f,
MovementPriority::MOVEMENT_COMBAT);
}
else
{
if (MoveTo(NAXX_MAP_ID, helper.mainTankPos10.first, helper.mainTankPos10.second, bot->GetPositionZ(), false, false, false,
false, MovementPriority::MOVEMENT_COMBAT))
return true;
return MoveInside(NAXX_MAP_ID, helper.mainTankPos10.first, helper.mainTankPos10.second, bot->GetPositionZ(), 2.0f,
MovementPriority::MOVEMENT_COMBAT);
}
}
}
else if (botAI->IsAssistTankOfIndex(bot, 1))
{
if (helper.BeforeDecimate())
{
if (MoveTo(bot->GetMapId(), helper.beforeDecimatePos.first, helper.beforeDecimatePos.second, bot->GetPositionZ(), false, false,
false, false, MovementPriority::MOVEMENT_COMBAT))
return true;
return MoveInside(bot->GetMapId(), helper.beforeDecimatePos.first, helper.beforeDecimatePos.second, bot->GetPositionZ(), 2.0f,
MovementPriority::MOVEMENT_COMBAT);
}
else
{
if (AI_VALUE2(bool, "has aggro", "current target"))
{
uint32 nearest = FindNearestWaypoint();
uint32 next_point = (nearest + 1) % intervals;
return MoveTo(bot->GetMapId(), waypoints[next_point].first, waypoints[next_point].second, bot->GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
}
else if (botAI->IsRangedDps(bot))
{
if (raid25)
{
if (botAI->GetClassIndex(bot, CLASS_HUNTER) == 0)
return MoveInside(NAXX_MAP_ID, helper.leftSlowDownPos.first, helper.leftSlowDownPos.second, bot->GetPositionZ(), 0.0f,
MovementPriority::MOVEMENT_COMBAT);
if (botAI->GetClassIndex(bot, CLASS_HUNTER) == 1)
return MoveInside(NAXX_MAP_ID, helper.rightSlowDownPos.first, helper.rightSlowDownPos.second, bot->GetPositionZ(), 0.0f,
MovementPriority::MOVEMENT_COMBAT);
}
return MoveInside(NAXX_MAP_ID, helper.rangedPos.first, helper.rangedPos.second, bot->GetPositionZ(), 3.0f,
MovementPriority::MOVEMENT_COMBAT);
}
else if (botAI->IsHeal(bot))
return MoveInside(NAXX_MAP_ID, helper.healPos.first, helper.healPos.second, bot->GetPositionZ(), 0.0f,
MovementPriority::MOVEMENT_COMBAT);
return false;
}
bool GluthSlowdownAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
bool raid25 = bot->GetRaidDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL;
if (!raid25)
return false;
if (helper.JustStartCombat())
return false;
switch (bot->getClass())
{
case CLASS_HUNTER:
return botAI->CastSpell("frost trap", bot);
break;
default:
break;
}
return false;
}

View File

@ -0,0 +1,3 @@
#include "RaidNaxxActions.h"
// Reserved for Gothik-specific actions.

View File

@ -0,0 +1,46 @@
#include "RaidNaxxActions.h"
#include "Playerbots.h"
bool GrobbulusGoBehindAction::Execute(Event /*event*/)
{
Unit* boss = AI_VALUE(Unit*, "boss target");
if (!boss)
return false;
// Position* pos = boss->GetPosition();
float orientation = boss->GetOrientation() + M_PI + delta_angle;
float x = boss->GetPositionX();
float y = boss->GetPositionY();
float z = boss->GetPositionZ();
float rx = x + cos(orientation) * distance;
float ry = y + sin(orientation) * distance;
return MoveTo(bot->GetMapId(), rx, ry, z, false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
bool GrobbulusMoveAwayAction::Execute(Event /*event*/)
{
Unit* boss = AI_VALUE(Unit*, "boss target");
if (!boss)
return false;
const float currentDistance = bot->GetExactDist2d(boss);
if (currentDistance >= distance)
return false;
const float angle = boss->GetAngle(bot);
const float x = boss->GetPositionX() + cos(angle) * distance;
const float y = boss->GetPositionY() + sin(angle) * distance;
const float z = bot->GetPositionZ();
return MoveTo(bot->GetMapId(), x, y, z, false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
uint32 GrobbulusRotateAction::GetCurrWaypoint()
{
uint32 current = FindNearestWaypoint();
if (clockwise)
return (current + 1) % intervals;
return (current + intervals - 1) % intervals;
}

View File

@ -0,0 +1,77 @@
#include "Playerbots.h"
#include "RaidNaxxActions.h"
#include "RaidNaxxSpellIds.h"
#include "Spell.h"
#include "Timer.h"
//bool HeiganDanceAction::CalculateSafe()
//{
// Unit* boss = AI_VALUE2(Unit*, "find target", "heigan the unclean");
// if (!boss)
// {
// return false;
// }
// uint32 now = getMSTime();
// platform_phase = boss->IsWithinDist2d(platform.first, platform.second, 10.0f);
// if (last_eruption_ms != 0 && now - last_eruption_ms > 15000)
// {
// ResetSafe();
// }
// if (boss->HasUnitState(UNIT_STATE_CASTING))
// {
// Spell* spell = boss->GetCurrentSpell(CURRENT_GENERIC_SPELL);
// if (!spell)
// {
// spell = boss->GetCurrentSpell(CURRENT_CHANNELED_SPELL);
// }
// if (spell)
// {
// SpellInfo const* info = spell->GetSpellInfo();
// bool isEruption = NaxxSpellIds::MatchesAnySpellId(info, {NaxxSpellIds::Eruption10});
// if (!isEruption && info && info->SpellName[LOCALE_enUS])
// {
// // Fallback to name for custom spell data.
// isEruption = botAI->EqualLowercaseName(info->SpellName[LOCALE_enUS], "eruption");
// }
// if (isEruption)
// {
// if (last_eruption_ms == 0 || now - last_eruption_ms > 500)
// {
// NextSafe();
// }
// last_eruption_ms = now;
// }
// }
// }
// return true;
//}
//
//bool HeiganDanceMeleeAction::Execute(Event event)
//{
// CalculateSafe();
// if (!platform_phase && botAI->IsMainTank(bot) && !AI_VALUE2(bool, "has aggro", "boss target"))
// {
// return false;
// }
// assert(curr_safe >= 0 && curr_safe <= 3);
// return MoveInside(bot->GetMapId(), waypoints[curr_safe].first, waypoints[curr_safe].second, bot->GetPositionZ(),
// botAI->IsMainTank(bot) ? 0 : 0, MovementPriority::MOVEMENT_COMBAT);
//}
//
//bool HeiganDanceRangedAction::Execute(Event event)
//{
// CalculateSafe();
// if (!platform_phase)
// {
// if (MoveTo(bot->GetMapId(), platform.first, platform.second, 276.54f, false, false, false, false,
// MovementPriority::MOVEMENT_COMBAT))
// {
// return true;
// }
// return MoveInside(bot->GetMapId(), platform.first, platform.second, 276.54f, 2.0f,
// MovementPriority::MOVEMENT_COMBAT);
// }
// botAI->InterruptSpell();
// return MoveInside(bot->GetMapId(), waypoints[curr_safe].first, waypoints[curr_safe].second, bot->GetPositionZ(), 0,
// MovementPriority::MOVEMENT_COMBAT);
//}

View File

@ -0,0 +1,177 @@
#include "RaidNaxxActions.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
bool KelthuzadChooseTargetAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
GuidVector attackers = context->GetValue<GuidVector>("attackers")->Get();
Unit* target = nullptr;
Unit *target_soldier = nullptr, *target_weaver = nullptr, *target_abomination = nullptr, *target_kelthuzad = nullptr,
*target_guardian = nullptr;
for (auto i = attackers.begin(); i != attackers.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (!unit)
continue;
if (botAI->EqualLowercaseName(unit->GetName(), "guardian of icecrown"))
{
if (!target_guardian)
target_guardian = unit;
else if (unit->GetVictim() && target_guardian->GetVictim() && unit->GetVictim()->ToPlayer() &&
target_guardian->GetVictim()->ToPlayer() && !botAI->IsAssistTank(unit->GetVictim()->ToPlayer()) &&
botAI->IsAssistTank(target_guardian->GetVictim()->ToPlayer()))
{
target_guardian = unit;
}
else if (unit->GetVictim() && target_guardian->GetVictim() && unit->GetVictim()->ToPlayer() &&
target_guardian->GetVictim()->ToPlayer() && !botAI->IsAssistTank(unit->GetVictim()->ToPlayer()) &&
!botAI->IsAssistTank(target_guardian->GetVictim()->ToPlayer()) &&
target_guardian->GetDistance2d(helper.center.first, helper.center.second) >
bot->GetDistance2d(unit))
{
target_guardian = unit;
}
}
if (unit->GetDistance2d(helper.center.first, helper.center.second) > 30.0f)
continue;
if (bot->GetDistance2d(unit) > sPlayerbotAIConfig.spellDistance)
continue;
if (botAI->EqualLowercaseName(unit->GetName(), "unstoppable abomination"))
{
if (target_abomination == nullptr ||
target_abomination->GetDistance2d(helper.center.first, helper.center.second) >
unit->GetDistance2d(helper.center.first, helper.center.second))
{
target_abomination = unit;
}
}
if (botAI->EqualLowercaseName(unit->GetName(), "soldier of the frozen wastes"))
{
if (target_soldier == nullptr ||
target_soldier->GetDistance2d(helper.center.first, helper.center.second) >
unit->GetDistance2d(helper.center.first, helper.center.second))
{
target_soldier = unit;
}
}
if (botAI->EqualLowercaseName(unit->GetName(), "soul weaver"))
{
if (target_weaver == nullptr || target_weaver->GetDistance2d(helper.center.first, helper.center.second) >
unit->GetDistance2d(helper.center.first, helper.center.second))
target_weaver = unit;
}
if (botAI->EqualLowercaseName(unit->GetName(), "kel'thuzad"))
target_kelthuzad = unit;
}
std::vector<Unit*> targets;
if (botAI->IsRanged(bot))
{
if (botAI->GetRangedDpsIndex(bot) <= 1)
targets = {target_soldier, target_weaver, target_abomination, target_kelthuzad};
else
targets = {target_weaver, target_soldier, target_abomination, target_kelthuzad};
}
else if (botAI->IsAssistTank(bot))
targets = {target_abomination, target_guardian, target_kelthuzad};
else
targets = {target_abomination, target_kelthuzad};
for (Unit* t : targets)
{
if (t)
{
target = t;
break;
}
}
if (context->GetValue<Unit*>("current target")->Get() == target)
return false;
if (target_kelthuzad && target == target_kelthuzad)
return Attack(target, true);
return Attack(target, false);
}
bool KelthuzadPositionAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
if (helper.IsPhaseOne())
{
if (AI_VALUE(Unit*, "current target") == nullptr)
return MoveInside(NAXX_MAP_ID, helper.center.first, helper.center.second, bot->GetPositionZ(), 3.0f,
MovementPriority::MOVEMENT_COMBAT);
}
else if (helper.IsPhaseTwo())
{
Unit* shadow_fissure = helper.GetAnyShadowFissure();
if (!shadow_fissure || !bot->IsWithinDistInMap(shadow_fissure, 10.0f))
{
float distance, angle;
if (botAI->IsMainTank(bot))
{
if (AI_VALUE2(bool, "has aggro", "current target"))
return MoveTo(NAXX_MAP_ID, helper.tank_pos.first, helper.tank_pos.second, bot->GetPositionZ(), false, false, false,
false, MovementPriority::MOVEMENT_COMBAT);
else
return false;
}
else if (botAI->IsRanged(bot))
{
uint32 index = botAI->GetRangedIndex(bot);
if (index < 8)
{
distance = 20.0f;
angle = index * M_PI / 4;
}
else
{
distance = 32.0f;
angle = (index - 8) * M_PI / 4;
}
float dx, dy;
dx = helper.center.first + cos(angle) * distance;
dy = helper.center.second + sin(angle) * distance;
return MoveTo(NAXX_MAP_ID, dx, dy, bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
else if (botAI->IsTank(bot))
{
Unit* cur_tar = AI_VALUE(Unit*, "current target");
if (cur_tar && cur_tar->GetVictim() && cur_tar->GetVictim()->ToPlayer() &&
botAI->EqualLowercaseName(cur_tar->GetName(), "guardian of icecrown") &&
botAI->IsAssistTank(cur_tar->GetVictim()->ToPlayer()))
{
return MoveTo(NAXX_MAP_ID, helper.assist_tank_pos.first, helper.assist_tank_pos.second, bot->GetPositionZ(),
false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
else
return false;
}
}
else
{
float dx, dy;
float angle;
if (!botAI->IsRanged(bot))
angle = shadow_fissure->GetAngle(helper.center.first, helper.center.second);
else
angle = bot->GetAngle(shadow_fissure) + M_PI;
dx = shadow_fissure->GetPositionX() + cos(angle) * 10.0f;
dy = shadow_fissure->GetPositionY() + sin(angle) * 10.0f;
return MoveTo(NAXX_MAP_ID, dx, dy, bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
}
return false;
}

View File

@ -0,0 +1,55 @@
#include "RaidNaxxActions.h"
#include "Playerbots.h"
bool LoathebPositionAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
if (botAI->IsTank(bot))
{
if (AI_VALUE2(bool, "has aggro", "boss target"))
return MoveTo(533, helper.mainTankPos.first, helper.mainTankPos.second, bot->GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT);
}
else if (botAI->IsRanged(bot))
return MoveInside(533, helper.rangePos.first, helper.rangePos.second, bot->GetPositionZ(), 1.0f,
MovementPriority::MOVEMENT_COMBAT);
return false;
}
bool LoathebChooseTargetAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
GuidVector attackers = context->GetValue<GuidVector>("attackers")->Get();
Unit* target = nullptr;
Unit* target_boss = nullptr;
Unit* target_spore = nullptr;
for (auto i = attackers.begin(); i != attackers.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (!unit)
continue;
if (!unit->IsAlive())
continue;
if (botAI->EqualLowercaseName(unit->GetName(), "spore"))
target_spore = unit;
if (botAI->EqualLowercaseName(unit->GetName(), "loatheb"))
target_boss = unit;
}
if (target_spore && bot->GetDistance2d(target_spore) <= 1.0f)
target = target_spore;
else
target = target_boss;
if (!target || context->GetValue<Unit*>("current target")->Get() == target)
return false;
return Attack(target);
}

View File

@ -0,0 +1,3 @@
#include "RaidNaxxActions.h"
// Reserved for Maexxna-specific actions.

View File

@ -0,0 +1,3 @@
#include "RaidNaxxActions.h"
// Reserved for Noth-specific actions.

View File

@ -0,0 +1,31 @@
#include "RaidNaxxActions.h"
#include <algorithm>
#include <cmath>
//bool PatchwerkRangedPositionAction::Execute(Event event)
//{
// Unit* boss = AI_VALUE2(Unit*, "find target", "patchwerk");
// if (!boss)
// return false;
//
// constexpr float minDistance = 12.0f;
// constexpr float maxDistance = 15.0f;
// const float distance = bot->GetExactDist2d(boss);
//
// if (distance >= minDistance && distance <= maxDistance)
// return false;
//
// const float desiredDistance = std::clamp(distance, minDistance, maxDistance);
// float angle = boss->GetAngle(bot);
//
// if (distance < 0.1f)
// angle = boss->GetOrientation();
//
// const float x = boss->GetPositionX() + std::cos(angle) * desiredDistance;
// const float y = boss->GetPositionY() + std::sin(angle) * desiredDistance;
// const float z = bot->GetPositionZ();
//
// return MoveTo(boss->GetMapId(), x, y, z, false, false, false, false, MovementPriority::MOVEMENT_COMBAT, true,
// false);
//}

View File

@ -0,0 +1,147 @@
#include "RaidNaxxActions.h"
#include "ObjectGuid.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
#include "SharedDefines.h"
bool RazuviousUseObedienceCrystalAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
// bot->GetCharm
if (Unit* charm = bot->GetCharm())
{
Unit* target = AI_VALUE2(Unit*, "find target", "instructor razuvious");
if (!target)
return false;
if (charm->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_ACTIVE) == NULL_MOTION_TYPE)
{
charm->GetMotionMaster()->Clear();
charm->GetMotionMaster()->MoveChase(target);
charm->GetAI()->AttackStart(target);
}
Aura* forceObedience = botAI->GetAura("force obedience", charm);
uint32 duration_time;
if (!forceObedience)
{
forceObedience = botAI->GetAura("mind control", charm);
duration_time = 60000;
}
else
duration_time = 90000;
if (!forceObedience)
return false;
if (charm->GetDistance(target) <= 0.51f)
{
// taunt
bool tauntUseful = true;
if (forceObedience->GetDuration() <= (duration_time - 5000))
{
if (target->GetVictim() && botAI->HasAura(29061, target->GetVictim()))
tauntUseful = false;
if (forceObedience->GetDuration() <= 3000)
tauntUseful = false;
}
if (forceObedience->GetDuration() >= (duration_time - 500))
tauntUseful = false;
if (tauntUseful && !charm->HasSpellCooldown(29060))
{
// shield
if (!charm->HasSpellCooldown(29061))
{
charm->CastSpell(charm, 29061, true);
charm->AddSpellCooldown(29061, 0, 30 * 1000);
}
charm->CastSpell(target, 29060, true);
charm->AddSpellCooldown(29060, 0, 20 * 1000);
}
// strike
if (!charm->HasSpellCooldown(61696))
{
charm->CastSpell(target, 61696, true);
charm->AddSpellCooldown(61696, 0, 4 * 1000);
}
}
}
else
{
Difficulty diff = bot->GetRaidDifficulty();
if (diff == RAID_DIFFICULTY_10MAN_NORMAL)
{
GuidVector npcs = AI_VALUE(GuidVector, "nearest npcs");
for (auto i = npcs.begin(); i != npcs.end(); i++)
{
Creature* unit = botAI->GetCreature(*i);
if (!unit)
continue;
if (botAI->IsMainTank(bot) && unit->GetSpawnId() != 128352)
continue;
if (!botAI->IsMainTank(bot) && unit->GetSpawnId() != 128353)
continue;
if (MoveTo(unit, 0.0f, MovementPriority::MOVEMENT_COMBAT))
return true;
Creature* creature = bot->GetNPCIfCanInteractWith(*i, UNIT_NPC_FLAG_SPELLCLICK);
if (!creature)
continue;
creature->HandleSpellClick(bot);
return true;
}
}
else
{
GuidVector attackers = context->GetValue<GuidVector>("attackers")->Get();
Unit* target = nullptr;
for (auto i = attackers.begin(); i != attackers.end(); ++i)
{
Unit* unit = botAI->GetUnit(*i);
if (!unit)
continue;
if (botAI->EqualLowercaseName(unit->GetName(), "death knight understudy"))
{
target = unit;
break;
}
}
if (target)
{
if (bot->GetDistance2d(target) > sPlayerbotAIConfig.spellDistance)
return MoveNear(target, sPlayerbotAIConfig.spellDistance, MovementPriority::MOVEMENT_COMBAT);
else
return botAI->CastSpell("mind control", target);
}
}
}
return false;
}
bool RazuviousTargetAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
Unit* razuvious = AI_VALUE2(Unit*, "find target", "instructor razuvious");
Unit* understudy = AI_VALUE2(Unit*, "find target", "death knight understudy");
Unit* target = nullptr;
if (botAI->IsTank(bot))
target = understudy;
else
target = razuvious;
if (AI_VALUE(Unit*, "current target") == target)
return false;
return Attack(target);
}

View File

@ -0,0 +1,104 @@
#include "RaidNaxxActions.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
#include "RaidNaxxBossHelper.h"
#include "RaidNaxxSpellIds.h"
bool SapphironGroundPositionAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
if (botAI->IsMainTank(bot))
{
if (AI_VALUE2(bool, "has aggro", "current target"))
return MoveTo(NAXX_MAP_ID, helper.mainTankPos.first, helper.mainTankPos.second, helper.GENERIC_HEIGHT, false, false, false,
false, MovementPriority::MOVEMENT_COMBAT);
return false;
}
if (helper.JustLanded())
{
uint32 index = botAI->GetGroupSlotIndex(bot);
float start_angle = 0.85 * M_PI;
float offset_angle = M_PI * 0.02 * index;
float angle = start_angle + offset_angle;
float distance;
if (botAI->IsRanged(bot))
distance = 35.0f;
else if (botAI->IsHeal(bot))
distance = 30.0f;
else
distance = 5.0f;
float posX = helper.center.first + cos(angle) * distance;
float posY = helper.center.second + sin(angle) * distance;
if (MoveTo(NAXX_MAP_ID, posX, posY, helper.GENERIC_HEIGHT, false, false, false, false, MovementPriority::MOVEMENT_COMBAT))
return true;
return MoveInside(NAXX_MAP_ID, posX, posY, helper.GENERIC_HEIGHT, 2.0f, MovementPriority::MOVEMENT_COMBAT);
}
else
{
std::vector<float> dest;
if (helper.FindPosToAvoidChill(dest))
return MoveTo(NAXX_MAP_ID, dest[0], dest[1], dest[2], false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
return false;
}
bool SapphironFlightPositionAction::Execute(Event /*event*/)
{
if (!helper.UpdateBossAI())
return false;
if (helper.WaitForExplosion())
return MoveToNearestIcebolt();
else
{
std::vector<float> dest;
if (helper.FindPosToAvoidChill(dest))
return MoveTo(NAXX_MAP_ID, dest[0], dest[1], dest[2], false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
return false;
}
bool SapphironFlightPositionAction::MoveToNearestIcebolt()
{
Group* group = bot->GetGroup();
if (!group)
return false;
Group::MemberSlotList const& slots = group->GetMemberSlots();
Player* playerWithIcebolt = nullptr;
float minDistance;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (NaxxSpellIds::HasAnyAura(botAI, member, {NaxxSpellIds::Icebolt10, NaxxSpellIds::Icebolt25}) ||
botAI->HasAura("icebolt", member, false, false, -1, true))
{
if (!playerWithIcebolt || minDistance > bot->GetDistance(member))
{
playerWithIcebolt = member;
minDistance = bot->GetDistance(member);
}
}
}
if (playerWithIcebolt)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "sapphiron");
if (boss)
{
float angle = boss->GetAngle(playerWithIcebolt);
float posX = playerWithIcebolt->GetPositionX() + cos(angle) * 3.0f;
float posY = playerWithIcebolt->GetPositionY() + sin(angle) * 3.0f;
if (MoveTo(NAXX_MAP_ID, posX, posY, helper.GENERIC_HEIGHT, false, false, false, false, MovementPriority::MOVEMENT_COMBAT))
return true;
return MoveNear(playerWithIcebolt, 3.0f, MovementPriority::MOVEMENT_COMBAT);
}
}
return false;
}

View File

@ -0,0 +1,18 @@
#include "RaidNaxxActions.h"
uint32 RotateAroundTheCenterPointAction::FindNearestWaypoint()
{
float minDistance = 0;
int ret = -1;
for (int i = 0; i < intervals; i++)
{
float w_x = waypoints[i].first, w_y = waypoints[i].second;
float dis = bot->GetDistance2d(w_x, w_y);
if (ret == -1 || dis < minDistance)
{
ret = i;
minDistance = dis;
}
}
return ret;
}

View File

@ -0,0 +1,134 @@
#include "RaidNaxxActions.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
#include "RaidNaxxSpellIds.h"
bool ThaddiusAttackNearestPetAction::isUseful()
{
if (!helper.UpdateBossAI())
return false;
if (!helper.IsPhasePet())
return false;
Unit* target = helper.GetNearestPet();
if (!bot->IsWithinDistInMap(target, 50.0f))
return false;
return true;
}
bool ThaddiusAttackNearestPetAction::Execute(Event /*event*/)
{
Unit* target = helper.GetNearestPet();
if (!bot->IsWithinLOSInMap(target))
return MoveTo(target, 0, MovementPriority::MOVEMENT_COMBAT);
if (AI_VALUE(Unit*, "current target") != target)
return Attack(target);
if (botAI->IsTank(bot) && AI_VALUE2(bool, "has aggro", "current target"))
{
std::pair<float, float> posForTank = helper.PetPhaseGetPosForTank();
return MoveTo(533, posForTank.first, posForTank.second, helper.tankPosZ, false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
if (botAI->IsRanged(bot))
{
std::pair<float, float> posForRanged = helper.PetPhaseGetPosForRanged();
return MoveTo(533, posForRanged.first, posForRanged.second, helper.tankPosZ, false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}
return false;
}
bool ThaddiusMoveToPlatformAction::isUseful() { return true; }
bool ThaddiusMoveToPlatformAction::Execute(Event /*event*/)
{
std::vector<std::pair<float, float>> position = {
// high left
{3462.99f, -2918.90f},
// high right
{3520.65f, -2976.51f},
// low left
{3471.36f, -2910.65f},
// low right
{3528.80f, -2967.04f},
// center
{3512.19f, -2928.58f},
};
float high_z = 312.00f, low_z = 304.02f;
bool is_left = bot->GetDistance2d(position[0].first, position[0].second) <
bot->GetDistance2d(position[1].first, position[1].second);
if (bot->GetPositionZ() >= (high_z - 3.0f))
{
if (is_left)
{
if (!MoveTo(bot->GetMapId(), position[0].first, position[0].second, high_z, false, false, false, false, MovementPriority::MOVEMENT_COMBAT))
{
float distance = bot->GetExactDist2d(position[0].first, position[0].second);
if (distance < sPlayerbotAIConfig.contactDistance)
JumpTo(bot->GetMapId(), position[2].first, position[2].second, low_z, MovementPriority::MOVEMENT_COMBAT);
// bot->TeleportTo(bot->GetMapId(), position[2].first, position[2].second, low_z, bot->GetOrientation());
}
}
else
{
if (!MoveTo(bot->GetMapId(), position[1].first, position[1].second, high_z, false, false, false, false, MovementPriority::MOVEMENT_COMBAT))
{
float distance = bot->GetExactDist2d(position[1].first, position[1].second);
if (distance < sPlayerbotAIConfig.contactDistance)
JumpTo(bot->GetMapId(), position[3].first, position[3].second, low_z, MovementPriority::MOVEMENT_COMBAT);
// bot->TeleportTo(bot->GetMapId(), position[3].first, position[3].second, low_z, bot->GetOrientation());
}
}
}
else
return MoveTo(bot->GetMapId(), position[4].first, position[4].second, low_z, false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
return true;
}
bool ThaddiusMovePolarityAction::isUseful()
{
return !botAI->IsMainTank(bot) || AI_VALUE2(bool, "has aggro", "current target");
}
bool ThaddiusMovePolarityAction::Execute(Event /*event*/)
{
std::vector<std::pair<float, float>> position = {
// left melee
{3508.29f, -2920.12f},
// left ranged
{3501.72f, -2913.36f},
// right melee
{3519.74f, -2931.69f},
// right ranged
{3524.32f, -2936.26f},
// center melee
{3512.19f, -2928.58f},
// center ranged
{3504.68f, -2936.68f},
};
uint32 idx;
if (NaxxSpellIds::HasAnyAura(
botAI, bot,
{NaxxSpellIds::NegativeCharge10, NaxxSpellIds::NegativeCharge25, NaxxSpellIds::NegativeChargeStack}) ||
botAI->HasAura("negative charge", bot, false, false, -1, true))
{
idx = 0;
}
else if (NaxxSpellIds::HasAnyAura(
botAI, bot,
{NaxxSpellIds::PositiveCharge10, NaxxSpellIds::PositiveCharge25, NaxxSpellIds::PositiveChargeStack}) ||
botAI->HasAura("positive charge", bot, false, false, -1, true))
{
idx = 1;
}
else
{
idx = 2;
}
idx = idx * 2 + botAI->IsRanged(bot);
return MoveTo(bot->GetMapId(), position[idx].first, position[idx].second, bot->GetPositionZ(), false, false, false, false, MovementPriority::MOVEMENT_COMBAT);
}

View File

@ -0,0 +1,322 @@
#include "RaidNaxxMultipliers.h"
#include "ChooseTargetActions.h"
#include "DKActions.h"
#include "DruidActions.h"
#include "DruidBearActions.h"
#include "FollowActions.h"
#include "GenericActions.h"
#include "GenericSpellActions.h"
#include "HunterActions.h"
#include "MageActions.h"
#include "MovementActions.h"
#include "PaladinActions.h"
#include "PriestActions.h"
#include "RaidNaxxActions.h"
#include "RaidNaxxSpellIds.h"
#include "ReachTargetActions.h"
#include "RogueActions.h"
#include "ScriptedCreature.h"
#include "ShamanActions.h"
#include "Spell.h"
#include "UseMeetingStoneAction.h"
#include "WarriorActions.h"
float GrobbulusMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "grobbulus");
if (!boss)
return 1.0f;
if (dynamic_cast<AvoidAoeAction*>(action))
return botAI->IsMainTank(bot) ? 0.0f : 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
return 1.0f;
}
//float HeiganDanceMultiplier::GetValue(Action* action)
//{
// Unit* boss = AI_VALUE2(Unit*, "find target", "heigan the unclean");
// if (!boss)
// {
// return 1.0f;
// }
// bool platform_phase = boss->IsWithinDist2d(2794.26f, -3706.67f, 10.0f);
// bool eruption_casting = false;
// if (boss->HasUnitState(UNIT_STATE_CASTING))
// {
// Spell* spell = boss->GetCurrentSpell(CURRENT_GENERIC_SPELL);
// if (!spell)
// {
// spell = boss->GetCurrentSpell(CURRENT_CHANNELED_SPELL);
// }
// if (spell)
// {
// SpellInfo const* info = spell->GetSpellInfo();
// bool isEruption = NaxxSpellIds::MatchesAnySpellId(info, {NaxxSpellIds::Eruption10});
// if (!isEruption && info && info->SpellName[LOCALE_enUS])
// {
// // Fallback to name for custom spell data.
// isEruption = botAI->EqualLowercaseName(info->SpellName[LOCALE_enUS], "eruption");
// }
// if (isEruption)
// {
// eruption_casting = true;
// }
// }
// }
// if (dynamic_cast<CombatFormationMoveAction*>(action) ||
// dynamic_cast<CastDisengageAction*>(action) ||
// dynamic_cast<CastBlinkBackAction*>(action) )
// {
// return 0.0f;
// }
// if (!platform_phase && !eruption_casting)
// {
// return 1.0f;
// }
// if (dynamic_cast<HeiganDanceAction*>(action) || dynamic_cast<CurePartyMemberAction*>(action))
// {
// return 1.0f;
// }
// if (dynamic_cast<CastSpellAction*>(action) && !dynamic_cast<CastMeleeSpellAction*>(action))
// {
// CastSpellAction* spellAction = dynamic_cast<CastSpellAction*>(action);
// uint32 spellId = AI_VALUE2(uint32, "spell id", spellAction->getSpell());
// SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
// if (!spellInfo)
// {
// return 0.0f;
// }
// uint32 castTime = spellInfo->CalcCastTime();
// if (castTime == 0 && !spellInfo->IsChanneled())
// {
// return 1.0f;
// }
// }
// return 0.0f;
//}
float LoathebGenericMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "loatheb");
if (!boss)
return 1.0f;
context->GetValue<bool>("neglect threat")->Set(true);
if (botAI->GetState() == BOT_STATE_COMBAT &&
(dynamic_cast<DpsAssistAction*>(action) || dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<CastDebuffSpellOnAttackerAction*>(action) || dynamic_cast<FleeAction*>(action) ||
dynamic_cast<CombatFormationMoveAction*>(action)))
{
return 0.0f;
}
if (!dynamic_cast<CastHealingSpellAction*>(action))
return 1.0f;
Aura* aura = NaxxSpellIds::GetAnyAura(bot, {NaxxSpellIds::NecroticAura10});
if (!aura)
{
// Fallback to name for custom spell data.
aura = botAI->GetAura("necrotic aura", bot);
}
if (!aura || aura->GetDuration() <= 1500)
return 1.0f;
return 0.0f;
}
float ThaddiusGenericMultiplier::GetValue(Action* action)
{
if (!helper.UpdateBossAI())
return 1.0f;
if (dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
// pet phase
if (helper.IsPhasePet() &&
(dynamic_cast<DpsAssistAction*>(action) || dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<CastDebuffSpellOnAttackerAction*>(action) ||
dynamic_cast<ReachPartyMemberToHealAction*>(action) || dynamic_cast<BuffOnMainTankAction*>(action)))
{
return 0.0f;
}
// die at the same time
Unit* target = AI_VALUE(Unit*, "current target");
Unit* feugen = AI_VALUE2(Unit*, "find target", "feugen");
Unit* stalagg = AI_VALUE2(Unit*, "find target", "stalagg");
if (helper.IsPhasePet() && target && feugen && stalagg && target->GetHealthPct() <= 40 &&
(feugen->GetHealthPct() >= target->GetHealthPct() + 3 || stalagg->GetHealthPct() >= target->GetHealthPct() + 3))
{
if (dynamic_cast<CastSpellAction*>(action) && !dynamic_cast<CastHealingSpellAction*>(action))
return 0.0f;
}
// magnetic pull
// uint32 curr_timer = eventMap->GetTimer();
// // if (curr_phase == 2 && bot->GetPositionZ() > 312.5f && dynamic_cast<MovementAction*>(action))
// {
// if (curr_phase == 2 && (curr_timer % 20000 >= 18000 || curr_timer % 20000 <= 2000) &&
// dynamic_cast<MovementAction*>(action))
// {
// // MotionMaster *mm = bot->GetMotionMaster();
// // mm->Clear();
// return 0.0f;
// }
// thaddius phase
// if (curr_phase == 8 && dynamic_cast<FleeAction*>(action))
// {
// return 0.0f;
// }
return 1.0f;
}
float SapphironGenericMultiplier::GetValue(Action* action)
{
if (!helper.UpdateBossAI())
return 1.0f;
if (dynamic_cast<CastDeathGripAction*>(action) || dynamic_cast<CombatFormationMoveAction*>(action))
return 0.0f;
return 1.0f;
}
float InstructorRazuviousGenericMultiplier::GetValue(Action* action)
{
if (!helper.UpdateBossAI())
return 1.0f;
context->GetValue<bool>("neglect threat")->Set(true);
if (botAI->GetState() == BOT_STATE_COMBAT &&
(dynamic_cast<DpsAssistAction*>(action) || dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<CastTauntAction*>(action) || dynamic_cast<CastDarkCommandAction*>(action) ||
dynamic_cast<CastHandOfReckoningAction*>(action) || dynamic_cast<CastGrowlAction*>(action)))
{
return 0.0f;
}
return 1.0f;
}
float KelthuzadGenericMultiplier::GetValue(Action* action)
{
if (!helper.UpdateBossAI())
return 1.0f;
if ((dynamic_cast<DpsAssistAction*>(action) || dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<CastDebuffSpellOnAttackerAction*>(action) || dynamic_cast<FleeAction*>(action)))
{
return 0.0f;
}
if (helper.IsPhaseOne())
{
if (dynamic_cast<CastTotemAction*>(action) || dynamic_cast<CastShadowfiendAction*>(action) ||
dynamic_cast<CastRaiseDeadAction*>(action) || dynamic_cast<CastFeignDeathAction*>(action) ||
dynamic_cast<CastInvisibilityAction*>(action) || dynamic_cast<CastVanishAction*>(action) ||
dynamic_cast<PetAttackAction*>(action))
{
return 0.0f;
}
}
if (helper.IsPhaseTwo())
{
if (dynamic_cast<CastBlizzardAction*>(action) || dynamic_cast<CastFrostNovaAction*>(action))
return 0.0f;
}
return 1.0f;
}
float AnubrekhanGenericMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "anub'rekhan");
if (!boss)
return 1.0f;
if (NaxxSpellIds::HasAnyAura(
botAI, boss, {NaxxSpellIds::LocustSwarm10, NaxxSpellIds::LocustSwarm10Alt, NaxxSpellIds::LocustSwarm25}) ||
botAI->HasAura("locust swarm", boss))
{
if (dynamic_cast<FleeAction*>(action))
return 0.0f;
}
return 1.0f;
}
float FourhorsemanGenericMultiplier::GetValue(Action* action)
{
Unit* boss = AI_VALUE2(Unit*, "find target", "sir zeliek");
if (!boss)
return 1.0f;
context->GetValue<bool>("neglect threat")->Set(true);
if ((dynamic_cast<DpsAssistAction*>(action) || dynamic_cast<TankAssistAction*>(action)))
return 0.0f;
return 1.0f;
}
// float GothikGenericMultiplier::GetValue(Action* action)
// {
// Unit* boss = AI_VALUE2(Unit*, "find target", "gothik the harvester");
// if (!boss)
// {
// return 1.0f;
// }
// BossAI* boss_ai = dynamic_cast<BossAI*>(boss->GetAI());
// EventMap* eventMap = boss_botAI->GetEvents();
// uint32 curr_phase = eventMap->GetPhaseMask();
// if (curr_phase == 1 && (dynamic_cast<FollowAction*>(action)))
// {
// return 0.0f;
// }
// if (curr_phase == 1 && (dynamic_cast<AttackAction*>(action)))
// {
// Unit* target = action->GetTarget();
// if (target == boss)
// {
// return 0.0f;
// }
// }
// return 1.0f;
// }
float GluthGenericMultiplier::GetValue(Action* action)
{
if (!helper.UpdateBossAI())
return 1.0f;
if ((dynamic_cast<DpsAssistAction*>(action) || dynamic_cast<TankAssistAction*>(action) ||
dynamic_cast<FleeAction*>(action) || dynamic_cast<CastDebuffSpellOnAttackerAction*>(action) ||
dynamic_cast<CastStarfallAction*>(action)))
{
return 0.0f;
}
if (botAI->IsMainTank(bot))
{
Aura* aura = NaxxSpellIds::GetAnyAura(bot, {NaxxSpellIds::MortalWound10, NaxxSpellIds::MortalWound25});
if (!aura)
{
// Fallback to name for custom spell data.
aura = botAI->GetAura("mortal wound", bot, false, true);
}
if (aura && aura->GetStackAmount() >= 5)
{
if (dynamic_cast<CastTauntAction*>(action) || dynamic_cast<CastDarkCommandAction*>(action) ||
dynamic_cast<CastHandOfReckoningAction*>(action) || dynamic_cast<CastGrowlAction*>(action))
{
return 0.0f;
}
}
}
if (dynamic_cast<PetAttackAction*>(action))
{
Unit* target = AI_VALUE(Unit*, "current target");
if (helper.IsZombieChow(target))
return 0.0f;
}
return 1.0f;
}

View File

@ -0,0 +1,115 @@
#ifndef _PLAYERRBOT_RAIDNAXXMULTIPLIERS_H
#define _PLAYERRBOT_RAIDNAXXMULTIPLIERS_H
#include "Multiplier.h"
#include "RaidNaxxBossHelper.h"
class GrobbulusMultiplier : public Multiplier
{
public:
GrobbulusMultiplier(PlayerbotAI* ai) : Multiplier(ai, "grobbulus") {}
public:
virtual float GetValue(Action* action);
};
//class HeiganDanceMultiplier : public Multiplier
//{
//public:
// HeiganDanceMultiplier(PlayerbotAI* ai) : Multiplier(ai, "helgan dance") {}
//
//public:
// virtual float GetValue(Action* action);
//};
class LoathebGenericMultiplier : public Multiplier
{
public:
LoathebGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "loatheb generic") {}
public:
virtual float GetValue(Action* action);
};
class ThaddiusGenericMultiplier : public Multiplier
{
public:
ThaddiusGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "thaddius generic"), helper(ai) {}
public:
virtual float GetValue(Action* action);
private:
ThaddiusBossHelper helper;
};
class SapphironGenericMultiplier : public Multiplier
{
public:
SapphironGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "sapphiron generic"), helper(ai) {}
virtual float GetValue(Action* action);
private:
SapphironBossHelper helper;
};
class InstructorRazuviousGenericMultiplier : public Multiplier
{
public:
InstructorRazuviousGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "instructor razuvious generic"), helper(ai) {}
virtual float GetValue(Action* action);
private:
RazuviousBossHelper helper;
};
class KelthuzadGenericMultiplier : public Multiplier
{
public:
KelthuzadGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "kelthuzad generic"), helper(ai) {}
virtual float GetValue(Action* action);
private:
KelthuzadBossHelper helper;
};
class AnubrekhanGenericMultiplier : public Multiplier
{
public:
AnubrekhanGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "anubrekhan generic") {}
public:
virtual float GetValue(Action* action);
};
class FourhorsemanGenericMultiplier : public Multiplier
{
public:
FourhorsemanGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "fourhorseman generic") {}
public:
virtual float GetValue(Action* action);
};
// class GothikGenericMultiplier : public Multiplier
// {
// public:
// GothikGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "gothik generic") {}
// public:
// virtual float GetValue(Action* action);
// };
class GluthGenericMultiplier : public Multiplier
{
public:
GluthGenericMultiplier(PlayerbotAI* ai) : Multiplier(ai, "gluth generic"), helper(ai) {}
float GetValue(Action* action) override;
private:
GluthBossHelper helper;
};
#endif

View File

@ -0,0 +1,95 @@
// /*
// * 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_RAIDNAXXACTIONCONTEXT_H
#define _PLAYERBOT_RAIDNAXXACTIONCONTEXT_H
#include "Action.h"
#include "NamedObjectContext.h"
#include "RaidNaxxActions.h"
class RaidNaxxActionContext : public NamedObjectContext<Action>
{
public:
RaidNaxxActionContext()
{
creators["grobbulus go behind the boss"] = &RaidNaxxActionContext::go_behind_the_boss;
creators["rotate grobbulus"] = &RaidNaxxActionContext::rotate_grobbulus;
creators["grobbulus move center"] = &RaidNaxxActionContext::grobbulus_move_center;
creators["grobbulus move away"] = &RaidNaxxActionContext::grobbulus_move_away;
//creators["heigan dance melee"] = &RaidNaxxActionContext::heigan_dance_melee;
//creators["heigan dance ranged"] = &RaidNaxxActionContext::heigan_dance_ranged;
creators["thaddius attack nearest pet"] = &RaidNaxxActionContext::thaddius_attack_nearest_pet;
// creators["thaddius melee to place"] = &RaidNaxxActionContext::thaddius_tank_to_place;
// creators["thaddius ranged to place"] = &RaidNaxxActionContext::thaddius_ranged_to_place;
creators["thaddius move to platform"] = &RaidNaxxActionContext::thaddius_move_to_platform;
creators["thaddius move polarity"] = &RaidNaxxActionContext::thaddius_move_polarity;
creators["razuvious use obedience crystal"] = &RaidNaxxActionContext::razuvious_use_obedience_crystal;
creators["razuvious target"] = &RaidNaxxActionContext::razuvious_target;
creators["horseman attract alternatively"] = &RaidNaxxActionContext::horseman_attract_alternatively;
creators["horseman attack in order"] = &RaidNaxxActionContext::horseman_attack_in_order;
creators["sapphiron ground position"] = &RaidNaxxActionContext::sapphiron_ground_position;
creators["sapphiron flight position"] = &RaidNaxxActionContext::sapphiron_flight_position;
creators["kel'thuzad choose target"] = &RaidNaxxActionContext::kelthuzad_choose_target;
creators["kel'thuzad position"] = &RaidNaxxActionContext::kelthuzad_position;
creators["anub'rekhan choose target"] = &RaidNaxxActionContext::anubrekhan_choose_target;
creators["anub'rekhan position"] = &RaidNaxxActionContext::anubrekhan_position;
creators["gluth choose target"] = &RaidNaxxActionContext::gluth_choose_target;
creators["gluth position"] = &RaidNaxxActionContext::gluth_position;
creators["gluth slowdown"] = &RaidNaxxActionContext::gluth_slowdown;
//creators["patchwerk ranged position"] = &RaidNaxxActionContext::patchwerk_ranged_position;
creators["loatheb position"] = &RaidNaxxActionContext::loatheb_position;
creators["loatheb choose target"] = &RaidNaxxActionContext::loatheb_choose_target;
}
private:
static Action* go_behind_the_boss(PlayerbotAI* ai) { return new GrobbulusGoBehindAction(ai); }
static Action* rotate_grobbulus(PlayerbotAI* ai) { return new GrobbulusRotateAction(ai); }
static Action* grobbulus_move_center(PlayerbotAI* ai) { return new GrobblulusMoveCenterAction(ai); }
static Action* grobbulus_move_away(PlayerbotAI* ai) { return new GrobbulusMoveAwayAction(ai); }
//static Action* heigan_dance_melee(PlayerbotAI* ai) { return new HeiganDanceMeleeAction(ai); }
//static Action* heigan_dance_ranged(PlayerbotAI* ai) { return new HeiganDanceRangedAction(ai); }
static Action* thaddius_attack_nearest_pet(PlayerbotAI* ai) { return new ThaddiusAttackNearestPetAction(ai); }
// static Action* thaddius_tank_to_place(PlayerbotAI* ai) { return new ThaddiusMeleeToPlaceAction(ai); }
// static Action* thaddius_ranged_to_place(PlayerbotAI* ai) { return new ThaddiusRangedToPlaceAction(ai); }
static Action* thaddius_move_to_platform(PlayerbotAI* ai) { return new ThaddiusMoveToPlatformAction(ai); }
static Action* thaddius_move_polarity(PlayerbotAI* ai) { return new ThaddiusMovePolarityAction(ai); }
static Action* razuvious_target(PlayerbotAI* ai) { return new RazuviousTargetAction(ai); }
static Action* razuvious_use_obedience_crystal(PlayerbotAI* ai)
{
return new RazuviousUseObedienceCrystalAction(ai);
}
static Action* horseman_attract_alternatively(PlayerbotAI* ai)
{
return new HorsemanAttractAlternativelyAction(ai);
}
static Action* horseman_attack_in_order(PlayerbotAI* ai) { return new HorsemanAttactInOrderAction(ai); }
// static Action* sapphiron_ground_main_tank_position(PlayerbotAI* ai) { return new
// SapphironGroundMainTankPositionAction(ai); }
static Action* sapphiron_ground_position(PlayerbotAI* ai) { return new SapphironGroundPositionAction(ai); }
static Action* sapphiron_flight_position(PlayerbotAI* ai) { return new SapphironFlightPositionAction(ai); }
// static Action* sapphiron_avoid_chill(PlayerbotAI* ai) { return new SapphironAvoidChillAction(ai); }
static Action* kelthuzad_choose_target(PlayerbotAI* ai) { return new KelthuzadChooseTargetAction(ai); }
static Action* kelthuzad_position(PlayerbotAI* ai) { return new KelthuzadPositionAction(ai); }
static Action* anubrekhan_choose_target(PlayerbotAI* ai) { return new AnubrekhanChooseTargetAction(ai); }
static Action* anubrekhan_position(PlayerbotAI* ai) { return new AnubrekhanPositionAction(ai); }
static Action* gluth_choose_target(PlayerbotAI* ai) { return new GluthChooseTargetAction(ai); }
static Action* gluth_position(PlayerbotAI* ai) { return new GluthPositionAction(ai); }
static Action* gluth_slowdown(PlayerbotAI* ai) { return new GluthSlowdownAction(ai); }
//static Action* patchwerk_ranged_position(PlayerbotAI* ai) { return new PatchwerkRangedPositionAction(ai); }
static Action* loatheb_position(PlayerbotAI* ai) { return new LoathebPositionAction(ai); }
static Action* loatheb_choose_target(PlayerbotAI* ai) { return new LoathebChooseTargetAction(ai); }
};
#endif

View File

@ -0,0 +1,86 @@
// /*
// * 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_RAIDNAXXTRIGGERCONTEXT_H
#define _PLAYERBOT_RAIDNAXXTRIGGERCONTEXT_H
#include "AiObjectContext.h"
#include "NamedObjectContext.h"
#include "RaidNaxxTriggers.h"
class RaidNaxxTriggerContext : public NamedObjectContext<Trigger>
{
public:
RaidNaxxTriggerContext()
{
creators["mutating injection melee"] = &RaidNaxxTriggerContext::mutating_injection_melee;
creators["mutating injection ranged"] = &RaidNaxxTriggerContext::mutating_injection_ranged;
creators["mutating injection removed"] = &RaidNaxxTriggerContext::mutating_injection_removed;
creators["grobbulus cloud"] = &RaidNaxxTriggerContext::grobbulus_cloud;
//creators["heigan melee"] = &RaidNaxxTriggerContext::heigan_melee;
//creators["heigan ranged"] = &RaidNaxxTriggerContext::heigan_ranged;
creators["thaddius phase pet"] = &RaidNaxxTriggerContext::thaddius_phase_pet;
creators["thaddius phase pet lose aggro"] = &RaidNaxxTriggerContext::thaddius_phase_pet_lose_aggro;
creators["thaddius phase transition"] = &RaidNaxxTriggerContext::thaddius_phase_transition;
creators["thaddius phase thaddius"] = &RaidNaxxTriggerContext::thaddius_phase_thaddius;
creators["razuvious tank"] = &RaidNaxxTriggerContext::razuvious_tank;
creators["razuvious nontank"] = &RaidNaxxTriggerContext::razuvious_nontank;
creators["horseman attractors"] = &RaidNaxxTriggerContext::horseman_attractors;
creators["horseman except attractors"] = &RaidNaxxTriggerContext::horseman_except_attractors;
creators["sapphiron ground"] = &RaidNaxxTriggerContext::sapphiron_ground;
creators["sapphiron flight"] = &RaidNaxxTriggerContext::sapphiron_flight;
creators["kel'thuzad"] = &RaidNaxxTriggerContext::kelthuzad;
creators["anub'rekhan"] = &RaidNaxxTriggerContext::anubrekhan;
creators["faerlina"] = &RaidNaxxTriggerContext::faerlina;
creators["maexxna"] = &RaidNaxxTriggerContext::maexxna;
//creators["patchwerk tank"] = &RaidNaxxTriggerContext::patchwerk_tank;
//creators["patchwerk non-tank"] = &RaidNaxxTriggerContext::patchwerk_non_tank;
//creators["patchwerk ranged"] = &RaidNaxxTriggerContext::patchwerk_ranged;
creators["gluth"] = &RaidNaxxTriggerContext::gluth;
creators["gluth main tank mortal wound"] = &RaidNaxxTriggerContext::gluth_main_tank_mortal_wound;
creators["loatheb"] = &RaidNaxxTriggerContext::loatheb;
}
private:
static Trigger* mutating_injection_melee(PlayerbotAI* ai) { return new MutatingInjectionMeleeTrigger(ai); }
static Trigger* mutating_injection_ranged(PlayerbotAI* ai) { return new MutatingInjectionRangedTrigger(ai); }
static Trigger* mutating_injection_removed(PlayerbotAI* ai) { return new MutatingInjectionRemovedTrigger(ai); }
static Trigger* grobbulus_cloud(PlayerbotAI* ai) { return new GrobbulusCloudTrigger(ai); }
//static Trigger* heigan_melee(PlayerbotAI* ai) { return new HeiganMeleeTrigger(ai); }
//static Trigger* heigan_ranged(PlayerbotAI* ai) { return new HeiganRangedTrigger(ai); }
static Trigger* thaddius_phase_pet(PlayerbotAI* ai) { return new ThaddiusPhasePetTrigger(ai); }
static Trigger* thaddius_phase_pet_lose_aggro(PlayerbotAI* ai) { return new ThaddiusPhasePetLoseAggroTrigger(ai); }
static Trigger* thaddius_phase_transition(PlayerbotAI* ai) { return new ThaddiusPhaseTransitionTrigger(ai); }
static Trigger* thaddius_phase_thaddius(PlayerbotAI* ai) { return new ThaddiusPhaseThaddiusTrigger(ai); }
static Trigger* razuvious_tank(PlayerbotAI* ai) { return new RazuviousTankTrigger(ai); }
static Trigger* razuvious_nontank(PlayerbotAI* ai) { return new RazuviousNontankTrigger(ai); }
static Trigger* horseman_attractors(PlayerbotAI* ai) { return new HorsemanAttractorsTrigger(ai); }
static Trigger* horseman_except_attractors(PlayerbotAI* ai) { return new HorsemanExceptAttractorsTrigger(ai); }
static Trigger* sapphiron_ground(PlayerbotAI* ai) { return new SapphironGroundTrigger(ai); }
static Trigger* sapphiron_flight(PlayerbotAI* ai) { return new SapphironFlightTrigger(ai); }
static Trigger* kelthuzad(PlayerbotAI* ai) { return new KelthuzadTrigger(ai); }
static Trigger* anubrekhan(PlayerbotAI* ai) { return new AnubrekhanTrigger(ai); }
static Trigger* faerlina(PlayerbotAI* ai) { return new FaerlinaTrigger(ai); }
static Trigger* maexxna(PlayerbotAI* ai) { return new MaexxnaTrigger(ai); }
//static Trigger* patchwerk_tank(PlayerbotAI* ai) { return new PatchwerkTankTrigger(ai); }
//static Trigger* patchwerk_non_tank(PlayerbotAI* ai) { return new PatchwerkNonTankTrigger(ai); }
//static Trigger* patchwerk_ranged(PlayerbotAI* ai) { return new PatchwerkRangedTrigger(ai); }
static Trigger* gluth(PlayerbotAI* ai) { return new GluthTrigger(ai); }
static Trigger* gluth_main_tank_mortal_wound(PlayerbotAI* ai) { return new GluthMainTankMortalWoundTrigger(ai); }
static Trigger* loatheb(PlayerbotAI* ai) { return new LoathebTrigger(ai); }
};
#endif

View File

@ -0,0 +1,156 @@
#include "RaidNaxxStrategy.h"
#include "RaidNaxxMultipliers.h"
void RaidNaxxStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{
// Grobbulus
triggers.push_back(new TriggerNode("mutating injection melee",
{ NextAction("grobbulus move away", ACTION_RAID + 2) }
));
triggers.push_back(new TriggerNode("mutating injection ranged",
{ NextAction("grobbulus go behind the boss", ACTION_RAID + 2) }
));
triggers.push_back(new TriggerNode("mutating injection removed",
{ NextAction("grobbulus move center", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("grobbulus cloud",
{ NextAction("rotate grobbulus", ACTION_RAID + 1) }
));
// Heigan the Unclean
//triggers.push_back(new TriggerNode("heigan melee",
// { NextAction("heigan dance melee", ACTION_RAID + 1) }
//));
//triggers.push_back(new TriggerNode("heigan ranged",
// { NextAction("heigan dance ranged", ACTION_RAID + 1) }
//));
// Kel'Thuzad
triggers.push_back(
new TriggerNode("kel'thuzad",
{
NextAction("kel'thuzad position", ACTION_RAID + 2),
NextAction("kel'thuzad choose target", ACTION_RAID + 1)
})
);
// Anub'Rekhan
triggers.push_back(new TriggerNode("anub'rekhan",
{ NextAction("anub'rekhan position", ACTION_RAID + 1) }
));
// Grand Widow Faerlina
triggers.push_back(new TriggerNode("faerlina",
{ NextAction("avoid aoe", ACTION_RAID + 1) }
));
// Maexxna
triggers.push_back(
new TriggerNode("maexxna",
{
NextAction("rear flank", ACTION_RAID + 1),
NextAction("avoid aoe", ACTION_RAID + 1)
})
);
// Patchwerk
//triggers.push_back(new TriggerNode("patchwerk tank",
// { NextAction("tank face", ACTION_RAID + 2) }
//));
//triggers.push_back(new TriggerNode("patchwerk ranged",
// { NextAction("patchwerk ranged position", ACTION_RAID + 2) }
//));
//triggers.push_back(new TriggerNode("patchwerk non-tank",
// { NextAction("rear flank", ACTION_RAID + 1) }
//));
// Thaddius
triggers.push_back(new TriggerNode("thaddius phase pet",
{ NextAction("thaddius attack nearest pet", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("thaddius phase pet lose aggro",
{ NextAction("taunt spell", ACTION_RAID + 2) }
));
triggers.push_back(new TriggerNode("thaddius phase transition",
{ NextAction("thaddius move to platform", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("thaddius phase thaddius",
{ NextAction("thaddius move polarity", ACTION_RAID + 1) }
));
// Instructor Razuvious
triggers.push_back(new TriggerNode("razuvious tank",
{ NextAction("razuvious use obedience crystal", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("razuvious nontank",
{ NextAction("razuvious target", ACTION_RAID + 1) }
));
// four horseman
triggers.push_back(new TriggerNode("horseman attractors",
{ NextAction("horseman attract alternatively", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("horseman except attractors",
{ NextAction("horseman attack in order", ACTION_RAID + 1) }
));
// sapphiron
triggers.push_back(new TriggerNode("sapphiron ground",
{ NextAction("sapphiron ground position", ACTION_RAID + 1) }
));
triggers.push_back(new TriggerNode("sapphiron flight",
{ NextAction("sapphiron flight position", ACTION_RAID + 1) }
));
// Gluth
triggers.push_back(
new TriggerNode("gluth",
{
NextAction("gluth choose target", ACTION_RAID + 1),
NextAction("gluth position", ACTION_RAID + 1),
NextAction("gluth slowdown", ACTION_RAID)
})
);
triggers.push_back(new TriggerNode("gluth main tank mortal wound",
{ NextAction("taunt spell", ACTION_RAID + 1) }
));
// Loatheb
triggers.push_back(
new TriggerNode("loatheb",
{
NextAction("loatheb position", ACTION_RAID + 1),
NextAction("loatheb choose target", ACTION_RAID + 1)
})
);
}
void RaidNaxxStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
{
multipliers.push_back(new GrobbulusMultiplier(botAI));
//multipliers.push_back(new HeiganDanceMultiplier(botAI));
multipliers.push_back(new LoathebGenericMultiplier(botAI));
multipliers.push_back(new ThaddiusGenericMultiplier(botAI));
multipliers.push_back(new SapphironGenericMultiplier(botAI));
multipliers.push_back(new InstructorRazuviousGenericMultiplier(botAI));
multipliers.push_back(new KelthuzadGenericMultiplier(botAI));
multipliers.push_back(new AnubrekhanGenericMultiplier(botAI));
multipliers.push_back(new FourhorsemanGenericMultiplier(botAI));
// multipliers.push_back(new GothikGenericMultiplier(botAI));
multipliers.push_back(new GluthGenericMultiplier(botAI));
}

View File

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

View File

@ -0,0 +1,257 @@
#include "RaidNaxxTriggers.h"
#include "Playerbots.h"
#include "RaidNaxxSpellIds.h"
#include "Timer.h"
#include "Trigger.h"
bool MutatingInjectionMeleeTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "grobbulus");
if (!boss)
return false;
return MutatingInjectionTrigger::IsActive() && !botAI->IsRanged(bot);
}
bool MutatingInjectionRangedTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "grobbulus");
if (!boss)
return false;
return MutatingInjectionTrigger::IsActive() && botAI->IsRanged(bot);
}
bool AuraRemovedTrigger::IsActive()
{
bool check = botAI->HasAura(name, bot, false, false, -1, true);
bool ret = false;
if (prev_check && !check)
ret = true;
prev_check = check;
return ret;
}
bool MutatingInjectionRemovedTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "grobbulus");
if (!boss)
return false;
return HasNoAuraTrigger::IsActive() && botAI->GetState() == BOT_STATE_COMBAT && botAI->IsRanged(bot);
}
bool GrobbulusCloudTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "grobbulus");
if (!boss)
return false;
if (!botAI->IsMainTank(bot))
return false;
// bot->Yell("has aggro on " + boss->GetName() + " : " + to_string(AI_VALUE2(bool, "has aggro", "boss target")),
// LANG_UNIVERSAL);
if (!AI_VALUE2(bool, "has aggro", "boss target"))
return false;
uint32 now = getMSTime();
bool poison_cloud_casting = false;
if (boss->HasUnitState(UNIT_STATE_CASTING))
{
Spell* spell = boss->GetCurrentSpell(CURRENT_GENERIC_SPELL);
if (!spell)
spell = boss->GetCurrentSpell(CURRENT_CHANNELED_SPELL);
if (spell)
poison_cloud_casting = NaxxSpellIds::MatchesAnySpellId(spell->GetSpellInfo(), {NaxxSpellIds::PoisonCloud});
}
if (!poison_cloud_casting && last_cloud_ms != 0 && now - last_cloud_ms < CloudRotationDelayMs)
return false;
last_cloud_ms = now;
return true;
}
//bool HeiganMeleeTrigger::IsActive()
//{
// Unit* heigan = AI_VALUE2(Unit*, "find target", "heigan the unclean");
// if (!heigan)
// {
// return false;
// }
// return !botAI->IsRanged(bot);
//}
//
//bool HeiganRangedTrigger::IsActive()
//{
// Unit* heigan = AI_VALUE2(Unit*, "find target", "heigan the unclean");
// if (!heigan)
// {
// return false;
// }
// return botAI->IsRanged(bot);
//}
bool RazuviousTankTrigger::IsActive()
{
Difficulty diff = bot->GetRaidDifficulty();
if (diff == RAID_DIFFICULTY_10MAN_NORMAL)
return helper.UpdateBossAI() && botAI->IsTank(bot);
return helper.UpdateBossAI() && bot->getClass() == CLASS_PRIEST;
}
bool RazuviousNontankTrigger::IsActive()
{
Difficulty diff = bot->GetRaidDifficulty();
if (diff == RAID_DIFFICULTY_10MAN_NORMAL)
return helper.UpdateBossAI() && !(botAI->IsTank(bot));
return helper.UpdateBossAI() && !(bot->getClass() == CLASS_PRIEST);
}
bool HorsemanAttractorsTrigger::IsActive()
{
if (!helper.UpdateBossAI())
return false;
return helper.IsAttracter(bot);
}
bool HorsemanExceptAttractorsTrigger::IsActive()
{
if (!helper.UpdateBossAI())
return false;
return !helper.IsAttracter(bot);
}
bool SapphironGroundTrigger::IsActive()
{
if (!helper.UpdateBossAI())
return false;
return helper.IsPhaseGround();
}
bool SapphironFlightTrigger::IsActive()
{
if (!helper.UpdateBossAI())
return false;
return helper.IsPhaseFlight();
}
bool GluthTrigger::IsActive() { return helper.UpdateBossAI(); }
bool GluthMainTankMortalWoundTrigger::IsActive()
{
if (!helper.UpdateBossAI())
return false;
if (!botAI->IsAssistTankOfIndex(bot, 0))
return false;
Unit* mt = AI_VALUE(Unit*, "main tank");
if (!mt)
return false;
Aura* aura = NaxxSpellIds::GetAnyAura(mt, {NaxxSpellIds::MortalWound10, NaxxSpellIds::MortalWound25});
if (!aura)
{
// Fallback to name for custom spell data.
aura = botAI->GetAura("mortal wound", mt, false, true);
}
if (!aura || aura->GetStackAmount() < 5)
return false;
return true;
}
bool KelthuzadTrigger::IsActive() { return helper.UpdateBossAI(); }
bool AnubrekhanTrigger::IsActive() {
Unit* boss = AI_VALUE2(Unit*, "find target", "anub'rekhan");
if (!boss)
return false;
return true;
}
bool FaerlinaTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "grand widow faerlina");
if (!boss)
return false;
return true;
}
bool MaexxnaTrigger::IsActive()
{
Unit* boss = AI_VALUE2(Unit*, "find target", "maexxna");
if (!boss)
return false;
return !botAI->IsTank(bot);
}
//bool PatchwerkTankTrigger::IsActive()
//{
// Unit* boss = AI_VALUE2(Unit*, "find target", "patchwerk");
// if (!boss)
// {
// return false;
// }
// return !botAI->IsTank(bot) && !botAI->IsRanged(bot);
//}
//
//bool PatchwerkRangedTrigger::IsActive()
//{
// Unit* boss = AI_VALUE2(Unit*, "find target", "patchwerk");
// if (!boss)
// {
// return false;
// }
// return !botAI->IsTank(bot) && botAI->IsRanged(bot);
//}
//
//bool PatchwerkNonTankTrigger::IsActive()
//{
// Unit* boss = AI_VALUE2(Unit*, "find target", "patchwerk");
// if (!boss)
// {
// return false;
// }
// return !botAI->IsTank(bot);
//}
bool LoathebTrigger::IsActive() { return helper.UpdateBossAI(); }
bool ThaddiusPhasePetTrigger::IsActive()
{
if (!helper.UpdateBossAI())
return false;
return helper.IsPhasePet();
}
bool ThaddiusPhaseTransitionTrigger::IsActive()
{
if (!helper.UpdateBossAI())
return false;
return helper.IsPhaseTransition();
}
bool ThaddiusPhaseThaddiusTrigger::IsActive()
{
if (!helper.UpdateBossAI())
return false;
return helper.IsPhaseThaddius();
}

View File

@ -0,0 +1,259 @@
#ifndef _PLAYERBOT_RAIDNAXXTRIGGERS_H
#define _PLAYERBOT_RAIDNAXXTRIGGERS_H
#include "EventMap.h"
#include "GenericTriggers.h"
#include "PlayerbotAIConfig.h"
#include "RaidNaxxBossHelper.h"
#include "Trigger.h"
class MutatingInjectionTrigger : public HasAuraTrigger
{
public:
MutatingInjectionTrigger(PlayerbotAI* ai) : HasAuraTrigger(ai, "mutating injection", 1) {}
};
class MutatingInjectionMeleeTrigger : public MutatingInjectionTrigger
{
public:
MutatingInjectionMeleeTrigger(PlayerbotAI* ai) : MutatingInjectionTrigger(ai) {}
bool IsActive() override;
};
class MutatingInjectionRangedTrigger : public MutatingInjectionTrigger
{
public:
MutatingInjectionRangedTrigger(PlayerbotAI* ai) : MutatingInjectionTrigger(ai) {}
bool IsActive() override;
};
class AuraRemovedTrigger : public Trigger
{
public:
AuraRemovedTrigger(PlayerbotAI* botAI, std::string name) : Trigger(botAI, name, 1)
{
this->prev_check = false;
}
virtual bool IsActive() override;
protected:
bool prev_check;
};
class MutatingInjectionRemovedTrigger : public HasNoAuraTrigger
{
public:
MutatingInjectionRemovedTrigger(PlayerbotAI* ai) : HasNoAuraTrigger(ai, "mutating injection") {}
virtual bool IsActive();
};
class GrobbulusCloudTrigger : public Trigger
{
public:
GrobbulusCloudTrigger(PlayerbotAI* ai) : Trigger(ai, "grobbulus cloud event"), last_cloud_ms(0) {}
bool IsActive() override;
private:
uint32 last_cloud_ms;
static constexpr uint32 CloudRotationDelayMs = 15000;
};
//class HeiganMeleeTrigger : public Trigger
//{
//public:
// HeiganMeleeTrigger(PlayerbotAI* ai) : Trigger(ai, "heigan melee") {}
// virtual bool IsActive();
//};
//
//class HeiganRangedTrigger : public Trigger
//{
//public:
// HeiganRangedTrigger(PlayerbotAI* ai) : Trigger(ai, "heigan ranged") {}
// bool IsActive() override;
//};
class RazuviousTankTrigger : public Trigger
{
public:
RazuviousTankTrigger(PlayerbotAI* ai) : Trigger(ai, "instructor razuvious tank"), helper(ai) {}
bool IsActive() override;
private:
RazuviousBossHelper helper;
};
class RazuviousNontankTrigger : public Trigger
{
public:
RazuviousNontankTrigger(PlayerbotAI* ai) : Trigger(ai, "instructor razuvious non-tank"), helper(ai) {}
bool IsActive() override;
private:
RazuviousBossHelper helper;
};
class KelthuzadTrigger : public Trigger
{
public:
KelthuzadTrigger(PlayerbotAI* ai) : Trigger(ai, "kel'thuzad trigger"), helper(ai) {}
bool IsActive() override;
private:
KelthuzadBossHelper helper;
};
class AnubrekhanTrigger : public Trigger
{
public:
AnubrekhanTrigger(PlayerbotAI* ai) : Trigger(ai, "anub'rekhan") {}
bool IsActive() override;
};
class FaerlinaTrigger : public Trigger
{
public:
FaerlinaTrigger(PlayerbotAI* ai) : Trigger(ai, "faerlina") {}
bool IsActive() override;
};
class MaexxnaTrigger : public Trigger
{
public:
MaexxnaTrigger(PlayerbotAI* ai) : Trigger(ai, "maexxna") {}
bool IsActive() override;
};
//class PatchwerkTankTrigger : public Trigger
//{
//public:
// PatchwerkTankTrigger(PlayerbotAI* ai) : Trigger(ai, "patchwerk tank") {}
// bool IsActive() override;
//};
//
//class PatchwerkNonTankTrigger : public Trigger
//{
//public:
// PatchwerkNonTankTrigger(PlayerbotAI* ai) : Trigger(ai, "patchwerk non-tank") {}
// bool IsActive() override;
//};
//
//class PatchwerkRangedTrigger : public Trigger
//{
//public:
// PatchwerkRangedTrigger(PlayerbotAI* ai) : Trigger(ai, "patchwerk ranged") {}
// bool IsActive() override;
//};
class ThaddiusPhasePetTrigger : public Trigger
{
public:
ThaddiusPhasePetTrigger(PlayerbotAI* ai) : Trigger(ai, "thaddius phase pet"), helper(ai) {}
bool IsActive() override;
private:
ThaddiusBossHelper helper;
};
class ThaddiusPhasePetLoseAggroTrigger : public ThaddiusPhasePetTrigger
{
public:
ThaddiusPhasePetLoseAggroTrigger(PlayerbotAI* ai) : ThaddiusPhasePetTrigger(ai) {}
virtual bool IsActive()
{
Unit* target = AI_VALUE(Unit*, "current target");
return ThaddiusPhasePetTrigger::IsActive() && botAI->IsTank(bot) && target && target->GetVictim() != bot;
}
};
class ThaddiusPhaseTransitionTrigger : public Trigger
{
public:
ThaddiusPhaseTransitionTrigger(PlayerbotAI* ai) : Trigger(ai, "thaddius phase transition"), helper(ai) {}
bool IsActive() override;
private:
ThaddiusBossHelper helper;
};
class ThaddiusPhaseThaddiusTrigger : public Trigger
{
public:
ThaddiusPhaseThaddiusTrigger(PlayerbotAI* ai) : Trigger(ai, "thaddius phase thaddius"), helper(ai) {}
bool IsActive() override;
private:
ThaddiusBossHelper helper;
};
class HorsemanAttractorsTrigger : public Trigger
{
public:
HorsemanAttractorsTrigger(PlayerbotAI* ai) : Trigger(ai, "fourhorsemen attractors"), helper(ai) {}
bool IsActive() override;
private:
FourhorsemanBossHelper helper;
};
class HorsemanExceptAttractorsTrigger : public Trigger
{
public:
HorsemanExceptAttractorsTrigger(PlayerbotAI* ai) : Trigger(ai, "fourhorsemen except attractors"), helper(ai) {}
bool IsActive() override;
private:
FourhorsemanBossHelper helper;
};
class SapphironGroundTrigger : public Trigger
{
public:
SapphironGroundTrigger(PlayerbotAI* ai) : Trigger(ai, "sapphiron ground"), helper(ai) {}
bool IsActive() override;
private:
SapphironBossHelper helper;
};
class SapphironFlightTrigger : public Trigger
{
public:
SapphironFlightTrigger(PlayerbotAI* ai) : Trigger(ai, "sapphiron flight"), helper(ai) {}
bool IsActive() override;
private:
SapphironBossHelper helper;
};
class GluthTrigger : public Trigger
{
public:
GluthTrigger(PlayerbotAI* ai) : Trigger(ai, "gluth trigger"), helper(ai) {}
bool IsActive() override;
private:
GluthBossHelper helper;
};
class GluthMainTankMortalWoundTrigger : public Trigger
{
public:
GluthMainTankMortalWoundTrigger(PlayerbotAI* ai) : Trigger(ai, "gluth main tank mortal wound trigger"), helper(ai) {}
bool IsActive() override;
private:
GluthBossHelper helper;
};
class LoathebTrigger : public Trigger
{
public:
LoathebTrigger(PlayerbotAI* ai) : Trigger(ai, "loatheb"), helper(ai) {}
bool IsActive() override;
private:
LoathebBossHelper helper;
};
#endif

View File

@ -0,0 +1,533 @@
#ifndef _PLAYERBOT_RAIDNAXXBOSSHELPER_H
#define _PLAYERBOT_RAIDNAXXBOSSHELPER_H
#include <string>
#include "AiObject.h"
#include "AiObjectContext.h"
#include "EventMap.h"
#include "Log.h"
#include "NamedObjectContext.h"
#include "ObjectGuid.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "ScriptedCreature.h"
#include "SharedDefines.h"
#include "Spell.h"
#include "Timer.h"
#include "RaidNaxxSpellIds.h"
const uint32 NAXX_MAP_ID = 533;
template <class BossAiType>
class GenericBossHelper : public AiObject
{
public:
GenericBossHelper(PlayerbotAI* botAI, std::string name) : AiObject(botAI), _name(name) {}
virtual bool UpdateBossAI()
{
if (!bot->IsInCombat())
_unit = nullptr;
if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive()))
_unit = nullptr;
if (!_unit)
{
_unit = AI_VALUE2(Unit*, "find target", _name);
if (!_unit)
return false;
_target = _unit->ToCreature();
if (!_target)
return false;
_ai = dynamic_cast<BossAiType*>(_target->GetAI());
if (!_ai)
return false;
_event_map = &_ai->events;
if (!_event_map)
return false;
}
if (!_event_map)
return false;
_timer = getMSTime();
return true;
}
virtual void Reset()
{
_unit = nullptr;
_target = nullptr;
_ai = nullptr;
_event_map = nullptr;
_timer = 0;
}
protected:
std::string _name;
Unit* _unit = nullptr;
Creature* _target = nullptr;
BossAiType* _ai = nullptr;
EventMap* _event_map = nullptr;
uint32 _timer = 0;
};
class KelthuzadBossHelper : public AiObject
{
public:
KelthuzadBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {}
const std::pair<float, float> center = {3716.19f, -5106.58f};
const std::pair<float, float> tank_pos = {3709.19f, -5104.86f};
const std::pair<float, float> assist_tank_pos = {3746.05f, -5112.74f};
bool UpdateBossAI()
{
if (!bot->IsInCombat())
Reset();
if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive()))
Reset();
if (!_unit)
_unit = AI_VALUE2(Unit*, "find target", "kel'thuzad");
return _unit != nullptr;
}
bool IsPhaseOne() { return _unit && _unit->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE); }
bool IsPhaseTwo() { return _unit && !_unit->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE); }
Unit* GetAnyShadowFissure()
{
Unit* shadow_fissure = nullptr;
GuidVector units = *context->GetValue<GuidVector>("nearest triggers");
for (auto i = units.begin(); i != units.end(); i++)
{
Unit* unit = botAI->GetUnit(*i);
if (!unit)
continue;
if (botAI->EqualLowercaseName(unit->GetName(), "shadow fissure"))
shadow_fissure = unit;
}
return shadow_fissure;
}
private:
void Reset() { _unit = nullptr; }
Unit* _unit = nullptr;
};
class RazuviousBossHelper : public AiObject
{
public:
RazuviousBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {}
bool UpdateBossAI()
{
if (!bot->IsInCombat())
Reset();
if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive()))
Reset();
if (!_unit)
_unit = AI_VALUE2(Unit*, "find target", "instructor razuvious");
return _unit != nullptr;
}
private:
void Reset() { _unit = nullptr; }
Unit* _unit = nullptr;
};
class SapphironBossHelper : public AiObject
{
public:
const std::pair<float, float> mainTankPos = {3512.07f, -5274.06f};
const std::pair<float, float> center = {3517.31f, -5253.74f};
const float GENERIC_HEIGHT = 137.29f;
SapphironBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {}
bool UpdateBossAI()
{
if (!bot->IsInCombat())
Reset();
if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive()))
Reset();
if (!_unit)
{
_unit = AI_VALUE2(Unit*, "find target", "sapphiron");
if (!_unit)
return false;
}
bool now_flying = _unit->IsFlying();
if (_was_flying && !now_flying)
_last_land_ms = getMSTime();
_was_flying = now_flying;
return true;
}
bool IsPhaseGround() { return _unit && !_unit->IsFlying(); }
bool IsPhaseFlight() { return _unit && _unit->IsFlying(); }
bool JustLanded()
{
if (!_last_land_ms)
return false;
return getMSTime() - _last_land_ms <= POSITION_TIME_AFTER_LANDED;
}
bool WaitForExplosion()
{
if (!IsPhaseFlight())
return false;
Group* group = bot->GetGroup();
if (!group)
return false;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member &&
(NaxxSpellIds::HasAnyAura(botAI, member, {NaxxSpellIds::Icebolt10, NaxxSpellIds::Icebolt25}) ||
botAI->HasAura("icebolt", member, false, false, -1, true)))
{
return true;
}
}
return false;
}
bool FindPosToAvoidChill(std::vector<float>& dest)
{
Aura* aura = NaxxSpellIds::GetAnyAura(bot, {NaxxSpellIds::Chill25});
if (!aura)
{
// Fallback to name for custom spell data.
aura = botAI->GetAura("chill", bot);
}
if (!aura)
return false;
DynamicObject* dyn_obj = aura->GetDynobjOwner();
if (!dyn_obj)
return false;
Unit* currentTarget = AI_VALUE(Unit*, "current target");
float angle = 0;
uint32 index = botAI->GetGroupSlotIndex(bot);
if (currentTarget)
{
if (botAI->IsRanged(bot))
{
if (bot->GetExactDist2d(currentTarget) <= 45.0f)
angle = bot->GetAngle(dyn_obj) - M_PI + (rand_norm() - 0.5) * M_PI / 2;
else
{
if (index % 2 == 0)
angle = bot->GetAngle(currentTarget) + M_PI / 2;
else
angle = bot->GetAngle(currentTarget) - M_PI / 2;
}
}
else
{
if (index % 3 == 0)
angle = bot->GetAngle(currentTarget);
else if (index % 3 == 1)
angle = bot->GetAngle(currentTarget) + M_PI / 2;
else
angle = bot->GetAngle(currentTarget) - M_PI / 2;
}
}
else
angle = bot->GetAngle(dyn_obj) - M_PI + (rand_norm() - 0.5) * M_PI / 2;
dest = {bot->GetPositionX() + cos(angle) * 5.0f, bot->GetPositionY() + sin(angle) * 5.0f, bot->GetPositionZ()};
return true;
}
private:
void Reset()
{
_unit = nullptr;
_was_flying = false;
_last_land_ms = 0;
}
const uint32 POSITION_TIME_AFTER_LANDED = 5000;
Unit* _unit = nullptr;
bool _was_flying = false;
uint32 _last_land_ms = 0;
};
class GluthBossHelper : public AiObject
{
public:
const std::pair<float, float> mainTankPos25 = {3331.48f, -3109.06f};
const std::pair<float, float> mainTankPos10 = {3278.29f, -3162.06f};
const std::pair<float, float> beforeDecimatePos = {3267.34f, -3175.68f};
const std::pair<float, float> leftSlowDownPos = {3290.68f, -3141.65f};
const std::pair<float, float> rightSlowDownPos = {3300.78f, -3151.98f};
const std::pair<float, float> rangedPos = {3301.45f, -3139.29f};
const std::pair<float, float> healPos = {3303.09f, -3135.24f};
const float decimatedZombiePct = 10.0f;
GluthBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {}
bool UpdateBossAI()
{
if (!bot->IsInCombat())
Reset();
if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive()))
Reset();
if (!_unit)
{
_unit = AI_VALUE2(Unit*, "find target", "gluth");
if (!_unit)
return false;
}
if (_unit->IsInCombat())
{
if (_combat_start_ms == 0)
_combat_start_ms = getMSTime();
}
else
_combat_start_ms = 0;
return true;
}
bool BeforeDecimate()
{
if (!_unit || !_unit->HasUnitState(UNIT_STATE_CASTING))
return false;
Spell* spell = _unit->GetCurrentSpell(CURRENT_GENERIC_SPELL);
if (!spell)
spell = _unit->GetCurrentSpell(CURRENT_CHANNELED_SPELL);
if (!spell)
return false;
SpellInfo const* info = spell->GetSpellInfo();
if (!info)
return false;
if (NaxxSpellIds::MatchesAnySpellId(
info, {NaxxSpellIds::Decimate10, NaxxSpellIds::Decimate25, NaxxSpellIds::Decimate25Alt}))
return true;
// Fallback to name for custom spell data.
return info->SpellName[LOCALE_enUS] && botAI->EqualLowercaseName(info->SpellName[LOCALE_enUS], "decimate");
}
bool JustStartCombat() const { return _combat_start_ms != 0 && getMSTime() - _combat_start_ms < 10000; }
bool IsZombieChow(Unit* unit) const { return unit && botAI->EqualLowercaseName(unit->GetName(), "zombie chow"); }
private:
void Reset()
{
_unit = nullptr;
_combat_start_ms = 0;
}
Unit* _unit = nullptr;
uint32 _combat_start_ms = 0;
};
class LoathebBossHelper : public AiObject
{
public:
const std::pair<float, float> mainTankPos = {2877.57f, -3967.00f};
const std::pair<float, float> rangePos = {2896.96f, -3980.61f};
LoathebBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {}
bool UpdateBossAI()
{
if (!bot->IsInCombat())
Reset();
if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive()))
Reset();
if (!_unit)
_unit = AI_VALUE2(Unit*, "find target", "loatheb");
return _unit != nullptr;
}
private:
void Reset() { _unit = nullptr; }
Unit* _unit = nullptr;
};
class FourhorsemanBossHelper : public AiObject
{
public:
const float posZ = 241.27f;
const std::pair<float, float> attractPos[2] = {{2502.03f, -2910.90f},
{2484.61f, -2947.07f}}; // left (sir zeliek), right (lady blaumeux)
FourhorsemanBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {}
bool UpdateBossAI()
{
if (!bot->IsInCombat())
Reset();
else if (_combat_start_ms == 0)
_combat_start_ms = getMSTime();
if (_sir && (!_sir->IsInWorld() || !_sir->IsAlive()))
Reset();
if (!_sir)
{
_sir = AI_VALUE2(Unit*, "find target", "sir zeliek");
if (!_sir)
return false;
}
_lady = AI_VALUE2(Unit*, "find target", "lady blaumeux");
return true;
}
void Reset()
{
_sir = nullptr;
_lady = nullptr;
_combat_start_ms = 0;
posToGo = 0;
}
bool IsAttracter(Player* bot)
{
Difficulty diff = bot->GetRaidDifficulty();
if (diff == RAID_DIFFICULTY_25MAN_NORMAL)
{
return botAI->IsAssistRangedDpsOfIndex(bot, 0) || botAI->IsAssistHealOfIndex(bot, 0) ||
botAI->IsAssistHealOfIndex(bot, 1) || botAI->IsAssistHealOfIndex(bot, 2);
}
return botAI->IsAssistRangedDpsOfIndex(bot, 0) || botAI->IsAssistHealOfIndex(bot, 0);
}
void CalculatePosToGo(Player* bot)
{
bool raid25 = bot->GetRaidDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL;
Unit* lady = _lady;
if (!lady)
posToGo = 0;
else
{
uint32 elapsed_ms = _combat_start_ms ? getMSTime() - _combat_start_ms : 0;
// Interval: 24s - 15s - 15s - ...
posToGo = !(elapsed_ms <= 9000 || ((elapsed_ms - 9000) / 67500) % 2 == 0);
if (botAI->IsAssistRangedDpsOfIndex(bot, 0) || (raid25 && botAI->IsAssistHealOfIndex(bot, 1)))
posToGo = 1 - posToGo;
}
}
std::pair<float, float> CurrentAttractPos()
{
bool raid25 = bot->GetRaidDifficulty() == RAID_DIFFICULTY_25MAN_NORMAL;
float posX = attractPos[posToGo].first, posY = attractPos[posToGo].second;
if (posToGo == 1)
{
float offset_x = 0.0f;
float offset_y = 0.0f;
float bias = 4.5f;
if (raid25)
{
offset_x = -bias;
offset_y = bias;
}
posX += offset_x;
posY += offset_y;
}
return {posX, posY};
}
Unit* CurrentAttackTarget()
{
if (posToGo == 0)
return _sir;
return _lady;
}
protected:
Unit* _sir = nullptr;
Unit* _lady = nullptr;
uint32 _combat_start_ms = 0;
int posToGo = 0;
};
class ThaddiusBossHelper : public AiObject
{
public:
const std::pair<float, float> tankPosFeugen = {3522.94f, -3002.60f};
const std::pair<float, float> tankPosStalagg = {3436.14f, -2919.98f};
const std::pair<float, float> rangedPosFeugen = {3500.45f, -2997.92f};
const std::pair<float, float> rangedPosStalagg = {3441.01f, -2942.04f};
const float tankPosZ = 312.61f;
ThaddiusBossHelper(PlayerbotAI* botAI) : AiObject(botAI) {}
bool UpdateBossAI()
{
if (!bot->IsInCombat())
Reset();
if (_unit && (!_unit->IsInWorld() || !_unit->IsAlive()))
Reset();
if (!_unit)
{
_unit = AI_VALUE2(Unit*, "find target", "thaddius");
if (!_unit)
return false;
}
feugen = AI_VALUE2(Unit*, "find target", "feugen");
stalagg = AI_VALUE2(Unit*, "find target", "stalagg");
return true;
}
bool IsPhasePet() { return (feugen && feugen->IsAlive()) || (stalagg && stalagg->IsAlive()); }
bool IsPhaseTransition()
{
if (IsPhasePet())
return false;
return _unit && _unit->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
}
bool IsPhaseThaddius() { return !IsPhasePet() && !IsPhaseTransition(); }
Unit* GetNearestPet()
{
Unit* unit = nullptr;
if (feugen && feugen->IsAlive())
unit = feugen;
if (stalagg && stalagg->IsAlive() && (!feugen || bot->GetDistance(stalagg) < bot->GetDistance(feugen)))
unit = stalagg;
return unit;
}
std::pair<float, float> PetPhaseGetPosForTank()
{
if (GetNearestPet() == feugen)
return tankPosFeugen;
return tankPosStalagg;
}
std::pair<float, float> PetPhaseGetPosForRanged()
{
if (GetNearestPet() == feugen)
return rangedPosFeugen;
return rangedPosStalagg;
}
protected:
void Reset()
{
_unit = nullptr;
feugen = nullptr;
stalagg = nullptr;
}
Unit* _unit = nullptr;
Unit* feugen = nullptr;
Unit* stalagg = nullptr;
};
#endif

View File

@ -0,0 +1,165 @@
#ifndef _PLAYERBOT_RAIDNAXXSPELLIDS_H
#define _PLAYERBOT_RAIDNAXXSPELLIDS_H
#include <initializer_list>
#include "PlayerbotAI.h"
// use src/server/scripts/Northrend/Naxxramas/naxxramas.h for CreatureId, NaxxramasSay, NaxxramasEvent, NaxxramasMisc
namespace NaxxSpellIds
{
// Heigan
static constexpr uint32 Eruption10 = 29371;
/*
SPELL_SPELL_DISRUPTION = 29310,
SPELL_DECREPIT_FEVER = 29998,
SPELL_PLAGUE_CLOUD = 29350,
SPELL_TELEPORT_SELF = 30211
*/
// Grobbulus
static constexpr uint32 PoisonCloud = 28240;
// Thaddius polarity
static constexpr uint32 PositiveCharge10 = 28059;
static constexpr uint32 PositiveCharge25 = 28062;
static constexpr uint32 PositiveChargeStack = 29659;
static constexpr uint32 NegativeCharge10 = 28084;
static constexpr uint32 NegativeCharge25 = 28085;
static constexpr uint32 NegativeChargeStack = 29660;
/*
SPELL_MAGNETIC_PULL = 28337,
SPELL_TESLA_SHOCK = 28099,
SPELL_SHOCK_VISUAL = 28159,
// Stalagg
SPELL_POWER_SURGE = 54529,
SPELL_STALAGG_CHAIN = 28096,
// Feugen
SPELL_STATIC_FIELD = 28135,
SPELL_FEUGEN_CHAIN = 28111,
// Thaddius
SPELL_POLARITY_SHIFT = 28089,
SPELL_BALL_LIGHTNING = 28299,
SPELL_CHAIN_LIGHTNING = 28167,
SPELL_BERSERK = 27680,
SPELL_THADDIUS_VISUAL_LIGHTNING = 28136,
SPELL_THADDIUS_SPAWN_STUN = 28160,
SPELL_POSITIVE_CHARGE = 28062,
SPELL_POSITIVE_CHARGE_STACK = 29659,
SPELL_NEGATIVE_CHARGE = 28085,
SPELL_NEGATIVE_CHARGE_STACK = 29660,
SPELL_POSITIVE_POLARITY = 28059,
SPELL_NEGATIVE_POLARITY = 28084
*/
// Sapphiron
static constexpr uint32 Icebolt10 = 28522;
static constexpr uint32 Icebolt25 = 28526;
static constexpr uint32 Chill25 = 55699;
/*
// Fight
SPELL_FROST_AURA = 28531,
SPELL_CLEAVE = 19983,
SPELL_TAIL_SWEEP = 55697,
SPELL_SUMMON_BLIZZARD = 28560,
SPELL_LIFE_DRAIN = 28542,
SPELL_BERSERK = 26662,
// Ice block
SPELL_ICEBOLT_CAST = 28526,
SPELL_ICEBOLT_TRIGGER = 28522,
SPELL_FROST_MISSILE = 30101,
SPELL_FROST_EXPLOSION = 28524,
// Visuals
SPELL_SAPPHIRON_DIES = 29357
*/
// Gluth
static constexpr uint32 Decimate10 = 28374;
static constexpr uint32 Decimate25 = 54426;
static constexpr uint32 Decimate25Alt = 28375;
static constexpr uint32 MortalWound10 = 25646;
static constexpr uint32 MortalWound25 = 54378;
/*
SPELL_MORTAL_WOUND = 25646,
SPELL_ENRAGE = 28371,
SPELL_DECIMATE = 28374,
SPELL_DECIMATE_DAMAGE = 28375,
SPELL_BERSERK = 26662,
SPELL_INFECTED_WOUND = 29306,
SPELL_CHOW_SEARCHER = 28404
*/
// Anub'Rekhan
static constexpr uint32 LocustSwarm10 = 28785;
static constexpr uint32 LocustSwarm10Alt = 28786;
static constexpr uint32 LocustSwarm25 = 54021; // 25-man Locust Swarm
/*
SPELL_IMPALE = 28783,
SPELL_LOCUST_SWARM = 28785,
SPELL_SUMMON_CORPSE_SCARABS_5 = 29105,
SPELL_SUMMON_CORPSE_SCARABS_10 = 28864,
SPELL_BERSERK = 26662
ACHIEV_TIMED_START_EVENT = 9891,
EVENT_SPAWN_CRYPT_GUARDS_1 = 0,
EVENT_BERSERK = 1,
////
Position const cryptguardPositions[] = {
{ 3299.732f, -3502.489f, 287.077f, 2.378f },
{ 3299.086f, -3450.929f, 287.077f, 3.999f },
{ 3331.217f, -3476.607f, 287.074f, 3.269f }
};
*/
// Loatheb
static constexpr uint32 NecroticAura10 = 55593;
/*
SPELL_NECROTIC_AURA = 55593,
SPELL_SUMMON_SPORE = 29234,
SPELL_DEATHBLOOM = 29865,
SPELL_INEVITABLE_DOOM = 29204,
SPELL_BERSERK = 26662
*/
inline bool HasAnyAura(PlayerbotAI* botAI, Unit* unit, std::initializer_list<uint32> spellIds)
{
if (!botAI || !unit)
return false;
for (uint32 spellId : spellIds)
{
if (botAI->HasAura(spellId, unit))
return true;
}
return false;
}
inline Aura* GetAnyAura(Unit* unit, std::initializer_list<uint32> spellIds)
{
if (!unit)
return nullptr;
for (uint32 spellId : spellIds)
{
if (Aura* aura = unit->GetAura(spellId))
return aura;
}
return nullptr;
}
inline bool MatchesAnySpellId(SpellInfo const* info, std::initializer_list<uint32> spellIds)
{
if (!info)
return false;
for (uint32 spellId : spellIds)
{
if (info->Id == spellId)
return true;
}
return false;
}
} // namespace NaxxSpellIds
#endif

View File

@ -8,6 +8,7 @@
#include "RaidKarazhanStrategy.h"
#include "RaidGruulsLairStrategy.h"
#include "RaidMagtheridonStrategy.h"
#include "RaidNaxxStrategy.h"
#include "RaidSSCStrategy.h"
#include "RaidTempestKeepStrategy.h"
#include "RaidOsStrategy.h"
@ -28,6 +29,7 @@ public:
creators["karazhan"] = &RaidStrategyContext::karazhan;
creators["gruulslair"] = &RaidStrategyContext::gruulslair;
creators["magtheridon"] = &RaidStrategyContext::magtheridon;
creators["naxx"] = &RaidStrategyContext::naxx;
creators["ssc"] = &RaidStrategyContext::ssc;
creators["tempestkeep"] = &RaidStrategyContext::tempestkeep;
creators["wotlk-os"] = &RaidStrategyContext::wotlk_os;
@ -45,6 +47,7 @@ private:
static Strategy* karazhan(PlayerbotAI* botAI) { return new RaidKarazhanStrategy(botAI); }
static Strategy* gruulslair(PlayerbotAI* botAI) { return new RaidGruulsLairStrategy(botAI); }
static Strategy* magtheridon(PlayerbotAI* botAI) { return new RaidMagtheridonStrategy(botAI); }
static Strategy* naxx(PlayerbotAI* botAI) { return new RaidNaxxStrategy(botAI); }
static Strategy* ssc(PlayerbotAI* botAI) { return new RaidSSCStrategy(botAI); }
static Strategy* tempestkeep(PlayerbotAI* botAI) { return new RaidTempestKeepStrategy(botAI); }
static Strategy* wotlk_os(PlayerbotAI* botAI) { return new RaidOsStrategy(botAI); }

View File

@ -598,9 +598,9 @@ uint32 ChatHelper::parseSlot(std::string const text)
return EQUIPMENT_SLOT_END;
}
bool ChatHelper::parseable(std::string const text)
bool ChatHelper::parseableItem(std::string const text)
{
return text.find("|H") != std::string::npos || text == "questitem" || text == "ammo" ||
return text.find("|Hitem:") != std::string::npos || text == "questitem" || text == "ammo" ||
substrContainsInMap<uint32>(text, consumableSubClasses) ||
substrContainsInMap<uint32>(text, tradeSubClasses) || substrContainsInMap<uint32>(text, itemQualities) ||
substrContainsInMap<uint32>(text, slots) || substrContainsInMap<ChatMsg>(text, chats) ||

View File

@ -66,7 +66,7 @@ public:
static uint32 parseSlot(std::string const text);
uint32 parseSkill(std::string const text);
static bool parseable(std::string const text);
static bool parseableItem(std::string const text);
void eraseAllSubStr(std::string& mainStr, std::string const toErase);

View File

@ -4,59 +4,17 @@
*/
#include "AiObjectContext.h"
#include "ActionContext.h"
#include "ChatActionContext.h"
#include "ChatTriggerContext.h"
#include "Helpers.h"
#include "DKAiObjectContext.h"
#include "DruidAiObjectContext.h"
#include "HunterAiObjectContext.h"
#include "MageAiObjectContext.h"
#include "PaladinAiObjectContext.h"
#include "Playerbots.h"
#include "PriestAiObjectContext.h"
#include "RogueAiObjectContext.h"
#include "ShamanAiObjectContext.h"
#include "SharedValueContext.h"
#include "StrategyContext.h"
#include "TriggerContext.h"
#include "ValueContext.h"
#include "WarlockAiObjectContext.h"
#include "WarriorAiObjectContext.h"
#include "WorldPacketActionContext.h"
#include "WorldPacketTriggerContext.h"
#include "Ai/Dungeon/DungeonStrategyContext.h"
#include "Ai/Dungeon/WotlkDungeonActionContext.h"
#include "Ai/Dungeon/WotlkDungeonTriggerContext.h"
#include "Ai/Raid/RaidStrategyContext.h"
#include "Ai/Raid/Aq20/RaidAq20ActionContext.h"
#include "Ai/Raid/Aq20/RaidAq20TriggerContext.h"
#include "Ai/Raid/MoltenCore/RaidMcActionContext.h"
#include "Ai/Raid/MoltenCore/RaidMcTriggerContext.h"
#include "Ai/Raid/BlackwingLair/RaidBwlActionContext.h"
#include "Ai/Raid/BlackwingLair/RaidBwlTriggerContext.h"
#include "Ai/Raid/Karazhan/RaidKarazhanActionContext.h"
#include "Ai/Raid/Karazhan/RaidKarazhanTriggerContext.h"
#include "Ai/Raid/GruulsLair/RaidGruulsLairActionContext.h"
#include "Ai/Raid/GruulsLair/RaidGruulsLairTriggerContext.h"
#include "Ai/Raid/Magtheridon/RaidMagtheridonActionContext.h"
#include "Ai/Raid/Magtheridon/RaidMagtheridonTriggerContext.h"
#include "Ai/Raid/SerpentshrineCavern/RaidSSCActionContext.h"
#include "Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h"
#include "Ai/Raid/TempestKeep/RaidTempestKeepActionContext.h"
#include "Ai/Raid/TempestKeep/RaidTempestKeepTriggerContext.h"
#include "Ai/Raid/EyeOfEternity/RaidEoEActionContext.h"
#include "Ai/Raid/EyeOfEternity/RaidEoETriggerContext.h"
#include "Ai/Raid/VaultOfArchavon/RaidVoAActionContext.h"
#include "Ai/Raid/VaultOfArchavon/RaidVoATriggerContext.h"
#include "Ai/Raid/ObsidianSanctum/RaidOsActionContext.h"
#include "Ai/Raid/ObsidianSanctum/RaidOsTriggerContext.h"
#include "Ai/Raid/Ulduar/RaidUlduarActionContext.h"
#include "Ai/Raid/Ulduar/RaidUlduarTriggerContext.h"
#include "Ai/Raid/Onyxia/RaidOnyxiaActionContext.h"
#include "Ai/Raid/Onyxia/RaidOnyxiaTriggerContext.h"
#include "Ai/Raid/Icecrown/RaidIccActionContext.h"
#include "Ai/Raid/Icecrown/RaidIccTriggerContext.h"
SharedNamedObjectContextList<Strategy> AiObjectContext::sharedStrategyContexts;
SharedNamedObjectContextList<Action> AiObjectContext::sharedActionContexts;
@ -98,93 +56,6 @@ void AiObjectContext::BuildSharedContexts()
BuildSharedValueContexts(sharedValueContexts);
}
void AiObjectContext::BuildSharedStrategyContexts(SharedNamedObjectContextList<Strategy>& strategyContexts)
{
strategyContexts.Add(new StrategyContext());
strategyContexts.Add(new MovementStrategyContext());
strategyContexts.Add(new AssistStrategyContext());
strategyContexts.Add(new QuestStrategyContext());
strategyContexts.Add(new DungeonStrategyContext());
strategyContexts.Add(new RaidStrategyContext());
}
void AiObjectContext::BuildSharedActionContexts(SharedNamedObjectContextList<Action>& actionContexts)
{
actionContexts.Add(new ActionContext());
actionContexts.Add(new ChatActionContext());
actionContexts.Add(new WorldPacketActionContext());
actionContexts.Add(new RaidAq20ActionContext());
actionContexts.Add(new RaidMcActionContext());
actionContexts.Add(new RaidBwlActionContext());
actionContexts.Add(new RaidKarazhanActionContext());
actionContexts.Add(new RaidGruulsLairActionContext());
actionContexts.Add(new RaidMagtheridonActionContext());
actionContexts.Add(new RaidSSCActionContext());
actionContexts.Add(new RaidTempestKeepActionContext());
actionContexts.Add(new RaidOsActionContext());
actionContexts.Add(new RaidEoEActionContext());
actionContexts.Add(new RaidVoAActionContext());
actionContexts.Add(new RaidUlduarActionContext());
actionContexts.Add(new RaidOnyxiaActionContext());
actionContexts.Add(new RaidIccActionContext());
actionContexts.Add(new WotlkDungeonUKActionContext());
actionContexts.Add(new WotlkDungeonNexActionContext());
actionContexts.Add(new WotlkDungeonANActionContext());
actionContexts.Add(new WotlkDungeonOKActionContext());
actionContexts.Add(new WotlkDungeonDTKActionContext());
actionContexts.Add(new WotlkDungeonVHActionContext());
actionContexts.Add(new WotlkDungeonGDActionContext());
actionContexts.Add(new WotlkDungeonHoSActionContext());
actionContexts.Add(new WotlkDungeonHoLActionContext());
actionContexts.Add(new WotlkDungeonOccActionContext());
actionContexts.Add(new WotlkDungeonUPActionContext());
actionContexts.Add(new WotlkDungeonCoSActionContext());
actionContexts.Add(new WotlkDungeonFoSActionContext());
actionContexts.Add(new WotlkDungeonPoSActionContext());
actionContexts.Add(new WotlkDungeonToCActionContext());
}
void AiObjectContext::BuildSharedTriggerContexts(SharedNamedObjectContextList<Trigger>& triggerContexts)
{
triggerContexts.Add(new TriggerContext());
triggerContexts.Add(new ChatTriggerContext());
triggerContexts.Add(new WorldPacketTriggerContext());
triggerContexts.Add(new RaidAq20TriggerContext());
triggerContexts.Add(new RaidMcTriggerContext());
triggerContexts.Add(new RaidBwlTriggerContext());
triggerContexts.Add(new RaidKarazhanTriggerContext());
triggerContexts.Add(new RaidGruulsLairTriggerContext());
triggerContexts.Add(new RaidMagtheridonTriggerContext());
triggerContexts.Add(new RaidSSCTriggerContext());
triggerContexts.Add(new RaidTempestKeepTriggerContext());
triggerContexts.Add(new RaidOsTriggerContext());
triggerContexts.Add(new RaidEoETriggerContext());
triggerContexts.Add(new RaidVoATriggerContext());
triggerContexts.Add(new RaidUlduarTriggerContext());
triggerContexts.Add(new RaidOnyxiaTriggerContext());
triggerContexts.Add(new RaidIccTriggerContext());
triggerContexts.Add(new WotlkDungeonUKTriggerContext());
triggerContexts.Add(new WotlkDungeonNexTriggerContext());
triggerContexts.Add(new WotlkDungeonANTriggerContext());
triggerContexts.Add(new WotlkDungeonOKTriggerContext());
triggerContexts.Add(new WotlkDungeonDTKTriggerContext());
triggerContexts.Add(new WotlkDungeonVHTriggerContext());
triggerContexts.Add(new WotlkDungeonGDTriggerContext());
triggerContexts.Add(new WotlkDungeonHoSTriggerContext());
triggerContexts.Add(new WotlkDungeonHoLTriggerContext());
triggerContexts.Add(new WotlkDungeonOccTriggerContext());
triggerContexts.Add(new WotlkDungeonUPTriggerContext());
triggerContexts.Add(new WotlkDungeonCoSTriggerContext());
triggerContexts.Add(new WotlkDungeonFoSTriggerContext());
triggerContexts.Add(new WotlkDungeonPoSTriggerContext());
triggerContexts.Add(new WotlkDungeonToCTriggerContext());
}
void AiObjectContext::BuildSharedValueContexts(SharedNamedObjectContextList<UntypedValue>& valueContexts)
{
valueContexts.Add(new ValueContext());
}
std::vector<std::string> AiObjectContext::Save()
{
std::vector<std::string> result;

View File

@ -0,0 +1,57 @@
#include "AiObjectContext.h"
#include "ActionContext.h"
#include "ChatActionContext.h"
#include "WorldPacketActionContext.h"
#include "Ai/Raid/Aq20/RaidAq20ActionContext.h"
#include "Ai/Raid/MoltenCore/RaidMcActionContext.h"
#include "Ai/Raid/BlackwingLair/RaidBwlActionContext.h"
#include "Ai/Raid/Karazhan/RaidKarazhanActionContext.h"
#include "Ai/Raid/GruulsLair/RaidGruulsLairActionContext.h"
#include "Ai/Raid/Naxxramas/RaidNaxxActionContext.h"
#include "Ai/Raid/Magtheridon/RaidMagtheridonActionContext.h"
#include "Ai/Raid/SerpentshrineCavern/RaidSSCActionContext.h"
#include "Ai/Raid/TempestKeep/RaidTempestKeepActionContext.h"
#include "Ai/Raid/ObsidianSanctum/RaidOsActionContext.h"
#include "Ai/Raid/EyeOfEternity/RaidEoEActionContext.h"
#include "Ai/Raid/VaultOfArchavon/RaidVoAActionContext.h"
#include "Ai/Raid/Ulduar/RaidUlduarActionContext.h"
#include "Ai/Raid/Onyxia/RaidOnyxiaActionContext.h"
#include "Ai/Raid/Icecrown/RaidIccActionContext.h"
#include "Ai/Dungeon/WotlkDungeonActionContext.h"
void AiObjectContext::BuildSharedActionContexts(SharedNamedObjectContextList<Action>& actionContexts)
{
actionContexts.Add(new ActionContext());
actionContexts.Add(new ChatActionContext());
actionContexts.Add(new WorldPacketActionContext());
actionContexts.Add(new RaidAq20ActionContext());
actionContexts.Add(new RaidMcActionContext());
actionContexts.Add(new RaidBwlActionContext());
actionContexts.Add(new RaidKarazhanActionContext());
actionContexts.Add(new RaidGruulsLairActionContext());
actionContexts.Add(new RaidMagtheridonActionContext());
actionContexts.Add(new RaidSSCActionContext());
actionContexts.Add(new RaidTempestKeepActionContext());
actionContexts.Add(new RaidNaxxActionContext());
actionContexts.Add(new RaidOsActionContext());
actionContexts.Add(new RaidEoEActionContext());
actionContexts.Add(new RaidVoAActionContext());
actionContexts.Add(new RaidUlduarActionContext());
actionContexts.Add(new RaidOnyxiaActionContext());
actionContexts.Add(new RaidIccActionContext());
actionContexts.Add(new WotlkDungeonUKActionContext());
actionContexts.Add(new WotlkDungeonNexActionContext());
actionContexts.Add(new WotlkDungeonANActionContext());
actionContexts.Add(new WotlkDungeonOKActionContext());
actionContexts.Add(new WotlkDungeonDTKActionContext());
actionContexts.Add(new WotlkDungeonVHActionContext());
actionContexts.Add(new WotlkDungeonGDActionContext());
actionContexts.Add(new WotlkDungeonHoSActionContext());
actionContexts.Add(new WotlkDungeonHoLActionContext());
actionContexts.Add(new WotlkDungeonOccActionContext());
actionContexts.Add(new WotlkDungeonUPActionContext());
actionContexts.Add(new WotlkDungeonCoSActionContext());
actionContexts.Add(new WotlkDungeonFoSActionContext());
actionContexts.Add(new WotlkDungeonPoSActionContext());
actionContexts.Add(new WotlkDungeonToCActionContext());
}

View File

@ -0,0 +1,14 @@
#include "AiObjectContext.h"
#include "StrategyContext.h"
#include "Ai/Dungeon/DungeonStrategyContext.h"
#include "Ai/Raid/RaidStrategyContext.h"
void AiObjectContext::BuildSharedStrategyContexts(SharedNamedObjectContextList<Strategy>& strategyContexts)
{
strategyContexts.Add(new StrategyContext());
strategyContexts.Add(new MovementStrategyContext());
strategyContexts.Add(new AssistStrategyContext());
strategyContexts.Add(new QuestStrategyContext());
strategyContexts.Add(new DungeonStrategyContext());
strategyContexts.Add(new RaidStrategyContext());
}

View File

@ -0,0 +1,57 @@
#include "AiObjectContext.h"
#include "TriggerContext.h"
#include "ChatTriggerContext.h"
#include "WorldPacketTriggerContext.h"
#include "Ai/Raid/Aq20/RaidAq20TriggerContext.h"
#include "Ai/Raid/MoltenCore/RaidMcTriggerContext.h"
#include "Ai/Raid/BlackwingLair/RaidBwlTriggerContext.h"
#include "Ai/Raid/Karazhan/RaidKarazhanTriggerContext.h"
#include "Ai/Raid/GruulsLair/RaidGruulsLairTriggerContext.h"
#include "Ai/Raid/Magtheridon/RaidMagtheridonTriggerContext.h"
#include "Ai/Raid/Naxxramas/RaidNaxxTriggerContext.h"
#include "Ai/Raid/SerpentshrineCavern/RaidSSCTriggerContext.h"
#include "Ai/Raid/TempestKeep/RaidTempestKeepTriggerContext.h"
#include "Ai/Raid/ObsidianSanctum/RaidOsTriggerContext.h"
#include "Ai/Raid/EyeOfEternity/RaidEoETriggerContext.h"
#include "Ai/Raid/VaultOfArchavon/RaidVoATriggerContext.h"
#include "Ai/Raid/Ulduar/RaidUlduarTriggerContext.h"
#include "Ai/Raid/Onyxia/RaidOnyxiaTriggerContext.h"
#include "Ai/Raid/Icecrown/RaidIccTriggerContext.h"
#include "Ai/Dungeon/WotlkDungeonTriggerContext.h"
void AiObjectContext::BuildSharedTriggerContexts(SharedNamedObjectContextList<Trigger>& triggerContexts)
{
triggerContexts.Add(new TriggerContext());
triggerContexts.Add(new ChatTriggerContext());
triggerContexts.Add(new WorldPacketTriggerContext());
triggerContexts.Add(new RaidAq20TriggerContext());
triggerContexts.Add(new RaidMcTriggerContext());
triggerContexts.Add(new RaidBwlTriggerContext());
triggerContexts.Add(new RaidKarazhanTriggerContext());
triggerContexts.Add(new RaidGruulsLairTriggerContext());
triggerContexts.Add(new RaidMagtheridonTriggerContext());
triggerContexts.Add(new RaidNaxxTriggerContext());
triggerContexts.Add(new RaidSSCTriggerContext());
triggerContexts.Add(new RaidTempestKeepTriggerContext());
triggerContexts.Add(new RaidOsTriggerContext());
triggerContexts.Add(new RaidEoETriggerContext());
triggerContexts.Add(new RaidVoATriggerContext());
triggerContexts.Add(new RaidUlduarTriggerContext());
triggerContexts.Add(new RaidOnyxiaTriggerContext());
triggerContexts.Add(new RaidIccTriggerContext());
triggerContexts.Add(new WotlkDungeonUKTriggerContext());
triggerContexts.Add(new WotlkDungeonNexTriggerContext());
triggerContexts.Add(new WotlkDungeonANTriggerContext());
triggerContexts.Add(new WotlkDungeonOKTriggerContext());
triggerContexts.Add(new WotlkDungeonDTKTriggerContext());
triggerContexts.Add(new WotlkDungeonVHTriggerContext());
triggerContexts.Add(new WotlkDungeonGDTriggerContext());
triggerContexts.Add(new WotlkDungeonHoSTriggerContext());
triggerContexts.Add(new WotlkDungeonHoLTriggerContext());
triggerContexts.Add(new WotlkDungeonOccTriggerContext());
triggerContexts.Add(new WotlkDungeonUPTriggerContext());
triggerContexts.Add(new WotlkDungeonCoSTriggerContext());
triggerContexts.Add(new WotlkDungeonFoSTriggerContext());
triggerContexts.Add(new WotlkDungeonPoSTriggerContext());
triggerContexts.Add(new WotlkDungeonToCTriggerContext());
}

View File

@ -0,0 +1,7 @@
#include "AiObjectContext.h"
#include "ValueContext.h"
void AiObjectContext::BuildSharedValueContexts(SharedNamedObjectContextList<UntypedValue>& valueContexts)
{
valueContexts.Add(new ValueContext());
}

View File

@ -30,7 +30,7 @@ bool ExternalEventHelper::ParseChatCommand(std::string const command, Player* ow
return true;
}
if (!ChatHelper::parseable(command))
if (!ChatHelper::parseableItem(command))
return false;
HandleCommand("c", command, owner);

View File

@ -9,6 +9,7 @@
#include "ArenaTeamMgr.h"
#include "DatabaseEnv.h"
#include "PlayerbotAI.h"
#include "RaceMgr.h"
#include "ScriptMgr.h"
#include "SharedDefines.h"
#include "SocialMgr.h"
@ -60,7 +61,7 @@ Player* RandomPlayerbotFactory::CreateRandomBot(WorldSession* session, uint8 cls
const bool alliance = static_cast<bool>(urand(0, 1));
std::vector<uint8> raceOptions;
for (uint8 race = RACE_HUMAN; race < MAX_RACES; ++race)
for (uint8 race = RACE_HUMAN; race < sRaceMgr->GetMaxRaces(); ++race)
{
// skip disabled with config races
if ((1 << (race - 1)) & sWorld->getIntConfig(CONFIG_CHARACTER_CREATING_DISABLED_RACEMASK))

View File

@ -1532,6 +1532,21 @@ std::vector<std::string> PlayerbotAI::GetStrategies(BotState type)
void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
{
static const std::vector<std::string> allInstanceStrategies =
{
"aq20", "bwl", "karazhan", "gruulslair", "icc", "magtheridon", "moltencore",
"naxx", "onyxia", "ssc", "tempestkeep", "ulduar", "voa", "wotlk-an", "wotlk-cos",
"wotlk-dtk", "wotlk-eoe", "wotlk-fos", "wotlk-gd", "wotlk-hol", "wotlk-hor",
"wotlk-hos", "wotlk-nex", "wotlk-occ", "wotlk-ok", "wotlk-os", "wotlk-pos",
"wotlk-toc", "wotlk-uk", "wotlk-up", "wotlk-vh"
};
for (const std::string& strat : allInstanceStrategies)
{
engines[BOT_STATE_COMBAT]->removeStrategy(strat);
engines[BOT_STATE_NON_COMBAT]->removeStrategy(strat);
}
std::string strategyName;
switch (mapId)
{
@ -1550,6 +1565,9 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
case 532:
strategyName = "karazhan"; // Karazhan
break;
case 533:
strategyName = "naxx"; // Naxxramas
break;
case 544:
strategyName = "magtheridon"; // Magtheridon's Lair
break;
@ -1628,10 +1646,13 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
default:
break;
}
if (strategyName.empty())
return;
engines[BOT_STATE_COMBAT]->addStrategy(strategyName);
engines[BOT_STATE_NON_COMBAT]->addStrategy(strategyName);
if (tellMaster && !strategyName.empty())
{
std::ostringstream out;

View File

@ -37,6 +37,7 @@
#include "PlayerbotFactory.h"
#include "Playerbots.h"
#include "Position.h"
#include "RaceMgr.h"
#include "Random.h"
#include "RandomPlayerbotFactory.h"
#include "ServerFacade.h"
@ -1995,7 +1996,7 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
}
// add all initial position
for (uint32 i = 1; i < MAX_RACES; i++)
for (uint32 i = 1; i < sRaceMgr->GetMaxRaces(); i++)
{
for (uint32 j = 1; j < MAX_CLASSES; j++)
{
@ -2008,7 +2009,7 @@ void RandomPlayerbotMgr::PrepareTeleportCache()
for (int32 l = 1; l <= 5; l++)
{
if ((1 << (i - 1)) & RACEMASK_ALLIANCE)
if ((1 << (i - 1)) & sRaceMgr->GetAllianceRaceMask())
allianceStarterPerLevelCache[(uint8)l].push_back(pos);
else
hordeStarterPerLevelCache[(uint8)l].push_back(pos);
@ -3126,7 +3127,7 @@ void RandomPlayerbotMgr::PrintStats()
std::map<uint8, uint32> lvlPerRace;
std::map<uint8, uint32> lvlPerClass;
for (uint8 race = RACE_HUMAN; race < MAX_RACES; ++race)
for (uint8 race = RACE_HUMAN; race < sRaceMgr->GetMaxRaces(); ++race)
{
perRace[race] = 0;
lvlPerRace[race] = 0;
@ -3273,7 +3274,7 @@ void RandomPlayerbotMgr::PrintStats()
}
LOG_INFO("playerbots", "Bots race:");
for (uint8 race = RACE_HUMAN; race < MAX_RACES; ++race)
for (uint8 race = RACE_HUMAN; race < sRaceMgr->GetMaxRaces(); ++race)
{
if (perRace[race])
{

View File

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

View File

@ -14,6 +14,7 @@
#include "MapMgr.h"
#include "PathGenerator.h"
#include "Playerbots.h"
#include "RaceMgr.h"
#include "TransportMgr.h"
#include "VMapFactory.h"
#include "VMapMgr2.h"
@ -3335,7 +3336,7 @@ void TravelMgr::LoadQuestTravelTable()
std::ostringstream out;
for (uint8 race = RACE_HUMAN; race < MAX_RACES; race++)
for (uint8 race = RACE_HUMAN; race < sRaceMgr->GetMaxRaces(); race++)
{
for (uint8 cls = CLASS_WARRIOR; cls < MAX_CLASSES; ++cls)
{

View File

@ -11,6 +11,7 @@
#include "BudgetValues.h"
#include "PathGenerator.h"
#include "Playerbots.h"
#include "RaceMgr.h"
#include "ServerFacade.h"
#include "TransportMgr.h"
@ -1660,7 +1661,7 @@ void TravelNodeMap::generateStartNodes()
startNames[RACE_GNOME] = "Dwarf and Gnome";
startNames[RACE_TROLL] = "Orc and Troll";
for (uint32 i = 0; i < MAX_RACES; i++)
for (uint32 i = 0; i < sRaceMgr->GetMaxRaces(); i++)
{
for (uint32 j = 0; j < MAX_CLASSES; j++)
{

View File

@ -180,8 +180,13 @@ bool PlayerbotAIConfig::Initialize()
"165739,165738,175245,175970,176325,176327,123329,2560"),
disallowedGameObjects);
LoadSet<std::set<uint32>>(
sConfigMgr->GetOption<std::string>("AiPlayerbot.AttunementQuests", "10279,10277,10282,10283,10284,10285,10296,10297,10298,11481,11482,11488,11490,11492,10901"),
sConfigMgr->GetOption<std::string>("AiPlayerbot.AttunementQuests", "10279,10277,10282,10283,10284,10285,10296,10297,10298,11481,11482,11488,11490,11492,10901,10888,10445,10985"),
attunementQuests);
LoadSet<std::set<uint32>>(
sConfigMgr->GetOption<std::string>("AiPlayerbot.UnobtainableItems", "12468,46978"),
unobtainableItems);
botAutologin = sConfigMgr->GetOption<bool>("AiPlayerbot.BotAutologin", false);
randomBotAutologin = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotAutologin", true);
minRandomBots = sConfigMgr->GetOption<int32>("AiPlayerbot.MinRandomBots", 500);

View File

@ -99,6 +99,7 @@ public:
bool tellWhenAvoidAoe;
std::set<uint32> disallowedGameObjects;
std::set<uint32> attunementQuests;
std::set<uint32> unobtainableItems;
uint32 openGoSpell;
bool randomBotAutologin;

View File

@ -17,6 +17,7 @@
#include "Playerbots.h"
#include "BattlefieldScript.h"
#include "Channel.h"
#include "Config.h"
#include "DatabaseEnv.h"
@ -518,12 +519,20 @@ public:
void OnBattlegroundEnd(Battleground* bg, TeamId /*winnerTeam*/) override { bgStrategies.erase(bg->GetInstanceID()); }
};
// Workaround for missing InitEnabledHooksIfNeeded for new BattlefieldScript in ScriptMgr
class PlayerbotsBattlefieldScript : public BattlefieldScript
{
public:
PlayerbotsBattlefieldScript() : BattlefieldScript("PlayerbotsBattlefieldScript") { }
};
void AddPlayerbotsSecureLoginScripts();
void AddSC_TempestKeepBotScripts();
void AddPlayerbotsScripts()
{
new PlayerbotsBattlefieldScript();
new PlayerbotsDatabaseScript();
new PlayerbotsPlayerScript();
new PlayerbotsMiscScript();