mirror of
https://github.com/mod-playerbots/mod-playerbots
synced 2025-11-29 15:58:20 +08:00
Merge branch 'liyunfan1223:master' into login-range
This commit is contained in:
26
README.md
26
README.md
@@ -2,6 +2,8 @@
|
|||||||
<a href="https://github.com/liyunfan1223/mod-playerbots/blob/master/README.md">English</a>
|
<a href="https://github.com/liyunfan1223/mod-playerbots/blob/master/README.md">English</a>
|
||||||
|
|
|
|
||||||
<a href="https://github.com/liyunfan1223/mod-playerbots/blob/master/README_CN.md">中文</a>
|
<a href="https://github.com/liyunfan1223/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>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
@@ -16,23 +18,25 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
# Playerbots Module
|
# Playerbots Module
|
||||||
`mod-playerbots` is an [AzerothCore](https://www.azerothcore.org/) module that adds player-like bots to a server. The project is based off [IKE3's Playerbots](https://github.com/ike3/mangosbot). Features include:
|
`mod-playerbots` is an [AzerothCore](https://www.azerothcore.org/) module that adds player-like bots to a server. The project is based off [IKE3's Playerbots](https://github.com/ike3/mangosbot) and requires a custom branch of AzerothCore to compile and run: [liyunfan1223/azerothcore-wotlk/tree/Playerbot](https://github.com/liyunfan1223/azerothcore-wotlk/tree/Playerbot).
|
||||||
|
|
||||||
- Bots that utilize real player data, allowing players to interact with their other characters, form parties, level up, and more;
|
Features include:
|
||||||
- Random bots that wander through the world and behave like players, simulating the MMO experience;
|
|
||||||
- Bots capable of running raids and battlegrounds;
|
- 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;
|
- Highly configurable settings to define how bots behave;
|
||||||
- Excellent performance, even when running thousands of bots.
|
- Excellent performance, even when running thousands of bots.
|
||||||
|
|
||||||
**This project is still under development**. If you encounter any errors or experience crashes, we kindly request that you [report them as GitHub issues](https://github.com/liyunfan1223/mod-playerbots/issues/new?template=bug_report.md). Your valuable feedback will help us improve this project collaboratively.
|
**This project is still under development**. If you encounter any errors or experience crashes, we kindly request that you [report them as GitHub issues](https://github.com/liyunfan1223/mod-playerbots/issues/new?template=bug_report.md). Your valuable feedback will help us improve this project collaboratively.
|
||||||
|
|
||||||
**Playerbots Module** has a **[Discord server](https://discord.gg/NQm5QShwf9)** where you can discuss the project.
|
`mod-playerbots` has a **[Discord server](https://discord.gg/NQm5QShwf9)** where you can discuss the project, ask questions, and get involved in the community!
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Classic Installation
|
### Classic Installation
|
||||||
|
|
||||||
`mod-playerbots` requires a custom branch of AzerothCore to work: [liyunfan1223/azerothcore-wotlk/tree/Playerbot](https://github.com/liyunfan1223/azerothcore-wotlk/tree/Playerbot). To install the module, simply run:
|
As noted above, `mod-playerbots` requires a custom branch of AzerothCore: [liyunfan1223/azerothcore-wotlk/tree/Playerbot](https://github.com/liyunfan1223/azerothcore-wotlk/tree/Playerbot). To install the module, simply run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/liyunfan1223/azerothcore-wotlk.git --branch=Playerbot
|
git clone https://github.com/liyunfan1223/azerothcore-wotlk.git --branch=Playerbot
|
||||||
@@ -81,21 +85,21 @@ Use `docker compose up -d --build` to build and run the server. For more informa
|
|||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
The [Playerbots Wiki](https://github.com/liyunfan1223/mod-playerbots/wiki) contains an extensive overview of addons, commands, and recommended configurations. Please note that documentation may be incomplete or out-of-date in some sections. Contributions are welcome.
|
The [Playerbots Wiki](https://github.com/liyunfan1223/mod-playerbots/wiki) contains an extensive overview of addons, commands, raids with programmed bot strategies, and recommended performance configurations. Please note that documentation may be incomplete or out-of-date in some sections. Contributions are welcome.
|
||||||
|
|
||||||
## Frequently Asked Questions
|
## Frequently Asked Questions
|
||||||
|
|
||||||
- **Why aren't my bots casting spells?** Please make sure that the necessary English DBC file (enUS) is present.
|
- **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.
|
- **What platforms are supported?** We support Ubuntu, Windows, and macOS. Other Linux distros may work, but will not receive support.
|
||||||
- **Why isn't my source compiling?** Please [check the build status of our CI](https://github.com/liyunfan1223/mod-playerbots/actions). If the latest build is failing, rever to the last successful commit until we address the issue.
|
- **Why isn't my source compiling?** Please ensure that you are compiling with the required [custom branch of AzerothCore](https://github.com/liyunfan1223/azerothcore-wotlk/tree/Playerbot). Additionally, please [check the build status of our CI](https://github.com/liyunfan1223/mod-playerbots/actions). If the latest build is failing, rever to the last successful commit until we address the issue.
|
||||||
|
|
||||||
## Addons
|
## Addons
|
||||||
|
|
||||||
Typically, bots are controlled via chat commands. For larger bot groups, this can be unwieldy. As an alternative, community members have developed client Add-Ons to allow controlling bots through the in-game UI. We recommend you check out their projects:
|
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:
|
||||||
|
|
||||||
- [Multibot](https://github.com/Macx-Lio/MultiBot) (by Macx-Lio)
|
- [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)
|
- [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)
|
- [Unbot Addon (en)](https://github.com/noisiver/unbot-addon/tree/english) (English version translated by @Revision) [note: no longer under active development]
|
||||||
|
|
||||||
## Acknowledgements
|
## Acknowledgements
|
||||||
|
|
||||||
|
|||||||
@@ -710,6 +710,16 @@ AiPlayerbot.AutoUpgradeEquip = 1
|
|||||||
# Default: 0 (disabled)
|
# Default: 0 (disabled)
|
||||||
AiPlayerbot.HunterWolfPet = 0
|
AiPlayerbot.HunterWolfPet = 0
|
||||||
|
|
||||||
|
# Default pet stance when a bot summons a pet
|
||||||
|
# 0 = Passive, 1 = Defensive, 2 = Aggressive
|
||||||
|
# Default: 1 (Defensive)
|
||||||
|
AiPlayerbot.DefaultPetStance = 1
|
||||||
|
|
||||||
|
# Enable/disable debug messages about pet commands
|
||||||
|
# 0 = Disabled, 1 = Enabled
|
||||||
|
# Default = 0 (disabled)
|
||||||
|
AiPlayerbot.PetChatCommandDebug = 0
|
||||||
|
|
||||||
# Prohibit hunter bots from creating pets with any family ID listed below in ExcludedHunterPetFamilies
|
# Prohibit hunter bots from creating pets with any family ID listed below in ExcludedHunterPetFamilies
|
||||||
# See the creature_family database table for all pet families by ID (note: ID for spiders is 3)
|
# See the creature_family database table for all pet families by ID (note: ID for spiders is 3)
|
||||||
AiPlayerbot.ExcludedHunterPetFamilies = ""
|
AiPlayerbot.ExcludedHunterPetFamilies = ""
|
||||||
|
|||||||
@@ -4214,6 +4214,19 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// only keep updating till initializing time has completed,
|
||||||
|
// which prevents unneeded expensive GameTime calls.
|
||||||
|
if (_isBotInitializing)
|
||||||
|
{
|
||||||
|
_isBotInitializing = GameTime::GetUptime().count() < sPlayerbotAIConfig->maxRandomBots * 0.11;
|
||||||
|
|
||||||
|
// no activity allowed during bot initialization
|
||||||
|
if (_isBotInitializing)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// General exceptions
|
// General exceptions
|
||||||
if (activityType == PACKET_ACTIVITY)
|
if (activityType == PACKET_ACTIVITY)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -611,6 +611,7 @@ private:
|
|||||||
Item* FindItemInInventory(std::function<bool(ItemTemplate const*)> checkItem) const;
|
Item* FindItemInInventory(std::function<bool(ItemTemplate const*)> checkItem) const;
|
||||||
void HandleCommands();
|
void HandleCommands();
|
||||||
void HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang = LANG_UNIVERSAL);
|
void HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang = LANG_UNIVERSAL);
|
||||||
|
bool _isBotInitializing = false;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Player* bot;
|
Player* bot;
|
||||||
|
|||||||
@@ -578,6 +578,8 @@ bool PlayerbotAIConfig::Initialize()
|
|||||||
autoPickTalents = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoPickTalents", true);
|
autoPickTalents = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoPickTalents", true);
|
||||||
autoUpgradeEquip = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoUpgradeEquip", false);
|
autoUpgradeEquip = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoUpgradeEquip", false);
|
||||||
hunterWolfPet = sConfigMgr->GetOption<int32>("AiPlayerbot.HunterWolfPet", 0);
|
hunterWolfPet = sConfigMgr->GetOption<int32>("AiPlayerbot.HunterWolfPet", 0);
|
||||||
|
defaultPetStance = sConfigMgr->GetOption<int32>("AiPlayerbot.DefaultPetStance", 1);
|
||||||
|
petChatCommandDebug = sConfigMgr->GetOption<bool>("AiPlayerbot.PetChatCommandDebug", 0);
|
||||||
autoLearnTrainerSpells = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoLearnTrainerSpells", true);
|
autoLearnTrainerSpells = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoLearnTrainerSpells", true);
|
||||||
autoLearnQuestSpells = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoLearnQuestSpells", false);
|
autoLearnQuestSpells = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoLearnQuestSpells", false);
|
||||||
autoTeleportForLevel = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoTeleportForLevel", false);
|
autoTeleportForLevel = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoTeleportForLevel", false);
|
||||||
|
|||||||
@@ -335,6 +335,8 @@ public:
|
|||||||
bool autoPickTalents;
|
bool autoPickTalents;
|
||||||
bool autoUpgradeEquip;
|
bool autoUpgradeEquip;
|
||||||
int32 hunterWolfPet;
|
int32 hunterWolfPet;
|
||||||
|
int32 defaultPetStance;
|
||||||
|
int32 petChatCommandDebug;
|
||||||
bool autoLearnTrainerSpells;
|
bool autoLearnTrainerSpells;
|
||||||
bool autoDoQuests;
|
bool autoDoQuests;
|
||||||
bool enableNewRpgStrategy;
|
bool enableNewRpgStrategy;
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ public:
|
|||||||
"|cffcccccchttps://github.com/liyunfan1223/mod-playerbots|r");
|
"|cffcccccchttps://github.com/liyunfan1223/mod-playerbots|r");
|
||||||
}
|
}
|
||||||
|
|
||||||
/*if (sPlayerbotAIConfig->enabled || sPlayerbotAIConfig->randomBotAutologin)
|
if (sPlayerbotAIConfig->enabled || sPlayerbotAIConfig->randomBotAutologin)
|
||||||
{
|
{
|
||||||
std::string roundedTime =
|
std::string roundedTime =
|
||||||
std::to_string(std::ceil((sPlayerbotAIConfig->maxRandomBots * 0.11 / 60) * 10) / 10.0);
|
std::to_string(std::ceil((sPlayerbotAIConfig->maxRandomBots * 0.11 / 60) * 10) / 10.0);
|
||||||
@@ -118,7 +118,7 @@ public:
|
|||||||
ChatHandler(player->GetSession()).SendSysMessage(
|
ChatHandler(player->GetSession()).SendSysMessage(
|
||||||
"|cff00ff00Playerbots:|r bot initialization at server startup takes about '"
|
"|cff00ff00Playerbots:|r bot initialization at server startup takes about '"
|
||||||
+ roundedTime + "' minutes.");
|
+ roundedTime + "' minutes.");
|
||||||
}*/
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -166,18 +166,27 @@ void PlayerbotFactory::Init()
|
|||||||
}
|
}
|
||||||
|
|
||||||
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(gemId);
|
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(gemId);
|
||||||
if (proto) {
|
if (!proto)
|
||||||
if (proto->ItemLevel < 60)
|
{
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (proto->Flags & ITEM_FLAG_UNIQUE_EQUIPPABLE)
|
if (proto->ItemLevel < 60)
|
||||||
continue;
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (proto->Flags & ITEM_FLAG_UNIQUE_EQUIPPABLE)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sRandomItemMgr->IsTestItem(gemId))
|
if (sRandomItemMgr->IsTestItem(gemId))
|
||||||
continue;
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!proto || !sGemPropertiesStore.LookupEntry(proto->GemProperties))
|
if (!sGemPropertiesStore.LookupEntry(proto->GemProperties))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -185,7 +194,6 @@ void PlayerbotFactory::Init()
|
|||||||
// LOG_INFO("playerbots", "Add {} to enchantment gems", gemId);
|
// LOG_INFO("playerbots", "Add {} to enchantment gems", gemId);
|
||||||
enchantGemIdCache.push_back(gemId);
|
enchantGemIdCache.push_back(gemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_INFO("playerbots", "Loading {} enchantment gems", enchantGemIdCache.size());
|
LOG_INFO("playerbots", "Loading {} enchantment gems", enchantGemIdCache.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1020,14 +1028,17 @@ void PlayerbotFactory::ClearSkills()
|
|||||||
}
|
}
|
||||||
bot->SetUInt32Value(PLAYER_SKILL_INDEX(0), 0);
|
bot->SetUInt32Value(PLAYER_SKILL_INDEX(0), 0);
|
||||||
bot->SetUInt32Value(PLAYER_SKILL_INDEX(1), 0);
|
bot->SetUInt32Value(PLAYER_SKILL_INDEX(1), 0);
|
||||||
|
|
||||||
// unlearn default race/class skills
|
// unlearn default race/class skills
|
||||||
PlayerInfo const* info = sObjectMgr->GetPlayerInfo(bot->getRace(), bot->getClass());
|
if (PlayerInfo const* info = sObjectMgr->GetPlayerInfo(bot->getRace(), bot->getClass()))
|
||||||
for (PlayerCreateInfoSkills::const_iterator itr = info->skills.begin(); itr != info->skills.end(); ++itr)
|
|
||||||
{
|
{
|
||||||
uint32 skillId = itr->SkillId;
|
for (PlayerCreateInfoSkills::const_iterator itr = info->skills.begin(); itr != info->skills.end(); ++itr)
|
||||||
if (!bot->HasSkill(skillId))
|
{
|
||||||
continue;
|
uint32 skillId = itr->SkillId;
|
||||||
bot->SetSkill(skillId, 0, 0, 0);
|
if (!bot->HasSkill(skillId))
|
||||||
|
continue;
|
||||||
|
bot->SetSkill(skillId, 0, 0, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ void Qualified::Qualify(int qual)
|
|||||||
qualifier = out.str();
|
qualifier = out.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string const Qualified::MultiQualify(std::vector<std::string> qualifiers, const std::string& separator, const std::string_view brackets)
|
std::string const Qualified::MultiQualify(const std::vector<std::string>& qualifiers, const std::string& separator, const std::string_view brackets)
|
||||||
{
|
{
|
||||||
std::stringstream out;
|
std::stringstream out;
|
||||||
for (uint8 i = 0; i < qualifiers.size(); i++)
|
for (uint8 i = 0; i < qualifiers.size(); ++i)
|
||||||
{
|
{
|
||||||
const std::string& qualifier = qualifiers[i];
|
const std::string& qualifier = qualifiers[i];
|
||||||
if (i == qualifiers.size() - 1)
|
if (i == qualifiers.size() - 1)
|
||||||
@@ -40,13 +40,13 @@ std::string const Qualified::MultiQualify(std::vector<std::string> qualifiers, c
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> Qualified::getMultiQualifiers(std::string const qualifier1)
|
std::vector<std::string> Qualified::getMultiQualifiers(const std::string& qualifier1)
|
||||||
{
|
{
|
||||||
std::istringstream iss(qualifier1);
|
std::istringstream iss(qualifier1);
|
||||||
return {std::istream_iterator<std::string>{iss}, std::istream_iterator<std::string>{}};
|
return {std::istream_iterator<std::string>{iss}, std::istream_iterator<std::string>{}};
|
||||||
}
|
}
|
||||||
|
|
||||||
int32 Qualified::getMultiQualifier(std::string const qualifier1, uint32 pos)
|
int32 Qualified::getMultiQualifier(const std::string& qualifier1, uint32 pos)
|
||||||
{
|
{
|
||||||
return std::stoi(getMultiQualifiers(qualifier1)[pos]);
|
return std::stoi(getMultiQualifiers(qualifier1)[pos]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
|
|
||||||
@@ -29,10 +30,10 @@ public:
|
|||||||
|
|
||||||
std::string const getQualifier() { return qualifier; }
|
std::string const getQualifier() { return qualifier; }
|
||||||
|
|
||||||
static std::string const MultiQualify(std::vector<std::string> qualifiers, const std::string& separator,
|
static std::string const MultiQualify(const std::vector<std::string>& qualifiers, const std::string& separator,
|
||||||
const std::string_view brackets = "{}");
|
const std::string_view brackets = "{}");
|
||||||
static std::vector<std::string> getMultiQualifiers(std::string const qualifier1);
|
static std::vector<std::string> getMultiQualifiers(const std::string& qualifier1);
|
||||||
static int32 getMultiQualifier(std::string const qualifier1, uint32 pos);
|
static int32 getMultiQualifier(const std::string& qualifier1, uint32 pos);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::string qualifier;
|
std::string qualifier;
|
||||||
@@ -42,11 +43,11 @@ template <class T>
|
|||||||
class NamedObjectFactory
|
class NamedObjectFactory
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
typedef T* (*ObjectCreator)(PlayerbotAI* botAI);
|
using ObjectCreator = std::function<T*(PlayerbotAI* ai)>;
|
||||||
std::unordered_map<std::string, ObjectCreator> creators;
|
std::unordered_map<std::string, ObjectCreator> creators;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
T* create(std::string name, PlayerbotAI* botAI)
|
virtual T* create(std::string name, PlayerbotAI* botAI)
|
||||||
{
|
{
|
||||||
size_t found = name.find("::");
|
size_t found = name.find("::");
|
||||||
std::string qualifier;
|
std::string qualifier;
|
||||||
@@ -59,11 +60,9 @@ public:
|
|||||||
if (creators.find(name) == creators.end())
|
if (creators.find(name) == creators.end())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
ObjectCreator creator = creators[name];
|
ObjectCreator& creator = creators[name];
|
||||||
if (!creator)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
T* object = (*creator)(botAI);
|
T* object = creator(botAI);
|
||||||
Qualified* q = dynamic_cast<Qualified*>(object);
|
Qualified* q = dynamic_cast<Qualified*>(object);
|
||||||
if (q && found != std::string::npos)
|
if (q && found != std::string::npos)
|
||||||
q->Qualify(qualifier);
|
q->Qualify(qualifier);
|
||||||
@@ -74,7 +73,7 @@ public:
|
|||||||
std::set<std::string> supports()
|
std::set<std::string> supports()
|
||||||
{
|
{
|
||||||
std::set<std::string> keys;
|
std::set<std::string> keys;
|
||||||
for (typename std::unordered_map<std::string, ObjectCreator>::iterator it = creators.begin();
|
for (typename std::unordered_map<std::string, ObjectCreator>::const_iterator it = creators.begin();
|
||||||
it != creators.end(); it++)
|
it != creators.end(); it++)
|
||||||
keys.insert(it->first);
|
keys.insert(it->first);
|
||||||
|
|
||||||
@@ -93,7 +92,7 @@ public:
|
|||||||
|
|
||||||
virtual ~NamedObjectContext() { Clear(); }
|
virtual ~NamedObjectContext() { Clear(); }
|
||||||
|
|
||||||
T* create(std::string const name, PlayerbotAI* botAI)
|
virtual T* create(std::string name, PlayerbotAI* botAI) override
|
||||||
{
|
{
|
||||||
if (created.find(name) == created.end())
|
if (created.find(name) == created.end())
|
||||||
return created[name] = NamedObjectFactory<T>::create(name, botAI);
|
return created[name] = NamedObjectFactory<T>::create(name, botAI);
|
||||||
@@ -103,7 +102,7 @@ public:
|
|||||||
|
|
||||||
void Clear()
|
void Clear()
|
||||||
{
|
{
|
||||||
for (typename std::unordered_map<std::string, T*>::iterator i = created.begin(); i != created.end(); i++)
|
for (typename std::unordered_map<std::string, T*>::const_iterator i = created.begin(); i != created.end(); i++)
|
||||||
{
|
{
|
||||||
if (i->second)
|
if (i->second)
|
||||||
delete i->second;
|
delete i->second;
|
||||||
@@ -134,13 +133,13 @@ template <class T>
|
|||||||
class SharedNamedObjectContextList
|
class SharedNamedObjectContextList
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
typedef T* (*ObjectCreator)(PlayerbotAI* botAI);
|
using ObjectCreator = std::function<T*(PlayerbotAI* ai)>;
|
||||||
std::unordered_map<std::string, ObjectCreator> creators;
|
std::unordered_map<std::string, ObjectCreator> creators;
|
||||||
std::vector<NamedObjectContext<T>*> contexts;
|
std::vector<NamedObjectContext<T>*> contexts;
|
||||||
|
|
||||||
~SharedNamedObjectContextList()
|
~SharedNamedObjectContextList()
|
||||||
{
|
{
|
||||||
for (typename std::vector<NamedObjectContext<T>*>::iterator i = contexts.begin(); i != contexts.end(); i++)
|
for (typename std::vector<NamedObjectContext<T>*>::const_iterator i = contexts.begin(); i != contexts.end(); i++)
|
||||||
delete *i;
|
delete *i;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +157,7 @@ template <class T>
|
|||||||
class NamedObjectContextList
|
class NamedObjectContextList
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
typedef T* (*ObjectCreator)(PlayerbotAI* botAI);
|
using ObjectCreator = std::function<T*(PlayerbotAI* ai)>;
|
||||||
const std::unordered_map<std::string, ObjectCreator>& creators;
|
const std::unordered_map<std::string, ObjectCreator>& creators;
|
||||||
const std::vector<NamedObjectContext<T>*>& contexts;
|
const std::vector<NamedObjectContext<T>*>& contexts;
|
||||||
std::unordered_map<std::string, T*> created;
|
std::unordered_map<std::string, T*> created;
|
||||||
@@ -170,7 +169,7 @@ public:
|
|||||||
|
|
||||||
~NamedObjectContextList()
|
~NamedObjectContextList()
|
||||||
{
|
{
|
||||||
for (typename std::unordered_map<std::string, T*>::iterator i = created.begin(); i != created.end(); i++)
|
for (typename std::unordered_map<std::string, T*>::const_iterator i = created.begin(); i != created.end(); i++)
|
||||||
{
|
{
|
||||||
if (i->second)
|
if (i->second)
|
||||||
delete i->second;
|
delete i->second;
|
||||||
@@ -192,11 +191,9 @@ public:
|
|||||||
if (creators.find(name) == creators.end())
|
if (creators.find(name) == creators.end())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
ObjectCreator creator = creators.at(name);
|
const ObjectCreator& creator = creators.at(name);
|
||||||
if (!creator)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
T* object = (*creator)(botAI);
|
T* object = creator(botAI);
|
||||||
Qualified* q = dynamic_cast<Qualified*>(object);
|
Qualified* q = dynamic_cast<Qualified*>(object);
|
||||||
if (q && found != std::string::npos)
|
if (q && found != std::string::npos)
|
||||||
q->Qualify(qualifier);
|
q->Qualify(qualifier);
|
||||||
@@ -204,7 +201,7 @@ public:
|
|||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
T* GetContextObject(std::string const name, PlayerbotAI* botAI)
|
T* GetContextObject(const std::string& name, PlayerbotAI* botAI)
|
||||||
{
|
{
|
||||||
if (created.find(name) == created.end())
|
if (created.find(name) == created.end())
|
||||||
{
|
{
|
||||||
@@ -214,7 +211,7 @@ public:
|
|||||||
return created[name];
|
return created[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
std::set<std::string> GetSiblings(std::string const name)
|
std::set<std::string> GetSiblings(const std::string& name)
|
||||||
{
|
{
|
||||||
for (auto i = contexts.begin(); i != contexts.end(); i++)
|
for (auto i = contexts.begin(); i != contexts.end(); i++)
|
||||||
{
|
{
|
||||||
@@ -240,7 +237,7 @@ public:
|
|||||||
{
|
{
|
||||||
std::set<std::string> supported = (*i)->supports();
|
std::set<std::string> supported = (*i)->supports();
|
||||||
|
|
||||||
for (std::set<std::string>::iterator j = supported.begin(); j != supported.end(); j++)
|
for (std::set<std::string>::const_iterator j = supported.begin(); j != supported.end(); ++j)
|
||||||
result.insert(*j);
|
result.insert(*j);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,7 +247,7 @@ public:
|
|||||||
std::set<std::string> GetCreated()
|
std::set<std::string> GetCreated()
|
||||||
{
|
{
|
||||||
std::set<std::string> result;
|
std::set<std::string> result;
|
||||||
for (typename std::unordered_map<std::string, T*>::iterator i = created.begin(); i != created.end(); i++)
|
for (typename std::unordered_map<std::string, T*>::const_iterator i = created.begin(); i != created.end(); i++)
|
||||||
{
|
{
|
||||||
result.insert(i->first);
|
result.insert(i->first);
|
||||||
}
|
}
|
||||||
@@ -263,13 +260,13 @@ template <class T>
|
|||||||
class NamedObjectFactoryList
|
class NamedObjectFactoryList
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
typedef T* (*ObjectCreator)(PlayerbotAI* botAI);
|
using ObjectCreator = std::function<T*(PlayerbotAI* ai)>;
|
||||||
std::vector<NamedObjectFactory<T>*> factories;
|
std::vector<NamedObjectFactory<T>*> factories;
|
||||||
std::unordered_map<std::string, ObjectCreator> creators;
|
std::unordered_map<std::string, ObjectCreator> creators;
|
||||||
|
|
||||||
virtual ~NamedObjectFactoryList()
|
virtual ~NamedObjectFactoryList()
|
||||||
{
|
{
|
||||||
for (typename std::vector<NamedObjectFactory<T>*>::iterator i = factories.begin(); i != factories.end(); i++)
|
for (typename std::vector<NamedObjectFactory<T>*>::const_iterator i = factories.begin(); i != factories.end(); i++)
|
||||||
delete *i;
|
delete *i;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,11 +283,9 @@ public:
|
|||||||
if (creators.find(name) == creators.end())
|
if (creators.find(name) == creators.end())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
ObjectCreator creator = creators[name];
|
const ObjectCreator& creator = creators[name];
|
||||||
if (!creator)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
T* object = (*creator)(botAI);
|
T* object = creator(botAI);
|
||||||
Qualified* q = dynamic_cast<Qualified*>(object);
|
Qualified* q = dynamic_cast<Qualified*>(object);
|
||||||
if (q && found != std::string::npos)
|
if (q && found != std::string::npos)
|
||||||
q->Qualify(qualifier);
|
q->Qualify(qualifier);
|
||||||
@@ -307,7 +302,7 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
T* GetContextObject(std::string const name, PlayerbotAI* botAI)
|
T* GetContextObject(const std::string& name, PlayerbotAI* botAI)
|
||||||
{
|
{
|
||||||
if (T* object = create(name, botAI))
|
if (T* object = create(name, botAI))
|
||||||
return object;
|
return object;
|
||||||
|
|||||||
@@ -247,6 +247,7 @@ public:
|
|||||||
|
|
||||||
creators["toggle pet spell"] = &ActionContext::toggle_pet_spell;
|
creators["toggle pet spell"] = &ActionContext::toggle_pet_spell;
|
||||||
creators["pet attack"] = &ActionContext::pet_attack;
|
creators["pet attack"] = &ActionContext::pet_attack;
|
||||||
|
creators["set pet stance"] = &ActionContext::set_pet_stance;
|
||||||
|
|
||||||
creators["new rpg status update"] = &ActionContext::new_rpg_status_update;
|
creators["new rpg status update"] = &ActionContext::new_rpg_status_update;
|
||||||
creators["new rpg go grind"] = &ActionContext::new_rpg_go_grind;
|
creators["new rpg go grind"] = &ActionContext::new_rpg_go_grind;
|
||||||
@@ -434,6 +435,7 @@ private:
|
|||||||
|
|
||||||
static Action* toggle_pet_spell(PlayerbotAI* ai) { return new TogglePetSpellAutoCastAction(ai); }
|
static Action* toggle_pet_spell(PlayerbotAI* ai) { return new TogglePetSpellAutoCastAction(ai); }
|
||||||
static Action* pet_attack(PlayerbotAI* ai) { return new PetAttackAction(ai); }
|
static Action* pet_attack(PlayerbotAI* ai) { return new PetAttackAction(ai); }
|
||||||
|
static Action* set_pet_stance(PlayerbotAI* ai) { return new SetPetStanceAction(ai); }
|
||||||
|
|
||||||
static Action* new_rpg_status_update(PlayerbotAI* ai) { return new NewRpgStatusUpdateAction(ai); }
|
static Action* new_rpg_status_update(PlayerbotAI* ai) { return new NewRpgStatusUpdateAction(ai); }
|
||||||
static Action* new_rpg_go_grind(PlayerbotAI* ai) { return new NewRpgGoGrindAction(ai); }
|
static Action* new_rpg_go_grind(PlayerbotAI* ai) { return new NewRpgGoGrindAction(ai); }
|
||||||
|
|||||||
@@ -79,9 +79,10 @@
|
|||||||
#include "OpenItemAction.h"
|
#include "OpenItemAction.h"
|
||||||
#include "UnlockItemAction.h"
|
#include "UnlockItemAction.h"
|
||||||
#include "UnlockTradedItemAction.h"
|
#include "UnlockTradedItemAction.h"
|
||||||
#include "PetAction.h"
|
#include "TameAction.h"
|
||||||
#include "TellGlyphsAction.h"
|
#include "TellGlyphsAction.h"
|
||||||
#include "EquipGlyphsAction.h"
|
#include "EquipGlyphsAction.h"
|
||||||
|
#include "PetAction.h"
|
||||||
|
|
||||||
class ChatActionContext : public NamedObjectContext<Action>
|
class ChatActionContext : public NamedObjectContext<Action>
|
||||||
{
|
{
|
||||||
@@ -191,9 +192,11 @@ public:
|
|||||||
creators["lfg"] = &ChatActionContext::lfg;
|
creators["lfg"] = &ChatActionContext::lfg;
|
||||||
creators["calc"] = &ChatActionContext::calc;
|
creators["calc"] = &ChatActionContext::calc;
|
||||||
creators["wipe"] = &ChatActionContext::wipe;
|
creators["wipe"] = &ChatActionContext::wipe;
|
||||||
creators["pet"] = &ChatActionContext::pet;
|
creators["tame"] = &ChatActionContext::tame;
|
||||||
creators["glyphs"] = &ChatActionContext::glyphs; // Added for custom Glyphs
|
creators["glyphs"] = &ChatActionContext::glyphs; // Added for custom Glyphs
|
||||||
creators["glyph equip"] = &ChatActionContext::glyph_equip; // Added for custom Glyphs
|
creators["glyph equip"] = &ChatActionContext::glyph_equip; // Added for custom Glyphs
|
||||||
|
creators["pet"] = &ChatActionContext::pet;
|
||||||
|
creators["pet attack"] = &ChatActionContext::pet_attack;
|
||||||
creators["roll"] = &ChatActionContext::roll_action;
|
creators["roll"] = &ChatActionContext::roll_action;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,9 +304,11 @@ private:
|
|||||||
static Action* join(PlayerbotAI* ai) { return new JoinGroupAction(ai); }
|
static Action* join(PlayerbotAI* ai) { return new JoinGroupAction(ai); }
|
||||||
static Action* calc(PlayerbotAI* ai) { return new TellCalculateItemAction(ai); }
|
static Action* calc(PlayerbotAI* ai) { return new TellCalculateItemAction(ai); }
|
||||||
static Action* wipe(PlayerbotAI* ai) { return new WipeAction(ai); }
|
static Action* wipe(PlayerbotAI* ai) { return new WipeAction(ai); }
|
||||||
static Action* pet(PlayerbotAI* botAI) { return new PetAction(botAI); }
|
static Action* tame(PlayerbotAI* botAI) { return new TameAction(botAI); }
|
||||||
static Action* glyphs(PlayerbotAI* botAI) { return new TellGlyphsAction(botAI); } // Added for custom Glyphs
|
static Action* glyphs(PlayerbotAI* botAI) { return new TellGlyphsAction(botAI); } // Added for custom Glyphs
|
||||||
static Action* glyph_equip(PlayerbotAI* ai) { return new EquipGlyphsAction(ai); } // Added for custom Glyphs
|
static Action* glyph_equip(PlayerbotAI* ai) { return new EquipGlyphsAction(ai); } // Added for custom Glyphs
|
||||||
|
static Action* pet(PlayerbotAI* botAI) { return new PetAction(botAI); }
|
||||||
|
static Action* pet_attack(PlayerbotAI* botAI) { return new PetAction(botAI, "attack"); }
|
||||||
static Action* roll_action(PlayerbotAI* botAI) { return new RollAction(botAI); }
|
static Action* roll_action(PlayerbotAI* botAI) { return new RollAction(botAI); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -36,10 +36,13 @@ bool FollowAction::Execute(Event event)
|
|||||||
true, priority, true);
|
true, priority, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Pet* pet = bot->GetPet())
|
// This section has been commented out because it was forcing the pet to
|
||||||
{
|
// follow the bot on every "follow" action tick, overriding any attack or
|
||||||
botAI->PetFollow();
|
// stay commands that might have been issued by the player.
|
||||||
}
|
// if (Pet* pet = bot->GetPet())
|
||||||
|
// {
|
||||||
|
// botAI->PetFollow();
|
||||||
|
// }
|
||||||
// if (moved)
|
// if (moved)
|
||||||
// botAI->SetNextCheckDelay(sPlayerbotAIConfig->reactDelay);
|
// botAI->SetNextCheckDelay(sPlayerbotAIConfig->reactDelay);
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,19 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "GenericActions.h"
|
#include "GenericActions.h"
|
||||||
|
#include "PlayerbotAI.h"
|
||||||
|
#include "Player.h"
|
||||||
|
#include "Pet.h"
|
||||||
|
#include "PlayerbotAIConfig.h"
|
||||||
#include "CreatureAI.h"
|
#include "CreatureAI.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
#include "CharmInfo.h"
|
||||||
|
#include "SharedDefines.h"
|
||||||
|
#include "ObjectGuid.h"
|
||||||
|
#include "SpellMgr.h"
|
||||||
|
#include "SpellInfo.h"
|
||||||
|
#include <vector>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
enum PetSpells
|
enum PetSpells
|
||||||
{
|
{
|
||||||
@@ -23,7 +33,8 @@ enum PetSpells
|
|||||||
PET_DEVOUR_MAGIC_4 = 19736,
|
PET_DEVOUR_MAGIC_4 = 19736,
|
||||||
PET_DEVOUR_MAGIC_5 = 27276,
|
PET_DEVOUR_MAGIC_5 = 27276,
|
||||||
PET_DEVOUR_MAGIC_6 = 27277,
|
PET_DEVOUR_MAGIC_6 = 27277,
|
||||||
PET_DEVOUR_MAGIC_7 = 48011
|
PET_DEVOUR_MAGIC_7 = 48011,
|
||||||
|
PET_SPIRIT_WOLF_LEAP = 58867
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::vector<uint32> disabledPetSpells = {
|
static std::vector<uint32> disabledPetSpells = {
|
||||||
@@ -31,7 +42,7 @@ static std::vector<uint32> disabledPetSpells = {
|
|||||||
PET_COWER, PET_LEAP,
|
PET_COWER, PET_LEAP,
|
||||||
PET_SPELL_LOCK_1, PET_SPELL_LOCK_2,
|
PET_SPELL_LOCK_1, PET_SPELL_LOCK_2,
|
||||||
PET_DEVOUR_MAGIC_1, PET_DEVOUR_MAGIC_2, PET_DEVOUR_MAGIC_3,
|
PET_DEVOUR_MAGIC_1, PET_DEVOUR_MAGIC_2, PET_DEVOUR_MAGIC_3,
|
||||||
PET_DEVOUR_MAGIC_4, PET_DEVOUR_MAGIC_5, PET_DEVOUR_MAGIC_6, PET_DEVOUR_MAGIC_7
|
PET_DEVOUR_MAGIC_4, PET_DEVOUR_MAGIC_5, PET_DEVOUR_MAGIC_6, PET_DEVOUR_MAGIC_7, PET_SPIRIT_WOLF_LEAP
|
||||||
};
|
};
|
||||||
|
|
||||||
bool MeleeAction::isUseful()
|
bool MeleeAction::isUseful()
|
||||||
@@ -100,6 +111,11 @@ bool TogglePetSpellAutoCastAction::Execute(Event event)
|
|||||||
toggled = true;
|
toggled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debug message if pet spells have been toggled and debug is enabled
|
||||||
|
if (toggled && sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||||
|
botAI->TellMaster("Pet autocast spells have been toggled.");
|
||||||
|
|
||||||
return toggled;
|
return toggled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,22 +123,23 @@ bool PetAttackAction::Execute(Event event)
|
|||||||
{
|
{
|
||||||
Guardian* pet = bot->GetGuardianPet();
|
Guardian* pet = bot->GetGuardianPet();
|
||||||
if (!pet)
|
if (!pet)
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
// Do not attack if the pet's stance is set to "passive".
|
||||||
|
if (pet->GetReactState() == REACT_PASSIVE)
|
||||||
|
return false;
|
||||||
|
|
||||||
Unit* target = AI_VALUE(Unit*, "current target");
|
Unit* target = AI_VALUE(Unit*, "current target");
|
||||||
if (!target)
|
if (!target)
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
if (!bot->IsValidAttackTarget(target))
|
if (!bot->IsValidAttackTarget(target))
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
pet->SetReactState(REACT_PASSIVE);
|
// This section has been commented because it was overriding the
|
||||||
|
// pet's stance to "passive" every time the attack action was executed.
|
||||||
|
// pet->SetReactState(REACT_PASSIVE);
|
||||||
|
|
||||||
pet->ClearUnitState(UNIT_STATE_FOLLOW);
|
pet->ClearUnitState(UNIT_STATE_FOLLOW);
|
||||||
pet->AttackStop();
|
pet->AttackStop();
|
||||||
pet->SetTarget(target->GetGUID());
|
pet->SetTarget(target->GetGUID());
|
||||||
@@ -136,3 +153,76 @@ bool PetAttackAction::Execute(Event event)
|
|||||||
pet->ToCreature()->AI()->AttackStart(target);
|
pet->ToCreature()->AI()->AttackStart(target);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SetPetStanceAction::Execute(Event /*event*/)
|
||||||
|
{
|
||||||
|
// Prepare a list to hold all controlled pet and guardian creatures
|
||||||
|
std::vector<Creature*> targets;
|
||||||
|
|
||||||
|
// Add the bot's main pet (if it exists) to the target list
|
||||||
|
Pet* pet = bot->GetPet();
|
||||||
|
if (pet)
|
||||||
|
targets.push_back(pet);
|
||||||
|
|
||||||
|
// Loop through all units controlled by the bot (could be pets, guardians, etc.)
|
||||||
|
for (Unit::ControlSet::const_iterator itr = bot->m_Controlled.begin(); itr != bot->m_Controlled.end(); ++itr)
|
||||||
|
{
|
||||||
|
// Only add creatures (skip players, vehicles, etc.)
|
||||||
|
Creature* creature = dynamic_cast<Creature*>(*itr);
|
||||||
|
if (!creature)
|
||||||
|
continue;
|
||||||
|
// Avoid adding the main pet twice
|
||||||
|
if (pet && creature == pet)
|
||||||
|
continue;
|
||||||
|
targets.push_back(creature);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are no controlled pets or guardians, notify the player and exit
|
||||||
|
if (targets.empty())
|
||||||
|
{
|
||||||
|
botAI->TellError("You have no pet or guardian pet.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the default pet stance from the configuration
|
||||||
|
int32 stance = sPlayerbotAIConfig->defaultPetStance;
|
||||||
|
ReactStates react = REACT_DEFENSIVE;
|
||||||
|
std::string stanceText = "defensive (from config, fallback)";
|
||||||
|
|
||||||
|
// Map the config stance integer to a ReactStates value and a message
|
||||||
|
switch (stance)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
react = REACT_PASSIVE;
|
||||||
|
stanceText = "passive (from config)";
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
react = REACT_DEFENSIVE;
|
||||||
|
stanceText = "defensive (from config)";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
react = REACT_AGGRESSIVE;
|
||||||
|
stanceText = "aggressive (from config)";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
react = REACT_DEFENSIVE;
|
||||||
|
stanceText = "defensive (from config, fallback)";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the stance to all target creatures (pets/guardians)
|
||||||
|
for (Creature* target : targets)
|
||||||
|
{
|
||||||
|
target->SetReactState(react);
|
||||||
|
CharmInfo* charmInfo = target->GetCharmInfo();
|
||||||
|
// If the creature has a CharmInfo, set the player-visible stance as well
|
||||||
|
if (charmInfo)
|
||||||
|
charmInfo->SetPlayerReactState(react);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If debug is enabled in config, inform the master of the new stance
|
||||||
|
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||||
|
botAI->TellMaster("Pet stance set to " + stanceText + " (applied to all pets/guardians).");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
#define _PLAYERBOT_GENERICACTIONS_H
|
#define _PLAYERBOT_GENERICACTIONS_H
|
||||||
|
|
||||||
#include "AttackAction.h"
|
#include "AttackAction.h"
|
||||||
|
#include "Action.h"
|
||||||
|
#include "PlayerbotAI.h"
|
||||||
|
|
||||||
class PlayerbotAI;
|
class PlayerbotAI;
|
||||||
|
|
||||||
@@ -33,4 +35,12 @@ public:
|
|||||||
virtual bool Execute(Event event) override;
|
virtual bool Execute(Event event) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class SetPetStanceAction : public Action
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SetPetStanceAction(PlayerbotAI* botAI) : Action(botAI, "set pet stance") {}
|
||||||
|
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ bool MoveToTravelTargetAction::Execute(Event event)
|
|||||||
WorldLocation location = *target->getPosition();
|
WorldLocation location = *target->getPosition();
|
||||||
|
|
||||||
Group* group = bot->GetGroup();
|
Group* group = bot->GetGroup();
|
||||||
if (group && !urand(0, 1) && bot == botAI->GetGroupMaster())
|
if (group && !urand(0, 1) && bot == botAI->GetGroupMaster() && !bot->IsInCombat())
|
||||||
{
|
{
|
||||||
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,373 +4,254 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "PetAction.h"
|
#include "PetAction.h"
|
||||||
#include <algorithm>
|
|
||||||
#include <iomanip>
|
#include "CharmInfo.h"
|
||||||
#include <sstream>
|
#include "Creature.h"
|
||||||
|
#include "CreatureAI.h"
|
||||||
#include "Pet.h"
|
#include "Pet.h"
|
||||||
#include "SpellMgr.h"
|
|
||||||
#include "DBCStructure.h"
|
|
||||||
#include "Log.h"
|
|
||||||
#include "ObjectMgr.h"
|
|
||||||
#include "Player.h"
|
#include "Player.h"
|
||||||
#include "PlayerbotAI.h"
|
#include "PlayerbotAI.h"
|
||||||
#include "PlayerbotFactory.h"
|
#include "SharedDefines.h"
|
||||||
#include <random>
|
|
||||||
#include <cctype>
|
|
||||||
#include "WorldSession.h"
|
|
||||||
|
|
||||||
bool IsExoticPet(const CreatureTemplate* creature)
|
|
||||||
{
|
|
||||||
// Use the IsExotic() method from CreatureTemplate
|
|
||||||
return creature && creature->IsExotic();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HasBeastMastery(Player* bot)
|
|
||||||
{
|
|
||||||
// Beast Mastery talent aura ID for WotLK is 53270
|
|
||||||
return bot->HasAura(53270);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PetAction::Execute(Event event)
|
bool PetAction::Execute(Event event)
|
||||||
{
|
{
|
||||||
|
// Extract the command parameter from the event (e.g., "aggressive", "defensive", "attack", etc.)
|
||||||
std::string param = event.getParam();
|
std::string param = event.getParam();
|
||||||
std::istringstream iss(param);
|
if (param.empty() && !defaultCmd.empty())
|
||||||
std::string mode, value;
|
{
|
||||||
iss >> mode;
|
param = defaultCmd;
|
||||||
std::getline(iss, value);
|
}
|
||||||
value.erase(0, value.find_first_not_of(" ")); // trim leading spaces
|
|
||||||
|
|
||||||
bool found = false;
|
if (param.empty())
|
||||||
|
|
||||||
// Reset lastPetName/Id each time
|
|
||||||
lastPetName = "";
|
|
||||||
lastPetId = 0;
|
|
||||||
|
|
||||||
if (mode == "name" && !value.empty())
|
|
||||||
{
|
{
|
||||||
found = SetPetByName(value);
|
// If no parameter is provided, show usage instructions and return.
|
||||||
}
|
botAI->TellError("Usage: pet <aggressive|defensive|passive|stance|attack|follow|stay>");
|
||||||
else if (mode == "id" && !value.empty())
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
uint32 id = std::stoul(value);
|
|
||||||
found = SetPetById(id);
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
botAI->TellError("Invalid pet id.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (mode == "family" && !value.empty())
|
|
||||||
{
|
|
||||||
found = SetPetByFamily(value);
|
|
||||||
}
|
|
||||||
else if (mode == "rename" && !value.empty())
|
|
||||||
{
|
|
||||||
found = RenamePet(value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
botAI->TellError("Usage: pet name <name> | pet id <id> | pet family <family> | pet rename <new name> ");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!found)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// For non-rename commands, initialize pet and give feedback
|
|
||||||
if (mode != "rename")
|
|
||||||
{
|
|
||||||
Player* bot = botAI->GetBot();
|
|
||||||
PlayerbotFactory factory(bot, bot->GetLevel());
|
|
||||||
factory.InitPet();
|
|
||||||
factory.InitPetTalents();
|
|
||||||
|
|
||||||
if (!lastPetName.empty() && lastPetId != 0)
|
|
||||||
{
|
|
||||||
std::ostringstream oss;
|
|
||||||
oss << "Pet changed to " << lastPetName << ", ID: " << lastPetId << ".";
|
|
||||||
botAI->TellMaster(oss.str());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
botAI->TellMaster("Pet changed and initialized!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PetAction::SetPetByName(const std::string& name)
|
|
||||||
{
|
|
||||||
// Convert the input to lowercase for case-insensitive comparison
|
|
||||||
std::string lowerName = name;
|
|
||||||
std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower);
|
|
||||||
|
|
||||||
CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates();
|
|
||||||
Player* bot = botAI->GetBot();
|
Player* bot = botAI->GetBot();
|
||||||
|
|
||||||
for (auto itr = creatures->begin(); itr != creatures->end(); ++itr)
|
// Collect all controlled pets and guardians, except totems, into the targets vector.
|
||||||
{
|
std::vector<Creature*> targets;
|
||||||
const CreatureTemplate& creature = itr->second;
|
Pet* pet = bot->GetPet();
|
||||||
std::string creatureName = creature.Name;
|
if (pet)
|
||||||
std::transform(creatureName.begin(), creatureName.end(), creatureName.begin(), ::tolower);
|
targets.push_back(pet);
|
||||||
|
|
||||||
// Only match if names match (case-insensitive)
|
for (Unit::ControlSet::const_iterator itr = bot->m_Controlled.begin(); itr != bot->m_Controlled.end(); ++itr)
|
||||||
if (creatureName == lowerName)
|
{
|
||||||
|
Creature* creature = dynamic_cast<Creature*>(*itr);
|
||||||
|
if (!creature)
|
||||||
|
continue;
|
||||||
|
if (pet && creature == pet)
|
||||||
|
continue;
|
||||||
|
if (creature->IsTotem())
|
||||||
|
continue;
|
||||||
|
targets.push_back(creature);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no pets or guardians are found, notify and return.
|
||||||
|
if (targets.empty())
|
||||||
|
{
|
||||||
|
botAI->TellError("You have no pet or guardian pet.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactStates react;
|
||||||
|
std::string stanceText;
|
||||||
|
|
||||||
|
// Handle stance commands: aggressive, defensive, or passive.
|
||||||
|
if (param == "aggressive")
|
||||||
|
{
|
||||||
|
react = REACT_AGGRESSIVE;
|
||||||
|
stanceText = "aggressive";
|
||||||
|
}
|
||||||
|
else if (param == "defensive")
|
||||||
|
{
|
||||||
|
react = REACT_DEFENSIVE;
|
||||||
|
stanceText = "defensive";
|
||||||
|
}
|
||||||
|
else if (param == "passive")
|
||||||
|
{
|
||||||
|
react = REACT_PASSIVE;
|
||||||
|
stanceText = "passive";
|
||||||
|
}
|
||||||
|
// The "stance" command simply reports the current stance of each pet/guardian.
|
||||||
|
else if (param == "stance")
|
||||||
|
{
|
||||||
|
for (Creature* target : targets)
|
||||||
{
|
{
|
||||||
// Check if the pet is tameable at all
|
std::string type = target->IsPet() ? "pet" : "guardian";
|
||||||
if (!creature.IsTameable(true))
|
std::string name = target->GetName();
|
||||||
|
std::string stance;
|
||||||
|
switch (target->GetReactState())
|
||||||
|
{
|
||||||
|
case REACT_AGGRESSIVE:
|
||||||
|
stance = "aggressive";
|
||||||
|
break;
|
||||||
|
case REACT_DEFENSIVE:
|
||||||
|
stance = "defensive";
|
||||||
|
break;
|
||||||
|
case REACT_PASSIVE:
|
||||||
|
stance = "passive";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
stance = "unknown";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
botAI->TellMaster("Current stance of " + type + " \"" + name + "\": " + stance + ".");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// The "attack" command forces pets/guardians to attack the master's selected target.
|
||||||
|
else if (param == "attack")
|
||||||
|
{
|
||||||
|
// Try to get the master's selected target.
|
||||||
|
Player* master = botAI->GetMaster();
|
||||||
|
Unit* targetUnit = nullptr;
|
||||||
|
|
||||||
|
if (master)
|
||||||
|
{
|
||||||
|
ObjectGuid masterTargetGuid = master->GetTarget();
|
||||||
|
if (!masterTargetGuid.IsEmpty())
|
||||||
|
{
|
||||||
|
targetUnit = botAI->GetUnit(masterTargetGuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no valid target is selected, show an error and return.
|
||||||
|
if (!targetUnit)
|
||||||
|
{
|
||||||
|
botAI->TellError("No valid target selected by master.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!targetUnit->IsAlive())
|
||||||
|
{
|
||||||
|
botAI->TellError("Target is not alive.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!bot->IsValidAttackTarget(targetUnit))
|
||||||
|
{
|
||||||
|
botAI->TellError("Target is not a valid attack target for the bot.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool didAttack = false;
|
||||||
|
// For each controlled pet/guardian, command them to attack the selected target.
|
||||||
|
for (Creature* petCreature : targets)
|
||||||
|
{
|
||||||
|
CharmInfo* charmInfo = petCreature->GetCharmInfo();
|
||||||
|
if (!charmInfo)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Exotic pet check with talent requirement
|
petCreature->ClearUnitState(UNIT_STATE_FOLLOW);
|
||||||
if (IsExoticPet(&creature) && !HasBeastMastery(bot))
|
// Only command attack if not already attacking the target, or if not currently under command attack.
|
||||||
|
if (petCreature->GetVictim() != targetUnit ||
|
||||||
|
(petCreature->GetVictim() == targetUnit && !charmInfo->IsCommandAttack()))
|
||||||
{
|
{
|
||||||
botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent.");
|
if (petCreature->GetVictim())
|
||||||
return false;
|
petCreature->AttackStop();
|
||||||
|
|
||||||
|
if (!petCreature->IsPlayer() && petCreature->ToCreature()->IsAIEnabled)
|
||||||
|
{
|
||||||
|
// For AI-enabled creatures (NPC pets/guardians): issue attack command and set flags.
|
||||||
|
charmInfo->SetIsCommandAttack(true);
|
||||||
|
charmInfo->SetIsAtStay(false);
|
||||||
|
charmInfo->SetIsFollowing(false);
|
||||||
|
charmInfo->SetIsCommandFollow(false);
|
||||||
|
charmInfo->SetIsReturning(false);
|
||||||
|
|
||||||
|
petCreature->ToCreature()->AI()->AttackStart(targetUnit);
|
||||||
|
|
||||||
|
didAttack = true;
|
||||||
|
}
|
||||||
|
else // For charmed player pets/guardians
|
||||||
|
{
|
||||||
|
if (petCreature->GetVictim() && petCreature->GetVictim() != targetUnit)
|
||||||
|
petCreature->AttackStop();
|
||||||
|
|
||||||
|
charmInfo->SetIsCommandAttack(true);
|
||||||
|
charmInfo->SetIsAtStay(false);
|
||||||
|
charmInfo->SetIsFollowing(false);
|
||||||
|
charmInfo->SetIsCommandFollow(false);
|
||||||
|
charmInfo->SetIsReturning(false);
|
||||||
|
|
||||||
|
petCreature->Attack(targetUnit, true);
|
||||||
|
didAttack = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Inform the master if the command succeeded or failed.
|
||||||
|
if (didAttack && sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||||
|
botAI->TellMaster("Pet commanded to attack your target.");
|
||||||
|
else if (!didAttack)
|
||||||
|
botAI->TellError("Pet did not attack. (Already attacking or unable to attack target)");
|
||||||
|
return didAttack;
|
||||||
|
}
|
||||||
|
// The "follow" command makes all pets/guardians follow the bot.
|
||||||
|
else if (param == "follow")
|
||||||
|
{
|
||||||
|
botAI->PetFollow();
|
||||||
|
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||||
|
botAI->TellMaster("Pet commanded to follow.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// The "stay" command causes all pets/guardians to stop and stay in place.
|
||||||
|
else if (param == "stay")
|
||||||
|
{
|
||||||
|
for (Creature* target : targets)
|
||||||
|
{
|
||||||
|
// If not already in controlled motion, stop movement and set to idle.
|
||||||
|
bool controlledMotion =
|
||||||
|
target->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE;
|
||||||
|
if (!controlledMotion)
|
||||||
|
{
|
||||||
|
target->StopMovingOnCurrentPos();
|
||||||
|
target->GetMotionMaster()->Clear(false);
|
||||||
|
target->GetMotionMaster()->MoveIdle();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Final tameable check based on hunter's actual ability
|
CharmInfo* charmInfo = target->GetCharmInfo();
|
||||||
if (!creature.IsTameable(bot->CanTameExoticPets()))
|
if (charmInfo)
|
||||||
continue;
|
{
|
||||||
|
// Set charm/pet state flags for "stay".
|
||||||
|
charmInfo->SetCommandState(COMMAND_STAY);
|
||||||
|
charmInfo->SetIsCommandAttack(false);
|
||||||
|
charmInfo->SetIsCommandFollow(false);
|
||||||
|
charmInfo->SetIsFollowing(false);
|
||||||
|
charmInfo->SetIsReturning(false);
|
||||||
|
charmInfo->SetIsAtStay(!controlledMotion);
|
||||||
|
charmInfo->SaveStayPosition(controlledMotion);
|
||||||
|
if (target->ToPet())
|
||||||
|
target->ToPet()->ClearCastWhenWillAvailable();
|
||||||
|
|
||||||
lastPetName = creature.Name;
|
charmInfo->SetForcedSpell(0);
|
||||||
lastPetId = creature.Entry;
|
charmInfo->SetForcedTargetGUID();
|
||||||
return CreateAndSetPet(creature.Entry);
|
}
|
||||||
}
|
}
|
||||||
|
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||||
|
botAI->TellMaster("Pet commanded to stay.");
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
// Unknown command: show usage instructions and return.
|
||||||
botAI->TellError("No tameable pet found with name: " + name);
|
else
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PetAction::SetPetById(uint32 id)
|
|
||||||
{
|
|
||||||
CreatureTemplate const* creature = sObjectMgr->GetCreatureTemplate(id);
|
|
||||||
Player* bot = botAI->GetBot();
|
|
||||||
|
|
||||||
if (creature)
|
|
||||||
{
|
{
|
||||||
// Check if the pet is tameable at all
|
botAI->TellError("Unknown pet command: " + param +
|
||||||
if (!creature->IsTameable(true))
|
". Use: pet <aggressive|defensive|passive|stance|attack|follow|stay>");
|
||||||
{
|
|
||||||
botAI->TellError("No tameable pet found with id: " + std::to_string(id));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exotic pet check with talent requirement
|
|
||||||
if (IsExoticPet(creature) && !HasBeastMastery(bot))
|
|
||||||
{
|
|
||||||
botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Final tameable check based on hunter's actual ability
|
|
||||||
if (!creature->IsTameable(bot->CanTameExoticPets()))
|
|
||||||
{
|
|
||||||
botAI->TellError("No tameable pet found with id: " + std::to_string(id));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastPetName = creature->Name;
|
|
||||||
lastPetId = creature->Entry;
|
|
||||||
return CreateAndSetPet(creature->Entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
botAI->TellError("No tameable pet found with id: " + std::to_string(id));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PetAction::SetPetByFamily(const std::string& family)
|
|
||||||
{
|
|
||||||
std::string lowerFamily = family;
|
|
||||||
std::transform(lowerFamily.begin(), lowerFamily.end(), lowerFamily.begin(), ::tolower);
|
|
||||||
|
|
||||||
CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates();
|
|
||||||
Player* bot = botAI->GetBot();
|
|
||||||
|
|
||||||
std::vector<const CreatureTemplate*> candidates;
|
|
||||||
bool foundExotic = false;
|
|
||||||
|
|
||||||
for (auto itr = creatures->begin(); itr != creatures->end(); ++itr)
|
|
||||||
{
|
|
||||||
const CreatureTemplate& creature = itr->second;
|
|
||||||
|
|
||||||
if (!creature.IsTameable(true)) // allow exotics for search
|
|
||||||
continue;
|
|
||||||
|
|
||||||
CreatureFamilyEntry const* familyEntry = sCreatureFamilyStore.LookupEntry(creature.family);
|
|
||||||
if (!familyEntry)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
std::string familyName = familyEntry->Name[0];
|
|
||||||
std::transform(familyName.begin(), familyName.end(), familyName.begin(), ::tolower);
|
|
||||||
|
|
||||||
if (familyName != lowerFamily)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Exotic/BM check
|
|
||||||
if (IsExoticPet(&creature))
|
|
||||||
{
|
|
||||||
foundExotic = true;
|
|
||||||
if (!HasBeastMastery(bot))
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!creature.IsTameable(bot->CanTameExoticPets()))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
candidates.push_back(&creature);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (candidates.empty())
|
|
||||||
{
|
|
||||||
if (foundExotic && !HasBeastMastery(bot))
|
|
||||||
botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent.");
|
|
||||||
else
|
|
||||||
botAI->TellError("No tameable pet found with family: " + family);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Randomly select one from candidates
|
// For stance commands, apply the chosen stance to all targets.
|
||||||
std::random_device rd;
|
for (Creature* target : targets)
|
||||||
std::mt19937 gen(rd());
|
|
||||||
std::uniform_int_distribution<> dis(0, candidates.size() - 1);
|
|
||||||
|
|
||||||
const CreatureTemplate* selected = candidates[dis(gen)];
|
|
||||||
|
|
||||||
lastPetName = selected->Name;
|
|
||||||
lastPetId = selected->Entry;
|
|
||||||
return CreateAndSetPet(selected->Entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PetAction::RenamePet(const std::string& newName)
|
|
||||||
{
|
|
||||||
Player* bot = botAI->GetBot();
|
|
||||||
Pet* pet = bot->GetPet();
|
|
||||||
if (!pet)
|
|
||||||
{
|
{
|
||||||
botAI->TellError("You have no pet to rename.");
|
target->SetReactState(react);
|
||||||
return false;
|
CharmInfo* charmInfo = target->GetCharmInfo();
|
||||||
|
if (charmInfo)
|
||||||
|
charmInfo->SetPlayerReactState(react);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Length check (WoW max pet name is 12 characters)
|
// Inform the master of the new stance if debug is enabled.
|
||||||
if (newName.empty() || newName.length() > 12)
|
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
|
||||||
{
|
botAI->TellMaster("Pet stance set to " + stanceText + ".");
|
||||||
botAI->TellError("Pet name must be between 1 and 12 alphabetic characters.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Alphabetic character check
|
|
||||||
for (char c : newName)
|
|
||||||
{
|
|
||||||
if (!std::isalpha(static_cast<unsigned char>(c)))
|
|
||||||
{
|
|
||||||
botAI->TellError("Pet name must only contain alphabetic characters (A-Z, a-z).");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normalize case: capitalize first letter, lower the rest
|
|
||||||
std::string normalized = newName;
|
|
||||||
normalized[0] = std::toupper(normalized[0]);
|
|
||||||
for (size_t i = 1; i < normalized.size(); ++i)
|
|
||||||
normalized[i] = std::tolower(normalized[i]);
|
|
||||||
|
|
||||||
// Forbidden name check
|
|
||||||
if (sObjectMgr->IsReservedName(normalized))
|
|
||||||
{
|
|
||||||
botAI->TellError("That pet name is forbidden. Please choose another name.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the pet's name, save to DB, and send instant client update
|
|
||||||
pet->SetName(normalized);
|
|
||||||
pet->SavePetToDB(PET_SAVE_AS_CURRENT);
|
|
||||||
bot->GetSession()->SendPetNameQuery(pet->GetGUID(), pet->GetEntry());
|
|
||||||
|
|
||||||
botAI->TellMaster("Your pet has been renamed to " + normalized + "!");
|
|
||||||
botAI->TellMaster("If you do not see the new name, please dismiss and recall your pet.");
|
|
||||||
|
|
||||||
// Dismiss pet
|
|
||||||
bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT, true);
|
|
||||||
// Recall pet using Hunter's Call Pet spell (spellId 883)
|
|
||||||
if (bot->getClass() == CLASS_HUNTER && bot->HasSpell(883))
|
|
||||||
{
|
|
||||||
bot->CastSpell(bot, 883, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PetAction::CreateAndSetPet(uint32 creatureEntry)
|
|
||||||
{
|
|
||||||
Player* bot = botAI->GetBot();
|
|
||||||
if (bot->getClass() != CLASS_HUNTER || bot->GetLevel() < 10)
|
|
||||||
{
|
|
||||||
botAI->TellError("Only level 10+ hunters can have pets.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
CreatureTemplate const* creature = sObjectMgr->GetCreatureTemplate(creatureEntry);
|
|
||||||
if (!creature)
|
|
||||||
{
|
|
||||||
botAI->TellError("Creature template not found.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove current pet(s)
|
|
||||||
if (bot->GetPetStable() && bot->GetPetStable()->CurrentPet)
|
|
||||||
{
|
|
||||||
bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT);
|
|
||||||
bot->RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT);
|
|
||||||
}
|
|
||||||
if (bot->GetPetStable() && bot->GetPetStable()->GetUnslottedHunterPet())
|
|
||||||
{
|
|
||||||
bot->GetPetStable()->UnslottedPets.clear();
|
|
||||||
bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT);
|
|
||||||
bot->RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Actually create the new pet
|
|
||||||
Pet* pet = bot->CreateTamedPetFrom(creatureEntry, 0);
|
|
||||||
if (!pet)
|
|
||||||
{
|
|
||||||
botAI->TellError("Failed to create pet.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set pet level and add to world
|
|
||||||
pet->SetUInt32Value(UNIT_FIELD_LEVEL, bot->GetLevel() - 1);
|
|
||||||
pet->GetMap()->AddToMap(pet->ToCreature());
|
|
||||||
pet->SetUInt32Value(UNIT_FIELD_LEVEL, bot->GetLevel());
|
|
||||||
bot->SetMinion(pet, true);
|
|
||||||
pet->InitTalentForLevel();
|
|
||||||
pet->SavePetToDB(PET_SAVE_AS_CURRENT);
|
|
||||||
bot->PetSpellInitialize();
|
|
||||||
|
|
||||||
// Set stats
|
|
||||||
pet->InitStatsForLevel(bot->GetLevel());
|
|
||||||
pet->SetLevel(bot->GetLevel());
|
|
||||||
pet->SetPower(POWER_HAPPINESS, pet->GetMaxPower(Powers(POWER_HAPPINESS)));
|
|
||||||
pet->SetHealth(pet->GetMaxHealth());
|
|
||||||
|
|
||||||
// Enable autocast for active spells
|
|
||||||
for (PetSpellMap::const_iterator itr = pet->m_spells.begin(); itr != pet->m_spells.end(); ++itr)
|
|
||||||
{
|
|
||||||
if (itr->second.state == PETSPELL_REMOVED)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr->first);
|
|
||||||
if (!spellInfo)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (spellInfo->IsPassive())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
pet->ToggleAutocast(spellInfo, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,25 +10,20 @@
|
|||||||
|
|
||||||
#include "Action.h"
|
#include "Action.h"
|
||||||
#include "PlayerbotFactory.h"
|
#include "PlayerbotFactory.h"
|
||||||
|
#include "Unit.h"
|
||||||
|
|
||||||
class PlayerbotAI;
|
class PlayerbotAI;
|
||||||
|
|
||||||
class PetAction : public Action
|
class PetAction : public Action
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PetAction(PlayerbotAI* botAI) : Action(botAI, "pet") {}
|
PetAction(PlayerbotAI* botAI, const std::string& defaultCmd = "") : Action(botAI, "pet"), defaultCmd(defaultCmd) {}
|
||||||
|
|
||||||
bool Execute(Event event) override;
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool SetPetByName(const std::string& name);
|
bool warningEnabled = true;
|
||||||
bool SetPetById(uint32 id);
|
std::string defaultCmd;
|
||||||
bool SetPetByFamily(const std::string& family);
|
|
||||||
bool RenamePet(const std::string& newName);
|
|
||||||
|
|
||||||
bool CreateAndSetPet(uint32 creatureEntry);
|
|
||||||
|
|
||||||
std::string lastPetName;
|
|
||||||
uint32 lastPetId = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
500
src/strategy/actions/TameAction.cpp
Normal file
500
src/strategy/actions/TameAction.cpp
Normal file
@@ -0,0 +1,500 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
|
||||||
|
* and/or modify it under version 2 of the License, or (at your option), any later version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "TameAction.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <random>
|
||||||
|
#include <set>
|
||||||
|
#include <sstream>
|
||||||
|
#include "DBCStructure.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "ObjectMgr.h"
|
||||||
|
#include "Pet.h"
|
||||||
|
#include "Player.h"
|
||||||
|
#include "PlayerbotAI.h"
|
||||||
|
#include "PlayerbotFactory.h"
|
||||||
|
#include "SpellMgr.h"
|
||||||
|
#include "WorldSession.h"
|
||||||
|
|
||||||
|
bool IsExoticPet(const CreatureTemplate* creature)
|
||||||
|
{
|
||||||
|
// Use the IsExotic() method from CreatureTemplate
|
||||||
|
return creature && creature->IsExotic();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasBeastMastery(Player* bot)
|
||||||
|
{
|
||||||
|
// Beast Mastery talent aura ID for WotLK is 53270
|
||||||
|
return bot->HasAura(53270);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TameAction::Execute(Event event)
|
||||||
|
{
|
||||||
|
// Parse the user's input command into mode and value (e.g. "name wolf", "id 1234", etc.)
|
||||||
|
std::string param = event.getParam();
|
||||||
|
std::istringstream iss(param);
|
||||||
|
std::string mode, value;
|
||||||
|
iss >> mode;
|
||||||
|
std::getline(iss, value);
|
||||||
|
value.erase(0, value.find_first_not_of(" ")); // Remove leading spaces from value
|
||||||
|
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
// Reset any previous pet name/id state
|
||||||
|
lastPetName = "";
|
||||||
|
lastPetId = 0;
|
||||||
|
|
||||||
|
// If the command is "family" with no value, list all available pet families
|
||||||
|
if (mode == "family" && value.empty())
|
||||||
|
{
|
||||||
|
std::set<std::string> normalFamilies;
|
||||||
|
std::set<std::string> exoticFamilies;
|
||||||
|
Player* bot = botAI->GetBot();
|
||||||
|
|
||||||
|
// Loop over all creature templates and collect tameable families
|
||||||
|
CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates();
|
||||||
|
for (auto itr = creatures->begin(); itr != creatures->end(); ++itr)
|
||||||
|
{
|
||||||
|
const CreatureTemplate& creature = itr->second;
|
||||||
|
if (!creature.IsTameable(true))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
CreatureFamilyEntry const* familyEntry = sCreatureFamilyStore.LookupEntry(creature.family);
|
||||||
|
if (!familyEntry)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::string familyName = familyEntry->Name[0];
|
||||||
|
if (familyName.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (creature.IsExotic())
|
||||||
|
exoticFamilies.insert(familyName);
|
||||||
|
else
|
||||||
|
normalFamilies.insert(familyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the output message for the user
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << "Available pet families: ";
|
||||||
|
size_t count = 0;
|
||||||
|
for (const auto& name : normalFamilies)
|
||||||
|
{
|
||||||
|
if (count++ != 0)
|
||||||
|
oss << ", ";
|
||||||
|
oss << name;
|
||||||
|
}
|
||||||
|
if (!exoticFamilies.empty())
|
||||||
|
{
|
||||||
|
if (!normalFamilies.empty())
|
||||||
|
oss << " | ";
|
||||||
|
oss << "Exotic: ";
|
||||||
|
count = 0;
|
||||||
|
for (const auto& name : exoticFamilies)
|
||||||
|
{
|
||||||
|
if (count++ != 0)
|
||||||
|
oss << ", ";
|
||||||
|
oss << name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
botAI->TellError(oss.str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle "tame abandon" command to give up your current pet
|
||||||
|
if (mode == "abandon")
|
||||||
|
{
|
||||||
|
return AbandonPet();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to process the command based on mode and value
|
||||||
|
if (mode == "name" && !value.empty())
|
||||||
|
{
|
||||||
|
found = SetPetByName(value);
|
||||||
|
}
|
||||||
|
else if (mode == "id" && !value.empty())
|
||||||
|
{
|
||||||
|
// Try to convert value to an integer and set pet by ID
|
||||||
|
try
|
||||||
|
{
|
||||||
|
uint32 id = std::stoul(value);
|
||||||
|
found = SetPetById(id);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
botAI->TellError("Invalid tame id.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mode == "family" && !value.empty())
|
||||||
|
{
|
||||||
|
found = SetPetByFamily(value);
|
||||||
|
}
|
||||||
|
else if (mode == "rename" && !value.empty())
|
||||||
|
{
|
||||||
|
found = RenamePet(value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Unrecognized command or missing argument; show usage
|
||||||
|
botAI->TellError(
|
||||||
|
"Usage: tame name <name> | tame id <id> | tame family <family> | tame rename <new name> | tame abandon");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the requested tame/rename failed, return failure
|
||||||
|
if (!found)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// For all non-rename commands, initialize the new pet and talents, then notify the master
|
||||||
|
if (mode != "rename")
|
||||||
|
{
|
||||||
|
Player* bot = botAI->GetBot();
|
||||||
|
PlayerbotFactory factory(bot, bot->GetLevel());
|
||||||
|
factory.InitPet();
|
||||||
|
factory.InitPetTalents();
|
||||||
|
|
||||||
|
if (!lastPetName.empty() && lastPetId != 0)
|
||||||
|
{
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << "Pet changed to " << lastPetName << ", ID: " << lastPetId << ".";
|
||||||
|
botAI->TellMaster(oss.str());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
botAI->TellMaster("Pet changed and initialized!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TameAction::SetPetByName(const std::string& name)
|
||||||
|
{
|
||||||
|
// Make a lowercase copy of the input name for case-insensitive comparison
|
||||||
|
std::string lowerName = name;
|
||||||
|
std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower);
|
||||||
|
|
||||||
|
// Get the full list of creature templates from the object manager
|
||||||
|
CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates();
|
||||||
|
Player* bot = botAI->GetBot();
|
||||||
|
|
||||||
|
// Iterate through all creature templates
|
||||||
|
for (auto itr = creatures->begin(); itr != creatures->end(); ++itr)
|
||||||
|
{
|
||||||
|
const CreatureTemplate& creature = itr->second;
|
||||||
|
std::string creatureName = creature.Name;
|
||||||
|
// Convert creature's name to lowercase for comparison
|
||||||
|
std::transform(creatureName.begin(), creatureName.end(), creatureName.begin(), ::tolower);
|
||||||
|
|
||||||
|
// If the input name matches this creature's name
|
||||||
|
if (creatureName == lowerName)
|
||||||
|
{
|
||||||
|
// Skip if the creature isn't tameable at all
|
||||||
|
if (!creature.IsTameable(true))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// If the creature is exotic and the bot doesn't have Beast Mastery, show error and fail
|
||||||
|
if (IsExoticPet(&creature) && !HasBeastMastery(bot))
|
||||||
|
{
|
||||||
|
botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if the creature isn't tameable by this bot (respecting exotic pet rules)
|
||||||
|
if (!creature.IsTameable(bot->CanTameExoticPets()))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Store the found pet's name and entry ID for later use/feedback
|
||||||
|
lastPetName = creature.Name;
|
||||||
|
lastPetId = creature.Entry;
|
||||||
|
// Create and set this pet for the bot
|
||||||
|
return CreateAndSetPet(creature.Entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no suitable pet found, show an error and return failure
|
||||||
|
botAI->TellError("No tameable pet found with name: " + name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TameAction::SetPetById(uint32 id)
|
||||||
|
{
|
||||||
|
// Look up the creature template by its numeric entry/id
|
||||||
|
CreatureTemplate const* creature = sObjectMgr->GetCreatureTemplate(id);
|
||||||
|
Player* bot = botAI->GetBot();
|
||||||
|
|
||||||
|
// Proceed only if a valid creature was found
|
||||||
|
if (creature)
|
||||||
|
{
|
||||||
|
// Check if this creature is ever tameable (ignore bot's own restrictions for now)
|
||||||
|
if (!creature->IsTameable(true))
|
||||||
|
{
|
||||||
|
// If not tameable at all, show an error and fail
|
||||||
|
botAI->TellError("No tameable pet found with id: " + std::to_string(id));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's an exotic pet, make sure the bot has the Beast Mastery talent
|
||||||
|
if (IsExoticPet(creature) && !HasBeastMastery(bot))
|
||||||
|
{
|
||||||
|
botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the bot is actually allowed to tame this pet (honoring exotic pet rules)
|
||||||
|
if (!creature->IsTameable(bot->CanTameExoticPets()))
|
||||||
|
{
|
||||||
|
botAI->TellError("No tameable pet found with id: " + std::to_string(id));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remember this pet's name and id for later feedback
|
||||||
|
lastPetName = creature->Name;
|
||||||
|
lastPetId = creature->Entry;
|
||||||
|
// Set and create the pet for the bot
|
||||||
|
return CreateAndSetPet(creature->Entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no valid creature was found by id, show an error
|
||||||
|
botAI->TellError("No tameable pet found with id: " + std::to_string(id));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TameAction::SetPetByFamily(const std::string& family)
|
||||||
|
{
|
||||||
|
// Convert the input family name to lowercase for case-insensitive comparison
|
||||||
|
std::string lowerFamily = family;
|
||||||
|
std::transform(lowerFamily.begin(), lowerFamily.end(), lowerFamily.begin(), ::tolower);
|
||||||
|
|
||||||
|
// Get all creature templates from the object manager
|
||||||
|
CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates();
|
||||||
|
Player* bot = botAI->GetBot();
|
||||||
|
|
||||||
|
// Prepare a list of candidate creatures and track if any exotic pet is found
|
||||||
|
std::vector<const CreatureTemplate*> candidates;
|
||||||
|
bool foundExotic = false;
|
||||||
|
|
||||||
|
// Iterate through all creature templates
|
||||||
|
for (auto itr = creatures->begin(); itr != creatures->end(); ++itr)
|
||||||
|
{
|
||||||
|
const CreatureTemplate& creature = itr->second;
|
||||||
|
|
||||||
|
// Skip if this creature is never tameable
|
||||||
|
if (!creature.IsTameable(true))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Look up the family entry for this creature
|
||||||
|
CreatureFamilyEntry const* familyEntry = sCreatureFamilyStore.LookupEntry(creature.family);
|
||||||
|
if (!familyEntry)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Compare the family name in a case-insensitive way
|
||||||
|
std::string familyName = familyEntry->Name[0];
|
||||||
|
std::transform(familyName.begin(), familyName.end(), familyName.begin(), ::tolower);
|
||||||
|
|
||||||
|
if (familyName != lowerFamily)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// If the creature is exotic, check Beast Mastery talent requirements
|
||||||
|
if (IsExoticPet(&creature))
|
||||||
|
{
|
||||||
|
foundExotic = true;
|
||||||
|
if (!HasBeastMastery(bot))
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only add as candidate if this bot is allowed to tame it (including exotic rules)
|
||||||
|
if (!creature.IsTameable(bot->CanTameExoticPets()))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
candidates.push_back(&creature);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no candidates found, inform the user of the reason and return false
|
||||||
|
if (candidates.empty())
|
||||||
|
{
|
||||||
|
if (foundExotic && !HasBeastMastery(bot))
|
||||||
|
botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent.");
|
||||||
|
else
|
||||||
|
botAI->TellError("No tameable pet found with family: " + family);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Randomly select one candidate from the list to tame
|
||||||
|
std::random_device rd;
|
||||||
|
std::mt19937 gen(rd());
|
||||||
|
std::uniform_int_distribution<> dis(0, candidates.size() - 1);
|
||||||
|
|
||||||
|
const CreatureTemplate* selected = candidates[dis(gen)];
|
||||||
|
|
||||||
|
// Save the selected pet's name and id for feedback
|
||||||
|
lastPetName = selected->Name;
|
||||||
|
lastPetId = selected->Entry;
|
||||||
|
// Attempt to create and set the new pet for the bot
|
||||||
|
return CreateAndSetPet(selected->Entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TameAction::RenamePet(const std::string& newName)
|
||||||
|
{
|
||||||
|
Player* bot = botAI->GetBot();
|
||||||
|
Pet* pet = bot->GetPet();
|
||||||
|
// Check if the bot currently has a pet
|
||||||
|
if (!pet)
|
||||||
|
{
|
||||||
|
botAI->TellError("You have no pet to rename.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the new name: must not be empty and max 12 characters
|
||||||
|
if (newName.empty() || newName.length() > 12)
|
||||||
|
{
|
||||||
|
botAI->TellError("Pet name must be between 1 and 12 alphabetic characters.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure all characters in the new name are alphabetic
|
||||||
|
for (char c : newName)
|
||||||
|
{
|
||||||
|
if (!std::isalpha(static_cast<unsigned char>(c)))
|
||||||
|
{
|
||||||
|
botAI->TellError("Pet name must only contain alphabetic characters (A-Z, a-z).");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize the name: capitalize the first letter, lowercase the rest
|
||||||
|
std::string normalized = newName;
|
||||||
|
normalized[0] = std::toupper(normalized[0]);
|
||||||
|
for (size_t i = 1; i < normalized.size(); ++i)
|
||||||
|
normalized[i] = std::tolower(normalized[i]);
|
||||||
|
|
||||||
|
// Check if the new name is reserved or forbidden
|
||||||
|
if (sObjectMgr->IsReservedName(normalized))
|
||||||
|
{
|
||||||
|
botAI->TellError("That pet name is forbidden. Please choose another name.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the pet's name and save it to the database
|
||||||
|
pet->SetName(normalized);
|
||||||
|
pet->SavePetToDB(PET_SAVE_AS_CURRENT);
|
||||||
|
bot->GetSession()->SendPetNameQuery(pet->GetGUID(), pet->GetEntry());
|
||||||
|
|
||||||
|
// Notify the master about the rename and give a tip to update the client name display
|
||||||
|
botAI->TellMaster("Your pet has been renamed to " + normalized + "!");
|
||||||
|
botAI->TellMaster("If you do not see the new name, please dismiss and recall your pet.");
|
||||||
|
|
||||||
|
// Remove the current pet and (re-)cast Call Pet spell if the bot is a hunter
|
||||||
|
bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT, true);
|
||||||
|
if (bot->getClass() == CLASS_HUNTER && bot->HasSpell(883))
|
||||||
|
{
|
||||||
|
bot->CastSpell(bot, 883, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TameAction::CreateAndSetPet(uint32 creatureEntry)
|
||||||
|
{
|
||||||
|
Player* bot = botAI->GetBot();
|
||||||
|
// Ensure the player is a hunter and at least level 10 (required for pets)
|
||||||
|
if (bot->getClass() != CLASS_HUNTER || bot->GetLevel() < 10)
|
||||||
|
{
|
||||||
|
botAI->TellError("Only level 10+ hunters can have pets.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the creature template for the given entry (pet species info)
|
||||||
|
CreatureTemplate const* creature = sObjectMgr->GetCreatureTemplate(creatureEntry);
|
||||||
|
if (!creature)
|
||||||
|
{
|
||||||
|
botAI->TellError("Creature template not found.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the bot already has a current pet or an unslotted pet, remove them to avoid conflicts
|
||||||
|
if (bot->GetPetStable() && bot->GetPetStable()->CurrentPet)
|
||||||
|
{
|
||||||
|
bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT);
|
||||||
|
bot->RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT);
|
||||||
|
}
|
||||||
|
if (bot->GetPetStable() && bot->GetPetStable()->GetUnslottedHunterPet())
|
||||||
|
{
|
||||||
|
bot->GetPetStable()->UnslottedPets.clear();
|
||||||
|
bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT);
|
||||||
|
bot->RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the new tamed pet from the specified creature entry
|
||||||
|
Pet* pet = bot->CreateTamedPetFrom(creatureEntry, 0);
|
||||||
|
if (!pet)
|
||||||
|
{
|
||||||
|
botAI->TellError("Failed to create pet.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the pet's level to one below the bot's current level, then add to the map and set to full level
|
||||||
|
pet->SetUInt32Value(UNIT_FIELD_LEVEL, bot->GetLevel() - 1);
|
||||||
|
pet->GetMap()->AddToMap(pet->ToCreature());
|
||||||
|
pet->SetUInt32Value(UNIT_FIELD_LEVEL, bot->GetLevel());
|
||||||
|
// Set the pet as the bot's active minion
|
||||||
|
bot->SetMinion(pet, true);
|
||||||
|
// Initialize talents appropriate for the pet's level
|
||||||
|
pet->InitTalentForLevel();
|
||||||
|
// Save pet to the database as the current pet
|
||||||
|
pet->SavePetToDB(PET_SAVE_AS_CURRENT);
|
||||||
|
// Initialize available pet spells
|
||||||
|
bot->PetSpellInitialize();
|
||||||
|
|
||||||
|
// Further initialize pet stats to match the bot's level
|
||||||
|
pet->InitStatsForLevel(bot->GetLevel());
|
||||||
|
pet->SetLevel(bot->GetLevel());
|
||||||
|
// Set happiness and health of the pet to maximum values
|
||||||
|
pet->SetPower(POWER_HAPPINESS, pet->GetMaxPower(Powers(POWER_HAPPINESS)));
|
||||||
|
pet->SetHealth(pet->GetMaxHealth());
|
||||||
|
|
||||||
|
// Enable autocast for all active (not removed) non-passive spells the pet knows
|
||||||
|
for (PetSpellMap::const_iterator itr = pet->m_spells.begin(); itr != pet->m_spells.end(); ++itr)
|
||||||
|
{
|
||||||
|
if (itr->second.state == PETSPELL_REMOVED)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr->first);
|
||||||
|
if (!spellInfo)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (spellInfo->IsPassive())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
pet->ToggleAutocast(spellInfo, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TameAction::AbandonPet()
|
||||||
|
{
|
||||||
|
// Get the bot player and its current pet (if any)
|
||||||
|
Player* bot = botAI->GetBot();
|
||||||
|
Pet* pet = bot->GetPet();
|
||||||
|
|
||||||
|
// Check if the bot has a pet and that it is a hunter pet
|
||||||
|
if (pet && pet->getPetType() == HUNTER_PET)
|
||||||
|
{
|
||||||
|
// Remove the pet from the bot and mark it as deleted in the database
|
||||||
|
bot->RemovePet(pet, PET_SAVE_AS_DELETED);
|
||||||
|
// Inform the bot's master/player that the pet was abandoned
|
||||||
|
botAI->TellMaster("Your pet has been abandoned.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If there is no hunter pet, show an error message
|
||||||
|
botAI->TellError("You have no hunter pet to abandon.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/strategy/actions/TameAction.h
Normal file
34
src/strategy/actions/TameAction.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
|
||||||
|
* and/or modify it under version 2 of the License, or (at your option), any later version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PLAYERBOT_TAMEACTION_H
|
||||||
|
#define _PLAYERBOT_TAMEACTION_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include "Action.h"
|
||||||
|
#include "PlayerbotFactory.h"
|
||||||
|
|
||||||
|
class PlayerbotAI;
|
||||||
|
|
||||||
|
class TameAction : public Action
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TameAction(PlayerbotAI* botAI) : Action(botAI, "tame") {}
|
||||||
|
|
||||||
|
bool Execute(Event event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool SetPetByName(const std::string& name);
|
||||||
|
bool SetPetById(uint32 id);
|
||||||
|
bool SetPetByFamily(const std::string& family);
|
||||||
|
bool RenamePet(const std::string& newName);
|
||||||
|
bool CreateAndSetPet(uint32 creatureEntry);
|
||||||
|
bool AbandonPet();
|
||||||
|
|
||||||
|
std::string lastPetName;
|
||||||
|
uint32 lastPetId = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -41,6 +41,7 @@
|
|||||||
#include "UseMeetingStoneAction.h"
|
#include "UseMeetingStoneAction.h"
|
||||||
#include "NamedObjectContext.h"
|
#include "NamedObjectContext.h"
|
||||||
#include "ReleaseSpiritAction.h"
|
#include "ReleaseSpiritAction.h"
|
||||||
|
#include "PetAction.h"
|
||||||
|
|
||||||
class PlayerbotAI;
|
class PlayerbotAI;
|
||||||
|
|
||||||
@@ -70,6 +71,7 @@ public:
|
|||||||
creators["trade status extended"] = &WorldPacketActionContext::trade_status_extended;
|
creators["trade status extended"] = &WorldPacketActionContext::trade_status_extended;
|
||||||
creators["store loot"] = &WorldPacketActionContext::store_loot;
|
creators["store loot"] = &WorldPacketActionContext::store_loot;
|
||||||
creators["self resurrect"] = &WorldPacketActionContext::self_resurrect;
|
creators["self resurrect"] = &WorldPacketActionContext::self_resurrect;
|
||||||
|
creators["pet"] = &WorldPacketActionContext::pet;
|
||||||
|
|
||||||
// quest
|
// quest
|
||||||
creators["talk to quest giver"] = &WorldPacketActionContext::turn_in_quest;
|
creators["talk to quest giver"] = &WorldPacketActionContext::turn_in_quest;
|
||||||
@@ -139,6 +141,7 @@ private:
|
|||||||
static Action* tell_not_enough_reputation(PlayerbotAI* botAI) { return new TellMasterAction(botAI, "Not enough reputation"); }
|
static Action* tell_not_enough_reputation(PlayerbotAI* botAI) { return new TellMasterAction(botAI, "Not enough reputation"); }
|
||||||
static Action* tell_cannot_equip(PlayerbotAI* botAI) { return new InventoryChangeFailureAction(botAI); }
|
static Action* tell_cannot_equip(PlayerbotAI* botAI) { return new InventoryChangeFailureAction(botAI); }
|
||||||
static Action* self_resurrect(PlayerbotAI* botAI) { return new SelfResurrectAction(botAI); }
|
static Action* self_resurrect(PlayerbotAI* botAI) { return new SelfResurrectAction(botAI); }
|
||||||
|
static Action* pet(PlayerbotAI* botAI) { return new PetAction(botAI); }
|
||||||
|
|
||||||
// quest
|
// quest
|
||||||
static Action* quest_update_add_kill(PlayerbotAI* ai) { return new QuestUpdateAddKillAction(ai); }
|
static Action* quest_update_add_kill(PlayerbotAI* ai) { return new QuestUpdateAddKillAction(ai); }
|
||||||
|
|||||||
@@ -50,7 +50,9 @@ void GenericDKNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
|
|||||||
triggers.push_back(
|
triggers.push_back(
|
||||||
new TriggerNode("bone shield", NextAction::array(0, new NextAction("bone shield", 21.0f), nullptr)));
|
new TriggerNode("bone shield", NextAction::array(0, new NextAction("bone shield", 21.0f), nullptr)));
|
||||||
triggers.push_back(
|
triggers.push_back(
|
||||||
new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 11.0f), NULL)));
|
new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), NULL)));
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), NULL)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void DKBuffDpsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
void DKBuffDpsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||||
|
|||||||
@@ -171,6 +171,10 @@ void GenericDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|||||||
// NextAction::array(0, new NextAction("anti magic zone", ACTION_EMERGENCY + 1), nullptr)));
|
// NextAction::array(0, new NextAction("anti magic zone", ACTION_EMERGENCY + 1), nullptr)));
|
||||||
triggers.push_back(
|
triggers.push_back(
|
||||||
new TriggerNode("no pet", NextAction::array(0, new NextAction("raise dead", ACTION_NORMAL + 5), nullptr)));
|
new TriggerNode("no pet", NextAction::array(0, new NextAction("raise dead", ACTION_NORMAL + 5), nullptr)));
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), nullptr)));
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), nullptr)));
|
||||||
triggers.push_back(
|
triggers.push_back(
|
||||||
new TriggerNode("mind freeze", NextAction::array(0, new NextAction("mind freeze", ACTION_HIGH + 1), nullptr)));
|
new TriggerNode("mind freeze", NextAction::array(0, new NextAction("mind freeze", ACTION_HIGH + 1), nullptr)));
|
||||||
triggers.push_back(
|
triggers.push_back(
|
||||||
|
|||||||
@@ -119,6 +119,42 @@ void GenericDruidNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& trig
|
|||||||
// triggers.push_back(new TriggerNode("low mana", NextAction::array(0, new NextAction("innervate", ACTION_EMERGENCY
|
// triggers.push_back(new TriggerNode("low mana", NextAction::array(0, new NextAction("innervate", ACTION_EMERGENCY
|
||||||
// + 5), nullptr))); triggers.push_back(new TriggerNode("swimming", NextAction::array(0, new NextAction("aquatic
|
// + 5), nullptr))); triggers.push_back(new TriggerNode("swimming", NextAction::array(0, new NextAction("aquatic
|
||||||
// form", 1.0f), nullptr)));
|
// form", 1.0f), nullptr)));
|
||||||
|
|
||||||
|
triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("apply oil", 1.0f), nullptr)));
|
||||||
|
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode("party member critical health",
|
||||||
|
NextAction::array(0,
|
||||||
|
new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 7),
|
||||||
|
new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 6),
|
||||||
|
new NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 5),
|
||||||
|
nullptr)));
|
||||||
|
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode("party member low health",
|
||||||
|
NextAction::array(0,
|
||||||
|
new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 5),
|
||||||
|
new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 4),
|
||||||
|
new NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 3),
|
||||||
|
nullptr)));
|
||||||
|
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode("party member medium health",
|
||||||
|
NextAction::array(0, new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 3),
|
||||||
|
new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 2),
|
||||||
|
new NextAction("rejuvenation on party", ACTION_MEDIUM_HEAL + 1),
|
||||||
|
nullptr)));
|
||||||
|
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode("party member almost full health",
|
||||||
|
NextAction::array(0, new NextAction("wild growth on party", ACTION_LIGHT_HEAL + 3), new NextAction("rejuvenation on party", ACTION_LIGHT_HEAL + 2), NULL)));
|
||||||
|
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode("party member remove curse",
|
||||||
|
NextAction::array(0, new NextAction("remove curse on party", ACTION_DISPEL + 7), nullptr)));
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), nullptr)));
|
||||||
|
|
||||||
triggers.push_back(new TriggerNode("party member critical health", NextAction::array(0,
|
triggers.push_back(new TriggerNode("party member critical health", NextAction::array(0,
|
||||||
new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 7),
|
new NextAction("wild growth on party", ACTION_MEDIUM_HEAL + 7),
|
||||||
new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 6),
|
new NextAction("regrowth on party", ACTION_MEDIUM_HEAL + 6),
|
||||||
@@ -147,6 +183,7 @@ void GenericDruidNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& trig
|
|||||||
triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("apply oil", 1.0f), nullptr)));
|
triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("apply oil", 1.0f), nullptr)));
|
||||||
if (specTab == 1) // Feral
|
if (specTab == 1) // Feral
|
||||||
triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("apply stone", 1.0f), nullptr)));
|
triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("apply stone", 1.0f), nullptr)));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GenericDruidBuffStrategy::GenericDruidBuffStrategy(PlayerbotAI* botAI) : NonCombatStrategy(botAI)
|
GenericDruidBuffStrategy::GenericDruidBuffStrategy(PlayerbotAI* botAI) : NonCombatStrategy(botAI)
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ void GenericDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|||||||
NextAction::array(0, new NextAction("rebirth", ACTION_HIGH + 9), NULL)));
|
NextAction::array(0, new NextAction("rebirth", ACTION_HIGH + 9), NULL)));
|
||||||
triggers.push_back(new TriggerNode("being attacked",
|
triggers.push_back(new TriggerNode("being attacked",
|
||||||
NextAction::array(0, new NextAction("nature's grasp", ACTION_HIGH + 1), nullptr)));
|
NextAction::array(0, new NextAction("nature's grasp", ACTION_HIGH + 1), nullptr)));
|
||||||
|
triggers.push_back(new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), nullptr)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void DruidCureStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
void DruidCureStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||||
|
|||||||
@@ -102,9 +102,11 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
|
|||||||
new TriggerNode("unlock traded item", NextAction::array(0, new NextAction("unlock traded item", relevance), nullptr)));
|
new TriggerNode("unlock traded item", NextAction::array(0, new NextAction("unlock traded item", relevance), nullptr)));
|
||||||
triggers.push_back(
|
triggers.push_back(
|
||||||
new TriggerNode("wipe", NextAction::array(0, new NextAction("wipe", relevance), nullptr)));
|
new TriggerNode("wipe", NextAction::array(0, new NextAction("wipe", relevance), nullptr)));
|
||||||
triggers.push_back(new TriggerNode("pet", NextAction::array(0, new NextAction("pet", relevance), nullptr)));
|
triggers.push_back(new TriggerNode("tame", NextAction::array(0, new NextAction("tame", relevance), nullptr)));
|
||||||
triggers.push_back(new TriggerNode("glyphs", NextAction::array(0, new NextAction("glyphs", relevance), nullptr))); // Added for custom Glyphs
|
triggers.push_back(new TriggerNode("glyphs", NextAction::array(0, new NextAction("glyphs", relevance), nullptr))); // Added for custom Glyphs
|
||||||
triggers.push_back(new TriggerNode("glyph equip", NextAction::array(0, new NextAction("glyph equip", relevance), nullptr))); // Added for custom Glyphs
|
triggers.push_back(new TriggerNode("glyph equip", NextAction::array(0, new NextAction("glyph equip", relevance), nullptr))); // Added for custom Glyphs
|
||||||
|
triggers.push_back(new TriggerNode("pet", NextAction::array(0, new NextAction("pet", relevance), nullptr)));
|
||||||
|
triggers.push_back(new TriggerNode("pet attack", NextAction::array(0, new NextAction("pet attack", relevance), nullptr)));
|
||||||
triggers.push_back(new TriggerNode("roll", NextAction::array(0, new NextAction("roll", relevance), nullptr)));
|
triggers.push_back(new TriggerNode("roll", NextAction::array(0, new NextAction("roll", relevance), nullptr)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +187,9 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas
|
|||||||
supported.push_back("qi");
|
supported.push_back("qi");
|
||||||
supported.push_back("unlock items");
|
supported.push_back("unlock items");
|
||||||
supported.push_back("unlock traded item");
|
supported.push_back("unlock traded item");
|
||||||
supported.push_back("pet");
|
supported.push_back("tame");
|
||||||
supported.push_back("glyphs"); // Added for custom Glyphs
|
supported.push_back("glyphs"); // Added for custom Glyphs
|
||||||
supported.push_back("glyph equip"); // Added for custom Glyphs
|
supported.push_back("glyph equip"); // Added for custom Glyphs
|
||||||
|
supported.push_back("pet");
|
||||||
|
supported.push_back("pet attack");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,10 @@ void CombatStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|||||||
triggers.push_back(new TriggerNode("combat stuck", NextAction::array(0, new NextAction("reset", 1.0f), nullptr)));
|
triggers.push_back(new TriggerNode("combat stuck", NextAction::array(0, new NextAction("reset", 1.0f), nullptr)));
|
||||||
triggers.push_back(new TriggerNode("not facing target",
|
triggers.push_back(new TriggerNode("not facing target",
|
||||||
NextAction::array(0, new NextAction("set facing", ACTION_MOVE + 7), nullptr)));
|
NextAction::array(0, new NextAction("set facing", ACTION_MOVE + 7), nullptr)));
|
||||||
triggers.push_back(
|
// triggers.push_back(new TriggerNode("pet attack", NextAction::array(0, new NextAction("pet attack", 40.0f), nullptr)));
|
||||||
new TriggerNode("pet attack", NextAction::array(0, new NextAction("pet attack", 40.0f), nullptr)));
|
// The pet-attack trigger is commented out because it was forcing the bot's pet to attack, overriding stay and follow commands.
|
||||||
|
// Pets will automatically attack the bot's enemy if they are in "defensive" or "aggressive"
|
||||||
|
// stance, or if the master issues an attack command.
|
||||||
// triggers.push_back(new TriggerNode("combat long stuck", NextAction::array(0, new NextAction("hearthstone", 0.9f),
|
// triggers.push_back(new TriggerNode("combat long stuck", NextAction::array(0, new NextAction("hearthstone", 0.9f),
|
||||||
// new NextAction("repop", 0.8f), nullptr)));
|
// new NextAction("repop", 0.8f), nullptr)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ void HunterPetStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|||||||
{
|
{
|
||||||
triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("call pet", 60.0f), nullptr)));
|
triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("call pet", 60.0f), nullptr)));
|
||||||
triggers.push_back(new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), nullptr)));
|
triggers.push_back(new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), nullptr)));
|
||||||
|
triggers.push_back(new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), nullptr)));
|
||||||
triggers.push_back(new TriggerNode("pet not happy", NextAction::array(0, new NextAction("feed pet", 60.0f), nullptr)));
|
triggers.push_back(new TriggerNode("pet not happy", NextAction::array(0, new NextAction("feed pet", 60.0f), nullptr)));
|
||||||
triggers.push_back(new TriggerNode("hunters pet medium health", NextAction::array(0, new NextAction("mend pet", 60.0f), nullptr)));
|
triggers.push_back(new TriggerNode("hunters pet medium health", NextAction::array(0, new NextAction("mend pet", 60.0f), nullptr)));
|
||||||
triggers.push_back(new TriggerNode("hunters pet dead", NextAction::array(0, new NextAction("revive pet", 60.0f), nullptr)));
|
triggers.push_back(new TriggerNode("hunters pet dead", NextAction::array(0, new NextAction("revive pet", 60.0f), nullptr)));
|
||||||
|
|||||||
@@ -61,7 +61,8 @@ void FrostMageStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|||||||
|
|
||||||
// Pet/Defensive triggers
|
// Pet/Defensive triggers
|
||||||
triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon water elemental", 30.0f), nullptr)));
|
triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon water elemental", 30.0f), nullptr)));
|
||||||
triggers.push_back(new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 29.5f), nullptr)));
|
triggers.push_back(new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), nullptr)));
|
||||||
|
triggers.push_back(new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), nullptr)));
|
||||||
triggers.push_back(new TriggerNode("medium health", NextAction::array(0, new NextAction("ice barrier", 29.0f), nullptr)));
|
triggers.push_back(new TriggerNode("medium health", NextAction::array(0, new NextAction("ice barrier", 29.0f), nullptr)));
|
||||||
triggers.push_back(new TriggerNode("being attacked", NextAction::array(0, new NextAction("ice barrier", 29.0f), nullptr)));
|
triggers.push_back(new TriggerNode("being attacked", NextAction::array(0, new NextAction("ice barrier", 29.0f), nullptr)));
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ void GenericPriestStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|||||||
triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("apply oil", 1.0f), nullptr)));
|
triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("apply oil", 1.0f), nullptr)));
|
||||||
triggers.push_back(new TriggerNode("being attacked",
|
triggers.push_back(new TriggerNode("being attacked",
|
||||||
NextAction::array(0, new NextAction("power word: shield", ACTION_HIGH + 1), nullptr)));
|
NextAction::array(0, new NextAction("power word: shield", ACTION_HIGH + 1), nullptr)));
|
||||||
|
triggers.push_back(new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), nullptr)));
|
||||||
}
|
}
|
||||||
|
|
||||||
PriestCureStrategy::PriestCureStrategy(PlayerbotAI* botAI) : Strategy(botAI)
|
PriestCureStrategy::PriestCureStrategy(PlayerbotAI* botAI) : Strategy(botAI)
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ void PriestNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|||||||
|
|
||||||
triggers.push_back(
|
triggers.push_back(
|
||||||
new TriggerNode("group heal setting", NextAction::array(0, new NextAction("circle of healing on party", 27.0f), NULL)));
|
new TriggerNode("group heal setting", NextAction::array(0, new NextAction("circle of healing on party", 27.0f), NULL)));
|
||||||
|
triggers.push_back(new TriggerNode("new pet",
|
||||||
|
NextAction::array(0, new NextAction("set pet stance", 10.0f), nullptr)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PriestBuffStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
void PriestBuffStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||||
|
|||||||
@@ -133,6 +133,8 @@ void GenericShamanStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|||||||
// NextAction("riptide", 26.0f), nullptr)));
|
// NextAction("riptide", 26.0f), nullptr)));
|
||||||
triggers.push_back(new TriggerNode("heroism", NextAction::array(0, new NextAction("heroism", 31.0f), nullptr)));
|
triggers.push_back(new TriggerNode("heroism", NextAction::array(0, new NextAction("heroism", 31.0f), nullptr)));
|
||||||
triggers.push_back(new TriggerNode("bloodlust", NextAction::array(0, new NextAction("bloodlust", 30.0f), nullptr)));
|
triggers.push_back(new TriggerNode("bloodlust", NextAction::array(0, new NextAction("bloodlust", 30.0f), nullptr)));
|
||||||
|
triggers.push_back(new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), nullptr)));
|
||||||
|
triggers.push_back(new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 65.0f), nullptr)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShamanBuffDpsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
void ShamanBuffDpsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ void ShamanNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
|
|||||||
new TriggerNode("cure disease", NextAction::array(0, new NextAction("cure disease", 31.0f), nullptr)));
|
new TriggerNode("cure disease", NextAction::array(0, new NextAction("cure disease", 31.0f), nullptr)));
|
||||||
triggers.push_back(new TriggerNode("party member cure disease",
|
triggers.push_back(new TriggerNode("party member cure disease",
|
||||||
NextAction::array(0, new NextAction("cure disease on party", 30.0f), nullptr)));
|
NextAction::array(0, new NextAction("cure disease on party", 30.0f), nullptr)));
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), nullptr)));
|
||||||
|
triggers.push_back(
|
||||||
|
new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 65.0f), nullptr)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShamanNonCombatStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
|
void ShamanNonCombatStrategy::InitMultipliers(std::vector<Multiplier*>& multipliers)
|
||||||
|
|||||||
@@ -133,9 +133,11 @@ public:
|
|||||||
creators["calc"] = &ChatTriggerContext::calc;
|
creators["calc"] = &ChatTriggerContext::calc;
|
||||||
creators["qi"] = &ChatTriggerContext::qi;
|
creators["qi"] = &ChatTriggerContext::qi;
|
||||||
creators["wipe"] = &ChatTriggerContext::wipe;
|
creators["wipe"] = &ChatTriggerContext::wipe;
|
||||||
creators["pet"] = &ChatTriggerContext::pet;
|
creators["tame"] = &ChatTriggerContext::tame;
|
||||||
creators["glyphs"] = &ChatTriggerContext::glyphs; // Added for custom Glyphs
|
creators["glyphs"] = &ChatTriggerContext::glyphs; // Added for custom Glyphs
|
||||||
creators["glyph equip"] = &ChatTriggerContext::glyph_equip; // Added for custom Glyphs
|
creators["glyph equip"] = &ChatTriggerContext::glyph_equip; // Added for custom Glyphs
|
||||||
|
creators["pet"] = &ChatTriggerContext::pet;
|
||||||
|
creators["pet attack"] = &ChatTriggerContext::pet_attack;
|
||||||
creators["roll"] = &ChatTriggerContext::roll_action;
|
creators["roll"] = &ChatTriggerContext::roll_action;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,9 +251,11 @@ private:
|
|||||||
static Trigger* calc(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "calc"); }
|
static Trigger* calc(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "calc"); }
|
||||||
static Trigger* qi(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "qi"); }
|
static Trigger* qi(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "qi"); }
|
||||||
static Trigger* wipe(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "wipe"); }
|
static Trigger* wipe(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "wipe"); }
|
||||||
static Trigger* pet(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "pet"); }
|
static Trigger* tame(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "tame"); }
|
||||||
static Trigger* glyphs(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "glyphs"); } // Added for custom Glyphs
|
static Trigger* glyphs(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "glyphs"); } // Added for custom Glyphs
|
||||||
static Trigger* glyph_equip(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "glyph equip"); } // Added for custom Glyphs
|
static Trigger* glyph_equip(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "glyph equip"); } // Added for custom Glyphs
|
||||||
|
static Trigger* pet(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "pet"); }
|
||||||
|
static Trigger* pet_attack(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "pet attack"); }
|
||||||
static Trigger* roll_action(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "roll"); }
|
static Trigger* roll_action(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "roll"); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
#include "TemporarySummon.h"
|
#include "TemporarySummon.h"
|
||||||
#include "ThreatMgr.h"
|
#include "ThreatMgr.h"
|
||||||
#include "Timer.h"
|
#include "Timer.h"
|
||||||
|
#include "PlayerbotAI.h"
|
||||||
|
#include "Player.h"
|
||||||
|
|
||||||
bool LowManaTrigger::IsActive()
|
bool LowManaTrigger::IsActive()
|
||||||
{
|
{
|
||||||
@@ -685,3 +687,46 @@ bool AmmoCountTrigger::IsActive()
|
|||||||
|
|
||||||
return ItemCountTrigger::IsActive();
|
return ItemCountTrigger::IsActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool NewPetTrigger::IsActive()
|
||||||
|
{
|
||||||
|
// Get the bot player object from the AI
|
||||||
|
Player* bot = botAI->GetBot();
|
||||||
|
if (!bot)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Try to get the current pet; initialize guardian and GUID to null/empty
|
||||||
|
Pet* pet = bot->GetPet();
|
||||||
|
Guardian* guardian = nullptr;
|
||||||
|
ObjectGuid currentPetGuid = ObjectGuid::Empty;
|
||||||
|
|
||||||
|
// If bot has a pet, get its GUID
|
||||||
|
if (pet)
|
||||||
|
{
|
||||||
|
currentPetGuid = pet->GetGUID();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If no pet, try to get a guardian pet and its GUID
|
||||||
|
guardian = bot->GetGuardianPet();
|
||||||
|
if (guardian)
|
||||||
|
currentPetGuid = guardian->GetGUID();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the current pet or guardian GUID has changed (including becoming empty), reset the trigger state
|
||||||
|
if (currentPetGuid != lastPetGuid)
|
||||||
|
{
|
||||||
|
triggered = false;
|
||||||
|
lastPetGuid = currentPetGuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's a valid current pet/guardian (non-empty GUID) and we haven't triggered yet, activate trigger
|
||||||
|
if (currentPetGuid != ObjectGuid::Empty && !triggered)
|
||||||
|
{
|
||||||
|
triggered = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, do not activate
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|||||||
@@ -943,4 +943,16 @@ public:
|
|||||||
bool IsActive() override { return !bot->IsAlive() && bot->GetUInt32Value(PLAYER_SELF_RES_SPELL); }
|
bool IsActive() override { return !bot->IsAlive() && bot->GetUInt32Value(PLAYER_SELF_RES_SPELL); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class NewPetTrigger : public Trigger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
NewPetTrigger(PlayerbotAI* ai) : Trigger(ai, "new pet"), lastPetGuid(ObjectGuid::Empty), triggered(false) {}
|
||||||
|
|
||||||
|
bool IsActive() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
ObjectGuid lastPetGuid;
|
||||||
|
bool triggered;
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -227,6 +227,7 @@ public:
|
|||||||
creators["do quest status"] = &TriggerContext::do_quest_status;
|
creators["do quest status"] = &TriggerContext::do_quest_status;
|
||||||
creators["travel flight status"] = &TriggerContext::travel_flight_status;
|
creators["travel flight status"] = &TriggerContext::travel_flight_status;
|
||||||
creators["can self resurrect"] = &TriggerContext::can_self_resurrect;
|
creators["can self resurrect"] = &TriggerContext::can_self_resurrect;
|
||||||
|
creators["new pet"] = &TriggerContext::new_pet;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -425,6 +426,7 @@ private:
|
|||||||
static Trigger* do_quest_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_DO_QUEST); }
|
static Trigger* do_quest_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_DO_QUEST); }
|
||||||
static Trigger* travel_flight_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_TRAVEL_FLIGHT); }
|
static Trigger* travel_flight_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_TRAVEL_FLIGHT); }
|
||||||
static Trigger* can_self_resurrect(PlayerbotAI* ai) { return new SelfResurrectTrigger(ai); }
|
static Trigger* can_self_resurrect(PlayerbotAI* ai) { return new SelfResurrectTrigger(ai); }
|
||||||
|
static Trigger* new_pet(PlayerbotAI* ai) { return new NewPetTrigger(ai); }
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ void GenericWarlockNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& tr
|
|||||||
{
|
{
|
||||||
NonCombatStrategy::InitTriggers(triggers);
|
NonCombatStrategy::InitTriggers(triggers);
|
||||||
triggers.push_back(new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), nullptr)));
|
triggers.push_back(new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), nullptr)));
|
||||||
|
triggers.push_back(new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), nullptr)));
|
||||||
triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("fel domination", 30.0f), nullptr)));
|
triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("fel domination", 30.0f), nullptr)));
|
||||||
triggers.push_back(new TriggerNode("no soul shard", NextAction::array(0, new NextAction("create soul shard", 60.0f), nullptr)));
|
triggers.push_back(new TriggerNode("no soul shard", NextAction::array(0, new NextAction("create soul shard", 60.0f), nullptr)));
|
||||||
triggers.push_back(new TriggerNode("too many soul shards", NextAction::array(0, new NextAction("destroy soul shard", 60.0f), nullptr)));
|
triggers.push_back(new TriggerNode("too many soul shards", NextAction::array(0, new NextAction("destroy soul shard", 60.0f), nullptr)));
|
||||||
|
|||||||
Reference in New Issue
Block a user