mirror of
https://github.com/mod-playerbots/mod-playerbots.git
synced 2026-01-18 03:05:43 +00:00
Compare commits
131 Commits
feature/cr
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cf7f1aaef | ||
|
|
9f54d7e702 | ||
|
|
aeaaee15da | ||
|
|
a1137dbddc | ||
|
|
2eb98c3233 | ||
|
|
29613e29b7 | ||
|
|
e2c203a35e | ||
|
|
1b1ed18a23 | ||
|
|
2ab73c1fd5 | ||
|
|
6b97c379ba | ||
|
|
965d300203 | ||
|
|
dc55ecfd9c | ||
|
|
59d6eb139e | ||
|
|
00171a8c82 | ||
|
|
02e8465a3b | ||
|
|
f53a8704eb | ||
|
|
e5525958c8 | ||
|
|
9ae457d069 | ||
|
|
c9e98a6b4e | ||
|
|
3d9623f119 | ||
|
|
c9cc4324d3 | ||
|
|
b13fb7d12a | ||
|
|
962fdeb3d1 | ||
|
|
83c6977de5 | ||
|
|
686fe513b2 | ||
|
|
61402e83a1 | ||
|
|
b16789fa54 | ||
|
|
8f638b6a66 | ||
|
|
33f5e733dc | ||
|
|
9917863ca1 | ||
|
|
2317652d72 | ||
|
|
1fcd6c5cda | ||
|
|
88016789ba | ||
|
|
6be860c967 | ||
|
|
9971622093 | ||
|
|
895df9b197 | ||
|
|
467b63b840 | ||
|
|
66f5f597bb | ||
|
|
cafbd4681e | ||
|
|
f5c84ee7ff | ||
|
|
b6f882886d | ||
|
|
c1222da8b0 | ||
|
|
00cb177c86 | ||
|
|
5f697e806e | ||
|
|
934e73ae20 | ||
|
|
f4b4d8967f | ||
|
|
910b8a9c53 | ||
|
|
bb569b4d39 | ||
|
|
dde16674c3 | ||
|
|
e5b2791053 | ||
|
|
353c29dfc4 | ||
|
|
52c3e96641 | ||
|
|
38e2d8584b | ||
|
|
d5dbc4ddd7 | ||
|
|
2424f73bc4 | ||
|
|
cf743a186a | ||
|
|
10213d8381 | ||
|
|
d97870facd | ||
|
|
0c1700c117 | ||
|
|
0b1b0eaecc | ||
|
|
8e03371147 | ||
|
|
27311b734d | ||
|
|
bb5ed37cd3 | ||
|
|
e88c1b779b | ||
|
|
05057ae9b5 | ||
|
|
610a032379 | ||
|
|
ce2a990495 | ||
|
|
6effabfa42 | ||
|
|
cadbcbd447 | ||
|
|
08c739f918 | ||
|
|
0729d14787 | ||
|
|
a37dd2b9ae | ||
|
|
9c8ba42c64 | ||
|
|
e5bc495dbe | ||
|
|
85c7009fe1 | ||
|
|
ce51191e8f | ||
|
|
d02d61e690 | ||
|
|
80dbd22ba1 | ||
|
|
26a135a1ec | ||
|
|
983a55da86 | ||
|
|
e35900f9d0 | ||
|
|
d9f0d5a555 | ||
|
|
7d5c9e3ee0 | ||
|
|
43164e74e1 | ||
|
|
f7fea456ca | ||
|
|
12a5132c33 | ||
|
|
bbbf71d40c | ||
|
|
586c4d9d05 | ||
|
|
cb099bcaf4 | ||
|
|
5d3e64800f | ||
|
|
235f0249b2 | ||
|
|
7237b154e0 | ||
|
|
c3fd97b6c0 | ||
|
|
286213eb8b | ||
|
|
1d19dea974 | ||
|
|
6e1c9114df | ||
|
|
7e810f8174 | ||
|
|
e0df6558f5 | ||
|
|
50ac6e5b95 | ||
|
|
2c5185a7cb | ||
|
|
f874d2c79e | ||
|
|
5a4acbe36c | ||
|
|
e693b208be | ||
|
|
10ce94e065 | ||
|
|
553b8276eb | ||
|
|
f791ab61c4 | ||
|
|
3260ca1429 | ||
|
|
e1fa733aa5 | ||
|
|
525eceb5a2 | ||
|
|
bd13d6be80 | ||
|
|
d0ac9452f4 | ||
|
|
8a30d10617 | ||
|
|
5a0c27637e | ||
|
|
cea1e90f57 | ||
|
|
1fb66e9d75 | ||
|
|
31ed5cbb65 | ||
|
|
5b128b3300 | ||
|
|
3f050a4a77 | ||
|
|
5681f29060 | ||
|
|
cf4f0f6dc7 | ||
|
|
c90b155a70 | ||
|
|
780f6d60e0 | ||
|
|
1faf20f567 | ||
|
|
d26c2a3549 | ||
|
|
0e4c759e7f | ||
|
|
24f841f728 | ||
|
|
444be2994e | ||
|
|
8a68de4476 | ||
|
|
7d50ceef3d | ||
|
|
21ea3a7226 | ||
|
|
387c491265 |
40
.github/workflows/codestyle_cpp.yml
vendored
Normal file
40
.github/workflows/codestyle_cpp.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
name: C++ Codestyle
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- reopened
|
||||||
|
- synchronize
|
||||||
|
- ready_for_review
|
||||||
|
paths:
|
||||||
|
- src/**
|
||||||
|
- "!README.md"
|
||||||
|
- "!docs/**"
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: "codestyle-cppcheck-${{ github.event.pull_request.number }}"
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
triage:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: C++
|
||||||
|
if: github.event.pull_request.draft == false
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Setup python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.10'
|
||||||
|
- name: AzerothCore codestyle
|
||||||
|
run: python ./apps/codestyle/codestyle-cpp.py
|
||||||
|
- name: C++ Advanced
|
||||||
|
run: |
|
||||||
|
sudo apt update -y
|
||||||
|
sudo apt install -y cppcheck
|
||||||
|
cppcheck --force --inline-suppr --suppressions-list=./.suppress.cppcheck src/ --output-file=report.txt
|
||||||
|
|
||||||
|
if [ -s report.txt ]; then # if file is not empty
|
||||||
|
cat report.txt
|
||||||
|
exit 1 # let github action fails
|
||||||
|
fi
|
||||||
4
.github/workflows/core_build.yml
vendored
4
.github/workflows/core_build.yml
vendored
@@ -38,7 +38,7 @@ jobs:
|
|||||||
- name: Checkout AzerothCore
|
- name: Checkout AzerothCore
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: 'liyunfan1223/azerothcore-wotlk'
|
repository: 'mod-playerbots/azerothcore-wotlk'
|
||||||
ref: 'Playerbot'
|
ref: 'Playerbot'
|
||||||
|
|
||||||
- name: Set reusable strings
|
- name: Set reusable strings
|
||||||
@@ -50,7 +50,7 @@ jobs:
|
|||||||
- name: Checkout Playerbot Module
|
- name: Checkout Playerbot Module
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: 'liyunfan1223/mod-playerbots'
|
repository: 'mod-playerbots/mod-playerbots'
|
||||||
path: 'modules/mod-playerbots'
|
path: 'modules/mod-playerbots'
|
||||||
|
|
||||||
- name: Cache
|
- name: Cache
|
||||||
|
|||||||
4
.github/workflows/macos_build.yml
vendored
4
.github/workflows/macos_build.yml
vendored
@@ -22,12 +22,12 @@ jobs:
|
|||||||
- name: Checkout AzerothCore
|
- name: Checkout AzerothCore
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: 'liyunfan1223/azerothcore-wotlk'
|
repository: 'mod-playerbots/azerothcore-wotlk'
|
||||||
ref: 'Playerbot'
|
ref: 'Playerbot'
|
||||||
- name: Checkout Playerbot Module
|
- name: Checkout Playerbot Module
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: 'liyunfan1223/mod-playerbots'
|
repository: 'mod-playerbots/mod-playerbots'
|
||||||
path: 'modules/mod-playerbots'
|
path: 'modules/mod-playerbots'
|
||||||
- name: Cache
|
- name: Cache
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
|
|||||||
10
.github/workflows/windows_build.yml
vendored
10
.github/workflows/windows_build.yml
vendored
@@ -23,23 +23,27 @@ jobs:
|
|||||||
- name: Checkout AzerothCore
|
- name: Checkout AzerothCore
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: 'liyunfan1223/azerothcore-wotlk'
|
repository: 'mod-playerbots/azerothcore-wotlk'
|
||||||
ref: 'Playerbot'
|
ref: 'Playerbot'
|
||||||
|
path: 'ac'
|
||||||
- name: Checkout Playerbot Module
|
- name: Checkout Playerbot Module
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: 'liyunfan1223/mod-playerbots'
|
repository: 'mod-playerbots/mod-playerbots'
|
||||||
path: 'modules/mod-playerbots'
|
#path: 'modules/mod-playerbots'
|
||||||
|
path: ac/modules/mod-playerbots
|
||||||
- name: ccache
|
- name: ccache
|
||||||
uses: hendrikmuhs/ccache-action@v1.2.13
|
uses: hendrikmuhs/ccache-action@v1.2.13
|
||||||
- name: Configure OS
|
- name: Configure OS
|
||||||
shell: bash
|
shell: bash
|
||||||
|
working-directory: ac
|
||||||
env:
|
env:
|
||||||
CONTINUOUS_INTEGRATION: true
|
CONTINUOUS_INTEGRATION: true
|
||||||
run: |
|
run: |
|
||||||
./acore.sh install-deps
|
./acore.sh install-deps
|
||||||
- name: Build
|
- name: Build
|
||||||
shell: bash
|
shell: bash
|
||||||
|
working-directory: ac
|
||||||
run: |
|
run: |
|
||||||
export CTOOLS_BUILD=all
|
export CTOOLS_BUILD=all
|
||||||
./acore.sh compiler build
|
./acore.sh compiler build
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -49,3 +49,4 @@ local.properties
|
|||||||
.project
|
.project
|
||||||
.cproject
|
.cproject
|
||||||
.vscode
|
.vscode
|
||||||
|
.idea
|
||||||
|
|||||||
1
.suppress.cppcheck
Normal file
1
.suppress.cppcheck
Normal file
@@ -0,0 +1 @@
|
|||||||
|
cppcheckError
|
||||||
74
README.md
74
README.md
@@ -1,9 +1,9 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/liyunfan1223/mod-playerbots/blob/master/README.md">English</a>
|
<a href="https://github.com/mod-playerbots/mod-playerbots/blob/master/README.md">English</a>
|
||||||
|
|
|
|
||||||
<a href="https://github.com/liyunfan1223/mod-playerbots/blob/master/README_CN.md">中文</a>
|
<a href="https://github.com/mod-playerbots/mod-playerbots/blob/master/README_CN.md">中文</a>
|
||||||
|
|
|
|
||||||
<a href="https://github.com/brighton-chi/mod-playerbots/blob/readme/README_ES.md">Español</a>
|
<a href="https://github.com/mod-playerbots/mod-playerbots/blob/master/README_ES.md">Español</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
@@ -12,48 +12,50 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="https://github.com/liyunfan1223/mod-playerbots/actions/workflows/macos_build.yml/badge.svg">
|
<img src="https://github.com/mod-playerbots/mod-playerbots/actions/workflows/macos_build.yml/badge.svg">
|
||||||
<img src="https://github.com/liyunfan1223/mod-playerbots/actions/workflows/core_build.yml/badge.svg">
|
<img src="https://github.com/mod-playerbots/mod-playerbots/actions/workflows/core_build.yml/badge.svg">
|
||||||
<img src="https://github.com/liyunfan1223/mod-playerbots/actions/workflows/windows_build.yml/badge.svg">
|
<img src="https://github.com/mod-playerbots/mod-playerbots/actions/workflows/windows_build.yml/badge.svg">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
# Playerbots Module
|
# Playerbots Module
|
||||||
`mod-playerbots` is an [AzerothCore](https://www.azerothcore.org/) module that adds player-like bots to a server. The project is based off [IKE3's Playerbots](https://github.com/ike3/mangosbot) and requires a custom branch of AzerothCore to compile and run: [liyunfan1223/azerothcore-wotlk/tree/Playerbot](https://github.com/liyunfan1223/azerothcore-wotlk/tree/Playerbot).
|
`mod-playerbots` is an [AzerothCore](https://www.azerothcore.org/) module that adds player-like bots to a server. The project is based off [IKE3's Playerbots](https://github.com/ike3/mangosbot).
|
||||||
|
|
||||||
Features include:
|
Features include:
|
||||||
|
|
||||||
- The ability to log in alt characters as bots, allowing players to interact with their other characters, form parties, level up, and more;
|
- The ability to log in alt characters as bots, allowing players to interact with their other characters, form parties, level up, and more
|
||||||
- Random bots that wander through the world, complete quests, and otherwise behave like players, simulating the MMO experience;
|
- Random bots that wander through the world, complete quests, and otherwise behave like players, simulating the MMO experience
|
||||||
- Bots capable of running most raids and battlegrounds;
|
- Bots capable of running most raids and battlegrounds
|
||||||
- Highly configurable settings to define how bots behave;
|
- Highly configurable settings to define how bots behave
|
||||||
- Excellent performance, even when running thousands of bots.
|
- Excellent performance, even when running thousands of bots
|
||||||
|
|
||||||
**This project is still under development**. If you encounter any errors or experience crashes, we kindly request that you [report them as GitHub issues](https://github.com/liyunfan1223/mod-playerbots/issues/new?template=bug_report.md). Your valuable feedback will help us improve this project collaboratively.
|
We also have a **[Discord server](https://discord.gg/NQm5QShwf9)** where you can discuss the project, ask questions, and get involved in the community!
|
||||||
|
|
||||||
`mod-playerbots` has a **[Discord server](https://discord.gg/NQm5QShwf9)** where you can discuss the project, ask questions, and get involved in the community!
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Classic Installation
|
Supported platforms are Ubuntu, Windows, and macOS. Other Linux distributions may work, but may not receive support.
|
||||||
|
|
||||||
As noted above, `mod-playerbots` requires a custom branch of AzerothCore: [liyunfan1223/azerothcore-wotlk/tree/Playerbot](https://github.com/liyunfan1223/azerothcore-wotlk/tree/Playerbot). To install the module, simply run:
|
**All `mod-playerbots` installations require a custom branch of AzerothCore: [mod-playerbots/azerothcore-wotlk/tree/Playerbot](https://github.com/mod-playerbots/azerothcore-wotlk/tree/Playerbot).** This branch allows the `mod-playerbots` module to build and function. Updates from the upstream are implemented regularly to this branch. Instructions for installing this required branch and this module are provided below.
|
||||||
|
|
||||||
|
### Cloning the Repositories
|
||||||
|
|
||||||
|
To install both the required branch of AzerothCore and the `mod-playerbots` module from source, run the following:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/liyunfan1223/azerothcore-wotlk.git --branch=Playerbot
|
git clone https://github.com/mod-playerbots/azerothcore-wotlk.git --branch=Playerbot
|
||||||
cd azerothcore-wotlk/modules
|
cd azerothcore-wotlk/modules
|
||||||
git clone https://github.com/liyunfan1223/mod-playerbots.git --branch=master
|
git clone https://github.com/mod-playerbots/mod-playerbots.git --branch=master
|
||||||
```
|
```
|
||||||
|
|
||||||
For more information, refer to the [AzerothCore Installation Guide](https://www.azerothcore.org/wiki/installation) and [Installing a Module](https://www.azerothcore.org/wiki/installing-a-module) pages.
|
For more information, refer to the [AzerothCore Installation Guide](https://www.azerothcore.org/wiki/installation) and [Installing a Module](https://www.azerothcore.org/wiki/installing-a-module) pages.
|
||||||
|
|
||||||
### Docker Installation
|
### Docker Installation
|
||||||
|
|
||||||
**Docker installation is considered experimental.** To install the module on a Docker installation, run:
|
Docker installations are considered experimental (unofficial with limited support), and previous Docker experience is recommended. To install `mod-playerbots` on Docker, first clone the required branch of AzerothCore and this module:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/liyunfan1223/azerothcore-wotlk.git --branch=Playerbot
|
git clone https://github.com/mod-playerbots/azerothcore-wotlk.git --branch=Playerbot
|
||||||
cd azerothcore-wotlk/modules
|
cd azerothcore-wotlk/modules
|
||||||
git clone https://github.com/liyunfan1223/mod-playerbots.git --branch=master
|
git clone https://github.com/mod-playerbots/mod-playerbots.git --branch=master
|
||||||
```
|
```
|
||||||
|
|
||||||
Afterwards, create a `docker-compose.override.yml` file in the `azerothcore-wotlk` directory. This override file allows for mounting the modules directory to the `ac-worldserver` service which is required for it to run. Put the following inside and save:
|
Afterwards, create a `docker-compose.override.yml` file in the `azerothcore-wotlk` directory. This override file allows for mounting the modules directory to the `ac-worldserver` service which is required for it to run. Put the following inside and save:
|
||||||
@@ -77,36 +79,34 @@ services:
|
|||||||
- ./modules:/azerothcore/modules:ro
|
- ./modules:/azerothcore/modules:ro
|
||||||
```
|
```
|
||||||
|
|
||||||
For example, to double the experience gain rate per kill, take the setting `Rate.XP.Kill = 1` from [woldserver.conf](https://github.com/liyunfan1223/azerothcore-wotlk/blob/Playerbot/src/server/apps/worldserver/worldserver.conf.dist), convert it to an environment variable, and change it to the desired setting in the override file to get `AC_RATE_XP_KILL: "2"`. If you wanted to disable random bots from logging in automatically, take the `AiPlayerbot.RandomBotAutologin = 1` setting from [playerbots.conf](https://github.com/liyunfan1223/mod-playerbots/blob/master/conf/playerbots.conf.dist) and do the same to get `AC_AI_PLAYERBOT_RANDOM_BOT_AUTOLOGIN: "0"`. For more information on how to configure Azerothcore, Playerbots, and other module settings as environment variables in Docker Compose, see the "Configuring AzerothCore in Containers" section in the [Install With Docker](https://www.azerothcore.org/wiki/install-with-docker) guide.
|
For example, to double the experience gain rate per kill, take the setting `Rate.XP.Kill = 1` from [woldserver.conf](https://github.com/mod-playerbots/azerothcore-wotlk/blob/Playerbot/src/server/apps/worldserver/worldserver.conf.dist), convert it to an environment variable, and change it to the desired setting in the override file to get `AC_RATE_XP_KILL: "2"`. If you wanted to disable random bots from logging in automatically, take the `AiPlayerbot.RandomBotAutologin = 1` setting from [playerbots.conf](https://github.com/mod-playerbots/mod-playerbots/blob/master/conf/playerbots.conf.dist) and do the same to get `AC_AI_PLAYERBOT_RANDOM_BOT_AUTOLOGIN: "0"`. For more information on how to configure Azerothcore, Playerbots, and other module settings as environment variables in Docker Compose, see the "Configuring AzerothCore in Containers" section in the [Install With Docker](https://www.azerothcore.org/wiki/install-with-docker) guide.
|
||||||
|
|
||||||
Before building, consider setting the database password. One way to do this is to create a `.env` file in the root `azerothcore-wotlk` directory using the [template](https://github.com/liyunfan1223/azerothcore-wotlk/blob/Playerbot/conf/dist/env.docker). This file also allows you to set the user and group Docker uses for the services in case you run into any permissions issues, which are the most common cause for Docker installation problems.
|
Before building, consider setting the database password. One way to do this is to create a `.env` file in the root `azerothcore-wotlk` directory using the [template](https://github.com/mod-playerbots/azerothcore-wotlk/blob/Playerbot/conf/dist/env.docker). This file also allows you to set the user and group Docker uses for the services in case you run into any permissions issues, which are the most common cause for Docker installation problems.
|
||||||
|
|
||||||
Use `docker compose up -d --build` to build and run the server. For more information, including how to create an account and taking backups, refer to the [Install With Docker](https://www.azerothcore.org/wiki/install-with-docker) page.
|
Use `docker compose up -d --build` to build and run the server. For more information, including how to create an account and taking backups, refer to the [Install With Docker](https://www.azerothcore.org/wiki/install-with-docker) page.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
The [Playerbots Wiki](https://github.com/liyunfan1223/mod-playerbots/wiki) contains an extensive overview of addons, commands, raids with programmed bot strategies, and recommended performance configurations. Please note that documentation may be incomplete or out-of-date in some sections. Contributions are welcome.
|
The [Playerbots Wiki](https://github.com/mod-playerbots/mod-playerbots/wiki) contains an extensive overview of AddOns, commands, raids with programmed bot strategies, and recommended performance configurations. Please note that documentation may be incomplete or out-of-date in some sections, and contributions are welcome.
|
||||||
|
|
||||||
## Frequently Asked Questions
|
Bots are controlled via chat commands. For larger bot groups, this can be cumbersome. Because of this, community members have developed client AddOns to allow controlling bots through the in-game UI. We recommend you check out their projects listed in the [AddOns and Submodules](https://github.com/mod-playerbots/mod-playerbots/wiki/Playerbot-Addons-and-Sub%E2%80%90Modules) page.
|
||||||
|
|
||||||
- **Why aren't my bots casting spells?** Please make sure that the necessary English DBC file (enUS) is present.
|
## Contributing
|
||||||
- **What platforms are supported?** We support Ubuntu, Windows, and macOS. Other Linux distros may work, but will not receive support.
|
|
||||||
- **Why isn't my source compiling?** Please ensure that you are compiling with the required [custom branch of AzerothCore](https://github.com/liyunfan1223/azerothcore-wotlk/tree/Playerbot). Additionally, please [check the build status of our CI](https://github.com/liyunfan1223/mod-playerbots/actions). If the latest build is failing, rever to the last successful commit until we address the issue.
|
|
||||||
|
|
||||||
## Addons
|
This project is still under development. We encourage anyone to make contributions, anything from pull requests to reporting issues. If you encounter any errors or experience crashes, we encourage you [report them as GitHub issues](https://github.com/mod-playerbots/mod-playerbots/issues/new?template=bug_report.md). Your valuable feedback will help us improve this project collaboratively.
|
||||||
|
|
||||||
Typically, bots are controlled via chat commands. For larger bot groups, this can be unwieldy. As an alternative, community members have developed client Add-Ons to allow controlling bots through the in-game UI. We recommend you check out their projects:
|
If you make coding contributions, `mod-playerbots` complies with the [C++ Code Standards](https://www.azerothcore.org/wiki/cpp-code-standards) established by AzerothCore. Each Pull Request must include all test scenarios the author performed, along with their results, to demonstrate that the changes were properly verified.
|
||||||
|
|
||||||
- [Multibot](https://github.com/Macx-Lio/MultiBot) (by Macx-Lio), which includes English, Chinese, French, German, Korean, Russian, and Spanish support [note: active development is temporarily continuing on a fork in Macx-Lio's absence (https://github.com/Wishmaster117/MultiBot)]
|
We recommend joining the [Discord server](https://discord.gg/NQm5QShwf9) to make your contributions to the project easier, as a lot of active support is carried out through this server.
|
||||||
- [Unbot Addon (zh)](https://github.com/liyunfan1223/unbot-addon) (Chinese version by Liyunfan) [note: no longer under active development]
|
|
||||||
- [Unbot Addon (en)](https://github.com/noisiver/unbot-addon/tree/english) (English version translated by @Revision) [note: no longer under active development]
|
Please click on the "⭐" button to stay up to date and help us gain more visibility on GitHub!
|
||||||
|
|
||||||
## Acknowledgements
|
## Acknowledgements
|
||||||
|
|
||||||
`mod-playerbots` is is based off [ZhengPeiRu21/mod-playerbots](https://github.com/ZhengPeiRu21/mod-playerbots) and [celguar/mangosbot-bots](https://github.com/celguar/mangosbot-bots). We extend our gratitude to [@ZhengPeiRu21](https://github.com/ZhengPeiRu21) and [@celguar](https://github.com/celguar) for the continued efforts in maintaining the module.
|
`mod-playerbots` is based on [ZhengPeiRu21/mod-playerbots](https://github.com/ZhengPeiRu21/mod-playerbots) and [celguar/mangosbot-bots](https://github.com/celguar/mangosbot-bots). We extend our gratitude to [@ZhengPeiRu21](https://github.com/ZhengPeiRu21) and [@celguar](https://github.com/celguar) for their continued efforts in maintaining the module.
|
||||||
|
|
||||||
Also, a thank you to the many contributors who've helped build this project:
|
Also, a thank you to the many contributors who've helped build this project:
|
||||||
|
|
||||||
<a href="https://github.com/liyunfan1223/mod-playerbots/graphs/contributors">
|
<a href="https://github.com/mod-playerbots/mod-playerbots/graphs/contributors">
|
||||||
<img src="https://contrib.rocks/image?repo=liyunfan1223/mod-playerbots" />
|
<img src="https://contrib.rocks/image?repo=mod-playerbots/mod-playerbots" />
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -8,16 +8,16 @@
|
|||||||
|
|
||||||
## 安装
|
## 安装
|
||||||
|
|
||||||
请注意,此模块需要对AzerothCore进行特定的自定义更改。为了确保兼容性,您必须使用我fork的自定义分支来编译它,可以在这里找到:[liyunfan1223/azerothcore-wotlk/tree/Playerbot](https://github.com/liyunfan1223/azerothcore-wotlk/tree/Playerbot)。
|
请注意,此模块需要对AzerothCore进行特定的自定义更改。为了确保兼容性,您必须使用我fork的自定义分支来编译它,可以在这里找到:[mod-playerbots/azerothcore-wotlk/tree/Playerbot](https://github.com/mod-playerbots/azerothcore-wotlk/tree/Playerbot)。
|
||||||
|
|
||||||
要安装此模块,请参考AzerothCore Wiki的详细说明:[AzerothCore安装指南](https://www.azerothcore.org/wiki/installation)。
|
要安装此模块,请参考AzerothCore Wiki的详细说明:[AzerothCore安装指南](https://www.azerothcore.org/wiki/installation)。
|
||||||
|
|
||||||
我们提供了一个简单的方法来克隆该模块:
|
我们提供了一个简单的方法来克隆该模块:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/liyunfan1223/azerothcore-wotlk.git --branch=Playerbot
|
git clone https://github.com/mod-playerbots/azerothcore-wotlk.git --branch=Playerbot
|
||||||
cd azerothcore-wotlk/modules
|
cd azerothcore-wotlk/modules
|
||||||
git clone https://github.com/liyunfan1223/mod-playerbots.git --branch=master
|
git clone https://github.com/mod-playerbots/mod-playerbots.git --branch=master
|
||||||
```
|
```
|
||||||
|
|
||||||
## 快速开始与文档
|
## 快速开始与文档
|
||||||
@@ -60,7 +60,7 @@ git clone https://github.com/liyunfan1223/mod-playerbots.git --branch=master
|
|||||||
|
|
||||||
- 我们支持Ubuntu、Windows和macOS。
|
- 我们支持Ubuntu、Windows和macOS。
|
||||||
|
|
||||||
- 我们建立了持续集成工作流。您可以在[GitHub Actions](https://github.com/liyunfan1223/mod-playerbots/actions)中查看构建状态。
|
- 我们建立了持续集成工作流。您可以在[GitHub Actions](https://github.com/mod-playerbots/mod-playerbots/actions)中查看构建状态。
|
||||||
|
|
||||||
- 如果最新的构建状态失败,请恢复到上一个提交。我们将尽快解决此问题。
|
- 如果最新的构建状态失败,请恢复到上一个提交。我们将尽快解决此问题。
|
||||||
|
|
||||||
|
|||||||
263
apps/codestyle/codestyle-cpp.py
Normal file
263
apps/codestyle/codestyle-cpp.py
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
import io
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
|
||||||
|
# Get the src directory of the project
|
||||||
|
src_directory = os.path.join(os.getcwd(), 'src')
|
||||||
|
|
||||||
|
# Global variables
|
||||||
|
error_handler = False
|
||||||
|
results = {
|
||||||
|
"Multiple blank lines check": "Passed",
|
||||||
|
"Trailing whitespace check": "Passed",
|
||||||
|
"GetCounter() check": "Passed",
|
||||||
|
"Misc codestyle check": "Passed",
|
||||||
|
"GetTypeId() check": "Passed",
|
||||||
|
"NpcFlagHelpers check": "Passed",
|
||||||
|
"ItemFlagHelpers check": "Passed",
|
||||||
|
"ItemTemplateFlagHelpers check": "Passed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main function to parse all the files of the project
|
||||||
|
def parsing_file(directory: str) -> None:
|
||||||
|
print("Starting AzerothCore CPP Codestyle check...")
|
||||||
|
print(" ")
|
||||||
|
print("Please read the C++ Code Standards for AzerothCore:")
|
||||||
|
print("https://www.azerothcore.org/wiki/cpp-code-standards")
|
||||||
|
print(" ")
|
||||||
|
for root, _, files in os.walk(directory):
|
||||||
|
for file in files:
|
||||||
|
if not file.endswith('.ico'): # Skip .ico files that cannot be read
|
||||||
|
file_path = os.path.join(root, file)
|
||||||
|
file_name = file
|
||||||
|
try:
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as file:
|
||||||
|
multiple_blank_lines_check(file, file_path)
|
||||||
|
trailing_whitespace_check(file, file_path)
|
||||||
|
get_counter_check(file, file_path)
|
||||||
|
if not file_name.endswith('.cmake') and file_name != 'CMakeLists.txt':
|
||||||
|
misc_codestyle_check(file, file_path)
|
||||||
|
if file_name != 'Object.h':
|
||||||
|
get_typeid_check(file, file_path)
|
||||||
|
if file_name != 'Unit.h':
|
||||||
|
npcflags_helpers_check(file, file_path)
|
||||||
|
if file_name != 'Item.h':
|
||||||
|
itemflag_helpers_check(file, file_path)
|
||||||
|
if file_name != 'ItemTemplate.h':
|
||||||
|
itemtemplateflag_helpers_check(file, file_path)
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
print(f"\nCould not decode file {file_path}")
|
||||||
|
sys.exit(1)
|
||||||
|
# Output the results
|
||||||
|
print("")
|
||||||
|
for check, result in results.items():
|
||||||
|
print(f"{check} : {result}")
|
||||||
|
if error_handler:
|
||||||
|
print("\nPlease fix the codestyle issues above.")
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
print(f"\nEverything looks good")
|
||||||
|
|
||||||
|
# Codestyle patterns checking for multiple blank lines
|
||||||
|
def multiple_blank_lines_check(file: io, file_path: str) -> None:
|
||||||
|
global error_handler, results
|
||||||
|
file.seek(0) # Reset file pointer to the beginning
|
||||||
|
check_failed = False
|
||||||
|
consecutive_blank_lines = 0
|
||||||
|
# Parse all the file
|
||||||
|
for line_number, line in enumerate(file, start = 1):
|
||||||
|
if line.strip() == '':
|
||||||
|
consecutive_blank_lines += 1
|
||||||
|
if consecutive_blank_lines > 1:
|
||||||
|
print(f"Multiple blank lines found in {file_path} at line {line_number - 1}")
|
||||||
|
check_failed = True
|
||||||
|
else:
|
||||||
|
consecutive_blank_lines = 0
|
||||||
|
# Additional check for the end of the file
|
||||||
|
if consecutive_blank_lines >= 1:
|
||||||
|
print(f"Multiple blank lines found at the end of: {file_path}")
|
||||||
|
check_failed = True
|
||||||
|
# Handle the script error and update the result output
|
||||||
|
if check_failed:
|
||||||
|
error_handler = True
|
||||||
|
results["Multiple blank lines check"] = "Failed"
|
||||||
|
|
||||||
|
# Codestyle patterns checking for whitespace at the end of the lines
|
||||||
|
def trailing_whitespace_check(file: io, file_path: str) -> None:
|
||||||
|
global error_handler, results
|
||||||
|
file.seek(0) # Reset file pointer to the beginning
|
||||||
|
# Parse all the file
|
||||||
|
for line_number, line in enumerate(file, start = 1):
|
||||||
|
if line.endswith(' \n'):
|
||||||
|
print(f"Trailing whitespace found: {file_path} at line {line_number}")
|
||||||
|
if not error_handler:
|
||||||
|
error_handler = True
|
||||||
|
results["Trailing whitespace check"] = "Failed"
|
||||||
|
|
||||||
|
# Codestyle patterns checking for ObjectGuid::GetCounter()
|
||||||
|
def get_counter_check(file: io, file_path: str) -> None:
|
||||||
|
global error_handler, results
|
||||||
|
file.seek(0) # Reset file pointer to the beginning
|
||||||
|
# Parse all the file
|
||||||
|
for line_number, line in enumerate(file, start = 1):
|
||||||
|
if 'ObjectGuid::GetCounter()' in line:
|
||||||
|
print(f"Please use ObjectGuid::ToString().c_str() instead ObjectGuid::GetCounter(): {file_path} at line {line_number}")
|
||||||
|
if not error_handler:
|
||||||
|
error_handler = True
|
||||||
|
results["GetCounter() check"] = "Failed"
|
||||||
|
|
||||||
|
# Codestyle patterns checking for GetTypeId()
|
||||||
|
def get_typeid_check(file: io, file_path: str) -> None:
|
||||||
|
global error_handler, results
|
||||||
|
file.seek(0) # Reset file pointer to the beginning
|
||||||
|
check_failed = False
|
||||||
|
# Parse all the file
|
||||||
|
for line_number, line in enumerate(file, start = 1):
|
||||||
|
if 'GetTypeId() == TYPEID_ITEM' in line or 'GetTypeId() != TYPEID_ITEM' in line:
|
||||||
|
print(f"Please use IsItem() instead of GetTypeId(): {file_path} at line {line_number}")
|
||||||
|
check_failed = True
|
||||||
|
if 'GetTypeId() == TYPEID_UNIT' in line or 'GetTypeId() != TYPEID_UNIT' in line:
|
||||||
|
print(f"Please use IsCreature() instead of GetTypeId(): {file_path} at line {line_number}")
|
||||||
|
check_failed = True
|
||||||
|
if 'GetTypeId() == TYPEID_PLAYER' in line or 'GetTypeId() != TYPEID_PLAYER' in line:
|
||||||
|
print(f"Please use IsPlayer() instead of GetTypeId(): {file_path} at line {line_number}")
|
||||||
|
check_failed = True
|
||||||
|
if 'GetTypeId() == TYPEID_GAMEOBJECT' in line or 'GetTypeId() != TYPEID_GAMEOBJECT' in line:
|
||||||
|
print(f"Please use IsGameObject() instead of GetTypeId(): {file_path} at line {line_number}")
|
||||||
|
check_failed = True
|
||||||
|
if 'GetTypeId() == TYPEID_DYNOBJECT' in line or 'GetTypeId() != TYPEID_DYNOBJECT' in line:
|
||||||
|
print(f"Please use IsDynamicObject() instead of GetTypeId(): {file_path} at line {line_number}")
|
||||||
|
check_failed = True
|
||||||
|
# Handle the script error and update the result output
|
||||||
|
if check_failed:
|
||||||
|
error_handler = True
|
||||||
|
results["GetTypeId() check"] = "Failed"
|
||||||
|
|
||||||
|
# Codestyle patterns checking for NpcFlag helpers
|
||||||
|
def npcflags_helpers_check(file: io, file_path: str) -> None:
|
||||||
|
global error_handler, results
|
||||||
|
file.seek(0) # Reset file pointer to the beginning
|
||||||
|
check_failed = False
|
||||||
|
# Parse all the file
|
||||||
|
for line_number, line in enumerate(file, start = 1):
|
||||||
|
if 'GetUInt32Value(UNIT_NPC_FLAGS)' in line:
|
||||||
|
print(
|
||||||
|
f"Please use GetNpcFlags() instead of GetUInt32Value(UNIT_NPC_FLAGS): {file_path} at line {line_number}")
|
||||||
|
check_failed = True
|
||||||
|
if 'HasFlag(UNIT_NPC_FLAGS,' in line:
|
||||||
|
print(
|
||||||
|
f"Please use HasNpcFlag() instead of HasFlag(UNIT_NPC_FLAGS, ...): {file_path} at line {line_number}")
|
||||||
|
check_failed = True
|
||||||
|
if 'SetUInt32Value(UNIT_NPC_FLAGS,' in line:
|
||||||
|
print(
|
||||||
|
f"Please use ReplaceAllNpcFlags() instead of SetUInt32Value(UNIT_NPC_FLAGS, ...): {file_path} at line {line_number}")
|
||||||
|
check_failed = True
|
||||||
|
if 'SetFlag(UNIT_NPC_FLAGS,' in line:
|
||||||
|
print(
|
||||||
|
f"Please use SetNpcFlag() instead of SetFlag(UNIT_NPC_FLAGS, ...): {file_path} at line {line_number}")
|
||||||
|
check_failed = True
|
||||||
|
if 'RemoveFlag(UNIT_NPC_FLAGS,' in line:
|
||||||
|
print(
|
||||||
|
f"Please use RemoveNpcFlag() instead of RemoveFlag(UNIT_NPC_FLAGS, ...): {file_path} at line {line_number}")
|
||||||
|
check_failed = True
|
||||||
|
# Handle the script error and update the result output
|
||||||
|
if check_failed:
|
||||||
|
error_handler = True
|
||||||
|
results["NpcFlagHelpers check"] = "Failed"
|
||||||
|
|
||||||
|
# Codestyle patterns checking for ItemFlag helpers
|
||||||
|
def itemflag_helpers_check(file: io, file_path: str) -> None:
|
||||||
|
global error_handler, results
|
||||||
|
file.seek(0) # Reset file pointer to the beginning
|
||||||
|
check_failed = False
|
||||||
|
# Parse all the file
|
||||||
|
for line_number, line in enumerate(file, start = 1):
|
||||||
|
if 'HasFlag(ITEM_FIELD_FLAGS, ITEM_FIELD_FLAG_REFUNDABLE)' in line:
|
||||||
|
print(
|
||||||
|
f"Please use IsRefundable() instead of HasFlag(ITEM_FIELD_FLAGS, ITEM_FIELD_FLAG_REFUNDABLE): {file_path} at line {line_number}")
|
||||||
|
check_failed = True
|
||||||
|
if 'HasFlag(ITEM_FIELD_FLAGS, ITEM_FIELD_FLAG_BOP_TRADEABLE)' in line:
|
||||||
|
print(
|
||||||
|
f"Please use IsBOPTradable() instead of HasFlag(ITEM_FIELD_FLAGS, ITEM_FIELD_FLAG_BOP_TRADEABLE): {file_path} at line {line_number}")
|
||||||
|
check_failed = True
|
||||||
|
if 'HasFlag(ITEM_FIELD_FLAGS, ITEM_FIELD_FLAG_WRAPPED)' in line:
|
||||||
|
print(
|
||||||
|
f"Please use IsWrapped() instead of HasFlag(ITEM_FIELD_FLAGS, ITEM_FIELD_FLAG_WRAPPED): {file_path} at line {line_number}")
|
||||||
|
check_failed = True
|
||||||
|
# Handle the script error and update the result output
|
||||||
|
if check_failed:
|
||||||
|
error_handler = True
|
||||||
|
results["ItemFlagHelpers check"] = "Failed"
|
||||||
|
|
||||||
|
# Codestyle patterns checking for ItemTemplate helpers
|
||||||
|
def itemtemplateflag_helpers_check(file: io, file_path: str) -> None:
|
||||||
|
global error_handler, results
|
||||||
|
file.seek(0) # Reset file pointer to the beginning
|
||||||
|
check_failed = False
|
||||||
|
# Parse all the file
|
||||||
|
for line_number, line in enumerate(file, start = 1):
|
||||||
|
if 'Flags & ITEM_FLAG' in line:
|
||||||
|
print(
|
||||||
|
f"Please use HasFlag(ItemFlag) instead of 'Flags & ITEM_FLAG_': {file_path} at line {line_number}")
|
||||||
|
check_failed = True
|
||||||
|
if 'Flags2 & ITEM_FLAG2' in line:
|
||||||
|
print(
|
||||||
|
f"Please use HasFlag2(ItemFlag2) instead of 'Flags2 & ITEM_FLAG2_': {file_path} at line {line_number}")
|
||||||
|
check_failed = True
|
||||||
|
if 'FlagsCu & ITEM_FLAGS_CU' in line:
|
||||||
|
print(
|
||||||
|
f"Please use HasFlagCu(ItemFlagsCustom) instead of 'FlagsCu & ITEM_FLAGS_CU_': {file_path} at line {line_number}")
|
||||||
|
check_failed = True
|
||||||
|
# Handle the script error and update the result output
|
||||||
|
if check_failed:
|
||||||
|
error_handler = True
|
||||||
|
results["ItemTemplateFlagHelpers check"] = "Failed"
|
||||||
|
|
||||||
|
# Codestyle patterns checking for various codestyle issues
|
||||||
|
def misc_codestyle_check(file: io, file_path: str) -> None:
|
||||||
|
global error_handler, results
|
||||||
|
file.seek(0) # Reset file pointer to the beginning
|
||||||
|
check_failed = False
|
||||||
|
|
||||||
|
# used to check for "if/else (...) {" "} else" ignores "if/else (...) {...}" "#define ... if/else (...) {"
|
||||||
|
ifelse_curlyregex = r"^[^#define].*\s+(if|else)(\s*\(.*\))?\s*{[^}]*$|}\s*else(\s*{[^}]*$)"
|
||||||
|
# used to catch double semicolons ";;" ignores "(;;)"
|
||||||
|
double_semiregex = r"(?<!\()\s*;;(?!\))"
|
||||||
|
# used to catch tabs
|
||||||
|
tab_regex = r"\t"
|
||||||
|
|
||||||
|
# Parse all the file
|
||||||
|
for line_number, line in enumerate(file, start = 1):
|
||||||
|
if 'const auto&' in line:
|
||||||
|
print(
|
||||||
|
f"Please use the 'auto const&' syntax instead of 'const auto&': {file_path} at line {line_number}")
|
||||||
|
check_failed = True
|
||||||
|
if re.search(r'\bconst\s+\w+\s*\*\b', line):
|
||||||
|
print(
|
||||||
|
f"Please use the 'Class/ObjectType const*' syntax instead of 'const Class/ObjectType*': {file_path} at line {line_number}")
|
||||||
|
check_failed = True
|
||||||
|
if [match for match in [' if(', ' if ( '] if match in line]:
|
||||||
|
print(
|
||||||
|
f"Please use the 'if (XXXX)' syntax instead of 'if(XXXX)': {file_path} at line {line_number}")
|
||||||
|
check_failed = True
|
||||||
|
if re.match(ifelse_curlyregex, line):
|
||||||
|
print(
|
||||||
|
f"Curly brackets are not allowed to be leading or trailing if/else statements. Place it on a new line: {file_path} at line {line_number}")
|
||||||
|
check_failed = True
|
||||||
|
if re.search(double_semiregex, line):
|
||||||
|
print(
|
||||||
|
f"Double semicolon (;;) found in {file_path} at line {line_number}")
|
||||||
|
check_failed = True
|
||||||
|
if re.match(tab_regex, line):
|
||||||
|
print(
|
||||||
|
f"Tab found! Replace it to 4 spaces: {file_path} at line {line_number}")
|
||||||
|
check_failed = True
|
||||||
|
|
||||||
|
# Handle the script error and update the result output
|
||||||
|
if check_failed:
|
||||||
|
error_handler = True
|
||||||
|
results["Misc codestyle check"] = "Failed"
|
||||||
|
|
||||||
|
# Main function
|
||||||
|
parsing_file(src_directory)
|
||||||
0
code_format.sh
Executable file → Normal file
0
code_format.sh
Executable file → Normal file
@@ -5,7 +5,7 @@
|
|||||||
# Overview
|
# Overview
|
||||||
# "Randombot": randomly generated bots that log in separately from players and populate the world. Depending on settings, randombots may automatically grind, quest, and upgrade equipment and can be invited to groups and given commands.
|
# "Randombot": randomly generated bots that log in separately from players and populate the world. Depending on settings, randombots may automatically grind, quest, and upgrade equipment and can be invited to groups and given commands.
|
||||||
# "Altbot": characters created on player accounts, which may be logged in by the player and invited to groups and given commands like randombots. Depending on settings, altbots can be limited to characters on the active player's account or in the active player's guild.
|
# "Altbot": characters created on player accounts, which may be logged in by the player and invited to groups and given commands like randombots. Depending on settings, altbots can be limited to characters on the active player's account or in the active player's guild.
|
||||||
# Information about commands to control bots and set their strategies can be found on the wiki at https://github.com/liyunfan1223/mod-playerbots/wiki/Playerbot-Commands.
|
# Information about commands to control bots and set their strategies can be found on the wiki at https://github.com/mod-playerbots/mod-playerbots/wiki/Playerbot-Commands.
|
||||||
|
|
||||||
####################################################################################################
|
####################################################################################################
|
||||||
# SECTION INDEX
|
# SECTION INDEX
|
||||||
@@ -21,10 +21,11 @@
|
|||||||
# THRESHOLDS
|
# THRESHOLDS
|
||||||
# QUESTS
|
# QUESTS
|
||||||
# COMBAT
|
# COMBAT
|
||||||
# PALADIN BUFFS STRATEGIES
|
# GREATER BUFFS STRATEGIES
|
||||||
# CHEATS
|
# CHEATS
|
||||||
# SPELLS
|
# SPELLS
|
||||||
# FLIGHTPATH
|
# FLIGHTPATH
|
||||||
|
# PROFESSIONS
|
||||||
# RANDOMBOT-SPECIFIC SETTINGS
|
# RANDOMBOT-SPECIFIC SETTINGS
|
||||||
# GENERAL
|
# GENERAL
|
||||||
# LEVELS
|
# LEVELS
|
||||||
@@ -36,7 +37,7 @@
|
|||||||
# RPG STRATEGY
|
# RPG STRATEGY
|
||||||
# TELEPORTS
|
# TELEPORTS
|
||||||
# BATTLEGROUND & ARENA & PVP
|
# BATTLEGROUND & ARENA & PVP
|
||||||
# INTERVALS
|
# RANDOM BOT TIMING AND BEHAVIOR
|
||||||
# PREMADE SPECS
|
# PREMADE SPECS
|
||||||
# INFORMATION
|
# INFORMATION
|
||||||
# WARRIOR
|
# WARRIOR
|
||||||
@@ -44,7 +45,7 @@
|
|||||||
# HUNTER
|
# HUNTER
|
||||||
# ROGUE
|
# ROGUE
|
||||||
# PRIEST
|
# PRIEST
|
||||||
# DEATHKNIGHT
|
# DEATH KNIGHT
|
||||||
# SHAMAN
|
# SHAMAN
|
||||||
# MAGE
|
# MAGE
|
||||||
# WARLOCK
|
# WARLOCK
|
||||||
@@ -55,7 +56,7 @@
|
|||||||
# HUNTER
|
# HUNTER
|
||||||
# ROGUE
|
# ROGUE
|
||||||
# PRIEST
|
# PRIEST
|
||||||
# DEATHKNIGHT
|
# DEATH KNIGHT
|
||||||
# SHAMAN
|
# SHAMAN
|
||||||
# MAGE
|
# MAGE
|
||||||
# WARLOCK
|
# WARLOCK
|
||||||
@@ -90,17 +91,20 @@ AiPlayerbot.MinRandomBots = 500
|
|||||||
AiPlayerbot.MaxRandomBots = 500
|
AiPlayerbot.MaxRandomBots = 500
|
||||||
|
|
||||||
# Randombot accounts
|
# Randombot accounts
|
||||||
# If you are not using any expansion at all, you may have to set this manually, in which case please ensure that RandomBotAccountCount is at least greater than (MaxRandomBots / 10 + AddClassAccountPoolSize)
|
# If you are not using any expansion at all, you may have to set this manually, in which case please
|
||||||
|
# ensure that RandomBotAccountCount is at least greater than (MaxRandomBots / 10 + AddClassAccountPoolSize)
|
||||||
# Default: 0 (automatic)
|
# Default: 0 (automatic)
|
||||||
AiPlayerbot.RandomBotAccountCount = 0
|
AiPlayerbot.RandomBotAccountCount = 0
|
||||||
|
|
||||||
# Delete all randombot accounts
|
# Delete all randombot accounts
|
||||||
# To apply this, set the number to 1 and run the Worldserver. Once deletion is complete, if you would like to recreate randombots, set the number back to 0 and rerun the Worldserver.
|
# To apply this, set the number to 1 and run the Worldserver. Once deletion is complete, if you would
|
||||||
|
# like to recreate randombots, set the number back to 0 and rerun the Worldserver.
|
||||||
AiPlayerbot.DeleteRandomBotAccounts = 0
|
AiPlayerbot.DeleteRandomBotAccounts = 0
|
||||||
|
|
||||||
# Disable randombots when no real players are logged in
|
# Disable randombots when no real players are logged in
|
||||||
# Default: 0 (randombots will login when server starts)
|
# Default: 0 (randombots will login when server starts)
|
||||||
# If enabled, randombots will only log in 30 seconds (default) after a real player logs in, and will log out 300 seconds (default) after all real players log out
|
# If enabled, randombots will only log in 30 seconds (default) after a real player logs in, and will
|
||||||
|
# log out 300 seconds (default) after all real players log out
|
||||||
AiPlayerbot.DisabledWithoutRealPlayer = 0
|
AiPlayerbot.DisabledWithoutRealPlayer = 0
|
||||||
AiPlayerbot.DisabledWithoutRealPlayerLoginDelay = 30
|
AiPlayerbot.DisabledWithoutRealPlayerLoginDelay = 30
|
||||||
AiPlayerbot.DisabledWithoutRealPlayerLogoutDelay = 300
|
AiPlayerbot.DisabledWithoutRealPlayerLogoutDelay = 300
|
||||||
@@ -152,7 +156,8 @@ AiPlayerbot.AllowGuildBots = 1
|
|||||||
AiPlayerbot.AllowTrustedAccountBots = 1
|
AiPlayerbot.AllowTrustedAccountBots = 1
|
||||||
|
|
||||||
# Randombots will create guilds with nearby randombots
|
# Randombots will create guilds with nearby randombots
|
||||||
# Note: currently, randombots will not invite more bots after a guild is created (i.e., randombot guilds will have only the 10 initial randombots needed to sign the charter)
|
# Note: currently, randombots will not invite more bots after a guild is created,
|
||||||
|
# meaning randombot guilds will have only the 10 initial randombots needed to sign the charter
|
||||||
# Default: 0 (disabled)
|
# Default: 0 (disabled)
|
||||||
AiPlayerbot.RandomBotGuildNearby = 0
|
AiPlayerbot.RandomBotGuildNearby = 0
|
||||||
|
|
||||||
@@ -186,7 +191,8 @@ AiPlayerbot.AutoInitOnly = 0
|
|||||||
# Default: 1.0 (same with the player)
|
# Default: 1.0 (same with the player)
|
||||||
AiPlayerbot.AutoInitEquipLevelLimitRatio = 1.0
|
AiPlayerbot.AutoInitEquipLevelLimitRatio = 1.0
|
||||||
|
|
||||||
# Bot automatically trains spells when talking to trainer (yes = train all available spells as long as the bot has the money, free = auto trains with no money cost, no = only list spells)
|
# Bot automatically trains spells when talking to trainer
|
||||||
|
# yes = train all available spells as long as the bot has the money, free = auto trains with no money cost, no = only list spells
|
||||||
AiPlayerbot.AutoTrainSpells = yes
|
AiPlayerbot.AutoTrainSpells = yes
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -263,7 +269,7 @@ AiPlayerbot.UseFastFlyMountAtMinLevel = 70
|
|||||||
AiPlayerbot.RandomBotShowHelmet = 1
|
AiPlayerbot.RandomBotShowHelmet = 1
|
||||||
AiPlayerbot.RandomBotShowCloak = 1
|
AiPlayerbot.RandomBotShowCloak = 1
|
||||||
|
|
||||||
# Randombots and altbots automatically equip upgrades (bots will equip any item obtained from looting or a quest if they are sufficient upgrades)
|
# Randombots and altbots automatically equip any items in their inventory that are sufficient upgrades
|
||||||
# Default: 1 (enabled)
|
# Default: 1 (enabled)
|
||||||
AiPlayerbot.AutoEquipUpgradeLoot = 1
|
AiPlayerbot.AutoEquipUpgradeLoot = 1
|
||||||
|
|
||||||
@@ -284,9 +290,6 @@ AiPlayerbot.TwoRoundsGearInit = 0
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
# Bots will say information about items when collecting them
|
|
||||||
AiPlayerbot.SayWhenCollectingItems = 1
|
|
||||||
|
|
||||||
# Bots keep looting when loot system is set to free for all
|
# Bots keep looting when loot system is set to free for all
|
||||||
# Default: 0 (disabled)
|
# Default: 0 (disabled)
|
||||||
AiPlayerbot.FreeMethodLoot = 0
|
AiPlayerbot.FreeMethodLoot = 0
|
||||||
@@ -314,7 +317,8 @@ AiPlayerbot.GlobalCooldown = 500
|
|||||||
# Max wait time when moving
|
# Max wait time when moving
|
||||||
AiPlayerbot.MaxWaitForMove = 5000
|
AiPlayerbot.MaxWaitForMove = 5000
|
||||||
|
|
||||||
# Disable use of MoveSplinePath for bot movement, will result in more erratic bot movement but means stun/snare/root/etc will work on bots (they wont reliably work when MoveSplinePath is enabled, though slowing effects still work ok)
|
# Enable/disable use of MoveSplinePath for bot movement
|
||||||
|
# Disabling will result in more erratic movement but is required for stuns, snares, and roots to work on bots
|
||||||
# Default: 0 - MoveSplinePath enabled
|
# Default: 0 - MoveSplinePath enabled
|
||||||
# 1 - MoveSplinePath disabled in BG/Arena only
|
# 1 - MoveSplinePath disabled in BG/Arena only
|
||||||
# 2 - MoveSplinePath disabled everywhere
|
# 2 - MoveSplinePath disabled everywhere
|
||||||
@@ -360,15 +364,15 @@ AiPlayerbot.LootDelay = 1000
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
|
# Distances are in yards
|
||||||
AiPlayerbot.FarDistance = 20.0
|
AiPlayerbot.FarDistance = 20.0
|
||||||
AiPlayerbot.SightDistance = 75.0
|
AiPlayerbot.SightDistance = 100.0
|
||||||
AiPlayerbot.SpellDistance = 28.5
|
AiPlayerbot.SpellDistance = 28.5
|
||||||
AiPlayerbot.ShootDistance = 5.0
|
AiPlayerbot.ShootDistance = 5.0
|
||||||
AiPlayerbot.ReactDistance = 150.0
|
|
||||||
AiPlayerbot.GrindDistance = 75.0
|
|
||||||
AiPlayerbot.HealDistance = 38.5
|
AiPlayerbot.HealDistance = 38.5
|
||||||
AiPlayerbot.LootDistance = 15.0
|
AiPlayerbot.LootDistance = 15.0
|
||||||
AiPlayerbot.FleeDistance = 5.0
|
AiPlayerbot.FleeDistance = 5.0
|
||||||
|
AiPlayerbot.AggroDistance = 22
|
||||||
AiPlayerbot.TooCloseDistance = 5.0
|
AiPlayerbot.TooCloseDistance = 5.0
|
||||||
AiPlayerbot.MeleeDistance = 0.75
|
AiPlayerbot.MeleeDistance = 0.75
|
||||||
AiPlayerbot.FollowDistance = 1.5
|
AiPlayerbot.FollowDistance = 1.5
|
||||||
@@ -376,7 +380,8 @@ AiPlayerbot.WhisperDistance = 6000.0
|
|||||||
AiPlayerbot.ContactDistance = 0.45
|
AiPlayerbot.ContactDistance = 0.45
|
||||||
AiPlayerbot.AoeRadius = 10
|
AiPlayerbot.AoeRadius = 10
|
||||||
AiPlayerbot.RpgDistance = 200
|
AiPlayerbot.RpgDistance = 200
|
||||||
AiPlayerbot.AggroDistance = 22
|
AiPlayerbot.GrindDistance = 75.0
|
||||||
|
AiPlayerbot.ReactDistance = 150.0
|
||||||
|
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
@@ -407,10 +412,11 @@ AiPlayerbot.HighMana = 65
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
# Bots pick their quest rewards (yes = picks the most useful item, no = list all rewards, ask = pick useful item and lists if multiple)
|
# Bots pick their quest rewards
|
||||||
|
# yes = picks the most useful item, no = list all rewards, ask = pick useful item and lists if multiple
|
||||||
AiPlayerbot.AutoPickReward = yes
|
AiPlayerbot.AutoPickReward = yes
|
||||||
|
|
||||||
# Sync quests with player (Bots will complete quests the moment you hand them in and will not loot quest items.)
|
# Sync quests with player (bots will complete quests the moment you hand them in and will not loot quest items.)
|
||||||
# Default: 1 (enabled)
|
# Default: 1 (enabled)
|
||||||
AiPlayerbot.SyncQuestWithPlayer = 1
|
AiPlayerbot.SyncQuestWithPlayer = 1
|
||||||
|
|
||||||
@@ -435,7 +441,7 @@ AiPlayerbot.DropObsoleteQuests = 1
|
|||||||
# Auto add dungeon/raid strategies when entering the instance if implemented
|
# Auto add dungeon/raid strategies when entering the instance if implemented
|
||||||
AiPlayerbot.ApplyInstanceStrategies = 1
|
AiPlayerbot.ApplyInstanceStrategies = 1
|
||||||
|
|
||||||
# Enable auto avoid aoe strategy (experimental)
|
# Enable auto avoid aoe strategy
|
||||||
# Default: 1 (enabled)
|
# Default: 1 (enabled)
|
||||||
AiPlayerbot.AutoAvoidAoe = 1
|
AiPlayerbot.AutoAvoidAoe = 1
|
||||||
|
|
||||||
@@ -462,7 +468,7 @@ AiPlayerbot.FleeingEnabled = 1
|
|||||||
####################################################################################################
|
####################################################################################################
|
||||||
|
|
||||||
####################################################################################################
|
####################################################################################################
|
||||||
# PALADIN BUFFS STRATEGIES
|
# GREATER BUFFS STRATEGIES
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
@@ -484,12 +490,14 @@ AiPlayerbot.RPWarningCooldown = 30
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
# Enable/Disable maintenance command (learn all available spells and skills, supplement consumables, repair, enchant equipment if bot's level is above AiPlayerbot.MinEnchantingBotLevel)
|
# Enable/Disable maintenance command
|
||||||
|
# Learn all available spells and skills, assign talent points, refresh consumables, repair, enchant equipment, socket gems, etc.
|
||||||
|
# Applies if bot's level is above AiPlayerbot.MinEnchantingBotLevel
|
||||||
# Default: 1 (enabled)
|
# Default: 1 (enabled)
|
||||||
AiPlayerbot.MaintenanceCommand = 1
|
AiPlayerbot.MaintenanceCommand = 1
|
||||||
|
|
||||||
# Enable/Disable specific maintenance command functionality for alt bots
|
# Enable/Disable specific maintenance command functionality for alt bots
|
||||||
# Disable to prevent players from giving free bags, spells, skill levels etc to their alt bots
|
# Disable to prevent players from giving free bags, spells, skill levels, etc. to their alt bots
|
||||||
# Default: 1 (enabled)
|
# Default: 1 (enabled)
|
||||||
AiPlayerbot.AltMaintenanceAmmo = 1
|
AiPlayerbot.AltMaintenanceAmmo = 1
|
||||||
AiPlayerbot.AltMaintenanceFood = 1
|
AiPlayerbot.AltMaintenanceFood = 1
|
||||||
@@ -501,6 +509,7 @@ AiPlayerbot.AltMaintenanceBags = 1
|
|||||||
AiPlayerbot.AltMaintenanceMounts = 1
|
AiPlayerbot.AltMaintenanceMounts = 1
|
||||||
AiPlayerbot.AltMaintenanceSkills = 1
|
AiPlayerbot.AltMaintenanceSkills = 1
|
||||||
|
|
||||||
|
# "Special Spells" consist of any spells listed in AiPlayerbot.RandomBotSpellIds and Death Gate for Death Knights
|
||||||
AiPlayerbot.AltMaintenanceClassSpells = 1
|
AiPlayerbot.AltMaintenanceClassSpells = 1
|
||||||
AiPlayerbot.AltMaintenanceAvailableSpells = 1
|
AiPlayerbot.AltMaintenanceAvailableSpells = 1
|
||||||
AiPlayerbot.AltMaintenanceSpecialSpells = 1
|
AiPlayerbot.AltMaintenanceSpecialSpells = 1
|
||||||
@@ -515,8 +524,8 @@ AiPlayerbot.AltMaintenanceReputation = 1
|
|||||||
AiPlayerbot.AltMaintenanceAttunementQuests = 1
|
AiPlayerbot.AltMaintenanceAttunementQuests = 1
|
||||||
AiPlayerbot.AltMaintenanceKeyring = 1
|
AiPlayerbot.AltMaintenanceKeyring = 1
|
||||||
|
|
||||||
|
# Enable/Disable autogear command, which automatically upgrades bots' gear
|
||||||
# Enable/Disable autogear command, which automatically upgrades bots' gear; the quality is limited by AutoGearQualityLimit and AutoGearScoreLimit
|
# The quality is limited by AutoGearQualityLimit and AutoGearScoreLimit
|
||||||
# Default: 1 (enabled)
|
# Default: 1 (enabled)
|
||||||
AiPlayerbot.AutoGearCommand = 1
|
AiPlayerbot.AutoGearCommand = 1
|
||||||
|
|
||||||
@@ -548,7 +557,7 @@ AiPlayerbot.AutoGearScoreLimit = 0
|
|||||||
# "mana" (bots have infinite mana)
|
# "mana" (bots have infinite mana)
|
||||||
# "power" (bots have infinite energy, rage, and runic power)
|
# "power" (bots have infinite energy, rage, and runic power)
|
||||||
# "taxi" (bots may use all flight paths, though they will not actually learn them)
|
# "taxi" (bots may use all flight paths, though they will not actually learn them)
|
||||||
# "raid" (bots use cheats implemented into raid strategies)
|
# "raid" (bots use cheats implemented into raid strategies (currently only for Ulduar))
|
||||||
# To use multiple cheats, separate them by commas below (e.g., to enable all, use "gold,health,mana,power,raid,taxi")
|
# To use multiple cheats, separate them by commas below (e.g., to enable all, use "gold,health,mana,power,raid,taxi")
|
||||||
# Default: food, taxi, and raid are enabled
|
# Default: food, taxi, and raid are enabled
|
||||||
AiPlayerbot.BotCheats = "food,taxi,raid"
|
AiPlayerbot.BotCheats = "food,taxi,raid"
|
||||||
@@ -580,6 +589,30 @@ AiPlayerbot.BotTaxiGapJitterMs = 100
|
|||||||
#
|
#
|
||||||
####################################################################################################
|
####################################################################################################
|
||||||
|
|
||||||
|
####################################################################################################
|
||||||
|
# PROFESSIONS
|
||||||
|
# Note: Random bots currently do not get professions
|
||||||
|
#
|
||||||
|
|
||||||
|
# Automatically adds the 'master fishing' strategy to bots that have the fishing skill when the bots master fishes.
|
||||||
|
# Default: 1 (Enabled)
|
||||||
|
AiPlayerbot.EnableFishingWithMaster = 1
|
||||||
|
|
||||||
|
# Distance from itself (in yards) that a bot with a master will search for water to fish
|
||||||
|
AiPlayerbot.FishingDistanceFromMaster = 10.0
|
||||||
|
|
||||||
|
# Distance from itself (in yards) that a bot without a master will search for water to fish
|
||||||
|
# Currently not relevant since masterless bots will not fish
|
||||||
|
AiPlayerbot.FishingDistance = 40.0
|
||||||
|
|
||||||
|
# Distance from water (in yards) beyond which a bot will remove the 'master fishing' strategy
|
||||||
|
AiPlayerbot.EndFishingWithMaster = 30.0
|
||||||
|
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
####################################################################################################
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
# #
|
# #
|
||||||
# RANDOMBOT-SPECIFIC SETTINGS #
|
# RANDOMBOT-SPECIFIC SETTINGS #
|
||||||
@@ -606,9 +639,11 @@ AiPlayerbot.RandomBotMaxLevel = 80
|
|||||||
AiPlayerbot.SyncLevelWithPlayers = 0
|
AiPlayerbot.SyncLevelWithPlayers = 0
|
||||||
|
|
||||||
# Mark many quests ≤ bot level as complete (slows down bot creation)
|
# Mark many quests ≤ bot level as complete (slows down bot creation)
|
||||||
|
# Default: 0 (disabled)
|
||||||
AiPlayerbot.PreQuests = 0
|
AiPlayerbot.PreQuests = 0
|
||||||
|
|
||||||
# Enable LFG for randombots
|
# Enable LFG for randombots
|
||||||
|
# Default: 1 (enabled)
|
||||||
AiPlayerbot.RandomBotJoinLfg = 1
|
AiPlayerbot.RandomBotJoinLfg = 1
|
||||||
|
|
||||||
# Enable/Disable periodic online - offline of randombots to mimic the real-world scenario where not all players are online simultaneously
|
# Enable/Disable periodic online - offline of randombots to mimic the real-world scenario where not all players are online simultaneously
|
||||||
@@ -629,7 +664,8 @@ AiPlayerbot.RandomBotHordeRatio = 50
|
|||||||
AiPlayerbot.DisableDeathKnightLogin = 0
|
AiPlayerbot.DisableDeathKnightLogin = 0
|
||||||
|
|
||||||
# Enable simulated expansion limitation for talents and glyphs
|
# Enable simulated expansion limitation for talents and glyphs
|
||||||
# If enabled, limits talent trees to 5 rows plus the middle talent of the 6th row for bots until level 61 and 7 rows plus the middle talent of the 8th row for bots until level 71
|
# If enabled, limits talent trees to 5 rows plus the middle talent of the 6th row for bots until level 61
|
||||||
|
# and 7 rows plus the middle talent of the 8th row for bots from level 61 until level 71
|
||||||
# Default: 0 (disabled)
|
# Default: 0 (disabled)
|
||||||
AiPlayerbot.LimitTalentsExpansion = 0
|
AiPlayerbot.LimitTalentsExpansion = 0
|
||||||
|
|
||||||
@@ -698,7 +734,7 @@ AiPlayerbot.RandomGearQualityLimit = 3
|
|||||||
# Max iLVL Phase 1(MC, Ony, ZG) = 78 | Phase 2(BWL) = 83 | Phase 2.5(AQ40) = 88 | Phase 3(Naxx40) = 92
|
# Max iLVL Phase 1(MC, Ony, ZG) = 78 | Phase 2(BWL) = 83 | Phase 2.5(AQ40) = 88 | Phase 3(Naxx40) = 92
|
||||||
# TBC
|
# TBC
|
||||||
# Max iLVL Tier 4 = 120 | Tier 5 = 133 | Tier 6 = 164
|
# Max iLVL Tier 4 = 120 | Tier 5 = 133 | Tier 6 = 164
|
||||||
# Max iLVL Phase 1(Kara, Gruul, Mag) = 125 | Phase 1.5(ZA) = 138 | Phase 2(SC, TK) = 141 | Phase 3(Hyjal, BT) = 156 | Phase 4(Sunwell) = 164
|
# Max iLVL Phase 1(Kara, Gruul, Mag) = 125 | Phase 2(SSC, TK, ZA) = 141 | Phase 3(Hyjal, BT) = 156 | Phase 4(Sunwell) = 164
|
||||||
# Wotlk
|
# Wotlk
|
||||||
# Max iLVL Tier 7(10/25) = 200/213 | Tier 8(10/25) = 225/232 | Tier 9(10/25) = 232/245 | Tier 10(10/25/HC) = 251/264/290
|
# Max iLVL Tier 7(10/25) = 200/213 | Tier 8(10/25) = 225/232 | Tier 9(10/25) = 232/245 | Tier 10(10/25/HC) = 251/264/290
|
||||||
# Max iLVL Phase 1(Naxx) = 224 | Phase 2(Ulduar) = 245 | Phase 3(ToC) = 258 | Phase 4(ICC) = 290
|
# Max iLVL Phase 1(Naxx) = 224 | Phase 2(Ulduar) = 245 | Phase 3(ToC) = 258 | Phase 4(ICC) = 290
|
||||||
@@ -709,12 +745,13 @@ AiPlayerbot.RandomGearScoreLimit = 0
|
|||||||
# Default: 1 (enabled)
|
# Default: 1 (enabled)
|
||||||
AiPlayerbot.IncrementalGearInit = 1
|
AiPlayerbot.IncrementalGearInit = 1
|
||||||
|
|
||||||
# Set minimum level of bots that will enchant their equipment (if greater than RandomBotMaxlevel, bots will not enchant equipment)
|
# Set minimum level of bots that will enchant and socket gems into their equipment with maintenance
|
||||||
|
# If greater than RandomBotMaxlevel, bots will not automatically enchant equipment or socket gems
|
||||||
# Default: 60
|
# Default: 60
|
||||||
AiPlayerbot.MinEnchantingBotLevel = 60
|
AiPlayerbot.MinEnchantingBotLevel = 60
|
||||||
|
|
||||||
# Enable expansion limitation for bot enchants
|
# Enable expansion limitation for bot enchants and gems
|
||||||
# If enabled, bots will not use TBC enchants until level 61 or WotLK enchants until level 71
|
# If enabled, bots will not use TBC enchants until level 61 or WotLK enchants and gems until level 71
|
||||||
# Default: 1 (enabled)
|
# Default: 1 (enabled)
|
||||||
AiPlayerbot.LimitEnchantExpansion = 1
|
AiPlayerbot.LimitEnchantExpansion = 1
|
||||||
|
|
||||||
@@ -810,7 +847,7 @@ AiPlayerbot.botActiveAloneSmartScaleWhenMaxLevel = 80
|
|||||||
#
|
#
|
||||||
|
|
||||||
# Quest that will be completed and rewarded for all randombots
|
# Quest that will be completed and rewarded for all randombots
|
||||||
AiPlayerbot.RandomBotQuestIds = "7848,3802,5505,6502,7761,10277,10285,11492,13188,13189,24499,24511,24710,24712"
|
AiPlayerbot.RandomBotQuestIds = "3802,5505,6502,7761,7848,10277,10285,11492,13188,13189,24499,24511,24710,24712"
|
||||||
|
|
||||||
# Randombots will group with nearby randombots to do shared quests
|
# Randombots will group with nearby randombots to do shared quests
|
||||||
AiPlayerbot.RandomBotGroupNearby = 0
|
AiPlayerbot.RandomBotGroupNearby = 0
|
||||||
@@ -861,19 +898,21 @@ AiPlayerbot.OpenGoSpell = 6477
|
|||||||
#
|
#
|
||||||
|
|
||||||
# Additional randombot strategies
|
# Additional randombot strategies
|
||||||
# Strategies added here are applied to all randombots, in addition (or subtraction) to spec/role-based default strategies. These rules are processed after the defaults.
|
# Strategies added here are applied to all randombots, in addition (or subtraction) to spec/role-based default strategies.
|
||||||
|
# These rules are processed after the defaults.
|
||||||
# Example: "+threat,-potions"
|
# Example: "+threat,-potions"
|
||||||
AiPlayerbot.RandomBotCombatStrategies = ""
|
AiPlayerbot.RandomBotCombatStrategies = ""
|
||||||
AiPlayerbot.RandomBotNonCombatStrategies = ""
|
AiPlayerbot.RandomBotNonCombatStrategies = ""
|
||||||
|
|
||||||
# Additional altbot strategies
|
# Additional altbot strategies
|
||||||
# Strategies added here are applied to all altbots, in addition (or subtraction) to spec/role-based default strategies. These rules are processed after the defaults.
|
# Strategies added here are applied to all altbots, in addition (or subtraction) to spec/role-based default strategies.
|
||||||
|
# These rules are processed after the defaults.
|
||||||
AiPlayerbot.CombatStrategies = ""
|
AiPlayerbot.CombatStrategies = ""
|
||||||
AiPlayerbot.NonCombatStrategies = ""
|
AiPlayerbot.NonCombatStrategies = ""
|
||||||
|
|
||||||
# Remove "healer dps" strategy on specified maps.
|
# Remove "healer dps" strategy on the maps specified below.
|
||||||
# Default: 0 (disabled)
|
# Default: 1 (enabled)
|
||||||
AiPlayerbot.HealerDPSMapRestriction = 0
|
AiPlayerbot.HealerDPSMapRestriction = 1
|
||||||
|
|
||||||
# List of Map IDs where "healer dps" strategy will be removed if AiPlayerbot.HealerDPSMapRestriction is enabled
|
# List of Map IDs where "healer dps" strategy will be removed if AiPlayerbot.HealerDPSMapRestriction is enabled
|
||||||
# Default: (Dungeon and Raid maps) "33,34,36,43,47,48,70,90,109,129,209,229,230,329,349,389,429,1001,1004,1007,269,540,542,543,545,546,547,552,553,554,555,556,557,558,560,585,574,575,576,578,595,599,600,601,602,604,608,619,632,650,658,668,409,469,509,531,532,534,544,548,550,564,565,580,249,533,603,615,616,624,631,649,724"
|
# Default: (Dungeon and Raid maps) "33,34,36,43,47,48,70,90,109,129,209,229,230,329,349,389,429,1001,1004,1007,269,540,542,543,545,546,547,552,553,554,555,556,557,558,560,585,574,575,576,578,595,599,600,601,602,604,608,619,632,650,658,668,409,469,509,531,532,534,544,548,550,564,565,580,249,533,603,615,616,624,631,649,724"
|
||||||
@@ -1057,7 +1096,8 @@ AiPlayerbot.ZoneBracket.4197 = 79,80
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
# Maps where bots can be teleported to
|
# Map IDs where bots can be teleported to
|
||||||
|
# Defaults: 0 = Eastern Kingdoms, 1 = Kalimdor, 530 = Outland, 571 = Northrend
|
||||||
AiPlayerbot.RandomBotMaps = 0,1,530,571
|
AiPlayerbot.RandomBotMaps = 0,1,530,571
|
||||||
|
|
||||||
# Probabilty bots teleport to banker (city)
|
# Probabilty bots teleport to banker (city)
|
||||||
@@ -1182,7 +1222,7 @@ AiPlayerbot.DeleteRandomBotArenaTeams = 0
|
|||||||
AiPlayerbot.PvpProhibitedZoneIds = "2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,3951"
|
AiPlayerbot.PvpProhibitedZoneIds = "2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,3951"
|
||||||
|
|
||||||
# PvP Restricted Areas (bots don't pvp)
|
# PvP Restricted Areas (bots don't pvp)
|
||||||
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754"
|
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312,3649,3887,3958,3724,4080,3938,3754,3786,3973"
|
||||||
|
|
||||||
# Improve reaction speeds in battlegrounds and arenas (may cause lag)
|
# Improve reaction speeds in battlegrounds and arenas (may cause lag)
|
||||||
AiPlayerbot.FastReactInBG = 1
|
AiPlayerbot.FastReactInBG = 1
|
||||||
@@ -1193,24 +1233,46 @@ AiPlayerbot.FastReactInBG = 1
|
|||||||
####################################################################################################
|
####################################################################################################
|
||||||
|
|
||||||
####################################################################################################
|
####################################################################################################
|
||||||
# INTERVALS
|
# RANDOM BOT TIMING AND BEHAVIOR
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
# All in seconds
|
# How often (in seconds) the random bot manager runs its main update loop
|
||||||
|
# Default: 20
|
||||||
AiPlayerbot.RandomBotUpdateInterval = 20
|
AiPlayerbot.RandomBotUpdateInterval = 20
|
||||||
|
|
||||||
|
# Minimum and maximum seconds before the manager re-evaluates and adjusts total random bot count
|
||||||
|
# Defaults: 1800 (min), 7200 (max)
|
||||||
AiPlayerbot.RandomBotCountChangeMinInterval = 1800
|
AiPlayerbot.RandomBotCountChangeMinInterval = 1800
|
||||||
AiPlayerbot.RandomBotCountChangeMaxInterval = 7200
|
AiPlayerbot.RandomBotCountChangeMaxInterval = 7200
|
||||||
|
|
||||||
|
# Minimum and maximum seconds a random bot will stay online before logging out
|
||||||
|
# Defaults: 600 (min), 28800 (max)
|
||||||
AiPlayerbot.MinRandomBotInWorldTime = 600
|
AiPlayerbot.MinRandomBotInWorldTime = 600
|
||||||
AiPlayerbot.MaxRandomBotInWorldTime = 28800
|
AiPlayerbot.MaxRandomBotInWorldTime = 28800
|
||||||
|
|
||||||
|
# Minimum and maximum seconds before a bot is eligible for re-randomization (gear, class, talents, etc.)
|
||||||
|
# Defaults: 7200 (min), 1209600 (max)
|
||||||
AiPlayerbot.MinRandomBotRandomizeTime = 7200
|
AiPlayerbot.MinRandomBotRandomizeTime = 7200
|
||||||
AiPlayerbot.MaxRandomBotRandomizeTime = 1209600
|
AiPlayerbot.MaxRandomBotRandomizeTime = 1209600
|
||||||
|
|
||||||
|
# Number of bots processed (login, logout, update) per manager update cycle
|
||||||
|
# Default: 60
|
||||||
AiPlayerbot.RandomBotsPerInterval = 60
|
AiPlayerbot.RandomBotsPerInterval = 60
|
||||||
|
|
||||||
|
# Minimum and maximum seconds after death before a bot revives
|
||||||
|
# Defaults: 60 (min), 300 (max)
|
||||||
AiPlayerbot.MinRandomBotReviveTime = 60
|
AiPlayerbot.MinRandomBotReviveTime = 60
|
||||||
AiPlayerbot.MaxRandomBotReviveTime = 300
|
AiPlayerbot.MaxRandomBotReviveTime = 300
|
||||||
|
|
||||||
|
# Minimum and maximum seconds between bot teleports to new areas or zones
|
||||||
|
# Defaults: 3600 (min), 18000 (max)
|
||||||
AiPlayerbot.MinRandomBotTeleportInterval = 3600
|
AiPlayerbot.MinRandomBotTeleportInterval = 3600
|
||||||
AiPlayerbot.MaxRandomBotTeleportInterval = 18000
|
AiPlayerbot.MaxRandomBotTeleportInterval = 18000
|
||||||
AiPlayerbot.PermanantlyInWorldTime = 31104000
|
|
||||||
|
# Number of seconds bots flagged as permanent stay in the world (31,104,000 ≈ 1 year)
|
||||||
|
# Default: 31104000
|
||||||
|
AiPlayerbot.PermanentlyInWorldTime = 31104000
|
||||||
|
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
@@ -1430,7 +1492,7 @@ AiPlayerbot.PremadeSpecLink.5.5.80 = 50332031003--005323241223112003102311351
|
|||||||
####################################################################################################
|
####################################################################################################
|
||||||
|
|
||||||
####################################################################################################
|
####################################################################################################
|
||||||
# DEATHKNIGHT
|
# DEATH KNIGHT
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
@@ -1753,7 +1815,7 @@ AiPlayerbot.RandomClassSpecIndex.5.2 = 2
|
|||||||
####################################################################################################
|
####################################################################################################
|
||||||
|
|
||||||
####################################################################################################
|
####################################################################################################
|
||||||
# DEATHKNIGHT
|
# DEATH KNIGHT
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|||||||
1976
data/sql/playerbots/updates/2025_10_09_00_ai_playerbot_texts_fix.sql
Normal file
1976
data/sql/playerbots/updates/2025_10_09_00_ai_playerbot_texts_fix.sql
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,255 @@
|
|||||||
|
DELETE FROM ai_playerbot_texts WHERE name IN (
|
||||||
|
'pet_usage_error',
|
||||||
|
'pet_no_pet_error',
|
||||||
|
'pet_stance_report',
|
||||||
|
'pet_no_target_error',
|
||||||
|
'pet_target_dead_error',
|
||||||
|
'pet_invalid_target_error',
|
||||||
|
'pet_pvp_prohibited_error',
|
||||||
|
'pet_attack_success',
|
||||||
|
'pet_attack_failed',
|
||||||
|
'pet_follow_success',
|
||||||
|
'pet_stay_success',
|
||||||
|
'pet_unknown_command_error',
|
||||||
|
'pet_stance_set_success',
|
||||||
|
'pet_type_pet',
|
||||||
|
'pet_type_guardian',
|
||||||
|
'pet_stance_aggressive',
|
||||||
|
'pet_stance_defensive',
|
||||||
|
'pet_stance_passive',
|
||||||
|
'pet_stance_unknown'
|
||||||
|
);
|
||||||
|
|
||||||
|
DELETE FROM ai_playerbot_texts_chance WHERE name IN (
|
||||||
|
'pet_usage_error',
|
||||||
|
'pet_no_pet_error',
|
||||||
|
'pet_stance_report',
|
||||||
|
'pet_no_target_error',
|
||||||
|
'pet_target_dead_error',
|
||||||
|
'pet_invalid_target_error',
|
||||||
|
'pet_pvp_prohibited_error',
|
||||||
|
'pet_attack_success',
|
||||||
|
'pet_attack_failed',
|
||||||
|
'pet_follow_success',
|
||||||
|
'pet_stay_success',
|
||||||
|
'pet_unknown_command_error',
|
||||||
|
'pet_stance_set_success',
|
||||||
|
'pet_type_pet',
|
||||||
|
'pet_type_guardian',
|
||||||
|
'pet_stance_aggressive',
|
||||||
|
'pet_stance_defensive',
|
||||||
|
'pet_stance_passive',
|
||||||
|
'pet_stance_unknown'
|
||||||
|
);
|
||||||
|
|
||||||
|
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
|
||||||
|
(1717, 'pet_usage_error', "Usage: pet <aggressive|defensive|passive|stance|attack|follow|stay>", 0, 0,
|
||||||
|
"사용법: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||||
|
"Utilisation: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||||
|
"Verwendung: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||||
|
"用法: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||||
|
"用法: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||||
|
"Uso: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||||
|
"Uso: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||||
|
"Использование: pet <aggressive|defensive|passive|stance|attack|follow|stay>"),
|
||||||
|
|
||||||
|
(1718, 'pet_no_pet_error', "You have no pet or guardian pet.", 0, 0,
|
||||||
|
"펫이나 수호자 펫이 없습니다.",
|
||||||
|
"Vous n'avez pas de familier ou gardien.",
|
||||||
|
"Du hast kein Tier oder Wächter.",
|
||||||
|
"你没有宠物或守护者宠物。",
|
||||||
|
"你沒有寵物或守護者寵物。",
|
||||||
|
"No tienes mascota o mascota guardián.",
|
||||||
|
"No tienes mascota o mascota guardián.",
|
||||||
|
"У вас нет питомца или защитника."),
|
||||||
|
|
||||||
|
(1719, 'pet_stance_report', "Current stance of %type \"%name\": %stance.", 0, 0,
|
||||||
|
"%type \"%name\"의 현재 태세: %stance.",
|
||||||
|
"Position actuelle du %type \"%name\": %stance.",
|
||||||
|
"Aktuelle Haltung des %type \"%name\": %stance.",
|
||||||
|
"%type \"%name\" 的当前姿态: %stance。",
|
||||||
|
"%type \"%name\" 的當前姿態: %stance。",
|
||||||
|
"Postura actual del %type \"%name\": %stance.",
|
||||||
|
"Postura actual del %type \"%name\": %stance.",
|
||||||
|
"Текущая позиция %type \"%name\": %stance."),
|
||||||
|
|
||||||
|
(1720, 'pet_no_target_error', "No valid target selected by master.", 0, 0,
|
||||||
|
"주인이 유효한 대상을 선택하지 않았습니다.",
|
||||||
|
"Aucune cible valide sélectionnée par le maître.",
|
||||||
|
"Kein gültiges Ziel vom Meister ausgewählt.",
|
||||||
|
"主人未选择有效目标。",
|
||||||
|
"主人未選擇有效目標。",
|
||||||
|
"No hay objetivo válido seleccionado por el maestro.",
|
||||||
|
"No hay objetivo válido seleccionado por el maestro.",
|
||||||
|
"Хозяин не выбрал действительную цель."),
|
||||||
|
|
||||||
|
(1721, 'pet_target_dead_error', "Target is not alive.", 0, 0,
|
||||||
|
"대상이 살아있지 않습니다.",
|
||||||
|
"La cible n'est pas vivante.",
|
||||||
|
"Das Ziel ist nicht am Leben.",
|
||||||
|
"目标未存活。",
|
||||||
|
"目標未存活。",
|
||||||
|
"El objetivo no está vivo.",
|
||||||
|
"El objetivo no está vivo.",
|
||||||
|
"Цель не жива."),
|
||||||
|
|
||||||
|
(1722, 'pet_invalid_target_error', "Target is not a valid attack target for the bot.", 0, 0,
|
||||||
|
"대상이 봇에게 유효한 공격 대상이 아닙니다.",
|
||||||
|
"La cible n'est pas une cible d'attaque valide pour le bot.",
|
||||||
|
"Das Ziel ist kein gültiges Angriffsziel für den Bot.",
|
||||||
|
"目标不是机器人的有效攻击目标。",
|
||||||
|
"目標不是機器人的有效攻擊目標。",
|
||||||
|
"El objetivo no es un objetivo de ataque válido para el bot.",
|
||||||
|
"El objetivo no es un objetivo de ataque válido para el bot.",
|
||||||
|
"Цель не является допустимой целью атаки для бота."),
|
||||||
|
|
||||||
|
(1723, 'pet_pvp_prohibited_error', "I cannot command my pet to attack players in PvP prohibited areas.", 0, 0,
|
||||||
|
"PvP 금지 지역에서는 펫에게 플레이어 공격 명령을 내릴 수 없습니다.",
|
||||||
|
"Je ne peux pas commander à mon familier d'attaquer des joueurs dans les zones où le PvP est interdit.",
|
||||||
|
"Ich kann meinem Tier nicht befehlen, Spieler in PvP-verbotenen Gebieten anzugreifen.",
|
||||||
|
"我不能命令我的宠物在禁止PvP的区域攻击玩家。",
|
||||||
|
"我不能命令我的寵物在禁止PvP的區域攻擊玩家。",
|
||||||
|
"No puedo ordenar a mi mascota atacar jugadores en áreas donde el PvP está prohibido.",
|
||||||
|
"No puedo ordenar a mi mascota atacar jugadores en áreas donde el PvP está prohibido.",
|
||||||
|
"Я не могу приказать своему питомцу атаковать игроков в зонах, где PvP запрещено."),
|
||||||
|
|
||||||
|
(1724, 'pet_attack_success', "Pet commanded to attack your target.", 0, 0,
|
||||||
|
"펫이 당신의 대상을 공격하도록 명령했습니다.",
|
||||||
|
"Le familier a reçu l'ordre d'attaquer votre cible.",
|
||||||
|
"Tier wurde befohlen, dein Ziel anzugreifen.",
|
||||||
|
"宠物已命令攻击你的目标。",
|
||||||
|
"寵物已命令攻擊你的目標。",
|
||||||
|
"Mascota ordenada a atacar tu objetivo.",
|
||||||
|
"Mascota ordenada a atacar tu objetivo.",
|
||||||
|
"Питомцу приказано атаковать вашу цель."),
|
||||||
|
|
||||||
|
(1725, 'pet_attack_failed', "Pet did not attack. (Already attacking or unable to attack target)", 0, 0,
|
||||||
|
"펫이 공격하지 않았습니다. (이미 공격 중이거나 대상 공격 불가)",
|
||||||
|
"Le familier n'a pas attaqué. (Attaque déjà en cours ou impossible d'attaquer la cible)",
|
||||||
|
"Tier hat nicht angegriffen. (Greift bereits an oder kann Ziel nicht angreifen)",
|
||||||
|
"宠物未攻击。(已在攻击或无法攻击目标)",
|
||||||
|
"寵物未攻擊。(已在攻擊或無法攻擊目標)",
|
||||||
|
"La mascota no atacó. (Ya está atacando o no puede atacar al objetivo)",
|
||||||
|
"La mascota no atacó. (Ya está atacando o no puede atacar al objetivo)",
|
||||||
|
"Питомец не атаковал. (Уже атакует или не может атаковать цель)"),
|
||||||
|
|
||||||
|
(1726, 'pet_follow_success', "Pet commanded to follow.", 0, 0,
|
||||||
|
"펫이 따라오도록 명령했습니다.",
|
||||||
|
"Le familier a reçu l'ordre de suivre.",
|
||||||
|
"Tier wurde befohlen zu folgen.",
|
||||||
|
"宠物已命令跟随。",
|
||||||
|
"寵物已命令跟隨。",
|
||||||
|
"Mascota ordenada a seguir.",
|
||||||
|
"Mascota ordenada a seguir.",
|
||||||
|
"Питомцу приказано следовать."),
|
||||||
|
|
||||||
|
(1727, 'pet_stay_success', "Pet commanded to stay.", 0, 0,
|
||||||
|
"펫이 머물도록 명령했습니다.",
|
||||||
|
"Le familier a reçu l'ordre de rester.",
|
||||||
|
"Tier wurde befohlen zu bleiben.",
|
||||||
|
"宠物已命令停留。",
|
||||||
|
"寵物已命令停留。",
|
||||||
|
"Mascota ordenada a quedarse.",
|
||||||
|
"Mascota ordenada a quedarse.",
|
||||||
|
"Питомцу приказано остаться."),
|
||||||
|
|
||||||
|
(1728, 'pet_unknown_command_error', "Unknown pet command: %param. Use: pet <aggressive|defensive|passive|stance|attack|follow|stay>", 0, 0,
|
||||||
|
"알 수 없는 펫 명령: %param. 사용법: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||||
|
"Commande de familier inconnue: %param. Utilisation: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||||
|
"Unbekannter Tierbefehl: %param. Verwendung: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||||
|
"未知宠物命令: %param。用法: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||||
|
"未知寵物命令: %param。用法: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||||
|
"Comando de mascota desconocido: %param. Uso: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||||
|
"Comando de mascota desconocido: %param. Uso: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
||||||
|
"Неизвестная команда питомца: %param. Использование: pet <aggressive|defensive|passive|stance|attack|follow|stay>"),
|
||||||
|
|
||||||
|
(1729, 'pet_stance_set_success', "Pet stance set to %stance.", 0, 0,
|
||||||
|
"펫 태세가 %stance(으)로 설정되었습니다.",
|
||||||
|
"Position du familier définie sur %stance.",
|
||||||
|
"Tierhaltung auf %stance gesetzt.",
|
||||||
|
"宠物姿态设置为 %stance。",
|
||||||
|
"寵物姿態設置為 %stance。",
|
||||||
|
"Postura de mascota establecida en %stance.",
|
||||||
|
"Postura de mascota establecida en %stance.",
|
||||||
|
"Позиция питомца установлена на %stance."),
|
||||||
|
|
||||||
|
(1730, 'pet_type_pet', "pet", 0, 0,
|
||||||
|
"펫",
|
||||||
|
"familier",
|
||||||
|
"Tier",
|
||||||
|
"宠物",
|
||||||
|
"寵物",
|
||||||
|
"mascota",
|
||||||
|
"mascota",
|
||||||
|
"питомец"),
|
||||||
|
|
||||||
|
(1731, 'pet_type_guardian', "guardian", 0, 0,
|
||||||
|
"수호자",
|
||||||
|
"gardien",
|
||||||
|
"Wächter",
|
||||||
|
"守护者",
|
||||||
|
"守護者",
|
||||||
|
"guardián",
|
||||||
|
"guardián",
|
||||||
|
"защитник"),
|
||||||
|
|
||||||
|
(1732, 'pet_stance_aggressive', "aggressive", 0, 0,
|
||||||
|
"공격적",
|
||||||
|
"agressif",
|
||||||
|
"aggressiv",
|
||||||
|
"进攻",
|
||||||
|
"進攻",
|
||||||
|
"agresivo",
|
||||||
|
"agresivo",
|
||||||
|
"агрессивная"),
|
||||||
|
|
||||||
|
(1733, 'pet_stance_defensive', "defensive", 0, 0,
|
||||||
|
"방어적",
|
||||||
|
"défensif",
|
||||||
|
"defensiv",
|
||||||
|
"防御",
|
||||||
|
"防禦",
|
||||||
|
"defensivo",
|
||||||
|
"defensivo",
|
||||||
|
"защитная"),
|
||||||
|
|
||||||
|
(1734, 'pet_stance_passive', "passive", 0, 0,
|
||||||
|
"수동적",
|
||||||
|
"passif",
|
||||||
|
"passiv",
|
||||||
|
"被动",
|
||||||
|
"被動",
|
||||||
|
"pasivo",
|
||||||
|
"pasivo",
|
||||||
|
"пассивная"),
|
||||||
|
|
||||||
|
(1735, 'pet_stance_unknown', "unknown", 0, 0,
|
||||||
|
"알 수 없음",
|
||||||
|
"inconnu",
|
||||||
|
"unbekannt",
|
||||||
|
"未知",
|
||||||
|
"未知",
|
||||||
|
"desconocido",
|
||||||
|
"desconocido",
|
||||||
|
"неизвестная");
|
||||||
|
|
||||||
|
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES
|
||||||
|
('pet_usage_error', 100),
|
||||||
|
('pet_no_pet_error', 100),
|
||||||
|
('pet_stance_report', 100),
|
||||||
|
('pet_no_target_error', 100),
|
||||||
|
('pet_target_dead_error', 100),
|
||||||
|
('pet_invalid_target_error', 100),
|
||||||
|
('pet_pvp_prohibited_error', 100),
|
||||||
|
('pet_attack_success', 100),
|
||||||
|
('pet_attack_failed', 100),
|
||||||
|
('pet_follow_success', 100),
|
||||||
|
('pet_stay_success', 100),
|
||||||
|
('pet_unknown_command_error', 100),
|
||||||
|
('pet_stance_set_success', 100),
|
||||||
|
('pet_type_pet', 100),
|
||||||
|
('pet_type_guardian', 100),
|
||||||
|
('pet_stance_aggressive', 100),
|
||||||
|
('pet_stance_defensive', 100),
|
||||||
|
('pet_stance_passive', 100),
|
||||||
|
('pet_stance_unknown', 100);
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
DELETE FROM ai_playerbot_texts WHERE name IN ('no_fishing_pole_error');
|
||||||
|
DELETE FROM ai_playerbot_texts_chance WHERE name IN ('no_fishing_pole_error');
|
||||||
|
|
||||||
|
INSERT INTO ai_playerbot_texts (id, name, text, say_type, reply_type, text_loc1, text_loc2, text_loc3, text_loc4, text_loc5, text_loc6, text_loc7, text_loc8) VALUES
|
||||||
|
(1736, 'no_fishing_pole_error', "I don't have a Fishing Pole", 0, 0,
|
||||||
|
"낚싯대가 없습니다",
|
||||||
|
"Je n’ai pas de canne à pêche",
|
||||||
|
"Ich habe keine Angelrute",
|
||||||
|
"我沒有釣魚竿",
|
||||||
|
"我没有钓鱼竿",
|
||||||
|
"No tengo una caña de pescar",
|
||||||
|
"No tengo una caña de pescar",
|
||||||
|
"У меня нет удочки");
|
||||||
|
|
||||||
|
INSERT INTO ai_playerbot_texts_chance (name, probability) VALUES ('no_fishing_pole_error', 100);
|
||||||
@@ -46,7 +46,6 @@
|
|||||||
#include "OutfitAction.h"
|
#include "OutfitAction.h"
|
||||||
#include "PositionAction.h"
|
#include "PositionAction.h"
|
||||||
#include "DropQuestAction.h"
|
#include "DropQuestAction.h"
|
||||||
#include "RaidNaxxActions.h"
|
|
||||||
#include "RandomBotUpdateAction.h"
|
#include "RandomBotUpdateAction.h"
|
||||||
#include "ReachTargetActions.h"
|
#include "ReachTargetActions.h"
|
||||||
#include "ReleaseSpiritAction.h"
|
#include "ReleaseSpiritAction.h"
|
||||||
@@ -64,6 +63,7 @@
|
|||||||
#include "WorldBuffAction.h"
|
#include "WorldBuffAction.h"
|
||||||
#include "XpGainAction.h"
|
#include "XpGainAction.h"
|
||||||
#include "NewRpgAction.h"
|
#include "NewRpgAction.h"
|
||||||
|
#include "FishingAction.h"
|
||||||
#include "CancelChannelAction.h"
|
#include "CancelChannelAction.h"
|
||||||
|
|
||||||
class PlayerbotAI;
|
class PlayerbotAI;
|
||||||
@@ -121,7 +121,7 @@ public:
|
|||||||
creators["shoot"] = &ActionContext::shoot;
|
creators["shoot"] = &ActionContext::shoot;
|
||||||
creators["follow"] = &ActionContext::follow;
|
creators["follow"] = &ActionContext::follow;
|
||||||
creators["move from group"] = &ActionContext::move_from_group;
|
creators["move from group"] = &ActionContext::move_from_group;
|
||||||
creators["flee to master"] = &ActionContext::flee_to_master;
|
creators["flee to group leader"] = &ActionContext::flee_to_group_leader;
|
||||||
creators["runaway"] = &ActionContext::runaway;
|
creators["runaway"] = &ActionContext::runaway;
|
||||||
creators["stay"] = &ActionContext::stay;
|
creators["stay"] = &ActionContext::stay;
|
||||||
creators["sit"] = &ActionContext::sit;
|
creators["sit"] = &ActionContext::sit;
|
||||||
@@ -191,6 +191,11 @@ public:
|
|||||||
creators["buy tabard"] = &ActionContext::buy_tabard;
|
creators["buy tabard"] = &ActionContext::buy_tabard;
|
||||||
creators["guild manage nearby"] = &ActionContext::guild_manage_nearby;
|
creators["guild manage nearby"] = &ActionContext::guild_manage_nearby;
|
||||||
creators["clean quest log"] = &ActionContext::clean_quest_log;
|
creators["clean quest log"] = &ActionContext::clean_quest_log;
|
||||||
|
creators["move near water"] = &ActionContext::move_near_water;
|
||||||
|
creators["go fishing"] = &ActionContext::go_fishing;
|
||||||
|
creators["use fishing bobber"] = &ActionContext::use_fishing_bobber;
|
||||||
|
creators["end master fishing"] = &ActionContext::end_master_fishing;
|
||||||
|
creators["remove bobber strategy"] = &ActionContext::remove_bobber_strategy;
|
||||||
creators["roll"] = &ActionContext::roll_action;
|
creators["roll"] = &ActionContext::roll_action;
|
||||||
creators["cancel channel"] = &ActionContext::cancel_channel;
|
creators["cancel channel"] = &ActionContext::cancel_channel;
|
||||||
|
|
||||||
@@ -318,7 +323,7 @@ private:
|
|||||||
static Action* runaway(PlayerbotAI* botAI) { return new RunAwayAction(botAI); }
|
static Action* runaway(PlayerbotAI* botAI) { return new RunAwayAction(botAI); }
|
||||||
static Action* follow(PlayerbotAI* botAI) { return new FollowAction(botAI); }
|
static Action* follow(PlayerbotAI* botAI) { return new FollowAction(botAI); }
|
||||||
static Action* move_from_group(PlayerbotAI* botAI) { return new MoveFromGroupAction(botAI); }
|
static Action* move_from_group(PlayerbotAI* botAI) { return new MoveFromGroupAction(botAI); }
|
||||||
static Action* flee_to_master(PlayerbotAI* botAI) { return new FleeToMasterAction(botAI); }
|
static Action* flee_to_group_leader(PlayerbotAI* botAI) { return new FleeToGroupLeaderAction(botAI); }
|
||||||
static Action* add_gathering_loot(PlayerbotAI* botAI) { return new AddGatheringLootAction(botAI); }
|
static Action* add_gathering_loot(PlayerbotAI* botAI) { return new AddGatheringLootAction(botAI); }
|
||||||
static Action* add_loot(PlayerbotAI* botAI) { return new AddLootAction(botAI); }
|
static Action* add_loot(PlayerbotAI* botAI) { return new AddLootAction(botAI); }
|
||||||
static Action* add_all_loot(PlayerbotAI* botAI) { return new AddAllLootAction(botAI); }
|
static Action* add_all_loot(PlayerbotAI* botAI) { return new AddAllLootAction(botAI); }
|
||||||
@@ -380,6 +385,11 @@ private:
|
|||||||
static Action* buy_tabard(PlayerbotAI* botAI) { return new BuyTabardAction(botAI); }
|
static Action* buy_tabard(PlayerbotAI* botAI) { return new BuyTabardAction(botAI); }
|
||||||
static Action* guild_manage_nearby(PlayerbotAI* botAI) { return new GuildManageNearbyAction(botAI); }
|
static Action* guild_manage_nearby(PlayerbotAI* botAI) { return new GuildManageNearbyAction(botAI); }
|
||||||
static Action* clean_quest_log(PlayerbotAI* botAI) { return new CleanQuestLogAction(botAI); }
|
static Action* clean_quest_log(PlayerbotAI* botAI) { return new CleanQuestLogAction(botAI); }
|
||||||
|
static Action* move_near_water(PlayerbotAI* botAI) { return new MoveNearWaterAction(botAI); }
|
||||||
|
static Action* go_fishing(PlayerbotAI* botAI) { return new FishingAction(botAI);}
|
||||||
|
static Action* use_fishing_bobber(PlayerbotAI* botAI) { return new UseBobberAction(botAI);}
|
||||||
|
static Action* end_master_fishing(PlayerbotAI* botAI) { return new EndMasterFishingAction(botAI); }
|
||||||
|
static Action* remove_bobber_strategy(PlayerbotAI* botAI) { return new RemoveBobberStrategyAction(botAI); }
|
||||||
static Action* roll_action(PlayerbotAI* botAI) { return new RollAction(botAI); }
|
static Action* roll_action(PlayerbotAI* botAI) { return new RollAction(botAI); }
|
||||||
|
|
||||||
// BG Tactics
|
// BG Tactics
|
||||||
@@ -49,7 +49,7 @@ bool AcceptInvitationAction::Execute(Event event)
|
|||||||
if (sRandomPlayerbotMgr->IsRandomBot(bot))
|
if (sRandomPlayerbotMgr->IsRandomBot(bot))
|
||||||
botAI->SetMaster(inviter);
|
botAI->SetMaster(inviter);
|
||||||
// else
|
// else
|
||||||
// sPlayerbotDbStore->Save(botAI);
|
// sPlayerbotRepository->Save(botAI);
|
||||||
|
|
||||||
botAI->ResetStrategies();
|
botAI->ResetStrategies();
|
||||||
botAI->ChangeStrategy("+follow,-lfg,-bg", BOT_STATE_NON_COMBAT);
|
botAI->ChangeStrategy("+follow,-lfg,-bg", BOT_STATE_NON_COMBAT);
|
||||||
@@ -59,7 +59,7 @@ bool AcceptInvitationAction::Execute(Event event)
|
|||||||
|
|
||||||
if (sPlayerbotAIConfig->summonWhenGroup && bot->GetDistance(inviter) > sPlayerbotAIConfig->sightDistance)
|
if (sPlayerbotAIConfig->summonWhenGroup && bot->GetDistance(inviter) > sPlayerbotAIConfig->sightDistance)
|
||||||
{
|
{
|
||||||
Teleport(inviter, bot);
|
Teleport(inviter, bot, true);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,5 @@ bool AcceptResurrectAction::Execute(Event event)
|
|||||||
packet << uint8(1); // accept
|
packet << uint8(1); // accept
|
||||||
bot->GetSession()->HandleResurrectResponseOpcode(packet); // queue the packet to get around race condition
|
bot->GetSession()->HandleResurrectResponseOpcode(packet); // queue the packet to get around race condition
|
||||||
|
|
||||||
botAI->ChangeEngine(BOT_STATE_NON_COMBAT);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,14 @@ bool ReachAreaTriggerAction::Execute(Event event)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bot->GetMotionMaster()->MovePoint(at->map, at->x, at->y, at->z);
|
bot->GetMotionMaster()->MovePoint(
|
||||||
|
/*id*/ at->map,
|
||||||
|
/*coords*/ at->x, at->y, at->z,
|
||||||
|
/*forcedMovement*/ FORCED_MOVEMENT_NONE,
|
||||||
|
/*speed*/ 0.0f, // default speed (not handled here)
|
||||||
|
/*orientation*/ 0.0f, // keep current orientation of bot
|
||||||
|
/*generatePath*/ true, // true => terrain path (2d mmap); false => straight spline (3d vmap)
|
||||||
|
/*forceDestination*/ false);
|
||||||
|
|
||||||
float distance = bot->GetDistance(at->x, at->y, at->z);
|
float distance = bot->GetDistance(at->x, at->y, at->z);
|
||||||
float delay = 1000.0f * distance / bot->GetSpeed(MOVE_RUN) + sPlayerbotAIConfig->reactDelay;
|
float delay = 1000.0f * distance / bot->GetSpeed(MOVE_RUN) + sPlayerbotAIConfig->reactDelay;
|
||||||
@@ -15,20 +15,19 @@
|
|||||||
#include "SharedDefines.h"
|
#include "SharedDefines.h"
|
||||||
#include "Unit.h"
|
#include "Unit.h"
|
||||||
|
|
||||||
bool AttackAction::Execute(Event event)
|
bool AttackAction::Execute(Event /*event*/)
|
||||||
{
|
{
|
||||||
Unit* target = GetTarget();
|
Unit* target = GetTarget();
|
||||||
if (!target)
|
if (!target)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!target->IsInWorld())
|
if (!target->IsInWorld())
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
return Attack(target);
|
return Attack(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AttackMyTargetAction::Execute(Event event)
|
bool AttackMyTargetAction::Execute(Event /*event*/)
|
||||||
{
|
{
|
||||||
Player* master = GetMaster();
|
Player* master = GetMaster();
|
||||||
if (!master)
|
if (!master)
|
||||||
@@ -51,7 +50,7 @@ bool AttackMyTargetAction::Execute(Event event)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
|
||||||
{
|
{
|
||||||
Unit* oldTarget = context->GetValue<Unit*>("current target")->Get();
|
Unit* oldTarget = context->GetValue<Unit*>("current target")->Get();
|
||||||
bool shouldMelee = bot->IsWithinMeleeRange(target) || botAI->IsMelee(bot);
|
bool shouldMelee = bot->IsWithinMeleeRange(target) || botAI->IsMelee(bot);
|
||||||
@@ -81,12 +80,15 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
|||||||
{
|
{
|
||||||
if (verbose)
|
if (verbose)
|
||||||
botAI->TellError(std::string(target->GetName()) + " is no longer in the world.");
|
botAI->TellError(std::string(target->GetName()) + " is no longer in the world.");
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((sPlayerbotAIConfig->IsInPvpProhibitedZone(bot->GetZoneId()) ||
|
// Check if bot OR target is in prohibited zone/area (skip for duels)
|
||||||
sPlayerbotAIConfig->IsInPvpProhibitedArea(bot->GetAreaId()))
|
if ((target->IsPlayer() || target->IsPet()) &&
|
||||||
&& (target->IsPlayer() || target->IsPet()))
|
(!bot->duel || bot->duel->Opponent != target) &&
|
||||||
|
(sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId()) ||
|
||||||
|
sPlayerbotAIConfig->IsPvpProhibited(target->GetZoneId(), target->GetAreaId())))
|
||||||
{
|
{
|
||||||
if (verbose)
|
if (verbose)
|
||||||
botAI->TellError("I cannot attack other players in PvP prohibited areas.");
|
botAI->TellError("I cannot attack other players in PvP prohibited areas.");
|
||||||
@@ -98,6 +100,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
|||||||
{
|
{
|
||||||
if (verbose)
|
if (verbose)
|
||||||
botAI->TellError(std::string(target->GetName()) + " is friendly to me.");
|
botAI->TellError(std::string(target->GetName()) + " is friendly to me.");
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,6 +108,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
|||||||
{
|
{
|
||||||
if (verbose)
|
if (verbose)
|
||||||
botAI->TellError(std::string(target->GetName()) + " is dead.");
|
botAI->TellError(std::string(target->GetName()) + " is dead.");
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,6 +116,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
|||||||
{
|
{
|
||||||
if (verbose)
|
if (verbose)
|
||||||
botAI->TellError(std::string(target->GetName()) + " is not in my sight.");
|
botAI->TellError(std::string(target->GetName()) + " is not in my sight.");
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,6 +124,7 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
|||||||
{
|
{
|
||||||
if (verbose)
|
if (verbose)
|
||||||
botAI->TellError("I am already attacking " + std::string(target->GetName()) + ".");
|
botAI->TellError("I am already attacking " + std::string(target->GetName()) + ".");
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,25 +159,28 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
|||||||
bot->StopMoving();
|
bot->StopMoving();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsMovingAllowed() && !bot->HasInArc(CAST_ANGLE_IN_FRONT, target))
|
if (botAI->CanMove() && !bot->HasInArc(CAST_ANGLE_IN_FRONT, target))
|
||||||
{
|
|
||||||
sServerFacade->SetFacingTo(bot, target);
|
sServerFacade->SetFacingTo(bot, target);
|
||||||
}
|
|
||||||
botAI->ChangeEngine(BOT_STATE_COMBAT);
|
botAI->ChangeEngine(BOT_STATE_COMBAT);
|
||||||
|
|
||||||
bot->Attack(target, shouldMelee);
|
bot->Attack(target, shouldMelee);
|
||||||
/* prevent pet dead immediately in group */
|
/* prevent pet dead immediately in group */
|
||||||
// if (bot->GetMap()->IsDungeon() && bot->GetGroup() && !target->IsInCombat()) {
|
// if (bot->GetMap()->IsDungeon() && bot->GetGroup() && !target->IsInCombat())
|
||||||
|
// {
|
||||||
// with_pet = false;
|
// with_pet = false;
|
||||||
// }
|
// }
|
||||||
// if (Pet* pet = bot->GetPet())
|
// if (Pet* pet = bot->GetPet())
|
||||||
// {
|
// {
|
||||||
// if (with_pet) {
|
// if (with_pet)
|
||||||
|
// {
|
||||||
// pet->SetReactState(REACT_DEFENSIVE);
|
// pet->SetReactState(REACT_DEFENSIVE);
|
||||||
// pet->SetTarget(target->GetGUID());
|
// pet->SetTarget(target->GetGUID());
|
||||||
// pet->GetCharmInfo()->SetIsCommandAttack(true);
|
// pet->GetCharmInfo()->SetIsCommandAttack(true);
|
||||||
// pet->AI()->AttackStart(target);
|
// pet->AI()->AttackStart(target);
|
||||||
// } else {
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
// pet->SetReactState(REACT_PASSIVE);
|
// pet->SetReactState(REACT_PASSIVE);
|
||||||
// pet->GetCharmInfo()->SetIsCommandFollow(true);
|
// pet->GetCharmInfo()->SetIsCommandFollow(true);
|
||||||
// pet->GetCharmInfo()->IsReturning();
|
// pet->GetCharmInfo()->IsReturning();
|
||||||
@@ -182,4 +191,4 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
|||||||
|
|
||||||
bool AttackDuelOpponentAction::isUseful() { return AI_VALUE(Unit*, "duel target"); }
|
bool AttackDuelOpponentAction::isUseful() { return AI_VALUE(Unit*, "duel target"); }
|
||||||
|
|
||||||
bool AttackDuelOpponentAction::Execute(Event event) { return Attack(AI_VALUE(Unit*, "duel target")); }
|
bool AttackDuelOpponentAction::Execute(Event /*event*/) { return Attack(AI_VALUE(Unit*, "duel target")); }
|
||||||
@@ -75,36 +75,79 @@ void AutoMaintenanceOnLevelupAction::LearnSpells(std::ostringstream* out)
|
|||||||
void AutoMaintenanceOnLevelupAction::LearnTrainerSpells(std::ostringstream* out)
|
void AutoMaintenanceOnLevelupAction::LearnTrainerSpells(std::ostringstream* out)
|
||||||
{
|
{
|
||||||
PlayerbotFactory factory(bot, bot->GetLevel());
|
PlayerbotFactory factory(bot, bot->GetLevel());
|
||||||
|
factory.InitSkills();
|
||||||
factory.InitClassSpells();
|
factory.InitClassSpells();
|
||||||
factory.InitAvailableSpells();
|
factory.InitAvailableSpells();
|
||||||
factory.InitSkills();
|
|
||||||
factory.InitPet();
|
factory.InitPet();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AutoMaintenanceOnLevelupAction::LearnQuestSpells(std::ostringstream* out)
|
void AutoMaintenanceOnLevelupAction::LearnQuestSpells(std::ostringstream* out)
|
||||||
{
|
{
|
||||||
// CreatureTemplate const* co = sCreatureStorage.LookupEntry<CreatureTemplate>(id);
|
|
||||||
ObjectMgr::QuestMap const& questTemplates = sObjectMgr->GetQuestTemplates();
|
ObjectMgr::QuestMap const& questTemplates = sObjectMgr->GetQuestTemplates();
|
||||||
for (ObjectMgr::QuestMap::const_iterator i = questTemplates.begin(); i != questTemplates.end(); ++i)
|
for (ObjectMgr::QuestMap::const_iterator i = questTemplates.begin(); i != questTemplates.end(); ++i)
|
||||||
{
|
{
|
||||||
//uint32 questId = i->first; //not used, line marked for removal.
|
|
||||||
Quest const* quest = i->second;
|
Quest const* quest = i->second;
|
||||||
|
|
||||||
if (!quest->GetRequiredClasses() || quest->IsRepeatable() || quest->GetMinLevel() < 10)
|
// only process class-specific quests to learn class-related spells, cuz
|
||||||
|
// we don't want all these bunch of entries to be handled!
|
||||||
|
if (!quest->GetRequiredClasses())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!bot->SatisfyQuestClass(quest, false) || quest->GetMinLevel() > bot->GetLevel() ||
|
// skip quests that are repeatable, too low level, or above bots' level
|
||||||
!bot->SatisfyQuestRace(quest, false))
|
if (quest->IsRepeatable() || quest->GetMinLevel() < 10 || quest->GetMinLevel() > bot->GetLevel())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (quest->GetRewSpellCast() > 0)
|
// skip if bot doesnt satisfy class, race, or skill requirements
|
||||||
|
if (!bot->SatisfyQuestClass(quest, false) || !bot->SatisfyQuestRace(quest, false) ||
|
||||||
|
!bot->SatisfyQuestSkill(quest, false))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// use the same logic and impl from Player::learnQuestRewardedSpells
|
||||||
|
|
||||||
|
int32 spellId = quest->GetRewSpellCast();
|
||||||
|
if (!spellId)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||||
|
if (!spellInfo)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// xinef: find effect with learn spell and check if we have this spell
|
||||||
|
bool found = false;
|
||||||
|
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
||||||
{
|
{
|
||||||
LearnSpell(quest->GetRewSpellCast(), out);
|
if (spellInfo->Effects[i].Effect == SPELL_EFFECT_LEARN_SPELL && 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 (triggeredInfo->Effects[0].Effect == SPELL_EFFECT_TRADE_SKILL)
|
||||||
|
break; // pussywizard: break and not cast the spell (found is false)
|
||||||
|
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (quest->GetRewSpell() > 0)
|
|
||||||
|
// xinef: we know the spell, continue
|
||||||
|
if (!found)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
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();
|
||||||
|
if (rewSpellId)
|
||||||
{
|
{
|
||||||
LearnSpell(quest->GetRewSpell(), out);
|
if (SpellInfo const* rewSpellInfo = sSpellMgr->GetSpellInfo(rewSpellId))
|
||||||
|
{
|
||||||
|
*out << FormatSpell(rewSpellInfo) << ", ";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*out << FormatSpell(spellInfo) << ", ";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,39 +164,6 @@ std::string const AutoMaintenanceOnLevelupAction::FormatSpell(SpellInfo const* s
|
|||||||
return out.str();
|
return out.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AutoMaintenanceOnLevelupAction::LearnSpell(uint32 spellId, std::ostringstream* out)
|
|
||||||
{
|
|
||||||
SpellInfo const* proto = sSpellMgr->GetSpellInfo(spellId);
|
|
||||||
if (!proto)
|
|
||||||
return;
|
|
||||||
|
|
||||||
bool learned = false;
|
|
||||||
for (uint8 j = 0; j < 3; ++j)
|
|
||||||
{
|
|
||||||
if (proto->Effects[j].Effect == SPELL_EFFECT_LEARN_SPELL)
|
|
||||||
{
|
|
||||||
uint32 learnedSpell = proto->Effects[j].TriggerSpell;
|
|
||||||
|
|
||||||
if (!bot->HasSpell(learnedSpell))
|
|
||||||
{
|
|
||||||
bot->learnSpell(learnedSpell);
|
|
||||||
*out << FormatSpell(sSpellMgr->GetSpellInfo(learnedSpell)) << ", ";
|
|
||||||
}
|
|
||||||
|
|
||||||
learned = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!learned)
|
|
||||||
{
|
|
||||||
if (!bot->HasSpell(spellId))
|
|
||||||
{
|
|
||||||
bot->learnSpell(spellId);
|
|
||||||
*out << FormatSpell(proto) << ", ";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AutoMaintenanceOnLevelupAction::AutoUpgradeEquip()
|
void AutoMaintenanceOnLevelupAction::AutoUpgradeEquip()
|
||||||
{
|
{
|
||||||
if (!sPlayerbotAIConfig->autoUpgradeEquip || !sRandomPlayerbotMgr->IsRandomBot(bot))
|
if (!sPlayerbotAIConfig->autoUpgradeEquip || !sRandomPlayerbotMgr->IsRandomBot(bot))
|
||||||
@@ -176,4 +186,3 @@ void AutoMaintenanceOnLevelupAction::AutoUpgradeEquip()
|
|||||||
factory.InitEquipment(true);
|
factory.InitEquipment(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +28,6 @@ protected:
|
|||||||
void LearnSpells(std::ostringstream* out);
|
void LearnSpells(std::ostringstream* out);
|
||||||
void LearnTrainerSpells(std::ostringstream* out);
|
void LearnTrainerSpells(std::ostringstream* out);
|
||||||
void LearnQuestSpells(std::ostringstream* out);
|
void LearnQuestSpells(std::ostringstream* out);
|
||||||
void LearnSpell(uint32 spellId, std::ostringstream* out);
|
|
||||||
std::string const FormatSpell(SpellInfo const* sInfo);
|
std::string const FormatSpell(SpellInfo const* sInfo);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ bool BankAction::Execute(Event event)
|
|||||||
for (GuidVector::iterator i = npcs.begin(); i != npcs.end(); i++)
|
for (GuidVector::iterator i = npcs.begin(); i != npcs.end(); i++)
|
||||||
{
|
{
|
||||||
Unit* npc = botAI->GetUnit(*i);
|
Unit* npc = botAI->GetUnit(*i);
|
||||||
if (!npc || !npc->HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_BANKER))
|
if (!npc || !npc->HasNpcFlag(UNIT_NPC_FLAG_BANKER))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
return ExecuteBank(text, npc);
|
return ExecuteBank(text, npc);
|
||||||
@@ -176,6 +176,7 @@ bool BGJoinAction::gatherArenaTeam(ArenaType type)
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
memberBotAI->Reset();
|
memberBotAI->Reset();
|
||||||
|
member->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP);
|
||||||
member->TeleportTo(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), 0);
|
member->TeleportTo(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), 0);
|
||||||
|
|
||||||
LOG_INFO("playerbots", "Bot {} <{}>: Member of <{}>", member->GetGUID().ToString().c_str(),
|
LOG_INFO("playerbots", "Bot {} <{}>: Member of <{}>", member->GetGUID().ToString().c_str(),
|
||||||
@@ -4,10 +4,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "BattleGroundTactics.h"
|
#include "BattleGroundTactics.h"
|
||||||
#include "BattleGroundJoinAction.h"
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include "ArenaTeam.h"
|
#include "ArenaTeam.h"
|
||||||
#include "ArenaTeamMgr.h"
|
#include "ArenaTeamMgr.h"
|
||||||
|
#include "BattleGroundJoinAction.h"
|
||||||
#include "Battleground.h"
|
#include "Battleground.h"
|
||||||
#include "BattlegroundAB.h"
|
#include "BattlegroundAB.h"
|
||||||
#include "BattlegroundAV.h"
|
#include "BattlegroundAV.h"
|
||||||
@@ -22,11 +24,12 @@
|
|||||||
#include "BattlegroundSA.h"
|
#include "BattlegroundSA.h"
|
||||||
#include "BattlegroundWS.h"
|
#include "BattlegroundWS.h"
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
|
#include "GameObject.h"
|
||||||
#include "IVMapMgr.h"
|
#include "IVMapMgr.h"
|
||||||
|
#include "PathGenerator.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "PositionValue.h"
|
#include "PositionValue.h"
|
||||||
#include "PvpTriggers.h"
|
#include "PvpTriggers.h"
|
||||||
#include "PathGenerator.h"
|
|
||||||
#include "ServerFacade.h"
|
#include "ServerFacade.h"
|
||||||
#include "Vehicle.h"
|
#include "Vehicle.h"
|
||||||
|
|
||||||
@@ -1754,7 +1757,7 @@ bool BGTactics::moveToStart(bool force)
|
|||||||
WS_WAITING_POS_ALLIANCE_2.GetPositionY() + frand(-4.0f, 4.0f),
|
WS_WAITING_POS_ALLIANCE_2.GetPositionY() + frand(-4.0f, 4.0f),
|
||||||
WS_WAITING_POS_ALLIANCE_2.GetPositionZ());
|
WS_WAITING_POS_ALLIANCE_2.GetPositionZ());
|
||||||
}
|
}
|
||||||
else // BB_WSG_WAIT_SPOT_SPAWN
|
else // BB_WSG_WAIT_SPOT_SPAWN
|
||||||
{
|
{
|
||||||
if (bot->GetTeamId() == TEAM_HORDE)
|
if (bot->GetTeamId() == TEAM_HORDE)
|
||||||
MoveTo(bg->GetMapId(), WS_WAITING_POS_HORDE_3.GetPositionX() + frand(-10.0f, 10.0f),
|
MoveTo(bg->GetMapId(), WS_WAITING_POS_HORDE_3.GetPositionX() + frand(-10.0f, 10.0f),
|
||||||
@@ -1894,13 +1897,13 @@ bool BGTactics::selectObjective(bool reset)
|
|||||||
bool isDefender = role < defendersProhab;
|
bool isDefender = role < defendersProhab;
|
||||||
bool isAdvanced = !isDefender && role > 8;
|
bool isAdvanced = !isDefender && role > 8;
|
||||||
|
|
||||||
const auto& attackObjectives =
|
auto const& attackObjectives =
|
||||||
(team == TEAM_HORDE) ? AV_AttackObjectives_Horde : AV_AttackObjectives_Alliance;
|
(team == TEAM_HORDE) ? AV_AttackObjectives_Horde : AV_AttackObjectives_Alliance;
|
||||||
const auto& defendObjectives =
|
auto const& defendObjectives =
|
||||||
(team == TEAM_HORDE) ? AV_DefendObjectives_Horde : AV_DefendObjectives_Alliance;
|
(team == TEAM_HORDE) ? AV_DefendObjectives_Horde : AV_DefendObjectives_Alliance;
|
||||||
|
|
||||||
uint32 destroyedNodes = 0;
|
uint32 destroyedNodes = 0;
|
||||||
for (const auto& [nodeId, _] : defendObjectives)
|
for (auto const& [nodeId, _] : defendObjectives)
|
||||||
if (av->GetAVNodeInfo(nodeId).State == POINT_DESTROYED)
|
if (av->GetAVNodeInfo(nodeId).State == POINT_DESTROYED)
|
||||||
destroyedNodes++;
|
destroyedNodes++;
|
||||||
|
|
||||||
@@ -2000,7 +2003,7 @@ bool BGTactics::selectObjective(bool reset)
|
|||||||
std::vector<GameObject*> contestedObjectives;
|
std::vector<GameObject*> contestedObjectives;
|
||||||
std::vector<GameObject*> availableObjectives;
|
std::vector<GameObject*> availableObjectives;
|
||||||
|
|
||||||
for (const auto& [nodeId, goId] : defendObjectives)
|
for (auto const& [nodeId, goId] : defendObjectives)
|
||||||
{
|
{
|
||||||
const BG_AV_NodeInfo& node = av->GetAVNodeInfo(nodeId);
|
const BG_AV_NodeInfo& node = av->GetAVNodeInfo(nodeId);
|
||||||
if (node.State == POINT_DESTROYED)
|
if (node.State == POINT_DESTROYED)
|
||||||
@@ -2026,7 +2029,7 @@ bool BGTactics::selectObjective(bool reset)
|
|||||||
if (!BgObjective)
|
if (!BgObjective)
|
||||||
{
|
{
|
||||||
uint32 towersDown = 0;
|
uint32 towersDown = 0;
|
||||||
for (const auto& [nodeId, _] : attackObjectives)
|
for (auto const& [nodeId, _] : attackObjectives)
|
||||||
if (av->GetAVNodeInfo(nodeId).State == POINT_DESTROYED)
|
if (av->GetAVNodeInfo(nodeId).State == POINT_DESTROYED)
|
||||||
towersDown++;
|
towersDown++;
|
||||||
|
|
||||||
@@ -2053,7 +2056,7 @@ bool BGTactics::selectObjective(bool reset)
|
|||||||
{
|
{
|
||||||
std::vector<GameObject*> candidates;
|
std::vector<GameObject*> candidates;
|
||||||
|
|
||||||
for (const auto& [nodeId, goId] : attackObjectives)
|
for (auto const& [nodeId, goId] : attackObjectives)
|
||||||
{
|
{
|
||||||
const BG_AV_NodeInfo& node = av->GetAVNodeInfo(nodeId);
|
const BG_AV_NodeInfo& node = av->GetAVNodeInfo(nodeId);
|
||||||
GameObject* go = bg->GetBGObject(goId);
|
GameObject* go = bg->GetBGObject(goId);
|
||||||
@@ -2105,13 +2108,13 @@ bool BGTactics::selectObjective(bool reset)
|
|||||||
Position objPos = BgObjective->GetPosition();
|
Position objPos = BgObjective->GetPosition();
|
||||||
|
|
||||||
Optional<uint8> linkedNodeId;
|
Optional<uint8> linkedNodeId;
|
||||||
for (const auto& [nodeId, goId] : attackObjectives)
|
for (auto const& [nodeId, goId] : attackObjectives)
|
||||||
if (bg->GetBGObject(goId) == BgObjective)
|
if (bg->GetBGObject(goId) == BgObjective)
|
||||||
linkedNodeId = nodeId;
|
linkedNodeId = nodeId;
|
||||||
|
|
||||||
if (!linkedNodeId)
|
if (!linkedNodeId)
|
||||||
{
|
{
|
||||||
for (const auto& [nodeId, goId] : defendObjectives)
|
for (auto const& [nodeId, goId] : defendObjectives)
|
||||||
if (bg->GetBGObject(goId) == BgObjective)
|
if (bg->GetBGObject(goId) == BgObjective)
|
||||||
linkedNodeId = nodeId;
|
linkedNodeId = nodeId;
|
||||||
}
|
}
|
||||||
@@ -2543,7 +2546,7 @@ bool BGTactics::selectObjective(bool reset)
|
|||||||
float bestDist = FLT_MAX;
|
float bestDist = FLT_MAX;
|
||||||
uint32 bestTrigger = 0;
|
uint32 bestTrigger = 0;
|
||||||
|
|
||||||
for (const auto& [nodeId, _, areaTrigger] : EY_AttackObjectives)
|
for (auto const& [nodeId, _, areaTrigger] : EY_AttackObjectives)
|
||||||
{
|
{
|
||||||
if (!IsOwned(nodeId))
|
if (!IsOwned(nodeId))
|
||||||
continue;
|
continue;
|
||||||
@@ -2610,7 +2613,7 @@ bool BGTactics::selectObjective(bool reset)
|
|||||||
// --- PRIORITY 2: Nearby unowned contested node ---
|
// --- PRIORITY 2: Nearby unowned contested node ---
|
||||||
if (!foundObjective)
|
if (!foundObjective)
|
||||||
{
|
{
|
||||||
for (const auto& [nodeId, _, __] : EY_AttackObjectives)
|
for (auto const& [nodeId, _, __] : EY_AttackObjectives)
|
||||||
{
|
{
|
||||||
if (IsOwned(nodeId))
|
if (IsOwned(nodeId))
|
||||||
continue;
|
continue;
|
||||||
@@ -2711,7 +2714,7 @@ bool BGTactics::selectObjective(bool reset)
|
|||||||
if (!foundObjective && strategy == EY_STRATEGY_FLAG_FOCUS)
|
if (!foundObjective && strategy == EY_STRATEGY_FLAG_FOCUS)
|
||||||
{
|
{
|
||||||
bool ownsAny = false;
|
bool ownsAny = false;
|
||||||
for (const auto& [nodeId, _, __] : EY_AttackObjectives)
|
for (auto const& [nodeId, _, __] : EY_AttackObjectives)
|
||||||
{
|
{
|
||||||
if (IsOwned(nodeId))
|
if (IsOwned(nodeId))
|
||||||
{
|
{
|
||||||
@@ -2739,7 +2742,7 @@ bool BGTactics::selectObjective(bool reset)
|
|||||||
float bestDist = FLT_MAX;
|
float bestDist = FLT_MAX;
|
||||||
Optional<uint32> bestNode;
|
Optional<uint32> bestNode;
|
||||||
|
|
||||||
for (const auto& [nodeId, _, __] : EY_AttackObjectives)
|
for (auto const& [nodeId, _, __] : EY_AttackObjectives)
|
||||||
{
|
{
|
||||||
if (IsOwned(nodeId))
|
if (IsOwned(nodeId))
|
||||||
continue;
|
continue;
|
||||||
@@ -2974,7 +2977,7 @@ bool BGTactics::selectObjective(bool reset)
|
|||||||
uint32 len = end(IC_AttackObjectives) - begin(IC_AttackObjectives);
|
uint32 len = end(IC_AttackObjectives) - begin(IC_AttackObjectives);
|
||||||
for (uint32 i = 0; i < len; i++)
|
for (uint32 i = 0; i < len; i++)
|
||||||
{
|
{
|
||||||
const auto& objective =
|
auto const& objective =
|
||||||
IC_AttackObjectives[(i + role) %
|
IC_AttackObjectives[(i + role) %
|
||||||
len]; // use role to determine which objective checked first
|
len]; // use role to determine which objective checked first
|
||||||
if (isleOfConquestBG->GetICNodePoint(objective.first).nodeState != NODE_STATE_CONTROLLED_H)
|
if (isleOfConquestBG->GetICNodePoint(objective.first).nodeState != NODE_STATE_CONTROLLED_H)
|
||||||
@@ -3126,7 +3129,7 @@ bool BGTactics::selectObjective(bool reset)
|
|||||||
uint32 len = end(IC_AttackObjectives) - begin(IC_AttackObjectives);
|
uint32 len = end(IC_AttackObjectives) - begin(IC_AttackObjectives);
|
||||||
for (uint32 i = 0; i < len; i++)
|
for (uint32 i = 0; i < len; i++)
|
||||||
{
|
{
|
||||||
const auto& objective =
|
auto const& objective =
|
||||||
IC_AttackObjectives[(i + role) %
|
IC_AttackObjectives[(i + role) %
|
||||||
len]; // use role to determine which objective checked first
|
len]; // use role to determine which objective checked first
|
||||||
if (isleOfConquestBG->GetICNodePoint(objective.first).nodeState != NODE_STATE_CONTROLLED_H)
|
if (isleOfConquestBG->GetICNodePoint(objective.first).nodeState != NODE_STATE_CONTROLLED_H)
|
||||||
@@ -3365,12 +3368,12 @@ bool BGTactics::resetObjective()
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Adjust role-change chance based on battleground type
|
// Adjust role-change chance based on battleground type
|
||||||
uint32 oddsToChangeRole = 1; // default low
|
uint32 oddsToChangeRole = 1; // default low
|
||||||
BattlegroundTypeId bgType = bg->GetBgTypeID();
|
BattlegroundTypeId bgType = bg->GetBgTypeID();
|
||||||
|
|
||||||
if (bgType == BATTLEGROUND_WS)
|
if (bgType == BATTLEGROUND_WS)
|
||||||
oddsToChangeRole = 2;
|
oddsToChangeRole = 2;
|
||||||
else if (bgType == BATTLEGROUND_EY || bgType == BATTLEGROUND_IC || bgType == BATTLEGROUND_AB)
|
else if (bgType == BATTLEGROUND_EY || bgType == BATTLEGROUND_IC || bgType == BATTLEGROUND_AB)
|
||||||
oddsToChangeRole = 1;
|
oddsToChangeRole = 1;
|
||||||
else if (bgType == BATTLEGROUND_AV)
|
else if (bgType == BATTLEGROUND_AV)
|
||||||
oddsToChangeRole = 0;
|
oddsToChangeRole = 0;
|
||||||
@@ -3578,6 +3581,16 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
|||||||
GuidVector closePlayers;
|
GuidVector closePlayers;
|
||||||
float flagRange = 0.0f;
|
float flagRange = 0.0f;
|
||||||
|
|
||||||
|
// Eye of the Storm helpers used later when handling capture positioning
|
||||||
|
BattlegroundEY* eyeBg = nullptr;
|
||||||
|
GameObject* eyCenterFlag = nullptr;
|
||||||
|
if (bgType == BATTLEGROUND_EY)
|
||||||
|
{
|
||||||
|
eyeBg = static_cast<BattlegroundEY*>(bg);
|
||||||
|
if (eyeBg)
|
||||||
|
eyCenterFlag = eyeBg->GetBGObject(BG_EY_OBJECT_FLAG_NETHERSTORM);
|
||||||
|
}
|
||||||
|
|
||||||
// Set up appropriate search ranges and object lists based on BG type
|
// Set up appropriate search ranges and object lists based on BG type
|
||||||
switch (bgType)
|
switch (bgType)
|
||||||
{
|
{
|
||||||
@@ -3607,27 +3620,82 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
|||||||
if (closeObjects.empty())
|
if (closeObjects.empty())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
auto keepStationaryWhileCapturing = [&](CurrentSpellTypes spellType)
|
||||||
|
{
|
||||||
|
Spell* currentSpell = bot->GetCurrentSpell(spellType);
|
||||||
|
if (!currentSpell || !currentSpell->m_spellInfo || currentSpell->m_spellInfo->Id != SPELL_CAPTURE_BANNER)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// If the capture target is no longer available (another bot already captured it), stop channeling
|
||||||
|
if (GameObject* targetFlag = currentSpell->m_targets.GetGOTarget())
|
||||||
|
{
|
||||||
|
if (!targetFlag->isSpawned() || targetFlag->GetGoState() != GO_STATE_READY)
|
||||||
|
{
|
||||||
|
bot->InterruptNonMeleeSpells(true);
|
||||||
|
resetObjective();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bot->InterruptNonMeleeSpells(true);
|
||||||
|
resetObjective();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bot->IsMounted())
|
||||||
|
{
|
||||||
|
bot->RemoveAurasByType(SPELL_AURA_MOUNTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bot->IsInDisallowedMountForm())
|
||||||
|
{
|
||||||
|
bot->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bot->isMoving())
|
||||||
|
{
|
||||||
|
bot->StopMoving();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// If we are already channeling the capture spell, keep the bot stationary and dismounted
|
||||||
|
if (keepStationaryWhileCapturing(CURRENT_CHANNELED_SPELL) || keepStationaryWhileCapturing(CURRENT_GENERIC_SPELL))
|
||||||
|
return true;
|
||||||
|
|
||||||
// First identify which flag/base we're trying to interact with
|
// First identify which flag/base we're trying to interact with
|
||||||
GameObject* targetFlag = nullptr;
|
GameObject* targetFlag = nullptr;
|
||||||
for (ObjectGuid const guid : closeObjects)
|
for (ObjectGuid const guid : closeObjects)
|
||||||
{
|
{
|
||||||
GameObject* go = botAI->GetGameObject(guid);
|
GameObject* go = botAI->GetGameObject(guid);
|
||||||
if (!go)
|
if (!go)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool const isEyCenterFlag = eyeBg && eyCenterFlag && eyCenterFlag->GetGUID() == go->GetGUID();
|
||||||
|
|
||||||
// Check if this object is a valid capture target
|
// Check if this object is a valid capture target
|
||||||
std::vector<uint32>::const_iterator f = find(vFlagIds.begin(), vFlagIds.end(), go->GetEntry());
|
std::vector<uint32>::const_iterator f = std::find(vFlagIds.begin(), vFlagIds.end(), go->GetEntry());
|
||||||
if (f == vFlagIds.end())
|
if (f == vFlagIds.end() && !isEyCenterFlag)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Verify the object is active and ready
|
// Verify the object is active and ready
|
||||||
if (!go->isSpawned() || go->GetGoState() != GO_STATE_READY)
|
if (!go->isSpawned() || go->GetGoState() != GO_STATE_READY)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if we're in range (using double range for enemy detection)
|
// Check if we're in range (using double range for enemy detection)
|
||||||
float const dist = bot->GetDistance(go);
|
float const dist = bot->GetDistance(go);
|
||||||
if (flagRange && dist > flagRange * 2.0f)
|
if (flagRange && dist > flagRange * 2.0f)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
targetFlag = go;
|
targetFlag = go;
|
||||||
break;
|
break;
|
||||||
@@ -3655,7 +3723,7 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if friendly players are already capturing
|
// Check if friendly players are already capturing
|
||||||
if (!closePlayers.empty())
|
if (!closePlayers.empty() && bgType != BATTLEGROUND_EY)
|
||||||
{
|
{
|
||||||
// Track number of friendly players capturing and the closest one
|
// Track number of friendly players capturing and the closest one
|
||||||
uint32 numCapturing = 0;
|
uint32 numCapturing = 0;
|
||||||
@@ -3664,14 +3732,17 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
|||||||
{
|
{
|
||||||
if (Unit* pFriend = botAI->GetUnit(guid))
|
if (Unit* pFriend = botAI->GetUnit(guid))
|
||||||
{
|
{
|
||||||
// Check if they're casting the capture spell
|
// Check if they're casting or channeling the capture spell
|
||||||
if (Spell* spell = pFriend->GetCurrentSpell(CURRENT_GENERIC_SPELL))
|
Spell* spell = pFriend->GetCurrentSpell(CURRENT_GENERIC_SPELL);
|
||||||
|
if (!spell)
|
||||||
{
|
{
|
||||||
if (spell->m_spellInfo->Id == SPELL_CAPTURE_BANNER)
|
spell = pFriend->GetCurrentSpell(CURRENT_CHANNELED_SPELL);
|
||||||
{
|
}
|
||||||
numCapturing++;
|
|
||||||
capturingPlayer = pFriend;
|
if (spell && spell->m_spellInfo && spell->m_spellInfo->Id == SPELL_CAPTURE_BANNER)
|
||||||
}
|
{
|
||||||
|
numCapturing++;
|
||||||
|
capturingPlayer = pFriend;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3704,9 +3775,11 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
|||||||
if (!go)
|
if (!go)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
bool const isEyCenterFlag = eyeBg && eyCenterFlag && eyCenterFlag->GetGUID() == go->GetGUID();
|
||||||
|
|
||||||
// Validate this is a capture target
|
// Validate this is a capture target
|
||||||
std::vector<uint32>::const_iterator f = find(vFlagIds.begin(), vFlagIds.end(), go->GetEntry());
|
std::vector<uint32>::const_iterator f = std::find(vFlagIds.begin(), vFlagIds.end(), go->GetEntry());
|
||||||
if (f == vFlagIds.end())
|
if (f == vFlagIds.end() && !isEyCenterFlag)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Check object is active
|
// Check object is active
|
||||||
@@ -3722,12 +3795,40 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Special handling for WSG and EY base flags
|
// Special handling for WSG and EY base flags
|
||||||
bool atBase = bgType == BATTLEGROUND_WS ? go->GetEntry() == vFlagsWS[bot->GetTeamId()]
|
bool isWsBaseFlag = bgType == BATTLEGROUND_WS && go->GetEntry() == vFlagsWS[bot->GetTeamId()];
|
||||||
: bgType == BATTLEGROUND_EY ? go->GetEntry() == vFlagsEY[0]
|
bool isEyBaseFlag = bgType == BATTLEGROUND_EY && go->GetEntry() == vFlagsEY[0];
|
||||||
: false;
|
|
||||||
|
// Ensure bots are inside the Eye of the Storm capture circle before casting
|
||||||
|
if (bgType == BATTLEGROUND_EY)
|
||||||
|
{
|
||||||
|
GameObject* captureFlag = (isEyBaseFlag && eyCenterFlag) ? eyCenterFlag : go;
|
||||||
|
float const requiredRange = 2.5f;
|
||||||
|
if (!bot->IsWithinDistInMap(captureFlag, requiredRange))
|
||||||
|
{
|
||||||
|
// Stay mounted while relocating to avoid mount/dismount loops
|
||||||
|
return MoveTo(bot->GetMapId(), captureFlag->GetPositionX(), captureFlag->GetPositionY(),
|
||||||
|
captureFlag->GetPositionZ());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once inside the circle, dismount and stop before starting the channel
|
||||||
|
if (bot->IsMounted())
|
||||||
|
{
|
||||||
|
bot->RemoveAurasByType(SPELL_AURA_MOUNTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bot->IsInDisallowedMountForm())
|
||||||
|
{
|
||||||
|
bot->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bot->isMoving())
|
||||||
|
{
|
||||||
|
bot->StopMoving();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Don't capture own flag in WSG unless carrying enemy flag
|
// Don't capture own flag in WSG unless carrying enemy flag
|
||||||
if (atBase && bgType == BATTLEGROUND_WS &&
|
if (isWsBaseFlag && bgType == BATTLEGROUND_WS &&
|
||||||
!(bot->HasAura(BG_WS_SPELL_WARSONG_FLAG) || bot->HasAura(BG_WS_SPELL_SILVERWING_FLAG)))
|
!(bot->HasAura(BG_WS_SPELL_WARSONG_FLAG) || bot->HasAura(BG_WS_SPELL_SILVERWING_FLAG)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -3743,7 +3844,7 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
|||||||
{
|
{
|
||||||
float const moveDist = bot->GetObjectSize() + go->GetObjectSize() + 0.1f;
|
float const moveDist = bot->GetObjectSize() + go->GetObjectSize() + 0.1f;
|
||||||
return MoveTo(bot->GetMapId(), go->GetPositionX() + (urand(0, 1) ? -moveDist : moveDist),
|
return MoveTo(bot->GetMapId(), go->GetPositionX() + (urand(0, 1) ? -moveDist : moveDist),
|
||||||
go->GetPositionY() + (urand(0, 1) ? -moveDist : moveDist), go->GetPositionZ());
|
go->GetPositionY() + (urand(0, 1) ? -moveDist : moveDist), go->GetPositionZ());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dismount before capturing
|
// Dismount before capturing
|
||||||
@@ -3772,7 +3873,7 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
|||||||
if (dist < INTERACTION_DISTANCE)
|
if (dist < INTERACTION_DISTANCE)
|
||||||
{
|
{
|
||||||
// Handle flag capture at base
|
// Handle flag capture at base
|
||||||
if (atBase)
|
if (isWsBaseFlag)
|
||||||
{
|
{
|
||||||
if (bot->GetTeamId() == TEAM_HORDE)
|
if (bot->GetTeamId() == TEAM_HORDE)
|
||||||
{
|
{
|
||||||
@@ -3811,28 +3912,50 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case BATTLEGROUND_EY:
|
case BATTLEGROUND_EY:
|
||||||
{ // Issue: Currently bots in EY take flag instantly without casttime
|
{ // Handle Netherstorm flag capture requiring a channel
|
||||||
if (dist < INTERACTION_DISTANCE)
|
if (dist < INTERACTION_DISTANCE)
|
||||||
{
|
{
|
||||||
// Dismount before interacting
|
// Dismount before interacting
|
||||||
if (bot->IsMounted())
|
if (bot->IsMounted())
|
||||||
|
{
|
||||||
bot->RemoveAurasByType(SPELL_AURA_MOUNTED);
|
bot->RemoveAurasByType(SPELL_AURA_MOUNTED);
|
||||||
|
}
|
||||||
|
|
||||||
if (bot->IsInDisallowedMountForm())
|
if (bot->IsInDisallowedMountForm())
|
||||||
|
{
|
||||||
bot->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT);
|
bot->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT);
|
||||||
|
}
|
||||||
|
|
||||||
// Handle center flag differently (requires spell cast)
|
// Handle center flag differently (requires spell cast)
|
||||||
if (atBase)
|
if (isEyCenterFlag)
|
||||||
{
|
{
|
||||||
|
for (uint8 type = CURRENT_MELEE_SPELL; type <= CURRENT_CHANNELED_SPELL; ++type)
|
||||||
|
{
|
||||||
|
if (Spell* currentSpell = bot->GetCurrentSpell(static_cast<CurrentSpellTypes>(type)))
|
||||||
|
{
|
||||||
|
// m_spellInfo may be null in some states: protect access
|
||||||
|
if (currentSpell->m_spellInfo && currentSpell->m_spellInfo->Id == SPELL_CAPTURE_BANNER)
|
||||||
|
{
|
||||||
|
bot->StopMoving();
|
||||||
|
botAI->SetNextCheckDelay(500);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(SPELL_CAPTURE_BANNER);
|
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(SPELL_CAPTURE_BANNER);
|
||||||
if (!spellInfo)
|
if (!spellInfo)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Spell* spell = new Spell(bot, spellInfo, TRIGGERED_NONE);
|
Spell* spell = new Spell(bot, spellInfo, TRIGGERED_NONE);
|
||||||
spell->m_targets.SetGOTarget(go);
|
spell->m_targets.SetGOTarget(go);
|
||||||
|
|
||||||
|
bot->StopMoving();
|
||||||
spell->prepare(&spell->m_targets);
|
spell->prepare(&spell->m_targets);
|
||||||
|
|
||||||
botAI->WaitForSpellCast(spell);
|
botAI->WaitForSpellCast(spell);
|
||||||
//return true; Intended to make a bot cast SPELL_CAPTURE_BANNER and wait for spell finish, but doesn't work and causes infinite loop
|
resetObjective();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pick up dropped flag
|
// Pick up dropped flag
|
||||||
@@ -3849,8 +3972,8 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
|||||||
return MoveTo(bot->GetMapId(), go->GetPositionX(), go->GetPositionY(), go->GetPositionZ());
|
return MoveTo(bot->GetMapId(), go->GetPositionX(), go->GetPositionY(), go->GetPositionZ());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4289,9 +4412,15 @@ bool ArenaTactics::moveToCenter(Battleground* bg)
|
|||||||
{
|
{
|
||||||
// they like to hang around at the tip of the pipes doing nothing, so we just teleport them down
|
// they like to hang around at the tip of the pipes doing nothing, so we just teleport them down
|
||||||
if (bot->GetDistance(1333.07f, 817.18f, 13.35f) < 4)
|
if (bot->GetDistance(1333.07f, 817.18f, 13.35f) < 4)
|
||||||
|
{
|
||||||
|
bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP);
|
||||||
bot->TeleportTo(bg->GetMapId(), 1330.96f, 816.75f, 3.2f, bot->GetOrientation());
|
bot->TeleportTo(bg->GetMapId(), 1330.96f, 816.75f, 3.2f, bot->GetOrientation());
|
||||||
|
}
|
||||||
if (bot->GetDistance(1250.13f, 764.79f, 13.34f) < 4)
|
if (bot->GetDistance(1250.13f, 764.79f, 13.34f) < 4)
|
||||||
|
{
|
||||||
|
bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP);
|
||||||
bot->TeleportTo(bg->GetMapId(), 1252.19f, 765.41f, 3.2f, bot->GetOrientation());
|
bot->TeleportTo(bg->GetMapId(), 1252.19f, 765.41f, 3.2f, bot->GetOrientation());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case BATTLEGROUND_RV:
|
case BATTLEGROUND_RV:
|
||||||
@@ -224,42 +224,36 @@ bool BuyAction::Execute(Event event)
|
|||||||
|
|
||||||
bool BuyAction::BuyItem(VendorItemData const* tItems, ObjectGuid vendorguid, ItemTemplate const* proto)
|
bool BuyAction::BuyItem(VendorItemData const* tItems, ObjectGuid vendorguid, ItemTemplate const* proto)
|
||||||
{
|
{
|
||||||
uint32 oldCount = AI_VALUE2(uint32, "item count", proto->Name1);
|
if (!tItems || !proto)
|
||||||
|
|
||||||
if (!tItems)
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
uint32 itemId = proto->ItemId;
|
uint32 itemId = proto->ItemId;
|
||||||
for (uint32 slot = 0; slot < tItems->GetItemCount(); slot++)
|
uint32 oldCount = bot->GetItemCount(itemId, false);
|
||||||
|
|
||||||
|
for (uint32 slot = 0; slot < tItems->GetItemCount(); ++slot)
|
||||||
{
|
{
|
||||||
if (tItems->GetItem(slot)->item == itemId)
|
if (tItems->GetItem(slot)->item != itemId)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
uint32 botMoney = bot->GetMoney();
|
||||||
|
if (botAI->HasCheat(BotCheatMask::gold))
|
||||||
|
bot->SetMoney(10000000);
|
||||||
|
|
||||||
|
bot->BuyItemFromVendorSlot(vendorguid, slot, itemId, 1, NULL_BAG, NULL_SLOT);
|
||||||
|
|
||||||
|
if (botAI->HasCheat(BotCheatMask::gold))
|
||||||
|
bot->SetMoney(botMoney);
|
||||||
|
|
||||||
|
uint32 newCount = bot->GetItemCount(itemId, false);
|
||||||
|
if (newCount > oldCount)
|
||||||
{
|
{
|
||||||
uint32 botMoney = bot->GetMoney();
|
std::ostringstream out;
|
||||||
if (botAI->HasCheat(BotCheatMask::gold))
|
out << "Buying " << ChatHelper::FormatItem(proto);
|
||||||
{
|
botAI->TellMaster(out.str());
|
||||||
bot->SetMoney(10000000);
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
bot->BuyItemFromVendorSlot(vendorguid, slot, itemId, 1, NULL_BAG, NULL_SLOT);
|
|
||||||
|
|
||||||
if (botAI->HasCheat(BotCheatMask::gold))
|
|
||||||
{
|
|
||||||
bot->SetMoney(botMoney);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldCount <
|
|
||||||
AI_VALUE2(
|
|
||||||
uint32, "item count",
|
|
||||||
proto->Name1)) // BuyItem Always returns false (unless unique) so we have to check the item counts.
|
|
||||||
{
|
|
||||||
std::ostringstream out;
|
|
||||||
out << "Buying " << ChatHelper::FormatItem(proto);
|
|
||||||
botAI->TellMaster(out.str());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
#include "ChangeStrategyAction.h"
|
#include "ChangeStrategyAction.h"
|
||||||
|
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "PlayerbotDbStore.h"
|
#include "PlayerbotRepository.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
|
||||||
bool ChangeCombatStrategyAction::Execute(Event event)
|
bool ChangeCombatStrategyAction::Execute(Event event)
|
||||||
@@ -24,7 +24,7 @@ bool ChangeCombatStrategyAction::Execute(Event event)
|
|||||||
case '+':
|
case '+':
|
||||||
case '-':
|
case '-':
|
||||||
case '~':
|
case '~':
|
||||||
sPlayerbotDbStore->Save(botAI);
|
sPlayerbotRepository->Save(botAI);
|
||||||
break;
|
break;
|
||||||
case '?':
|
case '?':
|
||||||
break;
|
break;
|
||||||
@@ -62,7 +62,7 @@ bool ChangeNonCombatStrategyAction::Execute(Event event)
|
|||||||
case '+':
|
case '+':
|
||||||
case '-':
|
case '-':
|
||||||
case '~':
|
case '~':
|
||||||
sPlayerbotDbStore->Save(botAI);
|
sPlayerbotRepository->Save(botAI);
|
||||||
break;
|
break;
|
||||||
case '?':
|
case '?':
|
||||||
break;
|
break;
|
||||||
@@ -106,6 +106,7 @@ bool FollowChatShortcutAction::Execute(Event event)
|
|||||||
else
|
else
|
||||||
botAI->TellMaster("You are too far away from me! I will there soon.");
|
botAI->TellMaster("You are too far away from me! I will there soon.");
|
||||||
|
|
||||||
|
bot->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP);
|
||||||
bot->TeleportTo(master->GetMapId(), master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(),
|
bot->TeleportTo(master->GetMapId(), master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(),
|
||||||
master->GetOrientation()); return true;
|
master->GetOrientation()); return true;
|
||||||
}
|
}
|
||||||
@@ -240,20 +241,6 @@ bool MaxDpsChatShortcutAction::Execute(Event event)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NaxxChatShortcutAction::Execute(Event event)
|
|
||||||
{
|
|
||||||
Player* master = GetMaster();
|
|
||||||
if (!master)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
botAI->Reset();
|
|
||||||
botAI->ChangeStrategy("+naxx", BOT_STATE_NON_COMBAT);
|
|
||||||
botAI->ChangeStrategy("+naxx", BOT_STATE_COMBAT);
|
|
||||||
botAI->TellMasterNoFacing("Add Naxx Strategies!");
|
|
||||||
// bot->Say("Add Naxx Strategies!", LANG_UNIVERSAL);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BwlChatShortcutAction::Execute(Event event)
|
bool BwlChatShortcutAction::Execute(Event event)
|
||||||
{
|
{
|
||||||
Player* master = GetMaster();
|
Player* master = GetMaster();
|
||||||
@@ -85,13 +85,6 @@ public:
|
|||||||
bool Execute(Event event) override;
|
bool Execute(Event event) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class NaxxChatShortcutAction : public Action
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
NaxxChatShortcutAction(PlayerbotAI* ai) : Action(ai, "naxx chat shortcut") {}
|
|
||||||
virtual bool Execute(Event event);
|
|
||||||
};
|
|
||||||
|
|
||||||
class BwlChatShortcutAction : public Action
|
class BwlChatShortcutAction : public Action
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -81,7 +81,7 @@ bool CheckMountStateAction::isUseful()
|
|||||||
// to mostly be an issue in tunnels of WSG and AV)
|
// to mostly be an issue in tunnels of WSG and AV)
|
||||||
float posZ = bot->GetPositionZ();
|
float posZ = bot->GetPositionZ();
|
||||||
float groundLevel = bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), posZ);
|
float groundLevel = bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), posZ);
|
||||||
if (!bot->IsMounted() && posZ < groundLevel)
|
if (!bot->IsMounted() && !bot->HasWaterWalkAura() && posZ < groundLevel)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Not useful when bot does not have mount strat and is not currently mounted
|
// Not useful when bot does not have mount strat and is not currently mounted
|
||||||
@@ -370,7 +370,7 @@ bool CheckMountStateAction::TryPreferredMount(Player* master) const
|
|||||||
|
|
||||||
bool CheckMountStateAction::TryRandomMountFiltered(const std::map<int32, std::vector<uint32>>& spells, int32 masterSpeed) const
|
bool CheckMountStateAction::TryRandomMountFiltered(const std::map<int32, std::vector<uint32>>& spells, int32 masterSpeed) const
|
||||||
{
|
{
|
||||||
for (const auto& pair : spells)
|
for (auto const& pair : spells)
|
||||||
{
|
{
|
||||||
int32 currentSpeed = pair.first;
|
int32 currentSpeed = pair.first;
|
||||||
|
|
||||||
@@ -378,7 +378,7 @@ bool CheckMountStateAction::TryRandomMountFiltered(const std::map<int32, std::ve
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Pick a random mount from the candidate group.
|
// Pick a random mount from the candidate group.
|
||||||
const auto& ids = pair.second;
|
auto const& ids = pair.second;
|
||||||
if (!ids.empty())
|
if (!ids.empty())
|
||||||
{
|
{
|
||||||
// Required here as otherwise bots won't mount in BG's due to them constant moving
|
// Required here as otherwise bots won't mount in BG's due to them constant moving
|
||||||
@@ -78,20 +78,17 @@ float ChooseRpgTargetAction::getMaxRelevance(GuidPosition guidP)
|
|||||||
if (!trigger->IsActive())
|
if (!trigger->IsActive())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
NextAction** nextActions = triggerNode->getHandlers();
|
std::vector<NextAction> nextActions = triggerNode->getHandlers();
|
||||||
|
|
||||||
bool isRpg = false;
|
bool isRpg = false;
|
||||||
|
|
||||||
for (int32 i = 0; i < NextAction::size(nextActions); i++)
|
for (NextAction nextAction : nextActions)
|
||||||
{
|
{
|
||||||
NextAction* nextAction = nextActions[i];
|
Action* action = botAI->GetAiObjectContext()->GetAction(nextAction.getName());
|
||||||
|
|
||||||
Action* action = botAI->GetAiObjectContext()->GetAction(nextAction->getName());
|
|
||||||
|
|
||||||
if (dynamic_cast<RpgEnabled*>(action))
|
if (dynamic_cast<RpgEnabled*>(action))
|
||||||
isRpg = true;
|
isRpg = true;
|
||||||
}
|
}
|
||||||
NextAction::destroy(nextActions);
|
|
||||||
|
|
||||||
if (isRpg)
|
if (isRpg)
|
||||||
{
|
{
|
||||||
@@ -311,7 +308,7 @@ bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldObject* target)
|
|||||||
bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldPosition pos)
|
bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldPosition pos)
|
||||||
{
|
{
|
||||||
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
|
||||||
Player* gmaster = botAI->GetGroupMaster();
|
Player* groupLeader = botAI->GetGroupLeader();
|
||||||
Player* realMaster = botAI->GetMaster();
|
Player* realMaster = botAI->GetMaster();
|
||||||
AiObjectContext* context = botAI->GetAiObjectContext();
|
AiObjectContext* context = botAI->GetAiObjectContext();
|
||||||
|
|
||||||
@@ -327,30 +324,30 @@ bool ChooseRpgTargetAction::isFollowValid(Player* bot, WorldPosition pos)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!gmaster || bot == gmaster)
|
if (!groupLeader || bot == groupLeader)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
|
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (bot->GetDistance(gmaster) > sPlayerbotAIConfig->rpgDistance * 2)
|
if (bot->GetDistance(groupLeader) > sPlayerbotAIConfig->rpgDistance * 2)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Formation* formation = AI_VALUE(Formation*, "formation");
|
Formation* formation = AI_VALUE(Formation*, "formation");
|
||||||
float distance = gmaster->GetDistance2d(pos.getX(), pos.getY());
|
float distance = groupLeader->GetDistance2d(pos.getX(), pos.getY());
|
||||||
|
|
||||||
if (!botAI->HasActivePlayerMaster() && distance < 50.0f)
|
if (!botAI->HasActivePlayerMaster() && distance < 50.0f)
|
||||||
{
|
{
|
||||||
Player* player = gmaster;
|
Player* player = groupLeader;
|
||||||
if (gmaster && !gmaster->isMoving() ||
|
if (groupLeader && !groupLeader->isMoving() ||
|
||||||
PAI_VALUE(WorldPosition, "last long move").distance(pos) < sPlayerbotAIConfig->reactDistance)
|
PAI_VALUE(WorldPosition, "last long move").distance(pos) < sPlayerbotAIConfig->reactDistance)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((inDungeon || !gmaster->HasPlayerFlag(PLAYER_FLAGS_RESTING)) && realMaster == gmaster && distance > 5.0f)
|
if ((inDungeon || !groupLeader->HasPlayerFlag(PLAYER_FLAGS_RESTING)) && realMaster == groupLeader && distance > 5.0f)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!gmaster->isMoving() && distance < 25.0f)
|
if (!groupLeader->isMoving() && distance < 25.0f)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (distance < formation->GetMaxDistance())
|
if (distance < formation->GetMaxDistance())
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
#include "LootObjectStack.h"
|
#include "LootObjectStack.h"
|
||||||
#include "NewRpgStrategy.h"
|
#include "NewRpgStrategy.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
#include "RtiTargetValue.h"
|
||||||
#include "PossibleRpgTargetsValue.h"
|
#include "PossibleRpgTargetsValue.h"
|
||||||
#include "PvpTriggers.h"
|
#include "PvpTriggers.h"
|
||||||
#include "ServerFacade.h"
|
#include "ServerFacade.h"
|
||||||
@@ -87,9 +88,7 @@ bool DropTargetAction::Execute(Event event)
|
|||||||
{
|
{
|
||||||
Spell const* spell = bot->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL); // Get the current spell being cast by the bot
|
Spell const* spell = bot->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL); // Get the current spell being cast by the bot
|
||||||
if (spell && spell->m_spellInfo->Id == 75) //Check spell is not nullptr before accessing m_spellInfo
|
if (spell && spell->m_spellInfo->Id == 75) //Check spell is not nullptr before accessing m_spellInfo
|
||||||
{
|
|
||||||
bot->InterruptSpell(CURRENT_AUTOREPEAT_SPELL); // Interrupt Auto Shot
|
bot->InterruptSpell(CURRENT_AUTOREPEAT_SPELL); // Interrupt Auto Shot
|
||||||
}
|
|
||||||
}
|
}
|
||||||
bot->AttackStop();
|
bot->AttackStop();
|
||||||
|
|
||||||
@@ -142,6 +141,23 @@ bool AttackRtiTargetAction::Execute(Event event)
|
|||||||
{
|
{
|
||||||
Unit* rtiTarget = AI_VALUE(Unit*, "rti target");
|
Unit* rtiTarget = AI_VALUE(Unit*, "rti target");
|
||||||
|
|
||||||
|
// Fallback: if the "rti target" value did not resolve a valid unit yet,
|
||||||
|
// try to resolve the raid icon directly from the group.
|
||||||
|
if (!rtiTarget)
|
||||||
|
{
|
||||||
|
if (Group* group = bot->GetGroup())
|
||||||
|
{
|
||||||
|
std::string const rti = AI_VALUE(std::string, "rti");
|
||||||
|
int32 const index = RtiTargetValue::GetRtiIndex(rti);
|
||||||
|
if (index >= 0)
|
||||||
|
{
|
||||||
|
ObjectGuid const guid = group->GetTargetIcon(index);
|
||||||
|
if (!guid.IsEmpty())
|
||||||
|
rtiTarget = botAI->GetUnit(guid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (rtiTarget && rtiTarget->IsInWorld() && rtiTarget->GetMapId() == bot->GetMapId())
|
if (rtiTarget && rtiTarget->IsInWorld() && rtiTarget->GetMapId() == bot->GetMapId())
|
||||||
{
|
{
|
||||||
botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Set({rtiTarget->GetGUID()});
|
botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Set({rtiTarget->GetGUID()});
|
||||||
@@ -153,9 +169,7 @@ bool AttackRtiTargetAction::Execute(Event event)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
|
||||||
botAI->TellError("I dont see my rti attack target");
|
botAI->TellError("I dont see my rti attack target");
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -120,7 +120,6 @@ void ChooseTravelTargetAction::getNewTarget(TravelTarget* newTarget, TravelTarge
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//Continue current target. 90% chance
|
//Continue current target. 90% chance
|
||||||
if (!foundTarget && urand(1, 100) > 10)
|
if (!foundTarget && urand(1, 100) > 10)
|
||||||
{
|
{
|
||||||
@@ -181,7 +180,7 @@ void ChooseTravelTargetAction::getNewTarget(TravelTarget* newTarget, TravelTarge
|
|||||||
void ChooseTravelTargetAction::setNewTarget(TravelTarget* newTarget, TravelTarget* oldTarget)
|
void ChooseTravelTargetAction::setNewTarget(TravelTarget* newTarget, TravelTarget* oldTarget)
|
||||||
{
|
{
|
||||||
// Tell the master where we are going.
|
// Tell the master where we are going.
|
||||||
if (!bot->GetGroup() || (botAI->GetGroupMaster() == bot))
|
if (!bot->GetGroup() || (botAI->GetGroupLeader() == bot))
|
||||||
ReportTravelTarget(newTarget, oldTarget);
|
ReportTravelTarget(newTarget, oldTarget);
|
||||||
|
|
||||||
// If we are heading to a creature/npc clear it from the ignore list.
|
// If we are heading to a creature/npc clear it from the ignore list.
|
||||||
@@ -673,7 +672,7 @@ bool ChooseTravelTargetAction::SetExploreTarget(TravelTarget* target)
|
|||||||
//271 south shore
|
//271 south shore
|
||||||
//35 booty bay
|
//35 booty bay
|
||||||
//380 The Barrens The Crossroads
|
//380 The Barrens The Crossroads
|
||||||
if(((ExploreTravelDestination * )activeTarget)->getAreaId() == 380)
|
if (((ExploreTravelDestination * )activeTarget)->getAreaId() == 380)
|
||||||
{
|
{
|
||||||
activePoints.push_back(activeTarget->getPoints(true)[0]);
|
activePoints.push_back(activeTarget->getPoints(true)[0]);
|
||||||
}
|
}
|
||||||
@@ -58,7 +58,6 @@ bool DropQuestAction::Execute(Event event)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool CleanQuestLogAction::Execute(Event event)
|
bool CleanQuestLogAction::Execute(Event event)
|
||||||
{
|
{
|
||||||
Player* requester = event.getOwner() ? event.getOwner() : GetMaster();
|
Player* requester = event.getOwner() ? event.getOwner() : GetMaster();
|
||||||
@@ -165,7 +164,6 @@ bool CleanQuestLogAction::Execute(Event event)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void CleanQuestLogAction::DropQuestType(uint8& numQuest, uint8 wantNum, bool isGreen, bool hasProgress, bool isComplete)
|
void CleanQuestLogAction::DropQuestType(uint8& numQuest, uint8 wantNum, bool isGreen, bool hasProgress, bool isComplete)
|
||||||
{
|
{
|
||||||
std::vector<uint8> slots;
|
std::vector<uint8> slots;
|
||||||
@@ -837,8 +837,8 @@ uint32 TalkAction::GetRandomEmote(Unit* unit, bool textEmote)
|
|||||||
types.push_back(TEXT_EMOTE_TALKEX);
|
types.push_back(TEXT_EMOTE_TALKEX);
|
||||||
types.push_back(TEXT_EMOTE_TALKQ);
|
types.push_back(TEXT_EMOTE_TALKQ);
|
||||||
|
|
||||||
if (unit && (unit->HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_TRAINER) ||
|
if (unit && (unit->HasNpcFlag(UNIT_NPC_FLAG_TRAINER) ||
|
||||||
unit->HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_QUESTGIVER)))
|
unit->HasNpcFlag(UNIT_NPC_FLAG_QUESTGIVER)))
|
||||||
{
|
{
|
||||||
types.push_back(TEXT_EMOTE_SALUTE);
|
types.push_back(TEXT_EMOTE_SALUTE);
|
||||||
}
|
}
|
||||||
@@ -864,8 +864,8 @@ uint32 TalkAction::GetRandomEmote(Unit* unit, bool textEmote)
|
|||||||
types.push_back(EMOTE_ONESHOT_EXCLAMATION);
|
types.push_back(EMOTE_ONESHOT_EXCLAMATION);
|
||||||
types.push_back(EMOTE_ONESHOT_QUESTION);
|
types.push_back(EMOTE_ONESHOT_QUESTION);
|
||||||
|
|
||||||
if (unit && (unit->HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_TRAINER) ||
|
if (unit && (unit->HasNpcFlag(UNIT_NPC_FLAG_TRAINER) ||
|
||||||
unit->HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_QUESTGIVER)))
|
unit->HasNpcFlag(UNIT_NPC_FLAG_QUESTGIVER)))
|
||||||
{
|
{
|
||||||
types.push_back(EMOTE_ONESHOT_SALUTE);
|
types.push_back(EMOTE_ONESHOT_SALUTE);
|
||||||
}
|
}
|
||||||
@@ -271,19 +271,38 @@ void EquipAction::EquipItem(Item* item)
|
|||||||
{
|
{
|
||||||
if (equippedItems[1])
|
if (equippedItems[1])
|
||||||
{
|
{
|
||||||
// Both slots are full - pick the worst item to replace
|
// Both slots are full - pick the worst item to replace, but only if new item is better
|
||||||
StatsWeightCalculator calc(bot);
|
StatsWeightCalculator calc(bot);
|
||||||
calc.SetItemSetBonus(false);
|
calc.SetItemSetBonus(false);
|
||||||
calc.SetOverflowPenalty(false);
|
calc.SetOverflowPenalty(false);
|
||||||
|
|
||||||
float firstItemScore = calc.CalculateItem(equippedItems[0]->GetTemplate()->ItemId);
|
// Calculate new item score with random properties
|
||||||
float secondItemScore = calc.CalculateItem(equippedItems[1]->GetTemplate()->ItemId);
|
int32 newItemRandomProp = item->GetItemRandomPropertyId();
|
||||||
|
float newItemScore = calc.CalculateItem(itemId, newItemRandomProp);
|
||||||
|
|
||||||
// If the second slot is worse, place the new item there
|
// Calculate equipped items scores with random properties
|
||||||
if (firstItemScore > secondItemScore)
|
int32 firstRandomProp = equippedItems[0]->GetItemRandomPropertyId();
|
||||||
|
int32 secondRandomProp = equippedItems[1]->GetItemRandomPropertyId();
|
||||||
|
float firstItemScore = calc.CalculateItem(equippedItems[0]->GetTemplate()->ItemId, firstRandomProp);
|
||||||
|
float secondItemScore = calc.CalculateItem(equippedItems[1]->GetTemplate()->ItemId, secondRandomProp);
|
||||||
|
|
||||||
|
// Determine which slot (if any) should be replaced
|
||||||
|
bool betterThanFirst = newItemScore > firstItemScore;
|
||||||
|
bool betterThanSecond = newItemScore > secondItemScore;
|
||||||
|
|
||||||
|
// Early return if new item is not better than either equipped item
|
||||||
|
if (!betterThanFirst && !betterThanSecond)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (betterThanFirst && betterThanSecond)
|
||||||
{
|
{
|
||||||
dstSlot++;
|
// New item is better than both - replace the worse of the two equipped items
|
||||||
|
if (firstItemScore > secondItemScore)
|
||||||
|
dstSlot++; // Replace second slot (worse)
|
||||||
|
// else: keep dstSlot as-is (replace first slot)
|
||||||
}
|
}
|
||||||
|
else if (betterThanSecond)
|
||||||
|
dstSlot++; // Only better than second slot - replace it
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -309,7 +328,6 @@ void EquipAction::EquipItem(Item* item)
|
|||||||
botAI->TellMaster(out);
|
botAI->TellMaster(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool EquipUpgradesAction::Execute(Event event)
|
bool EquipUpgradesAction::Execute(Event event)
|
||||||
{
|
{
|
||||||
if (!sPlayerbotAIConfig->autoEquipUpgradeLoot && !sRandomPlayerbotMgr->IsRandomBot(bot))
|
if (!sPlayerbotAIConfig->autoEquipUpgradeLoot && !sRandomPlayerbotMgr->IsRandomBot(bot))
|
||||||
@@ -326,6 +344,27 @@ bool EquipUpgradesAction::Execute(Event event)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.GetSource() == "item push result")
|
||||||
|
{
|
||||||
|
WorldPacket p(event.getPacket());
|
||||||
|
p.rpos(0);
|
||||||
|
ObjectGuid playerGuid;
|
||||||
|
uint32 received, created, sendChatMessage, itemSlot, itemId;
|
||||||
|
uint8 bagSlot;
|
||||||
|
|
||||||
|
p >> playerGuid;
|
||||||
|
p >> received;
|
||||||
|
p >> created;
|
||||||
|
p >> sendChatMessage;
|
||||||
|
p >> bagSlot;
|
||||||
|
p >> itemSlot;
|
||||||
|
p >> itemId;
|
||||||
|
|
||||||
|
ItemTemplate const* item = sObjectMgr->GetItemTemplate(itemId);
|
||||||
|
if (item->Class == ITEM_CLASS_TRADE_GOODS && item->SubClass == ITEM_SUBCLASS_MEAT)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
CollectItemsVisitor visitor;
|
CollectItemsVisitor visitor;
|
||||||
IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS);
|
IterateItems(&visitor, ITERATE_ITEMS_IN_BAGS);
|
||||||
|
|
||||||
@@ -338,9 +377,12 @@ bool EquipUpgradesAction::Execute(Event event)
|
|||||||
int32 randomProperty = item->GetItemRandomPropertyId();
|
int32 randomProperty = item->GetItemRandomPropertyId();
|
||||||
uint32 itemId = item->GetTemplate()->ItemId;
|
uint32 itemId = item->GetTemplate()->ItemId;
|
||||||
std::string itemUsageParam;
|
std::string itemUsageParam;
|
||||||
if (randomProperty != 0) {
|
if (randomProperty != 0)
|
||||||
|
{
|
||||||
itemUsageParam = std::to_string(itemId) + "," + std::to_string(randomProperty);
|
itemUsageParam = std::to_string(itemId) + "," + std::to_string(randomProperty);
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
itemUsageParam = std::to_string(itemId);
|
itemUsageParam = std::to_string(itemId);
|
||||||
}
|
}
|
||||||
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", itemUsageParam);
|
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", itemUsageParam);
|
||||||
@@ -369,9 +411,12 @@ bool EquipUpgradeAction::Execute(Event event)
|
|||||||
int32 randomProperty = item->GetItemRandomPropertyId();
|
int32 randomProperty = item->GetItemRandomPropertyId();
|
||||||
uint32 itemId = item->GetTemplate()->ItemId;
|
uint32 itemId = item->GetTemplate()->ItemId;
|
||||||
std::string itemUsageParam;
|
std::string itemUsageParam;
|
||||||
if (randomProperty != 0) {
|
if (randomProperty != 0)
|
||||||
|
{
|
||||||
itemUsageParam = std::to_string(itemId) + "," + std::to_string(randomProperty);
|
itemUsageParam = std::to_string(itemId) + "," + std::to_string(randomProperty);
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
itemUsageParam = std::to_string(itemId);
|
itemUsageParam = std::to_string(itemId);
|
||||||
}
|
}
|
||||||
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", itemUsageParam);
|
ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", itemUsageParam);
|
||||||
510
src/Bot/BaseAi/Actions/FishingAction.cpp
Normal file
510
src/Bot/BaseAi/Actions/FishingAction.cpp
Normal file
@@ -0,0 +1,510 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||||
|
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "FishingAction.h"
|
||||||
|
#include "FishValues.h"
|
||||||
|
#include "Event.h"
|
||||||
|
|
||||||
|
#include "GridNotifiers.h"
|
||||||
|
#include "GridNotifiersImpl.h"
|
||||||
|
#include "ItemPackets.h"
|
||||||
|
#include "LastMovementValue.h"
|
||||||
|
#include "Map.h"
|
||||||
|
#include "MovementActions.h"
|
||||||
|
#include "Object.h"
|
||||||
|
#include "PlayerbotAI.h"
|
||||||
|
#include "PlayerbotTextMgr.h"
|
||||||
|
#include "Playerbots.h"
|
||||||
|
#include "Position.h"
|
||||||
|
|
||||||
|
uint32 const FISHING_SPELL = 7620;
|
||||||
|
uint32 const FISHING_POLE = 6256;
|
||||||
|
uint32 const FISHING_BOBBER = 35591;
|
||||||
|
float const MIN_DISTANCE_TO_WATER = 10.0f; // Minimum spell distance
|
||||||
|
float const MAX_DISTANCE_TO_WATER = 20.0f; // Maximum spell distance
|
||||||
|
float const HEIGHT_ABOVE_WATER_TOLERANCE = 1.0f; // Can stand in up to 1 unit of water and still fish.
|
||||||
|
float const SEARCH_INCREMENT = 2.5f;
|
||||||
|
float const HEIGHT_SEARCH_BUFFER = 10.0f; // Height buffer to prevent potentially missing the model the bot is standing on.
|
||||||
|
float const SEARCH_LAND_BUFFER = 0.5f;
|
||||||
|
uint32 const FISHING_LOCATION_TIMEOUT = 180000; //Three minutes
|
||||||
|
|
||||||
|
static bool IsFishingPole(Item* const item)
|
||||||
|
{
|
||||||
|
if (!item)
|
||||||
|
return false;
|
||||||
|
const ItemTemplate* proto = item->GetTemplate();
|
||||||
|
return proto && proto->Class == ITEM_CLASS_WEAPON &&
|
||||||
|
proto->SubClass == ITEM_SUBCLASS_WEAPON_FISHING_POLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
float HasFishableWaterOrLand(float x, float y, float z, Map* map, uint32 phaseMask, bool checkForLand=false)
|
||||||
|
{
|
||||||
|
if (!map)
|
||||||
|
return INVALID_HEIGHT;
|
||||||
|
|
||||||
|
LiquidData const& liq = map->GetLiquidData(phaseMask, x, y, z+HEIGHT_ABOVE_WATER_TOLERANCE, DEFAULT_COLLISION_HEIGHT, MAP_ALL_LIQUIDS);
|
||||||
|
float ground = map->GetHeight(phaseMask, x, y, z + HEIGHT_SEARCH_BUFFER, true);
|
||||||
|
if (liq.Entry == MAP_LIQUID_TYPE_NO_WATER)
|
||||||
|
{
|
||||||
|
if (checkForLand)
|
||||||
|
return ground;
|
||||||
|
return INVALID_HEIGHT;
|
||||||
|
}
|
||||||
|
if (checkForLand)
|
||||||
|
{
|
||||||
|
if (ground > liq.Level - HEIGHT_ABOVE_WATER_TOLERANCE)
|
||||||
|
return ground;
|
||||||
|
return INVALID_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (liq.Level + HEIGHT_ABOVE_WATER_TOLERANCE > ground)
|
||||||
|
{
|
||||||
|
if (abs(liq.DepthLevel) < 0.5f) // too shallow to fish in.
|
||||||
|
return INVALID_HEIGHT;
|
||||||
|
return liq.Level;
|
||||||
|
}
|
||||||
|
return INVALID_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasLosToWater(Player* bot, float wx, float wy, float waterZ)
|
||||||
|
{
|
||||||
|
float z = bot->GetCollisionHeight() + bot->GetPositionZ();
|
||||||
|
return bot->GetMap()->isInLineOfSight(
|
||||||
|
bot->GetPositionX(), bot->GetPositionY(), z,
|
||||||
|
wx, wy, waterZ,
|
||||||
|
bot->GetPhaseMask(),
|
||||||
|
LINEOFSIGHT_ALL_CHECKS,
|
||||||
|
VMAP::ModelIgnoreFlags::Nothing);
|
||||||
|
}
|
||||||
|
|
||||||
|
WorldPosition FindLandFromPosition(PlayerbotAI* botAI, float startDistance, float endDistance, float increment, float orientation, WorldPosition targetPos, float fishingSearchWindow, bool checkLOS = true)
|
||||||
|
{
|
||||||
|
Player* bot = botAI->GetBot();
|
||||||
|
Map* map = bot->GetMap();
|
||||||
|
uint32 phaseMask = bot->GetPhaseMask();
|
||||||
|
Player* master = botAI->GetMaster();
|
||||||
|
|
||||||
|
float targetX = targetPos.GetPositionX();
|
||||||
|
float targetY = targetPos.GetPositionY();
|
||||||
|
float targetZ = targetPos.GetPositionZ();
|
||||||
|
|
||||||
|
for (float dist = startDistance; dist <= endDistance; dist += increment)
|
||||||
|
{
|
||||||
|
//step backwards from position to bot to find edge of shore.
|
||||||
|
float checkX = targetX - dist * cos(orientation);
|
||||||
|
float checkY = targetY - dist * sin(orientation);
|
||||||
|
|
||||||
|
float groundZ = map->GetHeight(phaseMask, checkX, checkY, targetZ + HEIGHT_SEARCH_BUFFER, true);
|
||||||
|
|
||||||
|
if (groundZ == INVALID_HEIGHT)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
LiquidData const& liq = map->GetLiquidData(phaseMask, checkX, checkY, targetZ, DEFAULT_COLLISION_HEIGHT, MAP_ALL_LIQUIDS);
|
||||||
|
if (liq.Entry == MAP_LIQUID_TYPE_NO_WATER || groundZ > liq.DepthLevel + HEIGHT_ABOVE_WATER_TOLERANCE)
|
||||||
|
{
|
||||||
|
if (checkLOS)
|
||||||
|
{
|
||||||
|
bool hasLOS = map->isInLineOfSight(checkX, checkY, groundZ, targetX, targetY, targetZ, phaseMask, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::Nothing);
|
||||||
|
if (!hasLOS)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Add a distance check for the position to prevent the bot from moving out of range to the master.
|
||||||
|
if (master && botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT) && master->GetDistance(checkX, checkY, groundZ) > fishingSearchWindow - SEARCH_LAND_BUFFER)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
return WorldPosition(bot->GetMapId(), checkX, checkY, groundZ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return WorldPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
WorldPosition FindLandRadialFromPosition (PlayerbotAI* botAI, WorldPosition targetPos, float startDistance, float endDistance, float increment, float fishingSearchWindow, int angles = 16)
|
||||||
|
{
|
||||||
|
Player* bot = botAI->GetBot();
|
||||||
|
const int numDirections = angles;
|
||||||
|
std::vector<WorldPosition> boundaryPoints;
|
||||||
|
Player* master = botAI->GetMaster();
|
||||||
|
if (!master)
|
||||||
|
return WorldPosition();
|
||||||
|
|
||||||
|
Map* map = bot->GetMap();
|
||||||
|
uint32 phaseMask = bot->GetPhaseMask();
|
||||||
|
|
||||||
|
float targetX = targetPos.GetPositionX();
|
||||||
|
float targetY = targetPos.GetPositionY();
|
||||||
|
float targetZ = targetPos.GetPositionZ();
|
||||||
|
|
||||||
|
for (float dist = startDistance; dist <= endDistance; dist += increment)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numDirections; ++i)
|
||||||
|
{
|
||||||
|
float angle = (2.0f * M_PI * i) / numDirections;
|
||||||
|
float checkX = targetX - cos(angle) * dist;
|
||||||
|
float checkY = targetY - sin(angle) * dist;
|
||||||
|
|
||||||
|
float groundZ = HasFishableWaterOrLand(checkX, checkY, targetZ, map, phaseMask, true);
|
||||||
|
|
||||||
|
if (groundZ == INVALID_HEIGHT)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (map->isInLineOfSight(checkX, checkY, groundZ, targetX, targetY, targetZ, phaseMask, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::Nothing) && master->GetDistance(checkX, checkY, groundZ) > fishingSearchWindow - SEARCH_LAND_BUFFER)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
boundaryPoints.emplace_back(WorldPosition(bot->GetMapId(), checkX, checkY, groundZ));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!boundaryPoints.empty())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (boundaryPoints.empty())
|
||||||
|
return WorldPosition();
|
||||||
|
|
||||||
|
if (boundaryPoints.size() == 1)
|
||||||
|
return boundaryPoints[0];
|
||||||
|
|
||||||
|
float minDistance = FLT_MAX;
|
||||||
|
WorldLocation closestPoint = WorldPosition();
|
||||||
|
for (auto const& pos : boundaryPoints)
|
||||||
|
{
|
||||||
|
float distance = bot->GetExactDist2d(&pos);
|
||||||
|
if (distance < minDistance)
|
||||||
|
{
|
||||||
|
minDistance = distance;
|
||||||
|
closestPoint = pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return closestPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
WorldPosition FindWaterRadial(Player* bot, float x, float y, float z, Map* map, uint32 phaseMask, float minDistance, float maxDistance, float increment, bool checkLOS, int numDirections)
|
||||||
|
{
|
||||||
|
std::vector<WorldPosition> boundaryPoints;
|
||||||
|
|
||||||
|
float dist = minDistance;
|
||||||
|
while (dist <= maxDistance)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numDirections; ++i)
|
||||||
|
{
|
||||||
|
float angle = (2.0f * M_PI * i) / numDirections;
|
||||||
|
float checkX = x + cos(angle) * dist;
|
||||||
|
float checkY = y + sin(angle) * dist;
|
||||||
|
|
||||||
|
float waterZ = HasFishableWaterOrLand(checkX, checkY, z, map, phaseMask);
|
||||||
|
|
||||||
|
if (waterZ == INVALID_HEIGHT)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (checkLOS && !HasLosToWater(bot, checkX, checkY, waterZ))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
boundaryPoints.emplace_back(WorldPosition(bot->GetMapId(), checkX, checkY, waterZ));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!boundaryPoints.empty())
|
||||||
|
break;
|
||||||
|
|
||||||
|
dist += increment;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (boundaryPoints.empty())
|
||||||
|
return WorldPosition();
|
||||||
|
|
||||||
|
if (boundaryPoints.size() == 1)
|
||||||
|
return boundaryPoints[0];
|
||||||
|
// return the central point in the identified positions in to try to be perpendicular to the shore.
|
||||||
|
return boundaryPoints[boundaryPoints.size() / 2];
|
||||||
|
}
|
||||||
|
|
||||||
|
WorldPosition FindFishingHole(PlayerbotAI* botAI)
|
||||||
|
{
|
||||||
|
Player* player = botAI->GetBot();
|
||||||
|
GuidVector gos = PAI_VALUE(GuidVector, "nearest game objects no los");
|
||||||
|
GameObject* nearestFishingHole = nullptr;
|
||||||
|
float minDist = std::numeric_limits<float>::max();
|
||||||
|
for (auto const& guid : gos)
|
||||||
|
{
|
||||||
|
GameObject* go = botAI->GetGameObject(guid);
|
||||||
|
if (!go)
|
||||||
|
continue;
|
||||||
|
if (go->GetGoType() == GAMEOBJECT_TYPE_FISHINGHOLE)
|
||||||
|
{
|
||||||
|
float dist = player->GetDistance2d(go);
|
||||||
|
if (dist < minDist)
|
||||||
|
{
|
||||||
|
minDist = dist;
|
||||||
|
nearestFishingHole = go;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nearestFishingHole)
|
||||||
|
return WorldPosition(nearestFishingHole->GetMapId(), nearestFishingHole->GetPositionX(), nearestFishingHole->GetPositionY(), nearestFishingHole->GetPositionZ());
|
||||||
|
|
||||||
|
return WorldPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MoveNearWaterAction::Execute(Event event)
|
||||||
|
{
|
||||||
|
WorldPosition landSpot = AI_VALUE(WorldPosition, "fishing spot");
|
||||||
|
if (landSpot.IsValid())
|
||||||
|
return MoveTo(landSpot.GetMapId(), landSpot.GetPositionX(), landSpot.GetPositionY(), landSpot.GetPositionZ());
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MoveNearWaterAction::isUseful()
|
||||||
|
{
|
||||||
|
if (!AI_VALUE(bool, "can fish"))
|
||||||
|
return false;
|
||||||
|
FishingSpotValue* fishingSpotValueObject = (FishingSpotValue*)context->GetValue<WorldPosition>("fishing spot");
|
||||||
|
WorldPosition pos = fishingSpotValueObject->Get();
|
||||||
|
return !pos.IsValid() || fishingSpotValueObject->IsStale(FISHING_LOCATION_TIMEOUT) ||
|
||||||
|
bot->GetExactDist(&pos) < 0.1f;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MoveNearWaterAction::isPossible()
|
||||||
|
{
|
||||||
|
Player* master = botAI->GetMaster();
|
||||||
|
float fishingSearchWindow;
|
||||||
|
|
||||||
|
if (master)
|
||||||
|
fishingSearchWindow = sPlayerbotAIConfig->fishingDistanceFromMaster;
|
||||||
|
else
|
||||||
|
fishingSearchWindow = sPlayerbotAIConfig->fishingDistance;
|
||||||
|
|
||||||
|
WorldPosition fishingHole = FindFishingHole(botAI);
|
||||||
|
|
||||||
|
if (fishingHole.IsValid())
|
||||||
|
{
|
||||||
|
float distance = bot->GetExactDist2d(&fishingHole);
|
||||||
|
bool hasLOS = bot->IsWithinLOS(fishingHole.GetPositionX(), fishingHole.GetPositionY(), fishingHole.GetPositionZ());
|
||||||
|
// Water spot is in range, and we have LOS to it. Set bot position to fishing spot and do not move
|
||||||
|
if (distance >= MIN_DISTANCE_TO_WATER &&
|
||||||
|
distance <= MAX_DISTANCE_TO_WATER && hasLOS)
|
||||||
|
{
|
||||||
|
SET_AI_VALUE(WorldPosition, "fishing spot", WorldPosition(WorldPosition(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ())));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Water spot is out of range, lets look for a spot to move to for the fishing hole.
|
||||||
|
if (distance > MAX_DISTANCE_TO_WATER || distance < MIN_DISTANCE_TO_WATER)
|
||||||
|
{
|
||||||
|
float angle = bot->GetAngle(fishingHole.GetPositionX(), fishingHole.GetPositionY());
|
||||||
|
WorldPosition landSpot = FindLandRadialFromPosition(botAI, fishingHole, MIN_DISTANCE_TO_WATER, MAX_DISTANCE_TO_WATER, SEARCH_INCREMENT, fishingSearchWindow, 32);
|
||||||
|
if (landSpot.IsValid())
|
||||||
|
{
|
||||||
|
SET_AI_VALUE(WorldPosition, "fishing spot", landSpot);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Can the bot fish from current position?
|
||||||
|
WorldPosition waterAtCurrentPos =
|
||||||
|
FindWaterRadial(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetMap(),
|
||||||
|
bot->GetPhaseMask(), MIN_DISTANCE_TO_WATER, MAX_DISTANCE_TO_WATER, SEARCH_INCREMENT, true);
|
||||||
|
if (waterAtCurrentPos.IsValid())
|
||||||
|
{
|
||||||
|
SET_AI_VALUE(WorldPosition, "fishing spot",
|
||||||
|
WorldPosition(WorldPosition(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(),
|
||||||
|
bot->GetPositionZ())));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Lets find some water where we can fish.
|
||||||
|
WorldPosition water = FindWaterRadial(
|
||||||
|
bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
||||||
|
bot->GetMap(), bot->GetPhaseMask(),
|
||||||
|
MIN_DISTANCE_TO_WATER,
|
||||||
|
fishingSearchWindow + MAX_DISTANCE_TO_WATER,
|
||||||
|
SEARCH_INCREMENT, false);
|
||||||
|
|
||||||
|
if (!water.IsValid())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool hasLOS = bot->IsWithinLOS(water.GetPositionX(), water.GetPositionY(), water.GetPositionZ());
|
||||||
|
float angle = bot->GetAngle(water.GetPositionX(), water.GetPositionY());
|
||||||
|
WorldPosition landSpot =
|
||||||
|
FindLandFromPosition(botAI, 0.0f, MAX_DISTANCE_TO_WATER, 1.0f, angle, water, fishingSearchWindow, false);
|
||||||
|
|
||||||
|
if (landSpot.IsValid())
|
||||||
|
{
|
||||||
|
SET_AI_VALUE(WorldPosition, "fishing spot", landSpot);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EquipFishingPoleAction::Execute(Event event)
|
||||||
|
{
|
||||||
|
if (!_pole)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
WorldPacket eqPacket(CMSG_AUTOEQUIP_ITEM_SLOT, 2);
|
||||||
|
eqPacket << _pole->GetGUID() << uint8(EQUIPMENT_SLOT_MAINHAND);
|
||||||
|
WorldPackets::Item::AutoEquipItemSlot nicePacket(std::move(eqPacket));
|
||||||
|
nicePacket.Read();
|
||||||
|
bot->GetSession()->HandleAutoEquipItemSlotOpcode(nicePacket);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EquipFishingPoleAction::isUseful()
|
||||||
|
{
|
||||||
|
Item* mainHand = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
|
||||||
|
if (IsFishingPole(mainHand))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; ++slot)
|
||||||
|
{
|
||||||
|
if (Item* item = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot))
|
||||||
|
{
|
||||||
|
if (IsFishingPole(item))
|
||||||
|
{
|
||||||
|
_pole = item;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag)
|
||||||
|
{
|
||||||
|
if (Bag* pBag = bot->GetBagByPos(bag))
|
||||||
|
{
|
||||||
|
for (uint32 j = 0; j < pBag->GetBagSize(); ++j)
|
||||||
|
{
|
||||||
|
if (Item* item = pBag->GetItemByPos(j))
|
||||||
|
{
|
||||||
|
if (IsFishingPole(item))
|
||||||
|
{
|
||||||
|
_pole = item;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sRandomPlayerbotMgr->IsRandomBot(bot))
|
||||||
|
{
|
||||||
|
bot->StoreNewItemInBestSlots(FISHING_POLE, 1); // Try to get a fishing pole
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player* master = botAI->GetMaster();
|
||||||
|
if (!master)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::string masterName = master->GetName();
|
||||||
|
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
||||||
|
"no_fishing_pole_error", "I don't have a Fishing Pole",{});
|
||||||
|
botAI->Whisper(text, masterName);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FishingAction::Execute(Event event)
|
||||||
|
{
|
||||||
|
WorldPosition target = WorldPosition();
|
||||||
|
WorldPosition fishingHole = FindFishingHole(botAI);
|
||||||
|
if (fishingHole.IsValid())
|
||||||
|
{
|
||||||
|
Position pos = fishingHole;
|
||||||
|
float distance = bot->GetExactDist2d(&pos);
|
||||||
|
bool hasLOS = bot->IsWithinLOS(fishingHole.GetPositionX(), fishingHole.GetPositionY(), fishingHole.GetPositionZ());
|
||||||
|
if (distance < MAX_DISTANCE_TO_WATER &&
|
||||||
|
distance > MIN_DISTANCE_TO_WATER && hasLOS)
|
||||||
|
target = fishingHole;
|
||||||
|
}
|
||||||
|
if (!target.IsValid())
|
||||||
|
{
|
||||||
|
target = FindWaterRadial(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||||||
|
bot->GetPositionZ(), bot->GetMap(), bot->GetPhaseMask(),
|
||||||
|
MIN_DISTANCE_TO_WATER, MAX_DISTANCE_TO_WATER, SEARCH_INCREMENT, true, 32);
|
||||||
|
if (!target.IsValid())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Position pos = target;
|
||||||
|
|
||||||
|
if (!bot->HasInArc(1.0, &pos, 1.0))
|
||||||
|
{
|
||||||
|
float angle = bot->GetAngle(pos.GetPositionX(), pos.GetPositionY());
|
||||||
|
bot->SetOrientation(angle);
|
||||||
|
if (!bot->IsRooted())
|
||||||
|
bot->SendMovementFlagUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
EquipFishingPoleAction equipAction(botAI);
|
||||||
|
if (equipAction.isUseful())
|
||||||
|
return equipAction.Execute(event);
|
||||||
|
|
||||||
|
botAI->CastSpell(FISHING_SPELL, bot);
|
||||||
|
botAI->ChangeStrategy("+use bobber", BOT_STATE_NON_COMBAT);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FishingAction::isUseful()
|
||||||
|
{
|
||||||
|
if (!AI_VALUE(bool, "can fish"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
FishingSpotValue* fishingSpotValueObject = (FishingSpotValue*)context->GetValue<WorldPosition>("fishing spot");
|
||||||
|
WorldPosition pos = fishingSpotValueObject->Get();
|
||||||
|
|
||||||
|
if (!pos.IsValid() || fishingSpotValueObject->IsStale(FISHING_LOCATION_TIMEOUT))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return bot->GetExactDist(&pos) < 0.1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UseBobberAction::isUseful()
|
||||||
|
{
|
||||||
|
return AI_VALUE(bool, "can use fishing bobber");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UseBobberAction::Execute(Event event)
|
||||||
|
{
|
||||||
|
GuidVector gos = AI_VALUE(GuidVector, "nearest game objects no los");
|
||||||
|
for (auto const& guid : gos)
|
||||||
|
{
|
||||||
|
if (GameObject* go = botAI->GetGameObject(guid))
|
||||||
|
{
|
||||||
|
if (go->GetEntry() != FISHING_BOBBER)
|
||||||
|
continue;
|
||||||
|
if (go->GetOwnerGUID() != bot->GetGUID())
|
||||||
|
continue;
|
||||||
|
if (go->getLootState() == GO_READY)
|
||||||
|
{
|
||||||
|
go->Use(bot);
|
||||||
|
botAI->ChangeStrategy("-use bobber", BOT_STATE_NON_COMBAT);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EndMasterFishingAction::Execute(Event event)
|
||||||
|
{
|
||||||
|
botAI->ChangeStrategy("-master fishing", BOT_STATE_NON_COMBAT);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EndMasterFishingAction::isUseful()
|
||||||
|
{
|
||||||
|
FishingSpotValue* fishingSpotValueObject = (FishingSpotValue*)context->GetValue<WorldPosition>("fishing spot");
|
||||||
|
WorldPosition pos = fishingSpotValueObject->Get();
|
||||||
|
if (pos.IsValid() && !fishingSpotValueObject->IsStale(FISHING_LOCATION_TIMEOUT) && pos == bot->GetPosition())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
WorldPosition nearWater = FindWaterRadial(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
||||||
|
bot->GetMap(), bot->GetPhaseMask(), MIN_DISTANCE_TO_WATER, sPlayerbotAIConfig->endFishingWithMaster, 10.0f);
|
||||||
|
return !nearWater.IsValid();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RemoveBobberStrategyAction::Execute(Event event)
|
||||||
|
{
|
||||||
|
botAI->ChangeStrategy("-use bobber", BOT_STATE_NON_COMBAT);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
71
src/Bot/BaseAi/Actions/FishingAction.h
Normal file
71
src/Bot/BaseAi/Actions/FishingAction.h
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||||
|
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PLAYERBOT_FISHINGACTION_H
|
||||||
|
#define _PLAYERBOT_FISHINGACTION_H
|
||||||
|
|
||||||
|
#include "Action.h"
|
||||||
|
#include "MovementActions.h"
|
||||||
|
#include "Event.h"
|
||||||
|
#include "Playerbots.h"
|
||||||
|
|
||||||
|
extern const uint32 FISHING_SPELL;
|
||||||
|
extern const uint32 FISHING_POLE;
|
||||||
|
extern const uint32 FISHING_BOBBER;
|
||||||
|
|
||||||
|
WorldPosition FindWaterRadial(Player* bot, float x, float y, float z, Map* map, uint32 phaseMask, float minDistance, float maxDistance, float increment, bool checkLOS=false, int numDirections = 16);
|
||||||
|
|
||||||
|
class PlayerbotAI;
|
||||||
|
|
||||||
|
class FishingAction : public Action
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FishingAction(PlayerbotAI* botAI) : Action(botAI, "go fishing"){}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
bool isUseful() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class EquipFishingPoleAction : public Action
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EquipFishingPoleAction(PlayerbotAI* botAI) : Action(botAI, "equip fishing pole") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
bool isUseful() override;
|
||||||
|
private:
|
||||||
|
Item* _pole = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MoveNearWaterAction : public MovementAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MoveNearWaterAction(PlayerbotAI* botAI): MovementAction(botAI, "move near water") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
bool isUseful() override;
|
||||||
|
bool isPossible() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class UseBobberAction : public Action
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
UseBobberAction(PlayerbotAI* botAI) : Action(botAI, "use fishing bobber") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
bool isUseful() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class EndMasterFishingAction : public Action
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EndMasterFishingAction(PlayerbotAI* botAI) : Action(botAI, "end master fishing") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
bool isUseful() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RemoveBobberStrategyAction : public Action
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RemoveBobberStrategyAction(PlayerbotAI* botAI) : Action(botAI, "remove bobber strategy") {}
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
@@ -70,7 +70,7 @@ bool FollowAction::isUseful()
|
|||||||
if (!target.empty())
|
if (!target.empty())
|
||||||
fTarget = AI_VALUE(Unit*, target);
|
fTarget = AI_VALUE(Unit*, target);
|
||||||
else
|
else
|
||||||
fTarget = AI_VALUE(Unit*, "master target");
|
fTarget = AI_VALUE(Unit*, "group leader");
|
||||||
|
|
||||||
if (fTarget)
|
if (fTarget)
|
||||||
{
|
{
|
||||||
@@ -97,6 +97,8 @@ bool FollowAction::isUseful()
|
|||||||
|
|
||||||
distance = bot->GetDistance(loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ());
|
distance = bot->GetDistance(loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ());
|
||||||
}
|
}
|
||||||
|
if (botAI->HasStrategy("master fishing", BOT_STATE_NON_COMBAT))
|
||||||
|
return sServerFacade->IsDistanceGreaterThan(distance, sPlayerbotAIConfig->fishingDistanceFromMaster);
|
||||||
|
|
||||||
return sServerFacade->IsDistanceGreaterThan(distance, formation->GetMaxDistance());
|
return sServerFacade->IsDistanceGreaterThan(distance, formation->GetMaxDistance());
|
||||||
}
|
}
|
||||||
@@ -114,9 +116,9 @@ bool FollowAction::CanDeadFollow(Unit* target)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FleeToMasterAction::Execute(Event event)
|
bool FleeToGroupLeaderAction::Execute(Event event)
|
||||||
{
|
{
|
||||||
Unit* fTarget = AI_VALUE(Unit*, "master target");
|
Unit* fTarget = AI_VALUE(Unit*, "group leader");
|
||||||
bool canFollow = Follow(fTarget);
|
bool canFollow = Follow(fTarget);
|
||||||
if (!canFollow)
|
if (!canFollow)
|
||||||
{
|
{
|
||||||
@@ -146,22 +148,22 @@ bool FleeToMasterAction::Execute(Event event)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FleeToMasterAction::isUseful()
|
bool FleeToGroupLeaderAction::isUseful()
|
||||||
{
|
{
|
||||||
if (!botAI->GetGroupMaster())
|
if (!botAI->GetGroupLeader())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (botAI->GetGroupMaster() == bot)
|
if (botAI->GetGroupLeader() == bot)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Unit* target = AI_VALUE(Unit*, "current target");
|
Unit* target = AI_VALUE(Unit*, "current target");
|
||||||
if (target && botAI->GetGroupMaster()->GetTarget() == target->GetGUID())
|
if (target && botAI->GetGroupLeader()->GetTarget() == target->GetGUID())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
|
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Unit* fTarget = AI_VALUE(Unit*, "master target");
|
Unit* fTarget = AI_VALUE(Unit*, "group leader");
|
||||||
|
|
||||||
if (!CanDeadFollow(fTarget))
|
if (!CanDeadFollow(fTarget))
|
||||||
return false;
|
return false;
|
||||||
@@ -20,10 +20,10 @@ public:
|
|||||||
bool CanDeadFollow(Unit* target);
|
bool CanDeadFollow(Unit* target);
|
||||||
};
|
};
|
||||||
|
|
||||||
class FleeToMasterAction : public FollowAction
|
class FleeToGroupLeaderAction : public FollowAction
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
FleeToMasterAction(PlayerbotAI* botAI) : FollowAction(botAI, "flee to master") {}
|
FleeToGroupLeaderAction(PlayerbotAI* botAI) : FollowAction(botAI, "flee to group leader") {}
|
||||||
|
|
||||||
bool Execute(Event event) override;
|
bool Execute(Event event) override;
|
||||||
bool isUseful() override;
|
bool isUseful() override;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user