mirror of
https://github.com/mod-playerbots/mod-playerbots
synced 2025-11-29 15:58:20 +08:00
Compare commits
15 Commits
cadbcbd447
...
master_v16
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6620def962 | ||
|
|
cddd406b1c | ||
|
|
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
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
paths:
|
||||
- src/**
|
||||
- "!README.md"
|
||||
- "!docs/**"
|
||||
|
||||
concurrency:
|
||||
group: "codestyle-cppcheck-${{ github.event.pull_request.number }}"
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
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/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>
|
||||
|
||||
|
||||
@@ -18,25 +18,27 @@
|
||||
</div>
|
||||
|
||||
# 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:
|
||||
|
||||
- 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;
|
||||
- Bots capable of running most raids and battlegrounds;
|
||||
- Highly configurable settings to define how bots behave;
|
||||
- Excellent performance, even when running thousands of bots.
|
||||
- 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
|
||||
- Bots capable of running most raids and battlegrounds
|
||||
- Highly configurable settings to define how bots behave
|
||||
- Excellent performance, even when running thousands of bots
|
||||
|
||||
**This project is still under development**. If you encounter any errors or experience crashes, we kindly request that you [report them as GitHub issues](https://github.com/mod-playerbots/mod-playerbots/issues/new?template=bug_report.md). Your valuable feedback will help us improve this project collaboratively.
|
||||
|
||||
`mod-playerbots` has a **[Discord server](https://discord.gg/NQm5QShwf9)** where you can discuss the project, ask questions, and get involved in the community!
|
||||
We also have a **[Discord server](https://discord.gg/NQm5QShwf9)** where you can discuss the project, ask questions, and get involved in the community!
|
||||
|
||||
## 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
|
||||
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 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
|
||||
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
|
||||
|
||||
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.
|
||||
- **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.
|
||||
## Contributing
|
||||
|
||||
## Code standards
|
||||
- https://www.azerothcore.org/wiki/cpp-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.
|
||||
|
||||
## 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)]
|
||||
- [Unbot Addon (zh)](https://github.com/liyunfan1223/unbot-addon) (Chinese version by Liyunfan) [note: no longer under active development]
|
||||
- [Unbot Addon (en)](https://github.com/noisiver/unbot-addon/tree/english) (English version translated by @Revision) [note: no longer under active development]
|
||||
Please click on the "⭐" button to stay up to date and help us gain more visibility on GitHub!
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
`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:
|
||||
|
||||
|
||||
@@ -725,8 +725,8 @@ std::string const PlayerbotAIConfig::GetTimestampStr()
|
||||
// HH hour (2 digits 00-23)
|
||||
// MM minutes (2 digits 00-59)
|
||||
// SS seconds (2 digits 00-59)
|
||||
char buf[20];
|
||||
snprintf(buf, 20, "%04d-%02d-%02d %02d-%02d-%02d", aTm->tm_year + 1900, aTm->tm_mon + 1, aTm->tm_mday, aTm->tm_hour,
|
||||
char buf[32];
|
||||
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);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,9 @@
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "PlayerbotDbStore.h"
|
||||
#include "PlayerbotFactory.h"
|
||||
#include "PlayerbotOperations.h"
|
||||
#include "PlayerbotSecurity.h"
|
||||
#include "PlayerbotWorldThreadProcessor.h"
|
||||
#include "Playerbots.h"
|
||||
#include "RandomPlayerbotMgr.h"
|
||||
#include "SharedDefines.h"
|
||||
@@ -85,7 +87,6 @@ public:
|
||||
|
||||
void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId)
|
||||
{
|
||||
// bot is loading
|
||||
if (botLoading.find(playerGuid) != botLoading.end())
|
||||
return;
|
||||
|
||||
@@ -195,7 +196,9 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
|
||||
}
|
||||
|
||||
sRandomPlayerbotMgr->OnPlayerLogin(bot);
|
||||
OnBotLogin(bot);
|
||||
|
||||
auto op = std::make_unique<OnBotLoginOperation>(bot->GetGUID(), this);
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(op));
|
||||
|
||||
botLoading.erase(holder.GetGuid());
|
||||
}
|
||||
@@ -316,11 +319,9 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid)
|
||||
if (!botAI)
|
||||
return;
|
||||
|
||||
Group* group = bot->GetGroup();
|
||||
if (group && !bot->InBattleground() && !bot->InBattlegroundQueue() && botAI->HasActivePlayerMaster())
|
||||
{
|
||||
sPlayerbotDbStore->Save(botAI);
|
||||
}
|
||||
// Queue group cleanup operation for world thread
|
||||
auto cleanupOp = std::make_unique<BotLogoutGroupCleanupOperation>(guid);
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(cleanupOp));
|
||||
|
||||
LOG_DEBUG("playerbots", "Bot {} logging out", bot->GetName().c_str());
|
||||
bot->SaveToDB(false, false);
|
||||
@@ -549,6 +550,7 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
|
||||
|
||||
botAI->TellMaster("Hello!", PLAYERBOT_SECURITY_TALK);
|
||||
|
||||
// Queue group operations for world thread
|
||||
if (master && master->GetGroup() && !group)
|
||||
{
|
||||
Group* mgroup = master->GetGroup();
|
||||
@@ -556,24 +558,29 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
|
||||
{
|
||||
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())
|
||||
{
|
||||
mgroup->AddMember(bot);
|
||||
// Queue AddMember operation
|
||||
auto addOp = std::make_unique<GroupInviteOperation>(master->GetGUID(), bot->GetGUID());
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(addOp));
|
||||
}
|
||||
}
|
||||
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)
|
||||
{
|
||||
Group* newGroup = new Group();
|
||||
newGroup->Create(master);
|
||||
sGroupMgr->AddGroup(newGroup);
|
||||
newGroup->AddMember(bot);
|
||||
// Queue group creation and AddMember operation
|
||||
auto inviteOp = std::make_unique<GroupInviteOperation>(master->GetGUID(), bot->GetGUID());
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(inviteOp));
|
||||
}
|
||||
// if (master)
|
||||
// {
|
||||
@@ -1602,8 +1609,26 @@ void PlayerbotMgr::OnBotLoginInternal(Player* const bot)
|
||||
|
||||
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
|
||||
sPlayerbotTextMgr->AddLocalePriority(player->GetSession()->GetSessionDbcLocale());
|
||||
sPlayerbotTextMgr->AddLocalePriority(usedLocale);
|
||||
|
||||
if (sPlayerbotAIConfig->selfBotLevel > 2)
|
||||
HandlePlayerbotCommand("self", player);
|
||||
@@ -1611,7 +1636,7 @@ void PlayerbotMgr::OnPlayerLogin(Player* player)
|
||||
if (!sPlayerbotAIConfig->botAutologin)
|
||||
return;
|
||||
|
||||
uint32 accountId = player->GetSession()->GetAccountId();
|
||||
uint32 accountId = session->GetAccountId();
|
||||
QueryResult results = CharacterDatabase.Query("SELECT name FROM characters WHERE account = {}", accountId);
|
||||
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)
|
||||
{
|
||||
if (!locale)
|
||||
if (locale >= MAX_LOCALES)
|
||||
{
|
||||
LOG_WARN("playerbots", "Ignoring locale {} for bot texts because it exceeds MAX_LOCALES ({})", locale, MAX_LOCALES - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
botTextLocalePriority[locale]++;
|
||||
}
|
||||
|
||||
uint32 PlayerbotTextMgr::GetLocalePriority()
|
||||
{
|
||||
uint32 topLocale = 0;
|
||||
|
||||
// if no real players online, reset top locale
|
||||
if (!sWorldSessionMgr->GetActiveSessionCount())
|
||||
uint32 const activeSessions = sWorldSessionMgr->GetActiveSessionCount();
|
||||
if (!activeSessions)
|
||||
{
|
||||
ResetLocalePriority();
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32 topLocale = 0;
|
||||
for (uint8 i = 0; i < MAX_LOCALES; ++i)
|
||||
{
|
||||
if (botTextLocalePriority[i] > topLocale)
|
||||
if (botTextLocalePriority[i] > botTextLocalePriority[topLocale])
|
||||
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 "PlayerScript.h"
|
||||
#include "PlayerbotAIConfig.h"
|
||||
#include "PlayerbotWorldThreadProcessor.h"
|
||||
#include "RandomPlayerbotMgr.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "cs_playerbots.h"
|
||||
@@ -300,7 +301,8 @@ class PlayerbotsWorldScript : public WorldScript
|
||||
{
|
||||
public:
|
||||
PlayerbotsWorldScript() : WorldScript("PlayerbotsWorldScript", {
|
||||
WORLDHOOK_ON_BEFORE_WORLD_INITIALIZED
|
||||
WORLDHOOK_ON_BEFORE_WORLD_INITIALIZED,
|
||||
WORLDHOOK_ON_UPDATE
|
||||
}) {}
|
||||
|
||||
void OnBeforeWorldInitialized() override
|
||||
@@ -329,6 +331,13 @@ public:
|
||||
|
||||
LOG_INFO("server.loading", ">> Loaded playerbots config in {} ms", GetMSTimeDiffToNow(oldMSTime));
|
||||
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 +399,7 @@ public:
|
||||
|
||||
void OnPlayerbotUpdate(uint32 diff) override
|
||||
{
|
||||
sRandomPlayerbotMgr->UpdateAI(diff);
|
||||
sRandomPlayerbotMgr->UpdateSessions();
|
||||
sRandomPlayerbotMgr->UpdateSessions(); // Per-bot updates only
|
||||
}
|
||||
|
||||
void OnPlayerbotUpdateSessions(Player* player) override
|
||||
|
||||
@@ -40,7 +40,14 @@ bool ReachAreaTriggerAction::Execute(Event event)
|
||||
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 delay = 1000.0f * distance / bot->GetSpeed(MOVE_RUN) + sPlayerbotAIConfig->reactDelay;
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
*/
|
||||
|
||||
#include "BattleGroundTactics.h"
|
||||
#include "BattleGroundJoinAction.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "ArenaTeam.h"
|
||||
#include "ArenaTeamMgr.h"
|
||||
#include "BattleGroundJoinAction.h"
|
||||
#include "Battleground.h"
|
||||
#include "BattlegroundAB.h"
|
||||
#include "BattlegroundAV.h"
|
||||
@@ -22,11 +24,12 @@
|
||||
#include "BattlegroundSA.h"
|
||||
#include "BattlegroundWS.h"
|
||||
#include "Event.h"
|
||||
#include "GameObject.h"
|
||||
#include "IVMapMgr.h"
|
||||
#include "PathGenerator.h"
|
||||
#include "Playerbots.h"
|
||||
#include "PositionValue.h"
|
||||
#include "PvpTriggers.h"
|
||||
#include "PathGenerator.h"
|
||||
#include "ServerFacade.h"
|
||||
#include "Vehicle.h"
|
||||
|
||||
@@ -3578,6 +3581,16 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
||||
GuidVector closePlayers;
|
||||
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
|
||||
switch (bgType)
|
||||
{
|
||||
@@ -3607,27 +3620,82 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
||||
if (closeObjects.empty())
|
||||
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
|
||||
GameObject* targetFlag = nullptr;
|
||||
for (ObjectGuid const guid : closeObjects)
|
||||
{
|
||||
GameObject* go = botAI->GetGameObject(guid);
|
||||
if (!go)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool const isEyCenterFlag = eyeBg && eyCenterFlag && eyCenterFlag->GetGUID() == go->GetGUID();
|
||||
|
||||
// Check if this object is a valid capture target
|
||||
std::vector<uint32>::const_iterator f = find(vFlagIds.begin(), vFlagIds.end(), go->GetEntry());
|
||||
if (f == vFlagIds.end())
|
||||
std::vector<uint32>::const_iterator f = std::find(vFlagIds.begin(), vFlagIds.end(), go->GetEntry());
|
||||
if (f == vFlagIds.end() && !isEyCenterFlag)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Verify the object is active and ready
|
||||
if (!go->isSpawned() || go->GetGoState() != GO_STATE_READY)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if we're in range (using double range for enemy detection)
|
||||
float const dist = bot->GetDistance(go);
|
||||
if (flagRange && dist > flagRange * 2.0f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
targetFlag = go;
|
||||
break;
|
||||
@@ -3655,7 +3723,7 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
||||
}
|
||||
|
||||
// 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
|
||||
uint32 numCapturing = 0;
|
||||
@@ -3664,17 +3732,20 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
||||
{
|
||||
if (Unit* pFriend = botAI->GetUnit(guid))
|
||||
{
|
||||
// Check if they're casting the capture spell
|
||||
if (Spell* spell = pFriend->GetCurrentSpell(CURRENT_GENERIC_SPELL))
|
||||
// Check if they're casting or channeling the capture spell
|
||||
Spell* spell = pFriend->GetCurrentSpell(CURRENT_GENERIC_SPELL);
|
||||
if (!spell)
|
||||
{
|
||||
if (spell->m_spellInfo->Id == SPELL_CAPTURE_BANNER)
|
||||
spell = pFriend->GetCurrentSpell(CURRENT_CHANNELED_SPELL);
|
||||
}
|
||||
|
||||
if (spell && spell->m_spellInfo && spell->m_spellInfo->Id == SPELL_CAPTURE_BANNER)
|
||||
{
|
||||
numCapturing++;
|
||||
capturingPlayer = pFriend;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If friendlies are capturing, stay to defend but don't capture
|
||||
if (numCapturing > 0 && capturingPlayer && bot->GetGUID() != capturingPlayer->GetGUID())
|
||||
@@ -3704,9 +3775,11 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
||||
if (!go)
|
||||
continue;
|
||||
|
||||
bool const isEyCenterFlag = eyeBg && eyCenterFlag && eyCenterFlag->GetGUID() == go->GetGUID();
|
||||
|
||||
// Validate this is a capture target
|
||||
std::vector<uint32>::const_iterator f = find(vFlagIds.begin(), vFlagIds.end(), go->GetEntry());
|
||||
if (f == vFlagIds.end())
|
||||
std::vector<uint32>::const_iterator f = std::find(vFlagIds.begin(), vFlagIds.end(), go->GetEntry());
|
||||
if (f == vFlagIds.end() && !isEyCenterFlag)
|
||||
continue;
|
||||
|
||||
// Check object is active
|
||||
@@ -3722,12 +3795,40 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
||||
continue;
|
||||
|
||||
// Special handling for WSG and EY base flags
|
||||
bool atBase = bgType == BATTLEGROUND_WS ? go->GetEntry() == vFlagsWS[bot->GetTeamId()]
|
||||
: bgType == BATTLEGROUND_EY ? go->GetEntry() == vFlagsEY[0]
|
||||
: false;
|
||||
bool isWsBaseFlag = bgType == BATTLEGROUND_WS && go->GetEntry() == vFlagsWS[bot->GetTeamId()];
|
||||
bool isEyBaseFlag = bgType == BATTLEGROUND_EY && go->GetEntry() == vFlagsEY[0];
|
||||
|
||||
// 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
|
||||
if (atBase && bgType == BATTLEGROUND_WS &&
|
||||
if (isWsBaseFlag && bgType == BATTLEGROUND_WS &&
|
||||
!(bot->HasAura(BG_WS_SPELL_WARSONG_FLAG) || bot->HasAura(BG_WS_SPELL_SILVERWING_FLAG)))
|
||||
continue;
|
||||
|
||||
@@ -3772,7 +3873,7 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
||||
if (dist < INTERACTION_DISTANCE)
|
||||
{
|
||||
// Handle flag capture at base
|
||||
if (atBase)
|
||||
if (isWsBaseFlag)
|
||||
{
|
||||
if (bot->GetTeamId() == TEAM_HORDE)
|
||||
{
|
||||
@@ -3811,28 +3912,50 @@ bool BGTactics::atFlag(std::vector<BattleBotPath*> const& vPaths, std::vector<ui
|
||||
}
|
||||
}
|
||||
case BATTLEGROUND_EY:
|
||||
{ // Issue: Currently bots in EY take flag instantly without casttime
|
||||
{ // Handle Netherstorm flag capture requiring a channel
|
||||
if (dist < INTERACTION_DISTANCE)
|
||||
{
|
||||
// Dismount before interacting
|
||||
if (bot->IsMounted())
|
||||
{
|
||||
bot->RemoveAurasByType(SPELL_AURA_MOUNTED);
|
||||
}
|
||||
|
||||
if (bot->IsInDisallowedMountForm())
|
||||
{
|
||||
bot->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT);
|
||||
}
|
||||
|
||||
// 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);
|
||||
if (!spellInfo)
|
||||
return false;
|
||||
|
||||
Spell* spell = new Spell(bot, spellInfo, TRIGGERED_NONE);
|
||||
spell->m_targets.SetGOTarget(go);
|
||||
|
||||
bot->StopMoving();
|
||||
spell->prepare(&spell->m_targets);
|
||||
|
||||
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
|
||||
|
||||
@@ -81,7 +81,7 @@ bool CheckMountStateAction::isUseful()
|
||||
// to mostly be an issue in tunnels of WSG and AV)
|
||||
float posZ = bot->GetPositionZ();
|
||||
float groundLevel = bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), posZ);
|
||||
if (!bot->IsMounted() && posZ < groundLevel)
|
||||
if (!bot->IsMounted() && !bot->HasWaterWalkAura() && posZ < groundLevel)
|
||||
return false;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
Player* player = GetPlayer(event);
|
||||
@@ -84,12 +92,6 @@ bool GuildInviteAction::isUseful()
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
void GuildPromoteAction::SendPacket(WorldPacket packet)
|
||||
{
|
||||
WorldPackets::Guild::GuildPromoteMember data = WorldPacket(packet);
|
||||
bot->GetSession()->HandleGuildPromoteOpcode(data);
|
||||
}
|
||||
|
||||
bool GuildPromoteAction::PlayerIsValid(Player* member)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
void GuildDemoteAction::SendPacket(WorldPacket packet)
|
||||
{
|
||||
WorldPackets::Guild::GuildDemoteMember data = WorldPacket(packet);
|
||||
bot->GetSession()->HandleGuildDemoteOpcode(data);
|
||||
}
|
||||
|
||||
bool GuildDemoteAction::PlayerIsValid(Player* 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);
|
||||
}
|
||||
|
||||
void GuildRemoveAction::SendPacket(WorldPacket packet)
|
||||
{
|
||||
WorldPackets::Guild::GuildOfficerRemoveMember data = WorldPacket(packet);
|
||||
bot->GetSession()->HandleGuildRemoveOpcode(data);
|
||||
}
|
||||
|
||||
bool GuildRemoveAction::PlayerIsValid(Player* member)
|
||||
{
|
||||
return member->GetGuildId() == bot->GetGuildId() && GetRankId(bot) < GetRankId(member);
|
||||
|
||||
@@ -25,7 +25,7 @@ public:
|
||||
bool isUseful() override { return false; }
|
||||
|
||||
protected:
|
||||
virtual void SendPacket(WorldPacket data){};
|
||||
virtual void SendPacket(WorldPacket const& packet);
|
||||
virtual Player* GetPlayer(Event event);
|
||||
virtual bool PlayerIsValid(Player* member);
|
||||
virtual uint8 GetRankId(Player* member);
|
||||
@@ -44,7 +44,6 @@ public:
|
||||
bool isUseful() override;
|
||||
|
||||
protected:
|
||||
void SendPacket(WorldPacket data) override;
|
||||
bool PlayerIsValid(Player* member) override;
|
||||
};
|
||||
|
||||
@@ -59,7 +58,6 @@ public:
|
||||
bool isUseful() override;
|
||||
|
||||
protected:
|
||||
void SendPacket(WorldPacket data) override;
|
||||
bool PlayerIsValid(Player* member) override;
|
||||
};
|
||||
|
||||
@@ -74,7 +72,6 @@ public:
|
||||
bool isUseful() override;
|
||||
|
||||
protected:
|
||||
void SendPacket(WorldPacket data) override;
|
||||
bool PlayerIsValid(Player* member) override;
|
||||
};
|
||||
|
||||
@@ -89,7 +86,6 @@ public:
|
||||
bool isUseful() override;
|
||||
|
||||
protected:
|
||||
void SendPacket(WorldPacket data) override;
|
||||
bool PlayerIsValid(Player* member) override;
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
#include "Event.h"
|
||||
#include "GuildMgr.h"
|
||||
#include "Log.h"
|
||||
#include "PlayerbotOperations.h"
|
||||
#include "Playerbots.h"
|
||||
#include "PlayerbotWorldThreadProcessor.h"
|
||||
#include "ServerFacade.h"
|
||||
|
||||
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 (!group->isRaidGroup() && group->GetMembersCount() > 4)
|
||||
group->ConvertToRaid();
|
||||
{
|
||||
auto convertOp = std::make_unique<GroupConvertToRaidOperation>(inviter->GetGUID());
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(convertOp));
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
if (group && botAI->GetGrouperType() > GrouperType::LEADER_5 && !group->isRaidGroup() &&
|
||||
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))
|
||||
{
|
||||
@@ -221,7 +229,8 @@ bool InviteGuildToGroupAction::Execute(Event event)
|
||||
if (group && botAI->GetGrouperType() > GrouperType::LEADER_5 && !group->isRaidGroup() &&
|
||||
bot->GetGroup()->GetMembersCount() > 3)
|
||||
{
|
||||
group->ConvertToRaid();
|
||||
auto convertOp = std::make_unique<GroupConvertToRaidOperation>(bot->GetGUID());
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(convertOp));
|
||||
}
|
||||
|
||||
if (sPlayerbotAIConfig->inviteChat &&
|
||||
@@ -362,7 +371,10 @@ bool LfgAction::Execute(Event event)
|
||||
if (param.empty() || param == "5" || group->isRaidGroup())
|
||||
return false; // Group or raid is full so stop trying.
|
||||
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();
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <iomanip>
|
||||
#include <string>
|
||||
|
||||
#include "Corpse.h"
|
||||
#include "Event.h"
|
||||
#include "FleeManager.h"
|
||||
#include "G3D/Vector3.h"
|
||||
@@ -41,7 +42,6 @@
|
||||
#include "Unit.h"
|
||||
#include "Vehicle.h"
|
||||
#include "WaypointMovementGenerator.h"
|
||||
#include "Corpse.h"
|
||||
|
||||
MovementAction::MovementAction(PlayerbotAI* botAI, std::string const name) : Action(botAI, name)
|
||||
{
|
||||
@@ -199,8 +199,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
|
||||
{
|
||||
VehicleSeatEntry const* seat = vehicle->GetSeatForPassenger(bot);
|
||||
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->CanFly();
|
||||
if (!vehicleBase || !seat || !seat->CanControl()) // is passenger and cant move anyway
|
||||
return false;
|
||||
|
||||
@@ -208,20 +207,14 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
|
||||
if (distance > 0.01f)
|
||||
{
|
||||
MotionMaster& mm = *vehicleBase->GetMotionMaster(); // need to move vehicle, not bot
|
||||
// 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);
|
||||
mm.MovePoint(0, x, y, z, generatePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
mm.MovePointBackwards(0, x, y, z, allowPathVeh);
|
||||
mm.MovePointBackwards(0, x, y, z, generatePath);
|
||||
}
|
||||
float speed = backwards ? vehicleBase->GetSpeed(MOVE_RUN_BACK) : vehicleBase->GetSpeed(MOVE_RUN);
|
||||
float delay = 1000.0f * (distance / speed);
|
||||
@@ -248,22 +241,15 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
|
||||
// bot->CastStop();
|
||||
// botAI->InterruptSpell();
|
||||
// }
|
||||
|
||||
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);
|
||||
mm.MovePoint(0, x, y, z, generatePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
mm.MovePointBackwards(0, x, y, z, allowPath);
|
||||
mm.MovePointBackwards(0, x, y, z, generatePath);
|
||||
}
|
||||
float delay = 1000.0f * MoveDelay(distance, backwards);
|
||||
if (lessDelay)
|
||||
@@ -296,23 +282,16 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
|
||||
// bot->CastStop();
|
||||
// botAI->InterruptSpell();
|
||||
// }
|
||||
|
||||
MotionMaster& mm = *bot->GetMotionMaster();
|
||||
G3D::Vector3 endP = path.back();
|
||||
// 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);
|
||||
mm.MovePoint(0, x, y, z, generatePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
mm.MovePointBackwards(0, x, y, z, allowPath);
|
||||
mm.MovePointBackwards(0, x, y, z, generatePath);
|
||||
}
|
||||
float delay = 1000.0f * MoveDelay(distance, backwards);
|
||||
if (lessDelay)
|
||||
@@ -530,8 +509,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
|
||||
// {
|
||||
// AI_VALUE(LastMovement&, "last area trigger").lastAreaTrigger = entry;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// else {
|
||||
// LOG_DEBUG("playerbots", "!entry");
|
||||
// return bot->TeleportTo(movePosition.getMapId(), movePosition.getX(), movePosition.getY(),
|
||||
// movePosition.getZ(), movePosition.getO(), 0);
|
||||
@@ -882,8 +860,8 @@ bool MovementAction::ReachCombatTo(Unit* target, float distance)
|
||||
float predictDis = std::min(3.0f, target->GetObjectSize() * 2);
|
||||
tx += cos(target->GetOrientation()) * predictDis;
|
||||
ty += sin(target->GetOrientation()) * predictDis;
|
||||
if (!target->GetMap()->CheckCollisionAndGetValidCoords(target, target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(),
|
||||
tx, ty, tz))
|
||||
if (!target->GetMap()->CheckCollisionAndGetValidCoords(target, target->GetPositionX(), target->GetPositionY(),
|
||||
target->GetPositionZ(), tx, ty, tz))
|
||||
{
|
||||
tx = target->GetPositionX();
|
||||
ty = target->GetPositionY();
|
||||
@@ -1001,9 +979,8 @@ bool MovementAction::IsMovingAllowed()
|
||||
return false;
|
||||
|
||||
if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) ||
|
||||
bot->IsBeingTeleported() || bot->HasRootAura() || bot->HasSpiritOfRedemptionAura() ||
|
||||
bot->HasConfuseAura() || bot->IsCharmed() || bot->HasStunAura() ||
|
||||
bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
|
||||
bot->IsBeingTeleported() || bot->HasRootAura() || bot->HasSpiritOfRedemptionAura() || bot->HasConfuseAura() ||
|
||||
bot->IsCharmed() || bot->HasStunAura() || bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
|
||||
return false;
|
||||
|
||||
if (bot->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE)
|
||||
@@ -1011,8 +988,7 @@ bool MovementAction::IsMovingAllowed()
|
||||
return false;
|
||||
}
|
||||
|
||||
// if (bot->HasUnitMovementFlag(MOVEMENTFLAG_FALLING))
|
||||
// {
|
||||
// if (bot->HasUnitMovementFlag(MOVEMENTFLAG_FALLING)) {
|
||||
// return false;
|
||||
// }
|
||||
return bot->GetMotionMaster()->GetCurrentMovementGeneratorType() != FLIGHT_MOTION_TYPE;
|
||||
@@ -1036,49 +1012,19 @@ void MovementAction::UpdateMovementState()
|
||||
bool onGround = bot->GetPositionZ() <
|
||||
bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ()) + 1.0f;
|
||||
|
||||
// Keep bot->SendMovementFlagUpdate() withing the if statements to not intefere with bot behavior on ground/(shallow) waters
|
||||
|
||||
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);
|
||||
changed = true;
|
||||
}
|
||||
if (!bot->HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY))
|
||||
{
|
||||
bot->AddUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
|
||||
changed = true;
|
||||
}
|
||||
if (!bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING))
|
||||
// Keep bot->SendMovementFlagUpdate() withing the if statements to not intefere with bot behavior on
|
||||
// ground/(shallow) waters
|
||||
if (!bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING) &&
|
||||
bot->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) && !onGround)
|
||||
{
|
||||
bot->AddUnitMovementFlag(MOVEMENTFLAG_FLYING);
|
||||
changed = true;
|
||||
}
|
||||
if (changed)
|
||||
bot->SendMovementFlagUpdate();
|
||||
}
|
||||
else if (!hasFlightAura)
|
||||
{
|
||||
bool changed = false;
|
||||
if (bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING))
|
||||
|
||||
else if (bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING) &&
|
||||
(!bot->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) || onGround))
|
||||
{
|
||||
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);
|
||||
changed = true;
|
||||
}
|
||||
if (changed)
|
||||
bot->SendMovementFlagUpdate();
|
||||
}
|
||||
|
||||
@@ -1097,12 +1043,9 @@ void MovementAction::UpdateMovementState()
|
||||
wasMovementRestricted = isCurrentlyRestricted;
|
||||
|
||||
// Temporary speed increase in group
|
||||
// if (botAI->HasRealPlayerMaster())
|
||||
// {
|
||||
// if (botAI->HasRealPlayerMaster()) {
|
||||
// bot->SetSpeedRate(MOVE_RUN, 1.1f);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// } else {
|
||||
// bot->SetSpeedRate(MOVE_RUN, 1.0f);
|
||||
// }
|
||||
// check if target is not reachable (from Vmangos)
|
||||
@@ -1111,7 +1054,7 @@ void MovementAction::UpdateMovementState()
|
||||
// {
|
||||
// if (Unit* pTarget = sServerFacade->GetChaseTarget(bot))
|
||||
// {
|
||||
// if (!bot->IsWithinMeleeRange(pTarget) && pTarget->IsCreature())
|
||||
// if (!bot->IsWithinMeleeRange(pTarget) && pTarget->GetTypeId() == TYPEID_UNIT)
|
||||
// {
|
||||
// float angle = bot->GetAngle(pTarget);
|
||||
// float distance = 5.0f;
|
||||
@@ -1180,6 +1123,13 @@ bool MovementAction::Follow(Unit* target, float distance, float angle)
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
if (!bot->InBattleground() && sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target),
|
||||
sPlayerbotAIConfig->followDistance))
|
||||
{
|
||||
// botAI->TellError("No need to follow");
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
if (!bot->InBattleground()
|
||||
&& sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target->GetPositionX(),
|
||||
@@ -1297,17 +1247,21 @@ bool MovementAction::Follow(Unit* target, float distance, float angle)
|
||||
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())
|
||||
distance += angle;
|
||||
|
||||
// Do not cancel follow if the 2D distance is short but the Z still differs (e.g., master above).
|
||||
float dz1 = fabs(bot->GetPositionZ() - target->GetPositionZ());
|
||||
if (!bot->InBattleground()
|
||||
&& sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target), sPlayerbotAIConfig->followDistance)
|
||||
&& dz1 < sPlayerbotAIConfig->contactDistance)
|
||||
if (!bot->InBattleground() && sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target),
|
||||
sPlayerbotAIConfig->followDistance))
|
||||
{
|
||||
// botAI->TellError("No need to follow");
|
||||
return false; // truly in range (2D and Z) => no need to move
|
||||
return false;
|
||||
}
|
||||
|
||||
bot->HandleEmoteCommand(0);
|
||||
@@ -1388,7 +1342,7 @@ float MovementAction::MoveDelay(float distance, bool backwards)
|
||||
}
|
||||
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;
|
||||
return delay;
|
||||
@@ -1418,8 +1372,7 @@ void MovementAction::SetNextMovementDelay(float delayMillis)
|
||||
{
|
||||
AI_VALUE(LastMovement&, "last movement")
|
||||
.Set(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetOrientation(),
|
||||
delayMillis,
|
||||
MovementPriority::MOVEMENT_FORCED);
|
||||
delayMillis, MovementPriority::MOVEMENT_FORCED);
|
||||
}
|
||||
|
||||
bool MovementAction::Flee(Unit* target)
|
||||
@@ -1633,7 +1586,8 @@ bool MovementAction::MoveAway(Unit* target, float distance, bool backwards)
|
||||
dz = bot->GetPositionZ();
|
||||
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;
|
||||
}
|
||||
@@ -1655,7 +1609,8 @@ bool MovementAction::MoveAway(Unit* target, float distance, bool backwards)
|
||||
dz = bot->GetPositionZ();
|
||||
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;
|
||||
}
|
||||
@@ -1704,7 +1659,7 @@ bool MovementAction::Move(float angle, float distance)
|
||||
float x = bot->GetPositionX() + cos(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());
|
||||
if (z == -100000.0f || z == -200000.0f)
|
||||
z = bot->GetPositionZ();
|
||||
@@ -1727,8 +1682,7 @@ bool MovementAction::MoveInside(uint32 mapId, float x, float y, float z, float d
|
||||
// float MovementAction::SearchBestGroundZForPath(float x, float y, float z, bool generatePath, float range, bool
|
||||
// normal_only, float step)
|
||||
// {
|
||||
// if (!generatePath)
|
||||
// {
|
||||
// if (!generatePath) {
|
||||
// return z;
|
||||
// }
|
||||
// float min_length = 100000.0f;
|
||||
@@ -1739,12 +1693,10 @@ bool MovementAction::MoveInside(uint32 mapId, float x, float y, float z, float d
|
||||
// modified_z = bot->GetMapWaterOrGroundLevel(x, y, z + delta);
|
||||
// PathGenerator gen(bot);
|
||||
// gen.CalculatePath(x, y, modified_z);
|
||||
// if (gen.GetPathType() == PATHFIND_NORMAL && gen.getPathLength() < min_length)
|
||||
// {
|
||||
// if (gen.GetPathType() == PATHFIND_NORMAL && gen.getPathLength() < min_length) {
|
||||
// min_length = gen.getPathLength();
|
||||
// current_z = modified_z;
|
||||
// if (abs(current_z - z) < 0.5f)
|
||||
// {
|
||||
// if (abs(current_z - z) < 0.5f) {
|
||||
// return current_z;
|
||||
// }
|
||||
// }
|
||||
@@ -1753,12 +1705,10 @@ bool MovementAction::MoveInside(uint32 mapId, float x, float y, float z, float d
|
||||
// modified_z = bot->GetMapWaterOrGroundLevel(x, y, z + delta);
|
||||
// PathGenerator gen(bot);
|
||||
// gen.CalculatePath(x, y, modified_z);
|
||||
// if (gen.GetPathType() == PATHFIND_NORMAL && gen.getPathLength() < min_length)
|
||||
// {
|
||||
// if (gen.GetPathType() == PATHFIND_NORMAL && gen.getPathLength() < min_length) {
|
||||
// min_length = gen.getPathLength();
|
||||
// current_z = modified_z;
|
||||
// if (abs(current_z - z) < 0.5f)
|
||||
// {
|
||||
// if (abs(current_z - z) < 0.5f) {
|
||||
// return current_z;
|
||||
// }
|
||||
// }
|
||||
@@ -1767,22 +1717,18 @@ bool MovementAction::MoveInside(uint32 mapId, float x, float y, float z, float d
|
||||
// modified_z = bot->GetMapWaterOrGroundLevel(x, y, z + delta);
|
||||
// PathGenerator gen(bot);
|
||||
// gen.CalculatePath(x, y, modified_z);
|
||||
// if (gen.GetPathType() == PATHFIND_NORMAL && gen.getPathLength() < min_length)
|
||||
// {
|
||||
// if (gen.GetPathType() == PATHFIND_NORMAL && gen.getPathLength() < min_length) {
|
||||
// min_length = gen.getPathLength();
|
||||
// current_z = modified_z;
|
||||
// if (abs(current_z - z) < 0.5f)
|
||||
// {
|
||||
// if (abs(current_z - z) < 0.5f) {
|
||||
// return current_z;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if (current_z == INVALID_HEIGHT && normal_only)
|
||||
// {
|
||||
// if (current_z == INVALID_HEIGHT && normal_only) {
|
||||
// return INVALID_HEIGHT;
|
||||
// }
|
||||
// if (current_z == INVALID_HEIGHT && !normal_only)
|
||||
// {
|
||||
// if (current_z == INVALID_HEIGHT && !normal_only) {
|
||||
// return z;
|
||||
// }
|
||||
// return current_z;
|
||||
@@ -1937,7 +1883,8 @@ bool AvoidAoeAction::AvoidAuraWithDynamicObj()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(spellInfo->Id) != sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end())
|
||||
if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(spellInfo->Id) !=
|
||||
sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end())
|
||||
return false;
|
||||
|
||||
DynamicObject* dynOwner = aura->GetDynobjOwner();
|
||||
@@ -2002,7 +1949,8 @@ bool AvoidAoeAction::AvoidGameObjectWithDamage()
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(spellId) != sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end())
|
||||
if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(spellId) !=
|
||||
sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end())
|
||||
continue;
|
||||
|
||||
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
@@ -2028,7 +1976,8 @@ bool AvoidAoeAction::AvoidGameObjectWithDamage()
|
||||
lastTellTimer = time(NULL);
|
||||
lastMoveTimer = getMSTime();
|
||||
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);
|
||||
}
|
||||
return true;
|
||||
@@ -2071,7 +2020,8 @@ bool AvoidAoeAction::AvoidUnitWithDamageAura()
|
||||
sSpellMgr->GetSpellInfo(spellInfo->Effects[aurEff->GetEffIndex()].TriggerSpell);
|
||||
if (!triggerSpellInfo)
|
||||
continue;
|
||||
if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(triggerSpellInfo->Id) != sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end())
|
||||
if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(triggerSpellInfo->Id) !=
|
||||
sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end())
|
||||
return false;
|
||||
for (int j = 0; j < MAX_SPELL_EFFECTS; j++)
|
||||
{
|
||||
@@ -2093,7 +2043,8 @@ bool AvoidAoeAction::AvoidUnitWithDamageAura()
|
||||
lastTellTimer = time(NULL);
|
||||
lastMoveTimer = getMSTime();
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -2112,7 +2063,8 @@ Position MovementAction::BestPositionForMeleeToFlee(Position pos, float radius)
|
||||
if (currentTarget)
|
||||
{
|
||||
// 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 angleLeft = angle + (float)M_PI / 2;
|
||||
float angleRight = angle - (float)M_PI / 2;
|
||||
@@ -2327,8 +2279,7 @@ bool CombatFormationMoveAction::isUseful()
|
||||
bool CombatFormationMoveAction::Execute(Event event)
|
||||
{
|
||||
float dis = AI_VALUE(float, "disperse distance");
|
||||
if (dis <= 0.0f ||
|
||||
(!bot->IsInCombat() && botAI->HasStrategy("stay", BotState::BOT_STATE_NON_COMBAT)) ||
|
||||
if (dis <= 0.0f || (!bot->IsInCombat() && botAI->HasStrategy("stay", BotState::BOT_STATE_NON_COMBAT)) ||
|
||||
(bot->IsInCombat() && botAI->HasStrategy("stay", BotState::BOT_STATE_COMBAT)))
|
||||
return false;
|
||||
Player* playerToLeave = NearestGroupMember(dis);
|
||||
@@ -2489,12 +2440,13 @@ bool TankFaceAction::Execute(Event event)
|
||||
float goodAngle2 = Position::NormalizeOrientation(averageAngle - M_PI * 3 / 5);
|
||||
|
||||
// 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;
|
||||
float x, y, z;
|
||||
target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle1);
|
||||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
||||
x, y, z))
|
||||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||||
bot->GetPositionZ(), x, y, z))
|
||||
{
|
||||
/// @todo: movement control now is a mess, prepare to rewrite
|
||||
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
|
||||
@@ -2506,8 +2458,8 @@ bool TankFaceAction::Execute(Event event)
|
||||
}
|
||||
}
|
||||
target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle2);
|
||||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
||||
x, y, z))
|
||||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||||
bot->GetPositionZ(), x, y, z))
|
||||
{
|
||||
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
|
||||
Position pos(x, y, z);
|
||||
@@ -2520,13 +2472,17 @@ bool TankFaceAction::Execute(Event event)
|
||||
if (availablePos.empty())
|
||||
return false;
|
||||
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()
|
||||
{
|
||||
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.
|
||||
bool inFront = target->HasInArc(2.f * minAngle, bot);
|
||||
@@ -2540,7 +2496,10 @@ bool RearFlankAction::isUseful()
|
||||
bool RearFlankAction::Execute(Event event)
|
||||
{
|
||||
Unit* target = AI_VALUE(Unit*, "current target");
|
||||
if (!target) { return false; }
|
||||
if (!target)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
float angle = frand(minAngle, maxAngle);
|
||||
float baseDistance = bot->GetMeleeRange(target) * 0.5f;
|
||||
@@ -2559,8 +2518,8 @@ bool RearFlankAction::Execute(Event event)
|
||||
destination = &rightFlank;
|
||||
}
|
||||
|
||||
return MoveTo(bot->GetMapId(), destination->GetPositionX(), destination->GetPositionY(), destination->GetPositionZ(),
|
||||
false, false, false, true, MovementPriority::MOVEMENT_COMBAT);
|
||||
return MoveTo(bot->GetMapId(), destination->GetPositionX(), destination->GetPositionY(),
|
||||
destination->GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT);
|
||||
}
|
||||
|
||||
bool DisperseSetAction::Execute(Event event)
|
||||
@@ -2688,9 +2647,8 @@ bool SetFacingTargetAction::isUseful() { return !AI_VALUE2(bool, "facing", "curr
|
||||
bool SetFacingTargetAction::isPossible()
|
||||
{
|
||||
if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) ||
|
||||
bot->IsBeingTeleported() || bot->HasConfuseAura() || bot->IsCharmed() ||
|
||||
bot->HasStunAura() || bot->IsInFlight() ||
|
||||
bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
|
||||
bot->IsBeingTeleported() || bot->HasConfuseAura() || bot->IsCharmed() || bot->HasStunAura() ||
|
||||
bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
@@ -2720,12 +2678,13 @@ bool SetBehindTargetAction::Execute(Event event)
|
||||
float goodAngle1 = 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;
|
||||
float x, y, z;
|
||||
target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle1);
|
||||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
||||
x, y, z))
|
||||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||||
bot->GetPositionZ(), x, y, z))
|
||||
{
|
||||
/// @todo: movement control now is a mess, prepare to rewrite
|
||||
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
|
||||
@@ -2737,8 +2696,8 @@ bool SetBehindTargetAction::Execute(Event event)
|
||||
}
|
||||
}
|
||||
target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle2);
|
||||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(),
|
||||
x, y, z))
|
||||
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||||
bot->GetPositionZ(), x, y, z))
|
||||
{
|
||||
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
|
||||
Position pos(x, y, z);
|
||||
@@ -2751,7 +2710,8 @@ bool SetBehindTargetAction::Execute(Event event)
|
||||
if (availablePos.empty())
|
||||
return false;
|
||||
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)
|
||||
@@ -2833,7 +2793,7 @@ bool MoveAwayFromCreatureAction::Execute(Event event)
|
||||
|
||||
// Find all creatures with the specified Id
|
||||
std::vector<Unit*> creatures;
|
||||
for (auto const& guid : targets)
|
||||
for (const auto& guid : targets)
|
||||
{
|
||||
Unit* unit = botAI->GetUnit(guid);
|
||||
if (unit && (alive && unit->IsAlive()) && unit->GetEntry() == creatureId)
|
||||
@@ -2905,10 +2865,7 @@ bool MoveAwayFromCreatureAction::Execute(Event event)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MoveAwayFromCreatureAction::isPossible()
|
||||
{
|
||||
return bot->CanFreeMove();
|
||||
}
|
||||
bool MoveAwayFromCreatureAction::isPossible() { return bot->CanFreeMove(); }
|
||||
|
||||
bool MoveAwayFromPlayerWithDebuffAction::Execute(Event event)
|
||||
{
|
||||
@@ -2995,7 +2952,4 @@ bool MoveAwayFromPlayerWithDebuffAction::Execute(Event event)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MoveAwayFromPlayerWithDebuffAction::isPossible()
|
||||
{
|
||||
return bot->CanFreeMove();
|
||||
}
|
||||
bool MoveAwayFromPlayerWithDebuffAction::isPossible() { return bot->CanFreeMove(); }
|
||||
|
||||
@@ -29,12 +29,17 @@ public:
|
||||
|
||||
protected:
|
||||
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 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 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);
|
||||
bool normal_only = false, bool exact_waypoint = false,
|
||||
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL, bool lessDelay = false,
|
||||
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();
|
||||
bool Follow(Unit* target, float distance = sPlayerbotAIConfig->followDistance);
|
||||
bool Follow(Unit* target, float distance, float angle);
|
||||
@@ -51,10 +56,11 @@ protected:
|
||||
bool Flee(Unit* target);
|
||||
void ClearIdleState();
|
||||
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 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);
|
||||
Position BestPositionForMeleeToFlee(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,
|
||||
bool normal_only = false, float step = 8.0f);
|
||||
bool wasMovementRestricted = false;
|
||||
void DoMovePoint(Unit* unit, float x, float y, float z, bool generatePath, bool backwards);
|
||||
};
|
||||
|
||||
class FleeAction : public MovementAction
|
||||
@@ -149,11 +156,12 @@ public:
|
||||
|
||||
class RearFlankAction : public MovementAction
|
||||
{
|
||||
// 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.
|
||||
// Some dragons or mobs may have different danger zone angles, override if needed.
|
||||
// 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. Some dragons or mobs may have different danger zone angles, override if needed.
|
||||
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")
|
||||
{
|
||||
this->distance = distance;
|
||||
@@ -297,7 +305,9 @@ class MoveAwayFromCreatureAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
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 isPossible() override;
|
||||
@@ -312,7 +322,9 @@ class MoveAwayFromPlayerWithDebuffAction : public MovementAction
|
||||
{
|
||||
public:
|
||||
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 isPossible() override;
|
||||
|
||||
@@ -6,16 +6,17 @@
|
||||
#include "PassLeadershipToMasterAction.h"
|
||||
|
||||
#include "Event.h"
|
||||
#include "PlayerbotOperations.h"
|
||||
#include "Playerbots.h"
|
||||
#include "PlayerbotWorldThreadProcessor.h"
|
||||
|
||||
bool PassLeadershipToMasterAction::Execute(Event event)
|
||||
{
|
||||
if (Player* master = GetMaster())
|
||||
if (master && master != bot && bot->GetGroup() && bot->GetGroup()->IsMember(master->GetGUID()))
|
||||
{
|
||||
WorldPacket p(SMSG_GROUP_SET_LEADER, 8);
|
||||
p << master->GetGUID();
|
||||
bot->GetSession()->HandleGroupSetLeaderOpcode(p);
|
||||
auto setLeaderOp = std::make_unique<GroupSetLeaderOperation>(bot->GetGUID(), master->GetGUID());
|
||||
sPlayerbotWorldProcessor->QueueOperation(std::move(setLeaderOp));
|
||||
|
||||
if (!message.empty())
|
||||
botAI->TellMasterNoFacing(message);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "RTSCValues.h"
|
||||
#include "RtscAction.h"
|
||||
#include "PositionValue.h"
|
||||
#include "ByteBuffer.h"
|
||||
|
||||
Creature* SeeSpellAction::CreateWps(Player* wpOwner, float x, float y, float z, float o, uint32 entry, Creature* lastWp,
|
||||
bool important)
|
||||
@@ -31,27 +32,52 @@ Creature* SeeSpellAction::CreateWps(Player* wpOwner, float x, float y, float z,
|
||||
|
||||
bool SeeSpellAction::Execute(Event event)
|
||||
{
|
||||
WorldPacket p(event.getPacket()); //
|
||||
// RTSC packet data
|
||||
WorldPacket p(event.getPacket());
|
||||
uint8 castCount;
|
||||
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();
|
||||
|
||||
p.rpos(0);
|
||||
p >> castCount >> spellId >> castFlags;
|
||||
|
||||
if (!master)
|
||||
return false;
|
||||
|
||||
// read RTSC packet data
|
||||
p.rpos(0); // set read position to start
|
||||
p >> castCount >> spellId >> castFlags;
|
||||
|
||||
// if (!botAI->HasStrategy("RTSC", botAI->GetState()))
|
||||
// return false;
|
||||
|
||||
if (spellId != RTSC_MOVE_SPELL)
|
||||
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;
|
||||
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);
|
||||
SET_AI_VALUE(WorldPosition, "see spell location", spellPosition);
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
|
||||
#include "Event.h"
|
||||
#include "Playerbots.h"
|
||||
#include "ServerFacade.h"
|
||||
#include "AoeValues.h"
|
||||
#include "TargetValue.h"
|
||||
|
||||
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"); }
|
||||
|
||||
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()
|
||||
{
|
||||
|
||||
@@ -144,6 +144,8 @@ class CastStarfallAction : public CastSpellAction
|
||||
{
|
||||
public:
|
||||
CastStarfallAction(PlayerbotAI* botAI) : CastSpellAction(botAI, "starfall") {}
|
||||
|
||||
bool isUseful() override;
|
||||
};
|
||||
|
||||
class CastHurricaneAction : public CastSpellAction
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace GruulsLairHelpers
|
||||
SPELL_SPELL_SHIELD = 33054,
|
||||
|
||||
// Hunter
|
||||
SPELL_MISDIRECTION = 34477,
|
||||
SPELL_MISDIRECTION = 35079,
|
||||
|
||||
// Warlock
|
||||
SPELL_BANISH = 18647, // Rank 2
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace MagtheridonHelpers
|
||||
SPELL_FEAR = 6215,
|
||||
|
||||
// Hunter
|
||||
SPELL_MISDIRECTION = 34477,
|
||||
SPELL_MISDIRECTION = 35079,
|
||||
};
|
||||
|
||||
enum MagtheridonNPCs
|
||||
|
||||
Reference in New Issue
Block a user