mirror of
https://github.com/mod-playerbots/mod-playerbots
synced 2025-11-29 15:58:20 +08:00
Compare commits
16 Commits
cadbcbd447
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38e2d8584b | ||
|
|
d5dbc4ddd7 | ||
|
|
2424f73bc4 | ||
|
|
cf743a186a | ||
|
|
10213d8381 | ||
|
|
d97870facd | ||
|
|
0c1700c117 | ||
|
|
0b1b0eaecc | ||
|
|
8e03371147 | ||
|
|
27311b734d | ||
|
|
bb5ed37cd3 | ||
|
|
e88c1b779b | ||
|
|
05057ae9b5 | ||
|
|
610a032379 | ||
|
|
ce2a990495 | ||
|
|
6effabfa42 |
5
.github/workflows/codestyle_cpp.yml
vendored
5
.github/workflows/codestyle_cpp.yml
vendored
@@ -5,11 +5,16 @@ on:
|
|||||||
- opened
|
- opened
|
||||||
- reopened
|
- reopened
|
||||||
- synchronize
|
- synchronize
|
||||||
|
- ready_for_review
|
||||||
paths:
|
paths:
|
||||||
- src/**
|
- src/**
|
||||||
- "!README.md"
|
- "!README.md"
|
||||||
- "!docs/**"
|
- "!docs/**"
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: "codestyle-cppcheck-${{ github.event.pull_request.number }}"
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
triage:
|
triage:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
49
README.md
49
README.md
@@ -3,7 +3,7 @@
|
|||||||
|
|
|
|
||||||
<a href="https://github.com/mod-playerbots/mod-playerbots/blob/master/README_CN.md">中文</a>
|
<a href="https://github.com/mod-playerbots/mod-playerbots/blob/master/README_CN.md">中文</a>
|
||||||
|
|
|
|
||||||
<a href="https://github.com/brighton-chi/mod-playerbots/blob/readme/README_ES.md">Español</a>
|
<a href="https://github.com/mod-playerbots/mod-playerbots/blob/master/README_ES.md">Español</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
@@ -18,25 +18,27 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
# Playerbots Module
|
# Playerbots Module
|
||||||
`mod-playerbots` is an [AzerothCore](https://www.azerothcore.org/) module that adds player-like bots to a server. The project is based off [IKE3's Playerbots](https://github.com/ike3/mangosbot) and requires a custom branch of AzerothCore to compile and run: [mod-playerbots/azerothcore-wotlk/tree/Playerbot](https://github.com/mod-playerbots/azerothcore-wotlk/tree/Playerbot).
|
`mod-playerbots` is an [AzerothCore](https://www.azerothcore.org/) module that adds player-like bots to a server. The project is based off [IKE3's Playerbots](https://github.com/ike3/mangosbot).
|
||||||
|
|
||||||
Features include:
|
Features include:
|
||||||
|
|
||||||
- The ability to log in alt characters as bots, allowing players to interact with their other characters, form parties, level up, and more;
|
- The ability to log in alt characters as bots, allowing players to interact with their other characters, form parties, level up, and more
|
||||||
- Random bots that wander through the world, complete quests, and otherwise behave like players, simulating the MMO experience;
|
- Random bots that wander through the world, complete quests, and otherwise behave like players, simulating the MMO experience
|
||||||
- Bots capable of running most raids and battlegrounds;
|
- Bots capable of running most raids and battlegrounds
|
||||||
- Highly configurable settings to define how bots behave;
|
- Highly configurable settings to define how bots behave
|
||||||
- Excellent performance, even when running thousands of bots.
|
- Excellent performance, even when running thousands of bots
|
||||||
|
|
||||||
**This project is still under development**. If you encounter any errors or experience crashes, we kindly request that you [report them as GitHub issues](https://github.com/mod-playerbots/mod-playerbots/issues/new?template=bug_report.md). Your valuable feedback will help us improve this project collaboratively.
|
We also have a **[Discord server](https://discord.gg/NQm5QShwf9)** where you can discuss the project, ask questions, and get involved in the community!
|
||||||
|
|
||||||
`mod-playerbots` has a **[Discord server](https://discord.gg/NQm5QShwf9)** where you can discuss the project, ask questions, and get involved in the community!
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Classic Installation
|
Supported platforms are Ubuntu, Windows, and macOS. Other Linux distributions may work, but may not receive support.
|
||||||
|
|
||||||
As noted above, `mod-playerbots` requires a custom branch of AzerothCore: [mod-playerbots/azerothcore-wotlk/tree/Playerbot](https://github.com/mod-playerbots/azerothcore-wotlk/tree/Playerbot). To install the module, simply run:
|
**All `mod-playerbots` installations require a custom branch of AzerothCore: [mod-playerbots/azerothcore-wotlk/tree/Playerbot](https://github.com/mod-playerbots/azerothcore-wotlk/tree/Playerbot).** This branch allows the `mod-playerbots` module to build and function. Updates from the upstream are implemented regularly to this branch. Instructions for installing this required branch and this module are provided below.
|
||||||
|
|
||||||
|
### Cloning the Repositories
|
||||||
|
|
||||||
|
To install both the required branch of AzerothCore and the `mod-playerbots` module from source, run the following:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/mod-playerbots/azerothcore-wotlk.git --branch=Playerbot
|
git clone https://github.com/mod-playerbots/azerothcore-wotlk.git --branch=Playerbot
|
||||||
@@ -48,7 +50,7 @@ For more information, refer to the [AzerothCore Installation Guide](https://www.
|
|||||||
|
|
||||||
### Docker Installation
|
### Docker Installation
|
||||||
|
|
||||||
**Docker installation is considered experimental.** To install the module on a Docker installation, run:
|
Docker installations are considered experimental (unofficial with limited support), and previous Docker experience is recommended. To install `mod-playerbots` on Docker, first clone the required branch of AzerothCore and this module:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/mod-playerbots/azerothcore-wotlk.git --branch=Playerbot
|
git clone https://github.com/mod-playerbots/azerothcore-wotlk.git --branch=Playerbot
|
||||||
@@ -85,28 +87,23 @@ Use `docker compose up -d --build` to build and run the server. For more informa
|
|||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
The [Playerbots Wiki](https://github.com/mod-playerbots/mod-playerbots/wiki) contains an extensive overview of addons, commands, raids with programmed bot strategies, and recommended performance configurations. Please note that documentation may be incomplete or out-of-date in some sections. Contributions are welcome.
|
The [Playerbots Wiki](https://github.com/mod-playerbots/mod-playerbots/wiki) contains an extensive overview of AddOns, commands, raids with programmed bot strategies, and recommended performance configurations. Please note that documentation may be incomplete or out-of-date in some sections, and contributions are welcome.
|
||||||
|
|
||||||
## Frequently Asked Questions
|
Bots are controlled via chat commands. For larger bot groups, this can be cumbersome. Because of this, community members have developed client AddOns to allow controlling bots through the in-game UI. We recommend you check out their projects listed in the [AddOns and Submodules](https://github.com/mod-playerbots/mod-playerbots/wiki/Playerbot-Addons-and-Sub%E2%80%90Modules) page.
|
||||||
|
|
||||||
- **Why aren't my bots casting spells?** Please make sure that the necessary English DBC file (enUS) is present.
|
## Contributing
|
||||||
- **What platforms are supported?** We support Ubuntu, Windows, and macOS. Other Linux distros may work, but will not receive support.
|
|
||||||
- **Why isn't my source compiling?** Please ensure that you are compiling with the required [custom branch of AzerothCore](https://github.com/mod-playerbots/azerothcore-wotlk/tree/Playerbot). Additionally, please [check the build status of our CI](https://github.com/mod-playerbots/mod-playerbots/actions). If the latest build is failing, rever to the last successful commit until we address the issue.
|
|
||||||
|
|
||||||
## Code standards
|
This project is still under development. We encourage anyone to make contributions, anything from pull requests to reporting issues. If you encounter any errors or experience crashes, we encourage you [report them as GitHub issues](https://github.com/mod-playerbots/mod-playerbots/issues/new?template=bug_report.md). Your valuable feedback will help us improve this project collaboratively.
|
||||||
- https://www.azerothcore.org/wiki/cpp-code-standards
|
|
||||||
|
|
||||||
## Addons
|
If you make coding contributions, `mod-playerbots` complies with the [C++ Code Standards](https://www.azerothcore.org/wiki/cpp-code-standards) established by AzerothCore. Each Pull Request must include all test scenarios the author performed, along with their results, to demonstrate that the changes were properly verified.
|
||||||
|
|
||||||
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:
|
We recommend joining the [Discord server](https://discord.gg/NQm5QShwf9) to make your contributions to the project easier, as a lot of active support is carried out through this server.
|
||||||
|
|
||||||
- [Multibot](https://github.com/Macx-Lio/MultiBot) (by Macx-Lio), which includes English, Chinese, French, German, Korean, Russian, and Spanish support [note: active development is temporarily continuing on a fork in Macx-Lio's absence (https://github.com/Wishmaster117/MultiBot)]
|
Please click on the "⭐" button to stay up to date and help us gain more visibility on GitHub!
|
||||||
- [Unbot Addon (zh)](https://github.com/liyunfan1223/unbot-addon) (Chinese version by Liyunfan) [note: no longer under active development]
|
|
||||||
- [Unbot Addon (en)](https://github.com/noisiver/unbot-addon/tree/english) (English version translated by @Revision) [note: no longer under active development]
|
|
||||||
|
|
||||||
## Acknowledgements
|
## Acknowledgements
|
||||||
|
|
||||||
`mod-playerbots` is is based off [ZhengPeiRu21/mod-playerbots](https://github.com/ZhengPeiRu21/mod-playerbots) and [celguar/mangosbot-bots](https://github.com/celguar/mangosbot-bots). We extend our gratitude to [@ZhengPeiRu21](https://github.com/ZhengPeiRu21) and [@celguar](https://github.com/celguar) for the continued efforts in maintaining the module.
|
`mod-playerbots` is based on [ZhengPeiRu21/mod-playerbots](https://github.com/ZhengPeiRu21/mod-playerbots) and [celguar/mangosbot-bots](https://github.com/celguar/mangosbot-bots). We extend our gratitude to [@ZhengPeiRu21](https://github.com/ZhengPeiRu21) and [@celguar](https://github.com/celguar) for their continued efforts in maintaining the module.
|
||||||
|
|
||||||
Also, a thank you to the many contributors who've helped build this project:
|
Also, a thank you to the many contributors who've helped build this project:
|
||||||
|
|
||||||
|
|||||||
@@ -1477,6 +1477,7 @@ void PlayerbotAI::ApplyInstanceStrategies(uint32 mapId, bool tellMaster)
|
|||||||
break;
|
break;
|
||||||
case 544:
|
case 544:
|
||||||
strategyName = "magtheridon"; // Magtheridon's Lair
|
strategyName = "magtheridon"; // Magtheridon's Lair
|
||||||
|
break;
|
||||||
case 565:
|
case 565:
|
||||||
strategyName = "gruulslair"; // Gruul's Lair
|
strategyName = "gruulslair"; // Gruul's Lair
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -725,8 +725,8 @@ std::string const PlayerbotAIConfig::GetTimestampStr()
|
|||||||
// HH hour (2 digits 00-23)
|
// HH hour (2 digits 00-23)
|
||||||
// MM minutes (2 digits 00-59)
|
// MM minutes (2 digits 00-59)
|
||||||
// SS seconds (2 digits 00-59)
|
// SS seconds (2 digits 00-59)
|
||||||
char buf[20];
|
char buf[32];
|
||||||
snprintf(buf, 20, "%04d-%02d-%02d %02d-%02d-%02d", aTm->tm_year + 1900, aTm->tm_mon + 1, aTm->tm_mday, aTm->tm_hour,
|
snprintf(buf, sizeof(buf), "%04d-%02d-%02d %02d-%02d-%02d", aTm->tm_year + 1900, aTm->tm_mon + 1, aTm->tm_mday, aTm->tm_hour,
|
||||||
aTm->tm_min, aTm->tm_sec);
|
aTm->tm_min, aTm->tm_sec);
|
||||||
return std::string(buf);
|
return std::string(buf);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,9 @@
|
|||||||
#include "PlayerbotAIConfig.h"
|
#include "PlayerbotAIConfig.h"
|
||||||
#include "PlayerbotDbStore.h"
|
#include "PlayerbotDbStore.h"
|
||||||
#include "PlayerbotFactory.h"
|
#include "PlayerbotFactory.h"
|
||||||
|
#include "PlayerbotOperations.h"
|
||||||
#include "PlayerbotSecurity.h"
|
#include "PlayerbotSecurity.h"
|
||||||
|
#include "PlayerbotWorldThreadProcessor.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "RandomPlayerbotMgr.h"
|
#include "RandomPlayerbotMgr.h"
|
||||||
#include "SharedDefines.h"
|
#include "SharedDefines.h"
|
||||||
@@ -85,7 +87,6 @@ public:
|
|||||||
|
|
||||||
void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId)
|
void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId)
|
||||||
{
|
{
|
||||||
// bot is loading
|
|
||||||
if (botLoading.find(playerGuid) != botLoading.end())
|
if (botLoading.find(playerGuid) != botLoading.end())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -195,7 +196,9 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
|
|||||||
}
|
}
|
||||||
|
|
||||||
sRandomPlayerbotMgr->OnPlayerLogin(bot);
|
sRandomPlayerbotMgr->OnPlayerLogin(bot);
|
||||||
OnBotLogin(bot);
|
|
||||||
|
auto op = std::make_unique<OnBotLoginOperation>(bot->GetGUID(), this);
|
||||||
|
sPlayerbotWorldProcessor->QueueOperation(std::move(op));
|
||||||
|
|
||||||
botLoading.erase(holder.GetGuid());
|
botLoading.erase(holder.GetGuid());
|
||||||
}
|
}
|
||||||
@@ -316,11 +319,9 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid)
|
|||||||
if (!botAI)
|
if (!botAI)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Group* group = bot->GetGroup();
|
// Queue group cleanup operation for world thread
|
||||||
if (group && !bot->InBattleground() && !bot->InBattlegroundQueue() && botAI->HasActivePlayerMaster())
|
auto cleanupOp = std::make_unique<BotLogoutGroupCleanupOperation>(guid);
|
||||||
{
|
sPlayerbotWorldProcessor->QueueOperation(std::move(cleanupOp));
|
||||||
sPlayerbotDbStore->Save(botAI);
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DEBUG("playerbots", "Bot {} logging out", bot->GetName().c_str());
|
LOG_DEBUG("playerbots", "Bot {} logging out", bot->GetName().c_str());
|
||||||
bot->SaveToDB(false, false);
|
bot->SaveToDB(false, false);
|
||||||
@@ -549,6 +550,7 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
|
|||||||
|
|
||||||
botAI->TellMaster("Hello!", PLAYERBOT_SECURITY_TALK);
|
botAI->TellMaster("Hello!", PLAYERBOT_SECURITY_TALK);
|
||||||
|
|
||||||
|
// Queue group operations for world thread
|
||||||
if (master && master->GetGroup() && !group)
|
if (master && master->GetGroup() && !group)
|
||||||
{
|
{
|
||||||
Group* mgroup = master->GetGroup();
|
Group* mgroup = master->GetGroup();
|
||||||
@@ -556,24 +558,29 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
|
|||||||
{
|
{
|
||||||
if (!mgroup->isRaidGroup() && !mgroup->isLFGGroup() && !mgroup->isBGGroup() && !mgroup->isBFGroup())
|
if (!mgroup->isRaidGroup() && !mgroup->isLFGGroup() && !mgroup->isBGGroup() && !mgroup->isBFGroup())
|
||||||
{
|
{
|
||||||
mgroup->ConvertToRaid();
|
// Queue ConvertToRaid operation
|
||||||
|
auto convertOp = std::make_unique<GroupConvertToRaidOperation>(master->GetGUID());
|
||||||
|
sPlayerbotWorldProcessor->QueueOperation(std::move(convertOp));
|
||||||
}
|
}
|
||||||
if (mgroup->isRaidGroup())
|
if (mgroup->isRaidGroup())
|
||||||
{
|
{
|
||||||
mgroup->AddMember(bot);
|
// Queue AddMember operation
|
||||||
|
auto addOp = std::make_unique<GroupInviteOperation>(master->GetGUID(), bot->GetGUID());
|
||||||
|
sPlayerbotWorldProcessor->QueueOperation(std::move(addOp));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mgroup->AddMember(bot);
|
// Queue AddMember operation
|
||||||
|
auto addOp = std::make_unique<GroupInviteOperation>(master->GetGUID(), bot->GetGUID());
|
||||||
|
sPlayerbotWorldProcessor->QueueOperation(std::move(addOp));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (master && !group)
|
else if (master && !group)
|
||||||
{
|
{
|
||||||
Group* newGroup = new Group();
|
// Queue group creation and AddMember operation
|
||||||
newGroup->Create(master);
|
auto inviteOp = std::make_unique<GroupInviteOperation>(master->GetGUID(), bot->GetGUID());
|
||||||
sGroupMgr->AddGroup(newGroup);
|
sPlayerbotWorldProcessor->QueueOperation(std::move(inviteOp));
|
||||||
newGroup->AddMember(bot);
|
|
||||||
}
|
}
|
||||||
// if (master)
|
// if (master)
|
||||||
// {
|
// {
|
||||||
@@ -1602,8 +1609,26 @@ void PlayerbotMgr::OnBotLoginInternal(Player* const bot)
|
|||||||
|
|
||||||
void PlayerbotMgr::OnPlayerLogin(Player* player)
|
void PlayerbotMgr::OnPlayerLogin(Player* player)
|
||||||
{
|
{
|
||||||
|
if (!player)
|
||||||
|
return;
|
||||||
|
|
||||||
|
WorldSession* session = player->GetSession();
|
||||||
|
if (!session)
|
||||||
|
{
|
||||||
|
LOG_WARN("playerbots", "Unable to register locale priority for player {} because the session is missing", player->GetName());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DB locale (source of bot text translation)
|
||||||
|
LocaleConstant const databaseLocale = session->GetSessionDbLocaleIndex();
|
||||||
|
|
||||||
|
// For bot texts (DB-driven), prefer the database locale with a safe fallback.
|
||||||
|
LocaleConstant usedLocale = databaseLocale;
|
||||||
|
if (usedLocale >= MAX_LOCALES)
|
||||||
|
usedLocale = LOCALE_enUS; // fallback
|
||||||
|
|
||||||
// set locale priority for bot texts
|
// set locale priority for bot texts
|
||||||
sPlayerbotTextMgr->AddLocalePriority(player->GetSession()->GetSessionDbcLocale());
|
sPlayerbotTextMgr->AddLocalePriority(usedLocale);
|
||||||
|
|
||||||
if (sPlayerbotAIConfig->selfBotLevel > 2)
|
if (sPlayerbotAIConfig->selfBotLevel > 2)
|
||||||
HandlePlayerbotCommand("self", player);
|
HandlePlayerbotCommand("self", player);
|
||||||
@@ -1611,7 +1636,7 @@ void PlayerbotMgr::OnPlayerLogin(Player* player)
|
|||||||
if (!sPlayerbotAIConfig->botAutologin)
|
if (!sPlayerbotAIConfig->botAutologin)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
uint32 accountId = player->GetSession()->GetAccountId();
|
uint32 accountId = session->GetAccountId();
|
||||||
QueryResult results = CharacterDatabase.Query("SELECT name FROM characters WHERE account = {}", accountId);
|
QueryResult results = CharacterDatabase.Query("SELECT name FROM characters WHERE account = {}", accountId);
|
||||||
if (results)
|
if (results)
|
||||||
{
|
{
|
||||||
|
|||||||
93
src/PlayerbotOperation.h
Normal file
93
src/PlayerbotOperation.h
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||||
|
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PLAYERBOT_OPERATION_H
|
||||||
|
#define _PLAYERBOT_OPERATION_H
|
||||||
|
|
||||||
|
#include "Common.h"
|
||||||
|
#include "ObjectGuid.h"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Base class for thread-unsafe operations that must be executed in the world thread
|
||||||
|
*
|
||||||
|
* PlayerbotOperation represents an operation that needs to be deferred from a map thread
|
||||||
|
* to the world thread for safe execution. Examples include group modifications, LFG operations,
|
||||||
|
* guild operations, etc.
|
||||||
|
*
|
||||||
|
* Thread Safety:
|
||||||
|
* - The constructor and data members must be thread-safe (use copies, not pointers)
|
||||||
|
* - Execute() is called in the world thread and can safely perform thread-unsafe operations
|
||||||
|
* - Subclasses must not store raw pointers to (core/world thread) game object (use ObjectGuid instead)
|
||||||
|
*/
|
||||||
|
class PlayerbotOperation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~PlayerbotOperation() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Execute this operation in the world thread
|
||||||
|
*
|
||||||
|
* This method is called by PlayerbotWorldThreadProcessor::Update() which runs in the world thread.
|
||||||
|
* It's safe to perform any thread-unsafe operation here (Group, LFG, Guild, etc.)
|
||||||
|
*
|
||||||
|
* @return true if operation succeeded, false if it failed
|
||||||
|
*/
|
||||||
|
virtual bool Execute() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the bot GUID this operation is for (optional)
|
||||||
|
*
|
||||||
|
* Used for logging and debugging purposes.
|
||||||
|
*
|
||||||
|
* @return ObjectGuid of the bot, or ObjectGuid::Empty if not applicable
|
||||||
|
*/
|
||||||
|
virtual ObjectGuid GetBotGuid() const { return ObjectGuid::Empty; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the operation priority (higher = more urgent)
|
||||||
|
*
|
||||||
|
* Priority levels:
|
||||||
|
* - 100: Critical (crash prevention, cleanup operations)
|
||||||
|
* - 50: High (player-facing operations like group invites)
|
||||||
|
* - 10: Normal (background operations)
|
||||||
|
* - 0: Low (statistics, logging)
|
||||||
|
*
|
||||||
|
* @return Priority value (0-100)
|
||||||
|
*/
|
||||||
|
virtual uint32 GetPriority() const { return 10; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get a human-readable name for this operation
|
||||||
|
*
|
||||||
|
* Used for logging and debugging.
|
||||||
|
*
|
||||||
|
* @return Operation name
|
||||||
|
*/
|
||||||
|
virtual std::string GetName() const { return "Unknown Operation"; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if this operation is still valid
|
||||||
|
*
|
||||||
|
* Called before Execute() to check if the operation should still be executed.
|
||||||
|
* For example, if a bot logged out, group invite operations for that bot can be skipped.
|
||||||
|
*
|
||||||
|
* @return true if operation should be executed, false to skip
|
||||||
|
*/
|
||||||
|
virtual bool IsValid() const { return true; }
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Comparison operator for priority queue (higher priority first)
|
||||||
|
*/
|
||||||
|
struct PlayerbotOperationComparator
|
||||||
|
{
|
||||||
|
bool operator()(const std::unique_ptr<PlayerbotOperation>& a, const std::unique_ptr<PlayerbotOperation>& b) const
|
||||||
|
{
|
||||||
|
return a->GetPriority() < b->GetPriority(); // Lower priority goes to back of queue
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
500
src/PlayerbotOperations.h
Normal file
500
src/PlayerbotOperations.h
Normal file
@@ -0,0 +1,500 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||||
|
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PLAYERBOT_OPERATIONS_H
|
||||||
|
#define _PLAYERBOT_OPERATIONS_H
|
||||||
|
|
||||||
|
#include "Group.h"
|
||||||
|
#include "GroupMgr.h"
|
||||||
|
#include "GuildMgr.h"
|
||||||
|
#include "ObjectAccessor.h"
|
||||||
|
#include "PlayerbotOperation.h"
|
||||||
|
#include "Player.h"
|
||||||
|
#include "PlayerbotAI.h"
|
||||||
|
#include "PlayerbotMgr.h"
|
||||||
|
#include "PlayerbotDbStore.h"
|
||||||
|
#include "RandomPlayerbotMgr.h"
|
||||||
|
|
||||||
|
// Group invite operation
|
||||||
|
class GroupInviteOperation : public PlayerbotOperation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
GroupInviteOperation(ObjectGuid botGuid, ObjectGuid targetGuid)
|
||||||
|
: m_botGuid(botGuid), m_targetGuid(targetGuid)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Execute() override
|
||||||
|
{
|
||||||
|
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||||
|
Player* target = ObjectAccessor::FindPlayer(m_targetGuid);
|
||||||
|
|
||||||
|
if (!bot || !target)
|
||||||
|
{
|
||||||
|
LOG_DEBUG("playerbots", "GroupInviteOperation: Bot or target not found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if target is already in a group
|
||||||
|
if (target->GetGroup())
|
||||||
|
{
|
||||||
|
LOG_DEBUG("playerbots", "GroupInviteOperation: Target {} is already in a group", target->GetName());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Group* group = bot->GetGroup();
|
||||||
|
|
||||||
|
// Create group if bot doesn't have one
|
||||||
|
if (!group)
|
||||||
|
{
|
||||||
|
group = new Group;
|
||||||
|
if (!group->Create(bot))
|
||||||
|
{
|
||||||
|
delete group;
|
||||||
|
LOG_ERROR("playerbots", "GroupInviteOperation: Failed to create group for bot {}", bot->GetName());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sGroupMgr->AddGroup(group);
|
||||||
|
LOG_DEBUG("playerbots", "GroupInviteOperation: Created new group for bot {}", bot->GetName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to raid if needed (more than 5 members)
|
||||||
|
if (!group->isRaidGroup() && group->GetMembersCount() >= 5)
|
||||||
|
{
|
||||||
|
group->ConvertToRaid();
|
||||||
|
LOG_DEBUG("playerbots", "GroupInviteOperation: Converted group to raid");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add member to group
|
||||||
|
if (group->AddMember(target))
|
||||||
|
{
|
||||||
|
LOG_DEBUG("playerbots", "GroupInviteOperation: Successfully added {} to group", target->GetName());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG_ERROR("playerbots", "GroupInviteOperation: Failed to add {} to group", target->GetName());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectGuid GetBotGuid() const override { return m_botGuid; }
|
||||||
|
|
||||||
|
uint32 GetPriority() const override { return 50; } // High priority (player-facing)
|
||||||
|
|
||||||
|
std::string GetName() const override { return "GroupInvite"; }
|
||||||
|
|
||||||
|
bool IsValid() const override
|
||||||
|
{
|
||||||
|
// Check if bot still exists and is online
|
||||||
|
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||||
|
Player* target = ObjectAccessor::FindPlayer(m_targetGuid);
|
||||||
|
return bot && target;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ObjectGuid m_botGuid;
|
||||||
|
ObjectGuid m_targetGuid;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Remove member from group
|
||||||
|
class GroupRemoveMemberOperation : public PlayerbotOperation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
GroupRemoveMemberOperation(ObjectGuid botGuid, ObjectGuid targetGuid)
|
||||||
|
: m_botGuid(botGuid), m_targetGuid(targetGuid)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Execute() override
|
||||||
|
{
|
||||||
|
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||||
|
Player* target = ObjectAccessor::FindPlayer(m_targetGuid);
|
||||||
|
|
||||||
|
if (!bot || !target)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Group* group = bot->GetGroup();
|
||||||
|
if (!group)
|
||||||
|
{
|
||||||
|
LOG_DEBUG("playerbots", "GroupRemoveMemberOperation: Bot is not in a group");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!group->IsMember(target->GetGUID()))
|
||||||
|
{
|
||||||
|
LOG_DEBUG("playerbots", "GroupRemoveMemberOperation: Target is not in bot's group");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
group->RemoveMember(target->GetGUID());
|
||||||
|
LOG_DEBUG("playerbots", "GroupRemoveMemberOperation: Removed {} from group", target->GetName());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectGuid GetBotGuid() const override { return m_botGuid; }
|
||||||
|
|
||||||
|
uint32 GetPriority() const override { return 50; }
|
||||||
|
|
||||||
|
std::string GetName() const override { return "GroupRemoveMember"; }
|
||||||
|
|
||||||
|
bool IsValid() const override
|
||||||
|
{
|
||||||
|
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||||
|
return bot != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ObjectGuid m_botGuid;
|
||||||
|
ObjectGuid m_targetGuid;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert group to raid
|
||||||
|
class GroupConvertToRaidOperation : public PlayerbotOperation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
GroupConvertToRaidOperation(ObjectGuid botGuid) : m_botGuid(botGuid) {}
|
||||||
|
|
||||||
|
bool Execute() override
|
||||||
|
{
|
||||||
|
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||||
|
if (!bot)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Group* group = bot->GetGroup();
|
||||||
|
if (!group)
|
||||||
|
{
|
||||||
|
LOG_DEBUG("playerbots", "GroupConvertToRaidOperation: Bot is not in a group");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group->isRaidGroup())
|
||||||
|
{
|
||||||
|
LOG_DEBUG("playerbots", "GroupConvertToRaidOperation: Group is already a raid");
|
||||||
|
return true; // Success - already in desired state
|
||||||
|
}
|
||||||
|
|
||||||
|
group->ConvertToRaid();
|
||||||
|
LOG_DEBUG("playerbots", "GroupConvertToRaidOperation: Converted group to raid");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectGuid GetBotGuid() const override { return m_botGuid; }
|
||||||
|
|
||||||
|
uint32 GetPriority() const override { return 50; }
|
||||||
|
|
||||||
|
std::string GetName() const override { return "GroupConvertToRaid"; }
|
||||||
|
|
||||||
|
bool IsValid() const override
|
||||||
|
{
|
||||||
|
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||||
|
return bot != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ObjectGuid m_botGuid;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set group leader
|
||||||
|
class GroupSetLeaderOperation : public PlayerbotOperation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
GroupSetLeaderOperation(ObjectGuid botGuid, ObjectGuid newLeaderGuid)
|
||||||
|
: m_botGuid(botGuid), m_newLeaderGuid(newLeaderGuid)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Execute() override
|
||||||
|
{
|
||||||
|
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||||
|
Player* newLeader = ObjectAccessor::FindPlayer(m_newLeaderGuid);
|
||||||
|
|
||||||
|
if (!bot || !newLeader)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Group* group = bot->GetGroup();
|
||||||
|
if (!group)
|
||||||
|
{
|
||||||
|
LOG_DEBUG("playerbots", "GroupSetLeaderOperation: Bot is not in a group");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!group->IsMember(newLeader->GetGUID()))
|
||||||
|
{
|
||||||
|
LOG_DEBUG("playerbots", "GroupSetLeaderOperation: New leader is not in the group");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
group->ChangeLeader(newLeader->GetGUID());
|
||||||
|
LOG_DEBUG("playerbots", "GroupSetLeaderOperation: Changed leader to {}", newLeader->GetName());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectGuid GetBotGuid() const override { return m_botGuid; }
|
||||||
|
|
||||||
|
uint32 GetPriority() const override { return 50; }
|
||||||
|
|
||||||
|
std::string GetName() const override { return "GroupSetLeader"; }
|
||||||
|
|
||||||
|
bool IsValid() const override
|
||||||
|
{
|
||||||
|
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||||
|
Player* newLeader = ObjectAccessor::FindPlayer(m_newLeaderGuid);
|
||||||
|
return bot && newLeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ObjectGuid m_botGuid;
|
||||||
|
ObjectGuid m_newLeaderGuid;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Form arena group
|
||||||
|
class ArenaGroupFormationOperation : public PlayerbotOperation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ArenaGroupFormationOperation(ObjectGuid leaderGuid, std::vector<ObjectGuid> memberGuids,
|
||||||
|
uint32 requiredSize, uint32 arenaTeamId, std::string arenaTeamName)
|
||||||
|
: m_leaderGuid(leaderGuid), m_memberGuids(memberGuids),
|
||||||
|
m_requiredSize(requiredSize), m_arenaTeamId(arenaTeamId), m_arenaTeamName(arenaTeamName)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Execute() override
|
||||||
|
{
|
||||||
|
Player* leader = ObjectAccessor::FindPlayer(m_leaderGuid);
|
||||||
|
if (!leader)
|
||||||
|
{
|
||||||
|
LOG_ERROR("playerbots", "ArenaGroupFormationOperation: Leader not found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Remove all members from their existing groups
|
||||||
|
for (const ObjectGuid& memberGuid : m_memberGuids)
|
||||||
|
{
|
||||||
|
Player* member = ObjectAccessor::FindPlayer(memberGuid);
|
||||||
|
if (!member)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Group* memberGroup = member->GetGroup();
|
||||||
|
if (memberGroup)
|
||||||
|
{
|
||||||
|
memberGroup->RemoveMember(memberGuid);
|
||||||
|
LOG_DEBUG("playerbots", "ArenaGroupFormationOperation: Removed {} from their existing group",
|
||||||
|
member->GetName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Disband leader's existing group
|
||||||
|
Group* leaderGroup = leader->GetGroup();
|
||||||
|
if (leaderGroup)
|
||||||
|
{
|
||||||
|
leaderGroup->Disband(true);
|
||||||
|
LOG_DEBUG("playerbots", "ArenaGroupFormationOperation: Disbanded leader's existing group");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Create new group with leader
|
||||||
|
Group* newGroup = new Group();
|
||||||
|
if (!newGroup->Create(leader))
|
||||||
|
{
|
||||||
|
delete newGroup;
|
||||||
|
LOG_ERROR("playerbots", "ArenaGroupFormationOperation: Failed to create arena group for leader {}",
|
||||||
|
leader->GetName());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sGroupMgr->AddGroup(newGroup);
|
||||||
|
LOG_DEBUG("playerbots", "ArenaGroupFormationOperation: Created new arena group with leader {}",
|
||||||
|
leader->GetName());
|
||||||
|
|
||||||
|
// Step 4: Add members to the new group
|
||||||
|
uint32 addedMembers = 0;
|
||||||
|
for (const ObjectGuid& memberGuid : m_memberGuids)
|
||||||
|
{
|
||||||
|
Player* member = ObjectAccessor::FindPlayer(memberGuid);
|
||||||
|
if (!member)
|
||||||
|
{
|
||||||
|
LOG_DEBUG("playerbots", "ArenaGroupFormationOperation: Member {} not found, skipping",
|
||||||
|
memberGuid.ToString());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (member->GetLevel() < 70)
|
||||||
|
{
|
||||||
|
LOG_DEBUG("playerbots", "ArenaGroupFormationOperation: Member {} is below level 70, skipping",
|
||||||
|
member->GetName());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newGroup->AddMember(member))
|
||||||
|
{
|
||||||
|
addedMembers++;
|
||||||
|
LOG_DEBUG("playerbots", "ArenaGroupFormationOperation: Added {} to arena group",
|
||||||
|
member->GetName());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
LOG_ERROR("playerbots", "ArenaGroupFormationOperation: Failed to add {} to arena group",
|
||||||
|
member->GetName());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addedMembers == 0)
|
||||||
|
{
|
||||||
|
LOG_ERROR("playerbots", "ArenaGroupFormationOperation: No members were added to the arena group");
|
||||||
|
newGroup->Disband();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 5: Teleport members to leader and reset AI
|
||||||
|
for (const ObjectGuid& memberGuid : m_memberGuids)
|
||||||
|
{
|
||||||
|
Player* member = ObjectAccessor::FindPlayer(memberGuid);
|
||||||
|
if (!member || !newGroup->IsMember(memberGuid))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
PlayerbotAI* memberBotAI = sPlayerbotsMgr->GetPlayerbotAI(member);
|
||||||
|
if (memberBotAI)
|
||||||
|
memberBotAI->Reset();
|
||||||
|
|
||||||
|
member->RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TELEPORTED | AURA_INTERRUPT_FLAG_CHANGE_MAP);
|
||||||
|
member->TeleportTo(leader->GetMapId(), leader->GetPositionX(), leader->GetPositionY(),
|
||||||
|
leader->GetPositionZ(), 0);
|
||||||
|
|
||||||
|
LOG_DEBUG("playerbots", "ArenaGroupFormationOperation: Teleported {} to leader", member->GetName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we have enough members
|
||||||
|
if (newGroup->GetMembersCount() < m_requiredSize)
|
||||||
|
{
|
||||||
|
LOG_INFO("playerbots", "Team #{} <{}> Group is not ready for match (not enough members: {}/{})",
|
||||||
|
m_arenaTeamId, m_arenaTeamName, newGroup->GetMembersCount(), m_requiredSize);
|
||||||
|
newGroup->Disband();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO("playerbots", "Team #{} <{}> Group is ready for match with {} members",
|
||||||
|
m_arenaTeamId, m_arenaTeamName, newGroup->GetMembersCount());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectGuid GetBotGuid() const override { return m_leaderGuid; }
|
||||||
|
|
||||||
|
uint32 GetPriority() const override { return 60; } // Very high priority (arena/BG operations)
|
||||||
|
|
||||||
|
std::string GetName() const override { return "ArenaGroupFormation"; }
|
||||||
|
|
||||||
|
bool IsValid() const override
|
||||||
|
{
|
||||||
|
Player* leader = ObjectAccessor::FindPlayer(m_leaderGuid);
|
||||||
|
return leader != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ObjectGuid m_leaderGuid;
|
||||||
|
std::vector<ObjectGuid> m_memberGuids;
|
||||||
|
uint32 m_requiredSize;
|
||||||
|
uint32 m_arenaTeamId;
|
||||||
|
std::string m_arenaTeamName;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Bot logout group cleanup operation
|
||||||
|
class BotLogoutGroupCleanupOperation : public PlayerbotOperation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BotLogoutGroupCleanupOperation(ObjectGuid botGuid) : m_botGuid(botGuid) {}
|
||||||
|
|
||||||
|
bool Execute() override
|
||||||
|
{
|
||||||
|
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||||
|
if (!bot)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
PlayerbotAI* botAI = sPlayerbotsMgr->GetPlayerbotAI(bot);
|
||||||
|
if (!botAI)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Group* group = bot->GetGroup();
|
||||||
|
if (group && !bot->InBattleground() && !bot->InBattlegroundQueue() && botAI->HasActivePlayerMaster())
|
||||||
|
sPlayerbotDbStore->Save(botAI);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectGuid GetBotGuid() const override { return m_botGuid; }
|
||||||
|
uint32 GetPriority() const override { return 70; }
|
||||||
|
std::string GetName() const override { return "BotLogoutGroupCleanup"; }
|
||||||
|
|
||||||
|
bool IsValid() const override
|
||||||
|
{
|
||||||
|
Player* bot = ObjectAccessor::FindPlayer(m_botGuid);
|
||||||
|
return bot != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ObjectGuid m_botGuid;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add player bot operation (for logging in bots from map threads)
|
||||||
|
class AddPlayerBotOperation : public PlayerbotOperation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AddPlayerBotOperation(ObjectGuid botGuid, uint32 masterAccountId)
|
||||||
|
: m_botGuid(botGuid), m_masterAccountId(masterAccountId)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Execute() override
|
||||||
|
{
|
||||||
|
sRandomPlayerbotMgr->AddPlayerBot(m_botGuid, m_masterAccountId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectGuid GetBotGuid() const override { return m_botGuid; }
|
||||||
|
|
||||||
|
uint32 GetPriority() const override { return 50; } // High priority
|
||||||
|
|
||||||
|
std::string GetName() const override { return "AddPlayerBot"; }
|
||||||
|
|
||||||
|
bool IsValid() const override
|
||||||
|
{
|
||||||
|
return !ObjectAccessor::FindConnectedPlayer(m_botGuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ObjectGuid m_botGuid;
|
||||||
|
uint32 m_masterAccountId;
|
||||||
|
};
|
||||||
|
|
||||||
|
class OnBotLoginOperation : public PlayerbotOperation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
OnBotLoginOperation(ObjectGuid botGuid, PlayerbotHolder* holder)
|
||||||
|
: m_botGuid(botGuid), m_holder(holder)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Execute() override
|
||||||
|
{
|
||||||
|
Player* bot = ObjectAccessor::FindConnectedPlayer(m_botGuid);
|
||||||
|
if (!bot || !m_holder)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_holder->OnBotLogin(bot);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectGuid GetBotGuid() const override { return m_botGuid; }
|
||||||
|
uint32 GetPriority() const override { return 100; }
|
||||||
|
std::string GetName() const override { return "OnBotLogin"; }
|
||||||
|
|
||||||
|
bool IsValid() const override
|
||||||
|
{
|
||||||
|
return ObjectAccessor::FindConnectedPlayer(m_botGuid) != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ObjectGuid m_botGuid;
|
||||||
|
PlayerbotHolder* m_holder;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -190,26 +190,29 @@ bool PlayerbotTextMgr::GetBotText(std::string name, std::string& text, std::map<
|
|||||||
|
|
||||||
void PlayerbotTextMgr::AddLocalePriority(uint32 locale)
|
void PlayerbotTextMgr::AddLocalePriority(uint32 locale)
|
||||||
{
|
{
|
||||||
if (!locale)
|
if (locale >= MAX_LOCALES)
|
||||||
|
{
|
||||||
|
LOG_WARN("playerbots", "Ignoring locale {} for bot texts because it exceeds MAX_LOCALES ({})", locale, MAX_LOCALES - 1);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
botTextLocalePriority[locale]++;
|
botTextLocalePriority[locale]++;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32 PlayerbotTextMgr::GetLocalePriority()
|
uint32 PlayerbotTextMgr::GetLocalePriority()
|
||||||
{
|
{
|
||||||
uint32 topLocale = 0;
|
|
||||||
|
|
||||||
// if no real players online, reset top locale
|
// if no real players online, reset top locale
|
||||||
if (!sWorldSessionMgr->GetActiveSessionCount())
|
uint32 const activeSessions = sWorldSessionMgr->GetActiveSessionCount();
|
||||||
|
if (!activeSessions)
|
||||||
{
|
{
|
||||||
ResetLocalePriority();
|
ResetLocalePriority();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32 topLocale = 0;
|
||||||
for (uint8 i = 0; i < MAX_LOCALES; ++i)
|
for (uint8 i = 0; i < MAX_LOCALES; ++i)
|
||||||
{
|
{
|
||||||
if (botTextLocalePriority[i] > topLocale)
|
if (botTextLocalePriority[i] > botTextLocalePriority[topLocale])
|
||||||
topLocale = i;
|
topLocale = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
217
src/PlayerbotWorldThreadProcessor.cpp
Normal file
217
src/PlayerbotWorldThreadProcessor.cpp
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||||
|
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "PlayerbotWorldThreadProcessor.h"
|
||||||
|
|
||||||
|
#include "Log.h"
|
||||||
|
#include "PlayerbotAIConfig.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
PlayerbotWorldThreadProcessor::PlayerbotWorldThreadProcessor()
|
||||||
|
: m_enabled(true), m_maxQueueSize(10000), m_batchSize(100), m_queueWarningThreshold(80),
|
||||||
|
m_timeSinceLastUpdate(0), m_updateInterval(50) // Process at least every 50ms
|
||||||
|
{
|
||||||
|
LOG_INFO("playerbots", "PlayerbotWorldThreadProcessor initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerbotWorldThreadProcessor::~PlayerbotWorldThreadProcessor() { ClearQueue(); }
|
||||||
|
|
||||||
|
PlayerbotWorldThreadProcessor* PlayerbotWorldThreadProcessor::instance()
|
||||||
|
{
|
||||||
|
static PlayerbotWorldThreadProcessor instance;
|
||||||
|
return &instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerbotWorldThreadProcessor::Update(uint32 diff)
|
||||||
|
{
|
||||||
|
if (!m_enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Accumulate time
|
||||||
|
m_timeSinceLastUpdate += diff;
|
||||||
|
|
||||||
|
// Don't process too frequently to reduce overhead
|
||||||
|
if (m_timeSinceLastUpdate < m_updateInterval)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_timeSinceLastUpdate = 0;
|
||||||
|
|
||||||
|
// Check queue health (warn if getting full)
|
||||||
|
CheckQueueHealth();
|
||||||
|
|
||||||
|
// Process a batch of operations
|
||||||
|
ProcessBatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlayerbotWorldThreadProcessor::QueueOperation(std::unique_ptr<PlayerbotOperation> operation)
|
||||||
|
{
|
||||||
|
if (!operation)
|
||||||
|
{
|
||||||
|
LOG_ERROR("playerbots", "Attempted to queue null operation");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(m_queueMutex);
|
||||||
|
|
||||||
|
// Check if queue is full
|
||||||
|
if (m_operationQueue.size() >= m_maxQueueSize)
|
||||||
|
{
|
||||||
|
LOG_ERROR("playerbots",
|
||||||
|
"PlayerbotWorldThreadProcessor queue is full ({} operations). Dropping operation: {}",
|
||||||
|
m_maxQueueSize, operation->GetName());
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||||
|
m_stats.totalOperationsSkipped++;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queue the operation
|
||||||
|
m_operationQueue.push(std::move(operation));
|
||||||
|
|
||||||
|
// Update statistics
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||||
|
m_stats.currentQueueSize = static_cast<uint32>(m_operationQueue.size());
|
||||||
|
m_stats.maxQueueSize = std::max(m_stats.maxQueueSize, m_stats.currentQueueSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerbotWorldThreadProcessor::ProcessBatch()
|
||||||
|
{
|
||||||
|
// Extract a batch of operations from the queue
|
||||||
|
std::vector<std::unique_ptr<PlayerbotOperation>> batch;
|
||||||
|
batch.reserve(m_batchSize);
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_queueMutex);
|
||||||
|
|
||||||
|
// Extract up to batchSize operations
|
||||||
|
while (!m_operationQueue.empty() && batch.size() < m_batchSize)
|
||||||
|
{
|
||||||
|
batch.push_back(std::move(m_operationQueue.front()));
|
||||||
|
m_operationQueue.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update current queue size stat
|
||||||
|
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||||
|
m_stats.currentQueueSize = static_cast<uint32>(m_operationQueue.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute operations outside of lock to avoid blocking queue
|
||||||
|
uint32 totalExecutionTime = 0;
|
||||||
|
for (auto& operation : batch)
|
||||||
|
{
|
||||||
|
if (!operation)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Check if operation is still valid
|
||||||
|
if (!operation->IsValid())
|
||||||
|
{
|
||||||
|
LOG_DEBUG("playerbots", "Skipping invalid operation: {}", operation->GetName());
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||||
|
m_stats.totalOperationsSkipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time the execution
|
||||||
|
uint32 startTime = getMSTime();
|
||||||
|
|
||||||
|
// Execute the operation
|
||||||
|
bool success = operation->Execute();
|
||||||
|
|
||||||
|
uint32 executionTime = GetMSTimeDiffToNow(startTime);
|
||||||
|
totalExecutionTime += executionTime;
|
||||||
|
|
||||||
|
// Log slow operations
|
||||||
|
if (executionTime > 100)
|
||||||
|
LOG_WARN("playerbots", "Slow operation: {} took {}ms", operation->GetName(), executionTime);
|
||||||
|
|
||||||
|
// Update statistics
|
||||||
|
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||||
|
if (success)
|
||||||
|
m_stats.totalOperationsProcessed++;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_stats.totalOperationsFailed++;
|
||||||
|
LOG_DEBUG("playerbots", "Operation failed: {}", operation->GetName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (std::exception const& e)
|
||||||
|
{
|
||||||
|
LOG_ERROR("playerbots", "Exception in operation {}: {}", operation->GetName(), e.what());
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||||
|
m_stats.totalOperationsFailed++;
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
LOG_ERROR("playerbots", "Unknown exception in operation {}", operation->GetName());
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||||
|
m_stats.totalOperationsFailed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update average execution time
|
||||||
|
if (!batch.empty())
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||||
|
uint32 avgTime = totalExecutionTime / static_cast<uint32>(batch.size());
|
||||||
|
// Exponential moving average
|
||||||
|
m_stats.averageExecutionTimeMs =
|
||||||
|
(m_stats.averageExecutionTimeMs * 9 + avgTime) / 10; // 90% old, 10% new
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerbotWorldThreadProcessor::CheckQueueHealth()
|
||||||
|
{
|
||||||
|
uint32 queueSize = GetQueueSize();
|
||||||
|
uint32 threshold = (m_maxQueueSize * m_queueWarningThreshold) / 100;
|
||||||
|
|
||||||
|
if (queueSize >= threshold)
|
||||||
|
{
|
||||||
|
LOG_WARN("playerbots",
|
||||||
|
"PlayerbotWorldThreadProcessor queue is {}% full ({}/{}). "
|
||||||
|
"Consider increasing update frequency or batch size.",
|
||||||
|
(queueSize * 100) / m_maxQueueSize, queueSize, m_maxQueueSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 PlayerbotWorldThreadProcessor::GetQueueSize() const
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_queueMutex);
|
||||||
|
return static_cast<uint32>(m_operationQueue.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerbotWorldThreadProcessor::ClearQueue()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_queueMutex);
|
||||||
|
|
||||||
|
uint32 cleared = static_cast<uint32>(m_operationQueue.size());
|
||||||
|
if (cleared > 0)
|
||||||
|
LOG_INFO("playerbots", "Clearing {} queued operations", cleared);
|
||||||
|
|
||||||
|
// Clear the queue
|
||||||
|
while (!m_operationQueue.empty())
|
||||||
|
{
|
||||||
|
m_operationQueue.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset queue size stat
|
||||||
|
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||||
|
m_stats.currentQueueSize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerbotWorldThreadProcessor::Statistics PlayerbotWorldThreadProcessor::GetStatistics() const
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> statsLock(m_statsMutex);
|
||||||
|
return m_stats; // Return a copy
|
||||||
|
}
|
||||||
142
src/PlayerbotWorldThreadProcessor.h
Normal file
142
src/PlayerbotWorldThreadProcessor.h
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
||||||
|
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PLAYERBOT_WORLD_THREAD_PROCESSOR_H
|
||||||
|
#define _PLAYERBOT_WORLD_THREAD_PROCESSOR_H
|
||||||
|
|
||||||
|
#include "Common.h"
|
||||||
|
#include "PlayerbotOperation.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Processes thread-unsafe bot operations in the world thread
|
||||||
|
*
|
||||||
|
* The PlayerbotWorldThreadProcessor manages a queue of operations that must be executed
|
||||||
|
* in the world thread rather than map threads. This ensures thread safety for operations
|
||||||
|
* like group modifications, LFG, guilds, battlegrounds, etc.
|
||||||
|
*
|
||||||
|
* Architecture:
|
||||||
|
* - Map threads queue operations via QueueOperation()
|
||||||
|
* - World thread processes operations via Update() (called from WorldScript::OnUpdate)
|
||||||
|
* - Operations are processed in priority order
|
||||||
|
* - Thread-safe queue protected by mutex
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* auto op = std::make_unique<MyOperation>(botGuid, params);
|
||||||
|
* sPlayerbotWorldProcessor->QueueOperation(std::move(op));
|
||||||
|
*/
|
||||||
|
class PlayerbotWorldThreadProcessor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PlayerbotWorldThreadProcessor();
|
||||||
|
~PlayerbotWorldThreadProcessor();
|
||||||
|
|
||||||
|
static PlayerbotWorldThreadProcessor* instance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Update and process queued operations (called from world thread)
|
||||||
|
*
|
||||||
|
* This method should be called from WorldScript::OnUpdate hook, which runs in the world thread.
|
||||||
|
* It processes a batch of queued operations.
|
||||||
|
*
|
||||||
|
* @param diff Time since last update in milliseconds
|
||||||
|
*/
|
||||||
|
void Update(uint32 diff);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Queue an operation for execution in the world thread
|
||||||
|
*
|
||||||
|
* Thread-safe method that can be called from any thread (typically map threads).
|
||||||
|
* The operation will be executed later during Update().
|
||||||
|
*
|
||||||
|
* @param operation Unique pointer to the operation (ownership is transferred)
|
||||||
|
* @return true if operation was queued, false if queue is full
|
||||||
|
*/
|
||||||
|
bool QueueOperation(std::unique_ptr<PlayerbotOperation> operation);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get current queue size
|
||||||
|
*
|
||||||
|
* Thread-safe method for monitoring queue size.
|
||||||
|
*
|
||||||
|
* @return Number of operations waiting to be processed
|
||||||
|
*/
|
||||||
|
uint32 GetQueueSize() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Clear all queued operations
|
||||||
|
*
|
||||||
|
* Used during shutdown or emergency situations.
|
||||||
|
*/
|
||||||
|
void ClearQueue();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get statistics about operation processing
|
||||||
|
*/
|
||||||
|
struct Statistics
|
||||||
|
{
|
||||||
|
uint64 totalOperationsProcessed = 0;
|
||||||
|
uint64 totalOperationsFailed = 0;
|
||||||
|
uint64 totalOperationsSkipped = 0;
|
||||||
|
uint32 currentQueueSize = 0;
|
||||||
|
uint32 maxQueueSize = 0;
|
||||||
|
uint32 averageExecutionTimeMs = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
Statistics GetStatistics() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enable/disable operation processing
|
||||||
|
*
|
||||||
|
* When disabled, operations are still queued but not processed.
|
||||||
|
* Useful for testing or temporary suspension.
|
||||||
|
*
|
||||||
|
* @param enabled true to enable processing, false to disable
|
||||||
|
*/
|
||||||
|
void SetEnabled(bool enabled) { m_enabled = enabled; }
|
||||||
|
|
||||||
|
bool IsEnabled() const { return m_enabled; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* @brief Process a single batch of operations
|
||||||
|
*
|
||||||
|
* Extracts operations from queue and executes them.
|
||||||
|
* Called internally by Update().
|
||||||
|
*/
|
||||||
|
void ProcessBatch();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if queue is approaching capacity
|
||||||
|
*
|
||||||
|
* Logs warning if queue is getting full.
|
||||||
|
*/
|
||||||
|
void CheckQueueHealth();
|
||||||
|
|
||||||
|
// Thread-safe queue
|
||||||
|
mutable std::mutex m_queueMutex;
|
||||||
|
std::queue<std::unique_ptr<PlayerbotOperation>> m_operationQueue;
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
bool m_enabled;
|
||||||
|
uint32 m_maxQueueSize; // Maximum operations in queue
|
||||||
|
uint32 m_batchSize; // Operations to process per Update()
|
||||||
|
uint32 m_queueWarningThreshold; // Warn when queue reaches this percentage
|
||||||
|
|
||||||
|
// Statistics
|
||||||
|
mutable std::mutex m_statsMutex;
|
||||||
|
Statistics m_stats;
|
||||||
|
|
||||||
|
// Timing
|
||||||
|
uint32 m_timeSinceLastUpdate;
|
||||||
|
uint32 m_updateInterval; // Minimum ms between updates
|
||||||
|
};
|
||||||
|
|
||||||
|
#define sPlayerbotWorldProcessor PlayerbotWorldThreadProcessor::instance()
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
#include "Metric.h"
|
#include "Metric.h"
|
||||||
#include "PlayerScript.h"
|
#include "PlayerScript.h"
|
||||||
#include "PlayerbotAIConfig.h"
|
#include "PlayerbotAIConfig.h"
|
||||||
|
#include "PlayerbotWorldThreadProcessor.h"
|
||||||
#include "RandomPlayerbotMgr.h"
|
#include "RandomPlayerbotMgr.h"
|
||||||
#include "ScriptMgr.h"
|
#include "ScriptMgr.h"
|
||||||
#include "cs_playerbots.h"
|
#include "cs_playerbots.h"
|
||||||
@@ -81,12 +82,12 @@ public:
|
|||||||
PlayerbotsPlayerScript() : PlayerScript("PlayerbotsPlayerScript", {
|
PlayerbotsPlayerScript() : PlayerScript("PlayerbotsPlayerScript", {
|
||||||
PLAYERHOOK_ON_LOGIN,
|
PLAYERHOOK_ON_LOGIN,
|
||||||
PLAYERHOOK_ON_AFTER_UPDATE,
|
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_CRITERIA_PROGRESS,
|
||||||
PLAYERHOOK_ON_BEFORE_ACHI_COMPLETE,
|
PLAYERHOOK_ON_BEFORE_ACHI_COMPLETE,
|
||||||
PLAYERHOOK_CAN_PLAYER_USE_PRIVATE_CHAT,
|
PLAYERHOOK_CAN_PLAYER_USE_PRIVATE_CHAT,
|
||||||
|
PLAYERHOOK_CAN_PLAYER_USE_GROUP_CHAT,
|
||||||
|
PLAYERHOOK_CAN_PLAYER_USE_GUILD_CHAT,
|
||||||
|
PLAYERHOOK_CAN_PLAYER_USE_CHANNEL_CHAT,
|
||||||
PLAYERHOOK_ON_GIVE_EXP,
|
PLAYERHOOK_ON_GIVE_EXP,
|
||||||
PLAYERHOOK_ON_BEFORE_TELEPORT
|
PLAYERHOOK_ON_BEFORE_TELEPORT
|
||||||
}) {}
|
}) {}
|
||||||
@@ -163,14 +164,17 @@ public:
|
|||||||
{
|
{
|
||||||
botAI->HandleCommand(type, msg, player);
|
botAI->HandleCommand(type, msg, player);
|
||||||
|
|
||||||
return false;
|
// hotfix; otherwise the server will crash when whispering logout
|
||||||
|
// https://github.com/mod-playerbots/mod-playerbots/pull/1838
|
||||||
|
// TODO: find the root cause and solve it. (does not happen in party chat)
|
||||||
|
if (msg == "logout")
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnPlayerChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Group* group) override
|
bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Group* group) override
|
||||||
{
|
{
|
||||||
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
|
||||||
{
|
{
|
||||||
@@ -182,9 +186,10 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnPlayerChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg) override
|
bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Guild* guild) override
|
||||||
{
|
{
|
||||||
if (type == CHAT_MSG_GUILD)
|
if (type == CHAT_MSG_GUILD)
|
||||||
{
|
{
|
||||||
@@ -203,9 +208,10 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnPlayerChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Channel* channel) override
|
bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Channel* channel) override
|
||||||
{
|
{
|
||||||
if (PlayerbotMgr* playerbotMgr = GET_PLAYERBOT_MGR(player))
|
if (PlayerbotMgr* playerbotMgr = GET_PLAYERBOT_MGR(player))
|
||||||
{
|
{
|
||||||
@@ -216,6 +222,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
sRandomPlayerbotMgr->HandleCommand(type, msg, player);
|
sRandomPlayerbotMgr->HandleCommand(type, msg, player);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OnPlayerBeforeAchievementComplete(Player* player, AchievementEntry const* achievement) override
|
bool OnPlayerBeforeAchievementComplete(Player* player, AchievementEntry const* achievement) override
|
||||||
@@ -300,7 +307,8 @@ class PlayerbotsWorldScript : public WorldScript
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PlayerbotsWorldScript() : WorldScript("PlayerbotsWorldScript", {
|
PlayerbotsWorldScript() : WorldScript("PlayerbotsWorldScript", {
|
||||||
WORLDHOOK_ON_BEFORE_WORLD_INITIALIZED
|
WORLDHOOK_ON_BEFORE_WORLD_INITIALIZED,
|
||||||
|
WORLDHOOK_ON_UPDATE
|
||||||
}) {}
|
}) {}
|
||||||
|
|
||||||
void OnBeforeWorldInitialized() override
|
void OnBeforeWorldInitialized() override
|
||||||
@@ -329,6 +337,13 @@ public:
|
|||||||
|
|
||||||
LOG_INFO("server.loading", ">> Loaded playerbots config in {} ms", GetMSTimeDiffToNow(oldMSTime));
|
LOG_INFO("server.loading", ">> Loaded playerbots config in {} ms", GetMSTimeDiffToNow(oldMSTime));
|
||||||
LOG_INFO("server.loading", " ");
|
LOG_INFO("server.loading", " ");
|
||||||
|
LOG_INFO("server.loading", "Playerbots World Thread Processor initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnUpdate(uint32 diff) override
|
||||||
|
{
|
||||||
|
sPlayerbotWorldProcessor->Update(diff);
|
||||||
|
sRandomPlayerbotMgr->UpdateAI(diff); // World thread only
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -390,8 +405,7 @@ public:
|
|||||||
|
|
||||||
void OnPlayerbotUpdate(uint32 diff) override
|
void OnPlayerbotUpdate(uint32 diff) override
|
||||||
{
|
{
|
||||||
sRandomPlayerbotMgr->UpdateAI(diff);
|
sRandomPlayerbotMgr->UpdateSessions(); // Per-bot updates only
|
||||||
sRandomPlayerbotMgr->UpdateSessions();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnPlayerbotUpdateSessions(Player* player) override
|
void OnPlayerbotUpdateSessions(Player* player) override
|
||||||
|
|||||||
@@ -40,7 +40,14 @@ bool ReachAreaTriggerAction::Execute(Event event)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bot->GetMotionMaster()->MovePoint(at->map, at->x, at->y, at->z);
|
bot->GetMotionMaster()->MovePoint(
|
||||||
|
/*id*/ at->map,
|
||||||
|
/*coords*/ at->x, at->y, at->z,
|
||||||
|
/*forcedMovement*/ FORCED_MOVEMENT_NONE,
|
||||||
|
/*speed*/ 0.0f, // default speed (not handled here)
|
||||||
|
/*orientation*/ 0.0f, // keep current orientation of bot
|
||||||
|
/*generatePath*/ true, // true => terrain path (2d mmap); false => straight spline (3d vmap)
|
||||||
|
/*forceDestination*/ false);
|
||||||
|
|
||||||
float distance = bot->GetDistance(at->x, at->y, at->z);
|
float distance = bot->GetDistance(at->x, at->y, at->z);
|
||||||
float delay = 1000.0f * distance / bot->GetSpeed(MOVE_RUN) + sPlayerbotAIConfig->reactDelay;
|
float delay = 1000.0f * distance / bot->GetSpeed(MOVE_RUN) + sPlayerbotAIConfig->reactDelay;
|
||||||
|
|||||||
@@ -4,10 +4,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "BattleGroundTactics.h"
|
#include "BattleGroundTactics.h"
|
||||||
#include "BattleGroundJoinAction.h"
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include "ArenaTeam.h"
|
#include "ArenaTeam.h"
|
||||||
#include "ArenaTeamMgr.h"
|
#include "ArenaTeamMgr.h"
|
||||||
|
#include "BattleGroundJoinAction.h"
|
||||||
#include "Battleground.h"
|
#include "Battleground.h"
|
||||||
#include "BattlegroundAB.h"
|
#include "BattlegroundAB.h"
|
||||||
#include "BattlegroundAV.h"
|
#include "BattlegroundAV.h"
|
||||||
@@ -22,11 +24,12 @@
|
|||||||
#include "BattlegroundSA.h"
|
#include "BattlegroundSA.h"
|
||||||
#include "BattlegroundWS.h"
|
#include "BattlegroundWS.h"
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
|
#include "GameObject.h"
|
||||||
#include "IVMapMgr.h"
|
#include "IVMapMgr.h"
|
||||||
|
#include "PathGenerator.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "PositionValue.h"
|
#include "PositionValue.h"
|
||||||
#include "PvpTriggers.h"
|
#include "PvpTriggers.h"
|
||||||
#include "PathGenerator.h"
|
|
||||||
#include "ServerFacade.h"
|
#include "ServerFacade.h"
|
||||||
#include "Vehicle.h"
|
#include "Vehicle.h"
|
||||||
|
|
||||||
@@ -1754,7 +1757,7 @@ bool BGTactics::moveToStart(bool force)
|
|||||||
WS_WAITING_POS_ALLIANCE_2.GetPositionY() + frand(-4.0f, 4.0f),
|
WS_WAITING_POS_ALLIANCE_2.GetPositionY() + frand(-4.0f, 4.0f),
|
||||||
WS_WAITING_POS_ALLIANCE_2.GetPositionZ());
|
WS_WAITING_POS_ALLIANCE_2.GetPositionZ());
|
||||||
}
|
}
|
||||||
else // BB_WSG_WAIT_SPOT_SPAWN
|
else // BB_WSG_WAIT_SPOT_SPAWN
|
||||||
{
|
{
|
||||||
if (bot->GetTeamId() == TEAM_HORDE)
|
if (bot->GetTeamId() == TEAM_HORDE)
|
||||||
MoveTo(bg->GetMapId(), WS_WAITING_POS_HORDE_3.GetPositionX() + frand(-10.0f, 10.0f),
|
MoveTo(bg->GetMapId(), WS_WAITING_POS_HORDE_3.GetPositionX() + frand(-10.0f, 10.0f),
|
||||||
@@ -3365,12 +3368,12 @@ bool BGTactics::resetObjective()
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Adjust role-change chance based on battleground type
|
// Adjust role-change chance based on battleground type
|
||||||
uint32 oddsToChangeRole = 1; // default low
|
uint32 oddsToChangeRole = 1; // default low
|
||||||
BattlegroundTypeId bgType = bg->GetBgTypeID();
|
BattlegroundTypeId bgType = bg->GetBgTypeID();
|
||||||
|
|
||||||
if (bgType == BATTLEGROUND_WS)
|
if (bgType == BATTLEGROUND_WS)
|
||||||
oddsToChangeRole = 2;
|
oddsToChangeRole = 2;
|
||||||
else if (bgType == BATTLEGROUND_EY || bgType == BATTLEGROUND_IC || bgType == BATTLEGROUND_AB)
|
else if (bgType == BATTLEGROUND_EY || bgType == BATTLEGROUND_IC || bgType == BATTLEGROUND_AB)
|
||||||
oddsToChangeRole = 1;
|
oddsToChangeRole = 1;
|
||||||
else if (bgType == BATTLEGROUND_AV)
|
else if (bgType == BATTLEGROUND_AV)
|
||||||
oddsToChangeRole = 0;
|
oddsToChangeRole = 0;
|
||||||
@@ -3578,6 +3581,16 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
|||||||
GuidVector closePlayers;
|
GuidVector closePlayers;
|
||||||
float flagRange = 0.0f;
|
float flagRange = 0.0f;
|
||||||
|
|
||||||
|
// Eye of the Storm helpers used later when handling capture positioning
|
||||||
|
BattlegroundEY* eyeBg = nullptr;
|
||||||
|
GameObject* eyCenterFlag = nullptr;
|
||||||
|
if (bgType == BATTLEGROUND_EY)
|
||||||
|
{
|
||||||
|
eyeBg = static_cast<BattlegroundEY*>(bg);
|
||||||
|
if (eyeBg)
|
||||||
|
eyCenterFlag = eyeBg->GetBGObject(BG_EY_OBJECT_FLAG_NETHERSTORM);
|
||||||
|
}
|
||||||
|
|
||||||
// Set up appropriate search ranges and object lists based on BG type
|
// Set up appropriate search ranges and object lists based on BG type
|
||||||
switch (bgType)
|
switch (bgType)
|
||||||
{
|
{
|
||||||
@@ -3607,27 +3620,82 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
|||||||
if (closeObjects.empty())
|
if (closeObjects.empty())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
auto keepStationaryWhileCapturing = [&](CurrentSpellTypes spellType)
|
||||||
|
{
|
||||||
|
Spell* currentSpell = bot->GetCurrentSpell(spellType);
|
||||||
|
if (!currentSpell || !currentSpell->m_spellInfo || currentSpell->m_spellInfo->Id != SPELL_CAPTURE_BANNER)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// If the capture target is no longer available (another bot already captured it), stop channeling
|
||||||
|
if (GameObject* targetFlag = currentSpell->m_targets.GetGOTarget())
|
||||||
|
{
|
||||||
|
if (!targetFlag->isSpawned() || targetFlag->GetGoState() != GO_STATE_READY)
|
||||||
|
{
|
||||||
|
bot->InterruptNonMeleeSpells(true);
|
||||||
|
resetObjective();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bot->InterruptNonMeleeSpells(true);
|
||||||
|
resetObjective();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bot->IsMounted())
|
||||||
|
{
|
||||||
|
bot->RemoveAurasByType(SPELL_AURA_MOUNTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bot->IsInDisallowedMountForm())
|
||||||
|
{
|
||||||
|
bot->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bot->isMoving())
|
||||||
|
{
|
||||||
|
bot->StopMoving();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// If we are already channeling the capture spell, keep the bot stationary and dismounted
|
||||||
|
if (keepStationaryWhileCapturing(CURRENT_CHANNELED_SPELL) || keepStationaryWhileCapturing(CURRENT_GENERIC_SPELL))
|
||||||
|
return true;
|
||||||
|
|
||||||
// First identify which flag/base we're trying to interact with
|
// First identify which flag/base we're trying to interact with
|
||||||
GameObject* targetFlag = nullptr;
|
GameObject* targetFlag = nullptr;
|
||||||
for (ObjectGuid const guid : closeObjects)
|
for (ObjectGuid const guid : closeObjects)
|
||||||
{
|
{
|
||||||
GameObject* go = botAI->GetGameObject(guid);
|
GameObject* go = botAI->GetGameObject(guid);
|
||||||
if (!go)
|
if (!go)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool const isEyCenterFlag = eyeBg && eyCenterFlag && eyCenterFlag->GetGUID() == go->GetGUID();
|
||||||
|
|
||||||
// Check if this object is a valid capture target
|
// Check if this object is a valid capture target
|
||||||
std::vector<uint32>::const_iterator f = find(vFlagIds.begin(), vFlagIds.end(), go->GetEntry());
|
std::vector<uint32>::const_iterator f = std::find(vFlagIds.begin(), vFlagIds.end(), go->GetEntry());
|
||||||
if (f == vFlagIds.end())
|
if (f == vFlagIds.end() && !isEyCenterFlag)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Verify the object is active and ready
|
// Verify the object is active and ready
|
||||||
if (!go->isSpawned() || go->GetGoState() != GO_STATE_READY)
|
if (!go->isSpawned() || go->GetGoState() != GO_STATE_READY)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if we're in range (using double range for enemy detection)
|
// Check if we're in range (using double range for enemy detection)
|
||||||
float const dist = bot->GetDistance(go);
|
float const dist = bot->GetDistance(go);
|
||||||
if (flagRange && dist > flagRange * 2.0f)
|
if (flagRange && dist > flagRange * 2.0f)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
targetFlag = go;
|
targetFlag = go;
|
||||||
break;
|
break;
|
||||||
@@ -3655,7 +3723,7 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if friendly players are already capturing
|
// Check if friendly players are already capturing
|
||||||
if (!closePlayers.empty())
|
if (!closePlayers.empty() && bgType != BATTLEGROUND_EY)
|
||||||
{
|
{
|
||||||
// Track number of friendly players capturing and the closest one
|
// Track number of friendly players capturing and the closest one
|
||||||
uint32 numCapturing = 0;
|
uint32 numCapturing = 0;
|
||||||
@@ -3664,14 +3732,17 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
|||||||
{
|
{
|
||||||
if (Unit* pFriend = botAI->GetUnit(guid))
|
if (Unit* pFriend = botAI->GetUnit(guid))
|
||||||
{
|
{
|
||||||
// Check if they're casting the capture spell
|
// Check if they're casting or channeling the capture spell
|
||||||
if (Spell* spell = pFriend->GetCurrentSpell(CURRENT_GENERIC_SPELL))
|
Spell* spell = pFriend->GetCurrentSpell(CURRENT_GENERIC_SPELL);
|
||||||
|
if (!spell)
|
||||||
{
|
{
|
||||||
if (spell->m_spellInfo->Id == SPELL_CAPTURE_BANNER)
|
spell = pFriend->GetCurrentSpell(CURRENT_CHANNELED_SPELL);
|
||||||
{
|
}
|
||||||
numCapturing++;
|
|
||||||
capturingPlayer = pFriend;
|
if (spell && spell->m_spellInfo && spell->m_spellInfo->Id == SPELL_CAPTURE_BANNER)
|
||||||
}
|
{
|
||||||
|
numCapturing++;
|
||||||
|
capturingPlayer = pFriend;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3704,9 +3775,11 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
|||||||
if (!go)
|
if (!go)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
bool const isEyCenterFlag = eyeBg && eyCenterFlag && eyCenterFlag->GetGUID() == go->GetGUID();
|
||||||
|
|
||||||
// Validate this is a capture target
|
// Validate this is a capture target
|
||||||
std::vector<uint32>::const_iterator f = find(vFlagIds.begin(), vFlagIds.end(), go->GetEntry());
|
std::vector<uint32>::const_iterator f = std::find(vFlagIds.begin(), vFlagIds.end(), go->GetEntry());
|
||||||
if (f == vFlagIds.end())
|
if (f == vFlagIds.end() && !isEyCenterFlag)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Check object is active
|
// Check object is active
|
||||||
@@ -3722,12 +3795,40 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Special handling for WSG and EY base flags
|
// Special handling for WSG and EY base flags
|
||||||
bool atBase = bgType == BATTLEGROUND_WS ? go->GetEntry() == vFlagsWS[bot->GetTeamId()]
|
bool isWsBaseFlag = bgType == BATTLEGROUND_WS && go->GetEntry() == vFlagsWS[bot->GetTeamId()];
|
||||||
: bgType == BATTLEGROUND_EY ? go->GetEntry() == vFlagsEY[0]
|
bool isEyBaseFlag = bgType == BATTLEGROUND_EY && go->GetEntry() == vFlagsEY[0];
|
||||||
: false;
|
|
||||||
|
// Ensure bots are inside the Eye of the Storm capture circle before casting
|
||||||
|
if (bgType == BATTLEGROUND_EY)
|
||||||
|
{
|
||||||
|
GameObject* captureFlag = (isEyBaseFlag && eyCenterFlag) ? eyCenterFlag : go;
|
||||||
|
float const requiredRange = 2.5f;
|
||||||
|
if (!bot->IsWithinDistInMap(captureFlag, requiredRange))
|
||||||
|
{
|
||||||
|
// Stay mounted while relocating to avoid mount/dismount loops
|
||||||
|
return MoveTo(bot->GetMapId(), captureFlag->GetPositionX(), captureFlag->GetPositionY(),
|
||||||
|
captureFlag->GetPositionZ());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once inside the circle, dismount and stop before starting the channel
|
||||||
|
if (bot->IsMounted())
|
||||||
|
{
|
||||||
|
bot->RemoveAurasByType(SPELL_AURA_MOUNTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bot->IsInDisallowedMountForm())
|
||||||
|
{
|
||||||
|
bot->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bot->isMoving())
|
||||||
|
{
|
||||||
|
bot->StopMoving();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Don't capture own flag in WSG unless carrying enemy flag
|
// Don't capture own flag in WSG unless carrying enemy flag
|
||||||
if (atBase && bgType == BATTLEGROUND_WS &&
|
if (isWsBaseFlag && bgType == BATTLEGROUND_WS &&
|
||||||
!(bot->HasAura(BG_WS_SPELL_WARSONG_FLAG) || bot->HasAura(BG_WS_SPELL_SILVERWING_FLAG)))
|
!(bot->HasAura(BG_WS_SPELL_WARSONG_FLAG) || bot->HasAura(BG_WS_SPELL_SILVERWING_FLAG)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -3743,7 +3844,7 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
|||||||
{
|
{
|
||||||
float const moveDist = bot->GetObjectSize() + go->GetObjectSize() + 0.1f;
|
float const moveDist = bot->GetObjectSize() + go->GetObjectSize() + 0.1f;
|
||||||
return MoveTo(bot->GetMapId(), go->GetPositionX() + (urand(0, 1) ? -moveDist : moveDist),
|
return MoveTo(bot->GetMapId(), go->GetPositionX() + (urand(0, 1) ? -moveDist : moveDist),
|
||||||
go->GetPositionY() + (urand(0, 1) ? -moveDist : moveDist), go->GetPositionZ());
|
go->GetPositionY() + (urand(0, 1) ? -moveDist : moveDist), go->GetPositionZ());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dismount before capturing
|
// Dismount before capturing
|
||||||
@@ -3772,7 +3873,7 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
|||||||
if (dist < INTERACTION_DISTANCE)
|
if (dist < INTERACTION_DISTANCE)
|
||||||
{
|
{
|
||||||
// Handle flag capture at base
|
// Handle flag capture at base
|
||||||
if (atBase)
|
if (isWsBaseFlag)
|
||||||
{
|
{
|
||||||
if (bot->GetTeamId() == TEAM_HORDE)
|
if (bot->GetTeamId() == TEAM_HORDE)
|
||||||
{
|
{
|
||||||
@@ -3811,28 +3912,50 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case BATTLEGROUND_EY:
|
case BATTLEGROUND_EY:
|
||||||
{ // Issue: Currently bots in EY take flag instantly without casttime
|
{ // Handle Netherstorm flag capture requiring a channel
|
||||||
if (dist < INTERACTION_DISTANCE)
|
if (dist < INTERACTION_DISTANCE)
|
||||||
{
|
{
|
||||||
// Dismount before interacting
|
// Dismount before interacting
|
||||||
if (bot->IsMounted())
|
if (bot->IsMounted())
|
||||||
|
{
|
||||||
bot->RemoveAurasByType(SPELL_AURA_MOUNTED);
|
bot->RemoveAurasByType(SPELL_AURA_MOUNTED);
|
||||||
|
}
|
||||||
|
|
||||||
if (bot->IsInDisallowedMountForm())
|
if (bot->IsInDisallowedMountForm())
|
||||||
|
{
|
||||||
bot->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT);
|
bot->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT);
|
||||||
|
}
|
||||||
|
|
||||||
// Handle center flag differently (requires spell cast)
|
// Handle center flag differently (requires spell cast)
|
||||||
if (atBase)
|
if (isEyCenterFlag)
|
||||||
{
|
{
|
||||||
|
for (uint8 type = CURRENT_MELEE_SPELL; type <= CURRENT_CHANNELED_SPELL; ++type)
|
||||||
|
{
|
||||||
|
if (Spell* currentSpell = bot->GetCurrentSpell(static_cast<CurrentSpellTypes>(type)))
|
||||||
|
{
|
||||||
|
// m_spellInfo may be null in some states: protect access
|
||||||
|
if (currentSpell->m_spellInfo && currentSpell->m_spellInfo->Id == SPELL_CAPTURE_BANNER)
|
||||||
|
{
|
||||||
|
bot->StopMoving();
|
||||||
|
botAI->SetNextCheckDelay(500);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(SPELL_CAPTURE_BANNER);
|
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(SPELL_CAPTURE_BANNER);
|
||||||
if (!spellInfo)
|
if (!spellInfo)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Spell* spell = new Spell(bot, spellInfo, TRIGGERED_NONE);
|
Spell* spell = new Spell(bot, spellInfo, TRIGGERED_NONE);
|
||||||
spell->m_targets.SetGOTarget(go);
|
spell->m_targets.SetGOTarget(go);
|
||||||
|
|
||||||
|
bot->StopMoving();
|
||||||
spell->prepare(&spell->m_targets);
|
spell->prepare(&spell->m_targets);
|
||||||
|
|
||||||
botAI->WaitForSpellCast(spell);
|
botAI->WaitForSpellCast(spell);
|
||||||
//return true; Intended to make a bot cast SPELL_CAPTURE_BANNER and wait for spell finish, but doesn't work and causes infinite loop
|
resetObjective();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pick up dropped flag
|
// Pick up dropped flag
|
||||||
@@ -3849,8 +3972,8 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
|||||||
return MoveTo(bot->GetMapId(), go->GetPositionX(), go->GetPositionY(), go->GetPositionZ());
|
return MoveTo(bot->GetMapId(), go->GetPositionX(), go->GetPositionY(), go->GetPositionZ());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ bool CheckMountStateAction::isUseful()
|
|||||||
// to mostly be an issue in tunnels of WSG and AV)
|
// to mostly be an issue in tunnels of WSG and AV)
|
||||||
float posZ = bot->GetPositionZ();
|
float posZ = bot->GetPositionZ();
|
||||||
float groundLevel = bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), posZ);
|
float groundLevel = bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), posZ);
|
||||||
if (!bot->IsMounted() && posZ < groundLevel)
|
if (!bot->IsMounted() && !bot->HasWaterWalkAura() && posZ < groundLevel)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Not useful when bot does not have mount strat and is not currently mounted
|
// Not useful when bot does not have mount strat and is not currently mounted
|
||||||
|
|||||||
@@ -58,6 +58,14 @@ Player* GuidManageAction::GetPlayer(Event event)
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GuidManageAction::SendPacket(WorldPacket const& packet)
|
||||||
|
{
|
||||||
|
// make a heap copy because QueuePacket takes ownership
|
||||||
|
WorldPacket* data = new WorldPacket(packet);
|
||||||
|
|
||||||
|
bot->GetSession()->QueuePacket(data);
|
||||||
|
}
|
||||||
|
|
||||||
bool GuidManageAction::Execute(Event event)
|
bool GuidManageAction::Execute(Event event)
|
||||||
{
|
{
|
||||||
Player* player = GetPlayer(event);
|
Player* player = GetPlayer(event);
|
||||||
@@ -84,12 +92,6 @@ bool GuildInviteAction::isUseful()
|
|||||||
return bot->GetGuildId() && sGuildMgr->GetGuildById(bot->GetGuildId())->HasRankRight(bot, GR_RIGHT_INVITE);
|
return bot->GetGuildId() && sGuildMgr->GetGuildById(bot->GetGuildId())->HasRankRight(bot, GR_RIGHT_INVITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GuildInviteAction::SendPacket(WorldPacket packet)
|
|
||||||
{
|
|
||||||
WorldPackets::Guild::GuildInviteByName data = WorldPacket(packet);
|
|
||||||
bot->GetSession()->HandleGuildInviteOpcode(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GuildInviteAction::PlayerIsValid(Player* member)
|
bool GuildInviteAction::PlayerIsValid(Player* member)
|
||||||
{
|
{
|
||||||
return !member->GetGuildId() && (sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GUILD) ||
|
return !member->GetGuildId() && (sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GUILD) ||
|
||||||
@@ -101,12 +103,6 @@ bool GuildPromoteAction::isUseful()
|
|||||||
return bot->GetGuildId() && sGuildMgr->GetGuildById(bot->GetGuildId())->HasRankRight(bot, GR_RIGHT_PROMOTE);
|
return bot->GetGuildId() && sGuildMgr->GetGuildById(bot->GetGuildId())->HasRankRight(bot, GR_RIGHT_PROMOTE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GuildPromoteAction::SendPacket(WorldPacket packet)
|
|
||||||
{
|
|
||||||
WorldPackets::Guild::GuildPromoteMember data = WorldPacket(packet);
|
|
||||||
bot->GetSession()->HandleGuildPromoteOpcode(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GuildPromoteAction::PlayerIsValid(Player* member)
|
bool GuildPromoteAction::PlayerIsValid(Player* member)
|
||||||
{
|
{
|
||||||
return member->GetGuildId() == bot->GetGuildId() && GetRankId(bot) < GetRankId(member) - 1;
|
return member->GetGuildId() == bot->GetGuildId() && GetRankId(bot) < GetRankId(member) - 1;
|
||||||
@@ -117,12 +113,6 @@ bool GuildDemoteAction::isUseful()
|
|||||||
return bot->GetGuildId() && sGuildMgr->GetGuildById(bot->GetGuildId())->HasRankRight(bot, GR_RIGHT_DEMOTE);
|
return bot->GetGuildId() && sGuildMgr->GetGuildById(bot->GetGuildId())->HasRankRight(bot, GR_RIGHT_DEMOTE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GuildDemoteAction::SendPacket(WorldPacket packet)
|
|
||||||
{
|
|
||||||
WorldPackets::Guild::GuildDemoteMember data = WorldPacket(packet);
|
|
||||||
bot->GetSession()->HandleGuildDemoteOpcode(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GuildDemoteAction::PlayerIsValid(Player* member)
|
bool GuildDemoteAction::PlayerIsValid(Player* member)
|
||||||
{
|
{
|
||||||
return member->GetGuildId() == bot->GetGuildId() && GetRankId(bot) < GetRankId(member);
|
return member->GetGuildId() == bot->GetGuildId() && GetRankId(bot) < GetRankId(member);
|
||||||
@@ -133,12 +123,6 @@ bool GuildRemoveAction::isUseful()
|
|||||||
return bot->GetGuildId() && sGuildMgr->GetGuildById(bot->GetGuildId())->HasRankRight(bot, GR_RIGHT_REMOVE);
|
return bot->GetGuildId() && sGuildMgr->GetGuildById(bot->GetGuildId())->HasRankRight(bot, GR_RIGHT_REMOVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GuildRemoveAction::SendPacket(WorldPacket packet)
|
|
||||||
{
|
|
||||||
WorldPackets::Guild::GuildOfficerRemoveMember data = WorldPacket(packet);
|
|
||||||
bot->GetSession()->HandleGuildRemoveOpcode(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GuildRemoveAction::PlayerIsValid(Player* member)
|
bool GuildRemoveAction::PlayerIsValid(Player* member)
|
||||||
{
|
{
|
||||||
return member->GetGuildId() == bot->GetGuildId() && GetRankId(bot) < GetRankId(member);
|
return member->GetGuildId() == bot->GetGuildId() && GetRankId(bot) < GetRankId(member);
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public:
|
|||||||
bool isUseful() override { return false; }
|
bool isUseful() override { return false; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void SendPacket(WorldPacket data){};
|
virtual void SendPacket(WorldPacket const& packet);
|
||||||
virtual Player* GetPlayer(Event event);
|
virtual Player* GetPlayer(Event event);
|
||||||
virtual bool PlayerIsValid(Player* member);
|
virtual bool PlayerIsValid(Player* member);
|
||||||
virtual uint8 GetRankId(Player* member);
|
virtual uint8 GetRankId(Player* member);
|
||||||
@@ -44,7 +44,6 @@ public:
|
|||||||
bool isUseful() override;
|
bool isUseful() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void SendPacket(WorldPacket data) override;
|
|
||||||
bool PlayerIsValid(Player* member) override;
|
bool PlayerIsValid(Player* member) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -59,7 +58,6 @@ public:
|
|||||||
bool isUseful() override;
|
bool isUseful() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void SendPacket(WorldPacket data) override;
|
|
||||||
bool PlayerIsValid(Player* member) override;
|
bool PlayerIsValid(Player* member) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -74,7 +72,6 @@ public:
|
|||||||
bool isUseful() override;
|
bool isUseful() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void SendPacket(WorldPacket data) override;
|
|
||||||
bool PlayerIsValid(Player* member) override;
|
bool PlayerIsValid(Player* member) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -89,7 +86,6 @@ public:
|
|||||||
bool isUseful() override;
|
bool isUseful() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void SendPacket(WorldPacket data) override;
|
|
||||||
bool PlayerIsValid(Player* member) override;
|
bool PlayerIsValid(Player* member) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,9 @@
|
|||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "GuildMgr.h"
|
#include "GuildMgr.h"
|
||||||
#include "Log.h"
|
#include "Log.h"
|
||||||
|
#include "PlayerbotOperations.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
#include "PlayerbotWorldThreadProcessor.h"
|
||||||
#include "ServerFacade.h"
|
#include "ServerFacade.h"
|
||||||
|
|
||||||
bool InviteToGroupAction::Invite(Player* inviter, Player* player)
|
bool InviteToGroupAction::Invite(Player* inviter, Player* player)
|
||||||
@@ -27,7 +29,10 @@ bool InviteToGroupAction::Invite(Player* inviter, Player* player)
|
|||||||
{
|
{
|
||||||
if (GET_PLAYERBOT_AI(player) && !GET_PLAYERBOT_AI(player)->IsRealPlayer())
|
if (GET_PLAYERBOT_AI(player) && !GET_PLAYERBOT_AI(player)->IsRealPlayer())
|
||||||
if (!group->isRaidGroup() && group->GetMembersCount() > 4)
|
if (!group->isRaidGroup() && group->GetMembersCount() > 4)
|
||||||
group->ConvertToRaid();
|
{
|
||||||
|
auto convertOp = std::make_unique<GroupConvertToRaidOperation>(inviter->GetGUID());
|
||||||
|
sPlayerbotWorldProcessor->QueueOperation(std::move(convertOp));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WorldPacket p;
|
WorldPacket p;
|
||||||
@@ -89,7 +94,10 @@ bool InviteNearbyToGroupAction::Execute(Event event)
|
|||||||
// When inviting the 5th member of the group convert to raid for future invites.
|
// When inviting the 5th member of the group convert to raid for future invites.
|
||||||
if (group && botAI->GetGrouperType() > GrouperType::LEADER_5 && !group->isRaidGroup() &&
|
if (group && botAI->GetGrouperType() > GrouperType::LEADER_5 && !group->isRaidGroup() &&
|
||||||
bot->GetGroup()->GetMembersCount() > 3)
|
bot->GetGroup()->GetMembersCount() > 3)
|
||||||
group->ConvertToRaid();
|
{
|
||||||
|
auto convertOp = std::make_unique<GroupConvertToRaidOperation>(bot->GetGUID());
|
||||||
|
sPlayerbotWorldProcessor->QueueOperation(std::move(convertOp));
|
||||||
|
}
|
||||||
|
|
||||||
if (sPlayerbotAIConfig->inviteChat && sRandomPlayerbotMgr->IsRandomBot(bot))
|
if (sPlayerbotAIConfig->inviteChat && sRandomPlayerbotMgr->IsRandomBot(bot))
|
||||||
{
|
{
|
||||||
@@ -221,7 +229,8 @@ bool InviteGuildToGroupAction::Execute(Event event)
|
|||||||
if (group && botAI->GetGrouperType() > GrouperType::LEADER_5 && !group->isRaidGroup() &&
|
if (group && botAI->GetGrouperType() > GrouperType::LEADER_5 && !group->isRaidGroup() &&
|
||||||
bot->GetGroup()->GetMembersCount() > 3)
|
bot->GetGroup()->GetMembersCount() > 3)
|
||||||
{
|
{
|
||||||
group->ConvertToRaid();
|
auto convertOp = std::make_unique<GroupConvertToRaidOperation>(bot->GetGUID());
|
||||||
|
sPlayerbotWorldProcessor->QueueOperation(std::move(convertOp));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sPlayerbotAIConfig->inviteChat &&
|
if (sPlayerbotAIConfig->inviteChat &&
|
||||||
@@ -362,7 +371,10 @@ bool LfgAction::Execute(Event event)
|
|||||||
if (param.empty() || param == "5" || group->isRaidGroup())
|
if (param.empty() || param == "5" || group->isRaidGroup())
|
||||||
return false; // Group or raid is full so stop trying.
|
return false; // Group or raid is full so stop trying.
|
||||||
else
|
else
|
||||||
group->ConvertToRaid(); // We want a raid but are in a group so convert and continue.
|
{
|
||||||
|
auto convertOp = std::make_unique<GroupConvertToRaidOperation>(requester->GetGUID());
|
||||||
|
sPlayerbotWorldProcessor->QueueOperation(std::move(convertOp));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Group::MemberSlotList const& groupSlot = group->GetMemberSlots();
|
Group::MemberSlotList const& groupSlot = group->GetMemberSlots();
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "Corpse.h"
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "FleeManager.h"
|
#include "FleeManager.h"
|
||||||
#include "G3D/Vector3.h"
|
#include "G3D/Vector3.h"
|
||||||
@@ -41,7 +42,6 @@
|
|||||||
#include "Unit.h"
|
#include "Unit.h"
|
||||||
#include "Vehicle.h"
|
#include "Vehicle.h"
|
||||||
#include "WaypointMovementGenerator.h"
|
#include "WaypointMovementGenerator.h"
|
||||||
#include "Corpse.h"
|
|
||||||
|
|
||||||
MovementAction::MovementAction(PlayerbotAI* botAI, std::string const name) : Action(botAI, name)
|
MovementAction::MovementAction(PlayerbotAI* botAI, std::string const name) : Action(botAI, name)
|
||||||
{
|
{
|
||||||
@@ -192,14 +192,15 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool generatePath = !bot->IsFlying() && !bot->isSwimming();
|
bool generatePath = !bot->IsFlying() && !bot->isSwimming();
|
||||||
bool disableMoveSplinePath = sPlayerbotAIConfig->disableMoveSplinePath >= 2 ||
|
bool disableMoveSplinePath =
|
||||||
(sPlayerbotAIConfig->disableMoveSplinePath == 1 && bot->InBattleground());
|
sPlayerbotAIConfig->disableMoveSplinePath >= 2 ||
|
||||||
|
(sPlayerbotAIConfig->disableMoveSplinePath == 1 && bot->InBattleground());
|
||||||
if (Vehicle* vehicle = bot->GetVehicle())
|
if (Vehicle* vehicle = bot->GetVehicle())
|
||||||
{
|
{
|
||||||
VehicleSeatEntry const* seat = vehicle->GetSeatForPassenger(bot);
|
VehicleSeatEntry const* seat = vehicle->GetSeatForPassenger(bot);
|
||||||
Unit* vehicleBase = vehicle->GetBase();
|
Unit* vehicleBase = vehicle->GetBase();
|
||||||
// If the mover (vehicle) can fly, we DO NOT want an mmaps path (2D ground) => disable pathfinding
|
|
||||||
generatePath = !vehicleBase || !vehicleBase->CanFly();
|
generatePath = !vehicleBase || !vehicleBase->CanFly();
|
||||||
if (!vehicleBase || !seat || !seat->CanControl()) // is passenger and cant move anyway
|
if (!vehicleBase || !seat || !seat->CanControl()) // is passenger and cant move anyway
|
||||||
return false;
|
return false;
|
||||||
@@ -207,22 +208,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
|
|||||||
float distance = vehicleBase->GetExactDist(x, y, z); // use vehicle distance, not bot
|
float distance = vehicleBase->GetExactDist(x, y, z); // use vehicle distance, not bot
|
||||||
if (distance > 0.01f)
|
if (distance > 0.01f)
|
||||||
{
|
{
|
||||||
MotionMaster& mm = *vehicleBase->GetMotionMaster(); // need to move vehicle, not bot
|
DoMovePoint(vehicleBase, x, y, z, generatePath, backwards);
|
||||||
// Disable ground pathing if the bot/master/vehicle are flying
|
|
||||||
auto isFlying = [](Unit* u){ return u && (u->HasUnitMovementFlag(MOVEMENTFLAG_FLYING) || u->IsInFlight()); };
|
|
||||||
bool allowPathVeh = generatePath;
|
|
||||||
Unit* masterVeh = botAI ? botAI->GetMaster() : nullptr;
|
|
||||||
if (isFlying(vehicleBase) || isFlying(bot) || isFlying(masterVeh))
|
|
||||||
allowPathVeh = false;
|
|
||||||
mm.Clear();
|
|
||||||
if (!backwards)
|
|
||||||
{
|
|
||||||
mm.MovePoint(0, x, y, z, FORCED_MOVEMENT_NONE, 0.0f, 0.0f, allowPathVeh);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mm.MovePointBackwards(0, x, y, z, allowPathVeh);
|
|
||||||
}
|
|
||||||
float speed = backwards ? vehicleBase->GetSpeed(MOVE_RUN_BACK) : vehicleBase->GetSpeed(MOVE_RUN);
|
float speed = backwards ? vehicleBase->GetSpeed(MOVE_RUN_BACK) : vehicleBase->GetSpeed(MOVE_RUN);
|
||||||
float delay = 1000.0f * (distance / speed);
|
float delay = 1000.0f * (distance / speed);
|
||||||
if (lessDelay)
|
if (lessDelay)
|
||||||
@@ -248,23 +234,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
|
|||||||
// bot->CastStop();
|
// bot->CastStop();
|
||||||
// botAI->InterruptSpell();
|
// botAI->InterruptSpell();
|
||||||
// }
|
// }
|
||||||
|
DoMovePoint(bot, x, y, z, generatePath, backwards);
|
||||||
MotionMaster& mm = *bot->GetMotionMaster();
|
|
||||||
// No ground pathfinding if the bot/master are flying => allow true 3D (Z) movement
|
|
||||||
auto isFlying = [](Unit* u){ return u && (u->HasUnitMovementFlag(MOVEMENTFLAG_FLYING) || u->IsInFlight()); };
|
|
||||||
bool allowPath = generatePath;
|
|
||||||
Unit* master = botAI ? botAI->GetMaster() : nullptr;
|
|
||||||
if (isFlying(bot) || isFlying(master))
|
|
||||||
allowPath = false;
|
|
||||||
mm.Clear();
|
|
||||||
if (!backwards)
|
|
||||||
{
|
|
||||||
mm.MovePoint(0, x, y, z, FORCED_MOVEMENT_NONE, 0.0f, 0.0f, allowPath);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mm.MovePointBackwards(0, x, y, z, allowPath);
|
|
||||||
}
|
|
||||||
float delay = 1000.0f * MoveDelay(distance, backwards);
|
float delay = 1000.0f * MoveDelay(distance, backwards);
|
||||||
if (lessDelay)
|
if (lessDelay)
|
||||||
{
|
{
|
||||||
@@ -282,9 +252,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
|
|||||||
Movement::PointsArray path =
|
Movement::PointsArray path =
|
||||||
SearchForBestPath(x, y, z, modifiedZ, sPlayerbotAIConfig->maxMovementSearchTime, normal_only);
|
SearchForBestPath(x, y, z, modifiedZ, sPlayerbotAIConfig->maxMovementSearchTime, normal_only);
|
||||||
if (modifiedZ == INVALID_HEIGHT)
|
if (modifiedZ == INVALID_HEIGHT)
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
float distance = bot->GetExactDist(x, y, modifiedZ);
|
float distance = bot->GetExactDist(x, y, modifiedZ);
|
||||||
if (distance > 0.01f)
|
if (distance > 0.01f)
|
||||||
{
|
{
|
||||||
@@ -296,24 +264,8 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
|
|||||||
// bot->CastStop();
|
// bot->CastStop();
|
||||||
// botAI->InterruptSpell();
|
// botAI->InterruptSpell();
|
||||||
// }
|
// }
|
||||||
|
|
||||||
MotionMaster& mm = *bot->GetMotionMaster();
|
|
||||||
G3D::Vector3 endP = path.back();
|
G3D::Vector3 endP = path.back();
|
||||||
// No ground pathfinding if the bot/master are flying => allow true 3D (Z) movement
|
DoMovePoint(bot, x, y, z, generatePath, backwards);
|
||||||
auto isFlying = [](Unit* u){ return u && (u->HasUnitMovementFlag(MOVEMENTFLAG_FLYING) || u->IsInFlight()); };
|
|
||||||
bool allowPath = generatePath;
|
|
||||||
Unit* master = botAI ? botAI->GetMaster() : nullptr;
|
|
||||||
if (isFlying(bot) || isFlying(master))
|
|
||||||
allowPath = false;
|
|
||||||
mm.Clear();
|
|
||||||
if (!backwards)
|
|
||||||
{
|
|
||||||
mm.MovePoint(0, x, y, z, FORCED_MOVEMENT_NONE, 0.0f, 0.0f, allowPath);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mm.MovePointBackwards(0, x, y, z, allowPath);
|
|
||||||
}
|
|
||||||
float delay = 1000.0f * MoveDelay(distance, backwards);
|
float delay = 1000.0f * MoveDelay(distance, backwards);
|
||||||
if (lessDelay)
|
if (lessDelay)
|
||||||
{
|
{
|
||||||
@@ -581,9 +533,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
|
|||||||
// bool goTaxi = bot->ActivateTaxiPathTo({ tEntry->from, tEntry->to }, unit, 1);
|
// bool goTaxi = bot->ActivateTaxiPathTo({ tEntry->from, tEntry->to }, unit, 1);
|
||||||
|
|
||||||
// if (botAI->HasCheat(BotCheatMask::gold))
|
// if (botAI->HasCheat(BotCheatMask::gold))
|
||||||
// {
|
|
||||||
// bot->SetMoney(botMoney);
|
// bot->SetMoney(botMoney);
|
||||||
// }
|
|
||||||
// LOG_DEBUG("playerbots", "goTaxi");
|
// LOG_DEBUG("playerbots", "goTaxi");
|
||||||
// return goTaxi;
|
// return goTaxi;
|
||||||
// }
|
// }
|
||||||
@@ -874,7 +824,7 @@ bool MovementAction::ReachCombatTo(Unit* target, float distance)
|
|||||||
|
|
||||||
float deltaAngle = Position::NormalizeOrientation(targetOrientation - target->GetAngle(bot));
|
float deltaAngle = Position::NormalizeOrientation(targetOrientation - target->GetAngle(bot));
|
||||||
if (deltaAngle > M_PI)
|
if (deltaAngle > M_PI)
|
||||||
deltaAngle -= 2.0f * M_PI; // -PI..PI
|
deltaAngle -= 2.0f * M_PI; // -PI..PI
|
||||||
// if target is moving forward and moving far away, predict the position
|
// if target is moving forward and moving far away, predict the position
|
||||||
bool behind = fabs(deltaAngle) > M_PI_2;
|
bool behind = fabs(deltaAngle) > M_PI_2;
|
||||||
if (target->HasUnitMovementFlag(MOVEMENTFLAG_FORWARD) && behind)
|
if (target->HasUnitMovementFlag(MOVEMENTFLAG_FORWARD) && behind)
|
||||||
@@ -882,8 +832,8 @@ bool MovementAction::ReachCombatTo(Unit* target, float distance)
|
|||||||
float predictDis = std::min(3.0f, target->GetObjectSize() * 2);
|
float predictDis = std::min(3.0f, target->GetObjectSize() * 2);
|
||||||
tx += cos(target->GetOrientation()) * predictDis;
|
tx += cos(target->GetOrientation()) * predictDis;
|
||||||
ty += sin(target->GetOrientation()) * predictDis;
|
ty += sin(target->GetOrientation()) * predictDis;
|
||||||
if (!target->GetMap()->CheckCollisionAndGetValidCoords(target, target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(),
|
if (!target->GetMap()->CheckCollisionAndGetValidCoords(target, target->GetPositionX(), target->GetPositionY(),
|
||||||
tx, ty, tz))
|
target->GetPositionZ(), tx, ty, tz))
|
||||||
{
|
{
|
||||||
tx = target->GetPositionX();
|
tx = target->GetPositionX();
|
||||||
ty = target->GetPositionY();
|
ty = target->GetPositionY();
|
||||||
@@ -1001,9 +951,8 @@ bool MovementAction::IsMovingAllowed()
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) ||
|
if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) ||
|
||||||
bot->IsBeingTeleported() || bot->HasRootAura() || bot->HasSpiritOfRedemptionAura() ||
|
bot->IsBeingTeleported() || bot->HasRootAura() || bot->HasSpiritOfRedemptionAura() || bot->HasConfuseAura() ||
|
||||||
bot->HasConfuseAura() || bot->IsCharmed() || bot->HasStunAura() ||
|
bot->IsCharmed() || bot->HasStunAura() || bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
|
||||||
bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (bot->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE)
|
if (bot->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE)
|
||||||
@@ -1022,78 +971,86 @@ bool MovementAction::Follow(Unit* target, float distance) { return Follow(target
|
|||||||
|
|
||||||
void MovementAction::UpdateMovementState()
|
void MovementAction::UpdateMovementState()
|
||||||
{
|
{
|
||||||
int8 botInLiquidState = bot->GetLiquidData().Status;
|
const bool isCurrentlyRestricted = // see if the bot is currently slowed, rooted, or otherwise unable to move
|
||||||
|
bot->isFrozen() ||
|
||||||
|
bot->IsPolymorphed() ||
|
||||||
|
bot->HasRootAura() ||
|
||||||
|
bot->HasStunAura() ||
|
||||||
|
bot->HasConfuseAura() ||
|
||||||
|
bot->HasUnitState(UNIT_STATE_LOST_CONTROL);
|
||||||
|
|
||||||
if (botInLiquidState == LIQUID_MAP_IN_WATER || botInLiquidState == LIQUID_MAP_UNDER_WATER)
|
// no update movement flags while movement is current restricted.
|
||||||
|
if (!isCurrentlyRestricted && bot->IsAlive())
|
||||||
{
|
{
|
||||||
bot->SetSwim(true);
|
// state flags
|
||||||
}
|
const auto master = botAI ? botAI->GetMaster() : nullptr; // real player or not
|
||||||
else
|
const bool masterIsFlying = master ? master->HasUnitMovementFlag(MOVEMENTFLAG_FLYING) : true;
|
||||||
{
|
const bool masterIsSwimming = master ? master->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING) : true;
|
||||||
bot->SetSwim(false);
|
const auto liquidState = bot->GetLiquidData().Status; // default LIQUID_MAP_NO_WATER
|
||||||
}
|
const float gZ = bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
|
||||||
|
const bool wantsToFly = bot->HasIncreaseMountedFlightSpeedAura() || bot->HasFlyAura();
|
||||||
|
const bool isFlying = bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING);
|
||||||
|
const bool isWaterArea = liquidState != LIQUID_MAP_NO_WATER;
|
||||||
|
const bool isUnderWater = liquidState == LIQUID_MAP_UNDER_WATER;
|
||||||
|
const bool isInWater = liquidState == LIQUID_MAP_IN_WATER;
|
||||||
|
const bool isWaterWalking = bot->HasUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
|
||||||
|
const bool isSwimming = bot->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
|
||||||
|
const bool wantsToWaterWalk = bot->HasWaterWalkAura();
|
||||||
|
const bool wantsToSwim = isInWater || isUnderWater;
|
||||||
|
const bool onGroundZ = (bot->GetPositionZ() < gZ + 1.f) && !isWaterArea;
|
||||||
|
bool movementFlagsUpdated = false;
|
||||||
|
|
||||||
bool onGround = bot->GetPositionZ() <
|
// handle water state
|
||||||
bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ()) + 1.0f;
|
if (isWaterArea && !isFlying)
|
||||||
|
{
|
||||||
|
// water walking
|
||||||
|
if (wantsToWaterWalk && !isWaterWalking && !masterIsSwimming)
|
||||||
|
{
|
||||||
|
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
|
||||||
|
bot->AddUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
|
||||||
|
movementFlagsUpdated = true;
|
||||||
|
}
|
||||||
|
// swimming
|
||||||
|
else if (wantsToSwim && !isSwimming && masterIsSwimming)
|
||||||
|
{
|
||||||
|
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
|
||||||
|
bot->AddUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
|
||||||
|
movementFlagsUpdated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (isSwimming || isWaterWalking)
|
||||||
|
{
|
||||||
|
// reset water flags
|
||||||
|
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
|
||||||
|
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
|
||||||
|
movementFlagsUpdated = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Keep bot->SendMovementFlagUpdate() withing the if statements to not intefere with bot behavior on ground/(shallow) waters
|
// handle flying state
|
||||||
|
if (wantsToFly && !isFlying && masterIsFlying)
|
||||||
bool hasFlightAura = bot->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) || bot->HasAuraType(SPELL_AURA_FLY);
|
|
||||||
if (hasFlightAura)
|
|
||||||
{
|
|
||||||
bool changed = false;
|
|
||||||
if (!bot->HasUnitMovementFlag(MOVEMENTFLAG_CAN_FLY))
|
|
||||||
{
|
{
|
||||||
bot->AddUnitMovementFlag(MOVEMENTFLAG_CAN_FLY);
|
bot->AddUnitMovementFlag(MOVEMENTFLAG_CAN_FLY);
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
if (!bot->HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY))
|
|
||||||
{
|
|
||||||
bot->AddUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
|
bot->AddUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
if (!bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING))
|
|
||||||
{
|
|
||||||
bot->AddUnitMovementFlag(MOVEMENTFLAG_FLYING);
|
bot->AddUnitMovementFlag(MOVEMENTFLAG_FLYING);
|
||||||
changed = true;
|
movementFlagsUpdated = true;
|
||||||
}
|
}
|
||||||
if (changed)
|
else if ((!wantsToFly || onGroundZ) && isFlying)
|
||||||
bot->SendMovementFlagUpdate();
|
|
||||||
}
|
|
||||||
else if (!hasFlightAura)
|
|
||||||
{
|
|
||||||
bool changed = false;
|
|
||||||
if (bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING))
|
|
||||||
{
|
|
||||||
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_FLYING);
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
if (bot->HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY))
|
|
||||||
{
|
|
||||||
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
if (bot->HasUnitMovementFlag(MOVEMENTFLAG_CAN_FLY))
|
|
||||||
{
|
{
|
||||||
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_CAN_FLY);
|
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_CAN_FLY);
|
||||||
changed = true;
|
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
|
||||||
|
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_FLYING);
|
||||||
|
movementFlagsUpdated = true;
|
||||||
}
|
}
|
||||||
if (changed)
|
|
||||||
|
// detect if movement restrictions have been lifted, CC just ended.
|
||||||
|
if (wasMovementRestricted)
|
||||||
|
movementFlagsUpdated = true; // refresh movement state to ensure animations play correctly
|
||||||
|
|
||||||
|
if (movementFlagsUpdated)
|
||||||
bot->SendMovementFlagUpdate();
|
bot->SendMovementFlagUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
// See if the bot is currently slowed, rooted, or otherwise unable to move
|
// Save current state for the next check
|
||||||
bool isCurrentlyRestricted = bot->isFrozen() || bot->IsPolymorphed() || bot->HasRootAura() || bot->HasStunAura() ||
|
|
||||||
bot->HasConfuseAura() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL);
|
|
||||||
|
|
||||||
// Detect if movement restrictions have been lifted
|
|
||||||
if (wasMovementRestricted && !isCurrentlyRestricted && bot->IsAlive())
|
|
||||||
{
|
|
||||||
// CC just ended - refresh movement state to ensure animations play correctly
|
|
||||||
bot->SendMovementFlagUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save current state for the next check
|
|
||||||
wasMovementRestricted = isCurrentlyRestricted;
|
wasMovementRestricted = isCurrentlyRestricted;
|
||||||
|
|
||||||
// Temporary speed increase in group
|
// Temporary speed increase in group
|
||||||
@@ -1180,6 +1137,13 @@ bool MovementAction::Follow(Unit* target, float distance, float angle)
|
|||||||
if (!target)
|
if (!target)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (!bot->InBattleground() && sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target),
|
||||||
|
sPlayerbotAIConfig->followDistance))
|
||||||
|
{
|
||||||
|
// botAI->TellError("No need to follow");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
if (!bot->InBattleground()
|
if (!bot->InBattleground()
|
||||||
&& sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target->GetPositionX(),
|
&& sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target->GetPositionX(),
|
||||||
@@ -1297,17 +1261,21 @@ bool MovementAction::Follow(Unit* target, float distance, float angle)
|
|||||||
return MoveTo(target, sPlayerbotAIConfig->followDistance);
|
return MoveTo(target, sPlayerbotAIConfig->followDistance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target),
|
||||||
|
sPlayerbotAIConfig->followDistance))
|
||||||
|
{
|
||||||
|
// botAI->TellError("No need to follow");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (target->IsFriendlyTo(bot) && bot->IsMounted() && AI_VALUE(GuidVector, "all targets").empty())
|
if (target->IsFriendlyTo(bot) && bot->IsMounted() && AI_VALUE(GuidVector, "all targets").empty())
|
||||||
distance += angle;
|
distance += angle;
|
||||||
|
|
||||||
// Do not cancel follow if the 2D distance is short but the Z still differs (e.g., master above).
|
if (!bot->InBattleground() && sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target),
|
||||||
float dz1 = fabs(bot->GetPositionZ() - target->GetPositionZ());
|
sPlayerbotAIConfig->followDistance))
|
||||||
if (!bot->InBattleground()
|
|
||||||
&& sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target), sPlayerbotAIConfig->followDistance)
|
|
||||||
&& dz1 < sPlayerbotAIConfig->contactDistance)
|
|
||||||
{
|
{
|
||||||
// botAI->TellError("No need to follow");
|
// botAI->TellError("No need to follow");
|
||||||
return false; // truly in range (2D and Z) => no need to move
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bot->HandleEmoteCommand(0);
|
bot->HandleEmoteCommand(0);
|
||||||
@@ -1388,7 +1356,7 @@ float MovementAction::MoveDelay(float distance, bool backwards)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
speed = backwards ? bot->GetSpeed(MOVE_RUN_BACK) :bot->GetSpeed(MOVE_RUN);
|
speed = backwards ? bot->GetSpeed(MOVE_RUN_BACK) : bot->GetSpeed(MOVE_RUN);
|
||||||
}
|
}
|
||||||
float delay = distance / speed;
|
float delay = distance / speed;
|
||||||
return delay;
|
return delay;
|
||||||
@@ -1418,8 +1386,7 @@ void MovementAction::SetNextMovementDelay(float delayMillis)
|
|||||||
{
|
{
|
||||||
AI_VALUE(LastMovement&, "last movement")
|
AI_VALUE(LastMovement&, "last movement")
|
||||||
.Set(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetOrientation(),
|
.Set(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetOrientation(),
|
||||||
delayMillis,
|
delayMillis, MovementPriority::MOVEMENT_FORCED);
|
||||||
MovementPriority::MOVEMENT_FORCED);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MovementAction::Flee(Unit* target)
|
bool MovementAction::Flee(Unit* target)
|
||||||
@@ -1633,7 +1600,8 @@ bool MovementAction::MoveAway(Unit* target, float distance, bool backwards)
|
|||||||
dz = bot->GetPositionZ();
|
dz = bot->GetPositionZ();
|
||||||
exact = false;
|
exact = false;
|
||||||
}
|
}
|
||||||
if (MoveTo(target->GetMapId(), dx, dy, dz, false, false, true, exact, MovementPriority::MOVEMENT_COMBAT, false, backwards))
|
if (MoveTo(target->GetMapId(), dx, dy, dz, false, false, true, exact, MovementPriority::MOVEMENT_COMBAT, false,
|
||||||
|
backwards))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1655,7 +1623,8 @@ bool MovementAction::MoveAway(Unit* target, float distance, bool backwards)
|
|||||||
dz = bot->GetPositionZ();
|
dz = bot->GetPositionZ();
|
||||||
exact = false;
|
exact = false;
|
||||||
}
|
}
|
||||||
if (MoveTo(target->GetMapId(), dx, dy, dz, false, false, true, exact, MovementPriority::MOVEMENT_COMBAT, false, backwards))
|
if (MoveTo(target->GetMapId(), dx, dy, dz, false, false, true, exact, MovementPriority::MOVEMENT_COMBAT, false,
|
||||||
|
backwards))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1704,7 +1673,7 @@ bool MovementAction::Move(float angle, float distance)
|
|||||||
float x = bot->GetPositionX() + cos(angle) * distance;
|
float x = bot->GetPositionX() + cos(angle) * distance;
|
||||||
float y = bot->GetPositionY() + sin(angle) * distance;
|
float y = bot->GetPositionY() + sin(angle) * distance;
|
||||||
|
|
||||||
//TODO do we need GetMapWaterOrGroundLevel() if we're using CheckCollisionAndGetValidCoords() ?
|
// TODO do we need GetMapWaterOrGroundLevel() if we're using CheckCollisionAndGetValidCoords() ?
|
||||||
float z = bot->GetMapWaterOrGroundLevel(x, y, bot->GetPositionZ());
|
float z = bot->GetMapWaterOrGroundLevel(x, y, bot->GetPositionZ());
|
||||||
if (z == -100000.0f || z == -200000.0f)
|
if (z == -100000.0f || z == -200000.0f)
|
||||||
z = bot->GetPositionZ();
|
z = bot->GetPositionZ();
|
||||||
@@ -1758,9 +1727,7 @@ bool MovementAction::MoveInside(uint32 mapId, float x, float y, float z, float d
|
|||||||
// min_length = gen.getPathLength();
|
// min_length = gen.getPathLength();
|
||||||
// current_z = modified_z;
|
// current_z = modified_z;
|
||||||
// if (abs(current_z - z) < 0.5f)
|
// if (abs(current_z - z) < 0.5f)
|
||||||
// {
|
|
||||||
// return current_z;
|
// return current_z;
|
||||||
// }
|
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// for (delta = range / 2 + step; delta <= range; delta += 2) {
|
// for (delta = range / 2 + step; delta <= range; delta += 2) {
|
||||||
@@ -1857,6 +1824,46 @@ const Movement::PointsArray MovementAction::SearchForBestPath(float x, float y,
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MovementAction::DoMovePoint(Unit* unit, float x, float y, float z, bool generatePath, bool backwards)
|
||||||
|
{
|
||||||
|
if (!unit)
|
||||||
|
return;
|
||||||
|
|
||||||
|
MotionMaster* mm = unit->GetMotionMaster();
|
||||||
|
if (!mm)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// enable water walking
|
||||||
|
if (unit->HasUnitMovementFlag(MOVEMENTFLAG_WATERWALKING))
|
||||||
|
{
|
||||||
|
float gZ = unit->GetMapWaterOrGroundLevel(unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ());
|
||||||
|
unit->UpdatePosition(unit->GetPositionX(), unit->GetPositionY(), gZ, false);
|
||||||
|
// z = gZ; no overwrite Z axe otherwise you cant steer the bots into swimming when water walking.
|
||||||
|
}
|
||||||
|
|
||||||
|
mm->Clear();
|
||||||
|
if (backwards)
|
||||||
|
{
|
||||||
|
mm->MovePointBackwards(
|
||||||
|
/*id*/ 0,
|
||||||
|
/*coords*/ x, y, z,
|
||||||
|
/*generatePath*/ generatePath,
|
||||||
|
/*forceDestination*/ false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mm->MovePoint(
|
||||||
|
/*id*/ 0,
|
||||||
|
/*coords*/ x, y, z,
|
||||||
|
/*forcedMovement*/ FORCED_MOVEMENT_NONE,
|
||||||
|
/*speed*/ 0.f,
|
||||||
|
/*orientation*/ 0.f,
|
||||||
|
/*generatePath*/ generatePath, // true => terrain path (2d mmap); false => straight spline (3d vmap)
|
||||||
|
/*forceDestination*/ false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool FleeAction::Execute(Event event)
|
bool FleeAction::Execute(Event event)
|
||||||
{
|
{
|
||||||
return MoveAway(AI_VALUE(Unit*, "current target"), sPlayerbotAIConfig->fleeDistance, true);
|
return MoveAway(AI_VALUE(Unit*, "current target"), sPlayerbotAIConfig->fleeDistance, true);
|
||||||
@@ -1937,7 +1944,8 @@ bool AvoidAoeAction::AvoidAuraWithDynamicObj()
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(spellInfo->Id) != sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end())
|
if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(spellInfo->Id) !=
|
||||||
|
sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
DynamicObject* dynOwner = aura->GetDynobjOwner();
|
DynamicObject* dynOwner = aura->GetDynobjOwner();
|
||||||
@@ -2002,7 +2010,8 @@ bool AvoidAoeAction::AvoidGameObjectWithDamage()
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(spellId) != sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end())
|
if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(spellId) !=
|
||||||
|
sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||||
@@ -2028,7 +2037,8 @@ bool AvoidAoeAction::AvoidGameObjectWithDamage()
|
|||||||
lastTellTimer = time(NULL);
|
lastTellTimer = time(NULL);
|
||||||
lastMoveTimer = getMSTime();
|
lastMoveTimer = getMSTime();
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
out << "I'm avoiding " << name.str() << " (" << spellInfo->Id << ")" << " Radius " << radius << " - [Trap]";
|
out << "I'm avoiding " << name.str() << " (" << spellInfo->Id << ")" << " Radius " << radius
|
||||||
|
<< " - [Trap]";
|
||||||
bot->Say(out.str(), LANG_UNIVERSAL);
|
bot->Say(out.str(), LANG_UNIVERSAL);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -2071,7 +2081,8 @@ bool AvoidAoeAction::AvoidUnitWithDamageAura()
|
|||||||
sSpellMgr->GetSpellInfo(spellInfo->Effects[aurEff->GetEffIndex()].TriggerSpell);
|
sSpellMgr->GetSpellInfo(spellInfo->Effects[aurEff->GetEffIndex()].TriggerSpell);
|
||||||
if (!triggerSpellInfo)
|
if (!triggerSpellInfo)
|
||||||
continue;
|
continue;
|
||||||
if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(triggerSpellInfo->Id) != sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end())
|
if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(triggerSpellInfo->Id) !=
|
||||||
|
sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end())
|
||||||
return false;
|
return false;
|
||||||
for (int j = 0; j < MAX_SPELL_EFFECTS; j++)
|
for (int j = 0; j < MAX_SPELL_EFFECTS; j++)
|
||||||
{
|
{
|
||||||
@@ -2093,7 +2104,8 @@ bool AvoidAoeAction::AvoidUnitWithDamageAura()
|
|||||||
lastTellTimer = time(NULL);
|
lastTellTimer = time(NULL);
|
||||||
lastMoveTimer = getMSTime();
|
lastMoveTimer = getMSTime();
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
out << "I'm avoiding " << name.str() << " (" << triggerSpellInfo->Id << ")" << " Radius " << radius << " - [Unit Trigger]";
|
out << "I'm avoiding " << name.str() << " (" << triggerSpellInfo->Id << ")"
|
||||||
|
<< " Radius " << radius << " - [Unit Trigger]";
|
||||||
bot->Say(out.str(), LANG_UNIVERSAL);
|
bot->Say(out.str(), LANG_UNIVERSAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2112,7 +2124,8 @@ Position MovementAction::BestPositionForMeleeToFlee(Position pos, float radius)
|
|||||||
if (currentTarget)
|
if (currentTarget)
|
||||||
{
|
{
|
||||||
// Normally, move to left or right is the best position
|
// Normally, move to left or right is the best position
|
||||||
bool isTanking = (!currentTarget->isFrozen() && !currentTarget->HasRootAura()) && (currentTarget->GetVictim() == bot);
|
bool isTanking = (!currentTarget->isFrozen()
|
||||||
|
&& !currentTarget->HasRootAura()) && (currentTarget->GetVictim() == bot);
|
||||||
float angle = bot->GetAngle(currentTarget);
|
float angle = bot->GetAngle(currentTarget);
|
||||||
float angleLeft = angle + (float)M_PI / 2;
|
float angleLeft = angle + (float)M_PI / 2;
|
||||||
float angleRight = angle - (float)M_PI / 2;
|
float angleRight = angle - (float)M_PI / 2;
|
||||||
@@ -2327,8 +2340,7 @@ bool CombatFormationMoveAction::isUseful()
|
|||||||
bool CombatFormationMoveAction::Execute(Event event)
|
bool CombatFormationMoveAction::Execute(Event event)
|
||||||
{
|
{
|
||||||
float dis = AI_VALUE(float, "disperse distance");
|
float dis = AI_VALUE(float, "disperse distance");
|
||||||
if (dis <= 0.0f ||
|
if (dis <= 0.0f || (!bot->IsInCombat() && botAI->HasStrategy("stay", BotState::BOT_STATE_NON_COMBAT)) ||
|
||||||
(!bot->IsInCombat() && botAI->HasStrategy("stay", BotState::BOT_STATE_NON_COMBAT)) ||
|
|
||||||
(bot->IsInCombat() && botAI->HasStrategy("stay", BotState::BOT_STATE_COMBAT)))
|
(bot->IsInCombat() && botAI->HasStrategy("stay", BotState::BOT_STATE_COMBAT)))
|
||||||
return false;
|
return false;
|
||||||
Player* playerToLeave = NearestGroupMember(dis);
|
Player* playerToLeave = NearestGroupMember(dis);
|
||||||
@@ -2478,7 +2490,7 @@ bool TankFaceAction::Execute(Event event)
|
|||||||
|
|
||||||
float deltaAngle = Position::NormalizeOrientation(averageAngle - target->GetAngle(bot));
|
float deltaAngle = Position::NormalizeOrientation(averageAngle - target->GetAngle(bot));
|
||||||
if (deltaAngle > M_PI)
|
if (deltaAngle > M_PI)
|
||||||
deltaAngle -= 2.0f * M_PI; // -PI..PI
|
deltaAngle -= 2.0f * M_PI; // -PI..PI
|
||||||
|
|
||||||
float tolerable = M_PI_2;
|
float tolerable = M_PI_2;
|
||||||
|
|
||||||
@@ -2489,12 +2501,13 @@ bool TankFaceAction::Execute(Event event)
|
|||||||
float goodAngle2 = Position::NormalizeOrientation(averageAngle - M_PI * 3 / 5);
|
float goodAngle2 = Position::NormalizeOrientation(averageAngle - M_PI * 3 / 5);
|
||||||
|
|
||||||
// if dist < bot->GetMeleeRange(target) / 2, target will move backward
|
// if dist < bot->GetMeleeRange(target) / 2, target will move backward
|
||||||
float dist = std::max(bot->GetExactDist(target), bot->GetMeleeRange(target) / 2) - bot->GetCombatReach() - target->GetCombatReach();
|
float dist = std::max(bot->GetExactDist(target), bot->GetMeleeRange(target) / 2) - bot->GetCombatReach() -
|
||||||
|
target->GetCombatReach();
|
||||||
std::vector<Position> availablePos;
|
std::vector<Position> availablePos;
|
||||||
float x, y, z;
|
float x, y, z;
|
||||||
target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle1);
|
target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle1);
|
||||||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||||||
x, y, z))
|
bot->GetPositionZ(), x, y, z))
|
||||||
{
|
{
|
||||||
/// @todo: movement control now is a mess, prepare to rewrite
|
/// @todo: movement control now is a mess, prepare to rewrite
|
||||||
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
|
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
|
||||||
@@ -2506,8 +2519,8 @@ bool TankFaceAction::Execute(Event event)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle2);
|
target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle2);
|
||||||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||||||
x, y, z))
|
bot->GetPositionZ(), x, y, z))
|
||||||
{
|
{
|
||||||
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
|
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
|
||||||
Position pos(x, y, z);
|
Position pos(x, y, z);
|
||||||
@@ -2520,13 +2533,15 @@ bool TankFaceAction::Execute(Event event)
|
|||||||
if (availablePos.empty())
|
if (availablePos.empty())
|
||||||
return false;
|
return false;
|
||||||
Position nearest = GetNearestPosition(availablePos);
|
Position nearest = GetNearestPosition(availablePos);
|
||||||
return MoveTo(bot->GetMapId(), nearest.GetPositionX(), nearest.GetPositionY(), nearest.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT);
|
return MoveTo(bot->GetMapId(), nearest.GetPositionX(), nearest.GetPositionY(), nearest.GetPositionZ(), false, false,
|
||||||
|
false, true, MovementPriority::MOVEMENT_COMBAT);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RearFlankAction::isUseful()
|
bool RearFlankAction::isUseful()
|
||||||
{
|
{
|
||||||
Unit* target = AI_VALUE(Unit*, "current target");
|
Unit* target = AI_VALUE(Unit*, "current target");
|
||||||
if (!target) { return false; }
|
if (!target)
|
||||||
|
return false;
|
||||||
|
|
||||||
// Need to double the front angle check to account for mirrored angle.
|
// Need to double the front angle check to account for mirrored angle.
|
||||||
bool inFront = target->HasInArc(2.f * minAngle, bot);
|
bool inFront = target->HasInArc(2.f * minAngle, bot);
|
||||||
@@ -2540,7 +2555,8 @@ bool RearFlankAction::isUseful()
|
|||||||
bool RearFlankAction::Execute(Event event)
|
bool RearFlankAction::Execute(Event event)
|
||||||
{
|
{
|
||||||
Unit* target = AI_VALUE(Unit*, "current target");
|
Unit* target = AI_VALUE(Unit*, "current target");
|
||||||
if (!target) { return false; }
|
if (!target)
|
||||||
|
return false;
|
||||||
|
|
||||||
float angle = frand(minAngle, maxAngle);
|
float angle = frand(minAngle, maxAngle);
|
||||||
float baseDistance = bot->GetMeleeRange(target) * 0.5f;
|
float baseDistance = bot->GetMeleeRange(target) * 0.5f;
|
||||||
@@ -2559,8 +2575,8 @@ bool RearFlankAction::Execute(Event event)
|
|||||||
destination = &rightFlank;
|
destination = &rightFlank;
|
||||||
}
|
}
|
||||||
|
|
||||||
return MoveTo(bot->GetMapId(), destination->GetPositionX(), destination->GetPositionY(), destination->GetPositionZ(),
|
return MoveTo(bot->GetMapId(), destination->GetPositionX(), destination->GetPositionY(),
|
||||||
false, false, false, true, MovementPriority::MOVEMENT_COMBAT);
|
destination->GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DisperseSetAction::Execute(Event event)
|
bool DisperseSetAction::Execute(Event event)
|
||||||
@@ -2688,9 +2704,8 @@ bool SetFacingTargetAction::isUseful() { return !AI_VALUE2(bool, "facing", "curr
|
|||||||
bool SetFacingTargetAction::isPossible()
|
bool SetFacingTargetAction::isPossible()
|
||||||
{
|
{
|
||||||
if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) ||
|
if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) ||
|
||||||
bot->IsBeingTeleported() || bot->HasConfuseAura() || bot->IsCharmed() ||
|
bot->IsBeingTeleported() || bot->HasConfuseAura() || bot->IsCharmed() || bot->HasStunAura() ||
|
||||||
bot->HasStunAura() || bot->IsInFlight() ||
|
bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
|
||||||
bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -2710,7 +2725,7 @@ bool SetBehindTargetAction::Execute(Event event)
|
|||||||
|
|
||||||
float deltaAngle = Position::NormalizeOrientation(target->GetOrientation() - target->GetAngle(bot));
|
float deltaAngle = Position::NormalizeOrientation(target->GetOrientation() - target->GetAngle(bot));
|
||||||
if (deltaAngle > M_PI)
|
if (deltaAngle > M_PI)
|
||||||
deltaAngle -= 2.0f * M_PI; // -PI..PI
|
deltaAngle -= 2.0f * M_PI; // -PI..PI
|
||||||
|
|
||||||
float tolerable = M_PI_2;
|
float tolerable = M_PI_2;
|
||||||
|
|
||||||
@@ -2720,12 +2735,13 @@ bool SetBehindTargetAction::Execute(Event event)
|
|||||||
float goodAngle1 = Position::NormalizeOrientation(target->GetOrientation() + M_PI * 3 / 5);
|
float goodAngle1 = Position::NormalizeOrientation(target->GetOrientation() + M_PI * 3 / 5);
|
||||||
float goodAngle2 = Position::NormalizeOrientation(target->GetOrientation() - M_PI * 3 / 5);
|
float goodAngle2 = Position::NormalizeOrientation(target->GetOrientation() - M_PI * 3 / 5);
|
||||||
|
|
||||||
float dist = std::max(bot->GetExactDist(target), bot->GetMeleeRange(target) / 2) - bot->GetCombatReach() - target->GetCombatReach();
|
float dist = std::max(bot->GetExactDist(target), bot->GetMeleeRange(target) / 2) - bot->GetCombatReach() -
|
||||||
|
target->GetCombatReach();
|
||||||
std::vector<Position> availablePos;
|
std::vector<Position> availablePos;
|
||||||
float x, y, z;
|
float x, y, z;
|
||||||
target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle1);
|
target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle1);
|
||||||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||||||
x, y, z))
|
bot->GetPositionZ(), x, y, z))
|
||||||
{
|
{
|
||||||
/// @todo: movement control now is a mess, prepare to rewrite
|
/// @todo: movement control now is a mess, prepare to rewrite
|
||||||
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
|
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
|
||||||
@@ -2737,8 +2753,8 @@ bool SetBehindTargetAction::Execute(Event event)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle2);
|
target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle2);
|
||||||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||||||
x, y, z))
|
bot->GetPositionZ(), x, y, z))
|
||||||
{
|
{
|
||||||
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
|
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
|
||||||
Position pos(x, y, z);
|
Position pos(x, y, z);
|
||||||
@@ -2751,7 +2767,8 @@ bool SetBehindTargetAction::Execute(Event event)
|
|||||||
if (availablePos.empty())
|
if (availablePos.empty())
|
||||||
return false;
|
return false;
|
||||||
Position nearest = GetNearestPosition(availablePos);
|
Position nearest = GetNearestPosition(availablePos);
|
||||||
return MoveTo(bot->GetMapId(), nearest.GetPositionX(), nearest.GetPositionY(), nearest.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT);
|
return MoveTo(bot->GetMapId(), nearest.GetPositionX(), nearest.GetPositionY(), nearest.GetPositionZ(), false, false,
|
||||||
|
false, true, MovementPriority::MOVEMENT_COMBAT);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MoveOutOfCollisionAction::Execute(Event event)
|
bool MoveOutOfCollisionAction::Execute(Event event)
|
||||||
@@ -2822,7 +2839,7 @@ bool MoveFromGroupAction::Execute(Event event)
|
|||||||
{
|
{
|
||||||
float distance = atoi(event.getParam().c_str());
|
float distance = atoi(event.getParam().c_str());
|
||||||
if (!distance)
|
if (!distance)
|
||||||
distance = 20.0f; // flee distance from config is too small for this
|
distance = 20.0f; // flee distance from config is too small for this
|
||||||
return MoveFromGroup(distance);
|
return MoveFromGroup(distance);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2905,10 +2922,7 @@ bool MoveAwayFromCreatureAction::Execute(Event event)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MoveAwayFromCreatureAction::isPossible()
|
bool MoveAwayFromCreatureAction::isPossible() { return bot->CanFreeMove(); }
|
||||||
{
|
|
||||||
return bot->CanFreeMove();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MoveAwayFromPlayerWithDebuffAction::Execute(Event event)
|
bool MoveAwayFromPlayerWithDebuffAction::Execute(Event event)
|
||||||
{
|
{
|
||||||
@@ -2995,7 +3009,4 @@ bool MoveAwayFromPlayerWithDebuffAction::Execute(Event event)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MoveAwayFromPlayerWithDebuffAction::isPossible()
|
bool MoveAwayFromPlayerWithDebuffAction::isPossible() { return bot->CanFreeMove(); }
|
||||||
{
|
|
||||||
return bot->CanFreeMove();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -29,12 +29,17 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool JumpTo(uint32 mapId, float x, float y, float z, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
bool JumpTo(uint32 mapId, float x, float y, float z, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
||||||
bool MoveNear(uint32 mapId, float x, float y, float z, float distance = sPlayerbotAIConfig->contactDistance, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
bool MoveNear(uint32 mapId, float x, float y, float z, float distance = sPlayerbotAIConfig->contactDistance,
|
||||||
|
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
||||||
bool MoveToLOS(WorldObject* target, bool ranged = false);
|
bool MoveToLOS(WorldObject* target, bool ranged = false);
|
||||||
bool MoveTo(uint32 mapId, float x, float y, float z, bool idle = false, bool react = false,
|
bool MoveTo(uint32 mapId, float x, float y, float z, bool idle = false, bool react = false,
|
||||||
bool normal_only = false, bool exact_waypoint = false, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL, bool lessDelay = false, bool backwards = false);
|
bool normal_only = false, bool exact_waypoint = false,
|
||||||
bool MoveTo(WorldObject* target, float distance = 0.0f, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL, bool lessDelay = false,
|
||||||
bool MoveNear(WorldObject* target, float distance = sPlayerbotAIConfig->contactDistance, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
bool backwards = false);
|
||||||
|
bool MoveTo(WorldObject* target, float distance = 0.0f,
|
||||||
|
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
||||||
|
bool MoveNear(WorldObject* target, float distance = sPlayerbotAIConfig->contactDistance,
|
||||||
|
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
||||||
float GetFollowAngle();
|
float GetFollowAngle();
|
||||||
bool Follow(Unit* target, float distance = sPlayerbotAIConfig->followDistance);
|
bool Follow(Unit* target, float distance = sPlayerbotAIConfig->followDistance);
|
||||||
bool Follow(Unit* target, float distance, float angle);
|
bool Follow(Unit* target, float distance, float angle);
|
||||||
@@ -51,10 +56,11 @@ protected:
|
|||||||
bool Flee(Unit* target);
|
bool Flee(Unit* target);
|
||||||
void ClearIdleState();
|
void ClearIdleState();
|
||||||
void UpdateMovementState();
|
void UpdateMovementState();
|
||||||
bool MoveAway(Unit* target, float distance = sPlayerbotAIConfig -> fleeDistance, bool backwards = false);
|
bool MoveAway(Unit* target, float distance = sPlayerbotAIConfig->fleeDistance, bool backwards = false);
|
||||||
bool MoveFromGroup(float distance);
|
bool MoveFromGroup(float distance);
|
||||||
bool Move(float angle, float distance);
|
bool Move(float angle, float distance);
|
||||||
bool MoveInside(uint32 mapId, float x, float y, float z, float distance = sPlayerbotAIConfig->followDistance, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
bool MoveInside(uint32 mapId, float x, float y, float z, float distance = sPlayerbotAIConfig->followDistance,
|
||||||
|
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
|
||||||
void CreateWp(Player* wpOwner, float x, float y, float z, float o, uint32 entry, bool important = false);
|
void CreateWp(Player* wpOwner, float x, float y, float z, float o, uint32 entry, bool important = false);
|
||||||
Position BestPositionForMeleeToFlee(Position pos, float radius);
|
Position BestPositionForMeleeToFlee(Position pos, float radius);
|
||||||
Position BestPositionForRangedToFlee(Position pos, float radius);
|
Position BestPositionForRangedToFlee(Position pos, float radius);
|
||||||
@@ -74,6 +80,7 @@ private:
|
|||||||
const Movement::PointsArray SearchForBestPath(float x, float y, float z, float& modified_z, int maxSearchCount = 5,
|
const Movement::PointsArray SearchForBestPath(float x, float y, float z, float& modified_z, int maxSearchCount = 5,
|
||||||
bool normal_only = false, float step = 8.0f);
|
bool normal_only = false, float step = 8.0f);
|
||||||
bool wasMovementRestricted = false;
|
bool wasMovementRestricted = false;
|
||||||
|
void DoMovePoint(Unit* unit, float x, float y, float z, bool generatePath, bool backwards);
|
||||||
};
|
};
|
||||||
|
|
||||||
class FleeAction : public MovementAction
|
class FleeAction : public MovementAction
|
||||||
@@ -149,17 +156,18 @@ public:
|
|||||||
|
|
||||||
class RearFlankAction : public MovementAction
|
class RearFlankAction : public MovementAction
|
||||||
{
|
{
|
||||||
// 90 degree minimum angle prevents any frontal cleaves/breaths and avoids parry-hasting the boss.
|
// 90 degree minimum angle prevents any frontal cleaves/breaths and avoids parry-hasting the boss.
|
||||||
// 120 degree maximum angle leaves a 120 degree symmetrical cone at the tail end which is usually enough to avoid tail swipes.
|
// 120 degree maximum angle leaves a 120 degree symmetrical cone at the tail end which is usually enough to avoid
|
||||||
// Some dragons or mobs may have different danger zone angles, override if needed.
|
// tail swipes. Some dragons or mobs may have different danger zone angles, override if needed.
|
||||||
public:
|
public:
|
||||||
RearFlankAction(PlayerbotAI* botAI, float distance = 0.0f, float minAngle = ANGLE_90_DEG, float maxAngle = ANGLE_120_DEG)
|
RearFlankAction(PlayerbotAI* botAI, float distance = 0.0f, float minAngle = ANGLE_90_DEG,
|
||||||
|
float maxAngle = ANGLE_120_DEG)
|
||||||
: MovementAction(botAI, "rear flank")
|
: MovementAction(botAI, "rear flank")
|
||||||
{
|
{
|
||||||
this->distance = distance;
|
this->distance = distance;
|
||||||
this->minAngle = minAngle;
|
this->minAngle = minAngle;
|
||||||
this->maxAngle = maxAngle;
|
this->maxAngle = maxAngle;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Execute(Event event) override;
|
bool Execute(Event event) override;
|
||||||
bool isUseful() override;
|
bool isUseful() override;
|
||||||
@@ -297,7 +305,9 @@ class MoveAwayFromCreatureAction : public MovementAction
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MoveAwayFromCreatureAction(PlayerbotAI* botAI, std::string name, uint32 creatureId, float range, bool alive = true)
|
MoveAwayFromCreatureAction(PlayerbotAI* botAI, std::string name, uint32 creatureId, float range, bool alive = true)
|
||||||
: MovementAction(botAI, name), creatureId(creatureId), range(range), alive(alive) {}
|
: MovementAction(botAI, name), creatureId(creatureId), range(range), alive(alive)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
bool Execute(Event event) override;
|
bool Execute(Event event) override;
|
||||||
bool isPossible() override;
|
bool isPossible() override;
|
||||||
@@ -312,7 +322,9 @@ class MoveAwayFromPlayerWithDebuffAction : public MovementAction
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MoveAwayFromPlayerWithDebuffAction(PlayerbotAI* botAI, std::string name, uint32 spellId, float range)
|
MoveAwayFromPlayerWithDebuffAction(PlayerbotAI* botAI, std::string name, uint32 spellId, float range)
|
||||||
: MovementAction(botAI, name), spellId(spellId), range(range) {}
|
: MovementAction(botAI, name), spellId(spellId), range(range)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
bool Execute(Event event) override;
|
bool Execute(Event event) override;
|
||||||
bool isPossible() override;
|
bool isPossible() override;
|
||||||
|
|||||||
@@ -6,16 +6,17 @@
|
|||||||
#include "PassLeadershipToMasterAction.h"
|
#include "PassLeadershipToMasterAction.h"
|
||||||
|
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
|
#include "PlayerbotOperations.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
#include "PlayerbotWorldThreadProcessor.h"
|
||||||
|
|
||||||
bool PassLeadershipToMasterAction::Execute(Event event)
|
bool PassLeadershipToMasterAction::Execute(Event event)
|
||||||
{
|
{
|
||||||
if (Player* master = GetMaster())
|
if (Player* master = GetMaster())
|
||||||
if (master && master != bot && bot->GetGroup() && bot->GetGroup()->IsMember(master->GetGUID()))
|
if (master && master != bot && bot->GetGroup() && bot->GetGroup()->IsMember(master->GetGUID()))
|
||||||
{
|
{
|
||||||
WorldPacket p(SMSG_GROUP_SET_LEADER, 8);
|
auto setLeaderOp = std::make_unique<GroupSetLeaderOperation>(bot->GetGUID(), master->GetGUID());
|
||||||
p << master->GetGUID();
|
sPlayerbotWorldProcessor->QueueOperation(std::move(setLeaderOp));
|
||||||
bot->GetSession()->HandleGroupSetLeaderOpcode(p);
|
|
||||||
|
|
||||||
if (!message.empty())
|
if (!message.empty())
|
||||||
botAI->TellMasterNoFacing(message);
|
botAI->TellMasterNoFacing(message);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include "RTSCValues.h"
|
#include "RTSCValues.h"
|
||||||
#include "RtscAction.h"
|
#include "RtscAction.h"
|
||||||
#include "PositionValue.h"
|
#include "PositionValue.h"
|
||||||
|
#include "ByteBuffer.h"
|
||||||
|
|
||||||
Creature* SeeSpellAction::CreateWps(Player* wpOwner, float x, float y, float z, float o, uint32 entry, Creature* lastWp,
|
Creature* SeeSpellAction::CreateWps(Player* wpOwner, float x, float y, float z, float o, uint32 entry, Creature* lastWp,
|
||||||
bool important)
|
bool important)
|
||||||
@@ -31,27 +32,52 @@ Creature* SeeSpellAction::CreateWps(Player* wpOwner, float x, float y, float z,
|
|||||||
|
|
||||||
bool SeeSpellAction::Execute(Event event)
|
bool SeeSpellAction::Execute(Event event)
|
||||||
{
|
{
|
||||||
WorldPacket p(event.getPacket()); //
|
// RTSC packet data
|
||||||
|
WorldPacket p(event.getPacket());
|
||||||
|
uint8 castCount;
|
||||||
uint32 spellId;
|
uint32 spellId;
|
||||||
uint8 castCount, castFlags;
|
uint8 castFlags;
|
||||||
|
|
||||||
|
// check RTSC header size = castCount (uint8) + spellId (uint32) + castFlags (uint8)
|
||||||
|
uint32 const rtscHeaderSize = sizeof(uint8) + sizeof(uint32) + sizeof(uint8);
|
||||||
|
if (p.size() < rtscHeaderSize)
|
||||||
|
{
|
||||||
|
LOG_WARN("playerbots", "SeeSpellAction: Corrupt RTSC packet size={}, expected>={}", p.size(), rtscHeaderSize);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Player* master = botAI->GetMaster();
|
Player* master = botAI->GetMaster();
|
||||||
|
|
||||||
p.rpos(0);
|
|
||||||
p >> castCount >> spellId >> castFlags;
|
|
||||||
|
|
||||||
if (!master)
|
if (!master)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// read RTSC packet data
|
||||||
|
p.rpos(0); // set read position to start
|
||||||
|
p >> castCount >> spellId >> castFlags;
|
||||||
|
|
||||||
// if (!botAI->HasStrategy("RTSC", botAI->GetState()))
|
// if (!botAI->HasStrategy("RTSC", botAI->GetState()))
|
||||||
// return false;
|
// return false;
|
||||||
|
|
||||||
if (spellId != RTSC_MOVE_SPELL)
|
if (spellId != RTSC_MOVE_SPELL)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
// should not throw exception,just defensive measure to prevent any crashes when core function breaks.
|
||||||
|
|
||||||
SpellCastTargets targets;
|
SpellCastTargets targets;
|
||||||
targets.Read(p, botAI->GetMaster());
|
try
|
||||||
|
{
|
||||||
|
targets.Read(p, master);
|
||||||
|
if (!targets.GetDst())
|
||||||
|
{
|
||||||
|
// do not dereference a null destination; ignore malformed RTSC packets instead of crashing
|
||||||
|
LOG_WARN("playerbots", "SeeSpellAction: (malformed) RTSC payload does not contain full targets data");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ByteBufferException const&)
|
||||||
|
{
|
||||||
|
// ignore malformed RTSC packets instead of crashing
|
||||||
|
LOG_WARN("playerbots", "SeeSpellAction: Failed deserialization (malformed) RTSC payload");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
WorldPosition spellPosition(master->GetMapId(), targets.GetDst()->_position);
|
WorldPosition spellPosition(master->GetMapId(), targets.GetDst()->_position);
|
||||||
SET_AI_VALUE(WorldPosition, "see spell location", spellPosition);
|
SET_AI_VALUE(WorldPosition, "see spell location", spellPosition);
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
|
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
#include "ServerFacade.h"
|
||||||
|
#include "AoeValues.h"
|
||||||
|
#include "TargetValue.h"
|
||||||
|
|
||||||
NextAction** CastAbolishPoisonAction::getAlternatives()
|
NextAction** CastAbolishPoisonAction::getAlternatives()
|
||||||
{
|
{
|
||||||
@@ -30,6 +33,32 @@ bool CastEntanglingRootsCcAction::Execute(Event event) { return botAI->CastSpell
|
|||||||
Value<Unit*>* CastHibernateCcAction::GetTargetValue() { return context->GetValue<Unit*>("cc target", "hibernate"); }
|
Value<Unit*>* CastHibernateCcAction::GetTargetValue() { return context->GetValue<Unit*>("cc target", "hibernate"); }
|
||||||
|
|
||||||
bool CastHibernateCcAction::Execute(Event event) { return botAI->CastSpell("hibernate", GetTarget()); }
|
bool CastHibernateCcAction::Execute(Event event) { return botAI->CastSpell("hibernate", GetTarget()); }
|
||||||
|
bool CastStarfallAction::isUseful()
|
||||||
|
{
|
||||||
|
if (!CastSpellAction::isUseful())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Avoid breaking CC
|
||||||
|
WorldLocation aoePos = *context->GetValue<WorldLocation>("aoe position");
|
||||||
|
Unit* ccTarget = context->GetValue<Unit*>("current cc target")->Get();
|
||||||
|
if (ccTarget && ccTarget->IsAlive())
|
||||||
|
{
|
||||||
|
float dist2d = sServerFacade->GetDistance2d(ccTarget, aoePos.GetPositionX(), aoePos.GetPositionY());
|
||||||
|
if (sServerFacade->IsDistanceLessOrEqualThan(dist2d, sPlayerbotAIConfig->aoeRadius))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid single-target usage on initial pull
|
||||||
|
uint8 aoeCount = *context->GetValue<uint8>("aoe count");
|
||||||
|
if (aoeCount < 2)
|
||||||
|
{
|
||||||
|
Unit* target = context->GetValue<Unit*>("current target")->Get();
|
||||||
|
if (!target || (!botAI->HasAura("moonfire", target) && !botAI->HasAura("insect swarm", target)))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
NextAction** CastReviveAction::getPrerequisites()
|
NextAction** CastReviveAction::getPrerequisites()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -144,6 +144,8 @@ class CastStarfallAction : public CastSpellAction
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CastStarfallAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "starfall") {}
|
CastStarfallAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "starfall") {}
|
||||||
|
|
||||||
|
bool isUseful() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CastHurricaneAction : public CastSpellAction
|
class CastHurricaneAction : public CastSpellAction
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace GruulsLairHelpers
|
|||||||
SPELL_SPELL_SHIELD = 33054,
|
SPELL_SPELL_SHIELD = 33054,
|
||||||
|
|
||||||
// Hunter
|
// Hunter
|
||||||
SPELL_MISDIRECTION = 34477,
|
SPELL_MISDIRECTION = 35079,
|
||||||
|
|
||||||
// Warlock
|
// Warlock
|
||||||
SPELL_BANISH = 18647, // Rank 2
|
SPELL_BANISH = 18647, // Rank 2
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ namespace MagtheridonHelpers
|
|||||||
SPELL_FEAR = 6215,
|
SPELL_FEAR = 6215,
|
||||||
|
|
||||||
// Hunter
|
// Hunter
|
||||||
SPELL_MISDIRECTION = 34477,
|
SPELL_MISDIRECTION = 35079,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum MagtheridonNPCs
|
enum MagtheridonNPCs
|
||||||
|
|||||||
Reference in New Issue
Block a user