Compare commits

..

1 Commits

Author SHA1 Message Date
bash
2acf8e4b13 Minor updates after patches
only freeze bots during init when not in combat
2024-12-19 19:49:07 +01:00
1041 changed files with 6467 additions and 24559 deletions

View File

@@ -7,30 +7,29 @@ assignees: ''
---
**Bug Description**
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.
**Describe the bug**
A clear and concise description of what the bug is.
**Commit Hash**
**Commit hash**
The hash of the current commit.
**How To Reproduce Bug**
Detailed steps to reproduce the behavior.
**To Reproduce**
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.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Modules**
Please list all modules used as many are known to cause conflicts with Playerbots.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Playerbot Settings**
Number of bots, scaling settings, etc if performance related.
**System**
OS: [e.g. Windows, Linux]
Hardware: [e.g. CPU if performance related]
**Additional Context**
**Additional context**
Add any other context about the problem here.

View File

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

View File

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

View File

@@ -5,9 +5,9 @@ on:
pull_request:
branches: [ "master" ]
concurrency:
group: "macos-build-${{ github.event.pull_request.number }}"
cancel-in-progress: true
# concurrency:
# group: ${{ github.head_ref }} || concat(${{ github.ref }}, ${{ github.workflow }})
# cancel-in-progress: true
jobs:
macos-build:
@@ -42,4 +42,4 @@ jobs:
- name: Configure OS
run: source ./acore.sh install-deps
- 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:
branches: [ "master" ]
concurrency:
group: "windows-build-${{ github.event.pull_request.number }}"
cancel-in-progress: true
jobs:
windows-build:
strategy:

110
README.md
View File

@@ -1,38 +1,20 @@
<p align="center">
<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>
[English](README.md) | [Español](README_ES.md) | [中文](README_CN.md)
# 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;
- 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.
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.
**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
### 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
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
```
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
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
```
## Progress
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
services:
ac-worldserver:
volumes:
- ./modules:/azerothcore/modules:ro
```
- **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.
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
services:
ac-worldserver:
environment:
AC_RATE_XP_KILL: "1"
AC_AI_PLAYERBOT_RANDOM_BOT_AUTOLOGIN: "1"
volumes:
- ./modules:/azerothcore/modules:ro
```
- **Bots in Battleground:** Bots are now capable of actively participating in battlegrounds alongside real players, adding depth and excitement to these PvP scenarios.
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
- **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:
## Addon
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)
- [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)
## 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
`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
# GENERAL SETTINGS
# PLAYERBOT SETTINGS
# GENERAL
# SUMMON OPTIONS
# MOUNT
# GEAR
# LOOTING
# TIMERS
# DISTANCES
# THRESHOLDS
# QUESTS
# COMBAT
# CHEATS
# SPELLS
# PLAYERBOT RNDBOT SPECIFIC SETTINGS
# GENERAL
# LEVELS
# GEAR
# QUESTS
# ACTIVITIES
# SPELLS
# STRATEGIES
# TELEPORTS
# BATTLEGROUND & ARENA & PVP
# INTERVALS
# PREMADE SPECS
# INFORMATION
# WARRIOR
# PALADIN
# HUNTER
# ROGUE
# PRIEST
# DEATHKNIGHT
# SHAMAN
# MAGE
# WARLOCK
# DRUID
# RANDOM BOT DEFAULT TALENT SPEC
# WARRIOR
# PALADIN
# HUNTER
# ROGUE
# PRIEST
# DEATHKNIGHT
# SHAMAN
# MAGE
# WARLOCK
# DRUID
# PLAYERBOT SYSTEM SETTINGS
# DATABASE & CONNECTIONS
# DEBUG
# CHAT SETTINGS
# LOGS
# DEPRECIATED (TEMPORARY)
# PLAYERBOT SETTINGS
# GENERAL
# SUMMON OPTIONS
# GEAR
# LOOTING
# TIMERS
# DISTANCES
# THRESHOLDS
# QUESTS
# COMBAT
# CHEATS
# SPELLS
# PLAYERBOT RNDBOT SPECIFIC SETTINGS
# GENERAL
# LEVELS
# GEAR
# QUESTS
# SPELLS
# STRATEGIES
# TELEPORTS
# BATTLEGROUND & ARENA & PVP
# INTERVALS
# PREMADE SPECS
# INFORMATION
# WARRIOR
# PALADIN
# HUNTER
# ROGUE
# PRIEST
# DEATHKNIGHT
# SHAMAN
# MAGE
# WARLOCK
# DRUID
# RANDOM BOT DEFAULT TALENT SPEC
# WARRIOR
# PALADIN
# HUNTER
# ROGUE
# PRIEST
# DEATHKNIGHT
# SHAMAN
# MAGE
# WARLOCK
# DRUID
# PLAYERBOT SYSTEM SETTINGS
# DATABASE & CONNECTIONS
# DEBUG
# CHAT SETTINGS
# LOGS
# DEPRECIATED (TEMPORARY)
#
#
#
@@ -81,15 +79,13 @@ AiPlayerbot.Enabled = 1
# Enable random bot system
AiPlayerbot.RandomBotAutologin = 1
# Random bot count
AiPlayerbot.MinRandomBots = 500
AiPlayerbot.MaxRandomBots = 500
# Random bot account
# Please ensure that RandomBotAccountCount is greater than (MaxRandomBots / 10 + AddClassAccountPoolSize)
AiPlayerbot.RandomBotAccountCount = 200
# Random bot accounts
# If you are not using any expansion at all, you may have to set this manually, then
# please ensure that RandomBotAccountCount is at least greater than (MaxRandomBots / 10 + AddClassAccountPoolSize)
# default: 0 - Automatic
AiPlayerbot.RandomBotAccountCount = 0
# Random bot count
AiPlayerbot.MinRandomBots = 50
AiPlayerbot.MaxRandomBots = 50
# Delete all random bot accounts (reset randombots)
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
# 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)
# default: 1 (enable)
AiPlayerbot.AddClassCommand = 1
# Set the addclass command account pool size
# Addclass command uses a subset of accounts from RandomBotAccountCount
AiPlayerbot.AddClassAccountPoolSize = 50
# 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
AiPlayerbot.BotAutologin = 0
# Allow/deny bots from the player's account
AiPlayerbot.AllowAccountBots = 1
# Allow login other players' characters as bots
# Default: 0 (disabled)
AiPlayerbot.AllowPlayerBots = 0
# Allow/deny bots in the player's guild
# Allow/deny bots from your guild
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
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
#
@@ -372,9 +334,9 @@ AiPlayerbot.AggroDistance = 22
#
#
# Set XP rate for bots (default: 1.0)
# Server XP Rate * AiPlayerbot.PlayerbotsXPRate
AiPlayerbot.PlayerbotsXPRate = 1.0
# Set kill XP rate for bots (default: 1)
# Server XP Rate * AiPlayerbot.KillXPRate
AiPlayerbot.KillXPRate = 1
# Health/Mana levels
AiPlayerbot.CriticalHealth = 25
@@ -512,6 +474,14 @@ AiPlayerbot.RandomBotRandomPassword = 0
# Accounts to create for random bots
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.RandomBotMaxLevel = 80
@@ -525,31 +495,10 @@ AiPlayerbot.PreQuests = 0
# Enable LFG for random bots
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
# Need to reset rndbot after changing the setting (.playerbot rndbot reset)
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 +514,10 @@ AiPlayerbot.EnableRandomBotTrading = 1
AiPlayerbot.DisableRandomLevels = 0
# 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)
AiPlayerbot.RandomBotMinLevelChance = 0.1
# Chance random bot has max level on first randomize (default 0.1)
AiPlayerbot.RandomBotMaxLevelChance = 0.1
# Chance random bot has max level on first randomize (default 0.15)
AiPlayerbot.RandomBotMaxLevelChance = 0.15
# Fix the level of random bot (won't level up by grinding)
# Default: 0 (disable)
@@ -630,50 +576,6 @@ AiPlayerbot.EquipmentPersistenceLevel = 80
# Default: 1 (enabled)
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
#
#
#
@@ -696,8 +598,8 @@ AiPlayerbot.AutoDoQuests = 1
# Random Bots will behave more like real players (exprimental)
# This option will override AutoDoQuests
# Default: 1 (enabled)
AiPlayerbot.EnableNewRpgStrategy = 1
# Default: 0 (disabled)
AiPlayerbot.EnableNewRpgStrategy = 0
# Quest items to leave (do not destroy)
AiPlayerbot.RandomBotQuestItems = "6948,5175,5176,5177,5178,16309,12382,13704,11000"
@@ -785,56 +687,18 @@ AiPlayerbot.AutoTeleportForLevel = 1
# Enable BG/Arena for random Bots
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
# Required Configuration for RandomBotAutoJoinBG
#
# Known issue: When enabling a lot of brackats in combination with multiple instances,
# can lead to more instances created by bots than intended (over-queuing).
#
# This section controls the level brackets and
# automatic bot participation in battlegrounds and arenas.
#
# 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:
# Required for RandomBotAutoJoinBG
# Currently you can select 1 bracket (level range) for bots to auto join.
# Brackets for warsong 0:10-19, 1:20-29, 2:30-39, 3:40-49 etc. Default: 7 (level 80)
# 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.
# Count = amount of games Bots auto fill. (Count 1 for WSG = 20 bots).
AiPlayerbot.RandomBotAutoJoinWarsongBracket = 7
AiPlayerbot.RandomBotAutoJoinArenaBracket = 14
AiPlayerbot.RandomBotAutoJoinBGWarsongCount = 0
AiPlayerbot.RandomBotAutoJoinBGRatedArena2v2Count = 0
AiPlayerbot.RandomBotAutoJoinBGRatedArena3v3Count = 0
AiPlayerbot.RandomBotAutoJoinBGRatedArena5v5Count = 0
@@ -861,7 +725,7 @@ AiPlayerbot.DeleteRandomBotArenaTeams = 0
AiPlayerbot.PvpProhibitedZoneIds = "2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298,139"
# PvP Restricted Areas (bots don't pvp)
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268,4161,4010,4317,4312"
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392,2268"
# Improve react speed in battleground and arena (may cause lag)
AiPlayerbot.FastReactInBG = 1
@@ -880,8 +744,8 @@ AiPlayerbot.FastReactInBG = 1
AiPlayerbot.RandomBotUpdateInterval = 20
AiPlayerbot.RandomBotCountChangeMinInterval = 1800
AiPlayerbot.RandomBotCountChangeMaxInterval = 7200
AiPlayerbot.MinRandomBotInWorldTime = 600
AiPlayerbot.MaxRandomBotInWorldTime = 28800
AiPlayerbot.MinRandomBotInWorldTime = 3600
AiPlayerbot.MaxRandomBotInWorldTime = 1209600
AiPlayerbot.MinRandomBotRandomizeTime = 7200
AiPlayerbot.MaxRandomBotRandomizeTime = 1209600
AiPlayerbot.RandomBotsPerInterval = 60
@@ -889,7 +753,7 @@ AiPlayerbot.MinRandomBotReviveTime = 60
AiPlayerbot.MaxRandomBotReviveTime = 300
AiPlayerbot.MinRandomBotTeleportInterval = 3600
AiPlayerbot.MaxRandomBotTeleportInterval = 18000
AiPlayerbot.PermanantlyInWorldTime = 31104000
AiPlayerbot.RandomBotInWorldWithRotationDisabled = 31104000
#
#
@@ -972,8 +836,8 @@ AiPlayerbot.PremadeSpecLink.2.2.80 = 050501-05-05232051203331302133231331
AiPlayerbot.PremadeSpecName.3.0 = bm pve
AiPlayerbot.PremadeSpecGlyph.3.0 = 42912,43350,42902,43351,43338,45732
AiPlayerbot.PremadeSpecLink.3.0.60 = 51200201505112243110531051
AiPlayerbot.PremadeSpecLink.3.0.80 = 51200201505112243120531251-025305101
AiPlayerbot.PremadeSpecLink.3.0.60 = 51200201505112243100511351
AiPlayerbot.PremadeSpecLink.3.0.80 = 51200201505112253100531351-015305021
AiPlayerbot.PremadeSpecName.3.1 = mm pve
AiPlayerbot.PremadeSpecGlyph.3.1 = 42912,43350,42914,43351,43338,45732
AiPlayerbot.PremadeSpecLink.3.1.60 = -025315101030013233125031051
@@ -1007,16 +871,16 @@ AiPlayerbot.PremadeHunterPetLink.2.20 = 21000203300002110221
AiPlayerbot.PremadeSpecName.4.0 = as pve
AiPlayerbot.PremadeSpecGlyph.4.0 = 45768,43379,45761,43380,43378,45766
AiPlayerbot.PremadeSpecLink.4.0.60 = 005303104352100520103331051
AiPlayerbot.PremadeSpecLink.4.0.80 = 005303104352100520103331051-005005005003-2
AiPlayerbot.PremadeSpecLink.4.0.60 = 005323005350100520103331051
AiPlayerbot.PremadeSpecLink.4.0.80 = 005323005350100520103331051-005005005003-2
AiPlayerbot.PremadeSpecName.4.1 = combat pve
AiPlayerbot.PremadeSpecGlyph.4.1 = 42962,43379,45762,43380,43378,42969
AiPlayerbot.PremadeSpecLink.4.1.60 = -0252051000035015223100501251
AiPlayerbot.PremadeSpecLink.4.1.80 = 00532000523-0252051000035015223100501251
AiPlayerbot.PremadeSpecName.4.2 = subtlety pve
AiPlayerbot.PremadeSpecGlyph.4.2 = 42967,43379,45764,43380,43378,45767
AiPlayerbot.PremadeSpecLink.4.2.60 = --5022012030321121350115031151
AiPlayerbot.PremadeSpecLink.4.2.80 = 30532010114--5022012030321121350115031151
AiPlayerbot.PremadeSpecLink.4.2.60 = --5120122030321121050135031241
AiPlayerbot.PremadeSpecLink.4.2.80 = 0053231-2-5120222030321121050135231251
#
#
@@ -1060,7 +924,7 @@ AiPlayerbot.PremadeSpecGlyph.6.1 = 45805,43673,43547,43544,43672,43543
AiPlayerbot.PremadeSpecLink.6.1.60 = -32003350332203012300023101351
AiPlayerbot.PremadeSpecLink.6.1.80 = -32002350352203012300033101351-230200305003
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.80 = -320033500002-2301303050032151000150013133151
AiPlayerbot.PremadeSpecName.6.3 = double aura blood pve
@@ -1113,10 +977,6 @@ AiPlayerbot.PremadeSpecName.8.2 = frost pve
AiPlayerbot.PremadeSpecGlyph.8.2 = 42742,43339,50045,43364,43361,42751
AiPlayerbot.PremadeSpecLink.8.2.60 = --0533030313203100030152231151
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
#
#
@@ -1177,50 +1037,48 @@ AiPlayerbot.PremadeSpecLink.11.3.80 = -553202032322010053100030310511-205503012
###################################
# #
# WORLD BUFFS #
# 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
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.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
AiPlayerbot.WorldBuff.0.11.2.80.80 = 54212,57334 #DRUID RESTORATION
#
#
@@ -1234,7 +1092,7 @@ AiPlayerbot.WorldBuff.0.11.3.80.80 = 53760,57358 #DRUID FERAL
###################################
####################################################################################################
#
#
#
#
@@ -1640,6 +1498,12 @@ AiPlayerbot.EnableGuildTasks = 0
# Enable dungeon suggestions in lower case randomly
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
AiPlayerbot.RandomBotRpgChance = 0.20
@@ -1649,6 +1513,27 @@ AiPlayerbot.RandombotsWalkingRPG = 0
# Set randombots movement speed to walking only inside buildings
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
# Force botActiveAlone when bot is ... of real player
AiPlayerbot.BotActiveAloneForceWhenInRadius = 150
AiPlayerbot.BotActiveAloneForceWhenInZone = 1
AiPlayerbot.BotActiveAloneForceWhenInMap = 0
AiPlayerbot.BotActiveAloneForceWhenIsFriend = 1
AiPlayerbot.BotActiveAloneForceWhenInGuild = 1
# 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)
# spellid-radius, ...
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())
{
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())
{
@@ -304,16 +304,7 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
if (tab == 0)
engine->addStrategiesNoInit("arcane", "arcane aoe", nullptr);
else if (tab == 1)
{
if (player->HasSpell(44614) /*Frostfire Bolt*/ && player->HasAura(15047) /*Ice Shards*/)
{
engine->addStrategiesNoInit("frostfire", "frostfire aoe", nullptr);
}
else
{
engine->addStrategiesNoInit("fire", "fire aoe", nullptr);
}
}
engine->addStrategiesNoInit("fire", "fire aoe", nullptr);
else
engine->addStrategiesNoInit("frost", "frost aoe", nullptr);
@@ -321,8 +312,8 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
break;
case CLASS_WARRIOR:
if (tab == 2)
engine->addStrategiesNoInit("tank", "tank assist", "aoe", nullptr);
else if (tab == 0 || !player->HasSpell(1680)) // Whirlwind
engine->addStrategiesNoInit("tank", "tank assist", "aoe", "mark rti", nullptr);
else if (player->GetLevel() < 36 || tab == 0)
engine->addStrategiesNoInit("arms", "aoe", "dps assist", /*"behind",*/ nullptr);
else
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);
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);
}
@@ -369,19 +360,15 @@ void AiFactory::AddDefaultCombatStrategies(Player* player, PlayerbotAI* const fa
case CLASS_HUNTER:
engine->addStrategiesNoInit("dps", "aoe", "bdps", "dps assist", nullptr);
engine->addStrategy("dps debuff", false);
// if (tab == HUNTER_TAB_SURVIVAL)
// {
// engine->addStrategy("trap weave", false);
// }
break;
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
{
engine->addStrategiesNoInit("dps", "dps assist", "aoe", nullptr);
engine->addStrategiesNoInit("dps", "dps assist", "aoe", /*"behind",*/ nullptr);
}
break;
case CLASS_WARLOCK:
@@ -613,15 +600,14 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
if (!player->InBattleground())
{
nonCombatEngine->addStrategiesNoInit("nc", "food", "chat", "follow", "default", "quest", "loot",
"gather", "duel", "pvp", "buff", "mount", "emote", nullptr);
nonCombatEngine->addStrategiesNoInit("nc", "food", "chat", "follow", "default", "quest", "loot", "gather", "duel",
"buff", "mount", "emote", nullptr);
}
if (sPlayerbotAIConfig->autoSaveMana && PlayerbotAI::IsHeal(player, true))
if (sPlayerbotAIConfig->autoSaveMana)
{
nonCombatEngine->addStrategy("save mana", false);
}
if ((sRandomPlayerbotMgr->IsRandomBot(player)) && !player->InBattleground())
{
Player* master = facade->GetMaster();
@@ -639,11 +625,11 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
// if (!urand(0, 3))
// nonCombatEngine->addStrategy("attack tagged");
// nonCombatEngine->addStrategy("pvp", false);
nonCombatEngine->addStrategy("pvp", false);
// nonCombatEngine->addStrategy("collision");
nonCombatEngine->addStrategy("grind", false);
// nonCombatEngine->addStrategy("group");
// nonCombatEngine->addStrategy("guild");
nonCombatEngine->addStrategy("grind", false);
if (sPlayerbotAIConfig->enableNewRpgStrategy)
{
@@ -676,7 +662,7 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
PlayerbotAI* masterBotAI = GET_PLAYERBOT_AI(master);
if (masterBotAI || sRandomPlayerbotMgr->IsRandomBot(player))
{
// nonCombatEngine->addStrategy("pvp", false);
nonCombatEngine->addStrategy("pvp", false);
// nonCombatEngine->addStrategy("collision");
// nonCombatEngine->addStrategy("group");
// nonCombatEngine->addStrategy("guild");
@@ -696,7 +682,7 @@ void AiFactory::AddDefaultNonCombatStrategies(Player* player, PlayerbotAI* const
}
else
{
// nonCombatEngine->addStrategy("pvp", false);
nonCombatEngine->addStrategy("pvp", false);
nonCombatEngine->ChangeStrategy(sPlayerbotAIConfig->nonCombatStrategies);
}
}

View File

@@ -16,7 +16,7 @@ uint8 BroadcastHelper::GetLocale()
return locale;
}
bool BroadcastHelper::BroadcastTest(PlayerbotAI* ai, Player* /* bot */)
bool BroadcastHelper::BroadcastTest(PlayerbotAI* ai, Player* bot)
{
//return something to ignore the logic
return false;
@@ -609,7 +609,7 @@ bool BroadcastHelper::BroadcastLevelup(PlayerbotAI* ai, Player* bot)
return false;
}
bool BroadcastHelper::BroadcastGuildMemberPromotion(PlayerbotAI* ai, Player* /* bot */, Player* player)
bool BroadcastHelper::BroadcastGuildMemberPromotion(PlayerbotAI* ai, Player* bot, Player* player)
{
if (!sPlayerbotAIConfig->enableBroadcasts)
return false;
@@ -627,7 +627,7 @@ bool BroadcastHelper::BroadcastGuildMemberPromotion(PlayerbotAI* ai, Player* /*
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)
{
@@ -643,7 +643,7 @@ bool BroadcastHelper::BroadcastGuildMemberDemotion(PlayerbotAI* ai, Player* /* b
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)
return false;

View File

@@ -156,7 +156,7 @@ public:
return message;
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++)
{
std::string const rti = *i;
@@ -213,7 +213,7 @@ public:
Player* bot = botAI->GetBot();
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++)
{
bool isClass = message.find(i->first) == 0;

View File

@@ -6,9 +6,6 @@
#include "ChatHelper.h"
#include "AiFactory.h"
#include "Common.h"
#include "ItemTemplate.h"
#include "ObjectMgr.h"
#include "Playerbots.h"
#include "SpellInfo.h"
@@ -313,16 +310,7 @@ std::string const ChatHelper::FormatQuest(Quest const* quest)
}
std::ostringstream out;
QuestLocale const* locale = sObjectMgr->GetQuestLocale(quest->GetQuestId());
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";
out << "|cFFFFFF00|Hquest:" << quest->GetQuestId() << ':' << quest->GetQuestLevel() << "|h[" << quest->GetTitle() << "]|h|r";
return out.str();
}
@@ -330,7 +318,7 @@ std::string const ChatHelper::FormatGameobject(GameObject* go)
{
std::ostringstream out;
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();
}
@@ -339,8 +327,8 @@ std::string const ChatHelper::FormatWorldobject(WorldObject* wo)
std::ostringstream out;
out << "|cFFFFFF00|Hfound:" << wo->GetGUID().GetRawValue() << ":" << wo->GetEntry() << ":"
<< "|h[";
out << (wo->ToGameObject() ? ((GameObject*)wo)->GetNameForLocaleIdx(sWorld->GetDefaultDbcLocale())
: wo->GetNameForLocaleIdx(sWorld->GetDefaultDbcLocale()))
out << (wo->ToGameObject() ? ((GameObject*)wo)->GetNameForLocaleIdx(LOCALE_enUS)
: wo->GetNameForLocaleIdx(LOCALE_enUS))
<< "]|h|r";
return out.str();
}
@@ -373,9 +361,7 @@ std::string const ChatHelper::FormatWorldEntry(int32 entry)
std::string const ChatHelper::FormatSpell(SpellInfo const* spellInfo)
{
std::ostringstream out;
std::string spellName = spellInfo->SpellName[sWorld->GetDefaultDbcLocale()] ?
spellInfo->SpellName[sWorld->GetDefaultDbcLocale()] : spellInfo->SpellName[LOCALE_enUS];
out << "|cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellName << "]|h|r";
out << "|cffffffff|Hspell:" << spellInfo->Id << "|h[" << spellInfo->SpellName[LOCALE_enUS] << "]|h|r";
return out.str();
}
@@ -384,18 +370,11 @@ std::string const ChatHelper::FormatItem(ItemTemplate const* proto, uint32 count
char color[32];
sprintf(color, "%x", ItemQualityColors[proto->Quality]);
std::string itemName;
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;
// const std::string &name = sObjectMgr->GetItemLocale(proto->ItemId)->Name[LOCALE_enUS];
std::ostringstream out;
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)
out << "x" << count;
@@ -483,7 +462,7 @@ GuidVector ChatHelper::parseGameobjects(std::string const text)
break;
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);

View File

@@ -18,7 +18,7 @@ class FleePoint
{
public:
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
{
//uint32* tradeSkills = PlayerbotFactory::tradeSkills;
uint32* tradeSkills = PlayerbotFactory::tradeSkills;
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,
[[maybe_unused]] uint32* validIn /* = nullptr */)
uint32* validIn /* = nullptr */)
{
std::map<uint32, uint32> results;
@@ -571,10 +571,10 @@ std::map<uint32, uint32> GuildTaskMgr::GetTaskValues(uint32 owner, std::string c
} 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;
@@ -622,7 +622,7 @@ uint32 GuildTaskMgr::SetTaskValue(uint32 owner, uint32 guildId, std::string cons
return value;
}
bool GuildTaskMgr::HandleConsoleCommand(ChatHandler* /* handler */, char const* args)
bool GuildTaskMgr::HandleConsoleCommand(ChatHandler* handler, char const* args)
{
if (!sPlayerbotAIConfig->guildTaskEnabled)
{

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -13,18 +13,15 @@
#include "ChatFilter.h"
#include "ChatHelper.h"
#include "Common.h"
#include "CreatureData.h"
#include "Event.h"
#include "Item.h"
#include "NewRpgInfo.h"
#include "NewRpgStrategy.h"
#include "PlayerbotAIBase.h"
#include "PlayerbotAIConfig.h"
#include "PlayerbotSecurity.h"
#include "PlayerbotTextMgr.h"
#include "SpellAuras.h"
#include "Util.h"
#include "WorldPacket.h"
#include "NewRpgStrategy.h"
class AiObjectContext;
class Creature;
@@ -392,8 +389,6 @@ public:
void HandleMasterOutgoingPacket(WorldPacket const& packet);
void HandleTeleportAck();
void ChangeEngine(BotState type);
void ChangeEngineOnCombat();
void ChangeEngineOnNonCombat();
void DoNextAction(bool minimal = false);
virtual bool DoSpecificAction(std::string const name, Event event = Event(), bool silent = false,
std::string const qualifier = "");
@@ -413,8 +408,8 @@ public:
static bool IsRanged(Player* player, bool bySpec = false);
static bool IsMelee(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 IsCombo(Player* player);
static bool IsMainTank(Player* player);
static uint32 GetGroupTankNum(Player* player);
bool IsAssistTank(Player* player);
@@ -424,7 +419,7 @@ public:
bool HasAggro(Unit* unit);
int32 GetGroupSlotIndex(Player* player);
int32 GetRangedIndex(Player* player);
int32 GetClassIndex(Player* player, uint8 cls);
int32 GetClassIndex(Player* player, uint8_t cls);
int32 GetRangedDpsIndex(Player* player);
int32 GetMeleeIndex(Player* player);
@@ -439,8 +434,7 @@ public:
const AreaTableEntry* GetCurrentArea();
const AreaTableEntry* GetCurrentZone();
static 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::string const text, PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL);
bool TellMasterNoFacing(std::ostringstream& stream,
@@ -467,10 +461,7 @@ public:
bool PlayEmote(uint32 emote);
void Ping(float x, float y);
Item* FindPoison() const;
Item* FindAmmo() const;
Item* FindBandage() const;
Item* FindOpenableItem() const;
Item* FindLockedItem() const;
Item* FindConsumable(uint32 displayId) const;
Item* FindStoneFor(Item* weapon) const;
Item* FindOilFor(Item* weapon) const;
@@ -492,8 +483,8 @@ public:
virtual bool HasAuraToDispel(Unit* player, uint32 dispelType);
bool CanCastSpell(uint32 spellid, Unit* target, bool checkHasSpell = true, Item* itemTarget = nullptr,
Item* castItem = nullptr);
bool CanCastSpell(uint32 spellid, GameObject* goTarget, bool checkHasSpell = true);
bool CanCastSpell(uint32 spellid, float x, float y, float z, bool checkHasSpell = true,
bool CanCastSpell(uint32 spellid, GameObject* goTarget, uint8 effectMask, bool checkHasSpell = true);
bool CanCastSpell(uint32 spellid, float x, float y, float z, uint8 effectMask, bool checkHasSpell = true,
Item* itemTarget = nullptr);
bool HasAura(uint32 spellId, Unit const* player);
@@ -509,8 +500,7 @@ public:
bool IsInVehicle(bool canControl = false, bool canCast = false, bool canAttack = false, bool canTurn = 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);
bool HasSkill(SkillType skill);
bool IsAllowedCommand(std::string const text);
@@ -530,7 +520,7 @@ public:
bool IsAlt();
Player* GetGroupMaster();
// 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();
GuilderType GetGuilderType();
bool HasPlayerNearby(WorldPosition* pos, float range = sPlayerbotAIConfig->reactDistance);
@@ -565,7 +555,6 @@ public:
void ResetJumpDestination() { jumpDestination = Position(); }
bool CanMove();
static bool IsRealGuild(uint32 guildId);
bool IsInRealGuild();
static std::vector<std::string> dispel_whitelist;
bool EqualLowercaseName(std::string s1, std::string s2);
@@ -584,18 +573,13 @@ public:
std::set<uint32> GetCurrentIncompleteQuestIds();
void PetFollow();
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:
static void _fillGearScoreData(Player* player, Item* item, std::vector<uint32>* gearScore, uint32& twoHandScore,
bool mixed = false);
bool IsTellAllowed(PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL);
void UpdateAIGroupMembership();
Item* FindItemInInventory(std::function<bool(ItemTemplate const*)> checkItem) const;
void HandleCommands();
void HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang = LANG_UNIVERSAL);
bool _isBotInitializing = false;

View File

@@ -120,18 +120,16 @@ bool PlayerbotAIConfig::Initialize()
randomGearQualityLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomGearQualityLimit", 3);
randomGearScoreLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomGearScoreLimit", 0);
randomBotMinLevelChance = sConfigMgr->GetOption<float>("AiPlayerbot.RandomBotMinLevelChance", 0.1f);
randomBotMaxLevelChance = sConfigMgr->GetOption<float>("AiPlayerbot.RandomBotMaxLevelChance", 0.1f);
randomBotMaxLevelChance = sConfigMgr->GetOption<float>("AiPlayerbot.RandomBotMaxLevelChance", 0.15f);
randomBotRpgChance = sConfigMgr->GetOption<float>("AiPlayerbot.RandomBotRpgChance", 0.20f);
iterationsPerTick = sConfigMgr->GetOption<int32>("AiPlayerbot.IterationsPerTick", 100);
allowAccountBots = sConfigMgr->GetOption<bool>("AiPlayerbot.AllowAccountBots", true);
allowGuildBots = sConfigMgr->GetOption<bool>("AiPlayerbot.AllowGuildBots", true);
allowTrustedAccountBots = sConfigMgr->GetOption<bool>("AiPlayerbot.AllowTrustedAccountBots", true);
randomBotGuildNearby = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotGuildNearby", false);
randomBotInvitePlayer = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotInvitePlayer", 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");
LoadList<std::vector<uint32>>(randomBotMapsAsString, randomBotMaps);
@@ -157,7 +155,7 @@ bool PlayerbotAIConfig::Initialize()
botAutologin = sConfigMgr->GetOption<bool>("AiPlayerbot.BotAutologin", false);
randomBotAutologin = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotAutologin", true);
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);
randomBotCountChangeMinInterval =
sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotCountChangeMinInterval", 30 * MINUTE);
@@ -175,8 +173,8 @@ bool PlayerbotAIConfig::Initialize()
maxRandomBotReviveTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxRandomBotReviveTime", 5 * MINUTE);
minRandomBotTeleportInterval = sConfigMgr->GetOption<int32>("AiPlayerbot.MinRandomBotTeleportInterval", 1 * HOUR);
maxRandomBotTeleportInterval = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxRandomBotTeleportInterval", 5 * HOUR);
permanantlyInWorldTime =
sConfigMgr->GetOption<int32>("AiPlayerbot.PermanantlyInWorldTime", 1 * YEAR);
randomBotInWorldWithRotationDisabled =
sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotInWorldWithRotationDisabled", 1 * YEAR);
randomBotTeleportDistance = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotTeleportDistance", 100);
randomBotsPerInterval = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotsPerInterval", 60);
minRandomBotsPriceChangeInterval =
@@ -280,21 +278,9 @@ bool PlayerbotAIConfig::Initialize()
randomBotJoinBG = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotJoinBG", true);
randomBotAutoJoinBG = sConfigMgr->GetOption<bool>("AiPlayerbot.RandomBotAutoJoinBG", false);
randomBotAutoJoinWarsongBracket = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotAutoJoinWarsongBracket", 14);
randomBotAutoJoinArenaBracket = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotAutoJoinArenaBracket", 7);
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);
randomBotAutoJoinBGWarsongCount = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotAutoJoinBGWarsongCount", 0);
randomBotAutoJoinBGRatedArena2v2Count =
sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotAutoJoinBGRatedArena2v2Count", 0);
randomBotAutoJoinBGRatedArena3v3Count =
@@ -326,12 +312,9 @@ bool PlayerbotAIConfig::Initialize()
commandServerPort = sConfigMgr->GetOption<int32>("AiPlayerbot.CommandServerPort", 8888);
perfMonEnabled = sConfigMgr->GetOption<bool>("AiPlayerbot.PerfMonEnabled", false);
useGroundMountAtMinLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.UseGroundMountAtMinLevel", 20);
useFastGroundMountAtMinLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.UseFastGroundMountAtMinLevel", 40);
useFlyMountAtMinLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.UseFlyMountAtMinLevel", 60);
useFastFlyMountAtMinLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.UseFastFlyMountAtMinLevel", 70);
LOG_INFO("server.loading", "Loading TalentSpecs...");
LOG_INFO("server.loading", "---------------------------------------");
LOG_INFO("server.loading", " Loading TalentSpecs ");
LOG_INFO("server.loading", "---------------------------------------");
for (uint32 cls = 1; cls < MAX_CLASSES; ++cls)
{
@@ -421,22 +404,21 @@ bool PlayerbotAIConfig::Initialize()
{
for (uint32 classId = 0; classId < MAX_CLASSES; classId++)
{
for (uint32 specId = 0; specId <= MAX_WORLDBUFF_SPECNO; specId++)
for (uint32 specId = 0; specId < MAX_SPECNO; specId++)
{
for (uint32 minLevel = 0; minLevel <= randomBotMaxLevel; minLevel++)
for (uint32 minLevel = 0; minLevel < MAX_LEVEL; minLevel++)
{
for (uint32 maxLevel = minLevel; maxLevel <= randomBotMaxLevel; maxLevel++)
for (uint32 maxLevel = 0; maxLevel < MAX_LEVEL; maxLevel++)
{
loadWorldBuff(factionId, classId, specId, minLevel, maxLevel);
loadWorldBuf(factionId, classId, specId, minLevel, maxLevel);
}
loadWorldBuff(factionId, classId, specId, minLevel, 0);
}
}
}
}
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);
randomBotGuildCount = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotGuildCount", 20);
deleteRandomBotGuilds = sConfigMgr->GetOption<bool>("AiPlayerbot.DeleteRandomBotGuilds", false);
@@ -445,7 +427,8 @@ bool PlayerbotAIConfig::Initialize()
minGuildTaskChangeTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MinGuildTaskChangeTime", 3 * 24 * 3600);
maxGuildTaskChangeTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxGuildTaskChangeTime", 4 * 24 * 3600);
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);
maxGuildTaskRewardTime = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxGuildTaskRewardTime", 3600);
guildTaskAdvertCleanupTime = sConfigMgr->GetOption<int32>("AiPlayerbot.GuildTaskAdvertCleanupTime", 300);
@@ -475,6 +458,7 @@ bool PlayerbotAIConfig::Initialize()
autoInitEquipLevelLimitRatio = sConfigMgr->GetOption<float>("AiPlayerbot.AutoInitEquipLevelLimitRatio", 1.0);
maxAddedBots = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxAddedBots", 40);
maxAddedBotsPerClass = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxAddedBotsPerClass", 10);
addClassCommand = sConfigMgr->GetOption<int32>("AiPlayerbot.AddClassCommand", 1);
addClassAccountPoolSize = sConfigMgr->GetOption<int32>("AiPlayerbot.AddClassAccountPoolSize", 50);
maintenanceCommand = sConfigMgr->GetOption<int32>("AiPlayerbot.MaintenanceCommand", 1);
@@ -483,11 +467,8 @@ bool PlayerbotAIConfig::Initialize()
autoGearQualityLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.AutoGearQualityLimit", 3);
autoGearScoreLimit = sConfigMgr->GetOption<int32>("AiPlayerbot.AutoGearScoreLimit", 0);
playerbotsXPrate = sConfigMgr->GetOption<float>("AiPlayerbot.PlayerbotsXPRate", 1.0);
randomBotAllianceRatio = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotAllianceRatio", 50);
randomBotHordeRatio = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotHordeRatio", 50);
playerbotsXPrate = sConfigMgr->GetOption<int32>("AiPlayerbot.KillXPRate", 1);
disableDeathKnightLogin = sConfigMgr->GetOption<bool>("AiPlayerbot.DisableDeathKnightLogin", 0);
limitTalentsExpansion = sConfigMgr->GetOption<bool>("AiPlayerbot.LimitTalentsExpansion", 0);
botActiveAlone = sConfigMgr->GetOption<int32>("AiPlayerbot.BotActiveAlone", 100);
BotActiveAloneForceWhenInRadius = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotActiveAloneForceWhenInRadius", 150);
BotActiveAloneForceWhenInZone = sConfigMgr->GetOption<bool>("AiPlayerbot.BotActiveAloneForceWhenInZone", 1);
@@ -495,10 +476,10 @@ bool PlayerbotAIConfig::Initialize()
BotActiveAloneForceWhenIsFriend = sConfigMgr->GetOption<bool>("AiPlayerbot.BotActiveAloneForceWhenIsFriend", 1);
BotActiveAloneForceWhenInGuild = sConfigMgr->GetOption<bool>("AiPlayerbot.BotActiveAloneForceWhenInGuild", 1);
botActiveAloneSmartScale = sConfigMgr->GetOption<bool>("AiPlayerbot.botActiveAloneSmartScale", 1);
botActiveAloneSmartScaleDiffLimitfloor = sConfigMgr->GetOption<uint32>("AiPlayerbot.botActiveAloneSmartScaleDiffLimitfloor", 50);
botActiveAloneSmartScaleDiffLimitCeiling = sConfigMgr->GetOption<uint32>("AiPlayerbot.botActiveAloneSmartScaleDiffLimitCeiling", 200);
botActiveAloneSmartScaleWhenMinLevel = sConfigMgr->GetOption<uint32>("AiPlayerbot.botActiveAloneSmartScaleWhenMinLevel", 1);
botActiveAloneSmartScaleWhenMaxLevel = sConfigMgr->GetOption<uint32>("AiPlayerbot.botActiveAloneSmartScaleWhenMaxLevel", 80);
botActiveAloneSmartScaleWhenMinLevel =
sConfigMgr->GetOption<uint32>("AiPlayerbot.botActiveAloneSmartScaleWhenMinLevel", 1);
botActiveAloneSmartScaleWhenMaxLevel =
sConfigMgr->GetOption<uint32>("AiPlayerbot.botActiveAloneSmartScaleWhenMaxLevel", 80);
randombotsWalkingRPG = sConfigMgr->GetOption<bool>("AiPlayerbot.RandombotsWalkingRPG", false);
randombotsWalkingRPGInDoors = sConfigMgr->GetOption<bool>("AiPlayerbot.RandombotsWalkingRPG.InDoors", false);
@@ -506,9 +487,8 @@ bool PlayerbotAIConfig::Initialize()
limitEnchantExpansion = sConfigMgr->GetOption<int32>("AiPlayerbot.LimitEnchantExpansion", 1);
limitGearExpansion = sConfigMgr->GetOption<int32>("AiPlayerbot.LimitGearExpansion", 1);
randombotStartingLevel = sConfigMgr->GetOption<int32>("AiPlayerbot.RandombotStartingLevel", 5);
enablePeriodicOnlineOffline = sConfigMgr->GetOption<bool>("AiPlayerbot.EnablePeriodicOnlineOffline", false);
enableRandomBotTrading = sConfigMgr->GetOption<int32>("AiPlayerbot.EnableRandomBotTrading", 1);
periodicOnlineOfflineRatio = sConfigMgr->GetOption<float>("AiPlayerbot.PeriodicOnlineOfflineRatio", 2.0);
enableRotation = sConfigMgr->GetOption<bool>("AiPlayerbot.EnableRotation", false);
rotationPoolSize = sConfigMgr->GetOption<int32>("AiPlayerbot.RotationPoolSize", 500);
gearscorecheck = sConfigMgr->GetOption<bool>("AiPlayerbot.GearScoreCheck", false);
randomBotPreQuests = sConfigMgr->GetOption<bool>("AiPlayerbot.PreQuests", true);
@@ -525,7 +505,6 @@ bool PlayerbotAIConfig::Initialize()
autoTrainSpells = sConfigMgr->GetOption<std::string>("AiPlayerbot.AutoTrainSpells", "yes");
autoPickTalents = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoPickTalents", true);
autoUpgradeEquip = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoUpgradeEquip", false);
hunterWolfPet = sConfigMgr->GetOption<int32>("AiPlayerbot.HunterWolfPet", 0);
autoLearnTrainerSpells = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoLearnTrainerSpells", true);
autoLearnQuestSpells = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoLearnQuestSpells", false);
autoTeleportForLevel = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoTeleportForLevel", false);
@@ -550,11 +529,8 @@ bool PlayerbotAIConfig::Initialize()
{
return true;
}
if (sPlayerbotAIConfig->enabled)
{
sRandomPlayerbotMgr->Init();
}
if (sPlayerbotAIConfig->addClassCommand)
sRandomPlayerbotMgr->PrepareAddclassCache();
sRandomItemMgr->Init();
sRandomItemMgr->InitAfterAhBot();
@@ -568,6 +544,8 @@ bool PlayerbotAIConfig::Initialize()
sTravelMgr->LoadQuestTravelTable();
}
if (sPlayerbotAIConfig->randomBotJoinBG)
sRandomPlayerbotMgr->LoadBattleMastersCache();
if (sPlayerbotAIConfig->randomBotSuggestDungeons)
{
@@ -678,7 +656,7 @@ void PlayerbotAIConfig::log(std::string const fileName, char const* str, ...)
fflush(stdout);
}
void PlayerbotAIConfig::loadWorldBuff(uint32 factionId1, uint32 classId1, uint32 specId1, uint32 minLevel1, uint32 maxLevel1)
void PlayerbotAIConfig::loadWorldBuf(uint32 factionId1, uint32 classId1, uint32 specId1, uint32 minLevel1, uint32 maxLevel1)
{
std::vector<uint32> buffs;

View File

@@ -35,7 +35,6 @@ enum class HealingManaEfficiency : uint8
};
#define MAX_SPECNO 20
#define MAX_WORLDBUFF_SPECNO 3
class PlayerbotAIConfig
{
@@ -55,7 +54,7 @@ public:
bool IsInPvpProhibitedArea(uint32 id);
bool enabled;
bool allowAccountBots, allowGuildBots, allowTrustedAccountBots;
bool allowGuildBots, allowPlayerBots;
bool randomBotGuildNearby, randomBotInvitePlayer, inviteChat;
uint32 globalCoolDown, reactDelay, maxWaitForMove, disableMoveSplinePath, maxMovementSearchTime, expireActionTime,
dispelAuraDuration, passiveDelay, repeatDelay, errorDelay, rpgDelay, sitDelay, returnDelay, lootDelay;
@@ -86,7 +85,7 @@ public:
float randomGearLoweringChance;
int32 randomGearQualityLimit;
int32 randomGearScoreLimit;
float randomBotMinLevelChance, randomBotMaxLevelChance;
float randomBotMaxLevelChance;
float randomBotRpgChance;
uint32 minRandomBots, maxRandomBots;
uint32 randomBotUpdateInterval, randomBotCountChangeMinInterval, randomBotCountChangeMaxInterval;
@@ -95,7 +94,7 @@ public:
uint32 minRandomBotChangeStrategyTime, maxRandomBotChangeStrategyTime;
uint32 minRandomBotReviveTime, maxRandomBotReviveTime;
uint32 minRandomBotTeleportInterval, maxRandomBotTeleportInterval;
uint32 permanantlyInWorldTime;
uint32 randomBotInWorldWithRotationDisabled;
uint32 minRandomBotPvpTime, maxRandomBotPvpTime;
uint32 randomBotsPerInterval;
uint32 minRandomBotsPriceChangeInterval, maxRandomBotsPriceChangeInterval;
@@ -174,25 +173,12 @@ public:
bool randomBotJoinBG;
bool randomBotAutoJoinBG;
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 randomBotAutoJoinWarsongBracket;
uint32 randomBotAutoJoinArenaBracket;
uint32 randomBotAutoJoinBGWarsongCount;
uint32 randomBotAutoJoinBGRatedArena2v2Count;
uint32 randomBotAutoJoinBGRatedArena3v3Count;
uint32 randomBotAutoJoinBGRatedArena5v5Count;
bool randomBotLoginAtStartup;
uint32 randomBotTeleLowerLevel, randomBotTeleHigherLevel;
bool logInGroupOnly, logValuesPerTick;
@@ -235,8 +221,8 @@ public:
uint32 limitEnchantExpansion;
uint32 limitGearExpansion;
uint32 randombotStartingLevel;
bool enablePeriodicOnlineOffline;
float periodicOnlineOfflineRatio;
bool enableRotation;
uint32 rotationPoolSize;
bool gearscorecheck;
bool randomBotPreQuests;
@@ -275,11 +261,8 @@ public:
bool randomBotShowCloak;
bool randomBotFixedLevel;
bool disableRandomLevels;
float playerbotsXPrate;
uint32 randomBotAllianceRatio;
uint32 randomBotHordeRatio;
uint32 playerbotsXPrate;
bool disableDeathKnightLogin;
bool limitTalentsExpansion;
uint32 botActiveAlone;
uint32 BotActiveAloneForceWhenInRadius;
bool BotActiveAloneForceWhenInZone;
@@ -287,8 +270,6 @@ public:
bool BotActiveAloneForceWhenIsFriend;
bool BotActiveAloneForceWhenInGuild;
bool botActiveAloneSmartScale;
uint32 botActiveAloneSmartScaleDiffLimitfloor;
uint32 botActiveAloneSmartScaleDiffLimitCeiling;
uint32 botActiveAloneSmartScaleWhenMinLevel;
uint32 botActiveAloneSmartScaleWhenMaxLevel;
@@ -304,7 +285,6 @@ public:
std::string autoTrainSpells;
bool autoPickTalents;
bool autoUpgradeEquip;
int32 hunterWolfPet;
bool autoLearnTrainerSpells;
bool autoDoQuests;
bool enableNewRpgStrategy;
@@ -313,7 +293,6 @@ public:
bool autoLearnQuestSpells;
bool autoTeleportForLevel;
bool randomBotGroupNearby;
int32 enableRandomBotTrading;
uint32 tweakValue; // Debugging config
uint32 randomBotArenaTeamCount;
@@ -337,17 +316,12 @@ public:
bool botRepairWhenSummon;
bool autoInitOnly;
float autoInitEquipLevelLimitRatio;
int32 maxAddedBots;
int32 maxAddedBots, maxAddedBotsPerClass;
int32 addClassCommand;
int32 addClassAccountPoolSize;
int32 maintenanceCommand;
int32 autoGearCommand, autoGearCommandAltBots, autoGearQualityLimit, autoGearScoreLimit;
uint32 useGroundMountAtMinLevel;
uint32 useFastGroundMountAtMinLevel;
uint32 useFlyMountAtMinLevel;
uint32 useFastFlyMountAtMinLevel;
std::string const GetTimestampStr();
bool hasLog(std::string const fileName)
{
@@ -361,7 +335,7 @@ public:
}
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 specId, uint32 minLevel, uint32 maxLevel);
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);
};

View File

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

View File

@@ -9,9 +9,6 @@
#include <cstring>
#include <istream>
#include <string>
#include <openssl/sha.h>
#include <unordered_set>
#include <iomanip>
#include "ChannelMgr.h"
#include "CharacterCache.h"
@@ -20,7 +17,6 @@
#include "Define.h"
#include "Group.h"
#include "GroupMgr.h"
#include "GuildMgr.h"
#include "ObjectAccessor.h"
#include "ObjectGuid.h"
#include "ObjectMgr.h"
@@ -34,36 +30,6 @@
#include "WorldSession.h"
#include "ChannelMgr.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) {}
class PlayerbotLoginQueryHolder : public LoginQueryHolder
@@ -71,6 +37,7 @@ class PlayerbotLoginQueryHolder : public LoginQueryHolder
private:
uint32 masterAccountId;
PlayerbotHolder* playerbotHolder;
public:
PlayerbotLoginQueryHolder(PlayerbotHolder* playerbotHolder, uint32 masterAccount, uint32 accountId, ObjectGuid guid)
: LoginQueryHolder(accountId, guid), masterAccountId(masterAccount), playerbotHolder(playerbotHolder)
@@ -96,49 +63,6 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
if (!accountId)
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::make_shared<PlayerbotLoginQueryHolder>(this, masterAccountId, accountId, playerGuid);
if (!holder->Initialize())
@@ -147,23 +71,25 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
}
botLoading.insert(playerGuid);
// Always login in with world session to avoid race condition
sWorld->AddQueryHolderCallback(CharacterDatabase.DelayQueryHolder(holder))
.AfterComplete([this](SQLQueryHolderBase const& holder)
{ HandlePlayerBotLoginCallback(static_cast<PlayerbotLoginQueryHolder const&>(holder)); });
}
bool PlayerbotHolder::IsAccountLinked(uint32 accountId, uint32 linkedAccountId)
{
QueryResult result = PlayerbotsDatabase.Query(
"SELECT 1 FROM playerbot_account_links WHERE account_id = {} AND linked_account_id = {}", accountId, linkedAccountId);
return result != nullptr;
if (WorldSession* masterSession = sWorld->FindSession(masterAccountId))
{
masterSession->AddQueryHolderCallback(CharacterDatabase.DelayQueryHolder(holder))
.AfterComplete([this](SQLQueryHolderBase const& holder)
{ HandlePlayerBotLoginCallback(static_cast<PlayerbotLoginQueryHolder const&>(holder)); });
}
else
{
sWorld->AddQueryHolderCallback(CharacterDatabase.DelayQueryHolder(holder))
.AfterComplete([this](SQLQueryHolderBase const& holder)
{ HandlePlayerBotLoginCallback(static_cast<PlayerbotLoginQueryHolder const&>(holder)); });
}
}
void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder const& holder)
{
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
// allows channels to work as intended)
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();
WorldSession* masterSession = masterAccount ? sWorldSessionMgr->FindSession(masterAccount) : nullptr;
WorldSession* masterSession = masterAccount ? sWorld->FindSession(masterAccount) : nullptr;
// Check if masterSession->GetPlayer() is valid
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);
}
sRandomPlayerbotMgr->OnPlayerLogin(bot);
OnBotLogin(bot);
std::ostringstream out;
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());
}
@@ -314,7 +295,7 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid)
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);
WorldSession* botWorldSessionPtr = bot->GetSession();
@@ -371,7 +352,7 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid)
botWorldSessionPtr->HandleLogoutRequestOpcode(data);
if (!bot)
{
RemoveFromPlayerbotsMap(guid);
playerBots.erase(guid);
delete botWorldSessionPtr;
if (target)
delete target;
@@ -380,7 +361,7 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid)
}
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
if (target)
delete target;
@@ -390,7 +371,7 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid)
else if (bot && (logout || !botWorldSessionPtr->isLogingOut()))
{
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
delete botWorldSessionPtr; // finally delete the bot's WorldSession
}
@@ -427,17 +408,12 @@ void PlayerbotHolder::DisablePlayerBot(ObjectGuid guid)
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;
}
}
void PlayerbotHolder::RemoveFromPlayerbotsMap(ObjectGuid guid)
{
playerBots.erase(guid);
}
Player* PlayerbotHolder::GetPlayerBot(ObjectGuid playerGuid) const
{
PlayerBotMap::const_iterator it = playerBots.find(playerGuid);
@@ -461,7 +437,6 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
sPlayerbotsMgr->AddPlayerbotData(bot, true);
playerBots[bot->GetGUID()] = bot;
OnBotLoginInternal(bot);
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
@@ -509,7 +484,10 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
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
{
botAI->ResetStrategies(!sRandomPlayerbotMgr->IsRandomBot(bot));
// botAI->ResetStrategies(!sRandomPlayerbotMgr->IsRandomBot(bot));
botAI->ResetStrategies();
}
sPlayerbotDbStore->Load(botAI);
if (master && !master->HasUnitState(UNIT_STATE_IN_FLIGHT))
{
@@ -581,16 +559,12 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
}
bot->SaveToDB(false, false);
bool addClassBot = sRandomPlayerbotMgr->IsAddclassBot(bot->GetGUID().GetCounter());
if (addClassBot && master && isRandomAccount && abs((int)master->GetLevel() - (int)bot->GetLevel()) > 3)
if (master && isRandomAccount && master->GetLevel() < bot->GetLevel())
{
// PlayerbotFactory factory(bot, master->GetLevel());
// factory.Randomize(false);
uint32 mixedGearScore =
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);
factory.Randomize(false);
}
@@ -668,28 +642,25 @@ std::string const PlayerbotHolder::ProcessBotCommand(std::string const cmd, Obje
return "bot system is disabled";
uint32 botAccount = sCharacterCache->GetCharacterAccountIdByGuid(guid);
//bool isRandomBot = sRandomPlayerbotMgr->IsRandomBot(guid.GetCounter()); //not used, line marked for removal.
//bool isRandomAccount = sPlayerbotAIConfig->IsInRandomAccountList(botAccount); //not used, shadowed, line marked for removal.
//bool isMasterAccount = (masterAccountId == botAccount); //not used, line marked for removal.
bool isRandomBot = sRandomPlayerbotMgr->IsRandomBot(guid.GetCounter());
bool isRandomAccount = sPlayerbotAIConfig->IsInRandomAccountList(botAccount);
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))
return "player already logged in";
// For addaccount command, verify it's an account name
if (cmd == "addaccount")
{
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";
}
}
if (!sPlayerbotAIConfig->allowPlayerBots && !isRandomAccount && !isMasterAccount)
return "You cannot login another player's character as bot.";
AddPlayerBot(guid, masterAccountId);
return "ok";
@@ -715,10 +686,10 @@ std::string const PlayerbotHolder::ProcessBotCommand(std::string const cmd, Obje
if (!bot)
return "bot not found";
bool addClassBot = sRandomPlayerbotMgr->IsAddclassBot(guid.GetCounter());
if (!addClassBot)
return "ERROR: You can not use this command on non-addclass bot.";
if (!isRandomAccount || isRandomBot)
{
return "ERROR: You can not use this command on non-summoned random bot.";
}
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.";
}
// Use boot guard
BotInitGuard guard(bot->GetGUID());
if (guard.IsLocked())
{
return "Initialization already in progress, please wait.";
}
int gs;
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) *
sPlayerbotAIConfig->autoInitEquipLevelLimitRatio;
// work around: distinguish from 0 if no gear
if (mixedGearScore == 0)
mixedGearScore = 1;
PlayerbotFactory factory(bot, master->GetLevel(), ITEM_QUALITY_LEGENDARY, mixedGearScore);
factory.Randomize(false);
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)
{
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");
return messages;
}
@@ -1107,22 +1067,15 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
messages.push_back("Error: Invalid Class. Try again.");
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);
const std::unordered_set<ObjectGuid> &guidCache = sRandomPlayerbotMgr->addclassCache[RandomPlayerbotMgr::GetTeamClassIdx(teamId == TEAM_ALLIANCE, claz)];
for (const ObjectGuid &guid: guidCache)
std::vector<ObjectGuid> &guidCache = sRandomPlayerbotMgr->addclassCache[RandomPlayerbotMgr::GetTeamClassIdx(teamId == TEAM_ALLIANCE, claz)];
for (size_t i = 0; i < guidCache.size(); i++)
{
ObjectGuid guid = guidCache[i];
if (botLoading.find(guid) != botLoading.end())
continue;
if (ObjectAccessor::FindConnectedPlayer(guid))
continue;
uint32 guildId = sCharacterCache->GetCharacterGuildIdByGuid(guid);
if (guildId && PlayerbotAI::IsRealGuild(guildId))
continue;
AddPlayerBot(guid, master->GetSession()->GetAccountId());
messages.push_back("Add class " + std::string(charname));
return messages;
@@ -1194,48 +1147,22 @@ std::vector<std::string> PlayerbotHolder::HandlePlayerbotCommand(char const* arg
{
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);
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 +1645,3 @@ PlayerbotMgr* PlayerbotsMgr::GetPlayerbotMgr(Player* player)
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(){};
void AddPlayerBot(ObjectGuid guid, uint32 masterAccountId);
bool IsAccountLinked(uint32 accountId, uint32 masterAccountId);
void HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder const& holder);
void LogoutPlayerBot(ObjectGuid guid);
void DisablePlayerBot(ObjectGuid guid);
void RemoveFromPlayerbotsMap(ObjectGuid guid);
Player* GetPlayerBot(ObjectGuid guid) const;
Player* GetPlayerBot(ObjectGuid::LowType lowGuid) const;
PlayerBotMap::const_iterator GetPlayerBotsBegin() const { return playerBots.begin(); }
@@ -83,11 +81,6 @@ public:
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:
void OnBotLoginInternal(Player* const bot) override;
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 fromGS = (int32)botAI->GetEquipGearScore(from/*, false, false*/);
int32 botGS = (int32)botAI->GetEquipGearScore(bot, false, false);
int32 fromGS = (int32)botAI->GetEquipGearScore(from, false, false);
if (sPlayerbotAIConfig->gearscorecheck)
{
if (botGS && bot->GetLevel() > 15 && botGS > fromGS &&
@@ -139,7 +139,7 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
return PLAYERBOT_SECURITY_INVITE;
}
if (!ignoreGroup && group->IsFull())
if (group->IsFull())
{
if (reason)
*reason = PLAYERBOT_DENY_FULL_GROUP;
@@ -147,7 +147,7 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
return PLAYERBOT_SECURITY_TALK;
}
if (!ignoreGroup && group->GetLeaderGUID() != bot->GetGUID())
if (group->GetLeaderGUID() != bot->GetGUID())
{
if (reason)
*reason = PLAYERBOT_DENY_NOT_LEADER;
@@ -211,8 +211,8 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
break;
case PLAYERBOT_DENY_GEARSCORE:
{
int botGS = (int)botAI->GetEquipGearScore(bot/*, false, false*/);
int fromGS = (int)botAI->GetEquipGearScore(from/*, false, false*/);
int botGS = (int)botAI->GetEquipGearScore(bot, false, false);
int fromGS = (int)botAI->GetEquipGearScore(from, false, false);
int diff = (100 * (botGS - fromGS) / botGS);
int req = 12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel();
out << "Your gearscore is too low: |cffff0000" << fromGS << "|cffffffff/|cff00ff00" << botGS

View File

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

View File

@@ -23,8 +23,6 @@
#include "DatabaseLoader.h"
#include "GuildTaskMgr.h"
#include "Metric.h"
#include "PlayerScript.h"
#include "PlayerbotAIConfig.h"
#include "RandomPlayerbotMgr.h"
#include "ScriptMgr.h"
#include "cs_playerbots.h"
@@ -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
{
public:
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
}) {}
PlayerbotsPlayerScript() : PlayerScript("PlayerbotsPlayerScript") {}
void OnPlayerLogin(Player* player) override
void OnLogin(Player* player) override
{
if (!player->GetSession()->IsBot())
{
sPlayerbotsMgr->AddPlayerbotData(player, false);
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)
{
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);
ChatHandler(player->GetSession()).SendSysMessage(
"|cff00ff00Playerbots:|r bot initialization at server startup takes about '"
+ roundedTime + "' minutes.");
"Playerbots: bot initialization at server startup will require '" + roundedTime + "' minutes.");
}
}
}
void OnPlayerAfterUpdate(Player* player, uint32 diff) override
void OnAfterUpdate(Player* player, uint32 diff) override
{
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)
{
@@ -148,7 +138,7 @@ public:
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())
{
@@ -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)
{
@@ -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))
{
@@ -196,7 +186,7 @@ public:
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))
{
@@ -205,7 +195,7 @@ public:
return true;
}
bool OnPlayerBeforeAchievementComplete(Player* player, AchievementEntry const* /*achievement*/) override
bool OnBeforeAchiComplete(Player* player, AchievementEntry const* /*achievement*/) override
{
if (sRandomPlayerbotMgr->IsRandomBot(player))
{
@@ -213,17 +203,6 @@ public:
}
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
@@ -248,9 +227,7 @@ public:
class PlayerbotsServerScript : public ServerScript
{
public:
PlayerbotsServerScript() : ServerScript("PlayerbotsServerScript", {
SERVERHOOK_CAN_PACKET_RECEIVE
}) {}
PlayerbotsServerScript() : ServerScript("PlayerbotsServerScript") {}
void OnPacketReceived(WorldSession* session, WorldPacket const& packet) override
{
@@ -263,29 +240,12 @@ public:
class PlayerbotsWorldScript : public WorldScript
{
public:
PlayerbotsWorldScript() : WorldScript("PlayerbotsWorldScript", {
WORLDHOOK_ON_BEFORE_WORLD_INITIALIZED
}) {}
PlayerbotsWorldScript() : WorldScript("PlayerbotsWorldScript") {}
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();
LOG_INFO("server.loading", " ");
LOG_INFO("server.loading", "Load Playerbots Config...");
@@ -379,16 +339,13 @@ public:
sRandomPlayerbotMgr->OnPlayerLogout(player);
}
void OnPlayerbotLogoutBots() override
{
LOG_INFO("playerbots", "Logging out all bots...");
sRandomPlayerbotMgr->LogoutAllBots();
}
void OnPlayerbotLogoutBots() override { sRandomPlayerbotMgr->LogoutAllBots(); }
};
void AddPlayerbotsScripts()
{
new PlayerbotsDatabaseScript();
new PlayerbotsMetricScript();
new PlayerbotsPlayerScript();
new PlayerbotsMiscScript();
new PlayerbotsServerScript();

View File

@@ -833,7 +833,7 @@ bool RandomItemMgr::CanEquipWeapon(uint8 clazz, ItemTemplate const* proto)
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
LOG_INFO("playerbots", "Loading weightscales info");
@@ -1347,7 +1347,7 @@ uint32 RandomItemMgr::CalculateStatWeight(uint8 playerclass, uint8 spec, ItemTem
// check weapon dps
if (proto->IsWeaponVellum())
{
//WeaponAttackType attType = BASE_ATTACK; //not used, line marked for removal.
WeaponAttackType attType = BASE_ATTACK;
uint32 dps = 0;
for (uint8 i = 0; i < MAX_ITEM_PROTO_DAMAGES; i++)
@@ -2206,7 +2206,7 @@ void RandomItemMgr::BuildEquipCacheNew()
ObjectMgr::QuestMap const& questTemplates = sObjectMgr->GetQuestTemplates();
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;
if (quest->IsRepeatable())
@@ -2313,9 +2313,8 @@ void RandomItemMgr::BuildAmmoCache()
for (uint32 subClass = ITEM_SUBCLASS_ARROW; subClass <= ITEM_SUBCLASS_BULLET; subClass++)
{
QueryResult results = WorldDatabase.Query(
"SELECT entry 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, ItemLevel DESC",
"SELECT entry, Flags FROM item_template WHERE class = {} AND subclass = {} AND RequiredLevel <= {} and duration = 0 "
"ORDER BY stackable DESC, RequiredLevel DESC",
ITEM_CLASS_PROJECTILE, subClass, level);
if (!results)
continue;
@@ -2323,27 +2322,35 @@ void RandomItemMgr::BuildAmmoCache()
{
Field* fields = results->Fetch();
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;
break;
} 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()
{
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();
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};
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->Bonding != NO_BIND)
continue;
uint32 requiredLevel = proto->RequiredLevel;
if (requiredLevel > level || (level > 13 && requiredLevel < level - 13))
if (proto->RequiredLevel && (proto->RequiredLevel > level || proto->RequiredLevel < level - 10))
continue;
if (proto->RequiredSkill)
@@ -2374,44 +2380,39 @@ void RandomItemMgr::BuildPotionCache()
if (proto->Duration & 0x80000000)
continue;
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++)
for (uint8 j = 0; j < MAX_ITEM_PROTO_SPELLS; j++)
{
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;
break;
if (spellInfo->Effects[i].Effect == effect)
{
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};
for (uint8 i = 0; i < 2; ++i)
{
uint32 effect = effects[i];
uint32 size = potionCache[level][effect].size();
counter += size;
uint32 size = potionCache[level / 10][effect].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()
@@ -2477,7 +2478,7 @@ void RandomItemMgr::BuildFoodCache()
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())
return 0;

View File

@@ -157,7 +157,7 @@ public:
uint32 GetStatWeight(Player* player, uint32 itemId);
uint32 GetLiveStatWeight(Player* player, uint32 itemId);
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 GetRandomFood(uint32 level, uint32 category);
uint32 GetFood(uint32 level, uint32 category);
@@ -195,7 +195,7 @@ private:
std::map<RandomItemType, RandomItemPredicate*> predicates;
BotEquipCache equipCache;
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>>> foodCache;
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
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
* 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 "RandomPlayerbotFactory.h"
@@ -14,12 +14,11 @@
#include "ScriptMgr.h"
#include "SharedDefines.h"
#include "SocialMgr.h"
#include "Timer.h"
std::map<uint8, std::vector<uint8>> RandomPlayerbotFactory::availableRaces;
constexpr RandomPlayerbotFactory::NameRaceAndGender RandomPlayerbotFactory::CombineRaceAndGender(uint8 gender,
uint8 race)
uint8 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_GNOME);
availableRaces[CLASS_ROGUE].push_back(RACE_ORC);
availableRaces[CLASS_ROGUE].push_back(RACE_UNDEAD_PLAYER);
availableRaces[CLASS_ROGUE].push_back(RACE_TROLL);
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> hair = hairs[urand(0, hairs.size() - 1)];
@@ -244,7 +242,7 @@ Player* RandomPlayerbotFactory::CreateRandomBot(WorldSession* session, uint8 cls
delete player;
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;
}
@@ -256,7 +254,7 @@ Player* RandomPlayerbotFactory::CreateRandomBot(WorldSession* session, uint8 cls
player->learnSpell(50977, false);
}
LOG_DEBUG("playerbots", "Random bot created for account {} - name: \"{}\"; race: {}; class: {}", accountId,
name.c_str(), race, cls);
name.c_str(), race, cls);
return player;
}
@@ -278,19 +276,13 @@ std::string const RandomPlayerbotFactory::CreateRandomBotName(NameRaceAndGender
{
break;
}
Field* fields = result->Fetch();
botName = fields[0].Get<std::string>();
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;
}
}
}
// CONLANG NAME GENERATION
@@ -353,14 +345,6 @@ std::string const RandomPlayerbotFactory::CreateRandomBotName(NameRaceAndGender
botName.clear();
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);
}
@@ -378,14 +362,6 @@ std::string const RandomPlayerbotFactory::CreateRandomBotName(NameRaceAndGender
botName.clear();
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);
}
LOG_ERROR("playerbots", "Random name generation failed.");
@@ -393,76 +369,15 @@ std::string const RandomPlayerbotFactory::CreateRandomBotName(NameRaceAndGender
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()
{
/* multi-thread here is meaningless? since the async db operations */
if (sPlayerbotAIConfig->deleteRandomBotAccounts)
{
std::vector<uint32> botAccounts;
std::vector<uint32> botFriends;
// Calculates the total number of required accounts.
uint32 totalAccountCount = CalculateTotalAccountCount();
for (uint32 accountNumber = 0; accountNumber < totalAccountCount; ++accountNumber)
for (uint32 accountNumber = 0; accountNumber < sPlayerbotAIConfig->randomBotAccountCount; ++accountNumber)
{
std::ostringstream out;
out << sPlayerbotAIConfig->randomBotAccountPrefix << accountNumber;
@@ -472,79 +387,9 @@ void RandomPlayerbotFactory::CreateRandomBots()
botAccounts.push_back(accountId);
}
LOG_INFO("playerbots", "Deleting all random bot characters and accounts...");
// 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...");
LOG_INFO("playerbots", "Deleting all random bot characters, {} accounts collected...", botAccounts.size());
QueryResult results = LoginDatabase.Query("SELECT id FROM account WHERE username LIKE '{}%%'",
sPlayerbotAIConfig->randomBotAccountPrefix.c_str());
sPlayerbotAIConfig->randomBotAccountPrefix.c_str());
int32 deletion_count = 0;
if (results)
{
@@ -552,19 +397,16 @@ void RandomPlayerbotFactory::CreateRandomBots()
{
Field* fields = results->Fetch();
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);
} while (results->NextRow());
}
uint32 timer = getMSTime();
// Wait for all pending database operations to complete
while (LoginDatabase.QueueSize() || CharacterDatabase.QueueSize() || PlayerbotsDatabase.QueueSize())
{
std::this_thread::sleep_for(1s);
}
LOG_INFO("playerbots", ">> Random bot accounts and data deleted in {} ms", GetMSTimeDiffToNow(timer));
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
correctly the better way is turning the async db operation to sync db operation */
std::this_thread::sleep_for(10ms * sPlayerbotAIConfig->randomBotAccountCount);
LOG_INFO("playerbots", "Random bot characters deleted.");
LOG_INFO("playerbots", "Please reset the AiPlayerbot.DeleteRandomBotAccounts to 0 and restart the server...");
World::StopNow(SHUTDOWN_EXIT_CODE);
return;
@@ -572,14 +414,28 @@ void RandomPlayerbotFactory::CreateRandomBots()
LOG_INFO("playerbots", "Creating random bot accounts...");
std::unordered_map<NameRaceAndGender, std::vector<std::string>> nameCache;
uint32 totalAccCount = sPlayerbotAIConfig->randomBotAccountCount;
std::vector<std::future<void>> account_creations;
int account_creation = 0;
// Calculates the total number of required accounts.
uint32 totalAccountCount = CalculateTotalAccountCount();
uint32 timer = getMSTime();
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)
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;
out << sPlayerbotAIConfig->randomBotAccountPrefix << accountNumber;
@@ -608,26 +464,22 @@ void RandomPlayerbotFactory::CreateRandomBots()
LOG_DEBUG("playerbots", "Account {} created for random bots", accountName.c_str());
}
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 */
while (LoginDatabase.QueueSize())
{
std::this_thread::sleep_for(1s);
}
LOG_INFO("playerbots", ">> {} Accounts loaded into database in {} ms", account_creation, GetMSTimeDiffToNow(timer));
/* wait for async accounts create to make character create correctly, same as account delete */
LOG_INFO("playerbots", "Waiting for {} accounts loading into database...", account_creation);
std::this_thread::sleep_for(10ms * sPlayerbotAIConfig->randomBotAccountCount);
}
LOG_INFO("playerbots", "Creating random bot characters...");
uint32 totalRandomBotChars = 0;
uint32 totalCharCount = sPlayerbotAIConfig->randomBotAccountCount * 10;
std::vector<std::pair<Player*, uint32>> playerBots;
std::vector<WorldSession*> sessionBots;
int bot_creation = 0;
timer = getMSTime();
bool nameCached = false;
for (uint32 accountNumber = 0; accountNumber < totalAccountCount; ++accountNumber)
for (uint32 accountNumber = 0; accountNumber < sPlayerbotAIConfig->randomBotAccountCount; ++accountNumber)
{
std::ostringstream out;
out << sPlayerbotAIConfig->randomBotAccountPrefix << accountNumber;
@@ -649,41 +501,12 @@ void RandomPlayerbotFactory::CreateRandomBots()
{
continue;
}
if (!nameCached)
{
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);
LOG_INFO("playerbots", "Creating random bot characters for account: [{}/{}]", accountNumber + 1,
sPlayerbotAIConfig->randomBotAccountCount);
RandomPlayerbotFactory factory(accountId);
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);
for (uint8 cls = CLASS_WARRIOR; cls < MAX_CLASSES - count; ++cls)
@@ -720,13 +543,9 @@ void RandomPlayerbotFactory::CreateRandomBots()
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 */
while (CharacterDatabase.QueueSize())
{
std::this_thread::sleep_for(1s);
}
LOG_INFO("playerbots", ">> {} Characters loaded into database in {} ms", bot_creation, GetMSTimeDiffToNow(timer));
std::this_thread::sleep_for(5s + bot_creation * 5ms);
}
for (WorldSession* session : sessionBots)
@@ -738,7 +557,7 @@ void RandomPlayerbotFactory::CreateRandomBots()
}
LOG_INFO("server.loading", ">> {} random bot accounts with {} characters available",
sPlayerbotAIConfig->randomBotAccounts.size(), totalRandomBotChars);
sPlayerbotAIConfig->randomBotAccounts.size(), totalRandomBotChars);
}
void RandomPlayerbotFactory::CreateRandomGuilds()
@@ -808,7 +627,7 @@ void RandomPlayerbotFactory::CreateRandomGuilds()
if (!player)
{
LOG_ERROR("playerbots", "ObjectAccessor Cannot find player to set leader for guild {} . Skipped...",
guildName.c_str());
guildName.c_str());
continue;
}
@@ -819,7 +638,7 @@ void RandomPlayerbotFactory::CreateRandomGuilds()
if (!guild->Create(player, guildName))
{
LOG_ERROR("playerbots", "Error creating guild [ {} ] with leader [ {} ]", guildName.c_str(),
player->GetName().c_str());
player->GetName().c_str());
delete guild;
continue;
}
@@ -981,7 +800,7 @@ void RandomPlayerbotFactory::CreateRandomArenaTeams(ArenaType type, uint32 count
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()

View File

@@ -54,8 +54,6 @@ public:
static void CreateRandomGuilds();
static void CreateRandomArenaTeams(ArenaType slot, uint32 count);
static std::string const CreateRandomGuildName();
static uint32 CalculateTotalAccountCount();
static uint32 CalculateAvailableCharsPerAccount();
private:
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
#define _PLAYERBOT_RANDOMPLAYERBOTMGR_H
#include "NewRpgInfo.h"
#include "ObjectGuid.h"
#include "PlayerbotMgr.h"
@@ -112,8 +111,6 @@ public:
static bool HandlePlayerbotConsoleCommand(ChatHandler* handler, char const* args);
bool IsRandomBot(Player* bot);
bool IsRandomBot(ObjectGuid::LowType bot);
bool IsAddclassBot(Player* bot);
bool IsAddclassBot(ObjectGuid::LowType bot);
void Randomize(Player* bot);
void Clear(Player* bot);
void RandomizeFirst(Player* bot);
@@ -173,19 +170,10 @@ public:
static uint8 GetTeamClassIdx(bool isAlliance, uint8 claz) { return isAlliance * 20 + claz; }
void PrepareAddclassCache();
void PrepareZone2LevelBracket();
void PrepareTeleportCache();
void Init();
std::map<uint8, std::unordered_set<ObjectGuid>> addclassCache;
std::map<uint8, std::vector<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:
void OnBotLoginInternal(Player* const bot) override;
@@ -195,8 +183,6 @@ private:
botPID pid = botPID(1, 50, -50, 0, 0, 0);
float activityMod = 0.25;
bool _isBotInitializing = true;
bool _isBotLogging = true;
NewRpgStatistic rpgStasticTotal;
uint32 GetEventValue(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,
@@ -213,6 +199,7 @@ private:
void RandomTeleport(Player* bot);
void RandomTeleport(Player* bot, std::vector<WorldLocation>& locs, bool hearth = false);
uint32 GetZoneLevel(uint16 mapId, float teleX, float teleY, float teleZ);
void PrepareTeleportCache();
typedef void (RandomPlayerbotMgr::*ConsoleCommandHandler)(Player*);
std::vector<Player*> players;

View File

@@ -260,7 +260,7 @@ void TalentSpec::ReadTalents(Player* bot)
// Set the talent ranks to the ranks of the link.
void TalentSpec::ReadTalents(std::string const link)
{
//uint32 rank = 0; //not used, line marked for removal.
uint32 rank = 0;
uint32 pos = 0;
uint32 tab = 0;
std::string chr;
@@ -397,7 +397,7 @@ uint32 TalentSpec::highestTree()
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;
// 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.
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
{
CropTalents(level);

View File

@@ -22,26 +22,12 @@
WorldPosition::WorldPosition(std::string const str)
{
std::vector<std::string> tokens = split(str, '|');
if (tokens.size() == 5)
{
try
{
m_mapId = std::stoi(tokens[0]);
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;
}
}
std::stringstream out(str);
out >> this->m_mapId;
out >> this->m_positionX;
out >> this->m_positionY;
out >> this->m_positionZ;
out >> this->m_orientation;
}
WorldPosition::WorldPosition(uint32 mapId, const Position& pos)
@@ -375,25 +361,13 @@ std::string const WorldPosition::print()
std::string const WorldPosition::to_string()
{
std::stringstream out;
out << m_mapId << '|';
out << m_positionX << '|';
out << m_positionY << '|';
out << m_positionZ << '|';
out << m_orientation;
out << GetMapId();
out << GetPositionX();
out << GetPositionY();
out << GetPositionZ();
out << GetOrientation();
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)
{
@@ -644,7 +618,7 @@ void WorldPosition::loadMapAndVMap(uint32 mapId, uint8 x, uint8 y)
{
// load VMAPs for current map/grid...
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(
(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))
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"))
{

View File

@@ -131,7 +131,6 @@ public:
std::string const print();
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::ostringstream& out) { printWKT({*this}, out); }

View File

@@ -594,7 +594,7 @@ bool TravelNode::isEqual(TravelNode* compareNode)
void TravelNode::print([[maybe_unused]] bool printFailed)
{
// WorldPosition* startPosition = getPosition(); //not used, line marked for removal.
WorldPosition* startPosition = getPosition();
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,
float range)
{
//float x = pos.getX(); //not used, line marked for removal.
//float y = pos.getY(); //not used, line marked for removal.
//float z = pos.getZ(); //not used, line marked for removal.
float x = pos.getX();
float y = pos.getY();
float z = pos.getZ();
if (bot && !bot->GetMap())
return nullptr;
@@ -1348,9 +1348,6 @@ TravelNodeRoute TravelNodeMap::getRoute(WorldPosition startPos, WorldPosition en
std::vector<WorldPosition> newStartPath;
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.
std::partial_sort(startNodes.begin(), startNodes.begin() + 5, startNodes.end(),
[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())
{
//TravelNode* endNode = path.first; //not used, line marked for removal.
TravelNode* endNode = path.first;
std::string zoneName = startNode->getPosition()->getAreaName(true, true);
for (auto& pos : path.second.getPath())
@@ -1641,7 +1638,7 @@ void TravelNodeMap::generateNpcNodes()
else if (cInfo->npcflag & UNIT_NPC_FLAG_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)
{
@@ -1757,7 +1754,7 @@ void TravelNodeMap::generateAreaTriggerNodes()
else
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.
@@ -1979,7 +1976,7 @@ void TravelNodeMap::generateZoneMeanNodes()
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))
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)
{
uint32 rem = 0;
@@ -2210,7 +2207,7 @@ void TravelNodeMap::printMap()
std::vector<TravelNode*> anodes = getNodes();
//uint32 nr = 0; //not used, line marked for removal.
uint32 nr = 0;
for (auto& node : anodes)
{

View File

@@ -33,21 +33,12 @@ public:
static ChatCommandTable playerbotsDebugCommandTable = {
{"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 = {
{"bot", HandlePlayerbotCommand, SEC_PLAYER, Console::No},
{"gtask", HandleGuildTaskCommand, SEC_GAMEMASTER, Console::Yes},
{"pmon", HandlePerfMonCommand, SEC_GAMEMASTER, Console::Yes},
{"rndbot", HandleRandomPlayerbotCommand, SEC_GAMEMASTER, Console::Yes},
{"debug", playerbotsDebugCommandTable},
{"account", playerbotsAccountCommandTable},
};
static ChatCommandTable commandTable = {
@@ -110,103 +101,6 @@ public:
{
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(); }

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

View File

@@ -5,14 +5,11 @@
#include "DBCStores.h"
#include "ItemTemplate.h"
#include "ObjectMgr.h"
#include "PlayerbotAI.h"
#include "PlayerbotAIAware.h"
#include "SharedDefines.h"
#include "SpellAuraDefines.h"
#include "SpellInfo.h"
#include "SpellMgr.h"
#include "UpdateFields.h"
#include "Util.h"
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);
break;
case ITEM_SPELLTRIGGER_CHANCE_ON_HIT:
if (type_ & CollectorType::MELEE)
if (type_ == CollectorType::MELEE)
{
if (proto->Spells[j].SpellPPMRate > 0.01f)
CollectSpellStats(proto->Spells[j].SpellId, 1.0f, 60000 / proto->Spells[j].SpellPPMRate);
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;
default:
@@ -70,7 +67,7 @@ void StatsCollector::CollectItemStats(ItemTemplate const* proto)
if (proto->socketBonus)
{
if (const SpellItemEnchantmentEntry* enchant = sSpellItemEnchantmentStore.LookupEntry(proto->socketBonus))
if (const SpellItemEnchantmentEntry *enchant = sSpellItemEnchantmentStore.LookupEntry(proto->socketBonus))
CollectEnchantStats(enchant);
}
}
@@ -97,50 +94,34 @@ void StatsCollector::CollectSpellStats(uint32 spellId, float multiplier, int32 s
procFlags = eventEntry->procFlags;
else
procFlags = spellInfo->ProcFlags;
if (eventEntry && eventEntry->customChance)
procChance = eventEntry->customChance;
else
procChance = spellInfo->ProcChance;
bool lowChance = procChance <= 5;
bool lowChance = procChance <= 5;
if (lowChance || (procFlags && !CanBeTriggeredByType(spellInfo, procFlags)))
canNextTrigger = false;
if (spellInfo->StackAmount)
{
// Heuristic multiplier for spell with stackAmount since high stackAmount may not be available
if (spellInfo->StackAmount <= 1)
multiplier *= spellInfo->StackAmount * 1;
else if (spellInfo->StackAmount <= 5)
multiplier *= 1 + (spellInfo->StackAmount - 1) * 0.75;
else if (spellInfo->StackAmount <= 10)
multiplier *= 4 + (spellInfo->StackAmount - 5) * 0.6;
if (spellInfo->StackAmount <= 10)
multiplier *= spellInfo->StackAmount * 0.6;
else if (spellInfo->StackAmount <= 20)
multiplier *= 7 + (spellInfo->StackAmount - 10) * 0.4;
multiplier *= 6 + (spellInfo->StackAmount - 10) * 0.4;
else
multiplier *= 11;
multiplier *= 10;
}
for (int i = 0; i < MAX_SPELL_EFFECTS; i++)
{
const SpellEffectInfo& effectInfo = spellInfo->Effects[i];
if (!effectInfo.Effect)
continue;
switch (effectInfo.Effect)
{
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
if (!spellInfo->IsPositive())
break;
@@ -149,8 +130,7 @@ void StatsCollector::CollectSpellStats(uint32 spellId, float multiplier, int32 s
if (spellCooldown <= 2000 || spellInfo->GetDuration() == -1)
coverage = 1.0f;
else
coverage =
std::min(1.0f, (float)spellInfo->GetDuration() / (spellInfo->GetDuration() + spellCooldown));
coverage = std::min(1.0f, (float)spellInfo->GetDuration() / (spellInfo->GetDuration() + spellCooldown));
multiplier *= coverage;
HandleApplyAura(effectInfo, multiplier, canNextTrigger, triggerCooldown);
@@ -187,12 +167,12 @@ void StatsCollector::CollectSpellStats(uint32 spellId, float multiplier, int32 s
break;
float normalizedCd = std::max((float)spellCooldown / 1000, 5.0f);
int32 val = AverageValue(effectInfo);
if (type_ & (CollectorType::MELEE | CollectorType::RANGED))
if (type_ == CollectorType::MELEE || type_ == CollectorType::RANGED)
{
float transfer_multiplier = 1;
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;
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:
{
if (type_ & CollectorType::MELEE)
if (type_ == CollectorType::MELEE)
CollectSpellStats(enchant_spell_id, 0.25f);
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.
bool StatsCollector::SpecialSpellFilter(uint32 spellId)
{
bool StatsCollector::SpecialSpellFilter(uint32 spellId) {
// trinket
switch (spellId)
{
case 60764: // Totem of Splintering
if (type_ & (CollectorType::SPELL))
return true;
break;
case 27521: // Insightful Earthstorm Diamond
case 27521: // Insightful Earthstorm Diamond
stats[STATS_TYPE_MANA_REGENERATION] += 20;
return true;
case 55381: // Insightful Earthsiege Diamond
case 55381: // Insightful Earthsiege Diamond
stats[STATS_TYPE_MANA_REGENERATION] += 40;
return true;
case 39442: // Darkmoon Card: Wrath
if (!(type_ & CollectorType::SPELL_HEAL))
case 39442: // Darkmoon Card: Wrath
if (type_ != CollectorType::SPELL_HEAL)
stats[STATS_TYPE_CRIT] += 50;
return true;
case 59620: // Berserk
if (type_ & CollectorType::MELEE)
case 59620: // Berserk
if (type_ == CollectorType::MELEE)
stats[STATS_TYPE_ATTACK_POWER] += 120;
return true;
case 67702: // Death's Verdict
case 67702: // Death's Verdict
stats[STATS_TYPE_ATTACK_POWER] += 225;
return true;
case 67771: // Death's Verdict (heroic)
case 67771: // Death's Verdict (heroic)
stats[STATS_TYPE_ATTACK_POWER] += 260;
return true;
case 71406: // Tiny Abomination in a Jar
case 71406: // Tiny Abomination in a Jar
if (cls_ == CLASS_PALADIN)
stats[STATS_TYPE_ATTACK_POWER] += 600;
else
stats[STATS_TYPE_ATTACK_POWER] += 150;
return true;
case 71545: // Tiny Abomination in a Jar (heroic)
case 71545: // Tiny Abomination in a Jar (heroic)
if (cls_ == CLASS_PALADIN)
stats[STATS_TYPE_ATTACK_POWER] += 800;
else
stats[STATS_TYPE_ATTACK_POWER] += 200;
return true;
case 71519: // Deathbringer's Will
return true;
case 71519: // Deathbringer's Will
stats[STATS_TYPE_ATTACK_POWER] += 350;
return true;
case 71562: // Deathbringer's Will (heroic)
case 71562: // Deathbringer's Will (heroic)
stats[STATS_TYPE_ATTACK_POWER] += 400;
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
/// 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;
break;
case 71903: // Shadowmourne
case 71903: // Shadowmourne
stats[STATS_TYPE_STRENGTH] += 200;
return true;
default:
@@ -320,26 +295,26 @@ bool StatsCollector::SpecialEnchantFilter(uint32 enchantSpellId)
switch (enchantSpellId)
{
case 64440:
if (type_ & CollectorType::MELEE)
if (type_ == CollectorType::MELEE)
{
stats[STATS_TYPE_PARRY] += 50;
}
return true;
case 53365: // Rune of the Fallen Crusader
if (type_ & CollectorType::MELEE)
case 53365: // Rune of the Fallen Crusader
if (type_ == CollectorType::MELEE)
{
stats[STATS_TYPE_STRENGTH] += 75;
}
return true;
case 62157: // Rune of the Stoneskin Gargoyle
if (type_ & CollectorType::MELEE)
case 62157: // Rune of the Stoneskin Gargoyle
if (type_ == CollectorType::MELEE)
{
stats[STATS_TYPE_DEFENSE] += 25;
stats[STATS_TYPE_STAMINA] += 50;
}
return true;
case 64571: // Blood draining
if (type_ & CollectorType::MELEE)
case 64571: // Blood draining
if (type_ == CollectorType::MELEE)
{
stats[STATS_TYPE_STAMINA] += 50;
}
@@ -350,34 +325,18 @@ bool StatsCollector::SpecialEnchantFilter(uint32 enchantSpellId)
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);
uint32 spellFamilyName = 0;
if (eventEntry)
{
spellFamilyName = eventEntry->spellFamilyName;
flag96 spellFamilyMask = eventEntry->spellFamilyMask;
if (spellFamilyName != 0)
{
if (!CheckSpellValidation(spellFamilyName, spellFamilyMask, strict))
return false;
}
}
uint32 spellFamilyName = eventEntry ? eventEntry->spellFamilyName : 0;
uint32 triggerMask = TAKEN_HIT_PROC_FLAG_MASK; // Generic trigger mask
switch (type_)
{
case CollectorType::MELEE_DMG:
{
triggerMask |= MELEE_PROC_FLAG_MASK;
triggerMask |= SPELL_PROC_FLAG_MASK;
triggerMask |= PROC_FLAG_DONE_PERIODIC;
if (procFlags & triggerMask)
return true;
break;
}
case CollectorType::MELEE_TANK:
if (spellFamilyName != 0)
/// @todo Check specific trigger spell by spellFamilyMask
return true;
uint32 triggerMask = TAKEN_HIT_PROC_FLAG_MASK; // Generic trigger mask
switch (type_) {
case CollectorType::MELEE:
{
triggerMask |= MELEE_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 |= SPELL_PROC_FLAG_MASK;
triggerMask |= PROC_FLAG_DONE_PERIODIC;
triggerMask |= PERIODIC_PROC_FLAG_MASK;
if (procFlags & triggerMask)
return true;
break;
@@ -398,7 +357,6 @@ bool StatsCollector::CanBeTriggeredByType(SpellInfo const* spellInfo, uint32 pro
case CollectorType::SPELL_DMG:
{
triggerMask |= SPELL_PROC_FLAG_MASK;
triggerMask |= PROC_FLAG_DONE_PERIODIC;
// Healing spell cannot trigger
triggerMask &= ~PROC_FLAG_DONE_SPELL_NONE_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:
{
triggerMask |= SPELL_PROC_FLAG_MASK;
triggerMask |= PROC_FLAG_DONE_PERIODIC;
// Dmg spell should not trigger
triggerMask &= ~PROC_FLAG_DONE_SPELL_NONE_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)
return true;
break;
@@ -464,39 +419,39 @@ void StatsCollector::CollectByItemStatType(uint32 itemStatType, int32 val)
stats[STATS_TYPE_BLOCK_RATING] += val;
break;
case ITEM_MOD_HIT_MELEE_RATING:
if (type_ & CollectorType::MELEE)
if (type_ == CollectorType::MELEE)
stats[STATS_TYPE_HIT] += val;
break;
case ITEM_MOD_HIT_RANGED_RATING:
if (type_ & CollectorType::RANGED)
if (type_ == CollectorType::RANGED)
stats[STATS_TYPE_HIT] += val;
break;
case ITEM_MOD_HIT_SPELL_RATING:
if (type_ & CollectorType::SPELL)
if (type_ == CollectorType::SPELL)
stats[STATS_TYPE_HIT] += val;
break;
case ITEM_MOD_CRIT_MELEE_RATING:
if (type_ & CollectorType::MELEE)
if (type_ == CollectorType::MELEE)
stats[STATS_TYPE_CRIT] += val;
break;
case ITEM_MOD_CRIT_RANGED_RATING:
if (type_ & CollectorType::RANGED)
if (type_ == CollectorType::RANGED)
stats[STATS_TYPE_CRIT] += val;
break;
case ITEM_MOD_CRIT_SPELL_RATING:
if (type_ & CollectorType::SPELL)
if (type_ == CollectorType::SPELL)
stats[STATS_TYPE_CRIT] += val;
break;
case ITEM_MOD_HASTE_MELEE_RATING:
if (type_ & CollectorType::MELEE)
if (type_ == CollectorType::MELEE)
stats[STATS_TYPE_HASTE] += val;
break;
case ITEM_MOD_HASTE_RANGED_RATING:
if (type_ & CollectorType::RANGED)
if (type_ == CollectorType::RANGED)
stats[STATS_TYPE_HASTE] += val;
break;
case ITEM_MOD_HASTE_SPELL_RATING:
if (type_ & CollectorType::SPELL)
if (type_ == CollectorType::SPELL)
stats[STATS_TYPE_HASTE] += val;
break;
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,
uint32 triggerCooldown)
void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float multiplier, bool canNextTrigger, uint32 triggerCooldown)
{
if (effectInfo.Effect != SPELL_EFFECT_APPLY_AURA)
return;
int32 val = AverageValue(effectInfo);
switch (effectInfo.ApplyAuraName)
{
case SPELL_AURA_MOD_DAMAGE_DONE:
@@ -580,11 +534,11 @@ void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float mu
break;
}
case SPELL_AURA_MOD_ATTACK_POWER:
if (type_ & CollectorType::MELEE)
if (type_ == CollectorType::MELEE)
stats[STATS_TYPE_ATTACK_POWER] += val * multiplier;
break;
case SPELL_AURA_MOD_RANGED_ATTACK_POWER:
if (type_ & CollectorType::RANGED)
if (type_ == CollectorType::RANGED)
stats[STATS_TYPE_ATTACK_POWER] += val * multiplier;
break;
case SPELL_AURA_MOD_SHIELD_BLOCKVALUE:
@@ -610,7 +564,7 @@ void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float mu
case STAT_SPIRIT:
stats[STATS_TYPE_SPIRIT] += val * multiplier;
break;
case -1: // Stat all
case -1: // Stat all
stats[STATS_TYPE_STRENGTH] += val * multiplier;
stats[STATS_TYPE_AGILITY] += 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:
{
int32 statType = effectInfo.MiscValue;
if (statType & SPELL_SCHOOL_MASK_NORMAL) // physical
if (statType & SPELL_SCHOOL_MASK_NORMAL) // physical
stats[STATS_TYPE_ARMOR] += val * multiplier;
break;
}
@@ -650,39 +604,39 @@ void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float mu
stats[STATS_TYPE_BLOCK_RATING] += val * multiplier;
break;
case CR_HIT_MELEE:
if (type_ & CollectorType::MELEE)
if (type_ == CollectorType::MELEE)
stats[STATS_TYPE_HIT] += val * multiplier;
break;
case CR_HIT_RANGED:
if (type_ & CollectorType::RANGED)
if (type_ == CollectorType::RANGED)
stats[STATS_TYPE_HIT] += val * multiplier;
break;
case CR_HIT_SPELL:
if (type_ & CollectorType::SPELL)
if (type_ == CollectorType::SPELL)
stats[STATS_TYPE_HIT] += val * multiplier;
break;
case CR_CRIT_MELEE:
if (type_ & CollectorType::MELEE)
if (type_ == CollectorType::MELEE)
stats[STATS_TYPE_CRIT] += val * multiplier;
break;
case CR_CRIT_RANGED:
if (type_ & CollectorType::RANGED)
if (type_ == CollectorType::RANGED)
stats[STATS_TYPE_CRIT] += val * multiplier;
break;
case CR_CRIT_SPELL:
if (type_ & CollectorType::SPELL)
if (type_ == CollectorType::SPELL)
stats[STATS_TYPE_CRIT] += val * multiplier;
break;
case CR_HASTE_MELEE:
if (type_ & CollectorType::MELEE)
if (type_ == CollectorType::MELEE)
stats[STATS_TYPE_HASTE] += val * multiplier;
break;
case CR_HASTE_RANGED:
if (type_ & CollectorType::RANGED)
if (type_ == CollectorType::RANGED)
stats[STATS_TYPE_HASTE] += val * multiplier;
break;
case CR_HASTE_SPELL:
if (type_ & CollectorType::SPELL)
if (type_ == CollectorType::SPELL)
stats[STATS_TYPE_HASTE] += val * multiplier;
break;
case CR_EXPERTISE:
@@ -721,18 +675,12 @@ void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float mu
CollectSpellStats(effectInfo.TriggerSpell, multiplier, triggerCooldown);
break;
}
case SPELL_AURA_ADD_TARGET_TRIGGER:
{
if (canNextTrigger)
CollectSpellStats(effectInfo.TriggerSpell, multiplier, triggerCooldown);
break;
}
case SPELL_AURA_MOD_CRIT_DAMAGE_BONUS:
{
if (type_ != CollectorType::SPELL_HEAL)
{
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;
}
break;
@@ -744,7 +692,7 @@ void StatsCollector::HandleApplyAura(const SpellEffectInfo& effectInfo, float mu
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 randomPoints = int32(effectInfo.DieSides);
@@ -761,73 +709,4 @@ int32 StatsCollector::AverageValue(const SpellEffectInfo& effectInfo)
break;
}
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_TYPE_MELEE_DPS,
STATS_TYPE_RANGED_DPS,
// Bonus for unrecognized stats
STATS_TYPE_BONUS,
STATS_TYPE_MAX = 26
STATS_TYPE_MAX = 25
};
enum CollectorType : uint8
{
MELEE_DMG = 1,
MELEE_TANK = 2,
RANGED = 4,
SPELL_DMG = 8,
SPELL_HEAL = 16,
MELEE = MELEE_DMG | MELEE_TANK,
MELEE = 1,
RANGED = 2,
SPELL_DMG = 4,
SPELL_HEAL = 8,
SPELL = SPELL_DMG | SPELL_HEAL
};
@@ -67,21 +63,19 @@ public:
void CollectItemStats(ItemTemplate const* proto);
void CollectSpellStats(uint32 spellId, float multiplier = 1.0f, int32 spellCooldown = -1);
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:
int32 stats[STATS_TYPE_MAX];
private:
bool CanBeTriggeredByType(SpellInfo const* spellInfo, uint32 procFlags);
void CollectByItemStatType(uint32 itemStatType, int32 val);
bool SpecialSpellFilter(uint32 spellId);
bool SpecialEnchantFilter(uint32 enchantSpellId);
void HandleApplyAura(const SpellEffectInfo& effectInfo, float multiplier, bool canNextTrigger,
uint32 triggerCooldown);
void HandleApplyAura(const SpellEffectInfo& effectInfo, float multiplier, bool canNextTrigger, uint32 triggerCooldown);
int32 AverageValue(const SpellEffectInfo& effectInfo);
private:
CollectorType type_;
uint32 cls_;

View File

@@ -14,8 +14,6 @@
#include "PlayerbotAI.h"
#include "PlayerbotFactory.h"
#include "SharedDefines.h"
#include "SpellAuraDefines.h"
#include "SpellMgr.h"
#include "StatsCollector.h"
#include "Unit.h"
@@ -25,16 +23,15 @@ StatsWeightCalculator::StatsWeightCalculator(Player* player) : player_(player)
type_ = CollectorType::SPELL_HEAL;
else if (PlayerbotAI::IsCaster(player))
type_ = CollectorType::SPELL_DMG;
else if (PlayerbotAI::IsTank(player))
type_ = CollectorType::MELEE_TANK;
else if (PlayerbotAI::IsMelee(player))
type_ = CollectorType::MELEE_DMG;
type_ = CollectorType::MELEE;
else
type_ = CollectorType::RANGED;
cls = player->getClass();
tab = AiFactory::GetPlayerSpecTab(player);
collector_ = std::make_unique<StatsCollector>(type_, cls);
if (cls == CLASS_DEATH_KNIGHT && tab == DEATHKNIGHT_TAB_UNHOLY)
hitOverflowType_ = CollectorType::SPELL;
else if (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT)
@@ -82,7 +79,7 @@ float StatsWeightCalculator::CalculateItem(uint32 itemId)
CalculateItemTypePenalty(proto);
if (enable_item_set_bonus_)
CalculateItemSetMod(player_, proto);
CalculateItemSetBonus(player_, proto);
CalculateSocketBonus(player_, proto);
@@ -126,10 +123,8 @@ void StatsWeightCalculator::GenerateWeights(Player* player)
void StatsWeightCalculator::GenerateBasicWeights(Player* player)
{
// 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_BONUS] += 1.0f;
stats_weights_[STATS_TYPE_MELEE_DPS] += 0.01f;
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;
}
else if (cls == CLASS_WARLOCK || (cls == CLASS_MAGE && tab != MAGE_TAB_FIRE) ||
(cls == CLASS_PRIEST && tab == PRIEST_TAB_SHADOW) || // shadow
(cls == CLASS_DRUID && tab == DRUID_TAB_BALANCE)) // balance
(cls == CLASS_PRIEST && tab == PRIEST_TAB_SHADOW) || // shadow
(cls == CLASS_DRUID && tab == DRUID_TAB_BALANCE)) // balance
{
stats_weights_[STATS_TYPE_INTELLECT] += 0.3f;
stats_weights_[STATS_TYPE_SPIRIT] += 0.6f;
@@ -294,24 +289,16 @@ void StatsWeightCalculator::GenerateBasicWeights(Player* player)
stats_weights_[STATS_TYPE_HASTE] += 1.0f;
}
else if ((cls == CLASS_PALADIN && tab == PALADIN_TAB_HOLY) || // 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_PRIEST && tab != PRIEST_TAB_SHADOW) || // discipline / holy
(cls == CLASS_SHAMAN && tab == SHAMAN_TAB_RESTORATION) || // heal
(cls == CLASS_DRUID && tab == DRUID_TAB_RESTORATION))
{
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_MANA_REGENERATION] += 0.9f;
stats_weights_[STATS_TYPE_CRIT] += 0.6f;
stats_weights_[STATS_TYPE_HASTE] += 0.8f;
stats_weights_[STATS_TYPE_MANA_REGENERATION] += 1.2f;
stats_weights_[STATS_TYPE_CRIT] += 0.7f;
stats_weights_[STATS_TYPE_HASTE] += 1.0f;
stats_weights_[STATS_TYPE_RANGED_DPS] += 1.0f;
}
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_PARRY] += 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_VALUE] += 0.5f;
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_PARRY] += 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_HIT] += 2.0f;
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_DEFENSE] += 0.3f;
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_HIT] += 3.0f;
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;
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
if (isDoubleHand &&
((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_PROTECTION) ||
(cls == CLASS_PALADIN && tab == PALADIN_TAB_PROTECTION)))
@@ -499,20 +486,21 @@ void StatsWeightCalculator::CalculateItemTypePenalty(ItemTemplate const* proto)
weight_ *= 0.1;
}
// fury with titan's grip
if ((!isDoubleHand || proto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM ||
proto->SubClass == ITEM_SUBCLASS_WEAPON_STAFF) &&
if ((!isDoubleHand || proto->SubClass == ITEM_SUBCLASS_WEAPON_POLEARM || proto->SubClass == ITEM_SUBCLASS_WEAPON_STAFF) &&
(cls == CLASS_WARRIOR && tab == WARRIOR_TAB_FURY && player_->CanTitanGrip()))
{
weight_ *= 0.1;
}
}
if (proto->Class == ITEM_CLASS_WEAPON)
{
if (cls == CLASS_HUNTER && proto->SubClass == ITEM_SUBCLASS_WEAPON_THROWN)
{
weight_ *= 0.1;
}
if (cls == CLASS_ROGUE && (tab == ROGUE_TAB_ASSASSINATION || tab == ROGUE_TAB_SUBTLETY) &&
proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER)
if (cls == CLASS_ROGUE && tab == ROGUE_TAB_ASSASSINATION && proto->SubClass != ITEM_SUBCLASS_WEAPON_DAGGER)
{
weight_ *= 0.5;
weight_ *= 0.1;
}
if (cls == CLASS_ROGUE && player_->HasAura(13964) &&
(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;
}
if (cls == CLASS_DEATH_KNIGHT && player_->HasAura(50138) && !isDoubleHand)
{
weight_ *= 1.3;
}
bool slowDelay = proto->Delay > 2500;
if (cls == CLASS_SHAMAN && tab == SHAMAN_TAB_ENHANCEMENT && slowDelay)
weight_ *= 1.1;
@@ -556,24 +540,19 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
{
float hit_current, hit_overflow;
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_INCREASES_SPELL_PCT_TO_HIT); // suppression (18176)
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;
if (hit_overflow > hit_current)
validPoints = (hit_overflow - hit_current) / player->GetRatingMultiplier(CR_HIT_SPELL);
else
validPoints = 0;
}
else if (hitOverflowType_ & CollectorType::MELEE)
else if (hitOverflowType_ == CollectorType::MELEE)
{
hit_current = player->GetTotalAuraModifier(SPELL_AURA_MOD_HIT_CHANCE);
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;
expertise_current = player->GetUInt32Value(PLAYER_EXPERTISE);
@@ -610,13 +589,12 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
else
validPoints = 0;
collector_->stats[STATS_TYPE_EXPERTISE] =
std::min(collector_->stats[STATS_TYPE_EXPERTISE], (int)validPoints);
collector_->stats[STATS_TYPE_EXPERTISE] = std::min(collector_->stats[STATS_TYPE_EXPERTISE], (int)validPoints);
}
}
{
if (type_ & CollectorType::MELEE)
if (type_ == CollectorType::MELEE)
{
float defense_current, defense_overflow;
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;
armor_penetration_current = player->GetRatingBonusValue(CR_ARMOR_PENETRATION);
@@ -641,13 +619,11 @@ void StatsWeightCalculator::ApplyOverflowPenalty(Player* player)
float validPoints;
if (armor_penetration_overflow > armor_penetration_current)
validPoints = (armor_penetration_overflow - armor_penetration_current) /
player->GetRatingMultiplier(CR_ARMOR_PENETRATION);
validPoints = (armor_penetration_overflow - armor_penetration_current) / player->GetRatingMultiplier(CR_ARMOR_PENETRATION);
else
validPoints = 0;
collector_->stats[STATS_TYPE_ARMOR_PENETRATION] =
std::min(collector_->stats[STATS_TYPE_ARMOR_PENETRATION], (int)validPoints);
collector_->stats[STATS_TYPE_ARMOR_PENETRATION] = std::min(collector_->stats[STATS_TYPE_ARMOR_PENETRATION], (int)validPoints);
}
}
}
@@ -655,9 +631,9 @@ void StatsWeightCalculator::ApplyOverflowPenalty(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);
if (armor_penetration_current > 50)
stats_weights_[STATS_TYPE_ARMOR_PENETRATION] *= 1.2f;

View File

@@ -40,7 +40,7 @@ private:
void GenerateBasicWeights(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 CalculateItemTypePenalty(ItemTemplate const* proto);

View File

@@ -488,7 +488,7 @@ protected:
// node_name , action, prerequisite
#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, \
/*C*/ nullptr); \
@@ -496,7 +496,7 @@ protected:
// node_name , action, alternative
#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), \
/*C*/ nullptr); \
@@ -504,7 +504,7 @@ protected:
// node_name , action, continuer
#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, \
/*C*/ NextAction::array(0, new NextAction(con), nullptr)); \

View File

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

View File

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

@@ -119,7 +119,6 @@ public:
creators["move random"] = &StrategyContext::move_random;
creators["formation"] = &StrategyContext::combat_formation;
creators["move from group"] = &StrategyContext::move_from_group;
creators["worldbuff"] = &StrategyContext::world_buff;
}
private:
@@ -187,7 +186,6 @@ private:
static Strategy* move_random(PlayerbotAI* ai) { return new MoveRandomStrategy(ai); }
static Strategy* combat_formation(PlayerbotAI* ai) { return new CombatFormationStrategy(ai); }
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>

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