Compare commits

..

4 Commits

Author SHA1 Message Date
bash
adaf1e6b91 Update 2024_11_25_00.sql 2024-11-25 23:44:01 +01:00
bash
733c64343e Update 2024_11_25_00.sql 2024-11-25 23:37:19 +01:00
bash
e3bcadb19e Rename 2024-11-25 23:35:36 +01:00
bash
27d1bda34d database update data folder location 2024-11-25 18:38:55 +01:00
1044 changed files with 6359 additions and 27195 deletions

View File

@@ -7,30 +7,29 @@ assignees: ''
--- ---
**Bug Description** **Describe the bug**
A clear and concise description of what the bug is. If the bug is a crash, a crash log must be posted or the issue will be removed. A clear and concise description of what the bug is.
**Commit Hash** **Commit hash**
The hash of the current commit. The hash of the current commit.
**How To Reproduce Bug** **To Reproduce**
Detailed steps to reproduce the behavior. Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected Behavior** **Expected behavior**
A clear and concise description of what you expected to happen. A clear and concise description of what you expected to happen.
**Screenshots** **Screenshots**
If applicable, add screenshots to help explain your problem. If applicable, add screenshots to help explain your problem.
**Modules** **Desktop (please complete the following information):**
Please list all modules used as many are known to cause conflicts with Playerbots. - OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Playerbot Settings** **Additional context**
Number of bots, scaling settings, etc if performance related.
**System**
OS: [e.g. Windows, Linux]
Hardware: [e.g. CPU if performance related]
**Additional Context**
Add any other context about the problem here. Add any other context about the problem here.

View File

@@ -6,10 +6,6 @@ on:
pull_request: pull_request:
branches: [ "master" ] branches: [ "master" ]
concurrency:
group: "codestyle-${{ github.event.pull_request.number }}"
cancel-in-progress: true
jobs: jobs:
lint: lint:
name: "clang-format-always-success" name: "clang-format-always-success"
@@ -31,4 +27,4 @@ jobs:
# Check if there are any formatting changes # Check if there are any formatting changes
git diff --exit-code git diff --exit-code
shell: bash shell: bash
continue-on-error: true continue-on-error: true

View File

@@ -6,10 +6,6 @@ on:
pull_request: pull_request:
branches: [ "master" ] branches: [ "master" ]
concurrency:
group: "core-build-${{ github.event.pull_request.number }}"
cancel-in-progress: true
jobs: jobs:
build: build:
strategy: strategy:

View File

@@ -5,9 +5,9 @@ on:
pull_request: pull_request:
branches: [ "master" ] branches: [ "master" ]
concurrency: # concurrency:
group: "macos-build-${{ github.event.pull_request.number }}" # group: ${{ github.head_ref }} || concat(${{ github.ref }}, ${{ github.workflow }})
cancel-in-progress: true # cancel-in-progress: true
jobs: jobs:
macos-build: macos-build:
@@ -42,4 +42,4 @@ jobs:
- name: Configure OS - name: Configure OS
run: source ./acore.sh install-deps run: source ./acore.sh install-deps
- name: Build - name: Build
run: source ./apps/ci/mac/ci-compile.sh run: source ./apps/ci/mac/ci-compile.sh

View File

@@ -5,10 +5,6 @@ on:
pull_request: pull_request:
branches: [ "master" ] branches: [ "master" ]
concurrency:
group: "windows-build-${{ github.event.pull_request.number }}"
cancel-in-progress: true
jobs: jobs:
windows-build: windows-build:
strategy: strategy:

110
README.md
View File

@@ -1,38 +1,20 @@
<p align="center"> [English](README.md) | [Español](README_ES.md) | [中文](README_CN.md)
<a href="https://github.com/liyunfan1223/mod-playerbots/blob/master/README.md">English</a>
|
<a href="https://github.com/liyunfan1223/mod-playerbots/blob/master/README_CN.md">中文</a>
</p>
<div align="center">
<img src="icon.png" alt="Playerbots Icon" width="700px">
</div>
<div align="center">
<img src="https://github.com/liyunfan1223/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/liyunfan1223/mod-playerbots/actions/workflows/windows_build.yml/badge.svg">
</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). Features include:
- Bots that utilize real player data, allowing players to interact with their other characters, form parties, level up, and more; Welcome to the Playerbots Module for AzerothCore, a work in progress project based on the IKE3 Playerbots. These Playerbots utilize actual player data, allowing you to interact with your own alts, form parties, level up characters, and much more.
- Random bots that wander through the world and behave like players, simulating the MMO experience;
- Bots capable of running raids and battlegrounds;
- Highly configurable settings to define how bots behave;
- 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. If you encounter any errors or experience crashes, we kindly request that you report them as GitHub issues. Your valuable feedback will help us improve and enhance this project collaboratively.
**Playerbots Module** has a **[Discord server](https://discord.gg/NQm5QShwf9)** where you can discuss the project. You can also get more information in our [discord](https://discord.gg/NQm5QShwf9).
## Installation ## Installation
### Classic Installation Please note that this module requires specific custom changes to AzerothCore. To ensure compatibility, you must compile it with a custom branch from my fork, which can be found here: [liyunfan1223/azerothcore-wotlk/tree/Playerbot](https://github.com/liyunfan1223/azerothcore-wotlk/tree/Playerbot).
`mod-playerbots` requires a custom branch of AzerothCore to work: [liyunfan1223/azerothcore-wotlk/tree/Playerbot](https://github.com/liyunfan1223/azerothcore-wotlk/tree/Playerbot). To install the module, simply run: To install this module, please refer to the AzerothCore Wiki for detailed instructions: [AzerothCore Installation Guide](https://www.azerothcore.org/wiki/installation).
We've provided a simple method to clone the module:
```bash ```bash
git clone https://github.com/liyunfan1223/azerothcore-wotlk.git --branch=Playerbot git clone https://github.com/liyunfan1223/azerothcore-wotlk.git --branch=Playerbot
@@ -40,69 +22,57 @@ cd azerothcore-wotlk/modules
git clone https://github.com/liyunfan1223/mod-playerbots.git --branch=master git clone https://github.com/liyunfan1223/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. ## Quick Start & Documentation
### Docker Installation For a quick start and an extensive overview of available addons, commands, and recommended configuration please refer to the [Playerbots Wiki](https://github.com/liyunfan1223/mod-playerbots/wiki).
**Docker installation is considered experimental.** To install the module on a Docker installation, run: Please be aware that documentation for some newly added commands is currently lacking as the project is still under development.
```bash ## Progress
git clone https://github.com/liyunfan1223/azerothcore-wotlk.git --branch=Playerbot
cd azerothcore-wotlk/modules
git clone https://github.com/liyunfan1223/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: The module primarily emphasizes the following key features, and we have implemented improvements in these areas:
```yml - **Bots in World (Random bot):** We have enhanced the behavior of random bots to make them mimic real players more closely, creating a more authentic player server environment.
services:
ac-worldserver:
volumes:
- ./modules:/azerothcore/modules:ro
```
Additionally, this override file can be used to set custom configuration settings for `ac-worldserver` and any modules you install as environment variables: - **Bots in Raid:** We've empowered bots to conquer challenging raid content by implementing specific strategies for various bosses, making raid encounters more engaging. Additionally, we have enhanced bots' capabilities in various roles such as DPS, healing, and tanking, ensuring they contribute effectively to the success of raid groups.
```yml - **Bots in Battleground:** Bots are now capable of actively participating in battlegrounds alongside real players, adding depth and excitement to these PvP scenarios.
services:
ac-worldserver:
environment:
AC_RATE_XP_KILL: "1"
AC_AI_PLAYERBOT_RANDOM_BOT_AUTOLOGIN: "1"
volumes:
- ./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. - **Interation with Bots:** We have improved the interaction between real players and bots, enabling players to complete quests and level up with multiple characters while collaborating with the bot companions.
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. - **Player Progression Path:** We have designed an improved progression path for players, complemented by bots, to offer an alternative and engaging gameplay experience.
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. - **Stability:** Our efforts have focused on enhancing the overall stability of AzerothCore when using the Playerbots module. These improvements aim to prevent server crashes and ensure a smoother experience for all users.
## Documentation - **Configuration:** We have introduced a range of configurable options to cater to players with varying requirements, allowing for a more personalized experience.
The [Playerbots Wiki](https://github.com/liyunfan1223/mod-playerbots/wiki) contains an extensive overview of addons, commands, and recommended configurations. Please note that documentation may be incomplete or out-of-date in some sections. Contributions are welcome. It's essential to note that there is still a significant amount of work to be done as we continue to enhance the project. We welcome everyone to contribute in various ways.
## Frequently Asked Questions ## Addon
- **Why aren't my bots casting spells?** Please make sure that the necessary English DBC file (enUS) is present.
- **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 [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
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:
For enhanced control over the bots and to simplify command usage, you can also make use of available Playerbots addons:
- [Multibot](https://github.com/Macx-Lio/MultiBot) (by Macx-Lio) - [Multibot](https://github.com/Macx-Lio/MultiBot) (by Macx-Lio)
- [Unbot Addon (zh)](https://github.com/liyunfan1223/unbot-addon) (Chinese version by Liyunfan) - [Unbot Addon (zh)](https://github.com/liyunfan1223/unbot-addon) (Chinese version by Liyunfan)
- [Unbot Addon (en)](https://github.com/noisiver/unbot-addon/tree/english) (English version translated by @Revision) - [Unbot Addon (en)](https://github.com/noisiver/unbot-addon/tree/english) (English version translated by @Revision)
## Frequently Asked Questions
**Bots can't cast spells**
- Please make sure that the necessary English DBC file (enUS) is present.
**Compilation error**
- We support for Ubuntu, Windows, and macOS.
- Continuous integration workflows have been established. You can review the build status in [GitHub Actions](https://github.com/liyunfan1223/mod-playerbots/actions).
- If the latest build status fails, please revert to the previous commit. We will address the issue ASAP.
## 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. The code for this module is ported from [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 and @celguar for the continued efforts in maintaining the module.
We also want to express our sincere appreciation to all individuals who have contributed to playerbot development. Your dedication and efforts have been instrumental in shaping this project, and we are thankful for your contributions.
Also, a thank you to the many contributors who've helped build this project:
<a href="https://github.com/liyunfan1223/mod-playerbots/graphs/contributors">
<img src="https://contrib.rocks/image?repo=liyunfan1223/mod-playerbots" />
</a>

41
README_example.md Normal file
View File

@@ -0,0 +1,41 @@
# MY_NEW_MODULE
## Description
This module allows to do this and this.
## How to use ingame
Do this and that.
![my_new_module screenshot](/screenshots/my_module.png?raw=true "my_new_module screenshot")
<!-- Video example - We can't embed videos on github, only on github.io pages. If you can, make an animated gif of your video instead (but it's not required) -->
[![Youtube Link](https://i.imgur.com/Jhrdgv6.png)](https://www.youtube.com/watch?v=T6UEX47mPeE)
## Requirements
My_new_module requires:
- AzerothCore v4.0.0+
## Installation
```
1) Simply `git clone` the module under the `modules` directory of your AzerothCore source or copy paste it manually.
2) Import the SQL manually to the right Database (auth, world or characters) or with the `db_assembler.sh` (if `include.sh` provided).
3) Re-run cmake and launch a clean build of AzerothCore.
```
## Edit the module's configuration (optional)
If you need to change the module configuration, go to your server configuration directory (where your `worldserver` or `worldserver.exe` is), copy `my_module.conf.dist` to `my_module.conf` and edit that new file.
## Credits
* [Me](https://github.com/YOUR_GITHUB_NAME) (author of the module): Check out my soundcloud - Join my discord
* AzerothCore: [repository](https://github.com/azerothcore) - [website](http://azerothcore.org/) - [discord chat community](https://discord.gg/PaqQRkd)

View File

@@ -5,59 +5,57 @@
################################################################################################### ###################################################################################################
# SECTION INDEX # SECTION INDEX
# GENERAL SETTINGS # GENERAL SETTINGS
# PLAYERBOT SETTINGS # PLAYERBOT SETTINGS
# GENERAL # GENERAL
# SUMMON OPTIONS # SUMMON OPTIONS
# MOUNT # GEAR
# GEAR # LOOTING
# LOOTING # TIMERS
# TIMERS # DISTANCES
# DISTANCES # THRESHOLDS
# THRESHOLDS # QUESTS
# QUESTS # COMBAT
# COMBAT # CHEATS
# CHEATS # SPELLS
# SPELLS # PLAYERBOT RNDBOT SPECIFIC SETTINGS
# PLAYERBOT RNDBOT SPECIFIC SETTINGS # GENERAL
# GENERAL # LEVELS
# LEVELS # GEAR
# GEAR # QUESTS
# QUESTS # SPELLS
# ACTIVITIES # STRATEGIES
# SPELLS # TELEPORTS
# STRATEGIES # BATTLEGROUND & ARENA & PVP
# TELEPORTS # INTERVALS
# BATTLEGROUND & ARENA & PVP # PREMADE SPECS
# INTERVALS # INFORMATION
# PREMADE SPECS # WARRIOR
# INFORMATION # PALADIN
# WARRIOR # HUNTER
# PALADIN # ROGUE
# HUNTER # PRIEST
# ROGUE # DEATHKNIGHT
# PRIEST # SHAMAN
# DEATHKNIGHT # MAGE
# SHAMAN # WARLOCK
# MAGE # DRUID
# WARLOCK # RANDOM BOT DEFAULT TALENT SPEC
# DRUID # WARRIOR
# RANDOM BOT DEFAULT TALENT SPEC # PALADIN
# WARRIOR # HUNTER
# PALADIN # ROGUE
# HUNTER # PRIEST
# ROGUE # DEATHKNIGHT
# PRIEST # SHAMAN
# DEATHKNIGHT # MAGE
# SHAMAN # WARLOCK
# MAGE # DRUID
# WARLOCK # PLAYERBOT SYSTEM SETTINGS
# DRUID # DATABASE & CONNECTIONS
# PLAYERBOT SYSTEM SETTINGS # DEBUG
# DATABASE & CONNECTIONS # CHAT SETTINGS
# DEBUG # LOGS
# CHAT SETTINGS # DEPRECIATED (TEMPORARY)
# LOGS
# DEPRECIATED (TEMPORARY)
# #
# #
# #
@@ -69,7 +67,7 @@
################################### ###################################
# # # #
# GENERAL SETTINGS # # GENERAL SETTINGS #
# # # #
################################### ###################################
@@ -81,15 +79,13 @@ AiPlayerbot.Enabled = 1
# Enable random bot system # Enable random bot system
AiPlayerbot.RandomBotAutologin = 1 AiPlayerbot.RandomBotAutologin = 1
# Random bot count # Random bot account
AiPlayerbot.MinRandomBots = 500 # Please ensure that RandomBotAccountCount is greater than (MaxRandomBots / 10 + AddClassAccountPoolSize)
AiPlayerbot.MaxRandomBots = 500 AiPlayerbot.RandomBotAccountCount = 200
# Random bot accounts # Random bot count
# If you are not using any expansion at all, you may have to set this manually, then AiPlayerbot.MinRandomBots = 50
# please ensure that RandomBotAccountCount is at least greater than (MaxRandomBots / 10 + AddClassAccountPoolSize) AiPlayerbot.MaxRandomBots = 50
# default: 0 - Automatic
AiPlayerbot.RandomBotAccountCount = 0
# Delete all random bot accounts (reset randombots) # Delete all random bot accounts (reset randombots)
AiPlayerbot.DeleteRandomBotAccounts = 0 AiPlayerbot.DeleteRandomBotAccounts = 0
@@ -109,14 +105,18 @@ AiPlayerbot.DeleteRandomBotAccounts = 0
# #
# #
# The maximum number of bots that a player can control simultaneously # Maximum number of bots added by one account
AiPlayerbot.MaxAddedBots = 40 AiPlayerbot.MaxAddedBots = 40
# Maximum number of bots per class added by one account
AiPlayerbot.MaxAddedBotsPerClass = 40
# Enable/Disable create bot by addclass command (0 = GM only, 1 = enable) # Enable/Disable create bot by addclass command (0 = GM only, 1 = enable)
# default: 1 (enable) # default: 1 (enable)
AiPlayerbot.AddClassCommand = 1 AiPlayerbot.AddClassCommand = 1
# Set the addclass command account pool size # Set the addclass command account pool size
# Addclass command uses a subset of accounts from RandomBotAccountCount
AiPlayerbot.AddClassAccountPoolSize = 50 AiPlayerbot.AddClassAccountPoolSize = 50
# Bot group invitation permission level (0 = GM only, 1 = accept based on level, 2 = always accept) # Bot group invitation permission level (0 = GM only, 1 = accept based on level, 2 = always accept)
@@ -126,21 +126,13 @@ AiPlayerbot.GroupInvitationPermission = 1
# auto-login all player alts as bots on player login # auto-login all player alts as bots on player login
AiPlayerbot.BotAutologin = 0 AiPlayerbot.BotAutologin = 0
# Allow/deny bots from the player's account # Allow login other players' characters as bots
AiPlayerbot.AllowAccountBots = 1 # Default: 0 (disabled)
AiPlayerbot.AllowPlayerBots = 0
# Allow/deny bots in the player's guild # Allow/deny bots from your guild
AiPlayerbot.AllowGuildBots = 1 AiPlayerbot.AllowGuildBots = 1
# Allow linking accounts for shared alt-bot control
AiPlayerbot.AllowTrustedAccountBots = 1
# Random bot guild count
AiPlayerbot.RandomBotGuildCount = 20
# Delete all random bot guilds
AiPlayerbot.DeleteRandomBotGuilds = 0
# Randombots will invite nearby bots to guilds # Randombots will invite nearby bots to guilds
AiPlayerbot.RandomBotGuildNearby = 0 AiPlayerbot.RandomBotGuildNearby = 0
@@ -207,36 +199,6 @@ AiPlayerbot.BotRepairWhenSummon = 1
# #
#################################################################################################### ####################################################################################################
####################################################################################################
# MOUNT
#
#
# Defines at what level a bot will naturally use its 60% ground mount
# note: was level 20 during WotLK, 30 during TBC, 40 during Vanilla
# default: 20
AiPlayerbot.UseGroundMountAtMinLevel = 20
# Defines at what level a bot will naturally use its 100% fast ground mount
# note: was level 40 during WotLK, 60 during Vanilla
# default: 40
AiPlayerbot.UseFastGroundMountAtMinLevel = 40
# Defines at what level a bot will naturally use its 150% fly mount
# note: was level 60 during WotLK, 70 during TBC
# default: 60
AiPlayerbot.UseFlyMountAtMinLevel = 60
# Defines at what level a bot will naturally use its 280% fast fly mount
# note: was level 70 during WotLK and TBC
# default: 70
AiPlayerbot.UseFastFlyMountAtMinLevel = 70
#
#
#
####################################################################################################
#################################################################################################### ####################################################################################################
# GEAR # GEAR
# #
@@ -372,9 +334,9 @@ AiPlayerbot.AggroDistance = 22
# #
# #
# Set XP rate for bots (default: 1.0) # Set kill XP rate for bots (default: 1)
# Server XP Rate * AiPlayerbot.PlayerbotsXPRate # Server XP Rate * AiPlayerbot.KillXPRate
AiPlayerbot.PlayerbotsXPRate = 1.0 AiPlayerbot.KillXPRate = 1
# Health/Mana levels # Health/Mana levels
AiPlayerbot.CriticalHealth = 25 AiPlayerbot.CriticalHealth = 25
@@ -406,10 +368,6 @@ AiPlayerbot.SyncQuestWithPlayer = 1
# Default: 0 (disabled) # Default: 0 (disabled)
AiPlayerbot.SyncQuestForPlayer = 0 AiPlayerbot.SyncQuestForPlayer = 0
# Bots will drop obsolete quests
# Default: 1 (enabled)
AiPlayerbot.DropObsoleteQuests = 1
# #
# #
# #
@@ -462,10 +420,6 @@ AiPlayerbot.MaintenanceCommand = 1
# default: 1 (enable) # default: 1 (enable)
AiPlayerbot.AutoGearCommand = 1 AiPlayerbot.AutoGearCommand = 1
# Enable/Disable autogear command on player alt bots
# Default: 1 (enable)
AiPlayerbot.AutoGearCommandAltBots = 1
# Equips quality limitation for auto gear command (1 = normal, 2 = uncommon, 3 = rare, 4 = epic, 5 = legendary) # Equips quality limitation for auto gear command (1 = normal, 2 = uncommon, 3 = rare, 4 = epic, 5 = legendary)
# default: 3 (rare) # default: 3 (rare)
AiPlayerbot.AutoGearQualityLimit = 3 AiPlayerbot.AutoGearQualityLimit = 3
@@ -512,6 +466,14 @@ AiPlayerbot.RandomBotRandomPassword = 0
# Accounts to create for random bots # Accounts to create for random bots
AiPlayerbot.RandomBotAccountPrefix = "rndbot" AiPlayerbot.RandomBotAccountPrefix = "rndbot"
# Enable/Disable rotation of bots (randomly select a bot from the bots pool to go online and rotate them periodically)
# Need to reset rndbot after changing the setting (.playerbot rndbot reset)
# default: 0 (disable, the online bots are fixed)
AiPlayerbot.EnableRotation = 0
# Bots pool size for rotation (should be less than RandomBotAccountCount * 10)
AiPlayerbot.RotationPoolSize = 500
AiPlayerbot.RandomBotMinLevel = 1 AiPlayerbot.RandomBotMinLevel = 1
AiPlayerbot.RandomBotMaxLevel = 80 AiPlayerbot.RandomBotMaxLevel = 80
@@ -525,31 +487,10 @@ AiPlayerbot.PreQuests = 0
# Enable LFG for random bots # Enable LFG for random bots
AiPlayerbot.RandomBotJoinLfg = 1 AiPlayerbot.RandomBotJoinLfg = 1
# Enable/Disable periodic online - offline to mimic the real-world scenario where not all players are online simultaneously
# When enabled, bots are randomly selected to go online or offline periodically from a larger set
# Default: 0 (disabled, the set of online bots remains fixed)
AiPlayerbot.EnablePeriodicOnlineOffline = 0
# Defines the ratio between the total number of bots (including offline ones) and the number of bots currently online (MaxRandomBots)
# This setting must greater than 1.0 and only applies when EnablePeriodicOnlineOffline
# Default: 2.0 (total number of bots is twice the number of MaxRandomBots)
AiPlayerbot.PeriodicOnlineOfflineRatio = 2.0
# Percentage ratio of alliance and horde
AiPlayerbot.RandomBotAllianceRatio = 50
AiPlayerbot.RandomBotHordeRatio = 50
# Disable death knight for bots login # Disable death knight for bots login
# Need to reset rndbot after changing the setting (.playerbot rndbot reset)
AiPlayerbot.DisableDeathKnightLogin = 0 AiPlayerbot.DisableDeathKnightLogin = 0
# Enable expansion limitation for talents and glyphs - ie: level <= 60 bot only uses talents
# available in vanilla, level <= 70 bot only uses talents available in TBC)
# Default: 0
AiPlayerbot.LimitTalentsExpansion = 0
# Configure random bots and addClass bot trading (0: Disabled, 1: Enabled, 2: Only buy, 3: Only Sell)
# Default: 1 (Enabled)
AiPlayerbot.EnableRandomBotTrading = 1
# #
# #
# #
@@ -565,13 +506,10 @@ AiPlayerbot.EnableRandomBotTrading = 1
AiPlayerbot.DisableRandomLevels = 0 AiPlayerbot.DisableRandomLevels = 0
# Set randombots starting level here if "AiPlayerbot.DisableRandomLevels" enabled # Set randombots starting level here if "AiPlayerbot.DisableRandomLevels" enabled
AiPlayerbot.RandombotStartingLevel = 1 AiPlayerbot.RandombotStartingLevel = 5
# Chance random bot has min level on first randomize (default 0.1) # Chance random bot has max level on first randomize (default 0.15)
AiPlayerbot.RandomBotMinLevelChance = 0.1 AiPlayerbot.RandomBotMaxLevelChance = 0.15
# Chance random bot has max level on first randomize (default 0.1)
AiPlayerbot.RandomBotMaxLevelChance = 0.1
# Fix the level of random bot (won't level up by grinding) # Fix the level of random bot (won't level up by grinding)
# Default: 0 (disable) # Default: 0 (disable)
@@ -630,50 +568,6 @@ AiPlayerbot.EquipmentPersistenceLevel = 80
# Default: 1 (enabled) # Default: 1 (enabled)
AiPlayerbot.AutoUpgradeEquip = 1 AiPlayerbot.AutoUpgradeEquip = 1
# Only set wolf pets for hunters to have higher damage (0 = disabled, 1 = enabled only for max-level, 2 = enabled)
# Default: 0 (disabled)
AiPlayerbot.HunterWolfPet = 0
#
#
#
####################################################################################################
####################################################################################################
# ACTIVITIES
#
#
# Specify percent of active bots
# The default is 100%, but would be automatically adjusted if botActiveAloneSmartScale
# is enabled. Regardless, this value is only applied to inactive areas where no real-players
# are detected. When real-players are nearby, friend, group, guild, BGs, instances etc,
# the value is always enforced to 100%
AiPlayerbot.BotActiveAlone = 100
# Force botActiveAlone when bot is ... of real player
AiPlayerbot.BotActiveAloneForceWhenInRadius = 150
AiPlayerbot.BotActiveAloneForceWhenInZone = 1
AiPlayerbot.BotActiveAloneForceWhenInMap = 0
AiPlayerbot.BotActiveAloneForceWhenIsFriend = 1
AiPlayerbot.BotActiveAloneForceWhenInGuild = 1
# SmartScale is enabled or not.
# The default is 1. When enabled (smart) scales the 'BotActiveAlone' value.
# (The scaling will be overruled by the BotActiveAloneForceWhen...rules)
#
# Limitfloor - when DIFF (latency) above floor, activity scaling is applied starting with 90%
# LimitCeiling - when DIFF (latency) above ceiling, activity is 0%;
#
# MinLevel - only apply scaling when level is above or equal to min(bot)Level
# MaxLevel - only apply scaling when level is lower or equal of max(bot)Level
#
AiPlayerbot.botActiveAloneSmartScale = 1
AiPlayerbot.botActiveAloneSmartScaleDiffLimitfloor = 50
AiPlayerbot.botActiveAloneSmartScaleDiffLimitCeiling = 200
AiPlayerbot.botActiveAloneSmartScaleWhenMinLevel = 1
AiPlayerbot.botActiveAloneSmartScaleWhenMaxLevel = 80
# #
# #
# #
@@ -694,11 +588,6 @@ AiPlayerbot.RandomBotGroupNearby = 0
# Default: 1 (enabled) # Default: 1 (enabled)
AiPlayerbot.AutoDoQuests = 1 AiPlayerbot.AutoDoQuests = 1
# Random Bots will behave more like real players (exprimental)
# This option will override AutoDoQuests
# Default: 1 (enabled)
AiPlayerbot.EnableNewRpgStrategy = 1
# Quest items to leave (do not destroy) # Quest items to leave (do not destroy)
AiPlayerbot.RandomBotQuestItems = "6948,5175,5176,5177,5178,16309,12382,13704,11000" AiPlayerbot.RandomBotQuestItems = "6948,5175,5176,5177,5178,16309,12382,13704,11000"
@@ -785,56 +674,18 @@ AiPlayerbot.AutoTeleportForLevel = 1
# Enable BG/Arena for random Bots # Enable BG/Arena for random Bots
AiPlayerbot.RandomBotJoinBG = 1 AiPlayerbot.RandomBotJoinBG = 1
# Enable Auto join BG - have bots start BG's and Arenas on their own # Enable Auto join BG - bots randomly join WSG and 2v2 Arena if server is not lagging
AiPlayerbot.RandomBotAutoJoinBG = 0 AiPlayerbot.RandomBotAutoJoinBG = 0
# Required Configuration for RandomBotAutoJoinBG # Required for RandomBotAutoJoinBG
# # Currently you can select 1 bracket (level range) for bots to auto join.
# Known issue: When enabling a lot of brackats in combination with multiple instances, # Brackets for warsong 0:10-19, 1:20-29, 2:30-39, 3:40-49 etc. Default: 7 (level 80)
# can lead to more instances created by bots than intended (over-queuing). # Brackets for rated arena: 0:10-14, 1:15-19 etc. Default: 14 (80-84)
# # For lower level ranges to work in Rated Arena, custom code changes need to be made.
# This section controls the level brackets and # Count = amount of games Bots auto fill. (Count 1 for WSG = 20 bots).
# automatic bot participation in battlegrounds and arenas. AiPlayerbot.RandomBotAutoJoinWarsongBracket = 7
#
# Brackets:
# - Specify the level ranges for bots to auto-join:
# - Warsong Gulch (WS): 0 = 10-19, 1 = 20-29, 2 = 30-39, ..., 7 = 80 (default: 7)
# - Rated Arena: 0 = 10-14, 1 = 15-19, ..., 14 = 80-84 (default: 14)
# - Multiple brackets can be specified as a comma-separated list (e.g., "0,2,5").
#
# Counts:
# - Specify the number of Battlegrounds to auto-fill per bracket.
# - For battlegrounds, Count is the number of bots per bracket. For example:
# - Warsong Gulch Count = 1 adds 20 bots (10 per team).
# - Ensure there are enough eligible bots to meet the specified counts.
#
# Arena Considerations:
# - Rated Arena brackets default to level 80-84 (bracket 14).
# - Custom code changes are required for lower-level arena brackets to function properly.
#
# Battleground bracket range possibilities:
# AiPlayerbot.RandomBotAutoJoinICBrackets = 0,1
# AiPlayerbot.RandomBotAutoJoinEYBrackets = 0,1,2
# AiPlayerbot.RandomBotAutoJoinAVBrackets = 0,1,2,3
# AiPlayerbot.RandomBotAutoJoinABBrackets = 0,1,2,3,4,5,6
# AiPlayerbot.RandomBotAutoJoinWSBrackets = 0,1,2,3,4,5,6,7
AiPlayerbot.RandomBotAutoJoinICBrackets = 1
AiPlayerbot.RandomBotAutoJoinEYBrackets = 2
AiPlayerbot.RandomBotAutoJoinAVBrackets = 3
AiPlayerbot.RandomBotAutoJoinABBrackets = 6
AiPlayerbot.RandomBotAutoJoinWSBrackets = 7
# Battlegrounds count (per bracket!):
AiPlayerbot.RandomBotAutoJoinBGICCount = 0
AiPlayerbot.RandomBotAutoJoinBGEYCount = 1
AiPlayerbot.RandomBotAutoJoinBGAVCount = 0
AiPlayerbot.RandomBotAutoJoinBGABCount = 1
AiPlayerbot.RandomBotAutoJoinBGWSCount = 1
# Arena configuration:
AiPlayerbot.RandomBotAutoJoinArenaBracket = 14 AiPlayerbot.RandomBotAutoJoinArenaBracket = 14
AiPlayerbot.RandomBotAutoJoinBGWarsongCount = 0
AiPlayerbot.RandomBotAutoJoinBGRatedArena2v2Count = 0 AiPlayerbot.RandomBotAutoJoinBGRatedArena2v2Count = 0
AiPlayerbot.RandomBotAutoJoinBGRatedArena3v3Count = 0 AiPlayerbot.RandomBotAutoJoinBGRatedArena3v3Count = 0
AiPlayerbot.RandomBotAutoJoinBGRatedArena5v5Count = 0 AiPlayerbot.RandomBotAutoJoinBGRatedArena5v5Count = 0
@@ -858,10 +709,10 @@ AiPlayerbot.RandomBotArenaTeamMinRating = 1000
AiPlayerbot.DeleteRandomBotArenaTeams = 0 AiPlayerbot.DeleteRandomBotArenaTeams = 0
# PvP Restricted Zones (bots don't pvp) # PvP Restricted Zones (bots don't pvp)
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,139" 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"
# PvP Restricted Areas (bots don't pvp) # PvP Restricted Areas (bots don't pvp)
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312" AiPlayerbot.PvpProhibitedAreaIds = "976,35,392"
# Improve react speed in battleground and arena (may cause lag) # Improve react speed in battleground and arena (may cause lag)
AiPlayerbot.FastReactInBG = 1 AiPlayerbot.FastReactInBG = 1
@@ -880,8 +731,8 @@ AiPlayerbot.FastReactInBG = 1
AiPlayerbot.RandomBotUpdateInterval = 20 AiPlayerbot.RandomBotUpdateInterval = 20
AiPlayerbot.RandomBotCountChangeMinInterval = 1800 AiPlayerbot.RandomBotCountChangeMinInterval = 1800
AiPlayerbot.RandomBotCountChangeMaxInterval = 7200 AiPlayerbot.RandomBotCountChangeMaxInterval = 7200
AiPlayerbot.MinRandomBotInWorldTime = 600 AiPlayerbot.MinRandomBotInWorldTime = 3600
AiPlayerbot.MaxRandomBotInWorldTime = 28800 AiPlayerbot.MaxRandomBotInWorldTime = 1209600
AiPlayerbot.MinRandomBotRandomizeTime = 7200 AiPlayerbot.MinRandomBotRandomizeTime = 7200
AiPlayerbot.MaxRandomBotRandomizeTime = 1209600 AiPlayerbot.MaxRandomBotRandomizeTime = 1209600
AiPlayerbot.RandomBotsPerInterval = 60 AiPlayerbot.RandomBotsPerInterval = 60
@@ -889,7 +740,7 @@ AiPlayerbot.MinRandomBotReviveTime = 60
AiPlayerbot.MaxRandomBotReviveTime = 300 AiPlayerbot.MaxRandomBotReviveTime = 300
AiPlayerbot.MinRandomBotTeleportInterval = 3600 AiPlayerbot.MinRandomBotTeleportInterval = 3600
AiPlayerbot.MaxRandomBotTeleportInterval = 18000 AiPlayerbot.MaxRandomBotTeleportInterval = 18000
AiPlayerbot.PermanantlyInWorldTime = 31104000 AiPlayerbot.RandomBotInWorldWithRotationDisabled = 31104000
# #
# #
@@ -972,8 +823,8 @@ AiPlayerbot.PremadeSpecLink.2.2.80 = 050501-05-05232051203331302133231331
AiPlayerbot.PremadeSpecName.3.0 = bm pve AiPlayerbot.PremadeSpecName.3.0 = bm pve
AiPlayerbot.PremadeSpecGlyph.3.0 = 42912,43350,42902,43351,43338,45732 AiPlayerbot.PremadeSpecGlyph.3.0 = 42912,43350,42902,43351,43338,45732
AiPlayerbot.PremadeSpecLink.3.0.60 = 51200201505112243110531051 AiPlayerbot.PremadeSpecLink.3.0.60 = 51200201505112243100511351
AiPlayerbot.PremadeSpecLink.3.0.80 = 51200201505112243120531251-025305101 AiPlayerbot.PremadeSpecLink.3.0.80 = 51200201505112253100531351-015305021
AiPlayerbot.PremadeSpecName.3.1 = mm pve AiPlayerbot.PremadeSpecName.3.1 = mm pve
AiPlayerbot.PremadeSpecGlyph.3.1 = 42912,43350,42914,43351,43338,45732 AiPlayerbot.PremadeSpecGlyph.3.1 = 42912,43350,42914,43351,43338,45732
AiPlayerbot.PremadeSpecLink.3.1.60 = -025315101030013233125031051 AiPlayerbot.PremadeSpecLink.3.1.60 = -025315101030013233125031051
@@ -1007,16 +858,16 @@ AiPlayerbot.PremadeHunterPetLink.2.20 = 21000203300002110221
AiPlayerbot.PremadeSpecName.4.0 = as pve AiPlayerbot.PremadeSpecName.4.0 = as pve
AiPlayerbot.PremadeSpecGlyph.4.0 = 45768,43379,45761,43380,43378,45766 AiPlayerbot.PremadeSpecGlyph.4.0 = 45768,43379,45761,43380,43378,45766
AiPlayerbot.PremadeSpecLink.4.0.60 = 005303104352100520103331051 AiPlayerbot.PremadeSpecLink.4.0.60 = 005323005350100520103331051
AiPlayerbot.PremadeSpecLink.4.0.80 = 005303104352100520103331051-005005005003-2 AiPlayerbot.PremadeSpecLink.4.0.80 = 005323005350100520103331051-005005005003-2
AiPlayerbot.PremadeSpecName.4.1 = combat pve AiPlayerbot.PremadeSpecName.4.1 = combat pve
AiPlayerbot.PremadeSpecGlyph.4.1 = 42962,43379,45762,43380,43378,42969 AiPlayerbot.PremadeSpecGlyph.4.1 = 42962,43379,45762,43380,43378,42969
AiPlayerbot.PremadeSpecLink.4.1.60 = -0252051000035015223100501251 AiPlayerbot.PremadeSpecLink.4.1.60 = -0252051000035015223100501251
AiPlayerbot.PremadeSpecLink.4.1.80 = 00532000523-0252051000035015223100501251 AiPlayerbot.PremadeSpecLink.4.1.80 = 00532000523-0252051000035015223100501251
AiPlayerbot.PremadeSpecName.4.2 = subtlety pve AiPlayerbot.PremadeSpecName.4.2 = subtlety pve
AiPlayerbot.PremadeSpecGlyph.4.2 = 42967,43379,45764,43380,43378,45767 AiPlayerbot.PremadeSpecGlyph.4.2 = 42967,43379,45764,43380,43378,45767
AiPlayerbot.PremadeSpecLink.4.2.60 = --5022012030321121350115031151 AiPlayerbot.PremadeSpecLink.4.2.60 = --5120122030321121050135031241
AiPlayerbot.PremadeSpecLink.4.2.80 = 30532010114--5022012030321121350115031151 AiPlayerbot.PremadeSpecLink.4.2.80 = 0053231-2-5120222030321121050135231251
# #
# #
@@ -1060,7 +911,7 @@ AiPlayerbot.PremadeSpecGlyph.6.1 = 45805,43673,43547,43544,43672,43543
AiPlayerbot.PremadeSpecLink.6.1.60 = -32003350332203012300023101351 AiPlayerbot.PremadeSpecLink.6.1.60 = -32003350332203012300023101351
AiPlayerbot.PremadeSpecLink.6.1.80 = -32002350352203012300033101351-230200305003 AiPlayerbot.PremadeSpecLink.6.1.80 = -32002350352203012300033101351-230200305003
AiPlayerbot.PremadeSpecName.6.2 = unholy pve AiPlayerbot.PremadeSpecName.6.2 = unholy pve
AiPlayerbot.PremadeSpecGlyph.6.2 = 43542,43673,43546,43544,43672,43549 AiPlayerbot.PremadeSpecGlyph.6.2 = 43542,43673,45804,43544,43672,43549
AiPlayerbot.PremadeSpecLink.6.2.60 = --2301303050032151000150013131151 AiPlayerbot.PremadeSpecLink.6.2.60 = --2301303050032151000150013131151
AiPlayerbot.PremadeSpecLink.6.2.80 = -320033500002-2301303050032151000150013133151 AiPlayerbot.PremadeSpecLink.6.2.80 = -320033500002-2301303050032151000150013133151
AiPlayerbot.PremadeSpecName.6.3 = double aura blood pve AiPlayerbot.PremadeSpecName.6.3 = double aura blood pve
@@ -1113,10 +964,6 @@ AiPlayerbot.PremadeSpecName.8.2 = frost pve
AiPlayerbot.PremadeSpecGlyph.8.2 = 42742,43339,50045,43364,43361,42751 AiPlayerbot.PremadeSpecGlyph.8.2 = 42742,43339,50045,43364,43361,42751
AiPlayerbot.PremadeSpecLink.8.2.60 = --0533030313203100030152231151 AiPlayerbot.PremadeSpecLink.8.2.60 = --0533030313203100030152231151
AiPlayerbot.PremadeSpecLink.8.2.80 = 23002303110003--0533030313203100030152231351 AiPlayerbot.PremadeSpecLink.8.2.80 = 23002303110003--0533030313203100030152231351
AiPlayerbot.PremadeSpecName.8.3 = frostfire pve
AiPlayerbot.PremadeSpecGlyph.8.3 = 44684,44920,42751,43339,43364,45737
AiPlayerbot.PremadeSpecLink.8.3.60 = -2305032012303331053120300051
AiPlayerbot.PremadeSpecLink.8.3.80 = -2305032012303331053120311351-023303031
# #
# #
@@ -1175,58 +1022,6 @@ AiPlayerbot.PremadeSpecLink.11.3.80 = -553202032322010053100030310511-205503012
# #
################################################################################################### ###################################################################################################
###################################
# #
# WORLD BUFFS #
# #
###################################
####################################################################################################
#
#
#
# Applies a permanent buff to all bots when not in combat simulating flasks, food, rune etc.
# WorldBuff.Faction.Class.Spec.MinLevel.MaxLevel
AiPlayerbot.WorldBuff.0.1.0.80.80 = 53760,57358 #WARRIOR ARMS
AiPlayerbot.WorldBuff.0.1.1.80.80 = 53760,57358 #WARRIOR FURY
AiPlayerbot.WorldBuff.0.1.2.80.80 = 53758,57356 #WARRIOR PROTECTION
AiPlayerbot.WorldBuff.0.2.0.80.80 = 60347,53749,57332 #PALADIN HOLY
AiPlayerbot.WorldBuff.0.2.1.80.80 = 53758,57356 #PALADIN PROTECTION
AiPlayerbot.WorldBuff.0.2.2.80.80 = 53760,57371 #PALADIN RETRIBUTION
AiPlayerbot.WorldBuff.0.3.0.80.80 = 53760,57325 #HUNTER BEAST
AiPlayerbot.WorldBuff.0.3.1.80.80 = 53760,57358 #HUNTER MARKSMANSHIP
AiPlayerbot.WorldBuff.0.3.2.80.80 = 53760,57367 #HUNTER SURVIVAL
AiPlayerbot.WorldBuff.0.4.0.80.80 = 53760,57325 #ROGUE ASSASINATION
AiPlayerbot.WorldBuff.0.4.1.80.80 = 53760,57358 #ROGUE COMBAT
AiPlayerbot.WorldBuff.0.4.2.80.80 = 53760,57367 #ROGUE SUBTLETY
AiPlayerbot.WorldBuff.0.5.0.80.80 = 53755,57327 #PRIEST DISCIPLINE
AiPlayerbot.WorldBuff.0.5.1.80.80 = 53755,57327 #PRIEST HOLY
AiPlayerbot.WorldBuff.0.5.2.80.80 = 53755,57327 #PRIEST SHADOW
AiPlayerbot.WorldBuff.0.6.0.80.80 = 53758,57356 #DEATH KNIGHT BLOOD
AiPlayerbot.WorldBuff.0.6.1.80.80 = 53760,57358 #DEATH KNIGHT FROST
AiPlayerbot.WorldBuff.0.6.2.80.80 = 53760,57358 #DEATH KNIGHT UNHOLY
AiPlayerbot.WorldBuff.0.6.3.80.80 = 53760,57371 #DEATH KNIGHT BLOOD DPS
AiPlayerbot.WorldBuff.0.7.0.80.80 = 53755,57327 #SHAMAN ELEMENTAL
AiPlayerbot.WorldBuff.0.7.1.80.80 = 53760,57325 #SHAMAN ENHANCEMENT
AiPlayerbot.WorldBuff.0.7.2.80.80 = 53755,57327 #SHAMAN RESTORATION
AiPlayerbot.WorldBuff.0.8.0.80.80 = 53755,57327 #MAGE ARCANE
AiPlayerbot.WorldBuff.0.8.1.80.80 = 53755,57327 #MAGE FIRE
AiPlayerbot.WorldBuff.0.8.2.80.80 = 53755,57327 #MAGE FROST
AiPlayerbot.WorldBuff.0.9.0.80.80 = 53755,57327 #WARLOCK AFFLICTION
AiPlayerbot.WorldBuff.0.9.1.80.80 = 53755,57327 #WARLOCK DEMONOLOGY
AiPlayerbot.WorldBuff.0.9.2.80.80 = 53755,57327 #WARLOCK DESTRUCTION
AiPlayerbot.WorldBuff.0.11.0.80.80 = 53755,57327 #DRUID BALANCE
AiPlayerbot.WorldBuff.0.11.1.80.80 = 53749,53763,57367 #DRUID FERAL BEAR
AiPlayerbot.WorldBuff.0.11.2.80.80 = 54212,57334 #DRUID RESTORATION
AiPlayerbot.WorldBuff.0.11.3.80.80 = 53760,57358 #DRUID FERAL CAT
#
#
#
###################################################################################################
################################### ###################################
# # # #
# RANDOM BOT DEFAULT TALENT SPEC # # RANDOM BOT DEFAULT TALENT SPEC #
@@ -1234,7 +1029,7 @@ AiPlayerbot.WorldBuff.0.11.3.80.80 = 53760,57358 #DRUID FERAL
################################### ###################################
#################################################################################################### ####################################################################################################
# #
# #
# #
@@ -1616,6 +1411,9 @@ AiPlayerbot.BroadcastChanceGuildManagement = 30000
# Example: AiPlayerbot.AllowedLogFiles = travelNodes.csv,travelPaths.csv,TravelNodeStore.h,bot_movement.csv,bot_location.csv # Example: AiPlayerbot.AllowedLogFiles = travelNodes.csv,travelPaths.csv,TravelNodeStore.h,bot_movement.csv,bot_location.csv
AiPlayerbot.AllowedLogFiles = "" AiPlayerbot.AllowedLogFiles = ""
Appender.Playerbots=2,5,0,Playerbots.log,w
Logger.playerbots=5,Console Playerbots
# #
# #
# #
@@ -1640,6 +1438,12 @@ AiPlayerbot.EnableGuildTasks = 0
# Enable dungeon suggestions in lower case randomly # Enable dungeon suggestions in lower case randomly
AiPlayerbot.SuggestDungeonsInLowerCaseRandomly = 0 AiPlayerbot.SuggestDungeonsInLowerCaseRandomly = 0
# Random bot guild count
AiPlayerbot.RandomBotGuildCount = 20
# Delete all random bot guilds
AiPlayerbot.DeleteRandomBotGuilds = 0
# Chance bot chooses RPG (Teleport to random camp for their level) instead of grinding # Chance bot chooses RPG (Teleport to random camp for their level) instead of grinding
AiPlayerbot.RandomBotRpgChance = 0.20 AiPlayerbot.RandomBotRpgChance = 0.20
@@ -1649,6 +1453,20 @@ AiPlayerbot.RandombotsWalkingRPG = 0
# Set randombots movement speed to walking only inside buildings # Set randombots movement speed to walking only inside buildings
AiPlayerbot.RandombotsWalkingRPG.InDoors = 0 AiPlayerbot.RandombotsWalkingRPG.InDoors = 0
# Specify percent of active bots
# The default is 10. With 10% of all bots going active or inactive each minute. Regardless
# This value is only applied to inactive areas where no real-players are detected, when
# real-players are nearby, friend, group, guild, bg, instances etc the value is always
# enforced to 100%
AiPlayerbot.BotActiveAlone = 100
# Specify smart scaling is enabled or not.
# The default is 1. When enabled (smart) scales the 'BotActiveAlone' value.
# Only when botLevel is between WhenMinLevel and WhenMaxLevel.
AiPlayerbot.botActiveAloneSmartScale = 1
AiPlayerbot.botActiveAloneSmartScaleWhenMinLevel = 1
AiPlayerbot.botActiveAloneSmartScaleWhenMaxLevel = 80
# Premade spell to avoid (undetected spells) # Premade spell to avoid (undetected spells)
# spellid-radius, ... # spellid-radius, ...
AiPlayerbot.PremadeAvoidAoe = 62234-4 AiPlayerbot.PremadeAvoidAoe = 62234-4

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +0,0 @@
DROP TABLE IF EXISTS `playerbot_account_keys`;
CREATE TABLE `playerbot_account_keys` (
`account_id` INT PRIMARY KEY,
`security_key` VARCHAR(255) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=INNODB DEFAULT CHARSET=latin1;

View File

@@ -1,9 +0,0 @@
DROP TABLE IF EXISTS `playerbot_account_links`;
CREATE TABLE `playerbot_account_links` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`account_id` INT NOT NULL,
`linked_account_id` INT NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY `account_link` (`account_id`, `linked_account_id`)
) ENGINE=INNODB DEFAULT CHARSET=latin1;

View File

@@ -1,4 +0,0 @@
-- Update max_level for TBC Heroic dungeons in `playerbots_dungeon_suggestion_definition`
UPDATE `playerbots_dungeon_suggestion_definition`
SET `max_level` = 73
WHERE `id` IN (40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56);

View File

@@ -1,22 +0,0 @@
-- Delete existing entries from playerbots_travelnode, playerbots_travelnode_path, and playerbots_travelnode_link tables
DELETE FROM `playerbots_travelnode_link` WHERE `node_id` = 3780;
DELETE FROM `playerbots_travelnode_path` WHERE `node_id` = 3780;
DELETE FROM `playerbots_travelnode` WHERE `id` = 3780;
-- Insert new entries into playerbots_travelnode
INSERT INTO `playerbots_travelnode` (`id`, `name`, `map_id`, `x`, `y`, `z`, `linked`)
VALUES (3780, 'Highlord Mograine', 533, 2524.32, -2951.28, 245.633, 1);
-- Insert new entries into playerbots_travelnode_path
INSERT INTO `playerbots_travelnode_path` (`node_id`, `to_node_id`, `nr`, `map_id`, `x`, `y`, `z`)
VALUES
(3780, 472, 0, 533, 2524.32, -2951.28, 245.633),
(3780, 472, 1, 533, 2528.79, -2948.58, 245.633),
(3780, 757, 0, 533, 2524.32, -2951.28, 245.633),
(3780, 757, 1, 533, 2517.62, -2959.38, 245.636);
-- Insert new entries into playerbots_travelnode_link
INSERT INTO `playerbots_travelnode_link` (`node_id`, `to_node_id`, `type`, `object`, `distance`, `swim_distance`, `extra_cost`, `calculated`, `max_creature_0`, `max_creature_1`, `max_creature_2`)
VALUES
(3780, 472, 1, 0, 5.3221, 0, 0, 1, 83, 0, 0),
(3780, 757, 1, 0, 10.6118, 0, 0, 1, 83, 0, 0);

View File

@@ -1,25 +0,0 @@
-- ##########################################################
-- # Playerbots RandomBots Performance Update
-- # Add missing index to reduce Deadlocks
-- # Author: Raz0r1337 aka St0ny
-- # Date: 2025-04-26
-- ##########################################################
-- Check if the index already exists
SET @index_exists := (
SELECT COUNT(1)
FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'playerbots_random_bots'
AND INDEX_NAME = 'idx_owner_bot_event'
);
-- Conditionally create the index only if it doesn't exist
SET @ddl := IF(@index_exists = 0,
'ALTER TABLE `playerbots_random_bots` ADD INDEX `idx_owner_bot_event` (`owner`, `bot`, `event`);',
'SELECT "Index idx_owner_bot_event already exists.";'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

View File

@@ -1,17 +0,0 @@
DROP TABLE IF EXISTS `playerbot_account_links`;
CREATE TABLE `playerbot_account_links` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`account_id` INT NOT NULL,
`linked_account_id` INT NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY `account_link` (`account_id`, `linked_account_id`)
) ENGINE=INNODB DEFAULT CHARSET=latin1;
DROP TABLE IF EXISTS `playerbot_account_keys`;
CREATE TABLE `playerbot_account_keys` (
`account_id` INT PRIMARY KEY,
`security_key` VARCHAR(255) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=INNODB DEFAULT CHARSET=latin1;

BIN
icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 KiB

View File

@@ -275,7 +275,7 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
if (!player->InBattleground()) if (!player->InBattleground())
{ {
engine->addStrategiesNoInit("racials", "chat", "default", "cast time", "potions", "duel", "boost", nullptr); engine->addStrategiesNoInit("racials", "chat", "default", "cast time", "duel", "boost", nullptr);
} }
if (sPlayerbotAIConfig->autoAvoidAoe && facade->HasRealPlayerMaster()) if (sPlayerbotAIConfig->autoAvoidAoe && facade->HasRealPlayerMaster())
{ {
@@ -304,16 +304,7 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
if (tab == 0) if (tab == 0)
engine->addStrategiesNoInit("arcane", "arcane aoe", nullptr); engine->addStrategiesNoInit("arcane", "arcane aoe", nullptr);
else if (tab == 1) else if (tab == 1)
{ engine->addStrategiesNoInit("fire", "fire aoe", nullptr);
if (player->HasSpell(44614) /*Frostfire Bolt*/ && player->HasAura(15047) /*Ice Shards*/)
{
engine->addStrategiesNoInit("frostfire", "frostfire aoe", nullptr);
}
else
{
engine->addStrategiesNoInit("fire", "fire aoe", nullptr);
}
}
else else
engine->addStrategiesNoInit("frost", "frost aoe", nullptr); engine->addStrategiesNoInit("frost", "frost aoe", nullptr);
@@ -321,8 +312,8 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
break; break;
case CLASS_WARRIOR: case CLASS_WARRIOR:
if (tab == 2) if (tab == 2)
engine->addStrategiesNoInit("tank", "tank assist", "aoe", nullptr); engine->addStrategiesNoInit("tank", "tank assist", "aoe", "mark rti", nullptr);
else if (tab == 0 || !player->HasSpell(1680)) // Whirlwind else if (player->GetLevel() < 36 || tab == 0)
engine->addStrategiesNoInit("arms", "aoe", "dps assist", /*"behind",*/ nullptr); engine->addStrategiesNoInit("arms", "aoe", "dps assist", /*"behind",*/ nullptr);
else else
engine->addStrategiesNoInit("fury", "aoe", "dps assist", /*"behind",*/ nullptr); engine->addStrategiesNoInit("fury", "aoe", "dps assist", /*"behind",*/ nullptr);
@@ -356,7 +347,7 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
engine->addStrategiesNoInit("heal", "cure", "dps assist", nullptr); engine->addStrategiesNoInit("heal", "cure", "dps assist", nullptr);
else else
{ {
if (player->HasSpell(768) /*cat form*/&& !player->HasAura(16931) /*thick hide*/) if (player->GetLevel() >= 20 && !player->HasAura(16931) /*thick hide*/)
{ {
engine->addStrategiesNoInit("cat", "dps assist", nullptr); engine->addStrategiesNoInit("cat", "dps assist", nullptr);
} }
@@ -369,19 +360,15 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
case CLASS_HUNTER: case CLASS_HUNTER:
engine->addStrategiesNoInit("dps", "aoe", "bdps", "dps assist", nullptr); engine->addStrategiesNoInit("dps", "aoe", "bdps", "dps assist", nullptr);
engine->addStrategy("dps debuff", false); engine->addStrategy("dps debuff", false);
// if (tab == HUNTER_TAB_SURVIVAL)
// {
// engine->addStrategy("trap weave", false);
// }
break; break;
case CLASS_ROGUE: case CLASS_ROGUE:
if (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY) if (tab == ROGUE_TAB_ASSASSINATION)
{ {
engine->addStrategiesNoInit("melee", "dps assist", "aoe", nullptr); engine->addStrategiesNoInit("melee", "dps assist", "aoe", /*"behind",*/ nullptr);
} }
else else
{ {
engine->addStrategiesNoInit("dps", "dps assist", "aoe", nullptr); engine->addStrategiesNoInit("dps", "dps assist", "aoe", /*"behind",*/ nullptr);
} }
break; break;
case CLASS_WARLOCK: case CLASS_WARLOCK:
@@ -613,15 +600,14 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
if (!player->InBattleground()) if (!player->InBattleground())
{ {
nonCombatEngine->addStrategiesNoInit("nc", "food", "chat", "follow", "default", "quest", "loot", nonCombatEngine->addStrategiesNoInit("nc", "food", "chat", "follow", "default", "quest", "loot", "gather", "duel",
"gather", "duel", "pvp", "buff", "mount", "emote", nullptr); "buff", "mount", "emote", nullptr);
} }
if (sPlayerbotAIConfig->autoSaveMana && PlayerbotAI::IsHeal(player, true)) if (sPlayerbotAIConfig->autoSaveMana)
{ {
nonCombatEngine->addStrategy("save mana", false); nonCombatEngine->addStrategy("save mana", false);
} }
if ((sRandomPlayerbotMgr->IsRandomBot(player)) && !player->InBattleground()) if ((sRandomPlayerbotMgr->IsRandomBot(player)) && !player->InBattleground())
{ {
Player* master = facade->GetMaster(); Player* master = facade->GetMaster();
@@ -639,17 +625,13 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
// if (!urand(0, 3)) // if (!urand(0, 3))
// nonCombatEngine->addStrategy("attack tagged"); // nonCombatEngine->addStrategy("attack tagged");
// nonCombatEngine->addStrategy("pvp", false); nonCombatEngine->addStrategy("pvp", false);
// nonCombatEngine->addStrategy("collision"); // nonCombatEngine->addStrategy("collision");
nonCombatEngine->addStrategy("grind", false);
// nonCombatEngine->addStrategy("group"); // nonCombatEngine->addStrategy("group");
// nonCombatEngine->addStrategy("guild"); // nonCombatEngine->addStrategy("guild");
nonCombatEngine->addStrategy("grind", false);
if (sPlayerbotAIConfig->enableNewRpgStrategy) if (sPlayerbotAIConfig->autoDoQuests)
{
nonCombatEngine->addStrategy("new rpg", false);
}
else if (sPlayerbotAIConfig->autoDoQuests)
{ {
// nonCombatEngine->addStrategy("travel"); // nonCombatEngine->addStrategy("travel");
nonCombatEngine->addStrategy("rpg", false); nonCombatEngine->addStrategy("rpg", false);
@@ -676,7 +658,7 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(master); PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(master);
if (masterBotAI || sRandomPlayerbotMgr->IsRandomBot(player)) if (masterBotAI || sRandomPlayerbotMgr->IsRandomBot(player))
{ {
// nonCombatEngine->addStrategy("pvp", false); nonCombatEngine->addStrategy("pvp", false);
// nonCombatEngine->addStrategy("collision"); // nonCombatEngine->addStrategy("collision");
// nonCombatEngine->addStrategy("group"); // nonCombatEngine->addStrategy("group");
// nonCombatEngine->addStrategy("guild"); // nonCombatEngine->addStrategy("guild");
@@ -696,7 +678,7 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
} }
else else
{ {
// nonCombatEngine->addStrategy("pvp", false); nonCombatEngine->addStrategy("pvp", false);
nonCombatEngine->ChangeStrategy(sPlayerbotAIConfig->nonCombatStrategies); nonCombatEngine->ChangeStrategy(sPlayerbotAIConfig->nonCombatStrategies);
} }
} }

View File

@@ -16,7 +16,7 @@ uint8 BroadcastHelper::GetLocale()
return locale; return locale;
} }
bool BroadcastHelper::BroadcastTest(PlayerbotAI* ai, Player* /* bot */) bool BroadcastHelper::BroadcastTest(PlayerbotAI* ai, Player* bot)
{ {
//return something to ignore the logic //return something to ignore the logic
return false; return false;
@@ -609,7 +609,7 @@ bool BroadcastHelper::BroadcastLevelup(PlayerbotAI* ai, Player* bot)
return false; return false;
} }
bool BroadcastHelper::BroadcastGuildMemberPromotion(PlayerbotAI* ai, Player* /* bot */, Player* player) bool BroadcastHelper::BroadcastGuildMemberPromotion(PlayerbotAI* ai, Player* bot, Player* player)
{ {
if (!sPlayerbotAIConfig->enableBroadcasts) if (!sPlayerbotAIConfig->enableBroadcasts)
return false; return false;
@@ -627,7 +627,7 @@ bool BroadcastHelper::BroadcastGuildMemberPromotion(PlayerbotAI* ai, Player* /*
return false; return false;
} }
bool BroadcastHelper::BroadcastGuildMemberDemotion(PlayerbotAI* ai, Player* /* bot */, Player* player) bool BroadcastHelper::BroadcastGuildMemberDemotion(PlayerbotAI* ai, Player* bot, Player* player)
{ {
if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceGuildManagement) if (urand(1, sPlayerbotAIConfig->broadcastChanceMaxValue) <= sPlayerbotAIConfig->broadcastChanceGuildManagement)
{ {
@@ -643,7 +643,7 @@ bool BroadcastHelper::BroadcastGuildMemberDemotion(PlayerbotAI* ai, Player* /* b
return false; return false;
} }
bool BroadcastHelper::BroadcastGuildGroupOrRaidInvite(PlayerbotAI* ai, Player* /* bot */, Player* player, Group* group) bool BroadcastHelper::BroadcastGuildGroupOrRaidInvite(PlayerbotAI* ai, Player* bot, Player* player, Group* group)
{ {
if (!sPlayerbotAIConfig->enableBroadcasts) if (!sPlayerbotAIConfig->enableBroadcasts)
return false; return false;

View File

@@ -156,7 +156,7 @@ public:
return message; return message;
bool found = false; bool found = false;
//bool isRti = false; //not used, shadowed by the next declaration, line marked for removal. bool isRti = false;
for (std::vector<std::string>::iterator i = rtis.begin(); i != rtis.end(); i++) for (std::vector<std::string>::iterator i = rtis.begin(); i != rtis.end(); i++)
{ {
std::string const rti = *i; std::string const rti = *i;
@@ -213,7 +213,7 @@ public:
Player* bot = botAI->GetBot(); Player* bot = botAI->GetBot();
bool found = false; bool found = false;
//bool isClass = false; //not used, shadowed by the next declaration, line marked for removal. bool isClass = false;
for (std::map<std::string, uint8>::iterator i = classNames.begin(); i != classNames.end(); i++) for (std::map<std::string, uint8>::iterator i = classNames.begin(); i != classNames.end(); i++)
{ {
bool isClass = message.find(i->first) == 0; bool isClass = message.find(i->first) == 0;

View File

@@ -6,9 +6,6 @@
#include "ChatHelper.h" #include "ChatHelper.h"
#include "AiFactory.h" #include "AiFactory.h"
#include "Common.h"
#include "ItemTemplate.h"
#include "ObjectMgr.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "SpellInfo.h" #include "SpellInfo.h"
@@ -313,16 +310,7 @@ std::string const ChatHelper::FormatQuest(Quest const* quest)
} }
std::ostringstream out; std::ostringstream out;
QuestLocale const* locale = sObjectMgr->GetQuestLocale(quest->GetQuestId()); out << "|cFFFFFF00|Hquest:" << quest->GetQuestId() << ':' << quest->GetQuestLevel() << "|h[" << quest->GetTitle() << "]|h|r";
std::string questTitle;
if (locale && locale->Title.size() > sWorld->GetDefaultDbcLocale())
questTitle = locale->Title[sWorld->GetDefaultDbcLocale()];
if (questTitle.empty())
questTitle = quest->GetTitle();
out << "|cFFFFFF00|Hquest:" << quest->GetQuestId() << ':' << quest->GetQuestLevel() << "|h[" << questTitle << "]|h|r";
return out.str(); return out.str();
} }
@@ -330,7 +318,7 @@ std::string const ChatHelper::FormatGameobject(GameObject* go)
{ {
std::ostringstream out; std::ostringstream out;
out << "|cFFFFFF00|Hfound:" << go->GetGUID().GetRawValue() << ":" << go->GetEntry() << ":" out << "|cFFFFFF00|Hfound:" << go->GetGUID().GetRawValue() << ":" << go->GetEntry() << ":"
<< "|h[" << go->GetNameForLocaleIdx(sWorld->GetDefaultDbcLocale()) << "]|h|r"; << "|h[" << go->GetNameForLocaleIdx(LOCALE_enUS) << "]|h|r";
return out.str(); return out.str();
} }
@@ -339,8 +327,8 @@ std::string const ChatHelper::FormatWorldobject(WorldObject* wo)
std::ostringstream out; std::ostringstream out;
out << "|cFFFFFF00|Hfound:" << wo->GetGUID().GetRawValue() << ":" << wo->GetEntry() << ":" out << "|cFFFFFF00|Hfound:" << wo->GetGUID().GetRawValue() << ":" << wo->GetEntry() << ":"
<< "|h["; << "|h[";
out << (wo->ToGameObject() ? ((GameObject*)wo)->GetNameForLocaleIdx(sWorld->GetDefaultDbcLocale()) out << (wo->ToGameObject() ? ((GameObject*)wo)->GetNameForLocaleIdx(LOCALE_enUS)
: wo->GetNameForLocaleIdx(sWorld->GetDefaultDbcLocale())) : wo->GetNameForLocaleIdx(LOCALE_enUS))
<< "]|h|r"; << "]|h|r";
return out.str(); return out.str();
} }
@@ -373,9 +361,7 @@ std::string const ChatHelper::FormatWorldEntry(int32 entry)
std::string const ChatHelper::FormatSpell(SpellInfo const* spellInfo) std::string const ChatHelper::FormatSpell(SpellInfo const* spellInfo)
{ {
std::ostringstream out; std::ostringstream out;
std::string spellName = spellInfo->SpellName[sWorld->GetDefaultDbcLocale()] ? out << "|cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[LOCALE_enUS] << "]|h|r";
spellInfo->SpellName[sWorld->GetDefaultDbcLocale()] : spellInfo->SpellName[LOCALE_enUS];
out << "|cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellName << "]|h|r";
return out.str(); return out.str();
} }
@@ -384,18 +370,11 @@ std::string const ChatHelper::FormatItem(ItemTemplate const* proto, uint32 count
char color[32]; char color[32];
sprintf(color, "%x", ItemQualityColors[proto->Quality]); sprintf(color, "%x", ItemQualityColors[proto->Quality]);
std::string itemName; // const std::string &name = sObjectMgr->GetItemLocale(proto->ItemId)->Name[LOCALE_enUS];
const ItemLocale* locale = sObjectMgr->GetItemLocale(proto->ItemId);
if (locale && locale->Name.size() > sWorld->GetDefaultDbcLocale())
itemName = locale->Name[sWorld->GetDefaultDbcLocale()];
if (itemName.empty())
itemName = proto->Name1;
std::ostringstream out; std::ostringstream out;
out << "|c" << color << "|Hitem:" << proto->ItemId << ":0:0:0:0:0:0:0" out << "|c" << color << "|Hitem:" << proto->ItemId << ":0:0:0:0:0:0:0"
<< "|h[" << itemName << "]|h|r"; << "|h[" << proto->Name1 << "]|h|r";
if (count > 1) if (count > 1)
out << "x" << count; out << "x" << count;
@@ -483,7 +462,7 @@ GuidVector ChatHelper::parseGameobjects(std::string const text)
break; break;
std::string const entryC = text.substr(pos, endPos - pos); // get std::string const within window i.e entry std::string const entryC = text.substr(pos, endPos - pos); // get std::string const within window i.e entry
//uint32 entry = atol(entryC.c_str()); // convert ascii to float uint32 entry = atol(entryC.c_str()); // convert ascii to float
ObjectGuid lootCurrent = ObjectGuid(guid); ObjectGuid lootCurrent = ObjectGuid(guid);

View File

@@ -18,7 +18,7 @@ class FleePoint
{ {
public: public:
FleePoint(PlayerbotAI* botAI, float x, float y, float z) FleePoint(PlayerbotAI* botAI, float x, float y, float z)
: x(x), y(y), z(z), sumDistance(0.0f), minDistance(0.0f), botAI(botAI) : botAI(botAI), sumDistance(0.0f), minDistance(0.0f), x(x), y(y), z(z)
{ {
} }

View File

@@ -168,7 +168,7 @@ public:
bool Apply(ItemTemplate const* proto) override bool Apply(ItemTemplate const* proto) override
{ {
//uint32* tradeSkills = PlayerbotFactory::tradeSkills; uint32* tradeSkills = PlayerbotFactory::tradeSkills;
for (uint32 i = 0; i < 13; ++i) for (uint32 i = 0; i < 13; ++i)
{ {
@@ -546,7 +546,7 @@ bool GuildTaskMgr::IsGuildTaskItem(uint32 itemId, uint32 guildId)
} }
std::map<uint32, uint32> GuildTaskMgr::GetTaskValues(uint32 owner, std::string const type, std::map<uint32, uint32> GuildTaskMgr::GetTaskValues(uint32 owner, std::string const type,
[[maybe_unused]] uint32* validIn /* = nullptr */) uint32* validIn /* = nullptr */)
{ {
std::map<uint32, uint32> results; std::map<uint32, uint32> results;
@@ -571,10 +571,10 @@ std::map<uint32, uint32> GuildTaskMgr::GetTaskValues(uint32 owner, std::string c
} while (result->NextRow()); } while (result->NextRow());
} }
return results; return std::move(results);
} }
uint32 GuildTaskMgr::GetTaskValue(uint32 owner, uint32 guildId, std::string const type, [[maybe_unused]] uint32* validIn /* = nullptr */) uint32 GuildTaskMgr::GetTaskValue(uint32 owner, uint32 guildId, std::string const type, uint32* validIn /* = nullptr */)
{ {
uint32 value = 0; uint32 value = 0;
@@ -622,7 +622,7 @@ uint32 GuildTaskMgr::SetTaskValue(uint32 owner, uint32 guildId, std::string cons
return value; return value;
} }
bool GuildTaskMgr::HandleConsoleCommand(ChatHandler* /* handler */, char const* args) bool GuildTaskMgr::HandleConsoleCommand(ChatHandler* handler, char const* args)
{ {
if (!sPlayerbotAIConfig->guildTaskEnabled) if (!sPlayerbotAIConfig->guildTaskEnabled)
{ {

View File

@@ -9,7 +9,7 @@
#include "Playerbots.h" #include "Playerbots.h"
#include "Unit.h" #include "Unit.h"
#define MAX_LOOT_OBJECT_COUNT 200 #define MAX_LOOT_OBJECT_COUNT 10
LootTarget::LootTarget(ObjectGuid guid) : guid(guid), asOfTime(time(nullptr)) {} LootTarget::LootTarget(ObjectGuid guid) : guid(guid), asOfTime(time(nullptr)) {}
@@ -81,8 +81,7 @@ void LootObject::Refresh(Player* bot, ObjectGuid lootGUID)
GameObject* go = botAI->GetGameObject(lootGUID); GameObject* go = botAI->GetGameObject(lootGUID);
if (go && go->isSpawned() && go->GetGoState() == GO_STATE_READY) if (go && go->isSpawned() && go->GetGoState() == GO_STATE_READY)
{ {
bool onlyHasQuestItems = true; bool isQuestItemOnly = false;
bool hasAnyQuestItems = false;
GameObjectQuestItemList const* items = sObjectMgr->GetGameObjectQuestItemList(go->GetEntry()); GameObjectQuestItemList const* items = sObjectMgr->GetGameObjectQuestItemList(go->GetEntry());
for (int i = 0; i < MAX_GAMEOBJECT_QUEST_ITEMS; i++) for (int i = 0; i < MAX_GAMEOBJECT_QUEST_ITEMS; i++)
@@ -90,88 +89,19 @@ void LootObject::Refresh(Player* bot, ObjectGuid lootGUID)
if (!items || i >= items->size()) if (!items || i >= items->size())
break; break;
uint32 itemId = uint32((*items)[i]); auto itemId = uint32((*items)[i]);
if (!itemId)
continue;
hasAnyQuestItems = true;
if (IsNeededForQuest(bot, itemId)) if (IsNeededForQuest(bot, itemId))
{ {
this->guid = lootGUID; this->guid = lootGUID;
return; return;
} }
isQuestItemOnly |= itemId > 0;
const ItemTemplate* proto = sObjectMgr->GetItemTemplate(itemId);
if (!proto)
continue;
if (proto->Class != ITEM_CLASS_QUEST)
{
onlyHasQuestItems = false;
}
} }
// Retrieve the correct loot table entry if (isQuestItemOnly)
uint32 lootEntry = go->GetGOInfo()->GetLootId();
if (lootEntry == 0)
return; return;
// Check the main loot template
if (const LootTemplate* lootTemplate = LootTemplates_Gameobject.GetLootFor(lootEntry))
{
Loot loot;
lootTemplate->Process(loot, LootTemplates_Gameobject, 1, bot);
for (const LootItem& item : loot.items)
{
uint32 itemId = item.itemid;
if (!itemId)
continue;
const ItemTemplate* proto = sObjectMgr->GetItemTemplate(itemId);
if (!proto)
continue;
if (proto->Class != ITEM_CLASS_QUEST)
{
onlyHasQuestItems = false;
break;
}
// If this item references another loot table, process it
if (const LootTemplate* refLootTemplate = LootTemplates_Reference.GetLootFor(itemId))
{
Loot refLoot;
refLootTemplate->Process(refLoot, LootTemplates_Reference, 1, bot);
for (const LootItem& refItem : refLoot.items)
{
uint32 refItemId = refItem.itemid;
if (!refItemId)
continue;
const ItemTemplate* refProto = sObjectMgr->GetItemTemplate(refItemId);
if (!refProto)
continue;
if (refProto->Class != ITEM_CLASS_QUEST)
{
onlyHasQuestItems = false;
break;
}
}
}
}
}
// If gameobject has only quest items that bot doesnt need, skip it.
if (hasAnyQuestItems && onlyHasQuestItems)
return;
// Otherwise, loot it.
guid = lootGUID;
uint32 goId = go->GetEntry(); uint32 goId = go->GetEntry();
uint32 lockId = go->GetGOInfo()->GetLockId(); uint32 lockId = go->GetGOInfo()->GetLockId();
LockEntry const* lockInfo = sLockStore.LookupEntry(lockId); LockEntry const* lockInfo = sLockStore.LookupEntry(lockId);
@@ -189,7 +119,6 @@ void LootObject::Refresh(Player* bot, ObjectGuid lootGUID)
guid = lootGUID; guid = lootGUID;
} }
break; break;
case LOCK_KEY_SKILL: case LOCK_KEY_SKILL:
if (goId == 13891 || goId == 19535) // Serpentbloom if (goId == 13891 || goId == 19535) // Serpentbloom
{ {
@@ -202,7 +131,6 @@ void LootObject::Refresh(Player* bot, ObjectGuid lootGUID)
guid = lootGUID; guid = lootGUID;
} }
break; break;
case LOCK_KEY_NONE: case LOCK_KEY_NONE:
guid = lootGUID; guid = lootGUID;
break; break;
@@ -272,11 +200,7 @@ LootObject::LootObject(LootObject const& other)
bool LootObject::IsLootPossible(Player* bot) bool LootObject::IsLootPossible(Player* bot)
{ {
if (IsEmpty() || !bot) if (IsEmpty() || !GetWorldObject(bot))
return false;
WorldObject* worldObj = GetWorldObject(bot); // Store result to avoid multiple calls
if (!worldObj)
return false; return false;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
@@ -287,7 +211,7 @@ bool LootObject::IsLootPossible(Player* bot)
if (reqItem && !bot->HasItemCount(reqItem, 1)) if (reqItem && !bot->HasItemCount(reqItem, 1))
return false; return false;
if (abs(worldObj->GetPositionZ() - bot->GetPositionZ()) > INTERACTION_DISTANCE -2.0f) if (abs(GetWorldObject(bot)->GetPositionZ() - bot->GetPositionZ()) > INTERACTION_DISTANCE)
return false; return false;
Creature* creature = botAI->GetCreature(guid); Creature* creature = botAI->GetCreature(guid);
@@ -312,32 +236,25 @@ bool LootObject::IsLootPossible(Player* bot)
uint32 skillValue = uint32(bot->GetSkillValue(skillId)); uint32 skillValue = uint32(bot->GetSkillValue(skillId));
if (reqSkillValue > skillValue) if (reqSkillValue > skillValue)
return false; return false;
if (skillId == SKILL_MINING && if (skillId == SKILL_MINING && !bot->HasItemCount(756, 1) &&
!bot->HasItemCount(756, 1) && !bot->HasItemCount(778, 1) &&
!bot->HasItemCount(778, 1) && !bot->HasItemCount(1819, 1) &&
!bot->HasItemCount(1819, 1) && !bot->HasItemCount(1893, 1) &&
!bot->HasItemCount(1893, 1) && !bot->HasItemCount(1959, 1) &&
!bot->HasItemCount(1959, 1) && !bot->HasItemCount(2901, 1) &&
!bot->HasItemCount(2901, 1) && !bot->HasItemCount(9465, 1) &&
!bot->HasItemCount(9465, 1) && !bot->HasItemCount(20723, 1) &&
!bot->HasItemCount(20723, 1) && !bot->HasItemCount(40772, 1) &&
!bot->HasItemCount(40772, 1) && !bot->HasItemCount(40892, 1) &&
!bot->HasItemCount(40892, 1) && !bot->HasItemCount(40893, 1) )
!bot->HasItemCount(40893, 1))
{ if (skillId == SKILL_SKINNING && !bot->HasItemCount(7005, 1) &&
return false; // Bot is missing a mining pick !bot->HasItemCount(40772, 1) &&
} !bot->HasItemCount(40893, 1) &&
!bot->HasItemCount(12709, 1) &&
if (skillId == SKILL_SKINNING && !bot->HasItemCount(19901, 1) )
!bot->HasItemCount(7005, 1) && return false;
!bot->HasItemCount(40772, 1) &&
!bot->HasItemCount(40893, 1) &&
!bot->HasItemCount(12709, 1) &&
!bot->HasItemCount(19901, 1))
{
return false; // Bot is missing a skinning knife
}
return true; return true;
} }
@@ -380,6 +297,7 @@ LootObject LootObjectStack::GetLoot(float maxDistance)
std::vector<LootObject> ordered = OrderByDistance(maxDistance); std::vector<LootObject> ordered = OrderByDistance(maxDistance);
return ordered.empty() ? LootObject() : *ordered.begin(); return ordered.empty() ? LootObject() : *ordered.begin();
} }
std::vector<LootObject> LootObjectStack::OrderByDistance(float maxDistance) std::vector<LootObject> LootObjectStack::OrderByDistance(float maxDistance)
{ {
availableLoot.shrink(time(nullptr) - 30); availableLoot.shrink(time(nullptr) - 30);
@@ -390,23 +308,17 @@ std::vector<LootObject> LootObjectStack::OrderByDistance(float maxDistance)
{ {
ObjectGuid guid = i->guid; ObjectGuid guid = i->guid;
LootObject lootObject(bot, guid); LootObject lootObject(bot, guid);
if (!lootObject.IsLootPossible(bot)) // Ensure loot object is valid if (!lootObject.IsLootPossible(bot))
continue; continue;
WorldObject* worldObj = lootObject.GetWorldObject(bot); float distance = bot->GetDistance(lootObject.GetWorldObject(bot));
if (!worldObj) // Prevent null pointer dereference
{
continue;
}
float distance = bot->GetDistance(worldObj);
if (!maxDistance || distance <= maxDistance) if (!maxDistance || distance <= maxDistance)
sortedMap[distance] = lootObject; sortedMap[distance] = lootObject;
} }
std::vector<LootObject> result; std::vector<LootObject> result;
for (auto& [_, lootObject] : sortedMap) for (std::map<float, LootObject>::iterator i = sortedMap.begin(); i != sortedMap.end(); i++)
result.push_back(lootObject); result.push_back(i->second);
return result; return result;
} }

File diff suppressed because it is too large Load Diff

View File

@@ -13,17 +13,13 @@
#include "ChatFilter.h" #include "ChatFilter.h"
#include "ChatHelper.h" #include "ChatHelper.h"
#include "Common.h" #include "Common.h"
#include "CreatureData.h"
#include "Event.h" #include "Event.h"
#include "Item.h" #include "Item.h"
#include "NewRpgInfo.h"
#include "NewRpgStrategy.h"
#include "PlayerbotAIBase.h" #include "PlayerbotAIBase.h"
#include "PlayerbotAIConfig.h" #include "PlayerbotAIConfig.h"
#include "PlayerbotSecurity.h" #include "PlayerbotSecurity.h"
#include "PlayerbotTextMgr.h" #include "PlayerbotTextMgr.h"
#include "SpellAuras.h" #include "SpellAuras.h"
#include "Util.h"
#include "WorldPacket.h" #include "WorldPacket.h"
class AiObjectContext; class AiObjectContext;
@@ -392,8 +388,6 @@ public:
void HandleMasterOutgoingPacket(WorldPacket const& packet); void HandleMasterOutgoingPacket(WorldPacket const& packet);
void HandleTeleportAck(); void HandleTeleportAck();
void ChangeEngine(BotState type); void ChangeEngine(BotState type);
void ChangeEngineOnCombat();
void ChangeEngineOnNonCombat();
void DoNextAction(bool minimal = false); void DoNextAction(bool minimal = false);
virtual bool DoSpecificAction(std::string const name, Event event = Event(), bool silent = false, virtual bool DoSpecificAction(std::string const name, Event event = Event(), bool silent = false,
std::string const qualifier = ""); std::string const qualifier = "");
@@ -413,8 +407,8 @@ public:
static bool IsRanged(Player* player, bool bySpec = false); static bool IsRanged(Player* player, bool bySpec = false);
static bool IsMelee(Player* player, bool bySpec = false); static bool IsMelee(Player* player, bool bySpec = false);
static bool IsCaster(Player* player, bool bySpec = false); static bool IsCaster(Player* player, bool bySpec = false);
static bool IsCombo(Player* player, bool bySpec = false);
static bool IsRangedDps(Player* player, bool bySpec = false); static bool IsRangedDps(Player* player, bool bySpec = false);
static bool IsCombo(Player* player);
static bool IsMainTank(Player* player); static bool IsMainTank(Player* player);
static uint32 GetGroupTankNum(Player* player); static uint32 GetGroupTankNum(Player* player);
bool IsAssistTank(Player* player); bool IsAssistTank(Player* player);
@@ -424,7 +418,7 @@ public:
bool HasAggro(Unit* unit); bool HasAggro(Unit* unit);
int32 GetGroupSlotIndex(Player* player); int32 GetGroupSlotIndex(Player* player);
int32 GetRangedIndex(Player* player); int32 GetRangedIndex(Player* player);
int32 GetClassIndex(Player* player, uint8 cls); int32 GetClassIndex(Player* player, uint8_t cls);
int32 GetRangedDpsIndex(Player* player); int32 GetRangedDpsIndex(Player* player);
int32 GetMeleeIndex(Player* player); int32 GetMeleeIndex(Player* player);
@@ -438,9 +432,8 @@ public:
std::vector<Player*> GetPlayersInGroup(); std::vector<Player*> GetPlayersInGroup();
const AreaTableEntry* GetCurrentArea(); const AreaTableEntry* GetCurrentArea();
const AreaTableEntry* GetCurrentZone(); const AreaTableEntry* GetCurrentZone();
static std::string GetLocalizedAreaName(const AreaTableEntry* entry); std::string GetLocalizedAreaName(const AreaTableEntry* entry);
static std::string GetLocalizedCreatureName(uint32 entry);
static std::string GetLocalizedGameObjectName(uint32 entry);
bool TellMaster(std::ostringstream& stream, PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL); bool TellMaster(std::ostringstream& stream, PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL);
bool TellMaster(std::string const text, PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL); bool TellMaster(std::string const text, PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL);
bool TellMasterNoFacing(std::ostringstream& stream, bool TellMasterNoFacing(std::ostringstream& stream,
@@ -467,10 +460,7 @@ public:
bool PlayEmote(uint32 emote); bool PlayEmote(uint32 emote);
void Ping(float x, float y); void Ping(float x, float y);
Item* FindPoison() const; Item* FindPoison() const;
Item* FindAmmo() const;
Item* FindBandage() const; Item* FindBandage() const;
Item* FindOpenableItem() const;
Item* FindLockedItem() const;
Item* FindConsumable(uint32 displayId) const; Item* FindConsumable(uint32 displayId) const;
Item* FindStoneFor(Item* weapon) const; Item* FindStoneFor(Item* weapon) const;
Item* FindOilFor(Item* weapon) const; Item* FindOilFor(Item* weapon) const;
@@ -492,8 +482,8 @@ public:
virtual bool HasAuraToDispel(Unit* player, uint32 dispelType); virtual bool HasAuraToDispel(Unit* player, uint32 dispelType);
bool CanCastSpell(uint32 spellid, Unit* target, bool checkHasSpell = true, Item* itemTarget = nullptr, bool CanCastSpell(uint32 spellid, Unit* target, bool checkHasSpell = true, Item* itemTarget = nullptr,
Item* castItem = nullptr); Item* castItem = nullptr);
bool CanCastSpell(uint32 spellid, GameObject* goTarget, bool checkHasSpell = true); bool CanCastSpell(uint32 spellid, GameObject* goTarget, uint8 effectMask, bool checkHasSpell = true);
bool CanCastSpell(uint32 spellid, float x, float y, float z, bool checkHasSpell = true, bool CanCastSpell(uint32 spellid, float x, float y, float z, uint8 effectMask, bool checkHasSpell = true,
Item* itemTarget = nullptr); Item* itemTarget = nullptr);
bool HasAura(uint32 spellId, Unit const* player); bool HasAura(uint32 spellId, Unit const* player);
@@ -509,8 +499,7 @@ public:
bool IsInVehicle(bool canControl = false, bool canCast = false, bool canAttack = false, bool canTurn = false, bool IsInVehicle(bool canControl = false, bool canCast = false, bool canAttack = false, bool canTurn = false,
bool fixed = false); bool fixed = false);
uint32 GetEquipGearScore(Player* player); uint32 GetEquipGearScore(Player* player, bool withBags, bool withBank);
//uint32 GetEquipGearScore(Player* player, bool withBags, bool withBank);
static uint32 GetMixedGearScore(Player* player, bool withBags, bool withBank, uint32 topN = 0); static uint32 GetMixedGearScore(Player* player, bool withBags, bool withBank, uint32 topN = 0);
bool HasSkill(SkillType skill); bool HasSkill(SkillType skill);
bool IsAllowedCommand(std::string const text); bool IsAllowedCommand(std::string const text);
@@ -530,7 +519,7 @@ public:
bool IsAlt(); bool IsAlt();
Player* GetGroupMaster(); Player* GetGroupMaster();
// Returns a semi-random (cycling) number that is fixed for each bot. // Returns a semi-random (cycling) number that is fixed for each bot.
uint32 GetFixedBotNumer(uint32 maxNum = 100, float cyclePerMin = 1); uint32 GetFixedBotNumer(BotTypeNumber typeNumber, uint32 maxNum = 100, float cyclePerMin = 1);
GrouperType GetGrouperType(); GrouperType GetGrouperType();
GuilderType GetGuilderType(); GuilderType GetGuilderType();
bool HasPlayerNearby(WorldPosition* pos, float range = sPlayerbotAIConfig->reactDistance); bool HasPlayerNearby(WorldPosition* pos, float range = sPlayerbotAIConfig->reactDistance);
@@ -565,7 +554,6 @@ public:
void ResetJumpDestination() { jumpDestination = Position(); } void ResetJumpDestination() { jumpDestination = Position(); }
bool CanMove(); bool CanMove();
static bool IsRealGuild(uint32 guildId);
bool IsInRealGuild(); bool IsInRealGuild();
static std::vector<std::string> dispel_whitelist; static std::vector<std::string> dispel_whitelist;
bool EqualLowercaseName(std::string s1, std::string s2); bool EqualLowercaseName(std::string s1, std::string s2);
@@ -584,21 +572,15 @@ public:
std::set<uint32> GetCurrentIncompleteQuestIds(); std::set<uint32> GetCurrentIncompleteQuestIds();
void PetFollow(); void PetFollow();
static float GetItemScoreMultiplier(ItemQualities quality); static float GetItemScoreMultiplier(ItemQualities quality);
static bool IsHealingSpell(uint32 spellFamilyName, flag96 spelFalimyFlags);
static SpellFamilyNames Class2SpellFamilyName(uint8 cls);
NewRpgInfo rpgInfo;
NewRpgStatistic rpgStatistic;
std::unordered_set<uint32> lowPriorityQuest;
private: private:
static void _fillGearScoreData(Player* player, Item* item, std::vector<uint32>* gearScore, uint32& twoHandScore, static void _fillGearScoreData(Player* player, Item* item, std::vector<uint32>* gearScore, uint32& twoHandScore,
bool mixed = false); bool mixed = false);
bool IsTellAllowed(PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL); bool IsTellAllowed(PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL);
void UpdateAIGroupMembership();
Item* FindItemInInventory(std::function<bool(ItemTemplate const*)> checkItem) const;
void HandleCommands(); void HandleCommands();
void HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang = LANG_UNIVERSAL); void HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang = LANG_UNIVERSAL);
bool _isBotInitializing = false; bool _isBotInitializing = true;
protected: protected:
Player* bot; Player* bot;

View File

@@ -120,18 +120,16 @@ bool PlayerbotAIConfig::Initialize()
randomGearQualityLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomGearQualityLimit", 3); randomGearQualityLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomGearQualityLimit", 3);
randomGearScoreLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomGearScoreLimit", 0); randomGearScoreLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomGearScoreLimit", 0);
randomBotMinLevelChance = sConfigMgr->GetOption<float>("AiPlayerbot.RandomBotMinLevelChance", 0.1f); randomBotMaxLevelChance = sConfigMgr->GetOption<float>("AiPlayerbot.RandomBotMaxLevelChance", 0.15f);
randomBotMaxLevelChance = sConfigMgr->GetOption<float>("AiPlayerbot.RandomBotMaxLevelChance", 0.1f);
randomBotRpgChance = sConfigMgr->GetOption<float>("AiPlayerbot.RandomBotRpgChance", 0.20f); randomBotRpgChance = sConfigMgr->GetOption<float>("AiPlayerbot.RandomBotRpgChance", 0.20f);
iterationsPerTick = sConfigMgr->GetOption<int32>("AiPlayerbot.IterationsPerTick", 100); iterationsPerTick = sConfigMgr->GetOption<int32>("AiPlayerbot.IterationsPerTick", 100);
allowAccountBots = sConfigMgr->GetOption<bool>("AiPlayerbot.AllowAccountBots", true);
allowGuildBots = sConfigMgr->GetOption<bool>("AiPlayerbot.AllowGuildBots", true); allowGuildBots = sConfigMgr->GetOption<bool>("AiPlayerbot.AllowGuildBots", true);
allowTrustedAccountBots = sConfigMgr->GetOption<bool>("AiPlayerbot.AllowTrustedAccountBots", true);
randomBotGuildNearby = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotGuildNearby", false); randomBotGuildNearby = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotGuildNearby", false);
randomBotInvitePlayer = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotInvitePlayer", false); randomBotInvitePlayer = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotInvitePlayer", false);
inviteChat = sConfigMgr->GetOption<bool>("AiPlayerbot.InviteChat", false); inviteChat = sConfigMgr->GetOption<bool>("AiPlayerbot.InviteChat", false);
allowPlayerBots = sConfigMgr->GetOption<bool>("AiPlayerbot.AllowPlayerBots", false);
randomBotMapsAsString = sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotMaps", "0,1,530,571"); randomBotMapsAsString = sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotMaps", "0,1,530,571");
LoadList<std::vector<uint32>>(randomBotMapsAsString, randomBotMaps); LoadList<std::vector<uint32>>(randomBotMapsAsString, randomBotMaps);
@@ -157,7 +155,7 @@ bool PlayerbotAIConfig::Initialize()
botAutologin = sConfigMgr->GetOption<bool>("AiPlayerbot.BotAutologin", false); botAutologin = sConfigMgr->GetOption<bool>("AiPlayerbot.BotAutologin", false);
randomBotAutologin = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotAutologin", true); randomBotAutologin = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotAutologin", true);
minRandomBots = sConfigMgr->GetOption<int32>("AiPlayerbot.MinRandomBots", 50); minRandomBots = sConfigMgr->GetOption<int32>("AiPlayerbot.MinRandomBots", 50);
maxRandomBots = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxRandomBots", 50); maxRandomBots = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxRandomBots", 200);
randomBotUpdateInterval = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotUpdateInterval", 20); randomBotUpdateInterval = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotUpdateInterval", 20);
randomBotCountChangeMinInterval = randomBotCountChangeMinInterval =
sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotCountChangeMinInterval", 30 * MINUTE); sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotCountChangeMinInterval", 30 * MINUTE);
@@ -175,8 +173,8 @@ bool PlayerbotAIConfig::Initialize()
maxRandomBotReviveTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxRandomBotReviveTime", 5 * MINUTE); maxRandomBotReviveTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxRandomBotReviveTime", 5 * MINUTE);
minRandomBotTeleportInterval = sConfigMgr->GetOption<int32>("AiPlayerbot.MinRandomBotTeleportInterval", 1 * HOUR); minRandomBotTeleportInterval = sConfigMgr->GetOption<int32>("AiPlayerbot.MinRandomBotTeleportInterval", 1 * HOUR);
maxRandomBotTeleportInterval = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxRandomBotTeleportInterval", 5 * HOUR); maxRandomBotTeleportInterval = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxRandomBotTeleportInterval", 5 * HOUR);
permanantlyInWorldTime = randomBotInWorldWithRotationDisabled =
sConfigMgr->GetOption<int32>("AiPlayerbot.PermanantlyInWorldTime", 1 * YEAR); sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotInWorldWithRotationDisabled", 1 * YEAR);
randomBotTeleportDistance = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotTeleportDistance", 100); randomBotTeleportDistance = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotTeleportDistance", 100);
randomBotsPerInterval = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotsPerInterval", 60); randomBotsPerInterval = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotsPerInterval", 60);
minRandomBotsPriceChangeInterval = minRandomBotsPriceChangeInterval =
@@ -280,21 +278,9 @@ bool PlayerbotAIConfig::Initialize()
randomBotJoinBG = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotJoinBG", true); randomBotJoinBG = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotJoinBG", true);
randomBotAutoJoinBG = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotAutoJoinBG", false); randomBotAutoJoinBG = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotAutoJoinBG", false);
randomBotAutoJoinWarsongBracket = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotAutoJoinWarsongBracket", 14);
randomBotAutoJoinArenaBracket = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotAutoJoinArenaBracket", 7); randomBotAutoJoinArenaBracket = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotAutoJoinArenaBracket", 7);
randomBotAutoJoinBGWarsongCount = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotAutoJoinBGWarsongCount", 0);
randomBotAutoJoinICBrackets = sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotAutoJoinICBrackets", "0,1");
randomBotAutoJoinEYBrackets = sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotAutoJoinEYBrackets", "0,1,2");
randomBotAutoJoinAVBrackets = sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotAutoJoinAVBrackets", "0,1,2,3");
randomBotAutoJoinABBrackets = sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotAutoJoinABBrackets", "0,1,2,3,4,5,6");
randomBotAutoJoinWSBrackets = sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotAutoJoinWSBrackets", "0,1,2,3,4,5,6,7");
randomBotAutoJoinBGICCount = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotAutoJoinBGICCount", 0);
randomBotAutoJoinBGEYCount = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotAutoJoinBGEYCount", 0);
randomBotAutoJoinBGAVCount = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotAutoJoinBGAVCount", 0);
randomBotAutoJoinBGABCount = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotAutoJoinBGABCount", 0);
randomBotAutoJoinBGWSCount = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotAutoJoinBGWSCount", 0);
randomBotAutoJoinBGRatedArena2v2Count = randomBotAutoJoinBGRatedArena2v2Count =
sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotAutoJoinBGRatedArena2v2Count", 0); sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotAutoJoinBGRatedArena2v2Count", 0);
randomBotAutoJoinBGRatedArena3v3Count = randomBotAutoJoinBGRatedArena3v3Count =
@@ -326,12 +312,9 @@ bool PlayerbotAIConfig::Initialize()
commandServerPort = sConfigMgr->GetOption<int32>("AiPlayerbot.CommandServerPort", 8888); commandServerPort = sConfigMgr->GetOption<int32>("AiPlayerbot.CommandServerPort", 8888);
perfMonEnabled = sConfigMgr->GetOption<bool>("AiPlayerbot.PerfMonEnabled", false); perfMonEnabled = sConfigMgr->GetOption<bool>("AiPlayerbot.PerfMonEnabled", false);
useGroundMountAtMinLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.UseGroundMountAtMinLevel", 20); LOG_INFO("server.loading", "---------------------------------------");
useFastGroundMountAtMinLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.UseFastGroundMountAtMinLevel", 40); LOG_INFO("server.loading", " Loading TalentSpecs ");
useFlyMountAtMinLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.UseFlyMountAtMinLevel", 60); LOG_INFO("server.loading", "---------------------------------------");
useFastFlyMountAtMinLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.UseFastFlyMountAtMinLevel", 70);
LOG_INFO("server.loading", "Loading TalentSpecs...");
for (uint32 cls = 1; cls < MAX_CLASSES; ++cls) for (uint32 cls = 1; cls < MAX_CLASSES; ++cls)
{ {
@@ -416,27 +399,22 @@ bool PlayerbotAIConfig::Initialize()
worldBuffs.clear(); worldBuffs.clear();
LOG_INFO("playerbots", "Loading Worldbuff...");
for (uint32 factionId = 0; factionId < 3; factionId++) for (uint32 factionId = 0; factionId < 3; factionId++)
{ {
for (uint32 classId = 0; classId < MAX_CLASSES; classId++) for (uint32 classId = 0; classId < MAX_CLASSES; classId++)
{ {
for (uint32 specId = 0; specId <= MAX_WORLDBUFF_SPECNO; specId++) for (uint32 minLevel = 0; minLevel < MAX_LEVEL; minLevel++)
{ {
for (uint32 minLevel = 0; minLevel <= randomBotMaxLevel; minLevel++) for (uint32 maxLevel = 0; maxLevel < MAX_LEVEL; maxLevel++)
{ {
for (uint32 maxLevel = minLevel; maxLevel <= randomBotMaxLevel; maxLevel++) loadWorldBuf(factionId, classId, minLevel, maxLevel);
{
loadWorldBuff(factionId, classId, specId, minLevel, maxLevel);
}
loadWorldBuff(factionId, classId, specId, minLevel, 0);
} }
} }
} }
} }
randomBotAccountPrefix = sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotAccountPrefix", "rndbot"); randomBotAccountPrefix = sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotAccountPrefix", "rndbot");
randomBotAccountCount = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotAccountCount", 0); randomBotAccountCount = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotAccountCount", 200);
deleteRandomBotAccounts = sConfigMgr->GetOption<bool>("AiPlayerbot.DeleteRandomBotAccounts", false); deleteRandomBotAccounts = sConfigMgr->GetOption<bool>("AiPlayerbot.DeleteRandomBotAccounts", false);
randomBotGuildCount = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotGuildCount", 20); randomBotGuildCount = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotGuildCount", 20);
deleteRandomBotGuilds = sConfigMgr->GetOption<bool>("AiPlayerbot.DeleteRandomBotGuilds", false); deleteRandomBotGuilds = sConfigMgr->GetOption<bool>("AiPlayerbot.DeleteRandomBotGuilds", false);
@@ -445,7 +423,8 @@ bool PlayerbotAIConfig::Initialize()
minGuildTaskChangeTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MinGuildTaskChangeTime", 3 * 24 * 3600); minGuildTaskChangeTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MinGuildTaskChangeTime", 3 * 24 * 3600);
maxGuildTaskChangeTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxGuildTaskChangeTime", 4 * 24 * 3600); maxGuildTaskChangeTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxGuildTaskChangeTime", 4 * 24 * 3600);
minGuildTaskAdvertisementTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MinGuildTaskAdvertisementTime", 300); minGuildTaskAdvertisementTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MinGuildTaskAdvertisementTime", 300);
maxGuildTaskAdvertisementTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxGuildTaskAdvertisementTime", 12 * 3600); maxGuildTaskAdvertisementTime =
sConfigMgr->GetOption<int32>("AiPlayerbot.MaxGuildTaskAdvertisementTime", 12 * 3600);
minGuildTaskRewardTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MinGuildTaskRewardTime", 300); minGuildTaskRewardTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MinGuildTaskRewardTime", 300);
maxGuildTaskRewardTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxGuildTaskRewardTime", 3600); maxGuildTaskRewardTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxGuildTaskRewardTime", 3600);
guildTaskAdvertCleanupTime = sConfigMgr->GetOption<int32>("AiPlayerbot.GuildTaskAdvertCleanupTime", 300); guildTaskAdvertCleanupTime = sConfigMgr->GetOption<int32>("AiPlayerbot.GuildTaskAdvertCleanupTime", 300);
@@ -475,30 +454,22 @@ bool PlayerbotAIConfig::Initialize()
autoInitEquipLevelLimitRatio = sConfigMgr->GetOption<float>("AiPlayerbot.AutoInitEquipLevelLimitRatio", 1.0); autoInitEquipLevelLimitRatio = sConfigMgr->GetOption<float>("AiPlayerbot.AutoInitEquipLevelLimitRatio", 1.0);
maxAddedBots = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxAddedBots", 40); maxAddedBots = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxAddedBots", 40);
maxAddedBotsPerClass = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxAddedBotsPerClass", 10);
addClassCommand = sConfigMgr->GetOption<int32>("AiPlayerbot.AddClassCommand", 1); addClassCommand = sConfigMgr->GetOption<int32>("AiPlayerbot.AddClassCommand", 1);
addClassAccountPoolSize = sConfigMgr->GetOption<int32>("AiPlayerbot.AddClassAccountPoolSize", 50); addClassAccountPoolSize = sConfigMgr->GetOption<int32>("AiPlayerbot.AddClassAccountPoolSize", 50);
maintenanceCommand = sConfigMgr->GetOption<int32>("AiPlayerbot.MaintenanceCommand", 1); maintenanceCommand = sConfigMgr->GetOption<int32>("AiPlayerbot.MaintenanceCommand", 1);
autoGearCommand = sConfigMgr->GetOption<int32>("AiPlayerbot.AutoGearCommand", 1); autoGearCommand = sConfigMgr->GetOption<int32>("AiPlayerbot.AutoGearCommand", 1);
autoGearCommandAltBots = sConfigMgr->GetOption<int32>("AiPlayerbot.AutoGearCommandAltBots", 1);
autoGearQualityLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.AutoGearQualityLimit", 3); autoGearQualityLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.AutoGearQualityLimit", 3);
autoGearScoreLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.AutoGearScoreLimit", 0); autoGearScoreLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.AutoGearScoreLimit", 0);
playerbotsXPrate = sConfigMgr->GetOption<float>("AiPlayerbot.PlayerbotsXPRate", 1.0); playerbotsXPrate = sConfigMgr->GetOption<int32>("AiPlayerbot.KillXPRate", 1);
randomBotAllianceRatio = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotAllianceRatio", 50);
randomBotHordeRatio = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotHordeRatio", 50);
disableDeathKnightLogin = sConfigMgr->GetOption<bool>("AiPlayerbot.DisableDeathKnightLogin", 0); disableDeathKnightLogin = sConfigMgr->GetOption<bool>("AiPlayerbot.DisableDeathKnightLogin", 0);
limitTalentsExpansion = sConfigMgr->GetOption<bool>("AiPlayerbot.LimitTalentsExpansion", 0);
botActiveAlone = sConfigMgr->GetOption<int32>("AiPlayerbot.BotActiveAlone", 100); botActiveAlone = sConfigMgr->GetOption<int32>("AiPlayerbot.BotActiveAlone", 100);
BotActiveAloneForceWhenInRadius = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotActiveAloneForceWhenInRadius", 150);
BotActiveAloneForceWhenInZone = sConfigMgr->GetOption<bool>("AiPlayerbot.BotActiveAloneForceWhenInZone", 1);
BotActiveAloneForceWhenInMap = sConfigMgr->GetOption<bool>("AiPlayerbot.BotActiveAloneForceWhenInMap", 0);
BotActiveAloneForceWhenIsFriend = sConfigMgr->GetOption<bool>("AiPlayerbot.BotActiveAloneForceWhenIsFriend", 1);
BotActiveAloneForceWhenInGuild = sConfigMgr->GetOption<bool>("AiPlayerbot.BotActiveAloneForceWhenInGuild", 1);
botActiveAloneSmartScale = sConfigMgr->GetOption<bool>("AiPlayerbot.botActiveAloneSmartScale", 1); botActiveAloneSmartScale = sConfigMgr->GetOption<bool>("AiPlayerbot.botActiveAloneSmartScale", 1);
botActiveAloneSmartScaleDiffLimitfloor = sConfigMgr->GetOption<uint32>("AiPlayerbot.botActiveAloneSmartScaleDiffLimitfloor", 50); botActiveAloneSmartScaleWhenMinLevel =
botActiveAloneSmartScaleDiffLimitCeiling = sConfigMgr->GetOption<uint32>("AiPlayerbot.botActiveAloneSmartScaleDiffLimitCeiling", 200); sConfigMgr->GetOption<uint32>("AiPlayerbot.botActiveAloneSmartScaleWhenMinLevel", 1);
botActiveAloneSmartScaleWhenMinLevel = sConfigMgr->GetOption<uint32>("AiPlayerbot.botActiveAloneSmartScaleWhenMinLevel", 1); botActiveAloneSmartScaleWhenMaxLevel =
botActiveAloneSmartScaleWhenMaxLevel = sConfigMgr->GetOption<uint32>("AiPlayerbot.botActiveAloneSmartScaleWhenMaxLevel", 80); sConfigMgr->GetOption<uint32>("AiPlayerbot.botActiveAloneSmartScaleWhenMaxLevel", 80);
randombotsWalkingRPG = sConfigMgr->GetOption<bool>("AiPlayerbot.RandombotsWalkingRPG", false); randombotsWalkingRPG = sConfigMgr->GetOption<bool>("AiPlayerbot.RandombotsWalkingRPG", false);
randombotsWalkingRPGInDoors = sConfigMgr->GetOption<bool>("AiPlayerbot.RandombotsWalkingRPG.InDoors", false); randombotsWalkingRPGInDoors = sConfigMgr->GetOption<bool>("AiPlayerbot.RandombotsWalkingRPG.InDoors", false);
@@ -506,9 +477,8 @@ bool PlayerbotAIConfig::Initialize()
limitEnchantExpansion = sConfigMgr->GetOption<int32>("AiPlayerbot.LimitEnchantExpansion", 1); limitEnchantExpansion = sConfigMgr->GetOption<int32>("AiPlayerbot.LimitEnchantExpansion", 1);
limitGearExpansion = sConfigMgr->GetOption<int32>("AiPlayerbot.LimitGearExpansion", 1); limitGearExpansion = sConfigMgr->GetOption<int32>("AiPlayerbot.LimitGearExpansion", 1);
randombotStartingLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.RandombotStartingLevel", 5); randombotStartingLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.RandombotStartingLevel", 5);
enablePeriodicOnlineOffline = sConfigMgr->GetOption<bool>("AiPlayerbot.EnablePeriodicOnlineOffline", false); enableRotation = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableRotation", false);
enableRandomBotTrading = sConfigMgr->GetOption<int32>("AiPlayerbot.EnableRandomBotTrading", 1); rotationPoolSize = sConfigMgr->GetOption<int32>("AiPlayerbot.RotationPoolSize", 500);
periodicOnlineOfflineRatio = sConfigMgr->GetOption<float>("AiPlayerbot.PeriodicOnlineOfflineRatio", 2.0);
gearscorecheck = sConfigMgr->GetOption<bool>("AiPlayerbot.GearScoreCheck", false); gearscorecheck = sConfigMgr->GetOption<bool>("AiPlayerbot.GearScoreCheck", false);
randomBotPreQuests = sConfigMgr->GetOption<bool>("AiPlayerbot.PreQuests", true); randomBotPreQuests = sConfigMgr->GetOption<bool>("AiPlayerbot.PreQuests", true);
@@ -521,16 +491,13 @@ bool PlayerbotAIConfig::Initialize()
twoRoundsGearInit = sConfigMgr->GetOption<bool>("AiPlayerbot.TwoRoundsGearInit", false); twoRoundsGearInit = sConfigMgr->GetOption<bool>("AiPlayerbot.TwoRoundsGearInit", false);
syncQuestWithPlayer = sConfigMgr->GetOption<bool>("AiPlayerbot.SyncQuestWithPlayer", true); syncQuestWithPlayer = sConfigMgr->GetOption<bool>("AiPlayerbot.SyncQuestWithPlayer", true);
syncQuestForPlayer = sConfigMgr->GetOption<bool>("AiPlayerbot.SyncQuestForPlayer", false); syncQuestForPlayer = sConfigMgr->GetOption<bool>("AiPlayerbot.SyncQuestForPlayer", false);
dropObsoleteQuests = sConfigMgr->GetOption<bool>("AiPlayerbot.DropObsoleteQuests", true);
autoTrainSpells = sConfigMgr->GetOption<std::string>("AiPlayerbot.AutoTrainSpells", "yes"); autoTrainSpells = sConfigMgr->GetOption<std::string>("AiPlayerbot.AutoTrainSpells", "yes");
autoPickTalents = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoPickTalents", true); autoPickTalents = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoPickTalents", true);
autoUpgradeEquip = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoUpgradeEquip", false); autoUpgradeEquip = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoUpgradeEquip", false);
hunterWolfPet = sConfigMgr->GetOption<int32>("AiPlayerbot.HunterWolfPet", 0);
autoLearnTrainerSpells = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoLearnTrainerSpells", true); autoLearnTrainerSpells = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoLearnTrainerSpells", true);
autoLearnQuestSpells = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoLearnQuestSpells", false); autoLearnQuestSpells = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoLearnQuestSpells", false);
autoTeleportForLevel = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoTeleportForLevel", false); autoTeleportForLevel = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoTeleportForLevel", false);
autoDoQuests = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoDoQuests", true); autoDoQuests = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoDoQuests", false);
enableNewRpgStrategy = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableNewRpgStrategy", false);
syncLevelWithPlayers = sConfigMgr->GetOption<bool>("AiPlayerbot.SyncLevelWithPlayers", false); syncLevelWithPlayers = sConfigMgr->GetOption<bool>("AiPlayerbot.SyncLevelWithPlayers", false);
freeFood = sConfigMgr->GetOption<bool>("AiPlayerbot.FreeFood", true); freeFood = sConfigMgr->GetOption<bool>("AiPlayerbot.FreeFood", true);
randomBotGroupNearby = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotGroupNearby", true); randomBotGroupNearby = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotGroupNearby", true);
@@ -550,11 +517,8 @@ bool PlayerbotAIConfig::Initialize()
{ {
return true; return true;
} }
if (sPlayerbotAIConfig->addClassCommand)
if (sPlayerbotAIConfig->enabled) sRandomPlayerbotMgr->PrepareAddclassCache();
{
sRandomPlayerbotMgr->Init();
}
sRandomItemMgr->Init(); sRandomItemMgr->Init();
sRandomItemMgr->InitAfterAhBot(); sRandomItemMgr->InitAfterAhBot();
@@ -568,6 +532,8 @@ bool PlayerbotAIConfig::Initialize()
sTravelMgr->LoadQuestTravelTable(); sTravelMgr->LoadQuestTravelTable();
} }
if (sPlayerbotAIConfig->randomBotJoinBG)
sRandomPlayerbotMgr->LoadBattleMastersCache();
if (sPlayerbotAIConfig->randomBotSuggestDungeons) if (sPlayerbotAIConfig->randomBotSuggestDungeons)
{ {
@@ -593,7 +559,7 @@ bool PlayerbotAIConfig::IsInRandomQuestItemList(uint32 id)
bool PlayerbotAIConfig::IsPvpProhibited(uint32 zoneId, uint32 areaId) bool PlayerbotAIConfig::IsPvpProhibited(uint32 zoneId, uint32 areaId)
{ {
return IsInPvpProhibitedZone(zoneId) || IsInPvpProhibitedArea(areaId) || IsInPvpProhibitedZone(areaId); return IsInPvpProhibitedZone(zoneId) || IsInPvpProhibitedArea(areaId);
} }
bool PlayerbotAIConfig::IsInPvpProhibitedZone(uint32 id) bool PlayerbotAIConfig::IsInPvpProhibitedZone(uint32 id)
@@ -678,50 +644,36 @@ void PlayerbotAIConfig::log(std::string const fileName, char const* str, ...)
fflush(stdout); fflush(stdout);
} }
void PlayerbotAIConfig::loadWorldBuff(uint32 factionId1, uint32 classId1, uint32 specId1, uint32 minLevel1, uint32 maxLevel1) void PlayerbotAIConfig::loadWorldBuf(uint32 factionId1, uint32 classId1, uint32 minLevel1, uint32 maxLevel1)
{ {
std::vector<uint32> buffs; std::vector<uint32> buffs;
std::ostringstream os; std::ostringstream os;
os << "AiPlayerbot.WorldBuff." << factionId1 << "." << classId1 << "." << specId1 << "." << minLevel1 << "." << maxLevel1; os << "AiPlayerbot.WorldBuff." << factionId1 << "." << classId1 << "." << minLevel1 << "." << maxLevel1;
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>(os.str().c_str(), "", false), buffs); LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>(os.str().c_str(), "", false), buffs);
for (auto buff : buffs) for (auto buff : buffs)
{ {
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1}; worldBuff wb = {buff, factionId1, classId1, minLevel1, maxLevel1};
worldBuffs.push_back(wb); worldBuffs.push_back(wb);
} }
if (maxLevel1 == 0) if (maxLevel1 == 0)
{ {
std::ostringstream os; std::ostringstream os;
os << "AiPlayerbot.WorldBuff." << factionId1 << "." << classId1 << "." << specId1 << "." << minLevel1; os << "AiPlayerbot.WorldBuff." << factionId1 << "." << classId1 << "." << minLevel1;
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>(os.str().c_str(), "", false), buffs); LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>(os.str().c_str(), "", false), buffs);
for (auto buff : buffs) for (auto buff : buffs)
{ {
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1}; worldBuff wb = {buff, factionId1, classId1, minLevel1, maxLevel1};
worldBuffs.push_back(wb); worldBuffs.push_back(wb);
} }
} }
if (maxLevel1 == 0 && minLevel1 == 0) if (maxLevel1 == 0 && minLevel1 == 0)
{
std::ostringstream os;
os << "AiPlayerbot.WorldBuff." << factionId1 << "." << factionId1 << "." << classId1 << "." << specId1;
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>(os.str().c_str(), "", false), buffs);
for (auto buff : buffs)
{
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1};
worldBuffs.push_back(wb);
}
}
if (maxLevel1 == 0 && minLevel1 == 0 && specId1 == 0)
{ {
std::ostringstream os; std::ostringstream os;
os << "AiPlayerbot.WorldBuff." << factionId1 << "." << factionId1 << "." << classId1; os << "AiPlayerbot.WorldBuff." << factionId1 << "." << factionId1 << "." << classId1;
@@ -730,12 +682,12 @@ void PlayerbotAIConfig::loadWorldBuff(uint32 factionId1, uint32 classId1, uint32
for (auto buff : buffs) for (auto buff : buffs)
{ {
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1}; worldBuff wb = {buff, factionId1, classId1, minLevel1, maxLevel1};
worldBuffs.push_back(wb); worldBuffs.push_back(wb);
} }
} }
if (classId1 == 0 && maxLevel1 == 0 && minLevel1 == 0 && specId1 == 0) if (classId1 == 0 && maxLevel1 == 0 && minLevel1 == 0)
{ {
std::ostringstream os; std::ostringstream os;
os << "AiPlayerbot.WorldBuff." << factionId1; os << "AiPlayerbot.WorldBuff." << factionId1;
@@ -744,12 +696,12 @@ void PlayerbotAIConfig::loadWorldBuff(uint32 factionId1, uint32 classId1, uint32
for (auto buff : buffs) for (auto buff : buffs)
{ {
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1}; worldBuff wb = {buff, factionId1, classId1, minLevel1, maxLevel1};
worldBuffs.push_back(wb); worldBuffs.push_back(wb);
} }
} }
if (factionId1 == 0 && classId1 == 0 && maxLevel1 == 0 && minLevel1 == 0 && specId1 == 0) if (factionId1 == 0 && classId1 == 0 && maxLevel1 == 0 && minLevel1 == 0)
{ {
std::ostringstream os; std::ostringstream os;
os << "AiPlayerbot.WorldBuff"; os << "AiPlayerbot.WorldBuff";
@@ -758,7 +710,7 @@ void PlayerbotAIConfig::loadWorldBuff(uint32 factionId1, uint32 classId1, uint32
for (auto buff : buffs) for (auto buff : buffs)
{ {
worldBuff wb = {buff, factionId1, classId1, specId1, minLevel1, maxLevel1}; worldBuff wb = {buff, factionId1, classId1, minLevel1, maxLevel1};
worldBuffs.push_back(wb); worldBuffs.push_back(wb);
} }
} }

View File

@@ -35,7 +35,6 @@ enum class HealingManaEfficiency : uint8
}; };
#define MAX_SPECNO 20 #define MAX_SPECNO 20
#define MAX_WORLDBUFF_SPECNO 3
class PlayerbotAIConfig class PlayerbotAIConfig
{ {
@@ -55,7 +54,7 @@ public:
bool IsInPvpProhibitedArea(uint32 id); bool IsInPvpProhibitedArea(uint32 id);
bool enabled; bool enabled;
bool allowAccountBots, allowGuildBots, allowTrustedAccountBots; bool allowGuildBots, allowPlayerBots;
bool randomBotGuildNearby, randomBotInvitePlayer, inviteChat; bool randomBotGuildNearby, randomBotInvitePlayer, inviteChat;
uint32 globalCoolDown, reactDelay, maxWaitForMove, disableMoveSplinePath, maxMovementSearchTime, expireActionTime, uint32 globalCoolDown, reactDelay, maxWaitForMove, disableMoveSplinePath, maxMovementSearchTime, expireActionTime,
dispelAuraDuration, passiveDelay, repeatDelay, errorDelay, rpgDelay, sitDelay, returnDelay, lootDelay; dispelAuraDuration, passiveDelay, repeatDelay, errorDelay, rpgDelay, sitDelay, returnDelay, lootDelay;
@@ -86,7 +85,7 @@ public:
float randomGearLoweringChance; float randomGearLoweringChance;
int32 randomGearQualityLimit; int32 randomGearQualityLimit;
int32 randomGearScoreLimit; int32 randomGearScoreLimit;
float randomBotMinLevelChance, randomBotMaxLevelChance; float randomBotMaxLevelChance;
float randomBotRpgChance; float randomBotRpgChance;
uint32 minRandomBots, maxRandomBots; uint32 minRandomBots, maxRandomBots;
uint32 randomBotUpdateInterval, randomBotCountChangeMinInterval, randomBotCountChangeMaxInterval; uint32 randomBotUpdateInterval, randomBotCountChangeMinInterval, randomBotCountChangeMaxInterval;
@@ -95,7 +94,7 @@ public:
uint32 minRandomBotChangeStrategyTime, maxRandomBotChangeStrategyTime; uint32 minRandomBotChangeStrategyTime, maxRandomBotChangeStrategyTime;
uint32 minRandomBotReviveTime, maxRandomBotReviveTime; uint32 minRandomBotReviveTime, maxRandomBotReviveTime;
uint32 minRandomBotTeleportInterval, maxRandomBotTeleportInterval; uint32 minRandomBotTeleportInterval, maxRandomBotTeleportInterval;
uint32 permanantlyInWorldTime; uint32 randomBotInWorldWithRotationDisabled;
uint32 minRandomBotPvpTime, maxRandomBotPvpTime; uint32 minRandomBotPvpTime, maxRandomBotPvpTime;
uint32 randomBotsPerInterval; uint32 randomBotsPerInterval;
uint32 minRandomBotsPriceChangeInterval, maxRandomBotsPriceChangeInterval; uint32 minRandomBotsPriceChangeInterval, maxRandomBotsPriceChangeInterval;
@@ -174,25 +173,12 @@ public:
bool randomBotJoinBG; bool randomBotJoinBG;
bool randomBotAutoJoinBG; bool randomBotAutoJoinBG;
uint32 randomBotAutoJoinWarsongBracket;
std::string randomBotAutoJoinICBrackets;
std::string randomBotAutoJoinEYBrackets;
std::string randomBotAutoJoinAVBrackets;
std::string randomBotAutoJoinABBrackets;
std::string randomBotAutoJoinWSBrackets;
uint32 randomBotAutoJoinBGICCount;
uint32 randomBotAutoJoinBGEYCount;
uint32 randomBotAutoJoinBGAVCount;
uint32 randomBotAutoJoinBGABCount;
uint32 randomBotAutoJoinBGWSCount;
uint32 randomBotAutoJoinArenaBracket; uint32 randomBotAutoJoinArenaBracket;
uint32 randomBotAutoJoinBGWarsongCount;
uint32 randomBotAutoJoinBGRatedArena2v2Count; uint32 randomBotAutoJoinBGRatedArena2v2Count;
uint32 randomBotAutoJoinBGRatedArena3v3Count; uint32 randomBotAutoJoinBGRatedArena3v3Count;
uint32 randomBotAutoJoinBGRatedArena5v5Count; uint32 randomBotAutoJoinBGRatedArena5v5Count;
bool randomBotLoginAtStartup; bool randomBotLoginAtStartup;
uint32 randomBotTeleLowerLevel, randomBotTeleHigherLevel; uint32 randomBotTeleLowerLevel, randomBotTeleHigherLevel;
bool logInGroupOnly, logValuesPerTick; bool logInGroupOnly, logValuesPerTick;
@@ -235,8 +221,8 @@ public:
uint32 limitEnchantExpansion; uint32 limitEnchantExpansion;
uint32 limitGearExpansion; uint32 limitGearExpansion;
uint32 randombotStartingLevel; uint32 randombotStartingLevel;
bool enablePeriodicOnlineOffline; bool enableRotation;
float periodicOnlineOfflineRatio; uint32 rotationPoolSize;
bool gearscorecheck; bool gearscorecheck;
bool randomBotPreQuests; bool randomBotPreQuests;
@@ -261,7 +247,6 @@ public:
uint32 spellId; uint32 spellId;
uint32 factionId = 0; uint32 factionId = 0;
uint32 classId = 0; uint32 classId = 0;
uint32 specId = 0;
uint32 minLevel = 0; uint32 minLevel = 0;
uint32 maxLevel = 0; uint32 maxLevel = 0;
}; };
@@ -275,20 +260,10 @@ public:
bool randomBotShowCloak; bool randomBotShowCloak;
bool randomBotFixedLevel; bool randomBotFixedLevel;
bool disableRandomLevels; bool disableRandomLevels;
float playerbotsXPrate; uint32 playerbotsXPrate;
uint32 randomBotAllianceRatio;
uint32 randomBotHordeRatio;
bool disableDeathKnightLogin; bool disableDeathKnightLogin;
bool limitTalentsExpansion;
uint32 botActiveAlone; uint32 botActiveAlone;
uint32 BotActiveAloneForceWhenInRadius;
bool BotActiveAloneForceWhenInZone;
bool BotActiveAloneForceWhenInMap;
bool BotActiveAloneForceWhenIsFriend;
bool BotActiveAloneForceWhenInGuild;
bool botActiveAloneSmartScale; bool botActiveAloneSmartScale;
uint32 botActiveAloneSmartScaleDiffLimitfloor;
uint32 botActiveAloneSmartScaleDiffLimitCeiling;
uint32 botActiveAloneSmartScaleWhenMinLevel; uint32 botActiveAloneSmartScaleWhenMinLevel;
uint32 botActiveAloneSmartScaleWhenMaxLevel; uint32 botActiveAloneSmartScaleWhenMaxLevel;
@@ -300,20 +275,16 @@ public:
bool twoRoundsGearInit; bool twoRoundsGearInit;
bool syncQuestWithPlayer; bool syncQuestWithPlayer;
bool syncQuestForPlayer; bool syncQuestForPlayer;
bool dropObsoleteQuests;
std::string autoTrainSpells; std::string autoTrainSpells;
bool autoPickTalents; bool autoPickTalents;
bool autoUpgradeEquip; bool autoUpgradeEquip;
int32 hunterWolfPet;
bool autoLearnTrainerSpells; bool autoLearnTrainerSpells;
bool autoDoQuests; bool autoDoQuests;
bool enableNewRpgStrategy;
bool syncLevelWithPlayers; bool syncLevelWithPlayers;
bool freeFood; bool freeFood;
bool autoLearnQuestSpells; bool autoLearnQuestSpells;
bool autoTeleportForLevel; bool autoTeleportForLevel;
bool randomBotGroupNearby; bool randomBotGroupNearby;
int32 enableRandomBotTrading;
uint32 tweakValue; // Debugging config uint32 tweakValue; // Debugging config
uint32 randomBotArenaTeamCount; uint32 randomBotArenaTeamCount;
@@ -337,16 +308,11 @@ public:
bool botRepairWhenSummon; bool botRepairWhenSummon;
bool autoInitOnly; bool autoInitOnly;
float autoInitEquipLevelLimitRatio; float autoInitEquipLevelLimitRatio;
int32 maxAddedBots; int32 maxAddedBots, maxAddedBotsPerClass;
int32 addClassCommand; int32 addClassCommand;
int32 addClassAccountPoolSize; int32 addClassAccountPoolSize;
int32 maintenanceCommand; int32 maintenanceCommand;
int32 autoGearCommand, autoGearCommandAltBots, autoGearQualityLimit, autoGearScoreLimit; int32 autoGearCommand, autoGearQualityLimit, autoGearScoreLimit;
uint32 useGroundMountAtMinLevel;
uint32 useFastGroundMountAtMinLevel;
uint32 useFlyMountAtMinLevel;
uint32 useFastFlyMountAtMinLevel;
std::string const GetTimestampStr(); std::string const GetTimestampStr();
bool hasLog(std::string const fileName) bool hasLog(std::string const fileName)
@@ -361,7 +327,7 @@ public:
} }
void log(std::string const fileName, const char* str, ...); void log(std::string const fileName, const char* str, ...);
void loadWorldBuff(uint32 factionId, uint32 classId, uint32 specId, uint32 minLevel, uint32 maxLevel); void loadWorldBuf(uint32 factionId, uint32 classId, uint32 minLevel, uint32 maxLevel);
static std::vector<std::vector<uint32>> ParseTempTalentsOrder(uint32 cls, std::string temp_talents_order); static std::vector<std::vector<uint32>> ParseTempTalentsOrder(uint32 cls, std::string temp_talents_order);
static std::vector<std::vector<uint32>> ParseTempPetTalentsOrder(uint32 spec, std::string temp_talents_order); static std::vector<std::vector<uint32>> ParseTempPetTalentsOrder(uint32 spec, std::string temp_talents_order);
}; };

View File

@@ -17,6 +17,11 @@ void PlayerbotDbStore::Load(PlayerbotAI* botAI)
stmt->SetData(0, guid); stmt->SetData(0, guid);
if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt)) if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt))
{ {
botAI->ClearStrategies(BOT_STATE_COMBAT);
botAI->ClearStrategies(BOT_STATE_NON_COMBAT);
botAI->ChangeStrategy("+chat", BOT_STATE_COMBAT);
botAI->ChangeStrategy("+chat", BOT_STATE_NON_COMBAT);
std::vector<std::string> values; std::vector<std::string> values;
do do
{ {
@@ -27,17 +32,9 @@ void PlayerbotDbStore::Load(PlayerbotAI* botAI)
if (key == "value") if (key == "value")
values.push_back(value); values.push_back(value);
else if (key == "co") else if (key == "co")
{
botAI->ClearStrategies(BOT_STATE_COMBAT);
botAI->ChangeStrategy("+chat", BOT_STATE_COMBAT);
botAI->ChangeStrategy(value, BOT_STATE_COMBAT); botAI->ChangeStrategy(value, BOT_STATE_COMBAT);
}
else if (key == "nc") else if (key == "nc")
{
botAI->ClearStrategies(BOT_STATE_NON_COMBAT);
botAI->ChangeStrategy("+chat", BOT_STATE_NON_COMBAT);
botAI->ChangeStrategy(value, BOT_STATE_NON_COMBAT); botAI->ChangeStrategy(value, BOT_STATE_NON_COMBAT);
}
else if (key == "dead") else if (key == "dead")
botAI->ChangeStrategy(value, BOT_STATE_DEAD); botAI->ChangeStrategy(value, BOT_STATE_DEAD);
} while (result->NextRow()); } while (result->NextRow());
@@ -52,11 +49,6 @@ void PlayerbotDbStore::Save(PlayerbotAI* botAI)
Reset(botAI); Reset(botAI);
PlayerbotsDatabasePreparedStatement* deleteStatement =
PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_DEL_DB_STORE);
deleteStatement->SetData(0, guid);
PlayerbotsDatabase.Execute(deleteStatement);
std::vector<std::string> data = botAI->GetAiObjectContext()->Save(); std::vector<std::string> data = botAI->GetAiObjectContext()->Save();
for (std::vector<std::string>::iterator i = data.begin(); i != data.end(); ++i) for (std::vector<std::string>::iterator i = data.begin(); i != data.end(); ++i)
{ {

View File

@@ -9,9 +9,6 @@
#include <cstring> #include <cstring>
#include <istream> #include <istream>
#include <string> #include <string>
#include <openssl/sha.h>
#include <unordered_set>
#include <iomanip>
#include "ChannelMgr.h" #include "ChannelMgr.h"
#include "CharacterCache.h" #include "CharacterCache.h"
@@ -20,7 +17,6 @@
#include "Define.h" #include "Define.h"
#include "Group.h" #include "Group.h"
#include "GroupMgr.h" #include "GroupMgr.h"
#include "GuildMgr.h"
#include "ObjectAccessor.h" #include "ObjectAccessor.h"
#include "ObjectGuid.h" #include "ObjectGuid.h"
#include "ObjectMgr.h" #include "ObjectMgr.h"
@@ -34,36 +30,6 @@
#include "WorldSession.h" #include "WorldSession.h"
#include "ChannelMgr.h" #include "ChannelMgr.h"
#include "BroadcastHelper.h" #include "BroadcastHelper.h"
#include "PlayerbotDbStore.h"
#include "WorldSessionMgr.h"
class BotInitGuard
{
public:
BotInitGuard(ObjectGuid guid) : guid(guid), active(false)
{
if (!botsBeingInitialized.contains(guid))
{
botsBeingInitialized.insert(guid);
active = true;
}
}
~BotInitGuard()
{
if (active)
botsBeingInitialized.erase(guid);
}
bool IsLocked() const { return !active; }
private:
ObjectGuid guid;
bool active;
static std::unordered_set<ObjectGuid> botsBeingInitialized;
};
std::unordered_set<ObjectGuid> BotInitGuard::botsBeingInitialized;
PlayerbotHolder::PlayerbotHolder() : PlayerbotAIBase(false) {} PlayerbotHolder::PlayerbotHolder() : PlayerbotAIBase(false) {}
class PlayerbotLoginQueryHolder : public LoginQueryHolder class PlayerbotLoginQueryHolder : public LoginQueryHolder
@@ -71,6 +37,7 @@ class PlayerbotLoginQueryHolder : public LoginQueryHolder
private: private:
uint32 masterAccountId; uint32 masterAccountId;
PlayerbotHolder* playerbotHolder; PlayerbotHolder* playerbotHolder;
public: public:
PlayerbotLoginQueryHolder(PlayerbotHolder* playerbotHolder, uint32 masterAccount, uint32 accountId, ObjectGuid guid) PlayerbotLoginQueryHolder(PlayerbotHolder* playerbotHolder, uint32 masterAccount, uint32 accountId, ObjectGuid guid)
: LoginQueryHolder(accountId, guid), masterAccountId(masterAccount), playerbotHolder(playerbotHolder) : LoginQueryHolder(accountId, guid), masterAccountId(masterAccount), playerbotHolder(playerbotHolder)
@@ -96,49 +63,6 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
if (!accountId) if (!accountId)
return; return;
WorldSession* masterSession = masterAccountId ? sWorldSessionMgr->FindSession(masterAccountId) : nullptr;
Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr;
bool isRndbot = !masterAccountId;
bool sameAccount = sPlayerbotAIConfig->allowAccountBots && accountId == masterAccountId;
Guild* guild = masterPlayer ? sGuildMgr->GetGuildById(masterPlayer->GetGuildId()) : nullptr;
bool sameGuild = sPlayerbotAIConfig->allowGuildBots && guild && guild->GetMember(playerGuid);
bool addClassBot = sRandomPlayerbotMgr->IsAddclassBot(playerGuid.GetCounter());
bool linkedAccount = sPlayerbotAIConfig->allowTrustedAccountBots && IsAccountLinked(accountId, masterAccountId);
bool allowed = true;
std::ostringstream out;
std::string botName;
sCharacterCache->GetCharacterNameByGuid(playerGuid, botName);
if (!isRndbot && !sameAccount && !sameGuild && !addClassBot && !linkedAccount)
{
allowed = false;
out << "Failure: You are not allowed to control bot " << botName.c_str();
}
if (masterAccountId && masterPlayer)
{
PlayerbotMgr* mgr = GET_PLAYERBOT_MGR(masterPlayer);
if (!mgr)
{
LOG_DEBUG("playerbots", "PlayerbotMgr not found for master player with GUID: {}", masterPlayer->GetGUID().GetRawValue());
return;
}
uint32 count = mgr->GetPlayerbotsCount() + botLoading.size();
if (count >= sPlayerbotAIConfig->maxAddedBots)
{
allowed = false;
out << "Failure: You have added too many bots (more than " << sPlayerbotAIConfig->maxAddedBots << ")";
}
}
if (!allowed)
{
if (masterSession)
{
ChatHandler ch(masterSession);
ch.SendSysMessage(out.str());
}
return;
}
std::shared_ptr<PlayerbotLoginQueryHolder> holder = std::shared_ptr<PlayerbotLoginQueryHolder> holder =
std::make_shared<PlayerbotLoginQueryHolder>(this, masterAccountId, accountId, playerGuid); std::make_shared<PlayerbotLoginQueryHolder>(this, masterAccountId, accountId, playerGuid);
if (!holder->Initialize()) if (!holder->Initialize())
@@ -147,23 +71,25 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
} }
botLoading.insert(playerGuid); botLoading.insert(playerGuid);
// Always login in with world session to avoid race condition if (WorldSession* masterSession = sWorld->FindSession(masterAccountId))
sWorld->AddQueryHolderCallback(CharacterDatabase.DelayQueryHolder(holder)) {
.AfterComplete([this](SQLQueryHolderBase const& holder) masterSession->AddQueryHolderCallback(CharacterDatabase.DelayQueryHolder(holder))
{ HandlePlayerBotLoginCallback(static_cast<PlayerbotLoginQueryHolder const&>(holder)); }); .AfterComplete([this](SQLQueryHolderBase const& holder)
} { HandlePlayerBotLoginCallback(static_cast<PlayerbotLoginQueryHolder const&>(holder)); });
}
bool PlayerbotHolder::IsAccountLinked(uint32 accountId, uint32 linkedAccountId) else
{ {
QueryResult result = PlayerbotsDatabase.Query( sWorld->AddQueryHolderCallback(CharacterDatabase.DelayQueryHolder(holder))
"SELECT 1 FROM playerbot_account_links WHERE account_id = {} AND linked_account_id = {}", accountId, linkedAccountId); .AfterComplete([this](SQLQueryHolderBase const& holder)
return result != nullptr; { HandlePlayerBotLoginCallback(static_cast<PlayerbotLoginQueryHolder const&>(holder)); });
}
} }
void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder const& holder) void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder const& holder)
{ {
uint32 botAccountId = holder.GetAccountId(); uint32 botAccountId = holder.GetAccountId();
// At login DBC locale should be what the server is set to use by default (as spells etc are hardcoded to ENUS this // At login DBC locale should be what the server is set to use by default (as spells etc are hardcoded to ENUS this
// allows channels to work as intended) // allows channels to work as intended)
WorldSession* botSession = new WorldSession(botAccountId, "", nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING, WorldSession* botSession = new WorldSession(botAccountId, "", nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING,
@@ -183,7 +109,7 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
} }
uint32 masterAccount = holder.GetMasterAccountId(); uint32 masterAccount = holder.GetMasterAccountId();
WorldSession* masterSession = masterAccount ? sWorldSessionMgr->FindSession(masterAccount) : nullptr; WorldSession* masterSession = masterAccount ? sWorld->FindSession(masterAccount) : nullptr;
// Check if masterSession->GetPlayer() is valid // Check if masterSession->GetPlayer() is valid
Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr; Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr;
@@ -192,9 +118,64 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
LOG_DEBUG("mod-playerbots", "Master session found but no player is associated for master account ID: {}", masterAccount); LOG_DEBUG("mod-playerbots", "Master session found but no player is associated for master account ID: {}", masterAccount);
} }
sRandomPlayerbotMgr->OnPlayerLogin(bot); std::ostringstream out;
OnBotLogin(bot); bool allowed = false;
if (botAccountId == masterAccount)
{
allowed = true;
}
else if (masterSession && sPlayerbotAIConfig->allowGuildBots && bot->GetGuildId() != 0 &&
bot->GetGuildId() == masterPlayer->GetGuildId())
{
allowed = true;
}
else if (sPlayerbotAIConfig->IsInRandomAccountList(botAccountId))
{
allowed = true;
}
else
{
allowed = false;
out << "Failure: You are not allowed to control bot " << bot->GetName().c_str();
}
if (allowed && masterSession && masterPlayer)
{
PlayerbotMgr* mgr = GET_PLAYERBOT_MGR(masterPlayer);
if (!mgr)
{
LOG_DEBUG("mod-playerbots", "PlayerbotMgr not found for master player with GUID: {}", masterPlayer->GetGUID().GetRawValue());
}
uint32 count = mgr->GetPlayerbotsCount();
uint32 cls_count = mgr->GetPlayerbotsCountByClass(bot->getClass());
if (count >= sPlayerbotAIConfig->maxAddedBots)
{
allowed = false;
out << "Failure: You have added too many bots";
}
else if (cls_count >= sPlayerbotAIConfig->maxAddedBotsPerClass)
{
allowed = false;
out << "Failure: You have added too many bots for this class";
}
}
if (allowed)
{
sRandomPlayerbotMgr->OnPlayerLogin(bot);
OnBotLogin(bot);
}
else
{
if (masterSession)
{
ChatHandler ch(masterSession);
ch.SendSysMessage(out.str());
}
botSession->LogoutPlayer(true);
delete botSession;
}
botLoading.erase(holder.GetGuid()); botLoading.erase(holder.GetGuid());
} }
@@ -314,7 +295,7 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid)
sPlayerbotDbStore->Save(botAI); sPlayerbotDbStore->Save(botAI);
} }
LOG_DEBUG("playerbots", "Bot {} logging out", bot->GetName().c_str()); LOG_INFO("playerbots", "Bot {} logging out", bot->GetName().c_str());
bot->SaveToDB(false, false); bot->SaveToDB(false, false);
WorldSession* botWorldSessionPtr = bot->GetSession(); WorldSession* botWorldSessionPtr = bot->GetSession();
@@ -371,7 +352,7 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid)
botWorldSessionPtr->HandleLogoutRequestOpcode(data); botWorldSessionPtr->HandleLogoutRequestOpcode(data);
if (!bot) if (!bot)
{ {
RemoveFromPlayerbotsMap(guid); playerBots.erase(guid);
delete botWorldSessionPtr; delete botWorldSessionPtr;
if (target) if (target)
delete target; delete target;
@@ -380,7 +361,7 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid)
} }
else else
{ {
RemoveFromPlayerbotsMap(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap playerBots.erase(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap
delete botWorldSessionPtr; // finally delete the bot's WorldSession delete botWorldSessionPtr; // finally delete the bot's WorldSession
if (target) if (target)
delete target; delete target;
@@ -390,7 +371,7 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid)
else if (bot && (logout || !botWorldSessionPtr->isLogingOut())) else if (bot && (logout || !botWorldSessionPtr->isLogingOut()))
{ {
botAI->TellMaster("Goodbye!"); botAI->TellMaster("Goodbye!");
RemoveFromPlayerbotsMap(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap playerBots.erase(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap
botWorldSessionPtr->LogoutPlayer(true); // this will delete the bot Player object and PlayerbotAI object botWorldSessionPtr->LogoutPlayer(true); // this will delete the bot Player object and PlayerbotAI object
delete botWorldSessionPtr; // finally delete the bot's WorldSession delete botWorldSessionPtr; // finally delete the bot's WorldSession
} }
@@ -427,17 +408,12 @@ void PlayerbotHolder::DisablePlayerBot(ObjectGuid guid)
delete target; delete target;
} }
RemoveFromPlayerbotsMap(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap playerBots.erase(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap
delete botAI; delete botAI;
} }
} }
void PlayerbotHolder::RemoveFromPlayerbotsMap(ObjectGuid guid)
{
playerBots.erase(guid);
}
Player* PlayerbotHolder::GetPlayerBot(ObjectGuid playerGuid) const Player* PlayerbotHolder::GetPlayerBot(ObjectGuid playerGuid) const
{ {
PlayerBotMap::const_iterator it = playerBots.find(playerGuid); PlayerBotMap::const_iterator it = playerBots.find(playerGuid);
@@ -461,7 +437,6 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
sPlayerbotsMgr->AddPlayerbotData(bot, true); sPlayerbotsMgr->AddPlayerbotData(bot, true);
playerBots[bot->GetGUID()] = bot; playerBots[bot->GetGUID()] = bot;
OnBotLoginInternal(bot); OnBotLoginInternal(bot);
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
@@ -509,7 +484,10 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
if (!groupValid) if (!groupValid)
{ {
bot->RemoveFromGroup(); WorldPacket p;
std::string const member = bot->GetName();
p << uint32(PARTY_OP_LEAVE) << member << uint32(0);
bot->GetSession()->HandleGroupDisbandOpcode(p);
} }
} }
@@ -520,9 +498,9 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
} }
else else
{ {
botAI->ResetStrategies(!sRandomPlayerbotMgr->IsRandomBot(bot)); // botAI->ResetStrategies(!sRandomPlayerbotMgr->IsRandomBot(bot));
botAI->ResetStrategies();
} }
sPlayerbotDbStore->Load(botAI);
if (master && !master->HasUnitState(UNIT_STATE_IN_FLIGHT)) if (master && !master->HasUnitState(UNIT_STATE_IN_FLIGHT))
{ {
@@ -581,16 +559,12 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
} }
bot->SaveToDB(false, false); bot->SaveToDB(false, false);
bool addClassBot = sRandomPlayerbotMgr->IsAddclassBot(bot->GetGUID().GetCounter()); if (master && isRandomAccount && master->GetLevel() < bot->GetLevel())
if (addClassBot && master && isRandomAccount && abs((int)master->GetLevel() - (int)bot->GetLevel()) > 3)
{ {
// PlayerbotFactory factory(bot, master->GetLevel()); // PlayerbotFactory factory(bot, master->GetLevel());
// factory.Randomize(false); // factory.Randomize(false);
uint32 mixedGearScore = uint32 mixedGearScore =
PlayerbotAI::GetMixedGearScore(master, true, false, 12) * sPlayerbotAIConfig->autoInitEquipLevelLimitRatio; PlayerbotAI::GetMixedGearScore(master, true, false, 12) * sPlayerbotAIConfig->autoInitEquipLevelLimitRatio;
// work around: distinguish from 0 if no gear
if (mixedGearScore == 0)
mixedGearScore = 1;
PlayerbotFactory factory(bot, master->GetLevel(), ITEM_QUALITY_LEGENDARY, mixedGearScore); PlayerbotFactory factory(bot, master->GetLevel(), ITEM_QUALITY_LEGENDARY, mixedGearScore);
factory.Randomize(false); factory.Randomize(false);
} }
@@ -668,28 +642,25 @@ std::string const PlayerbotHolder::ProcessBotCommand(std::string const cmd, Obje
return "bot system is disabled"; return "bot system is disabled";
uint32 botAccount = sCharacterCache->GetCharacterAccountIdByGuid(guid); uint32 botAccount = sCharacterCache->GetCharacterAccountIdByGuid(guid);
//bool isRandomBot = sRandomPlayerbotMgr->IsRandomBot(guid.GetCounter()); //not used, line marked for removal. bool isRandomBot = sRandomPlayerbotMgr->IsRandomBot(guid.GetCounter());
//bool isRandomAccount = sPlayerbotAIConfig->IsInRandomAccountList(botAccount); //not used, shadowed, line marked for removal. bool isRandomAccount = sPlayerbotAIConfig->IsInRandomAccountList(botAccount);
//bool isMasterAccount = (masterAccountId == botAccount); //not used, line marked for removal. bool isMasterAccount = (masterAccountId == botAccount);
if (cmd == "add" || cmd == "addaccount" || cmd == "login") if (!isRandomAccount && !isMasterAccount && !admin && masterguid)
{
Player* master = ObjectAccessor::FindConnectedPlayer(masterguid);
if (master && (!sPlayerbotAIConfig->allowGuildBots || !masterGuildId ||
(masterGuildId && sCharacterCache->GetCharacterGuildIdByGuid(guid) != masterGuildId)))
return "not in your guild or account";
}
if (cmd == "add" || cmd == "login")
{ {
if (ObjectAccessor::FindPlayer(guid)) if (ObjectAccessor::FindPlayer(guid))
return "player already logged in"; return "player already logged in";
// For addaccount command, verify it's an account name if (!sPlayerbotAIConfig->allowPlayerBots && !isRandomAccount && !isMasterAccount)
if (cmd == "addaccount") return "You cannot login another player's character as bot.";
{
uint32 accountId = sCharacterCache->GetCharacterAccountIdByGuid(guid);
if (!accountId)
return "character not found";
if (!sPlayerbotAIConfig->allowAccountBots && accountId != masterAccountId &&
!(sPlayerbotAIConfig->allowTrustedAccountBots && IsAccountLinked(accountId, masterAccountId)))
{
return "you can only add bots from your own account or linked accounts";
}
}
AddPlayerBot(guid, masterAccountId); AddPlayerBot(guid, masterAccountId);
return "ok"; return "ok";
@@ -715,10 +686,10 @@ std::string const PlayerbotHolder::ProcessBotCommand(std::string const cmd, Obje
if (!bot) if (!bot)
return "bot not found"; return "bot not found";
bool addClassBot = sRandomPlayerbotMgr->IsAddclassBot(guid.GetCounter()); if (!isRandomAccount || isRandomBot)
{
if (!addClassBot) return "ERROR: You can not use this command on non-summoned random bot.";
return "ERROR: You can not use this command on non-addclass bot."; }
if (!admin) if (!admin)
{ {
@@ -738,14 +709,6 @@ std::string const PlayerbotHolder::ProcessBotCommand(std::string const cmd, Obje
{ {
return "The command is not allowed, use init=auto instead."; return "The command is not allowed, use init=auto instead.";
} }
// Use boot guard
BotInitGuard guard(bot->GetGUID());
if (guard.IsLocked())
{
return "Initialization already in progress, please wait.";
}
int gs; int gs;
if (cmd == "init=white" || cmd == "init=common") if (cmd == "init=white" || cmd == "init=common")
{ {
@@ -781,9 +744,6 @@ std::string const PlayerbotHolder::ProcessBotCommand(std::string const cmd, Obje
{ {
uint32 mixedGearScore = PlayerbotAI::GetMixedGearScore(master, true, false, 12) * uint32 mixedGearScore = PlayerbotAI::GetMixedGearScore(master, true, false, 12) *
sPlayerbotAIConfig->autoInitEquipLevelLimitRatio; sPlayerbotAIConfig->autoInitEquipLevelLimitRatio;
// work around: distinguish from 0 if no gear
if (mixedGearScore == 0)
mixedGearScore = 1;
PlayerbotFactory factory(bot, master->GetLevel(), ITEM_QUALITY_LEGENDARY, mixedGearScore); PlayerbotFactory factory(bot, master->GetLevel(), ITEM_QUALITY_LEGENDARY, mixedGearScore);
factory.Randomize(false); factory.Randomize(false);
return "ok, gear score limit: " + std::to_string(mixedGearScore / PlayerbotAI::GetItemScoreMultiplier(ItemQualities(ITEM_QUALITY_EPIC))) + return "ok, gear score limit: " + std::to_string(mixedGearScore / PlayerbotAI::GetItemScoreMultiplier(ItemQualities(ITEM_QUALITY_EPIC))) +
@@ -875,7 +835,7 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
if (!*args) if (!*args)
{ {
messages.push_back("usage: list/reload/tweak/self or add/addaccount/init/remove PLAYERNAME\n"); messages.push_back("usage: list/reload/tweak/self or add/init/remove PLAYERNAME\n");
messages.push_back("usage: addclass CLASSNAME"); messages.push_back("usage: addclass CLASSNAME");
return messages; return messages;
} }
@@ -998,17 +958,9 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
if (!strcmp(cmd, "reload")) if (!strcmp(cmd, "reload"))
{ {
if (master->GetSession()->GetSecurity() >= SEC_GAMEMASTER) messages.push_back("Reloading config");
{ sPlayerbotAIConfig->Initialize();
sPlayerbotAIConfig->Initialize(); return messages;
messages.push_back("Config reloaded.");
return messages;
}
else
{
messages.push_back("ERROR: Only GM can use this command.");
return messages;
}
} }
if (!strcmp(cmd, "tweak")) if (!strcmp(cmd, "tweak"))
@@ -1107,22 +1059,15 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
messages.push_back("Error: Invalid Class. Try again."); messages.push_back("Error: Invalid Class. Try again.");
return messages; return messages;
} }
if (claz == 6 && master->GetLevel() < sWorld->getIntConfig(CONFIG_START_HEROIC_PLAYER_LEVEL))
{
messages.push_back("Your level is too low to summon Deathknight");
return messages;
}
uint8 teamId = master->GetTeamId(true); uint8 teamId = master->GetTeamId(true);
const std::unordered_set<ObjectGuid> &guidCache = sRandomPlayerbotMgr->addclassCache[RandomPlayerbotMgr::GetTeamClassIdx(teamId == TEAM_ALLIANCE, claz)]; std::vector<ObjectGuid> &guidCache = sRandomPlayerbotMgr->addclassCache[RandomPlayerbotMgr::GetTeamClassIdx(teamId == TEAM_ALLIANCE, claz)];
for (const ObjectGuid &guid: guidCache) for (size_t i = 0; i < guidCache.size(); i++)
{ {
ObjectGuid guid = guidCache[i];
if (botLoading.find(guid) != botLoading.end()) if (botLoading.find(guid) != botLoading.end())
continue; continue;
if (ObjectAccessor::FindConnectedPlayer(guid)) if (ObjectAccessor::FindConnectedPlayer(guid))
continue; continue;
uint32 guildId = sCharacterCache->GetCharacterGuildIdByGuid(guid);
if (guildId && PlayerbotAI::IsRealGuild(guildId))
continue;
AddPlayerBot(guid, master->GetSession()->GetAccountId()); AddPlayerBot(guid, master->GetSession()->GetAccountId());
messages.push_back("Add class " + std::string(charname)); messages.push_back("Add class " + std::string(charname));
return messages; return messages;
@@ -1194,48 +1139,22 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
{ {
std::string const s = *i; std::string const s = *i;
if (!strcmp(cmd, "addaccount")) uint32 accountId = GetAccountId(s);
if (!accountId)
{ {
// When using addaccount, first try to get account ID directly
uint32 accountId = GetAccountId(s);
if (!accountId)
{
// If not found, try to get account ID from character name
ObjectGuid charGuid = sCharacterCache->GetCharacterGuidByName(s);
if (!charGuid)
{
messages.push_back("Neither account nor character '" + s + "' found");
continue;
}
accountId = sCharacterCache->GetCharacterAccountIdByGuid(charGuid);
if (!accountId)
{
messages.push_back("Could not find account for character '" + s + "'");
continue;
}
}
QueryResult results = CharacterDatabase.Query("SELECT name FROM characters WHERE account = {}", accountId);
if (results)
{
do
{
Field* fields = results->Fetch();
std::string const charName = fields[0].Get<std::string>();
bots.insert(charName);
} while (results->NextRow());
}
}
else
{
// For regular add command, only add the specific character
ObjectGuid charGuid = sCharacterCache->GetCharacterGuidByName(s);
if (!charGuid)
{
messages.push_back("Character '" + s + "' not found");
continue;
}
bots.insert(s); bots.insert(s);
continue;
}
QueryResult results = CharacterDatabase.Query("SELECT name FROM characters WHERE account = {}", accountId);
if (results)
{
do
{
Field* fields = results->Fetch();
std::string const charName = fields[0].Get<std::string>();
bots.insert(charName);
} while (results->NextRow());
} }
} }
@@ -1718,121 +1637,3 @@ PlayerbotMgr* PlayerbotsMgr::GetPlayerbotMgr(Player* player)
return nullptr; return nullptr;
} }
void PlayerbotMgr::HandleSetSecurityKeyCommand(Player* player, const std::string& key)
{
uint32 accountId = player->GetSession()->GetAccountId();
// Hash the security key using SHA-256
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256((unsigned char*)key.c_str(), key.size(), hash);
// Convert the hash to a hexadecimal string
std::ostringstream hashedKey;
for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i)
hashedKey << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i];
// Store the hashed key in the database
PlayerbotsDatabase.Execute(
"REPLACE INTO playerbot_account_keys (account_id, security_key) VALUES ({}, '{}')",
accountId, hashedKey.str());
ChatHandler(player->GetSession()).PSendSysMessage("Security key set successfully.");
}
void PlayerbotMgr::HandleLinkAccountCommand(Player* player, const std::string& accountName, const std::string& key)
{
QueryResult result = LoginDatabase.Query("SELECT id FROM account WHERE username = '{}'", accountName);
if (!result)
{
ChatHandler(player->GetSession()).PSendSysMessage("Account not found.");
return;
}
Field* fields = result->Fetch();
uint32 linkedAccountId = fields[0].Get<uint32>();
result = PlayerbotsDatabase.Query("SELECT security_key FROM playerbot_account_keys WHERE account_id = {}", linkedAccountId);
if (!result)
{
ChatHandler(player->GetSession()).PSendSysMessage("Invalid security key.");
return;
}
// Hash the provided key
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256((unsigned char*)key.c_str(), key.size(), hash);
// Convert the hash to a hexadecimal string
std::ostringstream hashedKey;
for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i)
hashedKey << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i];
// Compare the hashed key with the stored hashed key
std::string storedKey = result->Fetch()->Get<std::string>();
if (hashedKey.str() != storedKey)
{
ChatHandler(player->GetSession()).PSendSysMessage("Invalid security key.");
return;
}
uint32 accountId = player->GetSession()->GetAccountId();
PlayerbotsDatabase.Execute(
"INSERT IGNORE INTO playerbot_account_links (account_id, linked_account_id) VALUES ({}, {})",
accountId, linkedAccountId);
PlayerbotsDatabase.Execute(
"INSERT IGNORE INTO playerbot_account_links (account_id, linked_account_id) VALUES ({}, {})",
linkedAccountId, accountId);
ChatHandler(player->GetSession()).PSendSysMessage("Account linked successfully.");
}
void PlayerbotMgr::HandleViewLinkedAccountsCommand(Player* player)
{
uint32 accountId = player->GetSession()->GetAccountId();
QueryResult result = PlayerbotsDatabase.Query("SELECT linked_account_id FROM playerbot_account_links WHERE account_id = {}", accountId);
if (!result)
{
ChatHandler(player->GetSession()).PSendSysMessage("No linked accounts.");
return;
}
ChatHandler(player->GetSession()).PSendSysMessage("Linked accounts:");
do
{
Field* fields = result->Fetch();
uint32 linkedAccountId = fields[0].Get<uint32>();
QueryResult accountResult = LoginDatabase.Query("SELECT username FROM account WHERE id = {}", linkedAccountId);
if (accountResult)
{
Field* accountFields = accountResult->Fetch();
std::string username = accountFields[0].Get<std::string>();
ChatHandler(player->GetSession()).PSendSysMessage("- {}", username.c_str());
}
else
{
ChatHandler(player->GetSession()).PSendSysMessage("- Unknown account");
}
} while (result->NextRow());
}
void PlayerbotMgr::HandleUnlinkAccountCommand(Player* player, const std::string& accountName)
{
QueryResult result = LoginDatabase.Query("SELECT id FROM account WHERE username = '{}'", accountName);
if (!result)
{
ChatHandler(player->GetSession()).PSendSysMessage("Account not found.");
return;
}
Field* fields = result->Fetch();
uint32 linkedAccountId = fields[0].Get<uint32>();
uint32 accountId = player->GetSession()->GetAccountId();
PlayerbotsDatabase.Execute("DELETE FROM playerbot_account_links WHERE (account_id = {} AND linked_account_id = {}) OR (account_id = {} AND linked_account_id = {})",
accountId, linkedAccountId, linkedAccountId, accountId);
ChatHandler(player->GetSession()).PSendSysMessage("Account unlinked successfully.");
}

View File

@@ -28,12 +28,10 @@ public:
virtual ~PlayerbotHolder(){}; virtual ~PlayerbotHolder(){};
void AddPlayerBot(ObjectGuid guid, uint32 masterAccountId); void AddPlayerBot(ObjectGuid guid, uint32 masterAccountId);
bool IsAccountLinked(uint32 accountId, uint32 masterAccountId);
void HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder const& holder); void HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder const& holder);
void LogoutPlayerBot(ObjectGuid guid); void LogoutPlayerBot(ObjectGuid guid);
void DisablePlayerBot(ObjectGuid guid); void DisablePlayerBot(ObjectGuid guid);
void RemoveFromPlayerbotsMap(ObjectGuid guid);
Player* GetPlayerBot(ObjectGuid guid) const; Player* GetPlayerBot(ObjectGuid guid) const;
Player* GetPlayerBot(ObjectGuid::LowType lowGuid) const; Player* GetPlayerBot(ObjectGuid::LowType lowGuid) const;
PlayerBotMap::const_iterator GetPlayerBotsBegin() const { return playerBots.begin(); } PlayerBotMap::const_iterator GetPlayerBotsBegin() const { return playerBots.begin(); }
@@ -83,11 +81,6 @@ public:
void SaveToDB(); void SaveToDB();
void HandleSetSecurityKeyCommand(Player* player, const std::string& key);
void HandleLinkAccountCommand(Player* player, const std::string& accountName, const std::string& key);
void HandleViewLinkedAccountsCommand(Player* player);
void HandleUnlinkAccountCommand(Player* player, const std::string& accountName);
protected: protected:
void OnBotLoginInternal(Player* const bot) override; void OnBotLoginInternal(Player* const bot) override;
void CheckTellErrors(uint32 elapsed); void CheckTellErrors(uint32 elapsed);

View File

@@ -86,8 +86,8 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
} }
} }
int32 botGS = (int32)botAI->GetEquipGearScore(bot/*, false, false*/); int32 botGS = (int32)botAI->GetEquipGearScore(bot, false, false);
int32 fromGS = (int32)botAI->GetEquipGearScore(from/*, false, false*/); int32 fromGS = (int32)botAI->GetEquipGearScore(from, false, false);
if (sPlayerbotAIConfig->gearscorecheck) if (sPlayerbotAIConfig->gearscorecheck)
{ {
if (botGS && bot->GetLevel() > 15 && botGS > fromGS && if (botGS && bot->GetLevel() > 15 && botGS > fromGS &&
@@ -139,7 +139,7 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
return PLAYERBOT_SECURITY_INVITE; return PLAYERBOT_SECURITY_INVITE;
} }
if (!ignoreGroup && group->IsFull()) if (group->IsFull())
{ {
if (reason) if (reason)
*reason = PLAYERBOT_DENY_FULL_GROUP; *reason = PLAYERBOT_DENY_FULL_GROUP;
@@ -147,7 +147,7 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
return PLAYERBOT_SECURITY_TALK; return PLAYERBOT_SECURITY_TALK;
} }
if (!ignoreGroup && group->GetLeaderGUID() != bot->GetGUID()) if (group->GetLeaderGUID() != bot->GetGUID())
{ {
if (reason) if (reason)
*reason = PLAYERBOT_DENY_NOT_LEADER; *reason = PLAYERBOT_DENY_NOT_LEADER;
@@ -211,8 +211,8 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
break; break;
case PLAYERBOT_DENY_GEARSCORE: case PLAYERBOT_DENY_GEARSCORE:
{ {
int botGS = (int)botAI->GetEquipGearScore(bot/*, false, false*/); int botGS = (int)botAI->GetEquipGearScore(bot, false, false);
int fromGS = (int)botAI->GetEquipGearScore(from/*, false, false*/); int fromGS = (int)botAI->GetEquipGearScore(from, false, false);
int diff = (100 * (botGS - fromGS) / botGS); int diff = (100 * (botGS - fromGS) / botGS);
int req = 12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel(); int req = 12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel();
out << "Your gearscore is too low: |cffff0000" << fromGS << "|cffffffff/|cff00ff00" << botGS out << "Your gearscore is too low: |cffff0000" << fromGS << "|cffffffff/|cff00ff00" << botGS

View File

@@ -6,7 +6,6 @@
#include "PlayerbotTextMgr.h" #include "PlayerbotTextMgr.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "WorldSessionMgr.h"
void PlayerbotTextMgr::replaceAll(std::string& str, const std::string& from, const std::string& to) void PlayerbotTextMgr::replaceAll(std::string& str, const std::string& from, const std::string& to)
{ {
@@ -185,7 +184,7 @@ uint32 PlayerbotTextMgr::GetLocalePriority()
uint32 topLocale = 0; uint32 topLocale = 0;
// if no real players online, reset top locale // if no real players online, reset top locale
if (!sWorldSessionMgr->GetActiveSessionCount()) if (!sWorld->GetActiveSessionCount())
{ {
ResetLocalePriority(); ResetLocalePriority();
return 0; return 0;

View File

@@ -23,8 +23,6 @@
#include "DatabaseLoader.h" #include "DatabaseLoader.h"
#include "GuildTaskMgr.h" #include "GuildTaskMgr.h"
#include "Metric.h" #include "Metric.h"
#include "PlayerScript.h"
#include "PlayerbotAIConfig.h"
#include "RandomPlayerbotMgr.h" #include "RandomPlayerbotMgr.h"
#include "ScriptMgr.h" #include "ScriptMgr.h"
#include "cs_playerbots.h" #include "cs_playerbots.h"
@@ -54,7 +52,7 @@ public:
void OnDatabaseSelectIndexLogout(Player* player, uint32& statementIndex, uint32& statementParam) override void OnDatabaseSelectIndexLogout(Player* player, uint32& statementIndex, uint32& statementParam) override
{ {
statementIndex = CHAR_UPD_CHAR_OFFLINE; statementIndex = CHAR_UPD_CHAR_ONLINE;
statementParam = player->GetGUID().GetCounter(); statementParam = player->GetGUID().GetCounter();
} }
@@ -74,53 +72,45 @@ public:
} }
}; };
class PlayerbotsMetricScript : public MetricScript
{
public:
PlayerbotsMetricScript() : MetricScript("PlayerbotsMetricScript") {}
void OnMetricLogging() override
{
if (sMetric->IsEnabled())
{
sMetric->LogValue("db_queue_playerbots", uint64(PlayerbotsDatabase.QueueSize()), {});
}
}
};
class PlayerbotsPlayerScript : public PlayerScript class PlayerbotsPlayerScript : public PlayerScript
{ {
public: public:
PlayerbotsPlayerScript() : PlayerScript("PlayerbotsPlayerScript", { PlayerbotsPlayerScript() : PlayerScript("PlayerbotsPlayerScript") {}
PLAYERHOOK_ON_LOGIN,
PLAYERHOOK_ON_AFTER_UPDATE,
PLAYERHOOK_ON_CHAT,
PLAYERHOOK_ON_CHAT_WITH_CHANNEL,
PLAYERHOOK_ON_CHAT_WITH_GROUP,
PLAYERHOOK_ON_BEFORE_CRITERIA_PROGRESS,
PLAYERHOOK_ON_BEFORE_ACHI_COMPLETE,
PLAYERHOOK_CAN_PLAYER_USE_PRIVATE_CHAT,
PLAYERHOOK_ON_GIVE_EXP
}) {}
void OnPlayerLogin(Player* player) override void OnLogin(Player* player) override
{ {
if (!player->GetSession()->IsBot()) if (!player->GetSession()->IsBot())
{ {
sPlayerbotsMgr->AddPlayerbotData(player, false); sPlayerbotsMgr->AddPlayerbotData(player, false);
sRandomPlayerbotMgr->OnPlayerLogin(player); sRandomPlayerbotMgr->OnPlayerLogin(player);
// Before modifying the following messages, please make sure it does not violate the AGPLv3.0 license
// especially if you are distributing a repack or hosting a public server
// e.g. you can replace the URL with your own repository,
// but it should be publicly accessible and include all modifications you've made
if (sPlayerbotAIConfig->enabled)
{
ChatHandler(player->GetSession()).SendSysMessage(
"|cff00ff00This server runs with |cff00ccffmod-playerbots|r "
"|cffcccccchttps://github.com/liyunfan1223/mod-playerbots|r");
}
if (sPlayerbotAIConfig->enabled || sPlayerbotAIConfig->randomBotAutologin) if (sPlayerbotAIConfig->enabled || sPlayerbotAIConfig->randomBotAutologin)
{ {
std::string roundedTime = std::string roundedTime =
std::to_string(std::ceil((sPlayerbotAIConfig->maxRandomBots * 0.11 / 60) * 10) / 10.0); std::to_string(std::ceil((sPlayerbotAIConfig->maxRandomBots * 0.13 / 60) * 10) / 10.0);
roundedTime = roundedTime.substr(0, roundedTime.find('.') + 2); roundedTime = roundedTime.substr(0, roundedTime.find('.') + 2);
ChatHandler(player->GetSession()).SendSysMessage( ChatHandler(player->GetSession()).SendSysMessage(
"|cff00ff00Playerbots:|r bot initialization at server startup takes about '" "Playerbots: bot initialization at server startup will require '" + roundedTime + "' minutes.");
+ roundedTime + "' minutes.");
} }
} }
} }
void OnPlayerAfterUpdate(Player* player, uint32 diff) override void OnAfterUpdate(Player* player, uint32 diff) override
{ {
if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(player)) if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(player))
{ {
@@ -133,7 +123,7 @@ public:
} }
} }
bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Player* receiver) override bool CanPlayerUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Player* receiver) override
{ {
if (type == CHAT_MSG_WHISPER) if (type == CHAT_MSG_WHISPER)
{ {
@@ -148,7 +138,7 @@ public:
return true; return true;
} }
void OnPlayerChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Group* group) override void OnChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Group* group) override
{ {
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
{ {
@@ -162,7 +152,7 @@ public:
} }
} }
void OnPlayerChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg) override void OnChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg) override
{ {
if (type == CHAT_MSG_GUILD) if (type == CHAT_MSG_GUILD)
{ {
@@ -183,7 +173,7 @@ public:
} }
} }
void OnPlayerChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Channel* channel) override void OnChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Channel* channel) override
{ {
if (PlayerbotMgr* playerbotMgr = GET_PLAYERBOT_MGR(player)) if (PlayerbotMgr* playerbotMgr = GET_PLAYERBOT_MGR(player))
{ {
@@ -196,7 +186,7 @@ public:
sRandomPlayerbotMgr->HandleCommand(type, msg, player); sRandomPlayerbotMgr->HandleCommand(type, msg, player);
} }
bool OnPlayerBeforeCriteriaProgress(Player* player, AchievementCriteriaEntry const* /*criteria*/) override bool OnBeforeCriteriaProgress(Player* player, AchievementCriteriaEntry const* /*criteria*/) override
{ {
if (sRandomPlayerbotMgr->IsRandomBot(player)) if (sRandomPlayerbotMgr->IsRandomBot(player))
{ {
@@ -205,7 +195,7 @@ public:
return true; return true;
} }
bool OnPlayerBeforeAchievementComplete(Player* player, AchievementEntry const* /*achievement*/) override bool OnBeforeAchiComplete(Player* player, AchievementEntry const* /*achievement*/) override
{ {
if (sRandomPlayerbotMgr->IsRandomBot(player)) if (sRandomPlayerbotMgr->IsRandomBot(player))
{ {
@@ -213,17 +203,6 @@ public:
} }
return true; return true;
} }
void OnPlayerGiveXP(Player* player, uint32& amount, Unit* /*victim*/, uint8 /*xpSource*/) override
{
if (!player->GetSession()->IsBot())
return;
if (sPlayerbotAIConfig->playerbotsXPrate != 1.0)
{
amount = static_cast<uint32>(std::round(static_cast<float>(amount) * sPlayerbotAIConfig->playerbotsXPrate));
}
}
}; };
class PlayerbotsMiscScript : public MiscScript class PlayerbotsMiscScript : public MiscScript
@@ -248,9 +227,7 @@ public:
class PlayerbotsServerScript : public ServerScript class PlayerbotsServerScript : public ServerScript
{ {
public: public:
PlayerbotsServerScript() : ServerScript("PlayerbotsServerScript", { PlayerbotsServerScript() : ServerScript("PlayerbotsServerScript") {}
SERVERHOOK_CAN_PACKET_RECEIVE
}) {}
void OnPacketReceived(WorldSession* session, WorldPacket const& packet) override void OnPacketReceived(WorldSession* session, WorldPacket const& packet) override
{ {
@@ -263,29 +240,12 @@ public:
class PlayerbotsWorldScript : public WorldScript class PlayerbotsWorldScript : public WorldScript
{ {
public: public:
PlayerbotsWorldScript() : WorldScript("PlayerbotsWorldScript", { PlayerbotsWorldScript() : WorldScript("PlayerbotsWorldScript") {}
WORLDHOOK_ON_BEFORE_WORLD_INITIALIZED
}) {}
void OnBeforeWorldInitialized() override void OnBeforeWorldInitialized() override
{ {
// Before modifying the following messages, please make sure it does not violate the AGPLv3.0 license
// especially if you are distributing a repack or hosting a public server
// e.g. you can replace the URL with your own repository,
// but it should be publicly accessible and include all modifications you've made
LOG_INFO("server.loading", "╔══════════════════════════════════════════════════════════╗");
LOG_INFO("server.loading", "║ ║");
LOG_INFO("server.loading", "║ AzerothCore Playerbots Module ║");
LOG_INFO("server.loading", "║ ║");
LOG_INFO("server.loading", "╟──────────────────────────────────────────────────────────╢");
LOG_INFO("server.loading", "║ mod-playerbots is a community-driven open-source ║");
LOG_INFO("server.loading", "║ project based on AzerothCore, licensed under AGPLv3.0 ║");
LOG_INFO("server.loading", "╟──────────────────────────────────────────────────────────╢");
LOG_INFO("server.loading", "║ https://github.com/liyunfan1223/mod-playerbots ║");
LOG_INFO("server.loading", "╚══════════════════════════════════════════════════════════╝");
uint32 oldMSTime = getMSTime(); uint32 oldMSTime = getMSTime();
LOG_INFO("server.loading", " "); LOG_INFO("server.loading", " ");
LOG_INFO("server.loading", "Load Playerbots Config..."); LOG_INFO("server.loading", "Load Playerbots Config...");
@@ -379,16 +339,13 @@ public:
sRandomPlayerbotMgr->OnPlayerLogout(player); sRandomPlayerbotMgr->OnPlayerLogout(player);
} }
void OnPlayerbotLogoutBots() override void OnPlayerbotLogoutBots() override { sRandomPlayerbotMgr->LogoutAllBots(); }
{
LOG_INFO("playerbots", "Logging out all bots...");
sRandomPlayerbotMgr->LogoutAllBots();
}
}; };
void AddPlayerbotsScripts() void AddPlayerbotsScripts()
{ {
new PlayerbotsDatabaseScript(); new PlayerbotsDatabaseScript();
new PlayerbotsMetricScript();
new PlayerbotsPlayerScript(); new PlayerbotsPlayerScript();
new PlayerbotsMiscScript(); new PlayerbotsMiscScript();
new PlayerbotsServerScript(); new PlayerbotsServerScript();

View File

@@ -833,7 +833,7 @@ bool RandomItemMgr::CanEquipWeapon(uint8 clazz, ItemTemplate const* proto)
void RandomItemMgr::BuildItemInfoCache() void RandomItemMgr::BuildItemInfoCache()
{ {
//uint32 maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL); //not used, line marked for removal. uint32 maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
// load weightscales // load weightscales
LOG_INFO("playerbots", "Loading weightscales info"); LOG_INFO("playerbots", "Loading weightscales info");
@@ -1347,7 +1347,7 @@ uint32 RandomItemMgr::CalculateStatWeight(uint8 playerclass, uint8 spec, ItemTem
// check weapon dps // check weapon dps
if (proto->IsWeaponVellum()) if (proto->IsWeaponVellum())
{ {
//WeaponAttackType attType = BASE_ATTACK; //not used, line marked for removal. WeaponAttackType attType = BASE_ATTACK;
uint32 dps = 0; uint32 dps = 0;
for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; i++) for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; i++)
@@ -2206,7 +2206,7 @@ void RandomItemMgr::BuildEquipCacheNew()
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 in this scope, line marked for removal. uint32 questId = i->first;
Quest const* quest = i->second; Quest const* quest = i->second;
if (quest->IsRepeatable()) if (quest->IsRepeatable())
@@ -2313,9 +2313,8 @@ void RandomItemMgr::BuildAmmoCache()
for (uint32 subClass = ITEM_SUBCLASS_ARROW; subClass <= ITEM_SUBCLASS_BULLET; subClass++) for (uint32 subClass = ITEM_SUBCLASS_ARROW; subClass <= ITEM_SUBCLASS_BULLET; subClass++)
{ {
QueryResult results = WorldDatabase.Query( QueryResult results = WorldDatabase.Query(
"SELECT entry FROM item_template WHERE class = {} AND subclass = {} AND RequiredLevel <= {} AND duration = 0 " "SELECT entry, Flags FROM item_template WHERE class = {} AND subclass = {} AND RequiredLevel <= {} and duration = 0 "
"AND (Flags & 16) = 0 AND dmg_min1 != 0 AND RequiredLevel != 0 " "ORDER BY stackable DESC, RequiredLevel DESC",
"ORDER BY stackable DESC, ItemLevel DESC",
ITEM_CLASS_PROJECTILE, subClass, level); ITEM_CLASS_PROJECTILE, subClass, level);
if (!results) if (!results)
continue; continue;
@@ -2323,27 +2322,35 @@ void RandomItemMgr::BuildAmmoCache()
{ {
Field* fields = results->Fetch(); Field* fields = results->Fetch();
uint32 entry = fields[0].Get<uint32>(); uint32 entry = fields[0].Get<uint32>();
ammoCache[level][subClass].push_back(entry); uint32 flags = fields[1].Get<uint32>();
if (flags & ITEM_FLAG_DEPRECATED)
{
continue;
}
ammoCache[level][subClass] = entry;
++counter; ++counter;
break;
} while (results->NextRow()); } while (results->NextRow());
} }
} }
LOG_INFO("server.loading", "Cached {} ammo", counter); // TEST LOG_INFO("server.loading", "Cached {} types of ammo", counter); // TEST
} }
std::vector<uint32> RandomItemMgr::GetAmmo(uint32 level, uint32 subClass) { return ammoCache[level][subClass]; } uint32 RandomItemMgr::GetAmmo(uint32 level, uint32 subClass) { return ammoCache[level][subClass]; }
void RandomItemMgr::BuildPotionCache() void RandomItemMgr::BuildPotionCache()
{ {
uint32 maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL); uint32 maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
// if (maxLevel > sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
// maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
LOG_INFO("playerbots", "Building potion cache for {} levels", maxLevel); LOG_INFO("server.loading", "Building potion cache for {} levels", maxLevel);
ItemTemplateContainer const* itemTemplates = sObjectMgr->GetItemTemplateStore(); ItemTemplateContainer const* itemTemplates = sObjectMgr->GetItemTemplateStore();
uint32 counter = 0; uint32 counter = 0;
for (uint32 level = 1; level <= maxLevel; level++) for (uint32 level = 1; level <= maxLevel + 1; level += 10)
{ {
uint32 effects[] = {SPELL_EFFECT_HEAL, SPELL_EFFECT_ENERGIZE}; uint32 effects[] = {SPELL_EFFECT_HEAL, SPELL_EFFECT_ENERGIZE};
for (uint8 i = 0; i < 2; ++i) for (uint8 i = 0; i < 2; ++i)
@@ -2360,9 +2367,8 @@ void RandomItemMgr::BuildPotionCache()
(proto->SubClass != ITEM_SUBCLASS_POTION && proto->SubClass != ITEM_SUBCLASS_FLASK) || (proto->SubClass != ITEM_SUBCLASS_POTION && proto->SubClass != ITEM_SUBCLASS_FLASK) ||
proto->Bonding != NO_BIND) proto->Bonding != NO_BIND)
continue; continue;
uint32 requiredLevel = proto->RequiredLevel; if (proto->RequiredLevel && (proto->RequiredLevel > level || proto->RequiredLevel < level - 10))
if (requiredLevel > level || (level > 13 && requiredLevel < level - 13))
continue; continue;
if (proto->RequiredSkill) if (proto->RequiredSkill)
@@ -2374,44 +2380,39 @@ void RandomItemMgr::BuildPotionCache()
if (proto->Duration & 0x80000000) if (proto->Duration & 0x80000000)
continue; continue;
for (uint8 j = 0; j < MAX_ITEM_PROTO_SPELLS; j++)
if (proto->AllowableClass != -1)
continue;
bool hybrid = false;
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(proto->Spells[0].SpellId);
if (!spellInfo)
continue;
// do not accept hybrid potion
for (uint8 i = 1; i < 3; i++)
{ {
if (spellInfo->Effects[i].Effect != 0) SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(proto->Spells[j].SpellId);
if (!spellInfo)
continue;
for (uint8 i = 0; i < 3; i++)
{ {
hybrid = true; if (spellInfo->Effects[i].Effect == effect)
break; {
potionCache[level / 10][effect].push_back(itr.first);
break;
}
} }
} }
if (hybrid)
continue;
if (spellInfo->Effects[0].Effect == effect)
potionCache[level][effect].push_back(itr.first);
} }
} }
} }
for (uint32 level = 1; level <= maxLevel; level++) for (uint32 level = 1; level <= maxLevel + 1; level += 10)
{ {
uint32 effects[] = {SPELL_EFFECT_HEAL, SPELL_EFFECT_ENERGIZE}; uint32 effects[] = {SPELL_EFFECT_HEAL, SPELL_EFFECT_ENERGIZE};
for (uint8 i = 0; i < 2; ++i) for (uint8 i = 0; i < 2; ++i)
{ {
uint32 effect = effects[i]; uint32 effect = effects[i];
uint32 size = potionCache[level][effect].size(); uint32 size = potionCache[level / 10][effect].size();
counter += size; ++counter;
LOG_DEBUG("server.loading", "Potion cache for level={}, effect={}: {} items", level, effect, size);
} }
} }
LOG_INFO("playerbots", "Cached {} potions", counter); LOG_INFO("server.loading", "Cached {} types of potions", counter); // TEST
} }
void RandomItemMgr::BuildFoodCache() void RandomItemMgr::BuildFoodCache()
@@ -2477,7 +2478,7 @@ void RandomItemMgr::BuildFoodCache()
uint32 RandomItemMgr::GetRandomPotion(uint32 level, uint32 effect) uint32 RandomItemMgr::GetRandomPotion(uint32 level, uint32 effect)
{ {
const std::vector<uint32> &potions = potionCache[level][effect]; std::vector<uint32> potions = potionCache[(level - 1) / 10][effect];
if (potions.empty()) if (potions.empty())
return 0; return 0;

View File

@@ -157,7 +157,7 @@ public:
uint32 GetStatWeight(Player* player, uint32 itemId); uint32 GetStatWeight(Player* player, uint32 itemId);
uint32 GetLiveStatWeight(Player* player, uint32 itemId); uint32 GetLiveStatWeight(Player* player, uint32 itemId);
uint32 GetRandomItem(uint32 level, RandomItemType type, RandomItemPredicate* predicate = nullptr); uint32 GetRandomItem(uint32 level, RandomItemType type, RandomItemPredicate* predicate = nullptr);
std::vector<uint32> GetAmmo(uint32 level, uint32 subClass); uint32 GetAmmo(uint32 level, uint32 subClass);
uint32 GetRandomPotion(uint32 level, uint32 effect); uint32 GetRandomPotion(uint32 level, uint32 effect);
uint32 GetRandomFood(uint32 level, uint32 category); uint32 GetRandomFood(uint32 level, uint32 category);
uint32 GetFood(uint32 level, uint32 category); uint32 GetFood(uint32 level, uint32 category);
@@ -195,7 +195,7 @@ private:
std::map<RandomItemType, RandomItemPredicate*> predicates; std::map<RandomItemType, RandomItemPredicate*> predicates;
BotEquipCache equipCache; BotEquipCache equipCache;
std::map<EquipmentSlots, std::set<InventoryType>> viableSlots; std::map<EquipmentSlots, std::set<InventoryType>> viableSlots;
std::map<uint32, std::map<uint32, std::vector<uint32>>> ammoCache; std::map<uint32, std::map<uint32, uint32>> ammoCache;
std::map<uint32, std::map<uint32, std::vector<uint32>>> potionCache; std::map<uint32, std::map<uint32, std::vector<uint32>>> potionCache;
std::map<uint32, std::map<uint32, std::vector<uint32>>> foodCache; std::map<uint32, std::map<uint32, std::vector<uint32>>> foodCache;
std::map<uint32, std::vector<uint32>> tradeCache; std::map<uint32, std::vector<uint32>> tradeCache;

View File

@@ -1,7 +1,7 @@
/* /*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version. * and/or modify it under version 2 of the License, or (at your option), any later version.
*/ */
#include "RandomPlayerbotFactory.h" #include "RandomPlayerbotFactory.h"
@@ -14,12 +14,11 @@
#include "ScriptMgr.h" #include "ScriptMgr.h"
#include "SharedDefines.h" #include "SharedDefines.h"
#include "SocialMgr.h" #include "SocialMgr.h"
#include "Timer.h"
std::map<uint8, std::vector<uint8>> RandomPlayerbotFactory::availableRaces; std::map<uint8, std::vector<uint8>> RandomPlayerbotFactory::availableRaces;
constexpr RandomPlayerbotFactory::NameRaceAndGender RandomPlayerbotFactory::CombineRaceAndGender(uint8 gender, constexpr RandomPlayerbotFactory::NameRaceAndGender RandomPlayerbotFactory::CombineRaceAndGender(uint8 gender,
uint8 race) uint8 race)
{ {
switch (race) switch (race)
{ {
@@ -79,7 +78,6 @@ RandomPlayerbotFactory::RandomPlayerbotFactory(uint32 accountId) : accountId(acc
availableRaces[CLASS_ROGUE].push_back(RACE_NIGHTELF); availableRaces[CLASS_ROGUE].push_back(RACE_NIGHTELF);
availableRaces[CLASS_ROGUE].push_back(RACE_GNOME); availableRaces[CLASS_ROGUE].push_back(RACE_GNOME);
availableRaces[CLASS_ROGUE].push_back(RACE_ORC); availableRaces[CLASS_ROGUE].push_back(RACE_ORC);
availableRaces[CLASS_ROGUE].push_back(RACE_UNDEAD_PLAYER);
availableRaces[CLASS_ROGUE].push_back(RACE_TROLL); availableRaces[CLASS_ROGUE].push_back(RACE_TROLL);
if (expansion >= EXPANSION_THE_BURNING_CRUSADE) if (expansion >= EXPANSION_THE_BURNING_CRUSADE)
{ {
@@ -225,7 +223,7 @@ Player* RandomPlayerbotFactory::CreateRandomBot(WorldSession* session, uint8 cls
} }
} }
//uint8 skinColor = skinColors[urand(0, skinColors.size() - 1)]; //not used, line marked for removal. uint8 skinColor = skinColors[urand(0, skinColors.size() - 1)];
std::pair<uint8, uint8> face = faces[urand(0, faces.size() - 1)]; std::pair<uint8, uint8> face = faces[urand(0, faces.size() - 1)];
std::pair<uint8, uint8> hair = hairs[urand(0, hairs.size() - 1)]; std::pair<uint8, uint8> hair = hairs[urand(0, hairs.size() - 1)];
@@ -244,7 +242,7 @@ Player* RandomPlayerbotFactory::CreateRandomBot(WorldSession* session, uint8 cls
delete player; delete player;
LOG_ERROR("playerbots", "Unable to create random bot for account {} - name: \"{}\"; race: {}; class: {}", LOG_ERROR("playerbots", "Unable to create random bot for account {} - name: \"{}\"; race: {}; class: {}",
accountId, name.c_str(), race, cls); accountId, name.c_str(), race, cls);
return nullptr; return nullptr;
} }
@@ -256,7 +254,7 @@ Player* RandomPlayerbotFactory::CreateRandomBot(WorldSession* session, uint8 cls
player->learnSpell(50977, false); player->learnSpell(50977, false);
} }
LOG_DEBUG("playerbots", "Random bot created for account {} - name: \"{}\"; race: {}; class: {}", accountId, LOG_DEBUG("playerbots", "Random bot created for account {} - name: \"{}\"; race: {}; class: {}", accountId,
name.c_str(), race, cls); name.c_str(), race, cls);
return player; return player;
} }
@@ -278,19 +276,13 @@ std::string const RandomPlayerbotFactory::CreateRandomBotName(NameRaceAndGender
{ {
break; break;
} }
Field* fields = result->Fetch(); Field* fields = result->Fetch();
botName = fields[0].Get<std::string>(); botName = fields[0].Get<std::string>();
if (ObjectMgr::CheckPlayerName(botName) == CHAR_NAME_SUCCESS) // Checks for reservation & profanity, too if (ObjectMgr::CheckPlayerName(botName) == CHAR_NAME_SUCCESS) // Checks for reservation & profanity, too
{ {
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHECK_NAME);
stmt->SetData(0, botName);
if (PreparedQueryResult result = CharacterDatabase.Query(stmt))
continue;
return botName; return botName;
} }
} }
// CONLANG NAME GENERATION // CONLANG NAME GENERATION
@@ -353,14 +345,6 @@ std::string const RandomPlayerbotFactory::CreateRandomBotName(NameRaceAndGender
botName.clear(); botName.clear();
continue; continue;
} }
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHECK_NAME);
stmt->SetData(0, botName);
if (PreparedQueryResult result = CharacterDatabase.Query(stmt))
{
botName.clear();
continue;
}
return std::move(botName); return std::move(botName);
} }
@@ -378,14 +362,6 @@ std::string const RandomPlayerbotFactory::CreateRandomBotName(NameRaceAndGender
botName.clear(); botName.clear();
continue; continue;
} }
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHECK_NAME);
stmt->SetData(0, botName);
if (PreparedQueryResult result = CharacterDatabase.Query(stmt))
{
botName.clear();
continue;
}
return std::move(botName); return std::move(botName);
} }
LOG_ERROR("playerbots", "Random name generation failed."); LOG_ERROR("playerbots", "Random name generation failed.");
@@ -393,76 +369,15 @@ std::string const RandomPlayerbotFactory::CreateRandomBotName(NameRaceAndGender
return std::move(botName); return std::move(botName);
} }
uint32 RandomPlayerbotFactory::CalculateTotalAccountCount()
{
// Calculates the total number of required accounts, either using the specified randomBotAccountCount
// or determining it dynamically based on the WOTLK condition, max random bots, rotation pool size,
// and additional class account pool size.
// Checks if randomBotAccountCount is set, otherwise calculate it dynamically.
if (sPlayerbotAIConfig->randomBotAccountCount > 0)
return sPlayerbotAIConfig->randomBotAccountCount;
// Avoid creating accounts if both maxRandom & ClassBots are set to zero.
if (sPlayerbotAIConfig->maxRandomBots == 0 &&
sPlayerbotAIConfig->addClassAccountPoolSize == 0)
return 0;
//bool isWOTLK = sWorld->getIntConfig(CONFIG_EXPANSION) == EXPANSION_WRATH_OF_THE_LICH_KING; //not used, line marked for removal.
// Determine divisor based on WOTLK condition
int divisor = CalculateAvailableCharsPerAccount();
// Calculate max bots
int maxBots = sPlayerbotAIConfig->maxRandomBots;
// Take perodic online - offline into account
if (sPlayerbotAIConfig->enablePeriodicOnlineOffline)
{
maxBots *= sPlayerbotAIConfig->periodicOnlineOfflineRatio;
}
// Calculate base accounts, add class account pool size, and add 1 as a fixed offset
uint32 baseAccounts = maxBots / divisor;
return baseAccounts + sPlayerbotAIConfig->addClassAccountPoolSize + 1;
}
uint32 RandomPlayerbotFactory::CalculateAvailableCharsPerAccount()
{
bool noDK = sPlayerbotAIConfig->disableDeathKnightLogin || sWorld->getIntConfig(CONFIG_EXPANSION) != EXPANSION_WRATH_OF_THE_LICH_KING;
uint32 availableChars = noDK ? 9 : 10;
uint32 hordeRatio = sPlayerbotAIConfig->randomBotHordeRatio;
uint32 allianceRatio = sPlayerbotAIConfig->randomBotAllianceRatio;
// horde : alliance = 50 : 50 -> 0%
// horde : alliance = 0 : 50 -> 50%
// horde : alliance = 10 : 50 -> 40%
float unavailableRatio = static_cast<float>((std::max(hordeRatio, allianceRatio) - std::min(hordeRatio, allianceRatio))) /
(std::max(hordeRatio, allianceRatio) * 2);
if (unavailableRatio != 0)
{
// conservative floor to ensure enough chars (may result in more accounts than needed)
availableChars = availableChars - availableChars * unavailableRatio;
}
return availableChars;
}
void RandomPlayerbotFactory::CreateRandomBots() void RandomPlayerbotFactory::CreateRandomBots()
{ {
/* multi-thread here is meaningless? since the async db operations */ /* multi-thread here is meaningless? since the async db operations */
if (sPlayerbotAIConfig->deleteRandomBotAccounts) if (sPlayerbotAIConfig->deleteRandomBotAccounts)
{ {
std::vector<uint32> botAccounts; std::vector<uint32> botAccounts;
std::vector<uint32> botFriends; std::vector<uint32> botFriends;
// Calculates the total number of required accounts. for (uint32 accountNumber = 0; accountNumber < sPlayerbotAIConfig->randomBotAccountCount; ++accountNumber)
uint32 totalAccountCount = CalculateTotalAccountCount();
for (uint32 accountNumber = 0; accountNumber < totalAccountCount; ++accountNumber)
{ {
std::ostringstream out; std::ostringstream out;
out << sPlayerbotAIConfig->randomBotAccountPrefix << accountNumber; out << sPlayerbotAIConfig->randomBotAccountPrefix << accountNumber;
@@ -472,79 +387,9 @@ void RandomPlayerbotFactory::CreateRandomBots()
botAccounts.push_back(accountId); botAccounts.push_back(accountId);
} }
LOG_INFO("playerbots", "Deleting all random bot characters and accounts..."); LOG_INFO("playerbots", "Deleting all random bot characters, {} accounts collected...", botAccounts.size());
// First execute all the cleanup SQL commands
// Clear playerbots_random_bots table
PlayerbotsDatabase.Execute("DELETE FROM playerbots_random_bots");
// Get the database names dynamically
std::string loginDBName = LoginDatabase.GetConnectionInfo()->database;
std::string characterDBName = CharacterDatabase.GetConnectionInfo()->database;
// Delete all characters from bot accounts
CharacterDatabase.Execute("DELETE FROM characters WHERE account IN (SELECT id FROM " + loginDBName + ".account WHERE username LIKE '{}%%')",
sPlayerbotAIConfig->randomBotAccountPrefix.c_str());
// Clean up orphaned entries in playerbots_guild_tasks
PlayerbotsDatabase.Execute("DELETE FROM playerbots_guild_tasks WHERE owner NOT IN (SELECT guid FROM " + characterDBName + ".characters)");
// Clean up orphaned entries in playerbots_db_store
PlayerbotsDatabase.Execute("DELETE FROM playerbots_db_store WHERE guid NOT IN (SELECT guid FROM " + characterDBName + ".characters WHERE account IN (SELECT id FROM " + loginDBName + ".account WHERE username NOT LIKE '{}%%'))",
sPlayerbotAIConfig->randomBotAccountPrefix.c_str());
// Clean up orphaned records in character-related tables
CharacterDatabase.Execute("DELETE FROM arena_team_member WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM arena_team WHERE arenaTeamId NOT IN (SELECT arenaTeamId FROM arena_team_member)");
CharacterDatabase.Execute("DELETE FROM character_account_data WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM character_achievement WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM character_achievement_progress WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM character_action WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM character_aura WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM character_glyphs WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM character_homebind WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM item_instance WHERE owner_guid NOT IN (SELECT guid FROM characters) AND owner_guid > 0");
CharacterDatabase.Execute("DELETE FROM character_inventory WHERE guid NOT IN (SELECT guid FROM characters)");
// Clean up pet data
CharacterDatabase.Execute("DELETE FROM character_pet WHERE owner NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM pet_aura WHERE guid NOT IN (SELECT id FROM character_pet)");
CharacterDatabase.Execute("DELETE FROM pet_spell WHERE guid NOT IN (SELECT id FROM character_pet)");
CharacterDatabase.Execute("DELETE FROM pet_spell_cooldown WHERE guid NOT IN (SELECT id FROM character_pet)");
// Clean up character data
CharacterDatabase.Execute("DELETE FROM character_queststatus WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM character_queststatus_rewarded WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM character_reputation WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM character_skills WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM character_social WHERE friend NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM character_spell WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM character_spell_cooldown WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM character_talent WHERE guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM corpse WHERE guid NOT IN (SELECT guid FROM characters)");
// Clean up group data
CharacterDatabase.Execute("DELETE FROM `groups` WHERE leaderGuid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM group_member WHERE memberGuid NOT IN (SELECT guid FROM characters)");
// Clean up mail
CharacterDatabase.Execute("DELETE FROM mail WHERE receiver NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM mail_items WHERE receiver NOT IN (SELECT guid FROM characters)");
// Clean up guild data
CharacterDatabase.Execute("DELETE FROM guild WHERE leaderguid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM guild_bank_eventlog WHERE guildid NOT IN (SELECT guildid FROM guild)");
CharacterDatabase.Execute("DELETE FROM guild_member WHERE guildid NOT IN (SELECT guildid FROM guild) OR guid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM guild_rank WHERE guildid NOT IN (SELECT guildid FROM guild)");
// Clean up petition data
CharacterDatabase.Execute("DELETE FROM petition WHERE ownerguid NOT IN (SELECT guid FROM characters)");
CharacterDatabase.Execute("DELETE FROM petition_sign WHERE ownerguid NOT IN (SELECT guid FROM characters) OR playerguid NOT IN (SELECT guid FROM characters)");
// Finally, delete the bot accounts themselves
LOG_INFO("playerbots", "Deleting random bot accounts...");
QueryResult results = LoginDatabase.Query("SELECT id FROM account WHERE username LIKE '{}%%'", QueryResult results = LoginDatabase.Query("SELECT id FROM account WHERE username LIKE '{}%%'",
sPlayerbotAIConfig->randomBotAccountPrefix.c_str()); sPlayerbotAIConfig->randomBotAccountPrefix.c_str());
int32 deletion_count = 0; int32 deletion_count = 0;
if (results) if (results)
{ {
@@ -552,19 +397,16 @@ void RandomPlayerbotFactory::CreateRandomBots()
{ {
Field* fields = results->Fetch(); Field* fields = results->Fetch();
uint32 accId = fields[0].Get<uint32>(); uint32 accId = fields[0].Get<uint32>();
LOG_DEBUG("playerbots", "Deleting account accID: {}({})...", accId, ++deletion_count); LOG_INFO("playerbots", "Deleting account accID: {}({})...", accId, ++deletion_count);
AccountMgr::DeleteAccount(accId); AccountMgr::DeleteAccount(accId);
} while (results->NextRow()); } while (results->NextRow());
} }
uint32 timer = getMSTime(); PlayerbotsDatabase.Execute(PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_DEL_RANDOM_BOTS));
/* TODO(yunfan): we need to sleep here to wait for async account deleted, or the newly account won't be created
// Wait for all pending database operations to complete correctly the better way is turning the async db operation to sync db operation */
while (LoginDatabase.QueueSize() || CharacterDatabase.QueueSize() || PlayerbotsDatabase.QueueSize()) std::this_thread::sleep_for(10ms * sPlayerbotAIConfig->randomBotAccountCount);
{ LOG_INFO("playerbots", "Random bot characters deleted.");
std::this_thread::sleep_for(1s);
}
LOG_INFO("playerbots", ">> Random bot accounts and data deleted in {} ms", GetMSTimeDiffToNow(timer));
LOG_INFO("playerbots", "Please reset the AiPlayerbot.DeleteRandomBotAccounts to 0 and restart the server..."); LOG_INFO("playerbots", "Please reset the AiPlayerbot.DeleteRandomBotAccounts to 0 and restart the server...");
World::StopNow(SHUTDOWN_EXIT_CODE); World::StopNow(SHUTDOWN_EXIT_CODE);
return; return;
@@ -572,14 +414,28 @@ void RandomPlayerbotFactory::CreateRandomBots()
LOG_INFO("playerbots", "Creating random bot accounts..."); LOG_INFO("playerbots", "Creating random bot accounts...");
std::unordered_map<NameRaceAndGender, std::vector<std::string>> nameCache; std::unordered_map<NameRaceAndGender, std::vector<std::string>> nameCache;
uint32 totalAccCount = sPlayerbotAIConfig->randomBotAccountCount;
std::vector<std::future<void>> account_creations; std::vector<std::future<void>> account_creations;
int account_creation = 0; int account_creation = 0;
// Calculates the total number of required accounts. LOG_INFO("playerbots", "Creating cache for names per gender and race.");
uint32 totalAccountCount = CalculateTotalAccountCount(); QueryResult result = CharacterDatabase.Query("SELECT name, gender FROM playerbots_names");
uint32 timer = getMSTime(); if (!result)
{
LOG_ERROR("playerbots", "No more unused names left");
return;
}
do
{
Field* fields = result->Fetch();
std::string name = fields[0].Get<std::string>();
NameRaceAndGender raceAndGender = static_cast<NameRaceAndGender>(fields[1].Get<uint8>());
if (sObjectMgr->CheckPlayerName(name) == CHAR_NAME_SUCCESS)
nameCache[raceAndGender].push_back(name);
for (uint32 accountNumber = 0; accountNumber < totalAccountCount; ++accountNumber) } while (result->NextRow());
for (uint32 accountNumber = 0; accountNumber < sPlayerbotAIConfig->randomBotAccountCount; ++accountNumber)
{ {
std::ostringstream out; std::ostringstream out;
out << sPlayerbotAIConfig->randomBotAccountPrefix << accountNumber; out << sPlayerbotAIConfig->randomBotAccountPrefix << accountNumber;
@@ -608,26 +464,22 @@ void RandomPlayerbotFactory::CreateRandomBots()
LOG_DEBUG("playerbots", "Account {} created for random bots", accountName.c_str()); LOG_DEBUG("playerbots", "Account {} created for random bots", accountName.c_str());
} }
if (account_creation) if (account_creation)
{ {
LOG_INFO("playerbots", "Waiting for {} accounts loading into database ({} queries)...", account_creation, LoginDatabase.QueueSize()); /* wait for async accounts create to make character create correctly, same as account delete */
/* wait for async accounts create to make character create correctly */ LOG_INFO("playerbots", "Waiting for {} accounts loading into database...", account_creation);
std::this_thread::sleep_for(10ms * sPlayerbotAIConfig->randomBotAccountCount);
while (LoginDatabase.QueueSize())
{
std::this_thread::sleep_for(1s);
}
LOG_INFO("playerbots", ">> {} Accounts loaded into database in {} ms", account_creation, GetMSTimeDiffToNow(timer));
} }
LOG_INFO("playerbots", "Creating random bot characters..."); LOG_INFO("playerbots", "Creating random bot characters...");
uint32 totalRandomBotChars = 0; uint32 totalRandomBotChars = 0;
uint32 totalCharCount = sPlayerbotAIConfig->randomBotAccountCount * 10;
std::vector<std::pair<Player*, uint32>> playerBots; std::vector<std::pair<Player*, uint32>> playerBots;
std::vector<WorldSession*> sessionBots; std::vector<WorldSession*> sessionBots;
int bot_creation = 0; int bot_creation = 0;
timer = getMSTime();
bool nameCached = false; for (uint32 accountNumber = 0; accountNumber < sPlayerbotAIConfig->randomBotAccountCount; ++accountNumber)
for (uint32 accountNumber = 0; accountNumber < totalAccountCount; ++accountNumber)
{ {
std::ostringstream out; std::ostringstream out;
out << sPlayerbotAIConfig->randomBotAccountPrefix << accountNumber; out << sPlayerbotAIConfig->randomBotAccountPrefix << accountNumber;
@@ -649,41 +501,12 @@ void RandomPlayerbotFactory::CreateRandomBots()
{ {
continue; continue;
} }
LOG_INFO("playerbots", "Creating random bot characters for account: [{}/{}]", accountNumber + 1,
if (!nameCached) sPlayerbotAIConfig->randomBotAccountCount);
{
nameCached = true;
LOG_INFO("playerbots", "Creating cache for names per gender and race...");
QueryResult result = CharacterDatabase.Query("SELECT name, gender FROM playerbots_names");
if (!result)
{
LOG_ERROR("playerbots", "No more unused names left");
return;
}
do
{
Field* fields = result->Fetch();
std::string name = fields[0].Get<std::string>();
NameRaceAndGender raceAndGender = static_cast<NameRaceAndGender>(fields[1].Get<uint8>());
if (sObjectMgr->CheckPlayerName(name) == CHAR_NAME_SUCCESS)
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHECK_NAME);
stmt->SetData(0, name);
if (PreparedQueryResult result = CharacterDatabase.Query(stmt))
continue;
nameCache[raceAndGender].push_back(name);
}
} while (result->NextRow());
}
LOG_DEBUG("playerbots", "Creating random bot characters for account: [{}/{}]", accountNumber + 1, totalAccountCount);
RandomPlayerbotFactory factory(accountId); RandomPlayerbotFactory factory(accountId);
WorldSession* session = new WorldSession(accountId, "", nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING, WorldSession* session = new WorldSession(accountId, "", nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING,
time_t(0), LOCALE_enUS, 0, false, false, 0, true); time_t(0), LOCALE_enUS, 0, false, false, 0, true);
sessionBots.push_back(session); sessionBots.push_back(session);
for (uint8 cls = CLASS_WARRIOR; cls < MAX_CLASSES - count; ++cls) for (uint8 cls = CLASS_WARRIOR; cls < MAX_CLASSES - count; ++cls)
@@ -720,13 +543,9 @@ void RandomPlayerbotFactory::CreateRandomBots()
if (bot_creation) if (bot_creation)
{ {
LOG_INFO("playerbots", "Waiting for {} characters loading into database ({} queries)...", bot_creation, CharacterDatabase.QueueSize()); LOG_INFO("playerbots", "Waiting for {} characters loading into database...", bot_creation);
/* wait for characters load into database, or characters will fail to loggin */ /* wait for characters load into database, or characters will fail to loggin */
while (CharacterDatabase.QueueSize()) std::this_thread::sleep_for(5s + bot_creation * 5ms);
{
std::this_thread::sleep_for(1s);
}
LOG_INFO("playerbots", ">> {} Characters loaded into database in {} ms", bot_creation, GetMSTimeDiffToNow(timer));
} }
for (WorldSession* session : sessionBots) for (WorldSession* session : sessionBots)
@@ -737,8 +556,8 @@ void RandomPlayerbotFactory::CreateRandomBots()
totalRandomBotChars += AccountMgr::GetCharactersCount(accountId); totalRandomBotChars += AccountMgr::GetCharactersCount(accountId);
} }
LOG_INFO("server.loading", ">> {} random bot accounts with {} characters available", LOG_INFO("server.loading", "{} random bot accounts with {} characters available",
sPlayerbotAIConfig->randomBotAccounts.size(), totalRandomBotChars); sPlayerbotAIConfig->randomBotAccounts.size(), totalRandomBotChars);
} }
void RandomPlayerbotFactory::CreateRandomGuilds() void RandomPlayerbotFactory::CreateRandomGuilds()
@@ -808,7 +627,7 @@ void RandomPlayerbotFactory::CreateRandomGuilds()
if (!player) if (!player)
{ {
LOG_ERROR("playerbots", "ObjectAccessor Cannot find player to set leader for guild {} . Skipped...", LOG_ERROR("playerbots", "ObjectAccessor Cannot find player to set leader for guild {} . Skipped...",
guildName.c_str()); guildName.c_str());
continue; continue;
} }
@@ -819,7 +638,7 @@ void RandomPlayerbotFactory::CreateRandomGuilds()
if (!guild->Create(player, guildName)) if (!guild->Create(player, guildName))
{ {
LOG_ERROR("playerbots", "Error creating guild [ {} ] with leader [ {} ]", guildName.c_str(), LOG_ERROR("playerbots", "Error creating guild [ {} ] with leader [ {} ]", guildName.c_str(),
player->GetName().c_str()); player->GetName().c_str());
delete guild; delete guild;
continue; continue;
} }
@@ -981,7 +800,7 @@ void RandomPlayerbotFactory::CreateRandomArenaTeams(ArenaType type, uint32 count
sPlayerbotAIConfig->randomBotArenaTeams.push_back(arenateam->GetId()); sPlayerbotAIConfig->randomBotArenaTeams.push_back(arenateam->GetId());
} }
LOG_DEBUG("playerbots", "{} random bot {}vs{} arena teams available", arenaTeamNumber, type, type); LOG_INFO("playerbots", "{} random bot arena teams available", arenaTeamNumber);
} }
std::string const RandomPlayerbotFactory::CreateRandomArenaTeamName() std::string const RandomPlayerbotFactory::CreateRandomArenaTeamName()

View File

@@ -54,8 +54,6 @@ public:
static void CreateRandomGuilds(); static void CreateRandomGuilds();
static void CreateRandomArenaTeams(ArenaType slot, uint32 count); static void CreateRandomArenaTeams(ArenaType slot, uint32 count);
static std::string const CreateRandomGuildName(); static std::string const CreateRandomGuildName();
static uint32 CalculateTotalAccountCount();
static uint32 CalculateAvailableCharsPerAccount();
private: private:
std::string const CreateRandomBotName(NameRaceAndGender raceAndGender); std::string const CreateRandomBotName(NameRaceAndGender raceAndGender);

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,6 @@
#ifndef _PLAYERBOT_RANDOMPLAYERBOTMGR_H #ifndef _PLAYERBOT_RANDOMPLAYERBOTMGR_H
#define _PLAYERBOT_RANDOMPLAYERBOTMGR_H #define _PLAYERBOT_RANDOMPLAYERBOTMGR_H
#include "NewRpgInfo.h"
#include "ObjectGuid.h" #include "ObjectGuid.h"
#include "PlayerbotMgr.h" #include "PlayerbotMgr.h"
@@ -112,8 +111,6 @@ public:
static bool HandlePlayerbotConsoleCommand(ChatHandler* handler, char const* args); static bool HandlePlayerbotConsoleCommand(ChatHandler* handler, char const* args);
bool IsRandomBot(Player* bot); bool IsRandomBot(Player* bot);
bool IsRandomBot(ObjectGuid::LowType bot); bool IsRandomBot(ObjectGuid::LowType bot);
bool IsAddclassBot(Player* bot);
bool IsAddclassBot(ObjectGuid::LowType bot);
void Randomize(Player* bot); void Randomize(Player* bot);
void Clear(Player* bot); void Clear(Player* bot);
void RandomizeFirst(Player* bot); void RandomizeFirst(Player* bot);
@@ -173,20 +170,7 @@ public:
static uint8 GetTeamClassIdx(bool isAlliance, uint8 claz) { return isAlliance * 20 + claz; } static uint8 GetTeamClassIdx(bool isAlliance, uint8 claz) { return isAlliance * 20 + claz; }
void PrepareAddclassCache(); void PrepareAddclassCache();
void PrepareZone2LevelBracket(); std::map<uint8, std::vector<ObjectGuid>> addclassCache;
void PrepareTeleportCache();
void Init();
std::map<uint8, std::unordered_set<ObjectGuid>> addclassCache;
std::map<uint8, std::vector<WorldLocation>> locsPerLevelCache;
std::map<uint8, std::vector<WorldLocation>> allianceStarterPerLevelCache;
std::map<uint8, std::vector<WorldLocation>> hordeStarterPerLevelCache;
struct LevelBracket {
uint32 low;
uint32 high;
bool InsideBracket(uint32 val) { return val >= low && val <= high; }
};
std::map<uint32, LevelBracket> zone2LevelBracket;
std::map<uint8, std::vector<WorldLocation>> bankerLocsPerLevelCache;
protected: protected:
void OnBotLoginInternal(Player* const bot) override; void OnBotLoginInternal(Player* const bot) override;
@@ -195,8 +179,6 @@ private:
botPID pid = botPID(1, 50, -50, 0, 0, 0); botPID pid = botPID(1, 50, -50, 0, 0, 0);
float activityMod = 0.25; float activityMod = 0.25;
bool _isBotInitializing = true; bool _isBotInitializing = true;
bool _isBotLogging = true;
NewRpgStatistic rpgStasticTotal;
uint32 GetEventValue(uint32 bot, std::string const event); uint32 GetEventValue(uint32 bot, std::string const event);
std::string const GetEventData(uint32 bot, std::string const event); std::string const GetEventData(uint32 bot, std::string const event);
uint32 SetEventValue(uint32 bot, std::string const event, uint32 value, uint32 validIn, uint32 SetEventValue(uint32 bot, std::string const event, uint32 value, uint32 validIn,
@@ -206,18 +188,19 @@ private:
time_t BgCheckTimer; time_t BgCheckTimer;
time_t LfgCheckTimer; time_t LfgCheckTimer;
time_t PlayersCheckTimer; time_t PlayersCheckTimer;
time_t printStatsTimer;
uint32 AddRandomBots(); uint32 AddRandomBots();
bool ProcessBot(uint32 bot); bool ProcessBot(uint32 bot);
void ScheduleRandomize(uint32 bot, uint32 time); void ScheduleRandomize(uint32 bot, uint32 time);
void RandomTeleport(Player* bot); void RandomTeleport(Player* bot);
void RandomTeleport(Player* bot, std::vector<WorldLocation>& locs, bool hearth = false); void RandomTeleport(Player* bot, std::vector<WorldLocation>& locs, bool hearth = false);
uint32 GetZoneLevel(uint16 mapId, float teleX, float teleY, float teleZ); uint32 GetZoneLevel(uint16 mapId, float teleX, float teleY, float teleZ);
void PrepareTeleportCache();
typedef void (RandomPlayerbotMgr::*ConsoleCommandHandler)(Player*); typedef void (RandomPlayerbotMgr::*ConsoleCommandHandler)(Player*);
std::vector<Player*> players; std::vector<Player*> players;
uint32 processTicks; uint32 processTicks;
std::map<uint8, std::vector<WorldLocation>> locsPerLevelCache;
std::map<uint8, std::vector<WorldLocation>> bankerLocsPerLevelCache;
// std::map<uint32, std::vector<WorldLocation>> rpgLocsCache; // std::map<uint32, std::vector<WorldLocation>> rpgLocsCache;
std::map<uint32, std::map<uint32, std::vector<WorldLocation>>> rpgLocsCacheLevel; std::map<uint32, std::map<uint32, std::vector<WorldLocation>>> rpgLocsCacheLevel;

View File

@@ -260,7 +260,7 @@ void TalentSpec::ReadTalents(Player* bot)
// Set the talent ranks to the ranks of the link. // Set the talent ranks to the ranks of the link.
void TalentSpec::ReadTalents(std::string const link) void TalentSpec::ReadTalents(std::string const link)
{ {
//uint32 rank = 0; //not used, line marked for removal. uint32 rank = 0;
uint32 pos = 0; uint32 pos = 0;
uint32 tab = 0; uint32 tab = 0;
std::string chr; std::string chr;
@@ -397,7 +397,7 @@ uint32 TalentSpec::highestTree()
std::string const TalentSpec::FormatSpec(Player* bot) std::string const TalentSpec::FormatSpec(Player* bot)
{ {
// uint8 cls = bot->getClass(); //not used, (used in lined 403), line marked for removal. uint8 cls = bot->getClass();
std::ostringstream out; std::ostringstream out;
// out << chathelper:: specs[cls][highestTree()] << " ("; // out << chathelper:: specs[cls][highestTree()] << " (";
@@ -468,7 +468,7 @@ bool TalentSpec::isEarlierVersionOf(TalentSpec& newSpec)
// Modifies current talents towards new talents up to a maxium of points. // Modifies current talents towards new talents up to a maxium of points.
void TalentSpec::ShiftTalents(TalentSpec* currentSpec, uint32 level) void TalentSpec::ShiftTalents(TalentSpec* currentSpec, uint32 level)
{ {
//uint32 currentPoints = currentSpec->GetTalentPoints(); //not used, line marked for removal. uint32 currentPoints = currentSpec->GetTalentPoints();
if (points >= LeveltoPoints(level)) // We have no more points to spend. Better reset and crop if (points >= LeveltoPoints(level)) // We have no more points to spend. Better reset and crop
{ {
CropTalents(level); CropTalents(level);

View File

@@ -22,26 +22,12 @@
WorldPosition::WorldPosition(std::string const str) WorldPosition::WorldPosition(std::string const str)
{ {
std::vector<std::string> tokens = split(str, '|'); std::stringstream out(str);
if (tokens.size() == 5) out >> this->m_mapId;
{ out >> this->m_positionX;
try out >> this->m_positionY;
{ out >> this->m_positionZ;
m_mapId = std::stoi(tokens[0]); out >> this->m_orientation;
m_positionX = std::stof(tokens[1]);
m_positionY = std::stof(tokens[2]);
m_positionZ = std::stof(tokens[3]);
m_orientation = std::stof(tokens[4]);
}
catch (const std::exception&)
{
m_mapId = 0;
m_positionX = 0.0f;
m_positionY = 0.0f;
m_positionZ = 0.0f;
m_orientation = 0.0f;
}
}
} }
WorldPosition::WorldPosition(uint32 mapId, const Position& pos) WorldPosition::WorldPosition(uint32 mapId, const Position& pos)
@@ -375,25 +361,13 @@ std::string const WorldPosition::print()
std::string const WorldPosition::to_string() std::string const WorldPosition::to_string()
{ {
std::stringstream out; std::stringstream out;
out << m_mapId << '|'; out << GetMapId();
out << m_positionX << '|'; out << GetPositionX();
out << m_positionY << '|'; out << GetPositionY();
out << m_positionZ << '|'; out << GetPositionZ();
out << m_orientation; out << GetOrientation();
return out.str(); return out.str();
} };
std::vector<std::string> WorldPosition::split(const std::string& s, char delimiter)
{
std::vector<std::string> tokens;
std::string token;
std::istringstream tokenStream(s);
while (std::getline(tokenStream, token, delimiter))
{
tokens.push_back(token);
}
return tokens;
}
void WorldPosition::printWKT(std::vector<WorldPosition> points, std::ostringstream& out, uint32 dim, bool loop) void WorldPosition::printWKT(std::vector<WorldPosition> points, std::ostringstream& out, uint32 dim, bool loop)
{ {
@@ -644,7 +618,7 @@ void WorldPosition::loadMapAndVMap(uint32 mapId, uint8 x, uint8 y)
{ {
// load VMAPs for current map/grid... // load VMAPs for current map/grid...
const MapEntry* i_mapEntry = sMapStore.LookupEntry(mapId); const MapEntry* i_mapEntry = sMapStore.LookupEntry(mapId);
//const char* mapName = i_mapEntry ? i_mapEntry->name[sWorld->GetDefaultDbcLocale()] : "UNNAMEDMAP\x0"; //not used, (usage are commented out below), line marked for removal. const char* mapName = i_mapEntry ? i_mapEntry->name[sWorld->GetDefaultDbcLocale()] : "UNNAMEDMAP\x0";
int vmapLoadResult = VMAP::VMapFactory::createOrGetVMapMgr()->loadMap( int vmapLoadResult = VMAP::VMapFactory::createOrGetVMapMgr()->loadMap(
(sWorld->GetDataPath() + "vmaps").c_str(), mapId, x, y); (sWorld->GetDataPath() + "vmaps").c_str(), mapId, x, y);
@@ -1103,7 +1077,7 @@ bool QuestRelationTravelDestination::isActive(Player* bot)
if (!bot->GetMap()->GetEntry()->IsWorldMap() || !bot->CanTakeQuest(questTemplate, false)) if (!bot->GetMap()->GetEntry()->IsWorldMap() || !bot->CanTakeQuest(questTemplate, false))
return false; return false;
//uint32 dialogStatus = sTravelMgr->getDialogStatus(bot, entry, questTemplate); //not used, shadowed by the next declaration, line marked for removal. uint32 dialogStatus = sTravelMgr->getDialogStatus(bot, entry, questTemplate);
if (AI_VALUE(bool, "can fight equal")) if (AI_VALUE(bool, "can fight equal"))
{ {

View File

@@ -131,7 +131,6 @@ public:
std::string const print(); std::string const print();
std::string const to_string(); std::string const to_string();
std::vector<std::string> split(const std::string& s, char delimiter);
void printWKT(std::vector<WorldPosition> points, std::ostringstream& out, uint32 dim = 0, bool loop = false); void printWKT(std::vector<WorldPosition> points, std::ostringstream& out, uint32 dim = 0, bool loop = false);
void printWKT(std::ostringstream& out) { printWKT({*this}, out); } void printWKT(std::ostringstream& out) { printWKT({*this}, out); }

View File

@@ -594,7 +594,7 @@ bool TravelNode::isEqual(TravelNode* compareNode)
void TravelNode::print([[maybe_unused]] bool printFailed) void TravelNode::print([[maybe_unused]] bool printFailed)
{ {
// WorldPosition* startPosition = getPosition(); //not used, line marked for removal. WorldPosition* startPosition = getPosition();
uint32 mapSize = getNodeMap(true).size(); uint32 mapSize = getNodeMap(true).size();
@@ -1167,9 +1167,9 @@ std::vector<TravelNode*> TravelNodeMap::getNodes(WorldPosition pos, float range)
TravelNode* TravelNodeMap::getNode(WorldPosition pos, [[maybe_unused]] std::vector<WorldPosition>& ppath, Unit* bot, TravelNode* TravelNodeMap::getNode(WorldPosition pos, [[maybe_unused]] std::vector<WorldPosition>& ppath, Unit* bot,
float range) float range)
{ {
//float x = pos.getX(); //not used, line marked for removal. float x = pos.getX();
//float y = pos.getY(); //not used, line marked for removal. float y = pos.getY();
//float z = pos.getZ(); //not used, line marked for removal. float z = pos.getZ();
if (bot && !bot->GetMap()) if (bot && !bot->GetMap())
return nullptr; return nullptr;
@@ -1348,9 +1348,6 @@ TravelNodeRoute TravelNodeMap::getRoute(WorldPosition startPos, WorldPosition en
std::vector<WorldPosition> newStartPath; std::vector<WorldPosition> newStartPath;
std::vector<TravelNode*> startNodes = m_nodes, endNodes = m_nodes; std::vector<TravelNode*> startNodes = m_nodes, endNodes = m_nodes;
if(!startNodes.size() || !endNodes.size())
return TravelNodeRoute();
// Partial sort to get the closest 5 nodes at the begin of the array. // Partial sort to get the closest 5 nodes at the begin of the array.
std::partial_sort(startNodes.begin(), startNodes.begin() + 5, startNodes.end(), std::partial_sort(startNodes.begin(), startNodes.begin() + 5, startNodes.end(),
[startPos](TravelNode* i, TravelNode* j) { return i->fDist(startPos) < j->fDist(startPos); }); [startPos](TravelNode* i, TravelNode* j) { return i->fDist(startPos) < j->fDist(startPos); });
@@ -1498,7 +1495,7 @@ TravelNode* TravelNodeMap::addZoneLinkNode(TravelNode* startNode)
{ {
for (auto& path : *startNode->getPaths()) for (auto& path : *startNode->getPaths())
{ {
//TravelNode* endNode = path.first; //not used, line marked for removal. TravelNode* endNode = path.first;
std::string zoneName = startNode->getPosition()->getAreaName(true, true); std::string zoneName = startNode->getPosition()->getAreaName(true, true);
for (auto& pos : path.second.getPath()) for (auto& pos : path.second.getPath())
@@ -1641,7 +1638,7 @@ void TravelNodeMap::generateNpcNodes()
else if (cInfo->npcflag & UNIT_NPC_FLAG_SPIRITGUIDE) else if (cInfo->npcflag & UNIT_NPC_FLAG_SPIRITGUIDE)
nodeName += " spiritguide"; nodeName += " spiritguide";
/*TravelNode* node = */ sTravelNodeMap->addNode(guidP, nodeName, true, true); //node not used, fragment marked for removal. TravelNode* node = sTravelNodeMap->addNode(guidP, nodeName, true, true);
} }
else if (cInfo->rank == 3) else if (cInfo->rank == 3)
{ {
@@ -1757,7 +1754,7 @@ void TravelNodeMap::generateAreaTriggerNodes()
else else
nodeName = inPos.getAreaName(false) + " portal"; nodeName = inPos.getAreaName(false) + " portal";
//TravelNode* entryNode = sTravelNodeMap->getNode(outPos, nullptr, 20.0f); // Entry side, portal exit. //not used, line marked for removal. TravelNode* entryNode = sTravelNodeMap->getNode(outPos, nullptr, 20.0f); // Entry side, portal exit.
TravelNode* outNode = sTravelNodeMap->addNode(outPos, nodeName, true, true); // Exit size, portal exit. TravelNode* outNode = sTravelNodeMap->addNode(outPos, nodeName, true, true); // Exit size, portal exit.
@@ -1979,7 +1976,7 @@ void TravelNodeMap::generateZoneMeanNodes()
WorldPosition pos = WorldPosition(points, WP_MEAN_CENTROID); WorldPosition pos = WorldPosition(points, WP_MEAN_CENTROID);
/*TravelNode* node = */sTravelNodeMap->addNode(pos, pos.getAreaName(), true, true, false); //node not used, but addNode as side effect, fragment marked for removal. TravelNode* node = sTravelNodeMap->addNode(pos, pos.getAreaName(), true, true, false);
} }
} }
@@ -2120,7 +2117,7 @@ void TravelNodeMap::removeUselessPaths()
if (path.second.getComplete() && startNode->hasLinkTo(path.first)) if (path.second.getComplete() && startNode->hasLinkTo(path.first))
ASSERT(true); ASSERT(true);
} }
uint32 it = 0/*, rem = 0*/; //rem not used in this scope, (shadowing) fragment marked for removal. uint32 it = 0, rem = 0;
while (true) while (true)
{ {
uint32 rem = 0; uint32 rem = 0;
@@ -2210,7 +2207,7 @@ void TravelNodeMap::printMap()
std::vector<TravelNode*> anodes = getNodes(); std::vector<TravelNode*> anodes = getNodes();
//uint32 nr = 0; //not used, line marked for removal. uint32 nr = 0;
for (auto& node : anodes) for (auto& node : anodes)
{ {

View File

@@ -33,21 +33,12 @@ public:
static ChatCommandTable playerbotsDebugCommandTable = { static ChatCommandTable playerbotsDebugCommandTable = {
{"bg", HandleDebugBGCommand, SEC_GAMEMASTER, Console::Yes}, {"bg", HandleDebugBGCommand, SEC_GAMEMASTER, Console::Yes},
}; };
static ChatCommandTable playerbotsAccountCommandTable = {
{"setKey", HandleSetSecurityKeyCommand, SEC_PLAYER, Console::No},
{"link", HandleLinkAccountCommand, SEC_PLAYER, Console::No},
{"linkedAccounts", HandleViewLinkedAccountsCommand, SEC_PLAYER, Console::No},
{"unlink", HandleUnlinkAccountCommand, SEC_PLAYER, Console::No},
};
static ChatCommandTable playerbotsCommandTable = { static ChatCommandTable playerbotsCommandTable = {
{"bot", HandlePlayerbotCommand, SEC_PLAYER, Console::No}, {"bot", HandlePlayerbotCommand, SEC_PLAYER, Console::No},
{"gtask", HandleGuildTaskCommand, SEC_GAMEMASTER, Console::Yes}, {"gtask", HandleGuildTaskCommand, SEC_GAMEMASTER, Console::Yes},
{"pmon", HandlePerfMonCommand, SEC_GAMEMASTER, Console::Yes}, {"pmon", HandlePerfMonCommand, SEC_GAMEMASTER, Console::Yes},
{"rndbot", HandleRandomPlayerbotCommand, SEC_GAMEMASTER, Console::Yes}, {"rndbot", HandleRandomPlayerbotCommand, SEC_GAMEMASTER, Console::Yes},
{"debug", playerbotsDebugCommandTable}, {"debug", playerbotsDebugCommandTable},
{"account", playerbotsAccountCommandTable},
}; };
static ChatCommandTable commandTable = { static ChatCommandTable commandTable = {
@@ -110,103 +101,6 @@ public:
{ {
return BGTactics::HandleConsoleCommand(handler, args); return BGTactics::HandleConsoleCommand(handler, args);
} }
static bool HandleSetSecurityKeyCommand(ChatHandler* handler, char const* args)
{
if (!args || !*args)
{
handler->PSendSysMessage("Usage: .playerbots account setKey <securityKey>");
return false;
}
Player* player = handler->GetSession()->GetPlayer();
std::string key = args;
PlayerbotMgr* mgr = sPlayerbotsMgr->GetPlayerbotMgr(player);
if (mgr)
{
mgr->HandleSetSecurityKeyCommand(player, key);
return true;
}
else
{
handler->PSendSysMessage("PlayerbotMgr instance not found.");
return false;
}
}
static bool HandleLinkAccountCommand(ChatHandler* handler, char const* args)
{
if (!args || !*args)
return false;
char* accountName = strtok((char*)args, " ");
char* key = strtok(nullptr, " ");
if (!accountName || !key)
{
handler->PSendSysMessage("Usage: .playerbots account link <accountName> <securityKey>");
return false;
}
Player* player = handler->GetSession()->GetPlayer();
PlayerbotMgr* mgr = sPlayerbotsMgr->GetPlayerbotMgr(player);
if (mgr)
{
mgr->HandleLinkAccountCommand(player, accountName, key);
return true;
}
else
{
handler->PSendSysMessage("PlayerbotMgr instance not found.");
return false;
}
}
static bool HandleViewLinkedAccountsCommand(ChatHandler* handler, char const* /*args*/)
{
Player* player = handler->GetSession()->GetPlayer();
PlayerbotMgr* mgr = sPlayerbotsMgr->GetPlayerbotMgr(player);
if (mgr)
{
mgr->HandleViewLinkedAccountsCommand(player);
return true;
}
else
{
handler->PSendSysMessage("PlayerbotMgr instance not found.");
return false;
}
}
static bool HandleUnlinkAccountCommand(ChatHandler* handler, char const* args)
{
if (!args || !*args)
return false;
char* accountName = strtok((char*)args, " ");
if (!accountName)
{
handler->PSendSysMessage("Usage: .playerbots account unlink <accountName>");
return false;
}
Player* player = handler->GetSession()->GetPlayer();
PlayerbotMgr* mgr = sPlayerbotsMgr->GetPlayerbotMgr(player);
if (mgr)
{
mgr->HandleUnlinkAccountCommand(player, accountName);
return true;
}
else
{
handler->PSendSysMessage("PlayerbotMgr instance not found.");
return false;
}
}
}; };
void AddSC_playerbots_commandscript() { new playerbots_commandscript(); } void AddSC_playerbots_commandscript() { new playerbots_commandscript(); }

View File

@@ -1,134 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#include "Queue.h"
#include "AiObjectContext.h"
#include "Log.h"
#include "PlayerbotAIConfig.h"
void Queue::Push(ActionBasket* action)
{
if (!action)
{
return;
}
for (ActionBasket* basket : actions)
{
if (action->getAction()->getName() == basket->getAction()->getName())
{
updateExistingBasket(basket, action);
return;
}
}
actions.push_back(action);
}
ActionNode* Queue::Pop()
{
ActionBasket* highestRelevanceBasket = findHighestRelevanceBasket();
if (!highestRelevanceBasket)
{
return nullptr;
}
return extractAndDeleteBasket(highestRelevanceBasket);
}
ActionBasket* Queue::Peek()
{
return findHighestRelevanceBasket();
}
uint32 Queue::Size()
{
return actions.size();
}
void Queue::RemoveExpired()
{
if (!sPlayerbotAIConfig->expireActionTime)
{
return;
}
std::list<ActionBasket*> expiredBaskets;
collectExpiredBaskets(expiredBaskets);
removeAndDeleteBaskets(expiredBaskets);
}
// Private helper methods
void Queue::updateExistingBasket(ActionBasket* existing, ActionBasket* newBasket)
{
if (existing->getRelevance() < newBasket->getRelevance())
{
existing->setRelevance(newBasket->getRelevance());
}
if (ActionNode* actionNode = newBasket->getAction())
{
delete actionNode;
}
delete newBasket;
}
ActionBasket* Queue::findHighestRelevanceBasket() const
{
if (actions.empty())
{
return nullptr;
}
float maxRelevance = -1.0f;
ActionBasket* selection = nullptr;
for (ActionBasket* basket : actions)
{
if (basket->getRelevance() > maxRelevance)
{
maxRelevance = basket->getRelevance();
selection = basket;
}
}
return selection;
}
ActionNode* Queue::extractAndDeleteBasket(ActionBasket* basket)
{
ActionNode* action = basket->getAction();
actions.remove(basket);
delete basket;
return action;
}
void Queue::collectExpiredBaskets(std::list<ActionBasket*>& expiredBaskets)
{
uint32 expiryTime = sPlayerbotAIConfig->expireActionTime;
for (ActionBasket* basket : actions)
{
if (basket->isExpired(expiryTime))
{
expiredBaskets.push_back(basket);
}
}
}
void Queue::removeAndDeleteBaskets(std::list<ActionBasket*>& basketsToRemove)
{
for (ActionBasket* basket : basketsToRemove)
{
actions.remove(basket);
if (ActionNode* action = basket->getAction())
{
delete action;
}
delete basket;
}
}

View File

@@ -1,94 +0,0 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#ifndef PLAYERBOT_QUEUE_H
#define PLAYERBOT_QUEUE_H
#include "Action.h"
#include "Common.h"
/**
* @class Queue
* @brief Manages a priority queue of actions for the playerbot system
*
* This queue maintains a list of ActionBasket objects, each containing an action
* and its relevance score. Actions with higher relevance scores are prioritized.
*/
class Queue
{
public:
Queue() = default;
~Queue() = default;
/**
* @brief Adds an action to the queue or updates existing action's relevance
* @param action Pointer to the ActionBasket to be added
*
* If an action with the same name exists, updates its relevance if the new
* relevance is higher, then deletes the new action. Otherwise, adds the new
* action to the queue.
*/
void Push(ActionBasket* action);
/**
* @brief Removes and returns the action with highest relevance
* @return Pointer to the highest relevance ActionNode, or nullptr if queue is empty
*
* Ownership of the returned ActionNode is transferred to the caller.
* The associated ActionBasket is deleted.
*/
ActionNode* Pop();
/**
* @brief Returns the action with highest relevance without removing it
* @return Pointer to the ActionBasket with highest relevance, or nullptr if queue is empty
*/
ActionBasket* Peek();
/**
* @brief Returns the current size of the queue
* @return Number of actions in the queue
*/
uint32 Size();
/**
* @brief Removes and deletes expired actions from the queue
*
* Uses sPlayerbotAIConfig->expireActionTime to determine if actions have expired.
* Both the ActionNode and ActionBasket are deleted for expired actions.
*/
void RemoveExpired();
private:
/**
* @brief Updates existing basket with new relevance and cleans up new basket
*/
void updateExistingBasket(ActionBasket* existing, ActionBasket* newBasket);
/**
* @brief Finds the basket with the highest relevance score
* @return Pointer to the highest relevance basket, or nullptr if queue is empty
*/
ActionBasket* findHighestRelevanceBasket() const;
/**
* @brief Extracts action from basket and handles basket cleanup
*/
ActionNode* extractAndDeleteBasket(ActionBasket* basket);
/**
* @brief Collects all expired baskets into the provided list
*/
void collectExpiredBaskets(std::list<ActionBasket*>& expiredBaskets);
/**
* @brief Removes and deletes all baskets in the provided list
*/
void removeAndDeleteBaskets(std::list<ActionBasket*>& basketsToRemove);
std::list<ActionBasket*> actions; /**< Container for action baskets */
};
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -122,7 +122,6 @@ public:
static void InitTalentsByParsedSpecLink(Player* bot, std::vector<std::vector<uint32>> parsedSpecLink, bool reset); static void InitTalentsByParsedSpecLink(Player* bot, std::vector<std::vector<uint32>> parsedSpecLink, bool reset);
void InitAvailableSpells(); void InitAvailableSpells();
void InitClassSpells(); void InitClassSpells();
void InitSpecialSpells();
void InitEquipment(bool incremental, bool second_chance = false); void InitEquipment(bool incremental, bool second_chance = false);
void InitPet(); void InitPet();
void InitAmmo(); void InitAmmo();
@@ -137,11 +136,7 @@ public:
void ApplyEnchantAndGemsNew(bool destoryOld = true); void ApplyEnchantAndGemsNew(bool destoryOld = true);
void InitInstanceQuests(); void InitInstanceQuests();
void UnbindInstance(); void UnbindInstance();
void InitKeyring();
void InitReputation();
void InitAttunementQuests();
void InitPotions();
private: private:
void Prepare(); void Prepare();
// void InitSecondEquipmentSet(); // void InitSecondEquipmentSet();
@@ -154,12 +149,14 @@ private:
void InitSpells(); void InitSpells();
void ClearSpells(); void ClearSpells();
void ClearSkills(); void ClearSkills();
void InitSpecialSpells();
void InitTalents(uint32 specNo); void InitTalents(uint32 specNo);
void InitTalentsByTemplate(uint32 specNo); void InitTalentsByTemplate(uint32 specNo);
void InitQuests(std::list<uint32>& questMap, bool withRewardItem = true); void InitQuests(std::list<uint32>& questMap);
void ClearInventory(); void ClearInventory();
void ClearAllItems(); void ClearAllItems();
void ResetQuests(); void ResetQuests();
void InitPotions();
std::vector<uint32> GetCurrentGemsCount(); std::vector<uint32> GetCurrentGemsCount();
bool CanEquipArmor(ItemTemplate const* proto); bool CanEquipArmor(ItemTemplate const* proto);
@@ -193,7 +190,7 @@ private:
uint32 itemQuality; uint32 itemQuality;
uint32 gearScoreLimit; uint32 gearScoreLimit;
static std::list<uint32> specialQuestIds; static std::list<uint32> specialQuestIds;
static std::unordered_map<uint32, std::vector<uint32>> trainerIdCache; std::vector<uint32> trainerIdCache;
static std::vector<uint32> enchantSpellIdCache; static std::vector<uint32> enchantSpellIdCache;
static std::vector<uint32> enchantGemIdCache; static std::vector<uint32> enchantGemIdCache;

View File

@@ -5,14 +5,11 @@
#include "DBCStores.h" #include "DBCStores.h"
#include "ItemTemplate.h" #include "ItemTemplate.h"
#include "ObjectMgr.h" #include "ObjectMgr.h"
#include "PlayerbotAI.h"
#include "PlayerbotAIAware.h"
#include "SharedDefines.h" #include "SharedDefines.h"
#include "SpellAuraDefines.h" #include "SpellAuraDefines.h"
#include "SpellInfo.h" #include "SpellInfo.h"
#include "SpellMgr.h" #include "SpellMgr.h"
#include "UpdateFields.h" #include "UpdateFields.h"
#include "Util.h"
StatsCollector::StatsCollector(CollectorType type, int32 cls) : type_(type), cls_(cls) { Reset(); } StatsCollector::StatsCollector(CollectorType type, int32 cls) : type_(type), cls_(cls) { Reset(); }
@@ -55,12 +52,12 @@ void StatsCollector::CollectItemStats(ItemTemplate const* proto)
CollectSpellStats(proto->Spells[j].SpellId, 1.0f, 0); CollectSpellStats(proto->Spells[j].SpellId, 1.0f, 0);
break; break;
case ITEM_SPELLTRIGGER_CHANCE_ON_HIT: case ITEM_SPELLTRIGGER_CHANCE_ON_HIT:
if (type_ & CollectorType::MELEE) if (type_ == CollectorType::MELEE)
{ {
if (proto->Spells[j].SpellPPMRate > 0.01f) if (proto->Spells[j].SpellPPMRate > 0.01f)
CollectSpellStats(proto->Spells[j].SpellId, 1.0f, 60000 / proto->Spells[j].SpellPPMRate); CollectSpellStats(proto->Spells[j].SpellId, 1.0f, 60000 / proto->Spells[j].SpellPPMRate);
else else
CollectSpellStats(proto->Spells[j].SpellId, 1.0f, 60000 / 1.8f); // Default PPM = 1.8 CollectSpellStats(proto->Spells[j].SpellId, 1.0f, 60000 / 1.8f); // Default PPM = 1.8
} }
break; break;
default: default:
@@ -70,7 +67,7 @@ void StatsCollector::CollectItemStats(ItemTemplate const* proto)
if (proto->socketBonus) if (proto->socketBonus)
{ {
if (const SpellItemEnchantmentEntry* enchant = sSpellItemEnchantmentStore.LookupEntry(proto->socketBonus)) if (const SpellItemEnchantmentEntry *enchant = sSpellItemEnchantmentStore.LookupEntry(proto->socketBonus))
CollectEnchantStats(enchant); CollectEnchantStats(enchant);
} }
} }
@@ -97,50 +94,34 @@ void StatsCollector::CollectSpellStats(uint32 spellId, float multiplier, int32 s
procFlags = eventEntry->procFlags; procFlags = eventEntry->procFlags;
else else
procFlags = spellInfo->ProcFlags; procFlags = spellInfo->ProcFlags;
if (eventEntry && eventEntry->customChance) if (eventEntry && eventEntry->customChance)
procChance = eventEntry->customChance; procChance = eventEntry->customChance;
else else
procChance = spellInfo->ProcChance; procChance = spellInfo->ProcChance;
bool lowChance = procChance <= 5; bool lowChance = procChance <= 5;
if (lowChance || (procFlags && !CanBeTriggeredByType(spellInfo, procFlags))) if (lowChance || (procFlags && !CanBeTriggeredByType(spellInfo, procFlags)))
canNextTrigger = false; canNextTrigger = false;
if (spellInfo->StackAmount) if (spellInfo->StackAmount)
{ {
// Heuristic multiplier for spell with stackAmount since high stackAmount may not be available // Heuristic multiplier for spell with stackAmount since high stackAmount may not be available
if (spellInfo->StackAmount <= 1) if (spellInfo->StackAmount <= 10)
multiplier *= spellInfo->StackAmount * 1; multiplier *= spellInfo->StackAmount * 0.6;
else if (spellInfo->StackAmount <= 5)
multiplier *= 1 + (spellInfo->StackAmount - 1) * 0.75;
else if (spellInfo->StackAmount <= 10)
multiplier *= 4 + (spellInfo->StackAmount - 5) * 0.6;
else if (spellInfo->StackAmount <= 20) else if (spellInfo->StackAmount <= 20)
multiplier *= 7 + (spellInfo->StackAmount - 10) * 0.4; multiplier *= 6 + (spellInfo->StackAmount - 10) * 0.4;
else else
multiplier *= 11; multiplier *= 10;
} }
for (int i = 0; i < MAX_SPELL_EFFECTS; i++) for (int i = 0; i < MAX_SPELL_EFFECTS; i++)
{ {
const SpellEffectInfo& effectInfo = spellInfo->Effects[i]; const SpellEffectInfo& effectInfo = spellInfo->Effects[i];
if (!effectInfo.Effect)
continue;
switch (effectInfo.Effect) switch (effectInfo.Effect)
{ {
case SPELL_EFFECT_APPLY_AURA: case SPELL_EFFECT_APPLY_AURA:
{ {
if (spellInfo->SpellFamilyName && /*effectInfo.ApplyAuraName != SPELL_AURA_DUMMY &&*/
effectInfo.ApplyAuraName != SPELL_AURA_PROC_TRIGGER_SPELL)
{
if (!CheckSpellValidation(spellInfo->SpellFamilyName, effectInfo.SpellClassMask))
return;
// Some dummy effects cannot be recognized, make some bonus to identify
stats[STATS_TYPE_BONUS] += 1;
}
/// @todo Handle negative spell /// @todo Handle negative spell
if (!spellInfo->IsPositive()) if (!spellInfo->IsPositive())
break; break;
@@ -149,8 +130,7 @@ void StatsCollector::CollectSpellStats(uint32 spellId, float multiplier, int32 s
if (spellCooldown <= 2000 || spellInfo->GetDuration() == -1) if (spellCooldown <= 2000 || spellInfo->GetDuration() == -1)
coverage = 1.0f; coverage = 1.0f;
else else
coverage = coverage = std::min(1.0f, (float)spellInfo->GetDuration() / (spellInfo->GetDuration() + spellCooldown));
std::min(1.0f, (float)spellInfo->GetDuration() / (spellInfo->GetDuration() + spellCooldown));
multiplier *= coverage; multiplier *= coverage;
HandleApplyAura(effectInfo, multiplier, canNextTrigger, triggerCooldown); HandleApplyAura(effectInfo, multiplier, canNextTrigger, triggerCooldown);
@@ -187,12 +167,12 @@ void StatsCollector::CollectSpellStats(uint32 spellId, float multiplier, int32 s
break; break;
float normalizedCd = std::max((float)spellCooldown / 1000, 5.0f); float normalizedCd = std::max((float)spellCooldown / 1000, 5.0f);
int32 val = AverageValue(effectInfo); int32 val = AverageValue(effectInfo);
if (type_ & (CollectorType::MELEE | CollectorType::RANGED)) if (type_ == CollectorType::MELEE || type_ == CollectorType::RANGED)
{ {
float transfer_multiplier = 1; float transfer_multiplier = 1;
stats[STATS_TYPE_ATTACK_POWER] += (float)val / normalizedCd * multiplier * transfer_multiplier; stats[STATS_TYPE_ATTACK_POWER] += (float)val / normalizedCd * multiplier * transfer_multiplier;
} }
else if (type_ & CollectorType::SPELL_DMG) else if (type_ == CollectorType::SPELL_DMG)
{ {
float transfer_multiplier = 0.5; float transfer_multiplier = 0.5;
stats[STATS_TYPE_SPELL_POWER] += (float)val / normalizedCd * multiplier * transfer_multiplier; stats[STATS_TYPE_SPELL_POWER] += (float)val / normalizedCd * multiplier * transfer_multiplier;
@@ -220,7 +200,7 @@ void StatsCollector::CollectEnchantStats(SpellItemEnchantmentEntry const* enchan
{ {
case ITEM_ENCHANTMENT_TYPE_COMBAT_SPELL: case ITEM_ENCHANTMENT_TYPE_COMBAT_SPELL:
{ {
if (type_ & CollectorType::MELEE) if (type_ == CollectorType::MELEE)
CollectSpellStats(enchant_spell_id, 0.25f); CollectSpellStats(enchant_spell_id, 0.25f);
break; break;
} }
@@ -245,60 +225,55 @@ void StatsCollector::CollectEnchantStats(SpellItemEnchantmentEntry const* enchan
} }
/// @todo Special case for some spell that hard to calculate, like trinket, relic, etc. /// @todo Special case for some spell that hard to calculate, like trinket, relic, etc.
bool StatsCollector::SpecialSpellFilter(uint32 spellId) bool StatsCollector::SpecialSpellFilter(uint32 spellId) {
{
// trinket // trinket
switch (spellId) switch (spellId)
{ {
case 60764: // Totem of Splintering case 27521: // Insightful Earthstorm Diamond
if (type_ & (CollectorType::SPELL))
return true;
break;
case 27521: // Insightful Earthstorm Diamond
stats[STATS_TYPE_MANA_REGENERATION] += 20; stats[STATS_TYPE_MANA_REGENERATION] += 20;
return true; return true;
case 55381: // Insightful Earthsiege Diamond case 55381: // Insightful Earthsiege Diamond
stats[STATS_TYPE_MANA_REGENERATION] += 40; stats[STATS_TYPE_MANA_REGENERATION] += 40;
return true; return true;
case 39442: // Darkmoon Card: Wrath case 39442: // Darkmoon Card: Wrath
if (!(type_ & CollectorType::SPELL_HEAL)) if (type_ != CollectorType::SPELL_HEAL)
stats[STATS_TYPE_CRIT] += 50; stats[STATS_TYPE_CRIT] += 50;
return true; return true;
case 59620: // Berserk case 59620: // Berserk
if (type_ & CollectorType::MELEE) if (type_ == CollectorType::MELEE)
stats[STATS_TYPE_ATTACK_POWER] += 120; stats[STATS_TYPE_ATTACK_POWER] += 120;
return true; return true;
case 67702: // Death's Verdict case 67702: // Death's Verdict
stats[STATS_TYPE_ATTACK_POWER] += 225; stats[STATS_TYPE_ATTACK_POWER] += 225;
return true; return true;
case 67771: // Death's Verdict (heroic) case 67771: // Death's Verdict (heroic)
stats[STATS_TYPE_ATTACK_POWER] += 260; stats[STATS_TYPE_ATTACK_POWER] += 260;
return true; return true;
case 71406: // Tiny Abomination in a Jar case 71406: // Tiny Abomination in a Jar
if (cls_ == CLASS_PALADIN) if (cls_ == CLASS_PALADIN)
stats[STATS_TYPE_ATTACK_POWER] += 600; stats[STATS_TYPE_ATTACK_POWER] += 600;
else else
stats[STATS_TYPE_ATTACK_POWER] += 150; stats[STATS_TYPE_ATTACK_POWER] += 150;
return true; return true;
case 71545: // Tiny Abomination in a Jar (heroic) case 71545: // Tiny Abomination in a Jar (heroic)
if (cls_ == CLASS_PALADIN) if (cls_ == CLASS_PALADIN)
stats[STATS_TYPE_ATTACK_POWER] += 800; stats[STATS_TYPE_ATTACK_POWER] += 800;
else else
stats[STATS_TYPE_ATTACK_POWER] += 200; stats[STATS_TYPE_ATTACK_POWER] += 200;
return true; return true;
case 71519: // Deathbringer's Will case 71519: // Deathbringer's Will
stats[STATS_TYPE_ATTACK_POWER] += 350; stats[STATS_TYPE_ATTACK_POWER] += 350;
return true; return true;
case 71562: // Deathbringer's Will (heroic) case 71562: // Deathbringer's Will (heroic)
stats[STATS_TYPE_ATTACK_POWER] += 400; stats[STATS_TYPE_ATTACK_POWER] += 400;
return true; return true;
case 71602: // Dislodged Foreign Object case 71602: // Dislodged Foreign Object
/// @todo The item can be triggered by heal spell, which mismatch with it's description /// @todo The item can be triggered by heal spell, which mismatch with it's description
/// Noticing that heroic item can not be triggered, probably a bug to report to AC /// Noticing that heroic item can not be triggered, probably a bug to report to AC
if (type_ & CollectorType::SPELL_HEAL) if (type_ == CollectorType::SPELL_HEAL)
return true; return true;
break; break;
case 71903: // Shadowmourne case 71903: // Shadowmourne
stats[STATS_TYPE_STRENGTH] += 200; stats[STATS_TYPE_STRENGTH] += 200;
return true; return true;
default: default:
@@ -320,26 +295,26 @@ bool StatsCollector::SpecialEnchantFilter(uint32 enchantSpellId)
switch (enchantSpellId) switch (enchantSpellId)
{ {
case 64440: case 64440:
if (type_ & CollectorType::MELEE) if (type_ == CollectorType::MELEE)
{ {
stats[STATS_TYPE_PARRY] += 50; stats[STATS_TYPE_PARRY] += 50;
} }
return true; return true;
case 53365: // Rune of the Fallen Crusader case 53365: // Rune of the Fallen Crusader
if (type_ & CollectorType::MELEE) if (type_ == CollectorType::MELEE)
{ {
stats[STATS_TYPE_STRENGTH] += 75; stats[STATS_TYPE_STRENGTH] += 75;
} }
return true; return true;
case 62157: // Rune of the Stoneskin Gargoyle case 62157: // Rune of the Stoneskin Gargoyle
if (type_ & CollectorType::MELEE) if (type_ == CollectorType::MELEE)
{ {
stats[STATS_TYPE_DEFENSE] += 25; stats[STATS_TYPE_DEFENSE] += 25;
stats[STATS_TYPE_STAMINA] += 50; stats[STATS_TYPE_STAMINA] += 50;
} }
return true; return true;
case 64571: // Blood draining case 64571: // Blood draining
if (type_ & CollectorType::MELEE) if (type_ == CollectorType::MELEE)
{ {
stats[STATS_TYPE_STAMINA] += 50; stats[STATS_TYPE_STAMINA] += 50;
} }
@@ -350,34 +325,18 @@ bool StatsCollector::SpecialEnchantFilter(uint32 enchantSpellId)
return false; return false;
} }
bool StatsCollector::CanBeTriggeredByType(SpellInfo const* spellInfo, uint32 procFlags, bool strict) bool StatsCollector::CanBeTriggeredByType(SpellInfo const* spellInfo, uint32 procFlags)
{ {
const SpellProcEventEntry* eventEntry = sSpellMgr->GetSpellProcEvent(spellInfo->Id); const SpellProcEventEntry* eventEntry = sSpellMgr->GetSpellProcEvent(spellInfo->Id);
uint32 spellFamilyName = 0; uint32 spellFamilyName = eventEntry ? eventEntry->spellFamilyName : 0;
if (eventEntry)
{
spellFamilyName = eventEntry->spellFamilyName;
flag96 spellFamilyMask = eventEntry->spellFamilyMask;
if (spellFamilyName != 0)
{
if (!CheckSpellValidation(spellFamilyName, spellFamilyMask, strict))
return false;
}
}
uint32 triggerMask = TAKEN_HIT_PROC_FLAG_MASK; // Generic trigger mask if (spellFamilyName != 0)
switch (type_) /// @todo Check specific trigger spell by spellFamilyMask
{ return true;
case CollectorType::MELEE_DMG:
{ uint32 triggerMask = TAKEN_HIT_PROC_FLAG_MASK; // Generic trigger mask
triggerMask |= MELEE_PROC_FLAG_MASK; switch (type_) {
triggerMask |= SPELL_PROC_FLAG_MASK; case CollectorType::MELEE:
triggerMask |= PROC_FLAG_DONE_PERIODIC;
if (procFlags & triggerMask)
return true;
break;
}
case CollectorType::MELEE_TANK:
{ {
triggerMask |= MELEE_PROC_FLAG_MASK; triggerMask |= MELEE_PROC_FLAG_MASK;
triggerMask |= SPELL_PROC_FLAG_MASK; triggerMask |= SPELL_PROC_FLAG_MASK;
@@ -390,7 +349,7 @@ bool StatsCollector::CanBeTriggeredByType(SpellInfo const* spellInfo, uint32 pro
{ {
triggerMask |= RANGED_PROC_FLAG_MASK; triggerMask |= RANGED_PROC_FLAG_MASK;
triggerMask |= SPELL_PROC_FLAG_MASK; triggerMask |= SPELL_PROC_FLAG_MASK;
triggerMask |= PROC_FLAG_DONE_PERIODIC; triggerMask |= PERIODIC_PROC_FLAG_MASK;
if (procFlags & triggerMask) if (procFlags & triggerMask)
return true; return true;
break; break;
@@ -398,7 +357,6 @@ bool StatsCollector::CanBeTriggeredByType(SpellInfo const* spellInfo, uint32 pro
case CollectorType::SPELL_DMG: case CollectorType::SPELL_DMG:
{ {
triggerMask |= SPELL_PROC_FLAG_MASK; triggerMask |= SPELL_PROC_FLAG_MASK;
triggerMask |= PROC_FLAG_DONE_PERIODIC;
// Healing spell cannot trigger // Healing spell cannot trigger
triggerMask &= ~PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS; triggerMask &= ~PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS;
triggerMask &= ~PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS; triggerMask &= ~PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS;
@@ -409,13 +367,10 @@ bool StatsCollector::CanBeTriggeredByType(SpellInfo const* spellInfo, uint32 pro
case CollectorType::SPELL_HEAL: case CollectorType::SPELL_HEAL:
{ {
triggerMask |= SPELL_PROC_FLAG_MASK; triggerMask |= SPELL_PROC_FLAG_MASK;
triggerMask |= PROC_FLAG_DONE_PERIODIC;
// Dmg spell should not trigger // Dmg spell should not trigger
triggerMask &= ~PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_NEG; triggerMask &= ~PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_NEG;
triggerMask &= ~PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG; triggerMask &= ~PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG;
if (!spellFamilyName) triggerMask &= ~PROC_FLAG_DONE_PERIODIC; // spellFamilyName = 0 and PROC_FLAG_DONE_PERIODIC -> it is a dmg spell
triggerMask &=
~PROC_FLAG_DONE_PERIODIC; // spellFamilyName = 0 and PROC_FLAG_DONE_PERIODIC -> it is a dmg spell
if (procFlags & triggerMask) if (procFlags & triggerMask)
return true; return true;
break; break;
@@ -464,39 +419,39 @@ void StatsCollector::CollectByItemStatType(uint32 itemStatType, int32 val)
stats[STATS_TYPE_BLOCK_RATING] += val; stats[STATS_TYPE_BLOCK_RATING] += val;
break; break;
case ITEM_MOD_HIT_MELEE_RATING: case ITEM_MOD_HIT_MELEE_RATING:
if (type_ & CollectorType::MELEE) if (type_ == CollectorType::MELEE)
stats[STATS_TYPE_HIT] += val; stats[STATS_TYPE_HIT] += val;
break; break;
case ITEM_MOD_HIT_RANGED_RATING: case ITEM_MOD_HIT_RANGED_RATING:
if (type_ & CollectorType::RANGED) if (type_ == CollectorType::RANGED)
stats[STATS_TYPE_HIT] += val; stats[STATS_TYPE_HIT] += val;
break; break;
case ITEM_MOD_HIT_SPELL_RATING: case ITEM_MOD_HIT_SPELL_RATING:
if (type_ & CollectorType::SPELL) if (type_ == CollectorType::SPELL)
stats[STATS_TYPE_HIT] += val; stats[STATS_TYPE_HIT] += val;
break; break;
case ITEM_MOD_CRIT_MELEE_RATING: case ITEM_MOD_CRIT_MELEE_RATING:
if (type_ & CollectorType::MELEE) if (type_ == CollectorType::MELEE)
stats[STATS_TYPE_CRIT] += val; stats[STATS_TYPE_CRIT] += val;
break; break;
case ITEM_MOD_CRIT_RANGED_RATING: case ITEM_MOD_CRIT_RANGED_RATING:
if (type_ & CollectorType::RANGED) if (type_ == CollectorType::RANGED)
stats[STATS_TYPE_CRIT] += val; stats[STATS_TYPE_CRIT] += val;
break; break;
case ITEM_MOD_CRIT_SPELL_RATING: case ITEM_MOD_CRIT_SPELL_RATING:
if (type_ & CollectorType::SPELL) if (type_ == CollectorType::SPELL)
stats[STATS_TYPE_CRIT] += val; stats[STATS_TYPE_CRIT] += val;
break; break;
case ITEM_MOD_HASTE_MELEE_RATING: case ITEM_MOD_HASTE_MELEE_RATING:
if (type_ & CollectorType::MELEE) if (type_ == CollectorType::MELEE)
stats[STATS_TYPE_HASTE] += val; stats[STATS_TYPE_HASTE] += val;
break; break;
case ITEM_MOD_HASTE_RANGED_RATING: case ITEM_MOD_HASTE_RANGED_RATING:
if (type_ & CollectorType::RANGED) if (type_ == CollectorType::RANGED)
stats[STATS_TYPE_HASTE] += val; stats[STATS_TYPE_HASTE] += val;
break; break;
case ITEM_MOD_HASTE_SPELL_RATING: case ITEM_MOD_HASTE_SPELL_RATING:
if (type_ & CollectorType::SPELL) if (type_ == CollectorType::SPELL)
stats[STATS_TYPE_HASTE] += val; stats[STATS_TYPE_HASTE] += val;
break; break;
case ITEM_MOD_HIT_RATING: case ITEM_MOD_HIT_RATING:
@@ -547,14 +502,13 @@ void StatsCollector::CollectByItemStatType(uint32 itemStatType, int32 val)
} }
} }
void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float multiplier, bool canNextTrigger, void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float multiplier, bool canNextTrigger, uint32 triggerCooldown)
uint32 triggerCooldown)
{ {
if (effectInfo.Effect != SPELL_EFFECT_APPLY_AURA) if (effectInfo.Effect != SPELL_EFFECT_APPLY_AURA)
return; return;
int32 val = AverageValue(effectInfo); int32 val = AverageValue(effectInfo);
switch (effectInfo.ApplyAuraName) switch (effectInfo.ApplyAuraName)
{ {
case SPELL_AURA_MOD_DAMAGE_DONE: case SPELL_AURA_MOD_DAMAGE_DONE:
@@ -580,11 +534,11 @@ void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float mu
break; break;
} }
case SPELL_AURA_MOD_ATTACK_POWER: case SPELL_AURA_MOD_ATTACK_POWER:
if (type_ & CollectorType::MELEE) if (type_ == CollectorType::MELEE)
stats[STATS_TYPE_ATTACK_POWER] += val * multiplier; stats[STATS_TYPE_ATTACK_POWER] += val * multiplier;
break; break;
case SPELL_AURA_MOD_RANGED_ATTACK_POWER: case SPELL_AURA_MOD_RANGED_ATTACK_POWER:
if (type_ & CollectorType::RANGED) if (type_ == CollectorType::RANGED)
stats[STATS_TYPE_ATTACK_POWER] += val * multiplier; stats[STATS_TYPE_ATTACK_POWER] += val * multiplier;
break; break;
case SPELL_AURA_MOD_SHIELD_BLOCKVALUE: case SPELL_AURA_MOD_SHIELD_BLOCKVALUE:
@@ -610,7 +564,7 @@ void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float mu
case STAT_SPIRIT: case STAT_SPIRIT:
stats[STATS_TYPE_SPIRIT] += val * multiplier; stats[STATS_TYPE_SPIRIT] += val * multiplier;
break; break;
case -1: // Stat all case -1: // Stat all
stats[STATS_TYPE_STRENGTH] += val * multiplier; stats[STATS_TYPE_STRENGTH] += val * multiplier;
stats[STATS_TYPE_AGILITY] += val * multiplier; stats[STATS_TYPE_AGILITY] += val * multiplier;
stats[STATS_TYPE_STAMINA] += val * multiplier; stats[STATS_TYPE_STAMINA] += val * multiplier;
@@ -625,7 +579,7 @@ void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float mu
case SPELL_AURA_MOD_RESISTANCE: case SPELL_AURA_MOD_RESISTANCE:
{ {
int32 statType = effectInfo.MiscValue; int32 statType = effectInfo.MiscValue;
if (statType & SPELL_SCHOOL_MASK_NORMAL) // physical if (statType & SPELL_SCHOOL_MASK_NORMAL) // physical
stats[STATS_TYPE_ARMOR] += val * multiplier; stats[STATS_TYPE_ARMOR] += val * multiplier;
break; break;
} }
@@ -650,39 +604,39 @@ void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float mu
stats[STATS_TYPE_BLOCK_RATING] += val * multiplier; stats[STATS_TYPE_BLOCK_RATING] += val * multiplier;
break; break;
case CR_HIT_MELEE: case CR_HIT_MELEE:
if (type_ & CollectorType::MELEE) if (type_ == CollectorType::MELEE)
stats[STATS_TYPE_HIT] += val * multiplier; stats[STATS_TYPE_HIT] += val * multiplier;
break; break;
case CR_HIT_RANGED: case CR_HIT_RANGED:
if (type_ & CollectorType::RANGED) if (type_ == CollectorType::RANGED)
stats[STATS_TYPE_HIT] += val * multiplier; stats[STATS_TYPE_HIT] += val * multiplier;
break; break;
case CR_HIT_SPELL: case CR_HIT_SPELL:
if (type_ & CollectorType::SPELL) if (type_ == CollectorType::SPELL)
stats[STATS_TYPE_HIT] += val * multiplier; stats[STATS_TYPE_HIT] += val * multiplier;
break; break;
case CR_CRIT_MELEE: case CR_CRIT_MELEE:
if (type_ & CollectorType::MELEE) if (type_ == CollectorType::MELEE)
stats[STATS_TYPE_CRIT] += val * multiplier; stats[STATS_TYPE_CRIT] += val * multiplier;
break; break;
case CR_CRIT_RANGED: case CR_CRIT_RANGED:
if (type_ & CollectorType::RANGED) if (type_ == CollectorType::RANGED)
stats[STATS_TYPE_CRIT] += val * multiplier; stats[STATS_TYPE_CRIT] += val * multiplier;
break; break;
case CR_CRIT_SPELL: case CR_CRIT_SPELL:
if (type_ & CollectorType::SPELL) if (type_ == CollectorType::SPELL)
stats[STATS_TYPE_CRIT] += val * multiplier; stats[STATS_TYPE_CRIT] += val * multiplier;
break; break;
case CR_HASTE_MELEE: case CR_HASTE_MELEE:
if (type_ & CollectorType::MELEE) if (type_ == CollectorType::MELEE)
stats[STATS_TYPE_HASTE] += val * multiplier; stats[STATS_TYPE_HASTE] += val * multiplier;
break; break;
case CR_HASTE_RANGED: case CR_HASTE_RANGED:
if (type_ & CollectorType::RANGED) if (type_ == CollectorType::RANGED)
stats[STATS_TYPE_HASTE] += val * multiplier; stats[STATS_TYPE_HASTE] += val * multiplier;
break; break;
case CR_HASTE_SPELL: case CR_HASTE_SPELL:
if (type_ & CollectorType::SPELL) if (type_ == CollectorType::SPELL)
stats[STATS_TYPE_HASTE] += val * multiplier; stats[STATS_TYPE_HASTE] += val * multiplier;
break; break;
case CR_EXPERTISE: case CR_EXPERTISE:
@@ -721,18 +675,12 @@ void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float mu
CollectSpellStats(effectInfo.TriggerSpell, multiplier, triggerCooldown); CollectSpellStats(effectInfo.TriggerSpell, multiplier, triggerCooldown);
break; break;
} }
case SPELL_AURA_ADD_TARGET_TRIGGER:
{
if (canNextTrigger)
CollectSpellStats(effectInfo.TriggerSpell, multiplier, triggerCooldown);
break;
}
case SPELL_AURA_MOD_CRIT_DAMAGE_BONUS: case SPELL_AURA_MOD_CRIT_DAMAGE_BONUS:
{ {
if (type_ != CollectorType::SPELL_HEAL) if (type_ != CollectorType::SPELL_HEAL)
{ {
int32 statType = effectInfo.MiscValue; int32 statType = effectInfo.MiscValue;
if (statType & SPELL_SCHOOL_MASK_NORMAL) // physical if (statType & SPELL_SCHOOL_MASK_NORMAL) // physical
stats[STATS_TYPE_CRIT] += 30 * val * multiplier; stats[STATS_TYPE_CRIT] += 30 * val * multiplier;
} }
break; break;
@@ -744,7 +692,7 @@ void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float mu
int32 StatsCollector::AverageValue(const SpellEffectInfo& effectInfo) int32 StatsCollector::AverageValue(const SpellEffectInfo& effectInfo)
{ {
//float basePointsPerLevel = effectInfo.RealPointsPerLevel; //not used, line marked for removal. float basePointsPerLevel = effectInfo.RealPointsPerLevel;
int32 basePoints = effectInfo.BasePoints; int32 basePoints = effectInfo.BasePoints;
int32 randomPoints = int32(effectInfo.DieSides); int32 randomPoints = int32(effectInfo.DieSides);
@@ -761,73 +709,4 @@ int32 StatsCollector::AverageValue(const SpellEffectInfo& effectInfo)
break; break;
} }
return basePoints; return basePoints;
} }
bool StatsCollector::CheckSpellValidation(uint32 spellFamilyName, flag96 spelFalimyFlags, bool strict)
{
if (PlayerbotAI::Class2SpellFamilyName(cls_) != spellFamilyName)
return false;
bool isHealingSpell = PlayerbotAI::IsHealingSpell(spellFamilyName, spelFalimyFlags);
// strict to healer
if (strict && (type_ & CollectorType::SPELL_HEAL))
{
return isHealingSpell;
}
if (!(type_ & CollectorType::SPELL_HEAL) && isHealingSpell)
return false;
// spells for caster/melee/tank are ambiguous
if (cls_ == CLASS_DRUID && spellFamilyName == SPELLFAMILY_DRUID && (type_ & CollectorType::MELEE))
{
uint32 castingFlagsA = 0x4 | 0x2 | 0x1 | 0x200000; // starfire | moonfire | wrath | insect swarm
uint32 castingFlagsB = 0x0;
uint32 castingFlagsC = 0x0;
flag96 invalidFlags = {castingFlagsA, castingFlagsB, castingFlagsC};
if (spelFalimyFlags & invalidFlags)
return false;
}
if (cls_ == CLASS_PALADIN && spellFamilyName == SPELLFAMILY_PALADIN && (type_ & CollectorType::MELEE_TANK))
{
uint32 retributionFlagsA = 0x0;
uint32 retributionFlagsB = 0x8000; // crusader strike
uint32 retributionFlagsC = 0x0;
flag96 invalidFlags = {retributionFlagsA, retributionFlagsB, retributionFlagsC};
if (spelFalimyFlags & invalidFlags)
return false;
}
if (cls_ == CLASS_PALADIN && spellFamilyName == SPELLFAMILY_PALADIN && (type_ & CollectorType::MELEE_DMG))
{
uint32 retributionFlagsA = 0x0;
uint32 retributionFlagsB = 0x100000 | 0x40; // shield of righteouness | holy shield
uint32 retributionFlagsC = 0x0;
flag96 invalidFlags = {retributionFlagsA, retributionFlagsB, retributionFlagsC};
if (spelFalimyFlags & invalidFlags)
return false;
}
if (cls_ == CLASS_SHAMAN && spellFamilyName == SPELLFAMILY_SHAMAN && (type_ & CollectorType::SPELL_DMG))
{
uint32 meleeFlagsA = 0x0;
uint32 meleeFlagsB = 0x1000010; // stromstrike
uint32 meleeFlagsC = 0x4; // lava lash
flag96 invalidFlags = {meleeFlagsA, meleeFlagsB, meleeFlagsC};
if (spelFalimyFlags & invalidFlags)
return false;
}
if (cls_ == CLASS_SHAMAN && spellFamilyName == SPELLFAMILY_SHAMAN && (type_ & CollectorType::MELEE_DMG))
{
uint32 casterFlagsA = 0x0;
uint32 casterFlagsB = 0x1000; // lava burst
uint32 casterFlagsC = 0x0;
flag96 invalidFlags = {casterFlagsA, casterFlagsB, casterFlagsC};
if (spelFalimyFlags & invalidFlags)
return false;
}
return true;
}

View File

@@ -42,19 +42,15 @@ enum StatsType : uint8
// Stats for weapon dps // Stats for weapon dps
STATS_TYPE_MELEE_DPS, STATS_TYPE_MELEE_DPS,
STATS_TYPE_RANGED_DPS, STATS_TYPE_RANGED_DPS,
// Bonus for unrecognized stats STATS_TYPE_MAX = 25
STATS_TYPE_BONUS,
STATS_TYPE_MAX = 26
}; };
enum CollectorType : uint8 enum CollectorType : uint8
{ {
MELEE_DMG = 1, MELEE = 1,
MELEE_TANK = 2, RANGED = 2,
RANGED = 4, SPELL_DMG = 4,
SPELL_DMG = 8, SPELL_HEAL = 8,
SPELL_HEAL = 16,
MELEE = MELEE_DMG | MELEE_TANK,
SPELL = SPELL_DMG | SPELL_HEAL SPELL = SPELL_DMG | SPELL_HEAL
}; };
@@ -67,21 +63,19 @@ public:
void CollectItemStats(ItemTemplate const* proto); void CollectItemStats(ItemTemplate const* proto);
void CollectSpellStats(uint32 spellId, float multiplier = 1.0f, int32 spellCooldown = -1); void CollectSpellStats(uint32 spellId, float multiplier = 1.0f, int32 spellCooldown = -1);
void CollectEnchantStats(SpellItemEnchantmentEntry const* enchant); void CollectEnchantStats(SpellItemEnchantmentEntry const* enchant);
bool CanBeTriggeredByType(SpellInfo const* spellInfo, uint32 procFlags, bool strict = true);
bool CheckSpellValidation(uint32 spellFamilyName, flag96 spelFalimyFlags, bool strict = true);
public: public:
int32 stats[STATS_TYPE_MAX]; int32 stats[STATS_TYPE_MAX];
private: private:
bool CanBeTriggeredByType(SpellInfo const* spellInfo, uint32 procFlags);
void CollectByItemStatType(uint32 itemStatType, int32 val); void CollectByItemStatType(uint32 itemStatType, int32 val);
bool SpecialSpellFilter(uint32 spellId); bool SpecialSpellFilter(uint32 spellId);
bool SpecialEnchantFilter(uint32 enchantSpellId); bool SpecialEnchantFilter(uint32 enchantSpellId);
void HandleApplyAura(const SpellEffectInfo& effectInfo, float multiplier, bool canNextTrigger, void HandleApplyAura(const SpellEffectInfo& effectInfo, float multiplier, bool canNextTrigger, uint32 triggerCooldown);
uint32 triggerCooldown);
int32 AverageValue(const SpellEffectInfo& effectInfo); int32 AverageValue(const SpellEffectInfo& effectInfo);
private: private:
CollectorType type_; CollectorType type_;
uint32 cls_; uint32 cls_;

View File

@@ -14,8 +14,6 @@
#include "PlayerbotAI.h" #include "PlayerbotAI.h"
#include "PlayerbotFactory.h" #include "PlayerbotFactory.h"
#include "SharedDefines.h" #include "SharedDefines.h"
#include "SpellAuraDefines.h"
#include "SpellMgr.h"
#include "StatsCollector.h" #include "StatsCollector.h"
#include "Unit.h" #include "Unit.h"
@@ -25,16 +23,15 @@ StatsWeightCalculator::StatsWeightCalculator(Player* player) : player_(player)
type_ = CollectorType::SPELL_HEAL; type_ = CollectorType::SPELL_HEAL;
else if (PlayerbotAI::IsCaster(player)) else if (PlayerbotAI::IsCaster(player))
type_ = CollectorType::SPELL_DMG; type_ = CollectorType::SPELL_DMG;
else if (PlayerbotAI::IsTank(player))
type_ = CollectorType::MELEE_TANK;
else if (PlayerbotAI::IsMelee(player)) else if (PlayerbotAI::IsMelee(player))
type_ = CollectorType::MELEE_DMG; type_ = CollectorType::MELEE;
else else
type_ = CollectorType::RANGED; type_ = CollectorType::RANGED;
cls = player->getClass(); cls = player->getClass();
tab = AiFactory::GetPlayerSpecTab(player); tab = AiFactory::GetPlayerSpecTab(player);
collector_ = std::make_unique<StatsCollector>(type_, cls); collector_ = std::make_unique<StatsCollector>(type_, cls);
if (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_UNHOLY) if (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_UNHOLY)
hitOverflowType_ = CollectorType::SPELL; hitOverflowType_ = CollectorType::SPELL;
else if (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT) else if (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT)
@@ -82,7 +79,7 @@ float StatsWeightCalculator::CalculateItem(uint32 itemId)
CalculateItemTypePenalty(proto); CalculateItemTypePenalty(proto);
if (enable_item_set_bonus_) if (enable_item_set_bonus_)
CalculateItemSetMod(player_, proto); CalculateItemSetBonus(player_, proto);
CalculateSocketBonus(player_, proto); CalculateSocketBonus(player_, proto);
@@ -126,10 +123,8 @@ void StatsWeightCalculator::GenerateWeights(Player* player)
void StatsWeightCalculator::GenerateBasicWeights(Player* player) void StatsWeightCalculator::GenerateBasicWeights(Player* player)
{ {
// Basic weights // Basic weights
stats_weights_[STATS_TYPE_STAMINA] += 0.1f; stats_weights_[STATS_TYPE_STAMINA] += 0.01f;
stats_weights_[STATS_TYPE_ARMOR] += 0.001f; stats_weights_[STATS_TYPE_ARMOR] += 0.001f;
stats_weights_[STATS_TYPE_BONUS] += 1.0f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 0.01f;
if (cls == CLASS_HUNTER && (tab == HUNTER_TAB_BEASTMASTER || tab == HUNTER_TAB_SURVIVAL)) if (cls == CLASS_HUNTER && (tab == HUNTER_TAB_BEASTMASTER || tab == HUNTER_TAB_SURVIVAL))
{ {
@@ -264,8 +259,8 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
stats_weights_[STATS_TYPE_MELEE_DPS] += 8.5f; stats_weights_[STATS_TYPE_MELEE_DPS] += 8.5f;
} }
else if (cls == CLASS_WARLOCK || (cls == CLASS_MAGE && tab != MAGE_TAB_FIRE) || else if (cls == CLASS_WARLOCK || (cls == CLASS_MAGE && tab != MAGE_TAB_FIRE) ||
(cls == CLASS_PRIEST && tab == PRIEST_TAB_SHADOW) || // shadow (cls == CLASS_PRIEST && tab == PRIEST_TAB_SHADOW) || // shadow
(cls == CLASS_DRUID && tab == DRUID_TAB_BALANCE)) // balance (cls == CLASS_DRUID && tab == DRUID_TAB_BALANCE)) // balance
{ {
stats_weights_[STATS_TYPE_INTELLECT] += 0.3f; stats_weights_[STATS_TYPE_INTELLECT] += 0.3f;
stats_weights_[STATS_TYPE_SPIRIT] += 0.6f; stats_weights_[STATS_TYPE_SPIRIT] += 0.6f;
@@ -294,24 +289,16 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
stats_weights_[STATS_TYPE_HASTE] += 1.0f; stats_weights_[STATS_TYPE_HASTE] += 1.0f;
} }
else if ((cls == CLASS_PALADIN && tab == PALADIN_TAB_HOLY) || // holy else if ((cls == CLASS_PALADIN && tab == PALADIN_TAB_HOLY) || // holy
(cls == CLASS_SHAMAN && tab == SHAMAN_TAB_RESTORATION)) // heal (cls == CLASS_PRIEST && tab != PRIEST_TAB_SHADOW) || // discipline / holy
{ (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_RESTORATION) || // heal
stats_weights_[STATS_TYPE_INTELLECT] += 0.9f;
stats_weights_[STATS_TYPE_SPIRIT] += 0.15f;
stats_weights_[STATS_TYPE_HEAL_POWER] += 1.0f;
stats_weights_[STATS_TYPE_MANA_REGENERATION] += 0.9f;
stats_weights_[STATS_TYPE_CRIT] += 0.6f;
stats_weights_[STATS_TYPE_HASTE] += 0.8f;
}
else if ((cls == CLASS_PRIEST && tab != PRIEST_TAB_SHADOW) || // discipline / holy
(cls == CLASS_DRUID && tab == DRUID_TAB_RESTORATION)) (cls == CLASS_DRUID && tab == DRUID_TAB_RESTORATION))
{ {
stats_weights_[STATS_TYPE_INTELLECT] += 0.8f; stats_weights_[STATS_TYPE_INTELLECT] += 0.8f;
stats_weights_[STATS_TYPE_SPIRIT] += 0.6f; stats_weights_[STATS_TYPE_SPIRIT] += 0.8f;
stats_weights_[STATS_TYPE_HEAL_POWER] += 1.0f; stats_weights_[STATS_TYPE_HEAL_POWER] += 1.0f;
stats_weights_[STATS_TYPE_MANA_REGENERATION] += 0.9f; stats_weights_[STATS_TYPE_MANA_REGENERATION] += 1.2f;
stats_weights_[STATS_TYPE_CRIT] += 0.6f; stats_weights_[STATS_TYPE_CRIT] += 0.7f;
stats_weights_[STATS_TYPE_HASTE] += 0.8f; stats_weights_[STATS_TYPE_HASTE] += 1.0f;
stats_weights_[STATS_TYPE_RANGED_DPS] += 1.0f; stats_weights_[STATS_TYPE_RANGED_DPS] += 1.0f;
} }
else if ((cls == CLASS_WARRIOR && tab == WARRIOR_TAB_PROTECTION) || else if ((cls == CLASS_WARRIOR && tab == WARRIOR_TAB_PROTECTION) ||
@@ -324,7 +311,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
stats_weights_[STATS_TYPE_DEFENSE] += 2.5f; stats_weights_[STATS_TYPE_DEFENSE] += 2.5f;
stats_weights_[STATS_TYPE_PARRY] += 2.0f; stats_weights_[STATS_TYPE_PARRY] += 2.0f;
stats_weights_[STATS_TYPE_DODGE] += 2.0f; stats_weights_[STATS_TYPE_DODGE] += 2.0f;
// stats_weights_[STATS_TYPE_RESILIENCE] += 2.0f; stats_weights_[STATS_TYPE_RESILIENCE] += 2.0f;
stats_weights_[STATS_TYPE_BLOCK_RATING] += 1.0f; stats_weights_[STATS_TYPE_BLOCK_RATING] += 1.0f;
stats_weights_[STATS_TYPE_BLOCK_VALUE] += 0.5f; stats_weights_[STATS_TYPE_BLOCK_VALUE] += 0.5f;
stats_weights_[STATS_TYPE_ARMOR] += 0.15f; stats_weights_[STATS_TYPE_ARMOR] += 0.15f;
@@ -343,7 +330,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
stats_weights_[STATS_TYPE_DEFENSE] += 3.5f; stats_weights_[STATS_TYPE_DEFENSE] += 3.5f;
stats_weights_[STATS_TYPE_PARRY] += 2.0f; stats_weights_[STATS_TYPE_PARRY] += 2.0f;
stats_weights_[STATS_TYPE_DODGE] += 2.0f; stats_weights_[STATS_TYPE_DODGE] += 2.0f;
// stats_weights_[STATS_TYPE_RESILIENCE] += 2.0f; stats_weights_[STATS_TYPE_RESILIENCE] += 2.0f;
stats_weights_[STATS_TYPE_ARMOR] += 0.15f; stats_weights_[STATS_TYPE_ARMOR] += 0.15f;
stats_weights_[STATS_TYPE_HIT] += 2.0f; stats_weights_[STATS_TYPE_HIT] += 2.0f;
stats_weights_[STATS_TYPE_CRIT] += 0.5f; stats_weights_[STATS_TYPE_CRIT] += 0.5f;
@@ -360,7 +347,7 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f; stats_weights_[STATS_TYPE_ATTACK_POWER] += 1.0f;
stats_weights_[STATS_TYPE_DEFENSE] += 0.3f; stats_weights_[STATS_TYPE_DEFENSE] += 0.3f;
stats_weights_[STATS_TYPE_DODGE] += 0.7f; stats_weights_[STATS_TYPE_DODGE] += 0.7f;
// stats_weights_[STATS_TYPE_RESILIENCE] += 1.0f; stats_weights_[STATS_TYPE_RESILIENCE] += 1.0f;
stats_weights_[STATS_TYPE_ARMOR] += 0.15f; stats_weights_[STATS_TYPE_ARMOR] += 0.15f;
stats_weights_[STATS_TYPE_HIT] += 3.0f; stats_weights_[STATS_TYPE_HIT] += 3.0f;
stats_weights_[STATS_TYPE_CRIT] += 1.3f; stats_weights_[STATS_TYPE_CRIT] += 1.3f;
@@ -393,7 +380,7 @@ void StatsWeightCalculator::GenerateAdditionalWeights(Player* player)
} }
} }
void StatsWeightCalculator::CalculateItemSetMod(Player* player, ItemTemplate const* proto) void StatsWeightCalculator::CalculateItemSetBonus(Player* player, ItemTemplate const* proto)
{ {
uint32 itemSet = proto->ItemSet; uint32 itemSet = proto->ItemSet;
if (!itemSet) if (!itemSet)
@@ -479,7 +466,7 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
// enhancement, rogue, ice dk, unholy dk, shield tank, fury warrior without titan's grip but with duel wield // enhancement, rogue, ice dk, unholy dk, shield tank, fury warrior without titan's grip but with duel wield
if (isDoubleHand && if (isDoubleHand &&
((cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && player_->CanDualWield()) || ((cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && player_->CanDualWield()) ||
(cls == CLASS_ROGUE) || (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_FROST) || (cls == CLASS_ROGUE) || (cls == CLASS_DEATH_KNIGHT && tab != DEATHKNIGHT_TAB_BLOOD) ||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !player_->CanTitanGrip() && player_->CanDualWield()) || (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && !player_->CanTitanGrip() && player_->CanDualWield()) ||
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_PROTECTION) || (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_PROTECTION) ||
(cls == CLASS_PALADIN && tab == PALADIN_TAB_PROTECTION))) (cls == CLASS_PALADIN && tab == PALADIN_TAB_PROTECTION)))
@@ -499,20 +486,21 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
weight_ *= 0.1; weight_ *= 0.1;
} }
// fury with titan's grip // fury with titan's grip
if ((!isDoubleHand || proto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM || if ((!isDoubleHand || proto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM || proto->SubClass == ITEM_SUBCLASS_WEAPON_STAFF) &&
proto->SubClass == ITEM_SUBCLASS_WEAPON_STAFF) &&
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && player_->CanTitanGrip())) (cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && player_->CanTitanGrip()))
{ {
weight_ *= 0.1; weight_ *= 0.1;
} }
}
if (proto->Class == ITEM_CLASS_WEAPON)
{
if (cls == CLASS_HUNTER && proto->SubClass == ITEM_SUBCLASS_WEAPON_THROWN) if (cls == CLASS_HUNTER && proto->SubClass == ITEM_SUBCLASS_WEAPON_THROWN)
{ {
weight_ *= 0.1; weight_ *= 0.1;
} }
if (cls == CLASS_ROGUE && (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY) && if (cls == CLASS_ROGUE && tab == ROGUE_TAB_ASSASSINATION && proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER)
proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER)
{ {
weight_ *= 0.5; weight_ *= 0.1;
} }
if (cls == CLASS_ROGUE && player_->HasAura(13964) && if (cls == CLASS_ROGUE && player_->HasAura(13964) &&
(proto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD || proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE)) (proto->SubClass == ITEM_SUBCLASS_WEAPON_SWORD || proto->SubClass == ITEM_SUBCLASS_WEAPON_AXE))
@@ -524,10 +512,6 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
{ {
weight_ *= 1.1; weight_ *= 1.1;
} }
if (cls == CLASS_DEATH_KNIGHT && player_->HasAura(50138) && !isDoubleHand)
{
weight_ *= 1.3;
}
bool slowDelay = proto->Delay > 2500; bool slowDelay = proto->Delay > 2500;
if (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && slowDelay) if (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && slowDelay)
weight_ *= 1.1; weight_ *= 1.1;
@@ -556,24 +540,19 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
{ {
float hit_current, hit_overflow; float hit_current, hit_overflow;
float validPoints; float validPoints;
if (hitOverflowType_ & CollectorType::SPELL) // m_modMeleeHitChance = (float)GetTotalAuraModifier(SPELL_AURA_MOD_HIT_CHANCE);
// m_modMeleeHitChance += GetRatingBonusValue(CR_HIT_MELEE);
if (hitOverflowType_ == CollectorType::SPELL)
{ {
hit_current = player->GetTotalAuraModifier(SPELL_AURA_MOD_SPELL_HIT_CHANCE); hit_current = player->GetTotalAuraModifier(SPELL_AURA_MOD_SPELL_HIT_CHANCE);
hit_current += player->GetTotalAuraModifier(SPELL_AURA_MOD_INCREASES_SPELL_PCT_TO_HIT); // suppression (18176)
hit_current += player->GetRatingBonusValue(CR_HIT_SPELL); hit_current += player->GetRatingBonusValue(CR_HIT_SPELL);
if (cls == CLASS_PRIEST && tab == PRIEST_TAB_SHADOW && player->HasAura(15835)) // Shadow Focus
hit_current += 3;
if (cls == CLASS_MAGE && tab == MAGE_TAB_ARCANE && player->HasAura(12840)) // Arcane Focus
hit_current += 3;
hit_overflow = SPELL_HIT_OVERFLOW; hit_overflow = SPELL_HIT_OVERFLOW;
if (hit_overflow > hit_current) if (hit_overflow > hit_current)
validPoints = (hit_overflow - hit_current) / player->GetRatingMultiplier(CR_HIT_SPELL); validPoints = (hit_overflow - hit_current) / player->GetRatingMultiplier(CR_HIT_SPELL);
else else
validPoints = 0; validPoints = 0;
} }
else if (hitOverflowType_ & CollectorType::MELEE) else if (hitOverflowType_ == CollectorType::MELEE)
{ {
hit_current = player->GetTotalAuraModifier(SPELL_AURA_MOD_HIT_CHANCE); hit_current = player->GetTotalAuraModifier(SPELL_AURA_MOD_HIT_CHANCE);
hit_current += player->GetRatingBonusValue(CR_HIT_MELEE); hit_current += player->GetRatingBonusValue(CR_HIT_MELEE);
@@ -597,7 +576,7 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
} }
{ {
if (type_ & CollectorType::MELEE) if (type_ == CollectorType::MELEE)
{ {
float expertise_current, expertise_overflow; float expertise_current, expertise_overflow;
expertise_current = player->GetUInt32Value(PLAYER_EXPERTISE); expertise_current = player->GetUInt32Value(PLAYER_EXPERTISE);
@@ -610,13 +589,12 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
else else
validPoints = 0; validPoints = 0;
collector_->stats[STATS_TYPE_EXPERTISE] = collector_->stats[STATS_TYPE_EXPERTISE] = std::min(collector_->stats[STATS_TYPE_EXPERTISE], (int)validPoints);
std::min(collector_->stats[STATS_TYPE_EXPERTISE], (int)validPoints);
} }
} }
{ {
if (type_ & CollectorType::MELEE) if (type_ == CollectorType::MELEE)
{ {
float defense_current, defense_overflow; float defense_current, defense_overflow;
defense_current = player->GetRatingBonusValue(CR_DEFENSE_SKILL); defense_current = player->GetRatingBonusValue(CR_DEFENSE_SKILL);
@@ -633,7 +611,7 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
} }
{ {
if (type_ & (CollectorType::MELEE | CollectorType::RANGED)) if (type_ == CollectorType::MELEE || type_ == CollectorType::RANGED)
{ {
float armor_penetration_current, armor_penetration_overflow; float armor_penetration_current, armor_penetration_overflow;
armor_penetration_current = player->GetRatingBonusValue(CR_ARMOR_PENETRATION); armor_penetration_current = player->GetRatingBonusValue(CR_ARMOR_PENETRATION);
@@ -641,13 +619,11 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
float validPoints; float validPoints;
if (armor_penetration_overflow > armor_penetration_current) if (armor_penetration_overflow > armor_penetration_current)
validPoints = (armor_penetration_overflow - armor_penetration_current) / validPoints = (armor_penetration_overflow - armor_penetration_current) / player->GetRatingMultiplier(CR_ARMOR_PENETRATION);
player->GetRatingMultiplier(CR_ARMOR_PENETRATION);
else else
validPoints = 0; validPoints = 0;
collector_->stats[STATS_TYPE_ARMOR_PENETRATION] = collector_->stats[STATS_TYPE_ARMOR_PENETRATION] = std::min(collector_->stats[STATS_TYPE_ARMOR_PENETRATION], (int)validPoints);
std::min(collector_->stats[STATS_TYPE_ARMOR_PENETRATION], (int)validPoints);
} }
} }
} }
@@ -655,9 +631,9 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
void StatsWeightCalculator::ApplyWeightFinetune(Player* player) void StatsWeightCalculator::ApplyWeightFinetune(Player* player)
{ {
{ {
if (type_ & (CollectorType::MELEE | CollectorType::RANGED)) if (type_ == CollectorType::MELEE || type_ == CollectorType::RANGED)
{ {
float armor_penetration_current/*, armor_penetration_overflow*/; //not used, line marked for removal. float armor_penetration_current, armor_penetration_overflow;
armor_penetration_current = player->GetRatingBonusValue(CR_ARMOR_PENETRATION); armor_penetration_current = player->GetRatingBonusValue(CR_ARMOR_PENETRATION);
if (armor_penetration_current > 50) if (armor_penetration_current > 50)
stats_weights_[STATS_TYPE_ARMOR_PENETRATION] *= 1.2f; stats_weights_[STATS_TYPE_ARMOR_PENETRATION] *= 1.2f;

View File

@@ -40,7 +40,7 @@ private:
void GenerateBasicWeights(Player* player); void GenerateBasicWeights(Player* player);
void GenerateAdditionalWeights(Player* player); void GenerateAdditionalWeights(Player* player);
void CalculateItemSetMod(Player* player, ItemTemplate const* proto); void CalculateItemSetBonus(Player* player, ItemTemplate const* proto);
void CalculateSocketBonus(Player* player, ItemTemplate const* proto); void CalculateSocketBonus(Player* player, ItemTemplate const* proto);
void CalculateItemTypePenalty(ItemTemplate const* proto); void CalculateItemTypePenalty(ItemTemplate const* proto);

View File

@@ -488,7 +488,7 @@ protected:
// node_name , action, prerequisite // node_name , action, prerequisite
#define ACTION_NODE_P(name, spell, pre) \ #define ACTION_NODE_P(name, spell, pre) \
static ActionNode* name([[maybe_unused]] PlayerbotAI* botAI) \ static ActionNode* name(PlayerbotAI* botAI) \
{ \ { \
return new ActionNode(spell, /*P*/ NextAction::array(0, new NextAction(pre), nullptr), /*A*/ nullptr, \ return new ActionNode(spell, /*P*/ NextAction::array(0, new NextAction(pre), nullptr), /*A*/ nullptr, \
/*C*/ nullptr); \ /*C*/ nullptr); \
@@ -496,7 +496,7 @@ protected:
// node_name , action, alternative // node_name , action, alternative
#define ACTION_NODE_A(name, spell, alt) \ #define ACTION_NODE_A(name, spell, alt) \
static ActionNode* name([[maybe_unused]] PlayerbotAI* botAI) \ static ActionNode* name(PlayerbotAI* botAI) \
{ \ { \
return new ActionNode(spell, /*P*/ nullptr, /*A*/ NextAction::array(0, new NextAction(alt), nullptr), \ return new ActionNode(spell, /*P*/ nullptr, /*A*/ NextAction::array(0, new NextAction(alt), nullptr), \
/*C*/ nullptr); \ /*C*/ nullptr); \
@@ -504,7 +504,7 @@ protected:
// node_name , action, continuer // node_name , action, continuer
#define ACTION_NODE_C(name, spell, con) \ #define ACTION_NODE_C(name, spell, con) \
static ActionNode* name([[maybe_unused]] PlayerbotAI* botAI) \ static ActionNode* name(PlayerbotAI* botAI) \
{ \ { \
return new ActionNode(spell, /*P*/ nullptr, /*A*/ nullptr, \ return new ActionNode(spell, /*P*/ nullptr, /*A*/ nullptr, \
/*C*/ NextAction::array(0, new NextAction(con), nullptr)); \ /*C*/ NextAction::array(0, new NextAction(con), nullptr)); \

View File

@@ -9,6 +9,8 @@
#include "ChatActionContext.h" #include "ChatActionContext.h"
#include "ChatTriggerContext.h" #include "ChatTriggerContext.h"
#include "Playerbots.h" #include "Playerbots.h"
#include "RaidIccActionContext.h"
#include "RaidIccTriggerContext.h"
#include "RaidUlduarTriggerContext.h" #include "RaidUlduarTriggerContext.h"
#include "RaidUlduarActionContext.h" #include "RaidUlduarActionContext.h"
#include "SharedValueContext.h" #include "SharedValueContext.h"
@@ -17,28 +19,18 @@
#include "ValueContext.h" #include "ValueContext.h"
#include "WorldPacketActionContext.h" #include "WorldPacketActionContext.h"
#include "WorldPacketTriggerContext.h" #include "WorldPacketTriggerContext.h"
#include "RaidStrategyContext.h" #include "raids/RaidStrategyContext.h"
#include "RaidBwlActionContext.h" #include "raids/blackwinglair/RaidBwlActionContext.h"
#include "RaidBwlTriggerContext.h" #include "raids/blackwinglair/RaidBwlTriggerContext.h"
#include "RaidNaxxActionContext.h" #include "raids/naxxramas/RaidNaxxActionContext.h"
#include "RaidNaxxTriggerContext.h" #include "raids/naxxramas/RaidNaxxTriggerContext.h"
#include "RaidIccActionContext.h" #include "raids/moltencore/RaidMcActionContext.h"
#include "RaidIccTriggerContext.h" #include "raids/moltencore/RaidMcTriggerContext.h"
#include "RaidOsActionContext.h" #include "raids/aq20/RaidAq20ActionContext.h"
#include "RaidOsTriggerContext.h" #include "raids/aq20/RaidAq20TriggerContext.h"
#include "RaidEoEActionContext.h" #include "dungeons/DungeonStrategyContext.h"
#include "RaidVoATriggerContext.h" #include "dungeons/wotlk/WotlkDungeonActionContext.h"
#include "RaidOnyxiaActionContext.h" #include "dungeons/wotlk/WotlkDungeonTriggerContext.h"
#include "RaidOnyxiaTriggerContext.h"
#include "RaidVoAActionContext.h"
#include "RaidEoETriggerContext.h"
#include "RaidMcActionContext.h"
#include "RaidMcTriggerContext.h"
#include "RaidAq20ActionContext.h"
#include "RaidAq20TriggerContext.h"
#include "DungeonStrategyContext.h"
#include "WotlkDungeonActionContext.h"
#include "WotlkDungeonTriggerContext.h"
AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI) AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
{ {
@@ -54,12 +46,8 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
actionContexts.Add(new WorldPacketActionContext()); actionContexts.Add(new WorldPacketActionContext());
actionContexts.Add(new RaidMcActionContext()); actionContexts.Add(new RaidMcActionContext());
actionContexts.Add(new RaidBwlActionContext()); actionContexts.Add(new RaidBwlActionContext());
actionContexts.Add(new RaidOnyxiaActionContext());
actionContexts.Add(new RaidAq20ActionContext()); actionContexts.Add(new RaidAq20ActionContext());
actionContexts.Add(new RaidNaxxActionContext()); actionContexts.Add(new RaidNaxxActionContext());
actionContexts.Add(new RaidOsActionContext());
actionContexts.Add(new RaidEoEActionContext());
actionContexts.Add(new RaidVoAActionContext());
actionContexts.Add(new RaidUlduarActionContext()); actionContexts.Add(new RaidUlduarActionContext());
actionContexts.Add(new RaidIccActionContext()); actionContexts.Add(new RaidIccActionContext());
actionContexts.Add(new WotlkDungeonUKActionContext()); actionContexts.Add(new WotlkDungeonUKActionContext());
@@ -75,19 +63,14 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
actionContexts.Add(new WotlkDungeonUPActionContext()); actionContexts.Add(new WotlkDungeonUPActionContext());
actionContexts.Add(new WotlkDungeonCoSActionContext()); actionContexts.Add(new WotlkDungeonCoSActionContext());
actionContexts.Add(new WotlkDungeonFoSActionContext()); actionContexts.Add(new WotlkDungeonFoSActionContext());
actionContexts.Add(new WotlkDungeonToCActionContext());
triggerContexts.Add(new TriggerContext()); triggerContexts.Add(new TriggerContext());
triggerContexts.Add(new ChatTriggerContext()); triggerContexts.Add(new ChatTriggerContext());
triggerContexts.Add(new WorldPacketTriggerContext()); triggerContexts.Add(new WorldPacketTriggerContext());
triggerContexts.Add(new RaidMcTriggerContext()); triggerContexts.Add(new RaidMcTriggerContext());
triggerContexts.Add(new RaidBwlTriggerContext()); triggerContexts.Add(new RaidBwlTriggerContext());
triggerContexts.Add(new RaidOnyxiaTriggerContext());
triggerContexts.Add(new RaidAq20TriggerContext()); triggerContexts.Add(new RaidAq20TriggerContext());
triggerContexts.Add(new RaidNaxxTriggerContext()); triggerContexts.Add(new RaidNaxxTriggerContext());
triggerContexts.Add(new RaidOsTriggerContext());
triggerContexts.Add(new RaidEoETriggerContext());
triggerContexts.Add(new RaidVoATriggerContext());
triggerContexts.Add(new RaidUlduarTriggerContext()); triggerContexts.Add(new RaidUlduarTriggerContext());
triggerContexts.Add(new RaidIccTriggerContext()); triggerContexts.Add(new RaidIccTriggerContext());
triggerContexts.Add(new WotlkDungeonUKTriggerContext()); triggerContexts.Add(new WotlkDungeonUKTriggerContext());
@@ -103,7 +86,6 @@ AiObjectContext::AiObjectContext(PlayerbotAI* botAI) : PlayerbotAIAware(botAI)
triggerContexts.Add(new WotlkDungeonUPTriggerContext()); triggerContexts.Add(new WotlkDungeonUPTriggerContext());
triggerContexts.Add(new WotlkDungeonCoSTriggerContext()); triggerContexts.Add(new WotlkDungeonCoSTriggerContext());
triggerContexts.Add(new WotlkDungeonFosTriggerContext()); triggerContexts.Add(new WotlkDungeonFosTriggerContext());
triggerContexts.Add(new WotlkDungeonToCTriggerContext());
valueContexts.Add(new ValueContext()); valueContexts.Add(new ValueContext());

View File

@@ -87,23 +87,27 @@ Engine::~Engine(void)
void Engine::Reset() void Engine::Reset()
{ {
strategyTypeMask = 0; strategyTypeMask = 0;
ActionNode* action = nullptr; ActionNode* action = nullptr;
do
while ((action = queue.Pop()) != nullptr)
{ {
action = queue.Pop();
if (!action)
break;
delete action; delete action;
} } while (true);
for (TriggerNode* trigger : triggers) for (std::vector<TriggerNode*>::iterator i = triggers.begin(); i != triggers.end(); i++)
{ {
TriggerNode* trigger = *i;
delete trigger; delete trigger;
} }
triggers.clear(); triggers.clear();
for (Multiplier* multiplier : multipliers) for (std::vector<Multiplier*>::iterator i = multipliers.begin(); i != multipliers.end(); i++)
{ {
Multiplier* multiplier = *i;
delete multiplier; delete multiplier;
} }
@@ -142,100 +146,160 @@ bool Engine::DoNextAction(Unit* unit, uint32 depth, bool minimal)
bool actionExecuted = false; bool actionExecuted = false;
ActionBasket* basket = nullptr; ActionBasket* basket = nullptr;
time_t currentTime = time(nullptr);
// Update triggers and push default actions time_t currentTime = time(nullptr);
// aiObjectContext->Update();
ProcessTriggers(minimal); ProcessTriggers(minimal);
PushDefaultActions(); PushDefaultActions();
uint32 iterations = 0; uint32 iterations = 0;
uint32 iterationsPerTick = queue.Size() * (minimal ? 2 : sPlayerbotAIConfig->iterationsPerTick); uint32 iterationsPerTick = queue.Size() * (minimal ? 2 : sPlayerbotAIConfig->iterationsPerTick);
do
while (++iterations <= iterationsPerTick)
{ {
basket = queue.Peek(); basket = queue.Peek();
if (!basket)
break;
float relevance = basket->getRelevance(); // for reference if (basket)
bool skipPrerequisites = basket->isSkipPrerequisites();
if (minimal && (relevance < 100))
continue;
Event event = basket->getEvent();
ActionNode* actionNode = queue.Pop(); // NOTE: Pop() deletes basket
Action* action = InitializeAction(actionNode);
if (!action)
{ {
LogAction("A:%s - UNKNOWN", actionNode->getName().c_str()); float relevance = basket->getRelevance(); // just for reference
} bool skipPrerequisites = basket->isSkipPrerequisites();
else if (action->isUseful())
{ if (minimal && (relevance < 100))
// Apply multipliers early to avoid unnecessary iterations continue;
for (Multiplier* multiplier : multipliers)
{ Event event = basket->getEvent();
relevance *= multiplier->GetValue(action); // NOTE: queue.Pop() deletes basket
ActionNode* actionNode = queue.Pop();
Action* action = InitializeAction(actionNode);
if (action)
action->setRelevance(relevance); action->setRelevance(relevance);
if (relevance <= 0) if (!action)
{
LogAction("Multiplier %s made action %s useless", multiplier->getName().c_str(), action->getName().c_str());
break;
}
}
if (action->isPossible() && relevance > 0)
{ {
if (!skipPrerequisites) // LOG_ERROR("playerbots", "Action: {} - is UNKNOWN - c:{} l:{}", actionNode->getName().c_str(),
// botAI->GetBot()->getClass(), botAI->GetBot()->GetLevel());
LogAction("A:%s - UNKNOWN", actionNode->getName().c_str());
}
else if (action->isUseful())
{
for (std::vector<Multiplier*>::iterator i = multipliers.begin(); i != multipliers.end(); i++)
{ {
LogAction("A:%s - PREREQ", action->getName().c_str()); Multiplier* multiplier = *i;
relevance *= multiplier->GetValue(action);
action->setRelevance(relevance);
if (MultiplyAndPush(actionNode->getPrerequisites(), relevance + 0.002f, false, event, "prereq")) if (!relevance)
{ {
PushAgain(actionNode, relevance + 0.001f, event); LogAction("Multiplier %s made action %s useless", multiplier->getName().c_str(),
continue; action->getName().c_str());
break;
} }
} }
PerformanceMonitorOperation* pmo = sPerformanceMonitor->start(PERF_MON_ACTION, action->getName(), &aiObjectContext->performanceStack); if (action->isPossible() && relevance)
actionExecuted = ListenAndExecute(action, event);
if (pmo)
pmo->finish();
if (actionExecuted)
{ {
LogAction("A:%s - OK", action->getName().c_str()); if (!skipPrerequisites)
MultiplyAndPush(actionNode->getContinuers(), relevance, false, event, "cont"); {
lastRelevance = relevance; LogAction("A:%s - PREREQ", action->getName().c_str());
delete actionNode; // Safe memory management
break; if (MultiplyAndPush(actionNode->getPrerequisites(), relevance + 0.002f, false, event, "prereq"))
{
PushAgain(actionNode, relevance + 0.001f, event);
continue;
}
}
PerformanceMonitorOperation* pmo = sPerformanceMonitor->start(PERF_MON_ACTION, action->getName(),
&aiObjectContext->performanceStack);
actionExecuted = ListenAndExecute(action, event);
if (pmo)
pmo->finish();
if (actionExecuted)
{
LogAction("A:%s - OK", action->getName().c_str());
MultiplyAndPush(actionNode->getContinuers(), relevance, false, event, "cont");
lastRelevance = relevance;
delete actionNode;
break;
}
else
{
LogAction("A:%s - FAILED", action->getName().c_str());
MultiplyAndPush(actionNode->getAlternatives(), relevance + 0.003f, false, event, "alt");
}
} }
else else
{ {
LogAction("A:%s - FAILED", action->getName().c_str()); if (botAI->HasStrategy("debug", BOT_STATE_NON_COMBAT))
{
std::ostringstream out;
out << "do: ";
out << action->getName();
out << " impossible (";
out << action->getRelevance() << ")";
if (!event.GetSource().empty())
out << " [" << event.GetSource() << "]";
botAI->TellMasterNoFacing(out);
}
LogAction("A:%s - IMPOSSIBLE", action->getName().c_str());
MultiplyAndPush(actionNode->getAlternatives(), relevance + 0.003f, false, event, "alt"); MultiplyAndPush(actionNode->getAlternatives(), relevance + 0.003f, false, event, "alt");
} }
} }
else else
{ {
LogAction("A:%s - IMPOSSIBLE", action->getName().c_str()); if (botAI->HasStrategy("debug", BOT_STATE_NON_COMBAT))
MultiplyAndPush(actionNode->getAlternatives(), relevance + 0.003f, false, event, "alt"); {
} std::ostringstream out;
} out << "do: ";
else out << action->getName();
{ out << " useless (";
LogAction("A:%s - USELESS", action->getName().c_str());
lastRelevance = relevance;
}
delete actionNode; // Always delete after processing the action node out << action->getRelevance() << ")";
if (!event.GetSource().empty())
out << " [" << event.GetSource() << "]";
botAI->TellMasterNoFacing(out);
}
lastRelevance = relevance;
LogAction("A:%s - USELESS", action->getName().c_str());
}
delete actionNode;
}
} while (basket && ++iterations <= iterationsPerTick);
// if (!basket)
// {
// lastRelevance = 0.0f;
// PushDefaultActions();
// // prevent the delay after pushing default actions
// if (queue.Peek() && depth < 1 && !minimal)
// return DoNextAction(unit, depth + 1, minimal);
// }
// MEMORY FIX TEST
/*
do
{
basket = queue.Peek();
if (basket)
{
// NOTE: queue.Pop() deletes basket
delete queue.Pop();
}
} }
while (basket);
*/
if (time(nullptr) - currentTime > 1) if (time(nullptr) - currentTime > 1)
{ {
LogAction("Execution time exceeded 1 second"); LogAction("too long execution");
} }
if (!actionExecuted) if (!actionExecuted)
@@ -503,7 +567,7 @@ std::string const Engine::ListStrategies()
std::string s = "Strategies: "; std::string s = "Strategies: ";
if (strategies.empty()) if (strategies.empty())
return s; return std::move(s);
for (std::map<std::string, Strategy*>::iterator i = strategies.begin(); i != strategies.end(); i++) for (std::map<std::string, Strategy*>::iterator i = strategies.begin(); i != strategies.end(); i++)
{ {

104
src/strategy/Queue.cpp Normal file
View File

@@ -0,0 +1,104 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#include "Queue.h"
#include "AiObjectContext.h"
#include "Log.h"
#include "PlayerbotAIConfig.h"
void Queue::Push(ActionBasket* action)
{
if (action)
{
for (std::list<ActionBasket*>::iterator iter = actions.begin(); iter != actions.end(); iter++)
{
ActionBasket* basket = *iter;
if (action->getAction()->getName() == basket->getAction()->getName())
{
if (basket->getRelevance() < action->getRelevance())
basket->setRelevance(action->getRelevance());
if (ActionNode* actionNode = action->getAction())
delete actionNode;
delete action;
return;
}
}
actions.push_back(action);
}
}
ActionNode* Queue::Pop()
{
float max = -1;
ActionBasket* selection = nullptr;
for (std::list<ActionBasket*>::iterator iter = actions.begin(); iter != actions.end(); iter++)
{
ActionBasket* basket = *iter;
if (basket->getRelevance() > max)
{
max = basket->getRelevance();
selection = basket;
}
}
if (selection != nullptr)
{
ActionNode* action = selection->getAction();
actions.remove(selection);
delete selection;
return action;
}
return nullptr;
}
ActionBasket* Queue::Peek()
{
float max = -1;
ActionBasket* selection = nullptr;
for (std::list<ActionBasket*>::iterator iter = actions.begin(); iter != actions.end(); iter++)
{
ActionBasket* basket = *iter;
if (basket->getRelevance() > max)
{
max = basket->getRelevance();
selection = basket;
}
}
return selection;
}
uint32 Queue::Size() { return actions.size(); }
void Queue::RemoveExpired()
{
std::list<ActionBasket*> expired;
for (std::list<ActionBasket*>::iterator iter = actions.begin(); iter != actions.end(); iter++)
{
ActionBasket* basket = *iter;
if (sPlayerbotAIConfig->expireActionTime && basket->isExpired(sPlayerbotAIConfig->expireActionTime))
expired.push_back(basket);
}
for (std::list<ActionBasket*>::iterator iter = expired.begin(); iter != expired.end(); iter++)
{
ActionBasket* basket = *iter;
actions.remove(basket);
if (ActionNode* action = basket->getAction())
{
delete action;
}
delete basket;
}
}

28
src/strategy/Queue.h Normal file
View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#ifndef _PLAYERBOT_QUEUE_H
#define _PLAYERBOT_QUEUE_H
#include "Action.h"
#include "Common.h"
class Queue
{
public:
Queue(void) {}
~Queue(void) {}
void Push(ActionBasket* action);
ActionNode* Pop();
ActionBasket* Peek();
uint32 Size();
void RemoveExpired();
private:
std::list<ActionBasket*> actions;
};
#endif

View File

@@ -31,7 +31,6 @@
#include "MeleeCombatStrategy.h" #include "MeleeCombatStrategy.h"
#include "MoveFromGroupStrategy.h" #include "MoveFromGroupStrategy.h"
#include "NamedObjectContext.h" #include "NamedObjectContext.h"
#include "NewRpgStrategy.h"
#include "NonCombatStrategy.h" #include "NonCombatStrategy.h"
#include "PassiveStrategy.h" #include "PassiveStrategy.h"
#include "PullStrategy.h" #include "PullStrategy.h"
@@ -83,7 +82,6 @@ public:
creators["reveal"] = &StrategyContext::reveal; creators["reveal"] = &StrategyContext::reveal;
creators["collision"] = &StrategyContext::collision; creators["collision"] = &StrategyContext::collision;
creators["rpg"] = &StrategyContext::rpg; creators["rpg"] = &StrategyContext::rpg;
creators["new rpg"] = &StrategyContext::new_rpg;
creators["travel"] = &StrategyContext::travel; creators["travel"] = &StrategyContext::travel;
creators["explore"] = &StrategyContext::explore; creators["explore"] = &StrategyContext::explore;
creators["map"] = &StrategyContext::map; creators["map"] = &StrategyContext::map;
@@ -119,7 +117,6 @@ public:
creators["move random"] = &StrategyContext::move_random; creators["move random"] = &StrategyContext::move_random;
creators["formation"] = &StrategyContext::combat_formation; creators["formation"] = &StrategyContext::combat_formation;
creators["move from group"] = &StrategyContext::move_from_group; creators["move from group"] = &StrategyContext::move_from_group;
creators["worldbuff"] = &StrategyContext::world_buff;
} }
private: private:
@@ -155,7 +152,6 @@ private:
static Strategy* reveal(PlayerbotAI* botAI) { return new RevealStrategy(botAI); } static Strategy* reveal(PlayerbotAI* botAI) { return new RevealStrategy(botAI); }
static Strategy* collision(PlayerbotAI* botAI) { return new CollisionStrategy(botAI); } static Strategy* collision(PlayerbotAI* botAI) { return new CollisionStrategy(botAI); }
static Strategy* rpg(PlayerbotAI* botAI) { return new RpgStrategy(botAI); } static Strategy* rpg(PlayerbotAI* botAI) { return new RpgStrategy(botAI); }
static Strategy* new_rpg(PlayerbotAI* botAI) { return new NewRpgStrategy(botAI); }
static Strategy* travel(PlayerbotAI* botAI) { return new TravelStrategy(botAI); } static Strategy* travel(PlayerbotAI* botAI) { return new TravelStrategy(botAI); }
static Strategy* explore(PlayerbotAI* botAI) { return new ExploreStrategy(botAI); } static Strategy* explore(PlayerbotAI* botAI) { return new ExploreStrategy(botAI); }
static Strategy* map(PlayerbotAI* botAI) { return new MapStrategy(botAI); } static Strategy* map(PlayerbotAI* botAI) { return new MapStrategy(botAI); }
@@ -187,7 +183,6 @@ private:
static Strategy* move_random(PlayerbotAI* ai) { return new MoveRandomStrategy(ai); } static Strategy* move_random(PlayerbotAI* ai) { return new MoveRandomStrategy(ai); }
static Strategy* combat_formation(PlayerbotAI* ai) { return new CombatFormationStrategy(ai); } static Strategy* combat_formation(PlayerbotAI* ai) { return new CombatFormationStrategy(ai); }
static Strategy* move_from_group(PlayerbotAI* botAI) { return new MoveFromGroupStrategy(botAI); } static Strategy* move_from_group(PlayerbotAI* botAI) { return new MoveFromGroupStrategy(botAI); }
static Strategy* world_buff(PlayerbotAI* botAI) { return new WorldBuffStrategy(botAI); }
}; };
class MovementStrategyContext : public NamedObjectContext<Strategy> class MovementStrategyContext : public NamedObjectContext<Strategy>

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