Compare commits

...

64 Commits

Author SHA1 Message Date
bash
502df01436 feat(Core/Travel): Soft-bias NAV_GROUND_STEEP + NAV_WATER at bot PathGenerator sites 2026-06-05 09:57:42 +02:00
bash
b78ecda436 fix(Core/Travel): Hoist portal/transport cheat above 2-point reject 2026-06-05 09:57:42 +02:00
bash
c956860d2d fix(Core/Travel): Match cmangos buildPath stitching, drop 75y guard 2026-06-05 09:57:42 +02:00
bash
692abfcc1a fix(Core/Travel): Preserve walk paths from taxi-path overwrite 2026-06-05 09:57:42 +02:00
bash
4444f2aae5 chore(Core/Travel): Warn admins to shutdown after generatenode 2026-06-05 09:57:42 +02:00
bash
5e93743f15 fix(Core/Travel): Skip 5y dedup when loading nodes from DB 2026-06-05 09:57:42 +02:00
bash
74bf2002f5 chore(DB/Travel): Temporarily disable Aldrassil ramp anchors 2026-06-05 09:57:42 +02:00
bash
973d87f8f5 fix(Core/Travel): Drop 2-point check, keep last-segment teleport guard 2026-06-05 09:57:42 +02:00
bash
28ff3b00c4 fix(Core/Travel): Reject paths with >75y final-segment teleport jumps 2026-06-05 09:57:42 +02:00
bash
bd1364db06 fix(Core/Travel): Reject 2-point BuildShortcut paths between non-adjacent nodes 2026-06-05 09:57:41 +02:00
bash
bb69ed12d6 chore(Core/Travel): Bump 2-point shortcut threshold to 75y 2026-06-05 09:57:41 +02:00
bash
cb442e8461 fix(Core/Travel): Reject 2-point BuildShortcut teleports in chained probe 2026-06-05 09:57:41 +02:00
bash
6f051f9d2b Revert non-progress chained-probe detection (broke valid paths) 2026-06-05 09:57:41 +02:00
bash
c9775fa66c fix(Core/Travel): Loosen chained-probe non-progress threshold 2026-06-05 09:57:41 +02:00
bash
2de92e681f fix(Core/Travel): Bail chained probe on non-progress oscillation 2026-06-05 09:57:41 +02:00
bash
7b57fe4b18 fix(Core/Travel): Chunk all saveNodeStore phases (deletes, nodes, links) 2026-06-05 09:57:41 +02:00
bash
273fad4bad fix(Core/Travel): Chunk saveNodeStore path inserts to avoid mega-tx 2026-06-05 09:57:41 +02:00
bash
c7faad9608 feat(DB/Travel): Add Aldrassil ramp travelnode anchors 2026-06-05 09:57:41 +02:00
bash
30489db77d chore(Core/Debug): Compact debug-move whisper format 2026-06-05 09:57:41 +02:00
bash
78fd6eff23 feat(Core/Travel): Sparse-segment clip in LaunchWalkSpline 2026-06-05 09:57:41 +02:00
bash
221ee82b83 feat(Core/RPG): Prefix-trim and sparse-segment clip on path dispatch 2026-06-05 09:57:41 +02:00
bash
7b13801d50 feat(Core/RPG): Port cmangos 8-angle LOS+navmesh-snap to MoveWorldObjectTo 2026-06-05 09:57:41 +02:00
bash
727232fe80 chore(Core/RPG): Loosen Z-mismatch threshold from 5y to 10y 2026-06-05 09:57:41 +02:00
bash
6ab59e61a9 fix(Core/RPG): Reject mmap paths whose endpoint Z misses dest 2026-06-05 09:57:41 +02:00
bash
b7c17681cf fix(Core/RPG): Reject mmap paths that LOS-fail any segment 2026-06-05 09:57:41 +02:00
bash
8aa78f6b39 feat(Core/RPG): Switch POI when current cluster is empty 2026-06-05 09:57:41 +02:00
bash
30eaebe3d5 fix(Core/RPG): Stop next to quest objects instead of on top of them 2026-06-05 09:57:41 +02:00
bash
7623c6babe chore: Drop bot movement console logs 2026-06-05 09:57:41 +02:00
bash
1bfb47e937 chore: Tighten comments in travel and movement code 2026-06-05 09:57:41 +02:00
bash
20a4efe149 chore(Core/Travel): Drop cmangos reference in RefineWalkPoints comment 2026-06-05 09:57:41 +02:00
bash
73bf6f0606 fix(Core/RPG): LOS check on MoveRandomNear samples to avoid tree tunneling 2026-06-05 09:57:41 +02:00
bash
1ba36e9c64 Revert "fix(Core/Travel): LOS check before trusting raw cmangos waypoints" 2026-06-05 09:57:41 +02:00
bash
9106bc673b fix(Core/Travel): LOS gate on empty-probe single-waypoint fallback 2026-06-05 09:57:41 +02:00
bash
1968d46496 fix(Core/Travel): LOS check before trusting raw cmangos waypoints 2026-06-05 09:57:41 +02:00
bash
18029f152b chore(Core/Travel): Revert travelnode threshold to 50y 2026-06-05 09:57:41 +02:00
bash
032315ff45 chore(Core/Travel): Bump travelnode threshold to 75y 2026-06-05 09:57:41 +02:00
bash
f44e0d2250 fix(Core/Travel): Trust travelnode waypoints when AC mmap rejects segments 2026-06-05 09:57:41 +02:00
bash
c3303d5318 feat(Core/Travel): Hardcode 50y travelnode threshold 2026-06-05 09:57:41 +02:00
bash
eb73f2795b core filter isnt working yet 2026-06-05 09:57:41 +02:00
bash
0eaf16458f refactor(Core/Travel): Drop redundant NAV_GROUND_STEEP excludes (core handles via IsBot) 2026-06-05 09:57:41 +02:00
bash
703d6f95ec fix(Core/Travel): Exclude NAV_GROUND_STEEP at all bot PathGenerator sites 2026-06-05 09:57:41 +02:00
bash
716c22156d feat(Core/Travel): Align MoveFarTo and probe pipeline with cmangos 2026-06-05 09:57:41 +02:00
bash
0b90d2d41d feat(Core/Travel): Cap bots at 50° via NAV_GROUND_STEEP exclude 2026-06-05 09:57:41 +02:00
bash
1078d8b89e feat(Core/Debug): Trace movement entry points and visualize travel nodes 2026-06-05 09:57:41 +02:00
bash
14844a52dd feat(Core/RPG): MoveFarTo flow, quest-pursuit at POI, MoveRandomNear retries 2026-06-05 09:57:41 +02:00
bash
b5b507c098 feat(Core/Travel): Travel-node graph routing for long-distance pathing 2026-06-05 09:57:41 +02:00
bash
69207acf76 feat(Core/Loot): Quest GO loot, bag-make-room, item-pursuit 2026-06-05 09:57:40 +02:00
bash
7d3a6e5d35 chore(Tools): Add mmap/vmap client-data extraction script 2026-06-05 09:57:40 +02:00
bash
e2c8ecc81c feat(DB/Travel): Import cmangos travel-node graph 2026-06-05 09:57:40 +02:00
Keleborn
714bb6bca3
Shorten paths (#2396)
<!--
Thank you for contributing to mod-playerbots, please make sure that
you...
1. Submit your PR to the test-staging branch, not master.
2. Read the guidelines below before submitting.
3. Don't delete parts of this template.

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

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

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

## Pull Request Description
<!-- Describe what this change does and why it is needed -->
This is designed to significantly shorten the overall path while
maintaining the more detailed structure. I tried to follow the principle
of removing any folder that had 1 or 2 files in it, shortenining
dungeon/raid names to acronyms, and removing instances when the base
name was in the file name (Like Raid) etc.


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

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



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



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



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



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



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

Plan and execute

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

## Final Checklist

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

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
2026-05-31 18:38:01 +02:00
dillyns
585027fab7
Remove fire totem override from enhancement AOE strategy (#2270)
<!--
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 -->
Currently in AOE enchancement shamans are dropping magma totems. They
should not be overriding the set fire totem strategy.

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

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



## How to Test the Changes
<!--
- Step-by-step instructions to test the change.
- Any required setup (e.g. multiple players, number of bots, specific
configuration).
- Expected behavior and how to verify it.
-->
Bring an enhancement shaman into medium aoe scenario (3 enemies)
They should no longer replace their fire totem with magma totem. They
should only drop the fire totem that is set in their strategies.

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



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



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



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

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

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



## Final Checklist

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

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
2026-05-31 06:41:13 -07:00
Crow
571735cd57
Fix crash from missing spellInfo check in TogglePetSpellAutoCastAction (#2431)
<!--
Thank you for contributing to mod-playerbots, please make sure that
you...
1. Submit your PR to the test-staging branch, not master.
2. Read the guidelines below before submitting.
3. Don't delete parts of this template.

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

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

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

## Pull Request Description
<!-- Describe what this change does and why it is needed -->
I had repeated crashes upon login traced to line 89 of
TogglePetSpellAutoCastAction, where a spellInfo check is missing. I
confirmed that adding the check fixed my crashing.

I don't know why there was invalid spellInfo to create the crash, as
this missing check is not a new development and I never had a crash
before, but clearly this code should be fixed in any case.

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

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



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



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



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



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



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



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

## Final Checklist

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

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
2026-05-30 20:10:33 -07:00
kadeshar
b430118124
Sunder Armor fixes (#2427)
<!--
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 -->

Arms and Fury using Sunder Armor when there is not Protection Warrior in
group

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

Invite warrior/warriors to group and check how they apply Sunder Armor
on dummy target


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



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

Yes, expained in Pull Request Description

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



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

Analyze orginal behavior

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

## Final Checklist

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

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
2026-05-30 14:14:54 -07:00
Keleborn
180be33d9d Remove stray folder. 2026-05-30 14:09:09 -07:00
Keleborn
9e251dc397 Fix conf merge error 2026-05-30 13:50:54 -07:00
Crow
92fa97c3aa
Rewrite Equipment-Randomization-Related Configs (#2409)
<!--
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 main focus on this PR is to clarify and eliminate overlap between
three very confusingly described yet important config options:
AutoUpgradeEquip, EquipmentPersistence, and IncrementalGearInit.

For context, after initialization, randombots generally get randomized
each time after logging in and periodically between
MinRandomBotRandomizeTime (configurable, default 2 hours) to
MaxRandomBotRandomizeTime (configurable, default 14 days). This
randomization happens only when the bot is idle.

Now let’s look at what those three config options currently say.
- AutoUpgradeEquip: Randombots automatically upgrade their equipment on
levelup.
- EquipmentPersistence: Enable/Disable bot equipment persistence (stop
random initialization) after certain level (EquipmentPersistenceLevel).
- IncrementalGearInit: If disabled, random bots can only upgrade
equipment through looting and quest

None of those descriptions are accurate.
- AutoUpgradeEquip determines if randombots, upon leveling up, refresh
ammo, reagents, food, consumables, potions, and, ONLY IF
IncrementalGearInit is also enabled and EquipmentPersistence is
disabled, upgrade equipment (yes, three config options required for this
one thing).
-	EquipmentPersistence affects both equipment and talents. 
- Disabling IncrementalGearInit does not prevent randombots from
changing their equipment through the login/periodic randomization
process unless EquipmentPersistence is enabled.

These config options shouldn’t overlap with or be dependent on each
other, and their names and descriptions should reflect what they
actually do. Thus, this PR does the following:
- AutoUpgradeEquip solely controls whether or not randombots
automatically upgrade their gear upon level up. No other config option
is involved for this purpose, and AutoUpgradeEquip no longer impacts
inventory items. This does mean that it is no longer possible to stop
randombots from being given ammo, potions, etc. when they level up. I
tend to think that randombots as they currently are cannot fully
function otherwise so I have no issue with the loss of this ability, but
if there is disagreement, then we need to introduce a new config option.
AutoUpgradeEquip is also now set to true in PlayerbotAIConfig to reflect
that the default is true in the .dist.
- I originally wanted to combine EquipmentPersistence and
EquipmentPersistenceLevel into a single config option that also included
talents in the name, but EquipmentPersistence is used in the level
brackets mod so I don’t want to make a breaking change there. I settled
for making EquipmentPersistence enabled by default and also reducing
EquipmentPersistenceLevel to 1 by default, in effect entirely disabling
periodic/login randomization of randombot talents and gear by default. I
only see people complain about these features so I think unless there is
some compelling performance or structural reason to the contrary, the
default should be that randombots do not continuously randomize their
talents and equipment.
- IncrementalGearInit is eliminated. There are two real functional
impacts: (1) as noted above, randombots cannot be blocked from receiving
standard inventory items upon level-up (though the way it is currently
being gated does not make sense anyway), and (2) you can no longer have
equipment persistence for equipment only but not talents (I cannot
imagine anybody would ever want to do that, and I don't think it was
even intended given the strange interaction between config options that
was needed to even accomplish that before).
- For all these settings, the config descriptions are updated to try to
be clear about what the player is actually configuring.

Beyond that, I made some clean-up changes to the config to fix some
typos and try to shorten it in places, and I also deleted a few config
options that do not appear in operative code anymore (and any
corresponding references in PlayerbotAIConfig). I also deleted some
comments and made some minor style changes in
AutoMaintenanceOnLevelupAction.cpp.


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

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

The main substance of the PR consists of changes to checks in
PlayerbotFactory and AutoMaintenanceOnLevelupAction.

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

- To test AutoUpgradeEquip, use the gm command .level 1 while targeting
an rndbot.
- To test EquipmentPersistence, the only way I could find to force an
incremental randomization was to use .playerbots rndbot level BotName.
This will do an incremental randomization and a level up. However, this
level does not go through the AutoMaintenanceOnLevelupAction path so
just disregard the +1 level aspect and consider it a pure test of
EquipmentPersistence.

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



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



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



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


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

## Final Checklist

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

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

---------

Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com>
2026-05-30 11:13:18 -07:00
Mat
ff001afd46
Reset instance ID via existing cmd refresh=raid for alt bots (#2422)
<!--
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 -->
Alt bots can now use refresh=raid cmd if cfg is set to 1.
`AiPlayerbot.ResetInstanceIdForAltBots = 1`


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

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



## How to Test the Changes
<!--
- Step-by-step instructions to test the change.
- Any required setup (e.g. multiple players, number of bots, specific
configuration).
- Expected behavior and how to verify it.
-->
Add alt bot, use .playerbots bot refresh=raid and it should say ok after
reseting instance.


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



- Does this change modify default bot behavior?
    - - [ ] No
    - - [x] Yes (**explain why**)
Alt bots will now reset instance ID if cfg is set to 1.


- Does this change add new decision branches or increase maintenance
complexity?
    - - [ ] No
    - - [x] Yes (**explain below**)
It will check if cfg is set to 1 to enable alt bots to reset instance
id.


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



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

## Final Checklist

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

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

---------

Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com>
2026-05-30 11:12:54 -07:00
Crow
32d10080a4
Improve bot trinket usage and fix related bugs (#2425)
<!--
Thank you for contributing to mod-playerbots, please make sure that
you...
1. Submit your PR to the test-staging branch, not master.
2. Read the guidelines below before submitting.
3. Don't delete parts of this template.

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

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

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

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

This PR makes three changes to UseTrinketAction:
1. It adds health and mana gating (based on existing config thresholds)
for bots to activate mana recovery, mana efficiency, and defensive
trinkets. The thresholds are mediumMana (default 40%) for mana recovery
trinkets, highMana (default 65%) for mana efficiency trinkets, and
lowHealth (default 45%) for defensive trinkets.
2. It removes the old overinclusive procflag/specproc gate introduced by
PR 1385, which prevents bots from using dozens of valid trinkets,
including some extremely powerful ones (such as Skull of Gul’dan, the
iconic TBC expansion BiS trinket for all casters), and replaces it with
a narrower exclusion that still addresses the original issue.

- Regarding PR 1385, focusing on liyunfan’s post specifically, I
interpet the issue to be relating to trinkets where the item data is
screwed up such that a passive effect was implemented as an on-use
spell. I had AI do a scan, and it seems that issue impacts only 2
trinkets in the game, Oracle Talisman of Ablution and Frenzyheart
Insignia of Fury, and specifically only the versions of those items that
are not legitimately obtainable (unclear why they exist at all). I’ve
excluded those trinkets from bots via the config now, and this PR
maintains the guards against those trinkets regardless but does so in a
narrower fashion. PR 1385's approach of using ProcFlags != 0 (i.e.,
excluding all on-use trinkets with non-zero ProcFlags) works only if
proc metadata can be used to distinguish between active/passive
trinkets, and that’s not even close to being the case. AI came up with
44 false positives, including many significant trinkets beyond Skull of
Gul’dan such as the ZG Hakkar quest trinkets, Badge of the Swarmguard,
Petrified Scarab, Scarab Brooch, Eye of the Dead, Essence of the Martyr,
Abacus of Violent Odds, Ribbon of Sacrifice, and Pendant of the Violet
Eye (and I’m not mentioning WotLK trinkets only because I don’t know
anything about what is relevant for that expansion).

3. It fixes an issue where bots were not respecting trinket cooldowns in
some cases. This resulted because trinkets with shared cooldown
categories (i.e., those that are not stackable) would substitute the
individual trinket cooldowns with shared category cooldowns, which let
bots spam usages of trinkets, by tracking per-item and per-category
trinket cooldowns locally. The root of this is based in AC; I don't know
if it should be considered a bug or not, but regardless it doesn't
impact players, presumably because cooldowns are enforced on the client
side.

- Here’s an illustration to explain the issue in practice. Skull of
Gul’dan has a 240s cooldown and Shifting Naaru Sliver has a 180s
cooldown, and they share a cooldown category so their usages cannot be
stacked. The shared cooldown matches the length of the on-use effect (so
20s for Skull and 15s for Sliver). The below is what can happen without
this PR (and is what I observed in testing).

- t = 0s: Shifting Naaru Sliver used, writes its 90s personal cooldown
and 15s shared category cooldown.
- t = 15s: Skull of Gul’dan used, writes its 120s personal cooldown and
20s shared category cooldown. Skull’s shared category cooldown
overwrites Sliver’s spell-cooldown entry, giving Sliver 20s left on its
personal cooldown instead of 75s.
- t = 35s: Sliver incorrectly appears ready and can be used again (55s
sooner than should be possible). Then Sliver’s shared category cooldown
in turn overwrites Skull’s longer personal cooldown.
- t = 50s: Skull incorrectly appears ready and can be used again (85s
sooner than should be possible).
  - Repeat.


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

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

The logic runs through the existing trinket use code. Specifically:

- Helpers are used to classify on-use trinket effects into mana
restoration, mana efficiency, and defensive/tank categories using
spell-effect checks
- Existing configured mana and health thresholds are applied to those
trinkets
- The old procflag gate is replaced with a one-time cached set of mixed
ON_USE/ON_EQUIP trinket spell ids
- Two small cooldown maps per bot are tracked in UseTrinketAction: one
for item cooldowns and one for shared category cooldowns


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

I did all of these things, but verification is always good. Overall, I
think it is worth running with this PR merged into test-staging if/when
that happens and keeping an eye on overall performance.
1. Equip a bot with a trinket referenced above, such as Skull of
Gul'dan, and confirm it is now used in combat.
2. Test mana-based classes with mana recovery and mana-efficiency
trinkets and confirm they are used only when below the applicable
configured mana threshold. An easy one to check is Glimmering Naaru
Sliver because it is a channel.
3. Test defensive on-use trinkets and confirm they are used only when
health is below the configured low-health threshold. Something like
Shadowmoon Insignia, which increases maximum health, is pretty obvious.
4. Confirm that the error versions of Oracle/Frenzyheart don’t stack
auras on bots (i.e., the bug addressed in PR 1385 has not returned). You
can do this by having a bot kill mobs and check .listauras, though I
checked through logging in the code because auras are noisy as hell.
5. Equip a bot with two trinkets that have shared cooldowns. I used
Skull of Gul’dan and Shifting Naaru Sliver. Go fight a mostly
tank-and-spank boss, such as Gruul. Use an add-on like Skada that tracks
buffs. You should see that before this PR, bots will use the trinkets
multiple times in one cooldown period, and after, they observe the
actual cooldowns.

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

Any additional impact is confined to UseTrinketAction. The exclusion of
the busted trinkets uses a cache that is built once per server process
followed by constant-time lookups. The per-bot trinket cooldown maps
also use constant-time lookups and store only a few timestamp entries
per bot.

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



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

Sort of--trinkets previously didn't have any consideration for effects
with respect to usage so that is new. I think it is necessary though to
have half-decent bot trinket usage, and there could be further
refinement for how bots decide to use trinkets based on this structure.

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

GPT-5.4 was used for investigation and root-cause analysis and
assistance with drafting. All resulting code and the PR rationale was
validated through in-game testing.

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

## Final Checklist

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

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
Bots also suck at using DPS trinkets properly, but I don't think there's
a simple way to address that unlike with mana recovery or defensive
trinkets. So that's to consider another day.

---------

Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com>
2026-05-30 11:12:34 -07:00
dillyns
1bbed177c8
Add Nefarian Fear Ward action and trigger, along with Wild Magic trigger (#2412)
<!--
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 -->
For Nefarian in BWL, this does the following:
Mages - Ice block if the mage class call "Wild Magic" happens
Priests - Buff the current target of Nefarian with Fear Ward

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

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



## How to Test the Changes
<!--
- Step-by-step instructions to test the change.
- Any required setup (e.g. multiple players, number of bots, specific
configuration).
- Expected behavior and how to verify it.
-->
Go to Nefarian (id 11583) or his phase 1 form (id 10162) in BWL with a
mage and a priest bot.
During phase 2, the priest bot should cast fear ward on whoever nefarian
is targeting.
Wait for a mage class call. Once it happens the mage bot should cast ice
block (if its available) to remove the Wild Magic debuff.


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



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



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



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


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

## Final Checklist

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

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
2026-05-30 07:14:38 -07:00
Lichborne
a3ca438bef
Make .playerbots bot commands case-insensitive (#2419)
## Pull Request Description

\`.playerbots bot add jared\` fails with "Character 'jared' not found"
even when \`Jared\` exists, because the lookup is case-sensitive. Same
issue hits \`remove\`, \`init=<...>\`, \`refresh\`, \`addaccount\`, and
any other subcommand that takes a character name.

Fix at the parser layer in \`HandlePlayerbotCommand\`: every subcommand
that takes a charname flows through one shared name-lookup step.
Normalize the typed name to canonical form (first letter upper, rest
lower) at that step so \`jared\`, \`JARED\`, \`JaReD\` all resolve to
\`Jared\`. The \`addaccount\` path still tries the raw token as an
account name first (account names are case-insensitive on the auth
side), then falls back to a normalized character-name lookup.

No subcommand's behavior changes — only the name lookup that precedes
them. Subcommands with no charname (\`initself\`, \`list\`, \`reload\`,
\`tweak\`, \`self\`, \`lookup\`, \`addclass\`) are unaffected.


## Feature Evaluation

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

One \`normalizePlayerName(s)\` call on each comma-separated token before
the existing \`GetCharacterGuidByName(s)\` lookup. Uses the helper this
module already calls in four other places.

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

Runs only when a player types a \`.playerbots bot ...\` command, on the
typed tokens. Not in any bot-tick or per-bot path. Cost does not scale
with bot count.


## How to Test the Changes

\`\`\`
.playerbots bot add Jared           → adds Jared
.playerbots bot add jared           → adds Jared
.playerbots bot add JARED           → adds Jared
.playerbots bot add JaReD           → adds Jared
.playerbots bot remove Jared        → removes Jared
.playerbots bot remove jared        → removes Jared
.playerbots bot init=auto jared     → re-rolls Jared's gear
.playerbots bot levelup jared → re-rolls Jared at his current level
.playerbots bot refresh jared       → refreshes Jared (HP/mana restored)
.playerbots bot refresh=raid jared  → unbinds Jared from saved raid IDs
.playerbots bot add jAred,saLLy,BOB → adds all three
.playerbots bot add nonexistent     → still reports not found
.playerbots bot initself            → still works (untouched path)
\`\`\`

All cases verified in-game.


## Impact Assessment

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

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

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


## AI Assistance

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

Used Claude to help trace the call graph (confirming which subcommands
are affected and which are untouched) and to draft the patch. Reviewed
and tested in-game before pushing.


## Final Checklist

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


## Notes for Reviewers

Diff is small (~25 lines in one file). One minor cosmetic side effect:
"Character 'X' not found" errors now echo the normalized form (\`Bogus\`
instead of \`bogus\`).

---------

Co-authored-by: Lichborne-AC <lzeppelin112@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 07:03:22 -07:00
Keleborn
28ec9b34b8
add conf option for disabling send mail (#2411)
<!--
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 -->
Fixes exploit in multiplayer servers where players can ask bots to mail
them items.


## 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.
Conf.
- Describe the **processing cost** when this logic executes across many
bots.
Cheap bool checkl


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

Use send mail command to try to get a bot to send you mail. Should
follow bool setting.

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



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



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



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


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

## Final Checklist

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

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
2026-05-30 06:52:54 -07:00
Mat
a1f9ff4542
fix bot leader handling (#2426)
<!--
Thank you for contributing to mod-playerbots, please make sure that
you...
1. Submit your PR to the test-staging branch, not master.
2. Read the guidelines below before submitting.
3. Don't delete parts of this template.

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

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

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

## Pull Request Description
<!-- Describe what this change does and why it is needed -->
This pr fixes bots "freaking out" after leader change and bots will
promote player to party leader after give leader command
closes #2420 #2424 

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

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



## How to Test the Changes
<!--
- Step-by-step instructions to test the change.
- Any required setup (e.g. multiple players, number of bots, specific
configuration).
- Expected behavior and how to verify it.
-->
1. Join rdf or make group
2. Promote bot to leader (rnd or alt), if bot was not leader
3. Type /p give leader
4. Bot will promote you to leader and it will follow you instead of
freaking out

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



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

1. Bots now reset their AI on every SMSG_GROUP_SET_LEADER packet, so old
leader strategies get cleared after a leader change.
2. Random bots auto-bind their master to a real-player group member
during reset botAI, so commands like give leader and follow logic that
depend on HasActivePlayerMaster() start working for them.
3. On OnBotLogin, bots no longer steal leadership from a real player,
they only force the leader change if the current leader is a bot or
offline.

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

Adds two small guards: an "is current leader a real player" check on
login, and a "find first real-player member" loop inside ResetAiAction.
Both reuse existing patterns (IsRealPlayer(), the OnPlayerLogin
master-assign loop).


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



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

## Final Checklist

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

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
2026-05-30 06:48:29 -07:00
NoxMax
0afaf753c6
Clarifies BG bracket auto-join comments and configs (#2417)
<!--
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 -->
playerbots.conf.dist:
1. Updated and clarified comments. At best they were misleading, at
worst they were wrong.
2. Reordered brackets configs top to bottom, from Warsong to IOC.
3. No config values have actually been changed.

PlayerbotAIConfig.cpp:
1. Made sure the config values actually match the .dist file.
2. Reordered brackets configs top to bottom, from Warsong to IOC.

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

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



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



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



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



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



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



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

## Final Checklist

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

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
Bracket ranges are from `pvpdifficulty_dbc`
2026-05-30 06:48:16 -07:00
Mat
240bb2dfca
Autogear suffixes (#2415)
<!--
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
Suffix scoring was already wired up in playerbots, bots could evaluate
suffix items they owned, but factory init scored candidates with
randomPropertyId = 0 and equipped via EquipNewItem which doesn't roll
suffixes, so suffix items always looked like junk and never got picked.

Added a cached item_enchantment_template pool in RandomItemMgr, a
PickBestRandomPropertyId helper that picks the best suffix per bot
class/spec, and added it on the equipped item via
SetItemRandomProperties.

Tested levels 20-80, suffixes match spec. Closes #2370.



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

For items with RandomProperty != 0 or RandomSuffix != 0, run the
item_enchantment_template pool once, score each candidate suffix against
the bot's existing class/spec stat weights, keep the highest, and stamp
that id on the item right after EquipNewItem. Items without a suffix
pool skip the helper entirely. Without this, every suffix template
scores as base stats only (often near zero) and never gets picked during
factory init.

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

Startup: one SELECT entry, ench FROM item_enchantment_template (few
hundred rows), parsed into an unordered_map<uint32, vector<uint32>>
once. No SQL afterwards.
Per bot during init: PickBestRandomPropertyId only runs on candidates
that actually have a suffix pool. Each call is a hash lookup plus a loop
over 5-15 enchantment ids, each doing DBC lookups already used by the
scoring code. Cost is mostly by the existing CalculateRandomProperty
work, not new logic.
Scales linearly with bot count, same as the rest of factory init. No new
per-tick work.


## How to Test the Changes

1. Generate a fresh random bot at level 20-79 and use autogear (cloth,
leather, mail, plate, lots of suffix gear at that range).
2. Inspect the bot's gear. Items with names like "of the Eagle", "of the
Monkey", "of Healing", etc... should be equipped, with the suffix stats
visible on the tooltip.
3. Generate a level 80 bot at high gear-score limit. Confirm it still
equips raid epics normally (epics have no suffix pool, so this path is
untouched).



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



- Does this change modify default bot behavior?
    - - [ ] No
    - - [x] Yes (**explain why**)
Bots now consider random-suffix items during factory init and equip them
with the best suffix for their class/spec. Before, suffix items were
effectively invisible to init autogear because they scored as base stats
only. This is the intended fix for #2370.


- Does this change add new decision branches or increase maintenance
complexity?
    - - [ ] No
    - - [x] Yes (**explain below**)
Added one new public method on StatsWeightCalculator
(PickBestRandomPropertyId) and one new cache in RandomItemMgr
(LoadEnchantmentPool + GetEnchantmentPool). The candidate list in
InitEquipment changed from vector<uint32> to vector<pair<uint32, int32>>
to carry the chosen suffix id alongside the item id. Logic mirrors
existing patterns in the same files (DBC lookups, SQL-backed caches,
signed randomPropertyId encoding), no new abstractions, no new wrappers.


## AI Assistance
<!--
AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
We expect contributors to be honest about what they do and do not
understand.
-->
Was AI assistance used while working on this change?
- - [ ] No
- - [x] Yes (**explain below**)
Used AI to help me find the bug. It mapped the existing scoring pipeline
and pointed out that PlayerbotFactory::InitEquipment was scoring
candidates with randomPropertyId = 0 and equipping via EquipNewItem
(which doesn't roll suffixes), so the scoring side was already built,
just never fed real suffix ids during init. I reviewed and tested all
the code in-game across levels 20-80 and multiple classes/specs.




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

## Final Checklist

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

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

---------

Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com>
Co-authored-by: bash <hermensb@gmail.com>
Co-authored-by: Revision <tkn963@gmail.com>
Co-authored-by: kadeshar <kadeshar@gmail.com>
2026-05-30 06:48:07 -07:00
469 changed files with 153065 additions and 2163 deletions

51
.claude/settings.json Normal file
View File

@ -0,0 +1,51 @@
{
"permissions": {
"allow": [
"Bash(awk '-F[\\(\\),]' ' *)",
"Bash(xargs ls -lah)",
"Bash(py --version)",
"Bash(py -3 --version)",
"Bash(py scripts/import_cmangos_travel_nodes.py)",
"Read(//home/dev/azerothcore_installer/_server/azerothcore/modules/mod-playerbots/data/sql/playerbots/updates/**)",
"Bash(py scripts/fixup_id_collision.py)",
"Bash(py scripts/insert_ignore.py)",
"Bash(grep -nA 6 \"passFilter\" C:/Users/Admin/git/main/azerothcore-wotlk/deps/recastnavigation/Detour/Include/DetourNavMeshQuery.h)",
"Bash(grep -nA 5 \"dtQueryFilter::passFilter\" C:/Users/Admin/git/main/azerothcore-wotlk/deps/recastnavigation/Detour/Source/DetourNavMeshQuery.cpp)",
"Bash(grep -nB1 -A2 \"modAlmostUnwalkableTriangles\" C:/Users/Admin/git/main/azerothcore-wotlk/src/tools/mmaps_generator/MapBuilder.cpp)",
"Bash(xargs grep -l \"MoveFarTo\\\\|ResolveMovePath\\\\|DispatchMovement\")",
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" status)",
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" add src/server/game/Movement/MovementGenerators/PathGenerator.h)",
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" commit -m \"feat\\(Core/Movement\\): Expose dtQueryFilter::setAreaCost via PathGenerator\")",
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" push)",
"Bash(wait)",
"Bash(git -C \"c:/Users/Admin/git/main/azerothcore-wotlk/modules/mod-playerbots\" show 3710c35a)",
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" show 9cccc5d26)",
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" add src/server/game/Movement/MovementGenerators/PathGenerator.cpp)",
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" commit -m \"feat\\(Core/Movement\\): Bias NAV_WATER 10x in default Player path filter\")",
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" commit -m \"Revert \\\\\"feat\\(Core/Movement\\): Bias NAV_WATER 10x in default Player path filter\\\\\"\")",
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" commit -m \"Revert \\\\\"feat\\(Core/Movement\\): Expose dtQueryFilter::setAreaCost via PathGenerator\\\\\"\")",
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" log --oneline cf9c6e9f3353d386f398cbe7a821abfd8fe9a4b3..HEAD)",
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" diff cf9c6e9f3353d386f398cbe7a821abfd8fe9a4b3..HEAD --stat)",
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" reset --hard cf9c6e9f3353d386f398cbe7a821abfd8fe9a4b3)",
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" push --force-with-lease)",
"Bash(git cherry-pick *)",
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" status -s)",
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" diff --stat)",
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" add src/server/game/Movement/MovementGenerators/PathGenerator.cpp src/server/game/Movement/MovementGenerators/PathGenerator.h)",
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" commit -m \"feat\\(Core/Movement\\): Cap Player path filter at 50° + water bias under MOD_PLAYERBOTS\")",
"Bash(grep -n \"CollectIncludeDirectories\\\\|sourcePath\\\\|GLOB.*\\\\\\\\.cpp\\\\\\\\|target_include\" C:/Users/Admin/git/main/azerothcore-wotlk/modules/CMakeLists.txt)",
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" commit -m \"fix\\(Core/Movement\\): Gate playerbot path filter on WorldSession::IsBot\\(\\)\")",
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" fetch origin fix/mmaps-config-overrides-and-aliases)",
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" log --oneline HEAD..origin/fix/mmaps-config-overrides-and-aliases)",
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" rebase origin/fix/mmaps-config-overrides-and-aliases)",
"Bash(git -C \"C:/Users/Admin/git/main/azerothcore-wotlk\" log --oneline -3)",
"Bash(grep -v \"//\")",
"Bash(grep -v \"^//\")"
],
"additionalDirectories": [
"C:\\Users\\Admin\\git\\main\\azerothcore-wotlk\\src\\common\\Collision\\Maps",
"C:\\Users\\Admin\\git\\main\\azerothcore-wotlk\\src\\tools\\mmaps_generator",
"C:\\Users\\Admin\\git\\main\\azerothcore-wotlk\\src\\server\\game\\Movement\\MovementGenerators"
]
}
}

View File

@ -48,6 +48,16 @@ Then build the server following the platform-specific instructions in our **[Ins
> **Testing branch:** A `test-staging` branch is available with the latest features and fixes before they are merged into `master`. To use it, clone with `--branch=test-staging` instead. Note that this branch may contain unstable or breaking changes — use it at your own risk and only if you are comfortable troubleshooting issues. > **Testing branch:** A `test-staging` branch is available with the latest features and fixes before they are merged into `master`. To use it, clone with `--branch=test-staging` instead. Note that this branch may contain unstable or breaking changes — use it at your own risk and only if you are comfortable troubleshooting issues.
### Required server configuration
In `worldserver.conf` (AzerothCore core config), set:
```ini
PreloadAllNonInstancedMapGrids = 1
```
This is required for `mod-playerbots`.
### Detailed Guides ### Detailed Guides
| Guide | Description | | Guide | Description |

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,632 @@
-- Imported from cmangos-playerbots ai_playerbot_travelnode (wotlk)
-- 626 new nodes (cmangos has them, we didn't)
-- Matched on (name, mapId) + closest position; remaining are unique to cmangos.
INSERT INTO `playerbots_travelnode` (`id`, `name`, `map_id`, `x`, `y`, `z`, `linked`) VALUES
(3781, ' portal', 0, 3476.3600, -4493.3600, 137.4900, 1),
(3782, ' portal', 530, 6107.2600, -6990.6400, 133.3170, 1),
(3783, ' spirithealer', 609, 1886.7800, -5784.5900, 102.8610, 1),
(3784, ' spirithealer', 609, 2116.1900, -5286.9400, 81.2151, 1),
(3785, ' spirithealer', 609, 2364.4200, -5771.3200, 151.3670, 1),
(3786, 'Absalan the Pious', 623, 36.0312, 40.4600, 25.0322, 1),
(3787, 'Acherus: The Ebon Hold flightMaster', 0, 2348.6300, -5669.2900, 382.3240, 1),
(3788, 'Acherus: The Ebon Hold spirithealer', 0, 2356.6500, -5663.0600, 382.2570, 1),
(3789, 'Alliance Log Ride 01 Begin', 571, 4274.5300, -3055.5500, 319.4630, 1),
(3790, 'Alliance PVP Barracks', 449, -9.1189, -4.2670, 5.5710, 1),
(3791, 'Alterac Mountains The Headland', 0, -80.9055, -331.6660, 136.4710, 1),
(3792, 'Alterac Mountains', 0, 498.3190, -1076.6400, 195.8960, 1),
(3793, 'Arathi Highlands The Sanctum', 0, -1532.4300, -1882.5600, 69.5533, 1),
(3794, 'Arathi Highlands The Tower of Arathor', 0, -1774.4800, -1518.3900, 75.2667, 1),
(3795, 'Archmage Pentarus', 603, -718.4560, -57.1132, 429.9240, 1),
(3796, 'Area 52 innkeeper', 530, 3062.1500, 3701.8200, 142.5620, 1),
(3797, 'Area52 Transporter', 530, 3092.5800, 3644.7200, 143.1360, 1),
(3798, 'Ashenvale Kargathia Keep', 1, 2437.0600, -3543.6300, 98.3115, 1),
(3799, 'Auberdine innkeeper', 1, 6406.5100, 515.3670, 8.7257, 1),
(3800, 'Auchindoun: Auchenai Crypts', 558, 63.4074, -175.2640, 15.4378, 1),
(3801, 'Auchindoun: Mana-Tombs', 557, -220.0700, -177.6620, -0.9810, 1),
(3802, 'Auchindoun: Sethekk Halls', 556, -102.4290, 177.5860, 0.0932, 1),
(3803, 'Auchindoun: Shadow Labyrinth', 555, -272.2950, -140.0660, 8.1563, 1),
(3804, 'Balzaphon', 329, 3733.2700, -3480.1100, 131.0400, 1),
(3805, 'Blackfathom Deeps', 48, -570.3640, 0.8977, -47.1378, 1),
(3806, 'Blackrock Depths', 230, 870.1800, -239.3270, -71.6776, 1),
(3807, 'Blackrock Spire', 229, -16.0931, -392.4040, 48.5157, 1),
(3808, 'Blades Edge Mountains Circle of Blood', 530, 2879.1300, 5979.4500, 6.2402, 1),
(3809, 'Blades Edge Mountains Vimgols Circle', 530, 3279.9000, 4640.3600, 216.5280, 1),
(3810, 'Blood elf start', 530, 10349.6000, -6357.2900, 33.4026, 1),
(3811, 'Bloodmyst Isle The Hidden Reef', 530, -1148.6600, -11127.4000, -76.0074, 1),
(3812, 'Borean Tundra Coldarra', 571, 3917.0600, 6817.9200, 150.5070, 1),
(3813, 'Borean Tundra Naxxanar', 571, 3750.9300, 3583.8600, 353.1330, 1),
(3814, 'Burning Steppes Blackrock Mountain', 0, -7996.6300, -1013.4100, 131.8670, 1),
(3815, 'Cannon Charging (Port)', 530, 1920.1300, 5581.9000, 269.7220, 1),
(3816, 'Cannon Prep', 0, -9569.6000, -13.7809, 63.9459, 1),
(3817, 'Cannon Prep', 1, -1327.6600, 85.9815, 130.2070, 1),
(3818, 'Cannon Prep', 530, -1742.2500, 5457.4000, -11.9282, 1),
(3819, 'Chief Thunder-Skins', 230, 847.8390, -181.1150, -49.6707, 1),
(3820, 'Coilfang: Serpentshrine Cavern', 548, 135.1620, -488.7410, 0.8400, 1),
(3821, 'Coilfang: The Slave Pens', 547, -71.6723, -322.2440, -1.5438, 1),
(3822, 'Coilfang: The Steamvault', 545, -91.9829, -255.4900, -12.5306, 1),
(3823, 'Coilfang: The Underbog', 546, 108.0610, -198.7420, 50.1184, 1),
(3824, 'Coilskar Witch', 585, 139.1010, -104.6440, -20.9245, 1),
(3825, 'Crystalsong Forest The Azure Front', 571, 5434.6500, 729.6780, 186.6960, 1),
(3826, 'Crystalsong Forest The Great Tree', 571, 5895.1000, 1022.6900, 185.5390, 1),
(3827, 'Crystalsong Forest The Twilight Rivulet', 571, 5508.9200, 432.9360, 161.7470, 1),
(3828, 'Dalaran Portal to Caverns of Time', 1, -8164.8000, -4768.5000, 34.3000, 1),
(3829, 'Dalaran Portal to Caverns of Time', 571, 5781.4800, 841.2580, 680.3790, 1),
(3830, 'Dalaran Portal to Darnassus', 1, 9656.5400, 2518.2600, 1331.6600, 1),
(3831, 'Dalaran Portal to Darnassus', 571, 5706.1600, 730.1020, 641.7450, 1),
(3832, 'Dalaran Portal to Exodar', 571, 5699.5800, 735.4690, 641.7690, 1),
(3833, 'Dalaran Portal to Ironforge', 0, -4613.7100, -915.2870, 501.0620, 1),
(3834, 'Dalaran Portal to Ironforge', 571, 5712.6800, 724.8450, 641.7360, 1),
(3835, 'Dalaran Portal to Orgrimmar', 1, 1470.0200, -4222.5900, 59.2213, 1),
(3836, 'Dalaran Portal to Orgrimmar', 571, 5925.8500, 593.2500, 640.5630, 1),
(3837, 'Dalaran Portal to Shattrath', 530, -1824.3200, 5417.2300, -12.4277, 1),
(3838, 'Dalaran Portal to Shattrath', 530, -1902.4900, 5442.8600, -12.4280, 1),
(3839, 'Dalaran Portal to Shattrath', 571, 5697.4900, 744.9120, 641.8190, 1),
(3840, 'Dalaran Portal to Shattrath', 571, 5941.6600, 584.8870, 640.5740, 1),
(3841, 'Dalaran Portal to Silvermoon', 530, 9998.4600, -7106.5500, 47.7054, 1),
(3842, 'Dalaran Portal to Silvermoon', 571, 5946.9800, 568.4790, 640.5730, 1),
(3843, 'Dalaran Portal to Stormwind', 571, 5719.1900, 719.6810, 641.7280, 1),
(3844, 'Dalaran Portal to Thunder Bluff', 1, -967.3750, 284.8200, 110.7730, 1),
(3845, 'Dalaran Portal to Thunder Bluff', 571, 5945.8100, 577.3570, 640.5740, 1),
(3846, 'Dalaran Portal to Undercity', 571, 5934.6600, 590.6880, 640.5750, 1),
(3847, 'Dalaran Violet Citadel Balcony', 571, 5865.0800, 840.2100, 846.3330, 1),
(3848, 'Darnassus The Temple Gardens', 1, 9814.7800, 2574.2100, 1314.4700, 1),
(3849, 'Darnassus Warriors Terrace', 1, 9976.2100, 2262.3700, 1334.5000, 1),
(3850, 'Darnassus innkeeper', 1, 9951.8800, 2282.8200, 1345.1600, 1),
(3851, 'Deadwind Pass Groshgok Compound', 0, -11121.6000, -2416.2600, 108.6530, 1),
(3852, 'Deadwind Pass Karazhan', 0, -11123.3000, -2006.7700, 47.2725, 1),
(3853, 'Deadwind Pass', 0, -10377.2000, -1785.4200, 94.3243, 1),
(3854, 'Deeprun Tram', 369, 13.6699, 80.6694, -4.2973, 1),
(3855, 'Desolace Bolgans Hole', 1, -2375.9500, 2469.7600, 74.4626, 1),
(3856, 'Desolace The Veiled Sea', 1, -1869.3900, 3404.3400, -50.7694, 1),
(3857, 'Dire Maul', 429, 135.5790, 192.0480, -3.3910, 1),
(3858, 'Doodad_CF_elevatorPlatform01entry', 548, 50.0000, -0.0071, -70.9017, 1),
(3859, 'Doodad_CF_elevatorPlatform_small01entry', 548, -59.1350, -98.7966, -51.7018, 1),
(3860, 'Doodad_FactoryElevator01entry', 554, 0.5438, -1.3935, -1.7012, 1),
(3861, 'Doodad_HF_Elevator_Gate01entry', 571, 254.1330, -5892.0000, 258.4950, 1),
(3862, 'Doodad_HF_Elevator_Gate02', 571, 263.1630, -5919.2500, 167.2690, 1),
(3863, 'Doodad_HF_Elevator_Gate02', 571, 263.1630, -5919.2500, 173.3200, 1),
(3864, 'Doodad_HF_Elevator_Gate02entry', 571, 275.2000, -5918.1300, 166.4950, 1),
(3865, 'Doodad_HF_Elevator_Gate03', 571, 135.1250, -5765.3500, 291.7530, 1),
(3866, 'Doodad_HF_Elevator_Gate03', 571, 135.1250, -5765.3500, 286.0490, 1),
(3867, 'Doodad_HF_Elevator_Gate03entry', 571, 133.3330, -5756.2700, 285.5940, 1),
(3868, 'Doodad_HF_Elevator_Lift01entry', 571, 259.9700, -5893.8300, 255.8280, 1),
(3869, 'Doodad_HF_Elevator_Lift01entry', 571, 261.1220, -5910.0100, 77.0138, 1),
(3870, 'Doodad_HF_Elevator_Lift01entry', 571, 261.9400, -5918.9400, 164.0950, 1),
(3871, 'Doodad_HF_Elevator_Lift_01entry', 571, 158.9330, -5760.0000, 38.3942, 1),
(3872, 'Doodad_ID_elevator01entry', 571, 3324.2300, -5134.7400, 300.5890, 1),
(3873, 'Doodad_ID_elevator02entry', 571, 3302.5400, -5103.7300, 300.5890, 1),
(3874, 'Doodad_ID_elevator03entry', 571, 3286.7600, -5135.8800, 300.5890, 1),
(3875, 'Doodad_LogRun_PumpElevator05', 571, 4264.2300, -3276.9500, 336.1010, 1),
(3876, 'Doodad_LogRun_PumpElevator05', 571, 4264.2300, -3276.9500, 330.6150, 1),
(3877, 'Doodad_LogRun_PumpElevator05entry', 571, 4264.2300, -3276.9500, 329.3720, 1),
(3878, 'Doodad_Nexus_Elevator_BaseStructure_01entry', 571, 3556.2500, 6928.7200, 251.3120, 1),
(3879, 'Doodad_Nexus_Elevator_BaseStructure_01entry', 571, 3979.2300, 7272.5600, 256.0160, 1),
(3880, 'Doodad_Nexus_Elevator_BaseStructure_01entry', 571, 4110.2800, 6755.6900, 249.1790, 1),
(3881, 'Doodad_Nexus_Elevator_BaseStructure_01entry', 578, 1213.8600, 1339.0600, 248.2530, 1),
(3882, 'Doodad_Nexus_Elevator_BaseStructure_01entry', 578, 1343.4300, 825.8630, 242.8180, 1),
(3883, 'Doodad_Nexus_Elevator_BaseStructure_01entry', 578, 789.8890, 992.0980, 251.2600, 1),
(3884, 'Doodad_Vrykul_Gondola01entry', 571, 698.9930, -3824.2500, 269.5360, 1),
(3885, 'Doodad_Vrykul_Gondola01entryentry', 571, 700.2670, -3823.5000, 269.3790, 1),
(3886, 'Doodad_Vrykul_Gondola_01entry', 575, -557.0330, 1543.4600, -288.9820, 1),
(3887, 'Doodad_icecrown_elevator02entry', 631, 4366.6900, 2365.6200, 358.4790, 1),
(3888, 'Doodad_mushroombase_elevator01entry', 530, 285.6000, 5927.2000, 26.6102, 1),
(3889, 'Doodad_org_arena_axe_pillar01entry', 618, 768.0000, -298.6670, 28.4867, 1),
(3890, 'Doodad_org_arena_lightning_pillar01entry', 618, 768.0000, -277.3330, 28.4867, 1),
(3891, 'Draenei start', 530, -3961.6400, -13931.2000, 100.6150, 1),
(3892, 'Dragonblight Frostmourne Cavern', 571, 4736.3900, -558.6370, 166.0830, 1),
(3893, 'Dragonblight Scarlet Tower', 571, 4692.0500, -356.1290, 178.7370, 1),
(3894, 'Dragonblight The Pit of Narjun', 571, 3740.0300, 2125.2500, 43.0709, 1),
(3895, 'Dragonblight Wintergarde Crypt', 571, 3584.5200, -781.0240, 155.6860, 1),
(3896, 'Dragonblight Wintergarde Mausoleum', 571, 3650.8400, -1123.3200, 89.2307, 1),
(3897, 'Drakuramas Teleport 02', 571, 6175.5900, -2000.6700, 241.7690, 1),
(3898, 'Dun Morogh Chill Breeze Valley', 0, -5550.9700, -76.1191, 426.9310, 1),
(3899, 'Dun Morogh Thunderbrew Distillery', 0, -5595.6700, -513.5950, 409.4150, 1),
(3900, 'Durotar Burning Blade Coven', 1, -88.3030, -4285.0100, 62.0652, 1),
(3901, 'Durotar Dustwind Cave', 1, 877.9880, -4745.6200, 30.4963, 1),
(3902, 'Durotar Razor Hill Barracks', 1, 319.0970, -4812.8800, 10.6054, 1),
(3903, 'Durotar The Den', 1, -588.7030, -4144.9400, 41.1033, 1),
(3904, 'Duskwood Forlorn Rowe', 0, -10320.8000, 368.2760, 60.5037, 1),
(3905, 'Duskwood Rolands Doom', 0, -11106.5000, -1160.6700, 42.2494, 1),
(3906, 'Dustwallow Marsh Emberstrifes Den', 1, -5103.3600, -3949.5300, 41.4934, 1),
(3907, 'Dustwallow Marsh Foothold Citadel', 1, -3721.7400, -4538.6000, 25.9170, 1),
(3908, 'Dustwallow Marsh North Point Tower', 1, -2885.3400, -3419.3200, 47.2420, 1),
(3909, 'Dustwallow Marsh The Great Sea', 1, -4026.5700, -4975.8200, 8.2153, 1),
(3910, 'Dustwallow to Stormwind Teleport', 0, -9008.7900, 851.3200, 105.8900, 1),
(3911, 'Eastern Plaguelands MazraAlor', 0, 3439.2500, -4980.9600, 195.8110, 1),
(3912, 'Eastern Plaguelands Terrorweb Tunnel', 0, 2903.1100, -2614.6700, 89.8071, 1),
(3913, 'Eastern Plaguelands The Noxious Glade', 0, 2714.6600, -5421.4700, 161.4070, 1),
(3914, 'Elevatorentry', 0, -5065.6200, 437.5100, 424.1080, 1),
(3915, 'Elevatorentry', 1, 2261.3300, -5565.5600, 34.2689, 1),
(3916, 'Elevatorentry', 530, 1914.5100, 5514.0200, 280.6880, 1),
(3917, 'Elevatorentry', 571, 2872.9500, 6234.9700, 104.9120, 1),
(3918, 'Elevatorentry', 571, 2894.6900, 6244.8900, 209.1970, 1),
(3919, 'Elevatorentry', 571, 4185.2900, 5283.6200, 39.6833, 1),
(3920, 'Elwynn Forest Thunder Falls', 0, -9291.1000, 677.6830, 131.7780, 1),
(3921, 'Entangle', 531, -8003.0000, 1222.9000, -82.1000, 1),
(3922, 'Entangle', 531, -8022.3000, 1149.0000, -89.1000, 1),
(3923, 'Entangle', 531, -8043.6000, 1254.1000, -84.3000, 1),
(3924, 'Eredar Soul-Eater', 552, 285.5190, 146.1550, 22.3118, 1),
(3925, 'Escape Voltarus', 571, 5875.4300, -1981.3700, 234.6710, 1),
(3926, 'Escape to the Isle of QuelDanas', 530, 12887.6000, -6869.2100, 10.1141, 1),
(3927, 'Escape to the Isle of QuelDanas', 585, 148.4010, 203.4430, -11.9579, 1),
(3928, 'Evergrove innkeeper', 530, 3022.9000, 5435.5900, 146.7010, 1),
(3929, 'Everlook Transporter', 1, 6755.2200, -4658.0400, 724.7950, 1),
(3930, 'Everlook innkeeper', 1, 6695.1500, -4673.0400, 721.6500, 1),
(3931, 'Eversong Woods Commons Hall', 530, 9822.0000, -6694.3700, 2.5945, 1),
(3932, 'Eversong Woods Falthrien Academy', 530, 10158.0000, -6026.5000, 63.7448, 1),
(3933, 'Eversong Woods Huntress of the Sun', 530, 9699.4300, -6701.3600, -0.2076, 1),
(3934, 'Exit Portal', 571, 3877.1400, 6980.8300, 152.0400, 1),
(3935, 'Feathermoon Stronghold innkeeper', 1, -4381.5900, 3289.4500, 13.6266, 1),
(3936, 'Felwood Bloodvenom River', 1, 5288.7400, -544.2570, 328.6870, 1),
(3937, 'Felwood Irontree Cavern', 1, 6353.3200, -1696.9200, 440.0420, 1),
(3938, 'Felwood Shrine of the Deceiver', 1, 4779.2600, -572.0400, 275.8440, 1),
(3939, 'Gadgetzan Transporter', 1, -7109.1200, -3825.2100, 10.1529, 1),
(3940, 'Gadgetzan innkeeper', 1, -7158.9600, -3841.6100, 8.8481, 1),
(3941, 'Gallows End Tavern innkeeper', 0, 2269.5100, 244.9440, 34.3402, 1),
(3942, 'Gates of AhnQiraj', 1, -8233.1200, 1535.7500, -0.3744, 1),
(3943, 'Ghostlands Deatholme', 530, 6465.7700, -6433.6600, 50.4155, 1),
(3944, 'Ghostlands Thalassian Pass', 530, 6554.0200, -6811.2000, 110.6000, 1),
(3945, 'Ghostly Baker', 532, -11057.7000, -1919.8300, 77.3515, 1),
(3946, 'Gnomeregan', 90, -617.4620, 370.6960, -247.1880, 1),
(3947, 'Gravity Lapse - Center Teleport', 585, 148.5000, 181.0000, -16.7000, 1),
(3948, 'Greater Shadowbat', 532, -10935.0000, -1999.5000, 49.4748, 1),
(3949, 'Grizzly Hills - Quest - Arugal Teleport Back', 571, 3841.4000, -3426.6500, 293.1040, 1),
(3950, 'Grizzly Hills Boulder Hills', 571, 5082.6400, -4724.1900, 287.5000, 1),
(3951, 'Grizzly Hills Duskhowl Den', 571, 3992.0100, -4523.4300, 195.6070, 1),
(3952, 'Grizzly Hills Ursocs Den', 571, 4692.7100, -3863.7500, 327.3770, 1),
(3953, 'Grizzly Hills Voldrune Dwelling', 571, 3005.7300, -2610.3100, 98.5530, 1),
(3954, 'Gruuls Lair', 565, 107.8710, 282.5120, 1.9718, 1),
(3955, 'Gunship Portal', 628, 747.0000, -1075.0000, 135.0000, 1),
(3956, 'Gunship Portal', 641, 16.4763, 0.0184, 20.4162, 1),
(3957, 'Gunship Portal', 642, 12.3199, 0.0963, 34.6508, 1),
(3958, 'Heart of the Deconstructor', 603, 886.2750, -12.0545, 409.6020, 1),
(3959, 'Hellfire Citadel: The Blood Furnace', 542, 331.0690, 28.0790, 9.7047, 1),
(3960, 'Hellfire Citadel: The Shattered Halls', 540, 203.6690, 157.2020, -42.3511, 1),
(3961, 'Hellfire Peninsula The Path of Anguish', 530, -525.2730, 2066.0700, 94.5205, 1),
(3962, 'Hex Lord Malacrass', 568, 117.3630, 923.5690, 33.9726, 1),
(3963, 'Hillsbrad Foothills Durnholde Keep', 0, -489.8100, -1480.9400, 88.1965, 1),
(3964, 'HiveZara Swarmer Teleport', 509, -9757.8700, 1416.7100, 76.7664, 1),
(3965, 'HiveZara Swarmer Teleport', 509, -9778.9100, 1419.9800, 61.0743, 1),
(3966, 'HiveZara Swarmer Teleport', 509, -9805.9500, 1422.8500, 77.5852, 1),
(3967, 'HiveZara Swarmer Teleport', 509, -9827.5800, 1506.2800, 82.3052, 1),
(3968, 'HiveZara Swarmer Teleport', 509, -9829.4200, 1456.3700, 90.7015, 1),
(3969, 'Horde Log Ride 01 Begin', 571, 4313.3700, -2958.1700, 318.4630, 1),
(3970, 'Howling Fjord New Agamand Inn', 571, 456.4610, -4519.2400, 244.9780, 1),
(3971, 'Howling Fjord Utgarde Keep', 571, 855.8150, -4831.8500, -115.7170, 1),
(3972, 'Howling Fjord Westguard Keep', 571, 1385.1700, -3244.6400, 162.8370, 1),
(3973, 'Howling Fjord Wildervar Mine', 571, 2604.4700, -5019.1400, 293.8260, 1),
(3974, 'Icecrown Jotunheim', 571, 7220.9200, 4113.1400, 630.8240, 1),
(3975, 'Icecrown Kulgalar Keep', 571, 6831.2300, 3817.0800, 621.1530, 1),
(3976, 'Icecrown Nazanak: The Forgotten Depths', 571, 5853.5100, 1807.1700, -344.9310, 1),
(3977, 'Icecrown The Fleshwerks', 571, 6610.5800, 3359.6700, 660.4740, 1),
(3978, 'Indisposed II', 571, 3454.1100, -2802.3700, 202.1400, 1),
(3979, 'Ironforge The Forlorn Cavern', 0, -4636.2200, -1110.0000, 501.4110, 1),
(3980, 'Ironforge flightMaster', 0, -4821.1300, -1152.4000, 502.2950, 1),
(3981, 'Ironforge innkeeper', 0, -4840.6700, -857.0940, 501.9970, 1),
(3982, 'Ironforge innkeeper', 0, -4908.1700, -970.9860, 505.2260, 1),
(3983, 'Ironforge', 0, -4832.2700, -1069.6400, 502.2680, 1),
(3984, 'Kalecgos', 585, 197.8630, -272.7440, -8.6516, 1),
(3985, 'Lady Faltheress', 129, 2583.1800, 695.8610, 56.8033, 1),
(3986, 'Lady Liadrin', 580, 1720.0200, 643.2330, 28.1335, 1),
(3987, 'Loch Modan Stoutlager Inn', 0, -5391.7300, -2974.8800, 325.9470, 1),
(3988, 'Lord Blackwood', 289, 200.2010, 150.8390, 109.8790, 1),
(3989, 'MaiKyl', 230, 842.7150, -181.5710, -49.6700, 1),
(3990, 'Majordomo Teleport', 409, 848.9330, -812.8750, -229.6010, 1),
(3991, 'Maraudon Portal Effect', 349, 386.2700, 33.4144, -130.9340, 1),
(3992, 'Maraudon', 349, 614.7090, -206.5360, -64.2963, 1),
(3993, 'Mesa Elevator', 209, 2600.8100, 1228.7200, -40.2788, 1),
(3994, 'Mesa Elevator', 209, 2600.8100, 1228.7200, 89.0211, 1),
(3995, 'Mesa Elevator', 209, 2617.5100, 1243.9200, -40.5284, 1),
(3996, 'Mesa Elevator', 209, 2617.5100, 1243.9200, 89.0253, 1),
(3997, 'Mesa Elevatorentry', 1, -1030.1400, -32.0337, 68.2329, 1),
(3998, 'Mesa Elevatorentry', 1, -1032.6600, -26.3241, 141.1470, 1),
(3999, 'Mesa Elevatorentry', 1, -1035.1900, -45.0725, 68.2586, 1),
(4000, 'Mesa Elevatorentry', 1, -1042.0500, -47.7792, 141.1470, 1),
(4001, 'Mesa Elevatorentry', 1, -1284.7700, 185.1110, 130.3230, 1),
(4002, 'Mesa Elevatorentry', 1, -1290.7100, 188.5430, 67.4105, 1),
(4003, 'Mesa Elevatorentry', 1, -1304.5600, 186.1990, 67.5106, 1),
(4004, 'Mesa Elevatorentry', 1, -1308.1800, 180.4040, 130.2800, 1),
(4005, 'Mesa Elevatorentry', 1, -4660.6000, -1828.2700, 85.6823, 1),
(4006, 'Mesa Elevatorentry', 1, -4666.2300, -1851.2600, 85.6813, 1),
(4007, 'Mesa Elevatorentry', 1, -4666.4800, -1832.0900, -45.3171, 1),
(4008, 'Mesa Elevatorentry', 1, -4670.1200, -1845.7300, -45.1181, 1),
(4009, 'Mesa Elevatorentry', 1, -5385.2000, -2485.3300, 89.4297, 1),
(4010, 'Mesa Elevatorentry', 1, -5385.2700, -2492.0100, -41.7147, 1),
(4011, 'Mesa Elevatorentry', 1, -5395.6800, -2501.7500, -41.5869, 1),
(4012, 'Mesa Elevatorentry', 1, -5402.5700, -2501.2400, 89.4297, 1),
(4013, 'Mesa Elevatorentry', 209, 2597.4300, 1232.1000, 89.4297, 1),
(4014, 'Mesa Elevatorentry', 209, 2604.3200, 1231.5800, -41.5870, 1),
(4015, 'Mesa Elevatorentry', 209, 2614.7300, 1241.3200, -41.7145, 1),
(4016, 'Mesa Elevatorentry', 209, 2614.8000, 1248.0000, 89.4297, 1),
(4017, 'Mesa Elevatorentry', 47, 1729.8800, 1354.2700, -45.1181, 1),
(4018, 'Mesa Elevatorentry', 47, 1733.5200, 1367.9100, -45.3171, 1),
(4019, 'Mesa Elevatorentry', 47, 1733.7700, 1348.7400, 85.6813, 1),
(4020, 'Mesa Elevatorentry', 47, 1739.4000, 1371.7300, 85.6823, 1),
(4021, 'Mole Machine Port to Grim Guzzler', 230, 901.0680, -143.9390, -49.7550, 1),
(4022, 'Molten Core', 409, 889.0990, -822.9660, -227.2440, 1),
(4023, 'Move Bind Sight', 230, 824.8090, -176.1660, -49.7551, 1),
(4024, 'Mudsprocket innkeeper', 1, -4629.4300, -3176.1400, 41.2339, 1),
(4025, 'Murta Grimgut', 209, 1891.0700, 1294.7800, 48.2347, 1),
(4026, 'Nagrand Abandoned Armory', 530, -2052.0900, 7432.7600, -24.9648, 1),
(4027, 'Naxxanar portal', 571, 3692.4300, 3577.9400, 473.3200, 1),
(4028, 'Naxxanar portal', 571, 3734.1400, 3571.1200, 341.6620, 1),
(4029, 'Naxxanar portal', 571, 3738.6400, 3567.6700, 294.5220, 1),
(4030, 'Naxxanar portal', 571, 3739.5000, 3567.0000, 337.5640, 1),
(4031, 'Naxxanar portal', 571, 3739.6600, 3567.2900, 286.7850, 1),
(4032, 'Naxxanar portal', 571, 3787.5700, 3558.1100, 469.3220, 1),
(4033, 'Naxxanar portal', 571, 3801.5000, 3586.0500, 49.5700, 1),
(4034, 'Naxxramas Teleport - Sapphiron Exit', 533, 3005.7300, -3414.7600, 297.0260, 1),
(4035, 'Netherstorm Manaforge Ara', 530, 3965.9500, 3911.9700, 178.4460, 1),
(4036, 'Netherstorm The Violet Tower', 530, 2243.2600, 2243.1400, 101.5590, 1),
(4037, 'Nexus Portal', 578, 1045.5700, 1104.2400, 361.0700, 1),
(4038, 'Nexus Portal', 578, 982.1730, 1055.4500, 359.9670, 1),
(4039, 'Nijels Point innkeeper', 1, 255.6150, 1253.7600, 192.2240, 1),
(4040, 'No Mans Land -> Out of Hyjal', 1, 5010.1700, -4554.9400, 852.1460, 1),
(4041, 'Onyxias Lair', 249, -90.7180, -106.2340, -38.1972, 1),
(4042, 'Orb of the Nexus', 578, 1048.2700, 991.3100, 361.0700, 1),
(4043, 'Orgrims Hammer', 622, 23.4324, 0.0201, 33.5795, 1),
(4044, 'Orgrims Hammerdock', 668, 5223.4700, 1537.8700, 645.6480, 1),
(4045, 'Orgrimmar flightMaster', 1, 1676.2500, -4313.4500, 61.9445, 1),
(4046, 'Out of Body Experience', 0, -465.6990, 1495.9800, 17.3595, 1),
(4047, 'Port to Mazthoril', 1, 5904.2000, -4045.9000, 596.4300, 1),
(4048, 'Portal Effect: Acherus', 609, 2400.0300, -5635.0000, 377.0170, 1),
(4049, 'Portal of Immolthar', 429, -37.5643, 813.4330, -7.4382, 1),
(4050, 'Portal to Blasted Lands', 0, -11708.4000, -3168.0000, -5.0700, 1),
(4051, 'Portal to Blasted Lands', 0, -4606.4400, -928.9970, 501.0700, 1),
(4052, 'Portal to Blasted Lands', 0, -9007.5800, 871.8700, 129.6920, 1),
(4053, 'Portal to Blasted Lands', 0, 1768.7700, 55.3698, -46.3194, 1),
(4054, 'Portal to Blasted Lands', 1, -944.0640, 274.8590, 111.7100, 1),
(4055, 'Portal to Blasted Lands', 1, 1472.0900, -4214.6300, 59.2208, 1),
(4056, 'Portal to Blasted Lands', 1, 9661.8300, 2509.6000, 1331.6300, 1),
(4057, 'Portal to Blasted Lands', 530, -4039.0700, -11555.1000, -138.3370, 1),
(4058, 'Portal to Blasted Lands', 530, 9984.0300, -7108.4300, 47.7049, 1),
(4059, 'Portal to Dalaran', 571, 5807.7500, 588.3470, 661.5050, 1),
(4060, 'Portal to Dalaran', 631, -72.7031, 2282.4500, 32.8673, 1),
(4061, 'Portal to Dalaran', 712, 4.3953, 13.6833, 20.8039, 1),
(4062, 'Portal to Dalaran', 713, 22.1770, 22.9527, 35.6576, 1),
(4063, 'Portal to Orgrimmar', 1, 1321.8100, -4383.1900, 26.2300, 1),
(4064, 'Portal to Orgrimmar', 1, 1921.3700, -4149.2400, 40.4075, 1),
(4065, 'Portal to Stormwind', 0, -8446.8300, 339.9310, 121.3290, 1),
(4066, 'Portal to Stormwind', 0, -9143.5800, 375.1030, 90.6907, 1),
(4067, 'Portal to The Purple Parlor', 571, 5822.3500, 837.0760, 680.6570, 1),
(4068, 'Portal to The Purple Parlor', 571, 5848.4800, 853.7060, 843.1820, 1),
(4069, 'Portal to The Violet Citadel', 571, 5413.0100, 2868.1800, 418.6750, 1),
(4070, 'Portal to The Violet Citadel', 571, 5819.2600, 829.7740, 680.2200, 1),
(4071, 'Portal to Undercity', 0, 1774.8000, 761.1270, 55.0477, 1),
(4072, 'Portal to Undercity', 0, 1962.6900, 235.9200, 39.7700, 1),
(4073, 'Portal: Moonglade Effect', 1, 7828.8400, -2245.6500, 463.7070, 1),
(4074, 'Portal: Return from Moonglade Effect', 571, 6215.5400, -8.4001, 410.1650, 1),
(4075, 'Portal: Valley of Echoes', 571, 6390.5300, 237.0120, 395.8130, 1),
(4076, 'Quest - Port Skettis Prisoner - Location 01', 530, -4106.6400, 3029.7600, 344.8770, 1),
(4077, 'Quest - Port Skettis Prisoner - Location 02', 530, -3720.3500, 3789.9100, 302.8880, 1),
(4078, 'Quest - Port Skettis Prisoner - Location 03', 530, -3669.5700, 3386.7400, 312.9550, 1),
(4079, 'Quest - Teleport: Caverns of Time', 1, -8380.5000, -4262.5600, -207.5340, 1),
(4080, 'Quetzlun', 571, 5716.2600, -4369.3400, 385.8850, 1),
(4081, 'Ragefire Chasm', 389, -223.4540, 87.2837, -24.9351, 1),
(4082, 'Raven', 209, 1886.6400, 1299.1100, 48.3146, 1),
(4083, 'Recall', 30, -1347.0000, -292.0000, 91.0000, 1),
(4084, 'Recall', 30, 648.0000, -34.0000, 47.0000, 1),
(4085, 'Redridge Mountains Renders Rock', 0, -8737.4300, -2213.5000, 149.9600, 1),
(4086, 'Redridge Mountains Stonewatch', 0, -9392.0000, -3026.6300, 136.7700, 1),
(4087, 'Return to Temper', 530, -3286.6300, -12908.6000, 11.7562, 1),
(4088, 'Revanchion', 429, -106.3030, 551.2760, -4.3970, 1),
(4089, 'Ritual Preparation', 575, 296.6890, -346.5040, 90.5482, 1),
(4090, 'Ritual of the Sword', 575, 296.6890, -346.5040, 108.5480, 1),
(4091, 'Rizzles Escape', 1, 3697.2000, -3967.1300, 28.3115, 1),
(4092, 'Sacrifice', 429, -25.9536, -448.2270, -36.0912, 1),
(4093, 'Sacrifice', 532, -11234.2000, -1698.4600, 179.2400, 1),
(4094, 'Sacrifice', 553, 4.0000, 608.0000, -13.8165, 1),
(4095, 'Samuro', 230, 847.6740, -175.8690, -49.6706, 1),
(4096, 'Scaffold Carsentry', 0, -6889.3100, -1211.7600, 240.4440, 1),
(4097, 'Scaffold Carsentry', 0, -6895.5000, -1338.1500, 240.2840, 1),
(4098, 'Scaffold Carsentry', 0, -6899.7000, -1207.6000, 240.4100, 1),
(4099, 'Scaffold Carsentry', 0, -6903.5800, -1201.6700, 193.8930, 1),
(4100, 'Scaffold Carsentry', 0, -6905.7300, -1211.8300, 214.0430, 1),
(4101, 'Scaffold Carsentry', 469, -6889.3100, -1211.7600, 240.4440, 1),
(4102, 'Scaffold Carsentry', 469, -6895.5000, -1338.1500, 240.2840, 1),
(4103, 'Scaffold Carsentry', 469, -6899.7000, -1207.6000, 240.4100, 1),
(4104, 'Scaffold Carsentry', 469, -6903.5800, -1201.6700, 193.8930, 1),
(4105, 'Scaffold Carsentry', 469, -6905.7300, -1211.8300, 214.0430, 1),
(4106, 'Scaffold Carsentry', 530, -3292.8900, 1901.2800, 142.1590, 1),
(4107, 'Scaffold Carsentry', 530, -3297.0700, 1898.6700, 101.3760, 1),
(4108, 'Scaffold Carsentry', 530, -3300.1400, 1902.2300, 168.4120, 1),
(4109, 'Scaffold Carsentry', 530, -3301.8300, 1895.4800, 122.1590, 1),
(4110, 'Scaffold Carsentry', 530, -3306.6700, 1902.6700, 168.5590, 1),
(4111, 'Scaffold Carsentry', 571, 8115.1400, -245.3940, 900.1890, 1),
(4112, 'Scaffold Carsentry', 571, 8118.4000, -380.0000, 1027.9200, 1),
(4113, 'Scaffold Carsentry', 571, 8118.7700, -386.8160, 981.7890, 1),
(4114, 'Scaffold Carsentry', 571, 8119.4000, -256.0000, 926.3570, 1),
(4115, 'Scaffold Carsentry', 571, 8119.6800, -379.0410, 1028.0500, 1),
(4116, 'Scaffold Carsentry', 571, 8122.6000, -247.5780, 926.3930, 1),
(4117, 'Scaffold Carsentry', 571, 8123.9300, -240.0710, 880.1890, 1),
(4118, 'Scaffold Carsentry', 571, 8127.2900, -380.4440, 1001.7900, 1),
(4119, 'Scroll of Recall', 0, -103.9880, -902.7950, 55.5340, 1),
(4120, 'Scroll of Recall', 0, -10446.9000, -3261.9100, 20.1790, 1),
(4121, 'Scroll of Recall', 0, -5506.3400, -704.3480, 392.6860, 1),
(4122, 'Scroll of Recall', 0, -9470.7600, 3.9090, 49.7940, 1),
(4123, 'Scroll of Recall', 0, 1804.8400, 196.3220, 70.3990, 1),
(4124, 'Scroll of Recall', 0, 286.3140, -2184.0900, 122.6120, 1),
(4125, 'Scroll of Recall', 1, -1060.2700, 23.1370, 141.4550, 1),
(4126, 'Scroll of Recall', 1, -506.2240, -2590.0800, 113.1500, 1),
(4127, 'Scroll of Recall', 1, -7135.7200, -3787.7700, 8.7990, 1),
(4128, 'Scroll of Recall', 1, 6395.7100, 433.2560, 33.2600, 1),
(4129, 'Scryers Tier innkeeper', 530, -2184.0400, 5399.6200, 51.9658, 1),
(4130, 'Searing Gorge Blackchar Cave', 0, -7249.7500, -814.5230, 296.6230, 1),
(4131, 'Searing Gorge Blackrock Mountain', 0, -7243.0600, -1122.9400, 274.6300, 1),
(4132, 'Searing Gorge The Slag Pit', 0, -6601.6300, -1250.7500, 188.0640, 1),
(4133, 'Sewer Teleport 01', 571, 5881.2000, 666.5000, 615.7940, 1),
(4134, 'Sewer Teleport 04', 571, 5815.3000, 714.6000, 619.0980, 1),
(4135, 'Shadow Port', 33, -103.4600, 2122.1000, 155.6550, 1),
(4136, 'Shadow Port', 33, -105.6540, 2154.9800, 156.4300, 1),
(4137, 'Shadow Port', 33, -84.9900, 2151.0100, 155.6200, 1),
(4138, 'Shadowblink', 469, -7469.9300, -1227.9300, 476.7770, 1),
(4139, 'Shadowblink', 469, -7486.3600, -1194.3200, 476.8000, 1),
(4140, 'Shadowblink', 469, -7500.7000, -1249.8900, 476.7980, 1),
(4141, 'Shadowblink', 469, -7506.5800, -1165.2600, 476.7960, 1),
(4142, 'Shadowblink', 469, -7524.3600, -1219.1200, 476.7940, 1),
(4143, 'Shadowblink', 469, -7538.6300, -1273.6400, 476.8000, 1),
(4144, 'Shadowblink', 469, -7542.4700, -1191.9200, 476.3550, 1),
(4145, 'Shadowblink', 469, -7561.5400, -1244.0100, 476.8000, 1),
(4146, 'Shadowblink', 469, -7581.1100, -1216.1900, 476.8000, 1),
(4147, 'Shadowmoon Darkcaster', 540, 72.4708, 184.4520, -13.2380, 1),
(4148, 'Shadowmoon Valley Oronoks Farm', 530, -2802.3200, 1286.4600, 77.3836, 1),
(4149, 'Shattrath Banish Teleport', 530, -1500.0300, 5217.1400, 32.4600, 1),
(4150, 'Shattrath City Aldor Rise', 530, -1751.2400, 5641.2300, 129.0710, 1),
(4151, 'Shattrath City Scryers Tier', 530, -2041.2900, 5561.9700, 54.4371, 1),
(4152, 'Shattrath City innkeeper', 530, -1903.7900, 5765.7000, 131.2960, 1),
(4153, 'Shattrath Portal to Darnassus', 530, -1790.9800, 5413.9800, -12.4282, 1),
(4154, 'Shattrath Portal to Exodar', 530, -1880.2800, 5357.5300, -12.4281, 1),
(4155, 'Shattrath Portal to Exodar', 530, -4031.2400, -11569.6000, -138.2990, 1),
(4156, 'Shattrath Portal to Ironforge', 530, -1795.7900, 5399.6300, -12.4281, 1),
(4157, 'Shattrath Portal to Orgrimmar', 530, -1934.4900, 5453.4800, -12.4279, 1),
(4158, 'Shattrath Portal to Silvermoon', 530, -1894.6900, 5362.3400, -12.4282, 1),
(4159, 'Shattrath Portal to Stormwind', 0, -8998.1400, 861.2540, 29.6206, 1),
(4160, 'Shattrath Portal to Stormwind', 530, -1792.7800, 5406.5400, -12.4279, 1),
(4161, 'Shattrath Portal to Thunder Bluff', 530, -1936.3200, 5445.9500, -12.4282, 1),
(4162, 'Shattrath Portal to Undercity', 0, 1773.4200, 61.7391, -46.3215, 1),
(4163, 'Shattrath Portal to Undercity', 530, -1931.4800, 5460.4900, -12.4281, 1),
(4164, 'Ship (The Bravery)dock', 0, -8644.6400, 1333.7100, 5.7993, 1),
(4165, 'Ship (The Bravery)dock', 1, 6419.1900, 818.6660, 6.1894, 1),
(4166, 'Ship (The Lady Mehley)dock', 0, -3898.2700, -597.7260, 5.5310, 1),
(4167, 'Ship (The Lady Mehley)dock', 1, -4006.9700, -4730.3300, 5.5366, 1),
(4168, 'Ship (The Maidens Fancy)dock', 0, -14279.5000, 571.2000, 6.0777, 1),
(4169, 'Ship (The Maidens Fancy)dock', 1, -997.3520, -3830.2500, 5.5803, 1),
(4170, 'Ship, Icebreaker (Northspear)dock', 571, 584.0140, -5098.6700, -6.4857, 1),
(4171, 'Ship, Night Elf (Elunes Blessing)dock', 1, 6546.9800, 927.7040, 6.1894, 1),
(4172, 'Ship, Night Elf (Elunes Blessing)dock', 530, -4264.1400, -11328.3000, 6.0035, 1),
(4173, 'Ship, Night Elf (Feathermoon Ferry)dock', 1, -4210.9100, 3280.7300, 5.7750, 1),
(4174, 'Ship, Night Elf (Moonspray)dock', 1, 6584.1600, 773.2750, 6.0986, 1),
(4175, 'Ship, Night Elf (Moonspray)dock', 1, 8545.0900, 1016.1700, 6.2912, 1),
(4176, 'Sholazar Basin Nesingwary Base Camp', 571, 5559.1100, 5765.4300, -77.9385, 1),
(4177, 'Sholazar Basin The Seabreach Flow', 571, 5527.8500, 5348.4900, -134.4620, 1),
(4178, 'Silithus Ortells Hideout', 1, -7578.7200, 196.9140, 11.5488, 1),
(4179, 'Silithus Twilights Run', 1, -6265.9300, 41.8866, 9.0625, 1),
(4180, 'Silverpine Forest Fenris Keep', 0, 993.8510, 690.0600, 74.8984, 1),
(4181, 'Sister Mercydock', 571, 254.1330, -3752.8000, 0.2291, 1),
(4182, 'Sister Mercydock', 571, 93.8904, -3681.4100, 0.2103, 1),
(4183, 'Skadi Teleport', 575, 476.7990, -511.1670, 104.7230, 1),
(4184, 'Skarthis the Summoner', 547, -76.9917, -157.0810, -2.1064, 1),
(4185, 'Socrethar Return Portal Effect', 530, 4778.4600, 3455.3600, 104.1300, 1),
(4186, 'Stonetalon Mountains Boulderslide Ravine', 1, -97.6734, 185.5910, 96.6757, 1),
(4187, 'Stonetalon Mountains Cragpool Lake', 1, 1605.9900, 96.7067, 98.5654, 1),
(4188, 'Stonetalon Mountains Windshear Mine', 1, 935.9090, -302.7410, 0.0220, 1),
(4189, 'Storm Peaks', 571, 8974.3700, -1280.5600, 1059.0100, 1),
(4190, 'Stormwind Stockade', 34, 131.1490, 3.3395, -25.5229, 1),
(4191, 'Stormwind to Dustwallow Teleport', 1, -3722.9100, -4413.9600, 26.1300, 1),
(4192, 'Stoutlager Inn innkeeper', 0, -5377.9100, -2973.9100, 323.2520, 1),
(4193, 'Stranglethorn Vale Kurzens Compound', 0, -11571.3000, -644.0530, 31.2554, 1),
(4194, 'Stranglethorn Vale The Vile Reef', 0, -12211.5000, 644.0560, -67.1350, 1),
(4195, 'Subwayentry', 369, -1.0668, -11.3475, -3.9157, 1),
(4196, 'Subwayentry', 369, -1.0668, 2470.3000, -3.9157, 1),
(4197, 'Subwayentry', 369, -39.7335, 9.7886, -3.9157, 1),
(4198, 'Subwayentry', 369, -39.7335, -10.3899, -3.9157, 1),
(4199, 'Subwayentry', 369, -39.7335, 30.3816, -3.9157, 1),
(4200, 'Subwayentry', 369, -50.9334, 2472.9300, -3.9157, 1),
(4201, 'Subwayentry', 369, -50.9334, 2512.1500, -3.9157, 1),
(4202, 'Subwayentry', 369, -51.0097, 2492.2300, -3.9157, 1),
(4203, 'Subwayentry', 369, 10.1333, 2510.4900, -3.9157, 1),
(4204, 'Subwayentry', 369, 10.1333, 2490.7400, -3.9157, 1),
(4205, 'Subwayentry', 369, 10.1333, 8.8000, -3.9157, 1),
(4206, 'Subwayentry', 369, 10.1896, 28.7706, -3.9157, 1),
(4207, 'Summon Menagerie', 578, 1116.1100, 1075.1700, 508.3490, 1),
(4208, 'Summon Menagerie', 578, 1163.7200, 1170.9900, 527.3220, 1),
(4209, 'Summon Menagerie', 578, 968.7080, 1042.4900, 527.3220, 1),
(4210, 'Sunken Temple', 109, -455.3780, 76.5158, -93.3827, 1),
(4211, 'Surface Portal', 571, 5795.8800, 2070.8700, -344.0460, 1),
(4212, 'Surface Portal', 571, 6203.8700, 2262.1700, 497.1970, 1),
(4213, 'Surge Needle Teleporter', 571, 3444.3800, 2361.8700, 38.7409, 1),
(4214, 'Swamp of Sorrows Stagalbog Cave', 0, -10919.3000, -3673.0500, 11.0245, 1),
(4215, 'Swamp of Sorrows Stonard', 0, -10437.8000, -3307.0400, 20.4499, 1),
(4216, 'Teldrassil Banethil Barrow Den', 1, 9794.8400, 1549.1500, 1262.8000, 1),
(4217, 'Teldrassil Fel Rock', 1, 10124.0000, 1115.9100, 1322.8200, 1),
(4218, 'Teldrassil Shadowthread Cave', 1, 10889.4000, 917.5860, 1326.6400, 1),
(4219, 'Teleport - Coldarra, Transitus Shield to Amber Ledge', 571, 3594.1800, 5997.5100, 136.2150, 1),
(4220, 'Teleport - Dalaran to Wintergrasp', 571, 5325.0600, 2843.3600, 409.2850, 1),
(4221, 'Teleport Darkshire', 0, -10566.0000, -1189.0000, 28.0000, 1),
(4222, 'Teleport Defenders', 607, 1226.4300, -71.9616, 70.0842, 1),
(4223, 'Teleport Duskwood', 0, -10368.0000, -422.0000, 64.1214, 1),
(4224, 'Teleport Elwynn', 0, -9104.0000, -70.0000, 83.0000, 1),
(4225, 'Teleport Goldshire', 0, -9464.0000, 62.0000, 56.0000, 1),
(4226, 'Teleport Left', 533, 2692.0000, -3399.2700, 267.6860, 1),
(4227, 'Teleport Moonbrook', 0, -11020.0000, 1436.0000, 43.6892, 1),
(4228, 'Teleport Players on Victory', 631, -548.9830, 2211.2400, 539.2900, 1),
(4229, 'Teleport Return', 533, 2685.0600, -3502.3700, 261.3150, 1),
(4230, 'Teleport Right', 533, 2692.0000, -3321.8600, 267.6860, 1),
(4231, 'Teleport Sanctum Moon - Down', 530, 7513.6300, -6388.9300, 23.8000, 1),
(4232, 'Teleport Sanctum Sun - Down', 530, 7199.4000, -7097.3600, 66.9700, 1),
(4233, 'Teleport To Zone In', 616, 728.0550, 1329.0300, 275.0000, 1),
(4234, 'Teleport Violet Citadel Spire Down', 571, 5790.0000, 734.0000, 640.0000, 1),
(4235, 'Teleport Westfall', 0, -10643.0000, 1052.0000, 33.8437, 1),
(4236, 'Teleport and Transform', 580, 1667.6400, 633.4660, 28.0500, 1),
(4237, 'Teleport back to Main Room', 603, 1970.6100, -25.5988, 324.5500, 1),
(4238, 'Teleport from Azshara Tower', 1, 3641.0000, -4702.0000, 121.0000, 1),
(4239, 'Teleport to Ashtongue NPCs', 564, 702.2200, 200.3000, 125.0100, 1),
(4240, 'Teleport to Center', 568, -34.3160, 1149.6400, 19.1550, 1),
(4241, 'Teleport to Chamber Illusion', 603, 2043.1200, -25.6981, 239.7210, 1),
(4242, 'Teleport to CoT Stratholme Phase 4', 595, 2071.5500, 1287.6800, 141.6870, 1),
(4243, 'Teleport to Council', 564, 603.4200, 305.9820, 271.9000, 1),
(4244, 'Teleport to Final Chamber Effect DND', 531, -8632.8400, 2055.8700, 108.8600, 1),
(4245, 'Teleport to Gnomeregan', 0, -5095.0000, 757.0000, 261.0000, 1),
(4246, 'Teleport to Hall of Command', 0, 2402.6200, -5633.2800, 377.0210, 1),
(4247, 'Teleport to Heart of Acherus', 609, 2419.9100, -5620.4800, 420.6440, 1),
(4248, 'Teleport to Icecrown Illusion', 603, 1949.1300, -80.6744, 239.9900, 1),
(4249, 'Teleport to Inside Violet Hold', 608, 1857.2400, 803.8770, 44.0085, 1),
(4250, 'Teleport to Lake Wintergrasp', 571, 4561.5800, 2835.3300, 389.7900, 1),
(4251, 'Teleport to Lake Wintergrasp', 571, 5025.7100, 3673.4100, 362.6870, 1),
(4252, 'Teleport to Lake Wintergrasp', 571, 5094.6700, 2170.3300, 365.6010, 1),
(4253, 'Teleport to Lake Wintergrasp', 571, 5386.0500, 2840.9700, 418.6750, 1),
(4254, 'Teleport to Molten Core DND', 409, 1080.0000, -483.0000, -108.0000, 1),
(4255, 'Teleport to Stormwind Illusion', 603, 1954.1400, 21.5220, 239.7180, 1),
(4256, 'Teleport to Sunwell Plateau', 580, 1861.4500, 495.1250, 82.9059, 1),
(4257, 'Teleport to Twin Emps Effect DND', 531, -8971.8100, 1321.4700, -104.2490, 1),
(4258, 'Teleport to Violet Stand', 571, 5724.6200, 1013.1700, 174.4800, 1),
(4259, 'Teleport to the Silvermoon', 530, 10021.1000, -7014.8700, 49.7100, 1),
(4260, 'Teleport to the Undercity', 0, 1805.9300, 335.6600, 70.3900, 1),
(4261, 'Teleport', 1, -3891.8000, -4609.9700, 9.5011, 1),
(4262, 'Teleport', 409, 736.5160, -1176.3500, -119.0060, 1),
(4263, 'Teleport', 531, -8306.6800, 2060.8400, 133.0620, 1),
(4264, 'Teleport', 531, -8330.6300, 2123.1400, 133.0620, 1),
(4265, 'Teleport', 533, 2633.4900, -3529.5600, 274.1110, 1),
(4266, 'Teleport', 533, 2905.6300, -3769.9600, 273.6200, 1),
(4267, 'Teleport', 571, 3574.2200, 6652.1300, 195.1850, 1),
(4268, 'Teleport', 571, 3646.7400, 5893.2000, 174.4830, 1),
(4269, 'Teleport', 571, 4590.9400, -5711.2400, 184.5070, 1),
(4270, 'Teleport', 576, 504.7420, 88.9122, -16.1245, 1),
(4271, 'Teleport', 578, 1103.4700, 1049.5700, 512.0000, 1),
(4272, 'Teleport', 600, -369.0000, -601.0000, 2.0000, 1),
(4273, 'Teleport: Argent Tournament', 571, 8480.5500, 1092.9000, 554.4850, 1),
(4274, 'Teleport: Black Temple', 530, -3560.5200, 583.3530, 10.9431, 1),
(4275, 'Teleport: Darnassus', 1, 9664.1400, 2526.3600, 1332.6900, 1),
(4276, 'Teleport: Moonglade', 1, 7992.6200, -2680.0400, 512.0990, 1),
(4277, 'Teleport: Shattrath', 530, -1842.0700, 5497.1700, -12.4306, 1),
(4278, 'Teleport: Stonard', 0, -10469.0000, -3331.5400, 25.4716, 1),
(4279, 'Teleport: Theramore', 1, -3748.1100, -4440.2100, 30.5688, 1),
(4280, 'Tempest Keep', 550, 411.4090, -39.8267, 20.1802, 1);
INSERT INTO `playerbots_travelnode` (`id`, `name`, `map_id`, `x`, `y`, `z`, `linked`) VALUES
(4281, 'Tempest Keep: The Arcatraz', 552, 278.6480, -12.6903, 22.4479, 1),
(4282, 'Tempest Keep: The Botanica', 553, 17.5470, 404.8610, -27.1645, 1),
(4283, 'Tempest Keep: The Mechanar', 554, 169.2420, -12.2941, -0.0010, 1),
(4284, 'Terokkar Forest Skettis', 530, -3944.9300, 3664.0900, 287.9900, 1),
(4285, 'Terokkar Forest Terokks Rest', 530, -3868.3500, 3521.8800, 278.5610, 1),
(4286, 'Test of Lore', 1, -2354.0300, -1902.0700, 95.7800, 1),
(4287, 'The Barrens Baeldun Keep', 1, -4071.7300, -2381.6400, 126.2140, 1),
(4288, 'The Barrens Dreadmist Den', 1, 318.8870, -2225.7300, 212.5040, 1),
(4289, 'The Chilled Quagmire spiritguide', 571, 5103.1300, 3462.1300, 368.5680, 1),
(4290, 'The Chilled Quagmire spirithealer', 571, 5099.0300, 3469.6700, 368.4850, 1),
(4291, 'The Nexus', 576, 545.9580, -125.9330, -24.9367, 1),
(4292, 'The Skybreaker', 623, 5.2403, 0.2579, 20.8691, 1),
(4293, 'The Storm Peaks Frostfloe Deep', 571, 8300.4300, -2564.8600, 1153.5900, 1),
(4294, 'The Storm Peaks Frostgrips Hollow', 571, 6956.1400, -2.5143, 808.5300, 1),
(4295, 'The Storm Peaks Gimoraks Den', 571, 7920.2400, -1625.2100, 910.8520, 1),
(4296, 'The Storm Peaks Hibernal Cavern', 571, 7241.0300, -2075.5900, 763.0780, 1),
(4297, 'The Storm Peaks Plain of Echoes', 571, 8109.8900, -2815.7900, 1135.3200, 1),
(4298, 'The Storm Peaks The Forlorn Mine', 571, 6929.4000, -1315.7700, 831.3460, 1),
(4299, 'The Storm Peaks The Frozen Mine', 571, 7768.5000, -16.3215, 864.4010, 1),
(4300, 'The Storm Peaks', 571, 7268.5100, -2125.3800, 778.1580, 1),
(4301, 'The Sunken Ring spiritguide', 571, 5104.7500, 2300.9500, 368.5680, 1),
(4302, 'The Zephyrdock', 1, -1026.8600, 358.4000, 133.3400, 1),
(4303, 'The Zephyrdock', 1, 1139.7300, -4142.9300, 52.0080, 1),
(4304, 'Thousand Needles Darkcloud Pinnacle', 1, -4904.6700, -1970.1300, 86.8811, 1),
(4305, 'Thousand Needles Roguefeather Den', 1, -5533.0400, -1602.8800, 29.1719, 1),
(4306, 'Thousand Needles Splithoof Hold', 1, -4952.9400, -2337.3000, -56.5321, 1),
(4307, 'Thousand Needles The Weathered Nook', 1, -5217.6500, -2788.9900, -7.4459, 1),
(4308, 'Thrall', 0, 1953.8400, 233.8350, 41.8800, 1),
(4309, 'Thrall', 1, 1920.0100, -4123.9500, 43.6300, 1),
(4310, 'Thunderbrew Distillery innkeeper', 0, -5601.6000, -531.2030, 399.7370, 1),
(4311, 'Tirisfal Glades Agamand Family Crypt', 0, 3043.6500, 681.8670, 67.0126, 1),
(4312, 'Tirisfal Glades Gallows End Tavern', 0, 2262.2600, 244.2570, 33.7170, 1),
(4313, 'To Icecrown Airship - Player - Aura - Teleport to Dalaran Trigger', 571, 5831.5300, 497.0880, 657.4660, 1),
(4314, 'Toshleys Station Transporter', 530, 2054.0500, 5569.1600, 263.5710, 1),
(4315, 'Translocate', 0, 1805.0000, 327.0000, 70.5000, 1),
(4316, 'Translocate', 530, -2259.7400, 3215.0300, -4.0500, 1),
(4317, 'Translocate', 530, -2307.3500, 3123.9200, 13.6900, 1),
(4318, 'Translocate', 530, -589.0000, 4079.0000, 143.3000, 1),
(4319, 'Translocate', 530, 12780.9000, -6877.5000, 22.7861, 1),
(4320, 'Translocate', 530, 9334.5000, -7880.7600, 74.9094, 1),
(4321, 'Translocation', 530, -594.0000, 4079.0000, 94.0000, 1),
(4322, 'Trespasser!', 571, 5773.0000, 703.5000, 641.6000, 1),
(4323, 'Trespasser!', 571, 5846.5000, 605.5000, 650.9000, 1),
(4324, 'Trespasser!', 571, 8460.0000, 700.0000, 547.4000, 1),
(4325, 'Trespasser!', 571, 8573.0000, 703.9000, 547.3000, 1),
(4326, 'Turtle (Green Island)dock', 571, 2649.6000, 844.8000, 1.7773, 1),
(4327, 'Turtle (Walker of Waves)dock', 571, 2638.1300, 938.4000, 1.7773, 1),
(4328, 'Uldaman', 70, -42.3058, 263.8750, -48.9355, 1),
(4329, 'Ulduar spirithealer', 571, 9025.6600, -1178.5700, 1060.0800, 1),
(4330, 'Undervatorentry', 0, 1552.8000, 240.7730, 55.4904, 1),
(4331, 'Undervatorentry', 0, 1596.1500, 283.2000, 55.4904, 1),
(4332, 'Undervatorentry', 0, 1596.2000, 197.1040, 55.4904, 1),
(4333, 'Undervatorentryentry', 0, 1564.0000, 240.6560, 55.7571, 1),
(4334, 'Undervatorentryentry', 0, 1595.3800, 197.7060, 55.4904, 1),
(4335, 'Undervatorentryentry', 0, 1595.3800, 213.3330, 57.8905, 1),
(4336, 'Undervatorentryentry', 0, 1595.6500, 271.8890, 55.7571, 1),
(4337, 'Use Legion Teleporter', 530, -2833.0900, 1949.8900, 201.2560, 1),
(4338, 'Vampiric Shadowbat', 532, -10930.9000, -1995.7500, 49.4768, 1),
(4339, 'Vanish', 309, -11516.1000, -1605.3100, 41.3000, 1),
(4340, 'Vator2entry', 90, -800.3290, 314.9250, -272.4900, 1),
(4341, 'Vatorentry', 0, -5163.7700, 655.3050, 348.5880, 1),
(4342, 'Vatorentry', 0, -5164.2400, 650.3540, 247.9780, 1),
(4343, 'Vortex', 616, 755.0000, 1301.0000, 280.0000, 1),
(4344, 'Watery Grave', 548, 337.6900, -732.8700, -13.7400, 1),
(4345, 'Watery Grave', 548, 365.5300, -737.1200, -14.0000, 1),
(4346, 'Watery Grave', 548, 366.2700, -709.4000, -13.9200, 1),
(4347, 'Watery Grave', 548, 372.8500, -690.8400, -13.9100, 1),
(4348, 'Weegli Blastfuse', 209, 1881.0500, 1297.3600, 48.4190, 1),
(4349, 'Western Plaguelands Mardenholde Keep', 0, 2936.4100, -1395.9000, 166.0270, 1),
(4350, 'Westfall The Cooper Residence', 0, -11023.1000, 1547.4800, 44.4936, 1),
(4351, 'Winterspring Everlook', 1, 6768.3400, -4668.5200, 723.7480, 1),
(4352, 'Winterspring Moon Horror Den', 1, 7124.2700, -4637.7800, 639.6550, 1),
(4353, 'Wyrmrest Temple flightMaster', 571, 3647.2600, 244.0510, 52.3397, 1),
(4354, 'Wyrmrest Temple spirithealer', 571, 3546.8600, 272.9880, 45.5817, 1),
(4355, 'Zeppelin (The Iron Eagle)dock', 0, -12452.0000, 221.0670, 31.7681, 1),
(4356, 'Zeppelin (The Iron Eagle)dock', 1, 1359.5700, -4632.2000, 53.6569, 1),
(4357, 'Zeppelin (The Purple Princess)dock', 0, -12407.5000, 211.8380, 31.5014, 1),
(4358, 'Zeppelin (The Purple Princess)dock', 0, 2061.4200, 235.5580, 100.1520, 1),
(4359, 'Zeppelin (The Thundercaller)dock', 0, 2064.2400, 291.6030, 97.0904, 1),
(4360, 'Zeppelin (The Thundercaller)dock', 1, 1319.3200, -4658.0900, 53.8358, 1),
(4361, 'Zeppelindock', 571, 1412.9900, -3092.5800, 166.2270, 1),
(4362, 'ZulDrak Amphitheater of Anguish', 571, 5715.7900, -2945.0100, 296.5510, 1),
(4363, 'ZulDrak ZimTorga', 571, 5756.5800, -3567.6300, 387.0390, 1),
(4364, '[PH] Teleport to Auberdine', 1, 6581.0500, 767.5000, 5.7843, 1),
(4365, '[PH] Teleport to Booty Bay', 0, -14457.0000, 496.4500, 39.1392, 1),
(4366, '[PH] Teleport to Felwood', 1, 5483.9000, -749.8810, 334.6210, 1),
(4367, '[PH] Teleport to GromGol', 0, -12415.0000, 207.6180, 31.5017, 1),
(4368, '[PH] Teleport to Menethil Harbor', 0, -3752.8100, -851.5580, 10.1153, 1),
(4369, '[PH] Teleport to Orgrimmar', 1, 1552.5000, -4420.6600, 8.9480, 1),
(4370, '[PH] Teleport to Theramore', 1, -3615.4900, -4467.3400, 21.6032, 1),
(4371, 'c-Maraudon', 1, -1424.0400, 2945.0100, 134.5400, 1),
(4372, 'c-Tauren start', 1, -3034.7000, 144.0400, 70.8700, 1),
(4373, 'c1-Blackfathom Deeps', 1, 4158.0100, 877.6000, -20.6800, 1),
(4374, 'c1-Blackrock Mountain', 0, -7502.2000, -1152.9800, 269.5500, 1),
(4375, 'c1-Coilfang', 530, 571.1000, 6938.9700, -16.8100, 1),
(4376, 'c1-Deadmine exit', 0, -11367.5000, 1617.1000, 71.2200, 1),
(4377, 'c1-Dire Maul', 1, -3626.3900, 917.3700, 150.1300, 1),
(4378, 'c1-Ebon Hold', 609, 2390.0200, -5640.9100, 377.0900, 1),
(4379, 'c1-Sunken Temple', 0, -10416.5000, -3832.5300, -36.9200, 1),
(4380, 'c1-The Noxious Pass', 609, 2528.2200, -5580.4400, 162.0200, 1),
(4381, 'c1-Timbermaw Hold', 1, 7016.7500, -2153.8400, 595.0900, 1),
(4382, 'c1-Wailing Caverns', 1, -588.5300, -2037.6900, 57.6000, 1),
(4383, 'c2-Blackfathom Deeps', 1, 4156.6000, 909.8900, -20.9700, 1),
(4384, 'c2-Blackrock Mountain', 0, -7591.3100, -1114.4400, 249.9100, 1),
(4385, 'c2-Coilfang', 530, 571.1000, 6938.9700, -15.2000, 1),
(4386, 'c2-Deadmine exit', 0, -11367.1000, 1610.4800, 76.6300, 1),
(4387, 'c2-Dire Maul', 1, -3628.0800, 919.5500, 137.8400, 1),
(4388, 'c2-Ebon Hold', 609, 2383.6500, -5645.2000, 420.7700, 1),
(4389, 'c2-Sunken Temple', 0, -10408.7000, -3834.2900, -44.6900, 1),
(4390, 'c2-The Noxious Pass', 609, 2538.3500, -5573.0500, 162.4600, 1),
(4391, 'c3-Blackfathom Deeps', 1, 4157.4600, 916.4400, -17.4000, 1),
(4392, 'c3-Coilfang', 530, 651.0700, 6865.3700, -82.3400, 1),
(4393, 'c3-Deadmine exit', 0, -11381.5000, 1584.1100, 82.1000, 1),
(4394, 'c3-Ebon Hold', 609, 2325.0300, -5659.6000, 382.2400, 1),
(4395, 'c3-Scarlet Enclave', 609, 2409.0900, -5722.3700, 154.0000, 1),
(4396, 'c3-Sunken Temple', 0, -10325.2000, -3865.8600, -44.4500, 1),
(4397, 'c3-The Noxious Pass', 609, 2546.6100, -5563.8900, 162.8800, 1),
(4398, 'c4-Coilfang', 530, 607.1700, 6908.6800, -49.2000, 1),
(4399, 'c4-Deadmine exit', 0, -11379.6000, 1578.7900, 87.8400, 1),
(4400, 'c4-Ebon Hold', 609, 2348.5800, -5695.3500, 382.2400, 1),
(4401, 'c4-Scarlet Enclave', 609, 2402.8600, -5727.0300, 154.0000, 1),
(4402, 'c4-The Noxious Pass', 609, 2563.5200, -5547.7900, 163.2700, 1),
(4403, 'c5-Coilfang', 530, 574.6800, 6942.9300, -37.7200, 1),
(4404, 'c5-Deadmine exit', 0, -11340.2000, 1571.6100, 94.4400, 1),
(4405, 'c6-Coilfang', 530, 723.7400, 6865.7800, -74.1000, 1),
(4406, 'c7-Coilfang', 530, 731.5700, 6866.0100, -70.4700, 1);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,35 @@
-- Manual travelnode coverage for the Aldrassil ramp in Shadowglen
-- (Teldrassil, map 1, zone 141). Adds 9 anchor nodes along the spiral
-- ramp (base -> intermediate ramp waypoints -> top platform near
-- Tenaron Stormgrip). All nodes are `linked = 0` so
-- `.playerbots travel generatenode` will iterate them and let mmap
-- compute the actual walk paths between consecutive nodes. Splitting
-- the climb into short segments (~30y each) gives mmap a much better
-- chance of resolving each piece than a single 300y end-to-end probe.
--
-- TEMPORARILY DISABLED: isolating generatenode behaviour on the OG
-- (cmangos-imported) graph. Re-enable by removing the comment prefix
-- once the regen flow is verified stable.
-- SET @n1 := (SELECT IFNULL(MAX(id), 0) + 1 FROM playerbots_travelnode);
-- SET @n2 := @n1 + 1;
-- SET @n3 := @n1 + 2;
-- SET @n4 := @n1 + 3;
-- SET @n5 := @n1 + 4;
-- SET @n6 := @n1 + 5;
-- SET @n7 := @n1 + 6;
-- SET @n8 := @n1 + 7;
-- SET @n9 := @n1 + 8;
-- INSERT INTO playerbots_travelnode (id, name, map_id, x, y, z, linked) VALUES
-- (@n1, 'Aldrassil Ramp 1 (base)', 1, 10413.756, 887.97363, 1319.3668, 0),
-- (@n2, 'Aldrassil Ramp 2', 1, 10440.520, 870.32320, 1328.9324, 0),
-- (@n3, 'Aldrassil Ramp 3', 1, 10497.001, 854.46014, 1345.1770, 0),
-- (@n4, 'Aldrassil Ramp 4', 1, 10517.199, 821.48640, 1354.7914, 0),
-- (@n5, 'Aldrassil Ramp 5', 1, 10477.926, 847.88855, 1372.1685, 0),
-- (@n6, 'Aldrassil Ramp 6', 1, 10455.358, 831.34240, 1380.9377, 0),
-- (@n7, 'Aldrassil Ramp 7', 1, 10460.220, 800.71716, 1388.3368, 0),
-- (@n8, 'Aldrassil Ramp 8', 1, 10507.434, 793.30420, 1397.2166, 0),
-- (@n9, 'Aldrassil Ramp 9 (top)', 1, 10495.496, 804.67700, 1397.2662, 0);
SELECT 1; -- no-op so the file is still valid SQL for the updater

View File

@ -0,0 +1,13 @@
DELETE FROM ai_playerbot_texts WHERE name IN (
'send_mail_disabled'
);
DELETE FROM ai_playerbot_texts_chance WHERE name IN (
'send_mail_disabled'
);
INSERT INTO ai_playerbot_texts (id, name, text, say_type, reply_type, text_loc1, text_loc2, text_loc3, text_loc4, text_loc5, text_loc6, text_loc7, text_loc8) VALUES
(1899, 'send_mail_disabled', 'I cannot send mail', 0, 0, '우편을 보낼 수 없습니다', 'Je ne peux pas envoyer de courrier', 'Ich kann keine Post senden', '我不能寄送邮件', '我不能寄送郵件', 'No puedo enviar correo', 'No puedo enviar correo', 'Я не могу отправить почту');
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES
('send_mail_disabled', 100);

351
extract_ac_client_data.sh Normal file
View File

@ -0,0 +1,351 @@
#!/usr/bin/env bash
# =============================================================================
# AzerothCore client-data extraction for mod-playerbots.
# Run from anywhere — output lands in $SERVER_DATA_DIR.
# =============================================================================
set -euo pipefail
# ─── PATHS ──────────────────────────────────────────────────────────────────
WOW_CLIENT_DATA="/home/dev/wow_client_data"
TOOLS_DIR="/home/dev/azerothcore_installer/_server/azerothcore/env/dist/bin"
SERVER_DATA_DIR="$TOOLS_DIR"
# ─── TOGGLES ────────────────────────────────────────────────────────────────
EXTRACT_DBC_AND_MAPS=true
EXTRACT_VMAPS=true
EXTRACT_MMAPS=true
MMAP_THREADS=0 # 0 = auto-detect (each thread uses 1-2 GB RAM)
MMAP_SINGLE_MAP="" # e.g. "489" for Warsong Gulch only
# Verbatim copy of azerothcore-wotlk/master:src/tools/mmaps_generator/mmaps-config.yaml
# (also the same config the mod-playerbots fork ships).
MMAPS_CONFIG_YAML=$(cat <<'YAML_EOF'
mmapsConfig:
skipLiquid: false
skipContinents: false
skipJunkMaps: true
skipBattlegrounds: false
# Path to the directory containing navigation data files.
# This directory should contain the "maps" and "vmaps" folders,
# and is also where the "mmaps" folder will be created or located.
dataDir: "./"
meshSettings:
# Here we have global config for recast navigation.
# It's possible to override these data on map or tile level (see mapsOverrides).
# Maximum slope angle (in degrees) NPCs can walk on.
# Surfaces steeper than this will be considered unwalkable.
walkableSlopeAngle: 50
# --- Cell Size Calculation ---
# Many parameters below are defined in "cell units".
# In RecastDemo, you often work with world units instead of cell units.
# The actual generator uses (src/tools/mmaps_generator/Config.cpp:28):
#
# cellSize = MMAP::GRID_SIZE / vertexPerMapEdge
#
# Where:
# MMAP::GRID_SIZE = 533.3333f (the size of one map tile in world units)
# vertexPerMapEdge = number of vertices along one edge of the full map grid
#
# Example (AC stock):
# vertexPerMapEdge = 2000 → cellSize ≈ 533.3333 / 2000 ≈ 0.2667 yd
#
# IMPORTANT: when changing vertexPerMapEdge, the per-cell parameters
# below (walkableHeight, walkableClimb, walkableRadius) must be re-scaled
# to preserve their world-unit semantics. Doubling vertexPerMapEdge
# halves cellSize, so cell counts must double to keep the same yd value.
#
# To convert a value from cell units to world units (e.g., walkableClimb),
# multiply by cellSize. For example, a walkableClimb of 6 at 2000 resolution:
# 6 × 0.2667 ≈ 1.60 yd
# Minimum ceiling height (in cell units) NPCs need to pass under an obstacle.
# Controls how much vertical clearance is required.
# To convert to world units, multiply by cellSize (see "Cell Size Calculation").
# 6 cells × 0.2667 yd ≈ 1.60 yd — matches WoW player capsule height at
# 2000 resolution (AC stock). Preserves the 1.60 yd world-unit
# ceiling-clearance requirement.
walkableHeight: 6
# Maximum height difference (in cell units) NPCs can step up or down.
# Higher values allow walking over fences, ledges, or steps.
# To convert to world units, multiply by cellSize (see "Cell Size Calculation").
#
# Vanilla WotLK uses 6, which allows creatures to "jump" over fences.
# Classic WotLK uses 4, which forces creatures to walk around fences.
# 6 cells × 0.2667 yd ≈ 1.60 yd — Vanilla-WotLK step semantics at
# 2000 resolution. Preserves the 1.60 yd world-unit step. The mmap
# is shared with every creature, NPC patrol, escort, and quest mob;
# tightening below stock breaks patrols that cross 1.5y ledges.
walkableClimb: 4
# Minimum distance (in cell units) around walkable surfaces.
# Helps prevent NPCs from clipping into walls and narrow gaps.
# To convert to world units, multiply by cellSize (see "Cell Size Calculation").
# 2 cells × 0.2667 yd ≈ 0.53 yd — AC stock world-unit buffer at
# 2000 resolution. Tested wider (= 0.71y world units): erodes polys
# near mountains/cliffs so pathfinder routes through surviving
# (higher/worse) polys → bot climbs mountains.
walkableRadius: 2
# Number of vertices along one edge of the entire map's navmesh grid.
# Higher values increase mesh resolution but also CPU/memory usage.
# 2000 = AC stock baseline. cellSize ≈ 0.2667 yd.
vertexPerMapEdge: 2000
# Number of vertices along one edge of each tile chunk.
# Must divide vertexPerMapEdge evenly — the generator uses integer
# division: tilesPerMapEdge = vertexPerMap / vertexPerTile
# (src/tools/mmaps_generator/Config.cpp:144).
# A higher vertex count per tile means fewer total tiles,
# reducing runtime work to load, unload, and manage tiles.
# 80 = AC stock baseline. 2000 / 80 = 25 tiles per map edge, 625
# tiles per map (~21y per tile). Lots of small tiles, low per-tile
# RAM, more seams to stitch across.
vertexPerTileEdge: 80
# Tolerance for how much a polygon can deviate from the original geometry when simplified.
# Higher values produce simpler (faster) meshes but can reduce accuracy.
# 0.8 (vs the AC stock 1.8 and recast canonical 1.3) keeps polygon
# edges close to real terrain. Targets "merged step into ramp"
# simplification artifacts that produce corner-cuts and false NOPATH.
maxSimplificationError: 0.8
# You can override any global parameter for a specific map by specifying its map ID.
# Inside each map override, you can also override parameters per individual tile,
# identified by a string "tileX,tileY" (coordinates).
#
# Overrides cascade: global settings → map overrides → tile overrides.
# For example:
#
# mapsOverrides:
# "0": # Map ID 0 overrides
# walkableRadius: 5 # Override global climb height for entire map 0
#
# tilesOverrides:
# "50,70": # Tile at coordinates (50,70) on map 0
# walkableSlopeAngle: 70 # Override slope angle locally just here
# walkableClimb: 4 # Also override climb height for this tile only
#
# "51,71":
# walkableClimb: 3 # Override climb height for tile (51,71)
#
# "48,32":
# walkableClimb: 1 # Even smaller climb for tile (48,32)
#
# "1": # Map ID 1 overrides example
# walkableHeight: 8 # Increase clearance for whole map 1
#
# tilesOverrides:
# "100,100":
# maxSimplificationError: 2.5 # Looser mesh simplification for this tile only
#
# "101,101":
# walkableRadius: 1 # Smaller NPC radius here for tight corridors
#
# This approach allows very fine-grained control of navigation mesh parameters
# on a per-map and per-tile basis, optimizing pathfinding quality and performance.
#
# All parameters defined globally are eligible for override.
# Just specify the parameter name and new value in the override section.
mapsOverrides:
"562": # Blade's Edge Arena
walkableRadius: 0 # This allows walking on the ropes to the pillars
"48": # Blackfathom Deeps
cellSizeVertical: 0.5334 # ch*2 = 0.2667 * 2 ≈ 0.5334. Reduce the chance to have underground levels.
"529": # Arathi Basin
tilesOverrides:
"30,29": # Lumber Mill
# Make sure that Fear will not drop players rom cliff -
# https://github.com/azerothcore/azerothcore-wotlk/pull/22462#issuecomment-3067024680
walkableSlopeAngle: 45
"530": # Outland
tilesOverrides:
"32,30": # Dark portal
walkableSlopeAngle: 45 # https://github.com/chromiecraft/chromiecraft/issues/8404#issuecomment-3476012660
# debugOutput generates debug files in the `meshes` directory for use with RecastDemo.
# This is useful for inspecting and debugging mmap generation visually.
#
# My workflow:
# 1. Install RecastDemo. I'm building it from the source of this fork: https://github.com/jackpoz/recastnavigation
# 2. In-game, move your character to the area you want to debug.
# 3. Type `.mmap loc` in chat. This will output:
# - The current tile file name (e.g., `04832.mmtile`)
# - The Recast config values used to generate that tile
# 4. Enable `debugOutput` and regenerate mmaps (preferably just the tile from step 3).
# - To regenerate only one tile, delete it from the `mmaps` folder.
# 5. After generation, you will find debug files in the `meshes` folder, including an OBJ file (e.g., `map0004832.obj`)
# 6. Copy these debug files to the `Meshes` folder used by RecastDemo.
# - RecastDemo expects this folder to be in the same directory as its executable.
# 7. In RecastDemo:
# - Click "Input Mesh" and select the `.obj` file
# - Choose "Solo Mesh" in the Sample selector
# 8. (Optional) Reuse the Recast config values from step 3:
# - `cellSizeHorizontal` → "Cell Size"
# - `walkableSlopeAngle` → "Max Slope"
# - `walkableClimb` → "Max Climb"
# - and so on
# 9. Scroll to the bottom of RecastDemo UI and press "Build" to generate the navigation mesh
debugOutput: false
YAML_EOF
)
# =============================================================================
# ─── DO NOT EDIT BELOW ──────────────────────────────────────────────────────
# =============================================================================
[ -n "$SERVER_DATA_DIR" ] || { echo "SERVER_DATA_DIR is not set"; exit 1; }
[ -n "$WOW_CLIENT_DATA" ] || { echo "WOW_CLIENT_DATA is not set"; exit 1; }
mkdir -p "$SERVER_DATA_DIR"
cd "$SERVER_DATA_DIR"
# ─── SAFETY: source MPQs are READ-ONLY to this script ──────────────────────
# Resolve both paths to canonical form and refuse to run if the output dir
# is inside the source. Combined with safe_rm() below, this script cannot
# touch any file inside WOW_CLIENT_DATA.
SERVER_DATA_DIR_REAL="$(cd "$SERVER_DATA_DIR" && pwd -P)"
WOW_CLIENT_DATA_REAL="$(cd "$WOW_CLIENT_DATA" && pwd -P 2>/dev/null || echo "$WOW_CLIENT_DATA")"
case "$SERVER_DATA_DIR_REAL/" in
"$WOW_CLIENT_DATA_REAL"/|"$WOW_CLIENT_DATA_REAL"/*)
echo "ERROR: SERVER_DATA_DIR ($SERVER_DATA_DIR_REAL) is inside WOW_CLIENT_DATA — refusing." >&2
exit 1
;;
esac
# Refuses to remove anything outside SERVER_DATA_DIR. Resolves the parent
# to absolute path so a symlink inside cwd can't trick us into traversing
# into the source. Use this for every cleanup in this script.
safe_rm() {
local target="$1"
local parent_abs base
parent_abs="$(cd "$(dirname -- "$target")" 2>/dev/null && pwd -P)" || return 0
base="$(basename -- "$target")"
local abs="$parent_abs/$base"
case "$abs/" in
"$SERVER_DATA_DIR_REAL"/|"$SERVER_DATA_DIR_REAL"/*) ;;
*)
echo "REFUSING to rm path outside SERVER_DATA_DIR: $target$abs" >&2
exit 1 ;;
esac
rm -rf -- "$target"
}
[ "$MMAP_THREADS" -eq 0 ] && MMAP_THREADS=$(nproc 2>/dev/null || echo 4)
echo "Working dir : $(pwd)"
echo "Tools dir : $TOOLS_DIR"
echo "Threads : $MMAP_THREADS"
echo "Steps : maps=$EXTRACT_DBC_AND_MAPS vmaps=$EXTRACT_VMAPS mmaps=$EXTRACT_MMAPS"
echo
# ─── Symlink Data/ → MPQ source (only when extracting from client) ──────────
if [ "$EXTRACT_DBC_AND_MAPS" = true ] || [ "$EXTRACT_VMAPS" = true ]; then
has_mpqs() { find "$1" -maxdepth 1 -iname "*.mpq" -print -quit 2>/dev/null | grep -q .; }
if has_mpqs "$WOW_CLIENT_DATA"; then
MPQ_DIR="$WOW_CLIENT_DATA"
elif has_mpqs "$WOW_CLIENT_DATA/Data"; then
MPQ_DIR="$WOW_CLIENT_DATA/Data"
else
echo "ERROR: no .mpq files in $WOW_CLIENT_DATA" >&2
exit 1
fi
MPQ_DIR="$(cd "$MPQ_DIR" && pwd)"
# Symlink only — refuse to clobber an existing real directory.
if [ -e Data ] && [ ! -L Data ]; then
echo "ERROR: Data/ exists in $(pwd) but is not a symlink" >&2
exit 1
fi
ln -sfn "$MPQ_DIR" Data
echo "Data/ → $MPQ_DIR"
fi
# ─── STEP 1: DBCs + Maps ────────────────────────────────────────────────────
if [ "$EXTRACT_DBC_AND_MAPS" = true ]; then
echo
echo "[1/3] Extracting DBCs + Maps..."
# Clean slate — map_extractor refuses to run if these dirs already exist.
safe_rm dbc
safe_rm maps
safe_rm Cameras
# -e 7 = bitfield MAP(1)|DBC(2)|CAMERA(4) — extract everything.
# The old "-e 2" was DBC-only and skipped maps + cameras entirely.
"$TOOLS_DIR/map_extractor" -e 7 -f 0
if [ ! -d maps ] || [ -z "$(ls -A maps 2>/dev/null)" ]; then
echo "ERROR: map_extractor finished but maps/ is empty — check its output above" >&2
exit 1
fi
fi
# ─── STEP 2: VMaps ──────────────────────────────────────────────────────────
if [ "$EXTRACT_VMAPS" = true ]; then
echo
echo "[2/3] Extracting VMaps..."
# Clean slate — vmap4_extractor refuses to run if these dirs already exist.
safe_rm Buildings
safe_rm vmaps
"$TOOLS_DIR/vmap4_extractor" -l -d ./Data
mkdir -p vmaps
"$TOOLS_DIR/vmap4_assembler" Buildings vmaps
safe_rm Buildings
if [ ! -d vmaps ] || [ -z "$(ls -A vmaps 2>/dev/null)" ]; then
echo "ERROR: vmap4_assembler finished but vmaps/ is empty — check output above" >&2
exit 1
fi
fi
# ─── STEP 3: MMaps ──────────────────────────────────────────────────────────
if [ "$EXTRACT_MMAPS" = true ]; then
if [ ! -d maps ]; then
echo "ERROR: maps/ missing in $(pwd) — run with EXTRACT_DBC_AND_MAPS=true once" >&2
exit 1
fi
if [ ! -d vmaps ]; then
echo "ERROR: vmaps/ missing in $(pwd) — run with EXTRACT_VMAPS=true once" >&2
exit 1
fi
echo
echo "[3/3] Generating MMaps... (do not interrupt)"
printf '%s\n' "$MMAPS_CONFIG_YAML" > mmaps-config.yaml
# Wipe any existing tiles before regenerating. Mixed tiles from
# previous runs (different cellSize / verticesPerTileEdge / etc.)
# would otherwise be silently kept and mixed with new ones,
# producing a corrupt navmesh. Clean slate every mmap run.
safe_rm mmaps
mkdir -p mmaps
# Workaround: some mmaps_generator builds write a few tiles to /mmaps
# via an absolute path. Pre-create it so the writes don't fail, then
# fold the strays into our local mmaps/ at the end.
sudo rm -rf /mmaps
sudo mkdir -p /mmaps && sudo chmod 777 /mmaps
CMD=("$TOOLS_DIR/mmaps_generator" --config mmaps-config.yaml --threads "$MMAP_THREADS")
[ -n "$MMAP_SINGLE_MAP" ] && CMD+=("$MMAP_SINGLE_MAP")
START=$(date +%s)
"${CMD[@]}"
ELAPSED=$(( $(date +%s) - START ))
if compgen -G "/mmaps/*.mmtile" >/dev/null; then
cp /mmaps/*.mmtile mmaps/ && rm -f /mmaps/*.mmtile
fi
echo
echo "MMap done in $((ELAPSED / 60))m $((ELAPSED % 60))s"
echo "Tiles: $(ls mmaps/*.mmtile 2>/dev/null | wc -l)"
fi
echo
echo "Done. Restart worldserver to pick up changes."

View File

@ -12,8 +12,8 @@ bool AutoMaintenanceOnLevelupAction::Execute(Event /*event*/)
{ {
AutoPickTalents(); AutoPickTalents();
AutoLearnSpell(); AutoLearnSpell();
AutoUpgradeEquip();
AutoTeleportForLevel(); AutoTeleportForLevel();
AutoUpgradeEquip();
return true; return true;
} }
@ -21,13 +21,11 @@ bool AutoMaintenanceOnLevelupAction::Execute(Event /*event*/)
void AutoMaintenanceOnLevelupAction::AutoTeleportForLevel() void AutoMaintenanceOnLevelupAction::AutoTeleportForLevel()
{ {
if (!sPlayerbotAIConfig.autoTeleportForLevel || !sRandomPlayerbotMgr.IsRandomBot(bot)) if (!sPlayerbotAIConfig.autoTeleportForLevel || !sRandomPlayerbotMgr.IsRandomBot(bot))
{
return; return;
}
if (botAI->HasRealPlayerMaster()) if (botAI->HasRealPlayerMaster())
{
return; return;
}
sRandomPlayerbotMgr.RandomTeleportForLevel(bot); sRandomPlayerbotMgr.RandomTeleportForLevel(bot);
return; return;
} }
@ -89,21 +87,17 @@ void AutoMaintenanceOnLevelupAction::LearnQuestSpells(std::ostringstream* out)
{ {
Quest const* quest = i->second; Quest const* quest = i->second;
// only process class-specific quests to learn class-related spells, cuz if (!quest->GetRequiredClasses() || quest->IsRepeatable() || quest->GetMinLevel() < 10 ||
// we don't want all these bunch of entries to be handled! quest->GetMinLevel() > bot->GetLevel())
if (!quest->GetRequiredClasses()) {
continue; continue;
}
// skip quests that are repeatable, too low level, or above bots' level
if (quest->IsRepeatable() || quest->GetMinLevel() < 10 || quest->GetMinLevel() > bot->GetLevel())
continue;
// skip if bot doesnt satisfy class, race, or skill requirements
if (!bot->SatisfyQuestClass(quest, false) || !bot->SatisfyQuestRace(quest, false) || if (!bot->SatisfyQuestClass(quest, false) || !bot->SatisfyQuestRace(quest, false) ||
!bot->SatisfyQuestSkill(quest, false)) !bot->SatisfyQuestSkill(quest, false))
{
continue; continue;
}
// use the same logic and impl from Player::learnQuestRewardedSpells
int32 spellId = quest->GetRewSpellCast(); int32 spellId = quest->GetRewSpellCast();
if (!spellId) if (!spellId)
@ -113,31 +107,26 @@ void AutoMaintenanceOnLevelupAction::LearnQuestSpells(std::ostringstream* out)
if (!spellInfo) if (!spellInfo)
continue; continue;
// xinef: find effect with learn spell and check if we have this spell
bool found = false; bool found = false;
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
{ {
if (spellInfo->Effects[i].Effect == SPELL_EFFECT_LEARN_SPELL && spellInfo->Effects[i].TriggerSpell && if (spellInfo->Effects[i].Effect == SPELL_EFFECT_LEARN_SPELL && spellInfo->Effects[i].TriggerSpell &&
!bot->HasSpell(spellInfo->Effects[i].TriggerSpell)) !bot->HasSpell(spellInfo->Effects[i].TriggerSpell))
{ {
// pusywizard: don't re-add profession specialties!
if (SpellInfo const* triggeredInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[i].TriggerSpell)) if (SpellInfo const* triggeredInfo = sSpellMgr->GetSpellInfo(spellInfo->Effects[i].TriggerSpell))
if (triggeredInfo->Effects[0].Effect == SPELL_EFFECT_TRADE_SKILL) if (triggeredInfo->Effects[0].Effect == SPELL_EFFECT_TRADE_SKILL)
break; // pussywizard: break and not cast the spell (found is false) break;
found = true; found = true;
break; break;
} }
} }
// xinef: we know the spell, continue
if (!found) if (!found)
continue; continue;
bot->CastSpell(bot, spellId, true); bot->CastSpell(bot, spellId, true);
// Check if RewardDisplaySpell is set to output the proper spell learned
// after processing quests. Output the original RewardSpell otherwise.
uint32 rewSpellId = quest->GetRewSpell(); uint32 rewSpellId = quest->GetRewSpell();
if (rewSpellId) if (rewSpellId)
{ {
@ -167,12 +156,11 @@ std::string const AutoMaintenanceOnLevelupAction::FormatSpell(SpellInfo const* s
void AutoMaintenanceOnLevelupAction::AutoUpgradeEquip() void AutoMaintenanceOnLevelupAction::AutoUpgradeEquip()
{ {
if (!sPlayerbotAIConfig.autoUpgradeEquip || !sRandomPlayerbotMgr.IsRandomBot(bot)) if (!sRandomPlayerbotMgr.IsRandomBot(bot))
return; return;
PlayerbotFactory factory(bot, bot->GetLevel()); PlayerbotFactory factory(bot, bot->GetLevel());
// Clean up old consumables before adding new ones
factory.CleanupConsumables(); factory.CleanupConsumables();
factory.InitAmmo(); factory.InitAmmo();
@ -181,9 +169,6 @@ void AutoMaintenanceOnLevelupAction::AutoUpgradeEquip()
factory.InitConsumables(); factory.InitConsumables();
factory.InitPotions(); factory.InitPotions();
if (!sPlayerbotAIConfig.equipmentPersistence || bot->GetLevel() < sPlayerbotAIConfig.equipmentPersistenceLevel) if (sPlayerbotAIConfig.autoUpgradeEquip)
{
if (sPlayerbotAIConfig.incrementalGearInit)
factory.InitEquipment(true); factory.InitEquipment(true);
}
} }

View File

@ -173,7 +173,7 @@ std::vector<uint32> const vFlagsIC = {GO_HORDE_BANNER,
GO_HORDE_BANNER_GRAVEYARD_H, GO_HORDE_BANNER_GRAVEYARD_H,
GO_HORDE_BANNER_GRAVEYARD_H_CONT}; GO_HORDE_BANNER_GRAVEYARD_H_CONT};
// BG Waypoints (vmangos) // BG Waypoints
// Horde Flag Room to Horde Graveyard // Horde Flag Room to Horde Graveyard
BattleBotPath vPath_WSG_HordeFlagRoom_to_HordeGraveyard = { BattleBotPath vPath_WSG_HordeFlagRoom_to_HordeGraveyard = {
@ -4292,6 +4292,8 @@ bool ArenaTactics::Execute(Event /*event*/)
if (losBlocked) if (losBlocked)
{ {
PathGenerator path(bot); PathGenerator path(bot);
path.SetNavTerrainCost(NAV_GROUND_STEEP, 5.0f);
path.SetNavTerrainCost(NAV_WATER, 10.0f);
path.CalculatePath(target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(), false); path.CalculatePath(target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(), false);
if (path.GetPathType() != PATHFIND_NOPATH) if (path.GetPathType() != PATHFIND_NOPATH)

View File

@ -6,10 +6,10 @@
#include "CheckValuesAction.h" #include "CheckValuesAction.h"
#include "Event.h" #include "Event.h"
#include "ObjectGuid.h"
#include "ServerFacade.h" #include "ServerFacade.h"
#include "PlayerbotAI.h" #include "PlayerbotAI.h"
#include "TravelNode.h"
#include "AiObjectContext.h" #include "AiObjectContext.h"
CheckValuesAction::CheckValuesAction(PlayerbotAI* botAI) : Action(botAI, "check values") {} CheckValuesAction::CheckValuesAction(PlayerbotAI* botAI) : Action(botAI, "check values") {}
@ -21,11 +21,6 @@ bool CheckValuesAction::Execute(Event /*event*/)
botAI->Ping(bot->GetPositionX(), bot->GetPositionY()); botAI->Ping(bot->GetPositionX(), bot->GetPositionY());
} }
if (botAI->HasStrategy("map", BOT_STATE_NON_COMBAT) || botAI->HasStrategy("map full", BOT_STATE_NON_COMBAT))
{
TravelNodeMap::instance().manageNodes(bot, botAI->HasStrategy("map full", BOT_STATE_NON_COMBAT));
}
GuidVector possible_targets = *context->GetValue<GuidVector>("possible targets"); GuidVector possible_targets = *context->GetValue<GuidVector>("possible targets");
GuidVector all_targets = *context->GetValue<GuidVector>("all targets"); GuidVector all_targets = *context->GetValue<GuidVector>("all targets");
GuidVector npcs = *context->GetValue<GuidVector>("nearest npcs"); GuidVector npcs = *context->GetValue<GuidVector>("nearest npcs");

View File

@ -76,7 +76,7 @@ bool DebugAction::Execute(Event event)
return false; return false;
std::vector<WorldPosition> beginPath, endPath; std::vector<WorldPosition> beginPath, endPath;
TravelNodeRoute route = TravelNodeMap::instance().getRoute(botPos, *points.front(), beginPath, bot); TravelNodeRoute route = TravelNodeMap::instance().FindRouteNearestNodes(botPos, *points.front(), beginPath, bot);
std::ostringstream out; std::ostringstream out;
out << "Traveling to " << dest->getTitle() << ": "; out << "Traveling to " << dest->getTitle() << ": ";
@ -196,18 +196,18 @@ bool DebugAction::Execute(Event event)
{ {
WorldPosition pos(bot); WorldPosition pos(bot);
std::string const name = "USER:" + text.substr(9); std::string suffix = text.size() > 9 ? text.substr(9) : pos.getAreaName();
std::string const name = "USER:" + suffix;
/* TravelNode* startNode = */ TravelNodeMap::instance().addNode(pos, name, false, false); // startNode not used, but addNode as side effect, fragment marked for removal. {
std::lock_guard<std::shared_timed_mutex> lock(TravelNodeMap::instance().m_nMapMtx);
TravelNodeMap::instance().addNode(pos, name, false, true);
for (auto& endNode : TravelNodeMap::instance().getNodes(pos, 2000)) for (auto& endNode : TravelNodeMap::instance().getNodes(pos, 2000))
{
endNode->setLinked(false); endNode->setLinked(false);
} }
botAI->TellMasterNoFacing("Node " + name + " created."); botAI->TellMasterNoFacing("Node " + name + " created. Use console command '.playerbots travel generatenode' to connect nodes.");
TravelNodeMap::instance().setHasToGen();
return true; return true;
} }
@ -223,14 +223,15 @@ bool DebugAction::Execute(Event event)
if (startNode->isImportant()) if (startNode->isImportant())
{ {
botAI->TellMasterNoFacing("Node can not be removed."); botAI->TellMasterNoFacing("Node can not be removed.");
return true;
} }
TravelNodeMap::instance().m_nMapMtx.lock(); {
std::lock_guard<std::shared_timed_mutex> lock(TravelNodeMap::instance().m_nMapMtx);
TravelNodeMap::instance().removeNode(startNode); TravelNodeMap::instance().removeNode(startNode);
botAI->TellMasterNoFacing("Node removed."); }
TravelNodeMap::instance().m_nMapMtx.unlock();
TravelNodeMap::instance().setHasToGen(); botAI->TellMasterNoFacing("Node removed. Use console command '.playerbots travel generatenode' to finalize nodes.");
return true; return true;
} }
@ -247,15 +248,17 @@ bool DebugAction::Execute(Event event)
node->removeLinkTo(path.first, true); node->removeLinkTo(path.first, true);
return true; return true;
} }
else if (text.find("gen node") != std::string::npos) else if (text.find("gen node") != std::string::npos ||
text.find("gen path") != std::string::npos)
{ {
// Pathfinder // Disabled: generateAll() touches Map / grid / mmap state that is only
TravelNodeMap::instance().generateNodes(); // safe to mutate on the world thread. Running it from a detached worker
return true; // (or from a bot tick on a MapUpdater thread) races with world updates
} // and freezes the server. Use the console command instead, which runs
else if (text.find("gen path") != std::string::npos) // synchronously on the world thread:
{ // .playerbots travel generatenode
TravelNodeMap::instance().generatePaths(); botAI->TellMasterNoFacing(
"Disabled in chat. Run '.playerbots travel generatenode' from the server console.");
return true; return true;
} }
else if (text.find("crop path") != std::string::npos) else if (text.find("crop path") != std::string::npos)
@ -275,7 +278,7 @@ bool DebugAction::Execute(Event event)
[] []
{ {
TravelNodeMap::instance().removeNodes(); TravelNodeMap::instance().removeNodes();
TravelNodeMap::instance().loadNodeStore(); TravelNodeMap::instance().LoadNodeStore();
}); });
t.detach(); t.detach();
@ -297,7 +300,7 @@ bool DebugAction::Execute(Event event)
// uint32 time = 60 * IN_MILLISECONDS; //not used, line marked for removal. // uint32 time = 60 * IN_MILLISECONDS; //not used, line marked for removal.
std::vector<WorldPosition> ppath = l.second->getPath(); std::vector<WorldPosition> ppath = l.second->GetPath();
for (auto p : ppath) for (auto p : ppath)
{ {

View File

@ -29,6 +29,10 @@ void DestroyItemAction::DestroyItem(FindItemVisitor* visitor)
std::vector<Item*> items = visitor->GetResult(); std::vector<Item*> items = visitor->GetResult();
for (Item* item : items) for (Item* item : items)
{ {
// backstop: never drop an active quest item
if (bot->HasQuestForItem(item->GetTemplate()->ItemId))
continue;
std::ostringstream out; std::ostringstream out;
out << chat->FormatItem(item->GetTemplate()) << " destroyed"; out << chat->FormatItem(item->GetTemplate()) << " destroyed";
botAI->TellMaster(out); botAI->TellMaster(out);
@ -67,18 +71,11 @@ bool SmartDestroyItemAction::Execute(Event /*event*/)
return true; return true;
} }
// ITEM_USAGE_QUEST is excluded — those are still-needed quest items
std::vector<uint32> bestToDestroy = {ITEM_USAGE_NONE}; // First destroy anything useless. std::vector<uint32> bestToDestroy = {ITEM_USAGE_NONE}; // First destroy anything useless.
if (!AI_VALUE(bool, "can sell") &&
AI_VALUE(
bool,
"should get money")) // We need money so quest items are less important since they can't directly be sold.
bestToDestroy.push_back(ITEM_USAGE_QUEST);
else // We don't need money so destroy the cheapest stuff.
{
bestToDestroy.push_back(ITEM_USAGE_VENDOR); bestToDestroy.push_back(ITEM_USAGE_VENDOR);
bestToDestroy.push_back(ITEM_USAGE_AH); bestToDestroy.push_back(ITEM_USAGE_AH);
}
// If we still need room // If we still need room
bestToDestroy.push_back( bestToDestroy.push_back(

View File

@ -154,9 +154,11 @@ void EquipAction::EquipItem(Item* item)
calculator.SetOverflowPenalty(false); calculator.SetOverflowPenalty(false);
// Calculate item scores once and store them // Calculate item scores once and store them
float newItemScore = calculator.CalculateItem(itemId); float newItemScore = calculator.CalculateItem(itemId, item->GetItemRandomPropertyId());
float mainHandScore = mainHandItem ? calculator.CalculateItem(mainHandItem->GetTemplate()->ItemId) : 0.0f; float mainHandScore = mainHandItem
float offHandScore = offHandItem ? calculator.CalculateItem(offHandItem->GetTemplate()->ItemId) : 0.0f; ? calculator.CalculateItem(mainHandItem->GetTemplate()->ItemId, mainHandItem->GetItemRandomPropertyId()) : 0.0f;
float offHandScore = offHandItem
? calculator.CalculateItem(offHandItem->GetTemplate()->ItemId, offHandItem->GetItemRandomPropertyId()) : 0.0f;
// Determine where this weapon can go // Determine where this weapon can go
bool canGoMain = (invType == INVTYPE_WEAPON || bool canGoMain = (invType == INVTYPE_WEAPON ||

View File

@ -19,83 +19,8 @@
#include "Transport.h" #include "Transport.h"
#include "Map.h" #include "Map.h"
namespace // Transport helpers (GetTransportForPosTolerant, FindBoardingPointOnTransport,
{ // BoardTransport) are now on MovementAction — inherited by FollowAction.
Transport* GetTransportForPosTolerant(Map* map, WorldObject* ref, uint32 phaseMask, float x, float y, float z)
{
if (!map || !ref)
return nullptr;
std::array<float, 4> const probes = { z, z + 0.5f, z + 1.5f, z - 0.5f };
for (float const pz : probes)
{
if (Transport* t = map->GetTransportForPos(phaseMask, x, y, pz, ref))
return t;
}
return nullptr;
}
// Attempts to find a point on the leader's transport that is closer to the bot,
// by probing along the segment from master -> bot and returning the last point
// that is still detected as being on the expected transport.
bool FindBoardingPointOnTransport(Map* map, Transport* expectedTransport, WorldObject* ref,
float masterX, float masterY, float masterZ,
float botX, float botY, float botZ,
float& outX, float& outY, float& outZ)
{
if (!map || !expectedTransport || !ref)
return false;
uint32 const phaseMask = ref->GetPhaseMask();
// Ensure master is actually detected on that transport (tolerant).
if (GetTransportForPosTolerant(map, ref, phaseMask, masterX, masterY, masterZ) != expectedTransport)
return false;
// The raycast in GetTransportForPos starts at (z + 2). Probe with a safe Z.
float const probeZ = std::max(masterZ, botZ);
// Adaptive step count: small platforms need tighter sampling.
float const dx2 = botX - masterX;
float const dy2 = botY - masterY;
float const dist2d = std::sqrt(dx2 * dx2 + dy2 * dy2);
int32 const steps = std::clamp(static_cast<int32>(dist2d / 0.75f), 10, 28);
float const dx = (botX - masterX) / static_cast<float>(steps);
float const dy = (botY - masterY) / static_cast<float>(steps);
// Master must actually be on the expected transport for this to work.
if (map->GetTransportForPos(ref->GetPhaseMask(), masterX, masterY, probeZ, ref) != expectedTransport)
return false;
float lastX = masterX;
float lastY = masterY;
bool found = false;
for (int32 i = 1; i <= steps; ++i)
{
float const px = masterX + dx * i;
float const py = masterY + dy * i;
Transport* const t = GetTransportForPosTolerant(map, ref, phaseMask, px, py, probeZ);
if (t != expectedTransport)
break;
lastX = px;
lastY = py;
found = true;
}
if (!found)
return false;
outX = lastX;
outY = lastY;
outZ = masterZ; // keep deck-level Z to encourage stepping onto the platform/boat
return true;
}
}
bool FollowAction::Execute(Event /*event*/) bool FollowAction::Execute(Event /*event*/)
{ {

View File

@ -86,7 +86,7 @@ bool TogglePetSpellAutoCastAction::Execute(Event /*event*/)
uint32 spellId = itr->first; uint32 spellId = itr->first;
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId); const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo->IsAutocastable()) if (!spellInfo || !spellInfo->IsAutocastable())
continue; continue;
bool shouldApply = true; bool shouldApply = true;

View File

@ -6,6 +6,7 @@
#include "GenericSpellActions.h" #include "GenericSpellActions.h"
#include <ctime> #include <ctime>
#include <unordered_set>
#include "Event.h" #include "Event.h"
#include "ItemTemplate.h" #include "ItemTemplate.h"
@ -23,6 +24,116 @@
using ai::buff::MakeAuraQualifierForBuff; using ai::buff::MakeAuraQualifierForBuff;
using ai::spell::HasSpellOrCategoryCooldown; using ai::spell::HasSpellOrCategoryCooldown;
namespace
{
std::unordered_set<uint32> const& GetMixedTriggerTrinketSpellIds()
{
static std::unordered_set<uint32> const mixedTriggerSpellIds = []()
{
std::unordered_set<uint32> onUseSpellIds;
std::unordered_set<uint32> onEquipSpellIds;
std::unordered_set<uint32> mixedSpellIds;
auto const* itemTemplates = sObjectMgr->GetItemTemplateStore();
if (!itemTemplates)
return mixedSpellIds;
auto const markSpellId = [&](int32 spellId, uint8 spellTrigger)
{
if (spellId <= 0)
return;
if (spellTrigger == ITEM_SPELLTRIGGER_ON_USE)
{
if (onEquipSpellIds.find(spellId) != onEquipSpellIds.end())
mixedSpellIds.insert(spellId);
onUseSpellIds.insert(spellId);
}
else if (spellTrigger == ITEM_SPELLTRIGGER_ON_EQUIP)
{
if (onUseSpellIds.find(spellId) != onUseSpellIds.end())
mixedSpellIds.insert(spellId);
onEquipSpellIds.insert(spellId);
}
};
for (auto const& itr : *itemTemplates)
{
ItemTemplate const& proto = itr.second;
if (proto.InventoryType != INVTYPE_TRINKET)
continue;
for (uint8 spellIndex = 0; spellIndex < MAX_ITEM_PROTO_SPELLS; ++spellIndex)
{
auto const& spellData = proto.Spells[spellIndex];
markSpellId(spellData.SpellId, spellData.SpellTrigger);
}
}
return mixedSpellIds;
}();
return mixedTriggerSpellIds;
}
bool IsManaRestoreEffect(SpellEffectInfo const& effectInfo)
{
return (effectInfo.Effect == SPELL_EFFECT_ENERGIZE &&
effectInfo.MiscValue == POWER_MANA) ||
(effectInfo.Effect == SPELL_EFFECT_APPLY_AURA &&
effectInfo.ApplyAuraName == SPELL_AURA_PERIODIC_ENERGIZE &&
effectInfo.MiscValue == POWER_MANA);
}
bool IsManaEfficiencyEffect(SpellEffectInfo const& effectInfo)
{
return effectInfo.Effect == SPELL_EFFECT_APPLY_AURA &&
(((effectInfo.ApplyAuraName == SPELL_AURA_MOD_POWER_REGEN ||
effectInfo.ApplyAuraName == SPELL_AURA_MOD_POWER_REGEN_PERCENT) &&
effectInfo.MiscValue == POWER_MANA) ||
effectInfo.ApplyAuraName == SPELL_AURA_MOD_POWER_COST_SCHOOL ||
effectInfo.ApplyAuraName == SPELL_AURA_MOD_POWER_COST_SCHOOL_PCT ||
effectInfo.ApplyAuraName == SPELL_AURA_MOD_MANA_REGEN_INTERRUPT);
}
bool IsDefensiveTankEffect(SpellEffectInfo const& effectInfo)
{
if (effectInfo.Effect != SPELL_EFFECT_APPLY_AURA)
return false;
uint32 const tankRatingsMask =
(1u << CR_DEFENSE_SKILL) |
(1u << CR_DODGE) |
(1u << CR_PARRY) |
(1u << CR_BLOCK) |
(1u << CR_HIT_TAKEN_MELEE) |
(1u << CR_HIT_TAKEN_RANGED) |
(1u << CR_HIT_TAKEN_SPELL) |
(1u << CR_CRIT_TAKEN_MELEE) |
(1u << CR_CRIT_TAKEN_RANGED) |
(1u << CR_CRIT_TAKEN_SPELL);
switch (effectInfo.ApplyAuraName)
{
case SPELL_AURA_MOD_RESISTANCE:
return (effectInfo.MiscValue & SPELL_SCHOOL_MASK_NORMAL) != 0;
case SPELL_AURA_MOD_RATING:
return (effectInfo.MiscValue & tankRatingsMask) != 0;
case SPELL_AURA_MOD_INCREASE_HEALTH:
case SPELL_AURA_MOD_INCREASE_HEALTH_PERCENT:
case SPELL_AURA_MOD_PARRY_PERCENT:
case SPELL_AURA_MOD_DODGE_PERCENT:
case SPELL_AURA_MOD_BLOCK_PERCENT:
case SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN:
return true;
default:
return false;
}
}
}
CastSpellAction::CastSpellAction(PlayerbotAI* botAI, std::string const spell) CastSpellAction::CastSpellAction(PlayerbotAI* botAI, std::string const spell)
: Action(botAI, spell), range(botAI->GetRange("spell")), spell(spell) {} : Action(botAI, spell), range(botAI->GetRange("spell")), spell(spell) {}
@ -429,52 +540,109 @@ bool UseTrinketAction::UseTrinket(Item* item)
uint8 bagIndex = item->GetBagSlot(); uint8 bagIndex = item->GetBagSlot();
uint8 slot = item->GetSlot(); uint8 slot = item->GetSlot();
// uint8 spell_index = 0; //not used, line marked for removal.
uint8 cast_count = 1; uint8 cast_count = 1;
ObjectGuid item_guid = item->GetGUID(); ObjectGuid item_guid = item->GetGUID();
uint32 glyphIndex = 0; uint32 glyphIndex = 0;
uint8 castFlags = 0; uint8 castFlags = 0;
uint32 targetFlag = TARGET_FLAG_NONE; uint32 targetFlag = TARGET_FLAG_NONE;
uint32 spellId = 0; uint32 spellId = 0;
int32 itemSpellCooldown = 0;
uint32 itemSpellCategory = 0;
int32 itemSpellCategoryCooldown = 0;
for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
{ {
if (item->GetTemplate()->Spells[i].SpellId > 0 && if (item->GetTemplate()->Spells[i].SpellId > 0 &&
item->GetTemplate()->Spells[i].SpellTrigger == ITEM_SPELLTRIGGER_ON_USE) item->GetTemplate()->Spells[i].SpellTrigger == ITEM_SPELLTRIGGER_ON_USE)
{ {
spellId = item->GetTemplate()->Spells[i].SpellId; spellId = item->GetTemplate()->Spells[i].SpellId;
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId); itemSpellCooldown = item->GetTemplate()->Spells[i].SpellCooldown;
itemSpellCategory = item->GetTemplate()->Spells[i].SpellCategory;
itemSpellCategoryCooldown = item->GetTemplate()->Spells[i].SpellCategoryCooldown;
uint64 const itemCooldownKey = (static_cast<uint64>(item->GetEntry()) << 32) | spellId;
uint32 const now = getMSTime();
if (itemSpellCooldown > 0)
{
auto const itemCooldownItr = trinketItemCooldownExpiries.find(itemCooldownKey);
if (itemCooldownItr != trinketItemCooldownExpiries.end())
{
if (itemCooldownItr->second > now)
return false;
trinketItemCooldownExpiries.erase(itemCooldownItr);
}
}
if (itemSpellCategory && itemSpellCategoryCooldown > 0)
{
auto const categoryCooldownItr = trinketCategoryCooldownExpiries.find(itemSpellCategory);
if (categoryCooldownItr != trinketCategoryCooldownExpiries.end())
{
if (categoryCooldownItr->second > now)
return false;
trinketCategoryCooldownExpiries.erase(categoryCooldownItr);
}
}
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);
if (!spellInfo || !spellInfo->IsPositive()) if (!spellInfo || !spellInfo->IsPositive())
return false; return false;
bool applyAura = false; bool applyAura = false;
bool restoresMana = false;
bool improvesManaEfficiency = false;
bool defensiveTankEffect = false;
for (int i = 0; i < MAX_SPELL_EFFECTS; i++) for (int i = 0; i < MAX_SPELL_EFFECTS; i++)
{ {
const SpellEffectInfo& effectInfo = spellInfo->Effects[i]; const SpellEffectInfo& effectInfo = spellInfo->Effects[i];
if (effectInfo.Effect == SPELL_EFFECT_APPLY_AURA) if (effectInfo.Effect == SPELL_EFFECT_APPLY_AURA)
{
applyAura = true; applyAura = true;
restoresMana = restoresMana || IsManaRestoreEffect(effectInfo);
improvesManaEfficiency = improvesManaEfficiency || IsManaEfficiencyEffect(effectInfo);
defensiveTankEffect = defensiveTankEffect || IsDefensiveTankEffect(effectInfo);
}
if (!applyAura && !restoresMana)
return false;
if (restoresMana || improvesManaEfficiency)
{
if (!AI_VALUE2(bool, "has mana", "self target"))
return false;
uint8 const manaPct = AI_VALUE2(uint8, "mana", "self target");
if ((restoresMana && manaPct >= sPlayerbotAIConfig.mediumMana) ||
manaPct >= sPlayerbotAIConfig.highMana)
{
return false;
}
}
if (defensiveTankEffect)
{
uint8 const healthPct = AI_VALUE2(uint8, "health", "self target");
if (healthPct > sPlayerbotAIConfig.lowHealth)
return false;
}
auto const& mixedTriggerTrinketSpellIds = GetMixedTriggerTrinketSpellIds();
// Exclude trinkets that expose the same spell as both ON_EQUIP and ON_USE across
// item templates. Those are equip/proc effects leaking into the active-use path,
// as seen with the error versions of Oracle Talisman of Ablution (44870) and
// Frenzyheart Insignia of Fury (44869).
if (mixedTriggerTrinketSpellIds.find(spellId) != mixedTriggerTrinketSpellIds.end())
return false;
if (!botAI->CanCastSpell(spellId, bot, false, nullptr, item))
return false;
break; break;
} }
} }
if (!applyAura)
return false;
uint32 spellProcFlag = spellInfo->ProcFlags;
// Handle items with procflag "if you kill a target that grants honor or experience"
// Bots will "learn" the trinket proc, so CanCastSpell() will be true
// e.g. on Item https://www.wowhead.com/wotlk/item=44074/oracle-talisman-of-ablution leading to
// constant casting of the proc spell onto themselfes https://www.wowhead.com/wotlk/spell=59787/oracle-ablutions
// This will lead to multiple hundreds of entries in m_appliedAuras -> Once killing an enemy -> Big diff time spikes
if (spellProcFlag != 0) return false;
if (!botAI->CanCastSpell(spellId, bot, false))
return false;
break;
}
}
if (!spellId) if (!spellId)
return false; return false;
@ -483,7 +651,25 @@ bool UseTrinketAction::UseTrinket(Item* item)
targetFlag = TARGET_FLAG_NONE; targetFlag = TARGET_FLAG_NONE;
packet << targetFlag << bot->GetPackGUID(); packet << targetFlag << bot->GetPackGUID();
bot->GetSession()->HandleUseItemOpcode(packet); bot->GetSession()->HandleUseItemOpcode(packet);
uint32 const now = getMSTime();
uint32 const cooldownDelay = bot->GetSpellCooldownDelay(spellId);
if (cooldownDelay > 0)
{
if (itemSpellCooldown > 0)
{
uint64 const itemCooldownKey = (static_cast<uint64>(item->GetEntry()) << 32) | spellId;
trinketItemCooldownExpiries[itemCooldownKey] = now + static_cast<uint32>(itemSpellCooldown);
}
if (itemSpellCategory && itemSpellCategoryCooldown > 0)
{
trinketCategoryCooldownExpiries[itemSpellCategory] = now + static_cast<uint32>(itemSpellCategoryCooldown);
}
}
return true; return true;
} }

View File

@ -334,6 +334,10 @@ public:
protected: protected:
bool UseTrinket(Item* trinket); bool UseTrinket(Item* trinket);
private:
std::unordered_map<uint64, uint32> trinketItemCooldownExpiries;
std::unordered_map<uint32, uint32> trinketCategoryCooldownExpiries;
}; };
class CastSpellOnEnemyHealerAction : public CastSpellAction class CastSpellOnEnemyHealerAction : public CastSpellAction

View File

@ -124,6 +124,8 @@ bool GoAction::Execute(Event event)
if (botAI->HasStrategy("debug move", BOT_STATE_NON_COMBAT)) if (botAI->HasStrategy("debug move", BOT_STATE_NON_COMBAT))
{ {
PathGenerator path(bot); PathGenerator path(bot);
path.SetNavTerrainCost(NAV_GROUND_STEEP, 5.0f);
path.SetNavTerrainCost(NAV_WATER, 10.0f);
path.CalculatePath(x, y, z, false); path.CalculatePath(x, y, z, false);

View File

@ -5,6 +5,9 @@
#include "LootAction.h" #include "LootAction.h"
#include <limits>
#include "Bag.h"
#include "ChatHelper.h" #include "ChatHelper.h"
#include "Event.h" #include "Event.h"
#include "GuildMgr.h" #include "GuildMgr.h"
@ -76,7 +79,11 @@ bool OpenLootAction::Execute(Event /*event*/)
bool result = DoLoot(lootObject); bool result = DoLoot(lootObject);
if (result) if (result)
{ {
AI_VALUE(LootObjectStack*, "available loot")->Remove(lootObject.guid); // MarkCompleted (not Remove) — "add all loot" reads
// "nearest corpses" without a lootable filter, so a plain
// Remove lets the same corpse re-enter the stack on the next
// tick. The completed set blocks re-add for ~5 min.
AI_VALUE(LootObjectStack*, "available loot")->MarkCompleted(lootObject.guid);
context->GetValue<LootObject>("loot target")->Set(LootObject()); context->GetValue<LootObject>("loot target")->Set(LootObject());
} }
return result; return result;
@ -139,8 +146,9 @@ bool OpenLootAction::DoLoot(LootObject& lootObject)
if (go && (go->GetGoState() != GO_STATE_READY)) if (go && (go->GetGoState() != GO_STATE_READY))
return false; return false;
// This prevents dungeon chests like Tribunal Chest (Halls of Stone) from being ninja'd by the bots // Block event-gated chests (Tribunal Chest, Gunship Armory) but allow
if (go && go->HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_INTERACT_COND)) // wild quest GOs (Moonpetal Lily etc.) when the bot is on the quest.
if (go && go->HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_INTERACT_COND) && !lootObject.isNeededQuestItem)
return false; return false;
// This prevents raid chests like Gunship Armory (ICC) from being ninja'd by the bots // This prevents raid chests like Gunship Armory (ICC) from being ninja'd by the bots
@ -377,6 +385,12 @@ bool StoreLootAction::Execute(Event event)
// bot->GetSession()->HandleLootMoneyOpcode(packet); // bot->GetSession()->HandleLootMoneyOpcode(packet);
} }
// one make-room destroy per loot packet — CanStoreNewItem after a junk
// destroy can still report full while CMSG_AUTOSTORE_LOOT_ITEM is
// queued, so a multi-quest-item packet would otherwise destroy more
// junk than necessary
bool destroyedThisPacket = false;
for (uint8 i = 0; i < items; ++i) for (uint8 i = 0; i < items; ++i)
{ {
uint32 itemid; uint32 itemid;
@ -402,7 +416,9 @@ bool StoreLootAction::Execute(Event event)
if (!proto) if (!proto)
continue; continue;
if (!botAI->HasActivePlayerMaster() && AI_VALUE(uint8, "bag space") > 80) // bags >80%: skip non-stackable junk (quest items exempt)
if (!botAI->HasActivePlayerMaster() && AI_VALUE(uint8, "bag space") > 80 &&
!bot->HasQuestForItem(itemid))
{ {
uint32 maxStack = proto->GetMaxStackSize(); uint32 maxStack = proto->GetMaxStackSize();
if (maxStack == 1) if (maxStack == 1)
@ -438,6 +454,55 @@ bool StoreLootAction::Execute(Event event)
GuildTaskMgr::instance().CheckItemTask(itemid, itemcount, ref->GetSource(), bot); GuildTaskMgr::instance().CheckItemTask(itemid, itemcount, ref->GetSource(), bot);
} }
// bags full + quest item: make room by dropping cheapest junk
if (!destroyedThisPacket && bot->HasQuestForItem(itemid))
{
ItemPosCountVec dest;
InventoryResult can =
bot->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, itemid, itemcount);
if (can == EQUIP_ERR_INVENTORY_FULL || can == EQUIP_ERR_BAG_FULL)
{
// picked by usage, not quality — high-level bots have no grays
Item* victim = nullptr;
uint32 minPrice = std::numeric_limits<uint32>::max();
auto consider = [&](uint8 bag, uint8 slot)
{
Item* it = bot->GetItemByPos(bag, slot);
if (!it)
return;
ItemTemplate const* tpl = it->GetTemplate();
if (!tpl)
return;
if (bot->HasQuestForItem(tpl->ItemId))
return;
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", tpl->ItemId);
if (usage != ITEM_USAGE_NONE && usage != ITEM_USAGE_VENDOR &&
usage != ITEM_USAGE_BAD_EQUIP && usage != ITEM_USAGE_BROKEN_EQUIP)
return;
if (tpl->SellPrice < minPrice)
{
minPrice = tpl->SellPrice;
victim = it;
}
};
for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; ++slot)
consider(INVENTORY_SLOT_BAG_0, slot);
for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag)
{
Bag* pBag = bot->GetBagByPos(bag);
if (!pBag)
continue;
for (uint32 slot = 0; slot < pBag->GetBagSize(); ++slot)
consider(bag, static_cast<uint8>(slot));
}
if (victim)
{
bot->DestroyItem(victim->GetBagSlot(), victim->GetSlot(), true);
destroyedThisPacket = true;
}
}
}
WorldPacket* packet = new WorldPacket(CMSG_AUTOSTORE_LOOT_ITEM, 1); WorldPacket* packet = new WorldPacket(CMSG_AUTOSTORE_LOOT_ITEM, 1);
*packet << itemindex; *packet << itemindex;
bot->GetSession()->QueuePacket(packet); bot->GetSession()->QueuePacket(packet);
@ -453,7 +518,7 @@ bool StoreLootAction::Execute(Event event)
BroadcastHelper::BroadcastLootingItem(botAI, bot, proto); BroadcastHelper::BroadcastLootingItem(botAI, bot, proto);
} }
AI_VALUE(LootObjectStack*, "available loot")->Remove(guid); AI_VALUE(LootObjectStack*, "available loot")->MarkCompleted(guid);
// release loot // release loot
WorldPacket* packet = new WorldPacket(CMSG_LOOT_RELEASE, 8); WorldPacket* packet = new WorldPacket(CMSG_LOOT_RELEASE, 8);

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,7 @@
#include "Action.h" #include "Action.h"
#include "LastMovementValue.h" #include "LastMovementValue.h"
#include "PathGenerator.h"
#include "PlayerbotAIConfig.h" #include "PlayerbotAIConfig.h"
class Player; class Player;
@ -22,12 +23,33 @@ class Position;
#define ANGLE_90_DEG M_PI_2 #define ANGLE_90_DEG M_PI_2
#define ANGLE_120_DEG (2.f * static_cast<float>(M_PI) / 3.f) #define ANGLE_120_DEG (2.f * static_cast<float>(M_PI) / 3.f)
// Default acceptable path types for GeneratePath
constexpr uint32 DEFAULT_PATH_ACCEPT_MASK = PATHFIND_NORMAL | PATHFIND_INCOMPLETE;
constexpr uint32 RELAXED_PATH_ACCEPT_MASK = PATHFIND_NORMAL | PATHFIND_INCOMPLETE | PATHFIND_FARFROMPOLY;
struct PathResult
{
Movement::PointsArray points;
G3D::Vector3 actualEnd;
G3D::Vector3 end;
PathType pathType;
bool reachable;
};
class MovementAction : public Action class MovementAction : public Action
{ {
public: public:
MovementAction(PlayerbotAI* botAI, std::string const name); MovementAction(PlayerbotAI* botAI, std::string const name);
protected: protected:
// Emit a one-line trace describing the imminent movement. No-op
// unless the bot has the "debug move" non-combat strategy.
// Subclasses (e.g. NewRpgBaseAction) may override to append richer
// context such as RPG status and target name. Optional `extra`
// is appended verbatim (use it to attach hop labels like
// "node:Stormwind innkeeper" or fallback reasons).
virtual void EmitDebugMove(char const* method, char const* generator, float x, float y, float z, char const* extra = nullptr);
bool JumpTo(uint32 mapId, float x, float y, float z, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); bool JumpTo(uint32 mapId, float x, float y, float z, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
bool MoveNear(uint32 mapId, float x, float y, float z, float distance = sPlayerbotAIConfig.contactDistance, bool MoveNear(uint32 mapId, float x, float y, float z, float distance = sPlayerbotAIConfig.contactDistance,
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
@ -66,6 +88,31 @@ protected:
bool FleePosition(Position pos, float radius, uint32 minInterval = 1000); bool FleePosition(Position pos, float radius, uint32 minInterval = 1000);
bool CheckLastFlee(float curAngle, std::list<FleeInfo>& infoList); bool CheckLastFlee(float curAngle, std::list<FleeInfo>& infoList);
PathResult GeneratePath(float x, float y, float z, uint32 acceptMask = DEFAULT_PATH_ACCEPT_MASK, bool forceDestination = false);
bool GetTravelPlan(TravelPlan& plan, WorldPosition destination);
bool ExecuteTravelPlan(TravelPlan& state);
// Transport boarding helpers (shared by FollowAction and travel plan)
static Transport* GetTransportForPosTolerant(Map* map, WorldObject* ref,
uint32 phaseMask, float x, float y, float z);
static bool FindBoardingPointOnTransport(Map* map, Transport* transport,
WorldObject* ref, float refX, float refY, float refZ,
float botX, float botY, float botZ,
float& outX, float& outY, float& outZ);
bool BoardTransport(Transport* transport);
private:
bool LaunchWalkSpline(TravelPlan& state);
bool CheckSplineProgress(TravelPlan& state);
bool MoveToSpline(TravelPlan& state, WorldPosition target);
// Per-segment mmap refinement of a travel-node-graph walk batch.
// The graph stores offline-baked coords whose straight-line
// interpolation may pass through geometry the bot can't actually
// traverse. Returns false if any segment is unwalkable per the
// live navmesh, in which case the caller should abort the plan.
bool RefineWalkPoints(std::vector<G3D::Vector3>& walkPoints);
protected: protected:
struct CheckAngle struct CheckAngle
{ {
@ -74,10 +121,6 @@ protected:
}; };
private: private:
// float SearchBestGroundZForPath(float x, float y, float z, bool generatePath, float range = 20.0f, bool
// normal_only = false, float step = 8.0f);
const Movement::PointsArray SearchForBestPath(float x, float y, float z, float& modified_z, int maxSearchCount = 5,
bool normal_only = false, float step = 8.0f);
bool wasMovementRestricted = false; bool wasMovementRestricted = false;
void DoMovePoint(Unit* unit, float x, float y, float z, bool generatePath, bool backwards); void DoMovePoint(Unit* unit, float x, float y, float z, bool generatePath, bool backwards);
}; };

View File

@ -44,6 +44,21 @@ bool ResetAiAction::Execute(Event event)
} }
} }
} }
if (Player* master = botAI->GetMaster())
{
Group* botGroup = bot->GetGroup();
Group* masterGroup = master->GetGroup();
if (botGroup && (!masterGroup || masterGroup != botGroup))
botAI->SetMaster(nullptr);
}
if (sRandomPlayerbotMgr.IsRandomBot(bot) && !bot->InBattleground())
{
if (bot->GetGroup() && (!botAI->GetMaster() || GET_PLAYERBOT_AI(botAI->GetMaster())))
{
if (Player* newMaster = botAI->FindNewMaster())
botAI->SetMaster(newMaster);
}
}
PlayerbotRepository::instance().Reset(botAI); PlayerbotRepository::instance().Reset(botAI);
botAI->ResetStrategies(false); botAI->ResetStrategies(false);
botAI->TellMaster("AI was reset to defaults"); botAI->TellMaster("AI was reset to defaults");

View File

@ -34,24 +34,23 @@ bool SendMailAction::Execute(Event event)
Player* receiver = GetMaster(); Player* receiver = GetMaster();
Player* tellTo = receiver; Player* tellTo = receiver;
std::vector<std::string> ss = split(text, ' ');
if (ss.size() > 1)
{
if (Player* p = ObjectAccessor::FindPlayer(ObjectGuid(uint64(ss[ss.size() - 1].c_str()))))
receiver = p;
}
if (!receiver) if (!receiver)
receiver = event.getOwner(); receiver = event.getOwner();
if (!receiver || receiver == bot) if (!receiver || receiver == bot)
{
return false; return false;
}
if (!tellTo) if (!tellTo)
tellTo = receiver; tellTo = receiver;
if (!sPlayerbotAIConfig.botSendMailEnabled)
{
bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault(
"send_mail_disabled", "I cannot send mail", {}),
LANG_UNIVERSAL, tellTo);
return false;
}
if (!mailboxFound && !randomBot) if (!mailboxFound && !randomBot)
{ {
bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault( bot->Whisper(PlayerbotTextMgr::instance().GetBotTextOrDefault(

View File

@ -16,7 +16,7 @@ void WorldPacketHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
triggers.push_back( triggers.push_back(
new TriggerNode("uninvite guid", { NextAction("uninvite", relevance) })); new TriggerNode("uninvite guid", { NextAction("uninvite", relevance) }));
triggers.push_back( triggers.push_back(
new TriggerNode("group set leader", { /*NextAction("leader", relevance),*/ })); new TriggerNode("group set leader", { NextAction("reset botAI", relevance) }));
triggers.push_back(new TriggerNode( triggers.push_back(new TriggerNode(
"not enough money", { NextAction("tell not enough money", relevance) })); "not enough money", { NextAction("tell not enough money", relevance) }));
triggers.push_back( triggers.push_back(

View File

@ -47,7 +47,13 @@ public:
{ {
if (allowedGOFlags.empty()) if (allowedGOFlags.empty())
{ {
// questgivers for accept/turn-in; rest for quest progression
// (chests, runes, altars, moonwells, lily piles, …)
allowedGOFlags.push_back(GAMEOBJECT_TYPE_QUESTGIVER); allowedGOFlags.push_back(GAMEOBJECT_TYPE_QUESTGIVER);
allowedGOFlags.push_back(GAMEOBJECT_TYPE_CHEST);
allowedGOFlags.push_back(GAMEOBJECT_TYPE_GOOBER);
allowedGOFlags.push_back(GAMEOBJECT_TYPE_SPELL_FOCUS);
allowedGOFlags.push_back(GAMEOBJECT_TYPE_GENERIC);
} }
} }

View File

@ -161,9 +161,7 @@ void ShamanAoeStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
} }
else if (tab == SHAMAN_TAB_ENHANCEMENT) else if (tab == SHAMAN_TAB_ENHANCEMENT)
{ {
triggers.push_back(new TriggerNode("medium aoe",{ NextAction("magma totem", 24.0f), triggers.push_back(new TriggerNode("medium aoe",{ NextAction("fire nova", 23.0f), }));
NextAction("fire nova", 23.0f), }));
triggers.push_back(new TriggerNode("maelstrom weapon 5 and medium aoe", { NextAction("chain lightning", 22.0f), })); triggers.push_back(new TriggerNode("maelstrom weapon 5 and medium aoe", { NextAction("chain lightning", 22.0f), }));
triggers.push_back(new TriggerNode("maelstrom weapon 4 and medium aoe", { NextAction("chain lightning", 21.0f), })); triggers.push_back(new TriggerNode("maelstrom weapon 4 and medium aoe", { NextAction("chain lightning", 21.0f), }));
triggers.push_back(new TriggerNode("enemy within melee", { NextAction("fire nova", 5.1f), })); triggers.push_back(new TriggerNode("enemy within melee", { NextAction("fire nova", 5.1f), }));

View File

@ -112,6 +112,7 @@ std::vector<NextAction> ArmsWarriorStrategy::getDefaultActions()
return { return {
NextAction("bladestorm", ACTION_DEFAULT + 0.2f), NextAction("bladestorm", ACTION_DEFAULT + 0.2f),
NextAction("mortal strike", ACTION_DEFAULT + 0.1f), NextAction("mortal strike", ACTION_DEFAULT + 0.1f),
NextAction("sunder armor", ACTION_DEFAULT + 0.05f),
NextAction("melee", ACTION_DEFAULT) NextAction("melee", ACTION_DEFAULT)
}; };
} }

View File

@ -5,6 +5,7 @@
#include "WarriorActions.h" #include "WarriorActions.h"
#include "AiFactory.h"
#include "Playerbots.h" #include "Playerbots.h"
bool CastBerserkerRageAction::isPossible() bool CastBerserkerRageAction::isPossible()
@ -36,6 +37,27 @@ bool CastBerserkerRageAction::isUseful()
bool CastSunderArmorAction::isUseful() bool CastSunderArmorAction::isUseful()
{ {
Group* group = bot->GetGroup();
if (!group)
return false;
if (!botAI->IsTank(bot, false))
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member || member == bot || !member->IsAlive() || !member->IsInWorld() ||
member->GetMapId() != bot->GetMapId())
{
continue;
}
if (member->getClass() == CLASS_WARRIOR &&
botAI->IsTank(member, false))
return false;
}
}
Aura* aura = botAI->GetAura("sunder armor", GetTarget(), false, true); Aura* aura = botAI->GetAura("sunder armor", GetTarget(), false, true);
return !aura || aura->GetStackAmount() < 5 || aura->GetDuration() <= 6000; return !aura || aura->GetStackAmount() < 5 || aura->GetDuration() <= 6000;
} }

View File

@ -3,7 +3,7 @@
#include "AiObjectContext.h" #include "AiObjectContext.h"
#include "Action.h" #include "Action.h"
#include "AuchenaiCryptsActions.h" #include "ACActions.h"
class TbcDungeonAuchenaiCryptsActionContext : public NamedObjectContext<Action> class TbcDungeonAuchenaiCryptsActionContext : public NamedObjectContext<Action>
{ {

View File

@ -1,7 +1,7 @@
#include "Playerbots.h" #include "Playerbots.h"
#include "AiFactory.h" #include "AiFactory.h"
#include "AuchenaiCryptsTriggers.h" #include "ACTriggers.h"
#include "AuchenaiCryptsActions.h" #include "ACActions.h"
// Shirrak the Dead Watcher // Shirrak the Dead Watcher

View File

@ -3,7 +3,7 @@
#include "AttackAction.h" #include "AttackAction.h"
#include "MovementActions.h" #include "MovementActions.h"
#include "AuchenaiCryptsTriggers.h" #include "ACTriggers.h"
// Shirrak the Dead Watcher // Shirrak the Dead Watcher

View File

@ -1,6 +1,6 @@
#include "AuchenaiCryptsMultipliers.h" #include "ACMultipliers.h"
#include "AuchenaiCryptsActions.h" #include "ACActions.h"
#include "AuchenaiCryptsTriggers.h" #include "ACTriggers.h"
#include "MovementActions.h" #include "MovementActions.h"
#include "ReachTargetActions.h" #include "ReachTargetActions.h"
#include "FollowActions.h" #include "FollowActions.h"

View File

@ -1,6 +1,6 @@
#include "AuchenaiCryptsTriggers.h" #include "ACTriggers.h"
#include "AuchenaiCryptsStrategy.h" #include "ACStrategy.h"
#include "AuchenaiCryptsMultipliers.h" #include "ACMultipliers.h"
void TbcDungeonAuchenaiCryptsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers) void TbcDungeonAuchenaiCryptsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
{ {

View File

@ -3,7 +3,7 @@
#include "AiObjectContext.h" #include "AiObjectContext.h"
#include "TriggerContext.h" #include "TriggerContext.h"
#include "AuchenaiCryptsTriggers.h" #include "ACTriggers.h"
class TbcDungeonAuchenaiCryptsTriggerContext : public NamedObjectContext<Trigger> class TbcDungeonAuchenaiCryptsTriggerContext : public NamedObjectContext<Trigger>
{ {

View File

@ -1,5 +1,5 @@
#include "Playerbots.h" #include "Playerbots.h"
#include "AuchenaiCryptsTriggers.h" #include "ACTriggers.h"
#include "AiObject.h" #include "AiObject.h"
#include "AiObjectContext.h" #include "AiObjectContext.h"

View File

@ -3,7 +3,7 @@
#include "Action.h" #include "Action.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "OldKingdomActions.h" #include "AKActions.h"
class WotlkDungeonOKActionContext : public NamedObjectContext<Action> class WotlkDungeonOKActionContext : public NamedObjectContext<Action>
{ {

View File

@ -1,5 +1,5 @@
#include "Playerbots.h" #include "Playerbots.h"
#include "OldKingdomActions.h" #include "AKActions.h"
bool AttackNadoxGuardianAction::Execute(Event /*event*/) bool AttackNadoxGuardianAction::Execute(Event /*event*/)
{ {

View File

@ -5,7 +5,7 @@
#include "AttackAction.h" #include "AttackAction.h"
#include "PlayerbotAI.h" #include "PlayerbotAI.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "OldKingdomTriggers.h" #include "AKTriggers.h"
class AttackNadoxGuardianAction : public AttackAction class AttackNadoxGuardianAction : public AttackAction
{ {

View File

@ -1,9 +1,9 @@
#include "OldKingdomMultipliers.h" #include "AKMultipliers.h"
#include "OldKingdomActions.h" #include "AKActions.h"
#include "GenericSpellActions.h" #include "GenericSpellActions.h"
#include "ChooseTargetActions.h" #include "ChooseTargetActions.h"
#include "MovementActions.h" #include "MovementActions.h"
#include "OldKingdomTriggers.h" #include "AKTriggers.h"
#include "Action.h" #include "Action.h"
float ElderNadoxMultiplier::GetValue(Action* action) float ElderNadoxMultiplier::GetValue(Action* action)

View File

@ -1,5 +1,5 @@
#include "OldKingdomStrategy.h" #include "AKStrategy.h"
#include "OldKingdomMultipliers.h" #include "AKMultipliers.h"
void WotlkDungeonOKStrategy::InitTriggers(std::vector<TriggerNode*> &triggers) void WotlkDungeonOKStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
{ {

View File

@ -3,7 +3,7 @@
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "AiObjectContext.h" #include "AiObjectContext.h"
#include "OldKingdomTriggers.h" #include "AKTriggers.h"
class WotlkDungeonOKTriggerContext : public NamedObjectContext<Trigger> class WotlkDungeonOKTriggerContext : public NamedObjectContext<Trigger>
{ {

View File

@ -1,5 +1,5 @@
#include "Playerbots.h" #include "Playerbots.h"
#include "OldKingdomTriggers.h" #include "AKTriggers.h"
#include "AiObject.h" #include "AiObject.h"
#include "AiObjectContext.h" #include "AiObjectContext.h"

View File

@ -3,7 +3,7 @@
#include "Action.h" #include "Action.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "AzjolNerubActions.h" #include "ANActions.h"
class WotlkDungeonANActionContext : public NamedObjectContext<Action> class WotlkDungeonANActionContext : public NamedObjectContext<Action>
{ {

View File

@ -1,5 +1,5 @@
#include "Playerbots.h" #include "Playerbots.h"
#include "AzjolNerubActions.h" #include "ANActions.h"
bool AttackWebWrapAction::isUseful() { return !botAI->IsHeal(bot); } bool AttackWebWrapAction::isUseful() { return !botAI->IsHeal(bot); }
bool AttackWebWrapAction::Execute(Event /*event*/) bool AttackWebWrapAction::Execute(Event /*event*/)

View File

@ -5,7 +5,7 @@
#include "AttackAction.h" #include "AttackAction.h"
#include "PlayerbotAI.h" #include "PlayerbotAI.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "AzjolNerubTriggers.h" #include "ANTriggers.h"
class AttackWebWrapAction : public AttackAction class AttackWebWrapAction : public AttackAction
{ {

View File

@ -1,9 +1,9 @@
#include "AzjolNerubMultipliers.h" #include "ANMultipliers.h"
#include "AzjolNerubActions.h" #include "ANActions.h"
#include "GenericSpellActions.h" #include "GenericSpellActions.h"
#include "ChooseTargetActions.h" #include "ChooseTargetActions.h"
#include "MovementActions.h" #include "MovementActions.h"
#include "AzjolNerubTriggers.h" #include "ANTriggers.h"
#include "Action.h" #include "Action.h"
float KrikthirMultiplier::GetValue(Action* action) float KrikthirMultiplier::GetValue(Action* action)

View File

@ -1,5 +1,5 @@
#include "AzjolNerubStrategy.h" #include "ANStrategy.h"
#include "AzjolNerubMultipliers.h" #include "ANMultipliers.h"
void WotlkDungeonANStrategy::InitTriggers(std::vector<TriggerNode*> &triggers) void WotlkDungeonANStrategy::InitTriggers(std::vector<TriggerNode*> &triggers)
{ {

View File

@ -3,7 +3,7 @@
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "AiObjectContext.h" #include "AiObjectContext.h"
#include "AzjolNerubTriggers.h" #include "ANTriggers.h"
class WotlkDungeonANTriggerContext : public NamedObjectContext<Trigger> class WotlkDungeonANTriggerContext : public NamedObjectContext<Trigger>
{ {

View File

@ -1,5 +1,5 @@
#include "Playerbots.h" #include "Playerbots.h"
#include "AzjolNerubTriggers.h" #include "ANTriggers.h"
#include "AiObject.h" #include "AiObject.h"
#include "AiObjectContext.h" #include "AiObjectContext.h"

View File

@ -3,7 +3,7 @@
#include "Action.h" #include "Action.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "CullingOfStratholmeActions.h" #include "CoSActions.h"
class WotlkDungeonCoSActionContext : public NamedObjectContext<Action> class WotlkDungeonCoSActionContext : public NamedObjectContext<Action>
{ {

View File

@ -1,5 +1,5 @@
#include "Playerbots.h" #include "Playerbots.h"
#include "CullingOfStratholmeActions.h" #include "CoSActions.h"
bool ExplodeGhoulSpreadAction::Execute(Event /*event*/) bool ExplodeGhoulSpreadAction::Execute(Event /*event*/)
{ {

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