Tame Chat Action / Pet Chat Action (stances/commands)

Hello everyone,

I am on a quest to make bot's pets completely functional, focuses on solving issues #1351 , #1230 , and #1137 . This PR achieves the following:

1. Changes the current "pet" chat command to "tame", which is more intuitive that only hunters can use it. The modes are "tame name (name)", "tame id (id)", "tame family (family)", "tame rename (new name)", and "tame abandon". Tame abandon is new - it simply abandons the current pet. Also, now, if you type in "tame family" by itself, it will list the available families. See pictures below for examples.
2. Added new "pet" chat command, with the following modes: "pet passive", "pet aggressive", "pet defensive", "pet stance" (shows current pet stance), "pet attack", "pet follow", and "pet stay". Previously, the pet's stance was not changeable, and there were some less than desired effects from summonable pets - see the issues above.
3. New config option: AiPlayerbot.DefaultPetStance, which changes the stance that all bot's pets are summoned as. This makes sure when feral spirits or treants are summoned by shamans and druids, they are immediately set to this configured stance. Set as 1 as default, which is defensive. (0 = Passive, 1 = Defensive, 2 = Aggressive)
4. New config option: AiPlayerbot.PetChatCommandDebug, which enables debug messages for the "pet" chat command. By default it is set to 0, which is disabled, but if you would like to see when pet's stances are changed, or when you tell the pet to attack/follow/stay, and when pet spells are auto-toggled, this is an option. I made this for myself mainly to test the command - if anyone notices any wierd pet behavior, this will be an option to help them report it as well.
5. Modified FollowActions to not constantly execute the petfollow action, overriding any stay or attack commands issued.
6. Modified GenericActions to have TogglePetSpellAutoCastAction optionally log when spells are toggled based on AiPlayerbot.PetChatCommandDebug.
7. Modified PetAttackAction to not attack if the pet is set to passive, and not override the pet's stance to passive every time it was executed.
8. Modified CombatStrategy.cpp to not constantly issue the petattack command, respecting the "pet stay" and "pet follow" commands. Pets will still automatically attack the enemy if set to aggressive or defensive.
9. Warlocks, Priests, Hunters, Shamans, Mages, Druids, and DKs (all classes that have summons): Added a "new pet" trigger that executes the "set pet stance" action. The "new pet" trigger happens only once, upon summoning a pet. It sets the pet's stance from AiPlayerbot.DefaultPetStance's value.
This commit is contained in:
ThePenguinMan96
2025-08-01 01:18:16 -07:00
parent 989b48f491
commit aa6f8153a1
30 changed files with 1000 additions and 372 deletions

View File

@@ -699,6 +699,16 @@ AiPlayerbot.AutoUpgradeEquip = 1
# Default: 0 (disabled)
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
#
#
#

View File

@@ -575,6 +575,8 @@ bool PlayerbotAIConfig::Initialize()
autoPickTalents = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoPickTalents", true);
autoUpgradeEquip = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoUpgradeEquip", false);
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);
autoLearnQuestSpells = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoLearnQuestSpells", false);
autoTeleportForLevel = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoTeleportForLevel", false);

View File

@@ -333,6 +333,8 @@ public:
bool autoPickTalents;
bool autoUpgradeEquip;
int32 hunterWolfPet;
int32 defaultPetStance;
int32 petChatCommandDebug;
bool autoLearnTrainerSpells;
bool autoDoQuests;
bool enableNewRpgStrategy;

View File

@@ -244,7 +244,8 @@ public:
creators["rpg mount anim"] = &ActionContext::rpg_mount_anim;
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 go grind"] = &ActionContext::new_rpg_go_grind;
@@ -431,6 +432,7 @@ private:
static Action* toggle_pet_spell(PlayerbotAI* ai) { return new TogglePetSpellAutoCastAction(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_go_grind(PlayerbotAI* ai) { return new NewRpgGoGrindAction(ai); }

View File

@@ -78,9 +78,10 @@
#include "OpenItemAction.h"
#include "UnlockItemAction.h"
#include "UnlockTradedItemAction.h"
#include "PetAction.h"
#include "TameAction.h"
#include "TellGlyphsAction.h"
#include "EquipGlyphsAction.h"
#include "PetAction.h"
class ChatActionContext : public NamedObjectContext<Action>
{
@@ -190,9 +191,11 @@ public:
creators["lfg"] = &ChatActionContext::lfg;
creators["calc"] = &ChatActionContext::calc;
creators["wipe"] = &ChatActionContext::wipe;
creators["pet"] = &ChatActionContext::pet;
creators["tame"] = &ChatActionContext::tame;
creators["glyphs"] = &ChatActionContext::glyphs; // Added for custom Glyphs
creators["glyph equip"] = &ChatActionContext::glyph_equip; // Added for custom Glyphs
creators["pet"] = &ChatActionContext::pet;
creators["pet attack"] = &ChatActionContext::pet_attack;
}
private:
@@ -299,9 +302,11 @@ private:
static Action* join(PlayerbotAI* ai) { return new JoinGroupAction(ai); }
static Action* calc(PlayerbotAI* ai) { return new TellCalculateItemAction(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* 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"); }
};
#endif

View File

@@ -36,10 +36,13 @@ bool FollowAction::Execute(Event event)
true, priority, true);
}
if (Pet* pet = bot->GetPet())
{
botAI->PetFollow();
}
// 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
// stay commands that might have been issued by the player.
// if (Pet* pet = bot->GetPet())
// {
// botAI->PetFollow();
// }
// if (moved)
// botAI->SetNextCheckDelay(sPlayerbotAIConfig->reactDelay);

View File

@@ -4,9 +4,19 @@
*/
#include "GenericActions.h"
#include "PlayerbotAI.h"
#include "Player.h"
#include "Pet.h"
#include "PlayerbotAIConfig.h"
#include "CreatureAI.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
{
@@ -100,6 +110,11 @@ bool TogglePetSpellAutoCastAction::Execute(Event event)
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;
}
@@ -107,22 +122,23 @@ bool PetAttackAction::Execute(Event event)
{
Guardian* pet = bot->GetGuardianPet();
if (!pet)
{
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");
if (!target)
{
return false;
}
if (!bot->IsValidAttackTarget(target))
{
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->AttackStop();
pet->SetTarget(target->GetGUID());
@@ -136,3 +152,79 @@ bool PetAttackAction::Execute(Event event)
pet->ToCreature()->AI()->AttackStart(target);
return true;
}
bool SetPetStanceAction::Execute(Event /*event*/)
{
// Get the bot player object from the AI
Player* bot = botAI->GetBot();
// 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;
}

View File

@@ -7,6 +7,8 @@
#define _PLAYERBOT_GENERICACTIONS_H
#include "AttackAction.h"
#include "Action.h"
#include "PlayerbotAI.h"
class PlayerbotAI;
@@ -33,4 +35,12 @@ public:
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

View File

@@ -4,373 +4,254 @@
*/
#include "PetAction.h"
#include <algorithm>
#include <iomanip>
#include <sstream>
#include "CharmInfo.h"
#include "Creature.h"
#include "CreatureAI.h"
#include "Pet.h"
#include "SpellMgr.h"
#include "DBCStructure.h"
#include "Log.h"
#include "ObjectMgr.h"
#include "Player.h"
#include "PlayerbotAI.h"
#include "PlayerbotFactory.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);
}
#include "SharedDefines.h"
bool PetAction::Execute(Event event)
{
// Extract the command parameter from the event (e.g., "aggressive", "defensive", "attack", 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(" ")); // trim leading spaces
if (param.empty() && !defaultCmd.empty())
{
param = defaultCmd;
}
bool found = false;
// Reset lastPetName/Id each time
lastPetName = "";
lastPetId = 0;
if (mode == "name" && !value.empty())
if (param.empty())
{
found = SetPetByName(value);
}
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> ");
// If no parameter is provided, show usage instructions and return.
botAI->TellError("Usage: pet <aggressive|defensive|passive|stance|attack|follow|stay>");
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();
for (auto itr = creatures->begin(); itr != creatures->end(); ++itr)
{
const CreatureTemplate& creature = itr->second;
std::string creatureName = creature.Name;
std::transform(creatureName.begin(), creatureName.end(), creatureName.begin(), ::tolower);
// Collect all controlled pets and guardians, except totems, into the targets vector.
std::vector<Creature*> targets;
Pet* pet = bot->GetPet();
if (pet)
targets.push_back(pet);
// Only match if names match (case-insensitive)
if (creatureName == lowerName)
for (Unit::ControlSet::const_iterator itr = bot->m_Controlled.begin(); itr != bot->m_Controlled.end(); ++itr)
{
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
if (!creature.IsTameable(true))
std::string type = target->IsPet() ? "pet" : "guardian";
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;
// Exotic pet check with talent requirement
if (IsExoticPet(&creature) && !HasBeastMastery(bot))
petCreature->ClearUnitState(UNIT_STATE_FOLLOW);
// 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.");
return false;
if (petCreature->GetVictim())
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
if (!creature.IsTameable(bot->CanTameExoticPets()))
continue;
CharmInfo* charmInfo = target->GetCharmInfo();
if (charmInfo)
{
// 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;
lastPetId = creature.Entry;
return CreateAndSetPet(creature.Entry);
charmInfo->SetForcedSpell(0);
charmInfo->SetForcedTargetGUID();
}
}
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
botAI->TellMaster("Pet commanded to stay.");
return true;
}
botAI->TellError("No tameable pet found with name: " + name);
return false;
}
bool PetAction::SetPetById(uint32 id)
{
CreatureTemplate const* creature = sObjectMgr->GetCreatureTemplate(id);
Player* bot = botAI->GetBot();
if (creature)
// Unknown command: show usage instructions and return.
else
{
// Check if the pet is tameable at all
if (!creature->IsTameable(true))
{
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);
botAI->TellError("Unknown pet command: " + param +
". Use: pet <aggressive|defensive|passive|stance|attack|follow|stay>");
return false;
}
// Randomly select one from candidates
std::random_device rd;
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)
// For stance commands, apply the chosen stance to all targets.
for (Creature* target : targets)
{
botAI->TellError("You have no pet to rename.");
return false;
target->SetReactState(react);
CharmInfo* charmInfo = target->GetCharmInfo();
if (charmInfo)
charmInfo->SetPlayerReactState(react);
}
// Length check (WoW max pet name is 12 characters)
if (newName.empty() || newName.length() > 12)
{
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);
}
// Inform the master of the new stance if debug is enabled.
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
botAI->TellMaster("Pet stance set to " + stanceText + ".");
return true;
}

View File

@@ -10,25 +10,20 @@
#include "Action.h"
#include "PlayerbotFactory.h"
#include "Unit.h"
class PlayerbotAI;
class PetAction : public Action
{
public:
PetAction(PlayerbotAI* botAI) : Action(botAI, "pet") {}
PetAction(PlayerbotAI* botAI, const std::string& defaultCmd = "") : Action(botAI, "pet"), defaultCmd(defaultCmd) {}
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);
std::string lastPetName;
uint32 lastPetId = 0;
bool warningEnabled = true;
std::string defaultCmd;
};
#endif

View 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;
}
}

View 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

View File

@@ -41,6 +41,7 @@
#include "UseMeetingStoneAction.h"
#include "NamedObjectContext.h"
#include "ReleaseSpiritAction.h"
#include "PetAction.h"
class PlayerbotAI;
@@ -70,6 +71,7 @@ public:
creators["trade status extended"] = &WorldPacketActionContext::trade_status_extended;
creators["store loot"] = &WorldPacketActionContext::store_loot;
creators["self resurrect"] = &WorldPacketActionContext::self_resurrect;
creators["pet"] = &WorldPacketActionContext::pet;
// 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_cannot_equip(PlayerbotAI* botAI) { return new InventoryChangeFailureAction(botAI); }
static Action* self_resurrect(PlayerbotAI* botAI) { return new SelfResurrectAction(botAI); }
static Action* pet(PlayerbotAI* botAI) { return new PetAction(botAI); }
// quest
static Action* quest_update_add_kill(PlayerbotAI* ai) { return new QuestUpdateAddKillAction(ai); }

View File

@@ -50,7 +50,9 @@ void GenericDKNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& trigger
triggers.push_back(
new TriggerNode("bone shield", NextAction::array(0, new NextAction("bone shield", 21.0f), nullptr)));
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)

View File

@@ -171,6 +171,10 @@ void GenericDKStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
// NextAction::array(0, new NextAction("anti magic zone", ACTION_EMERGENCY + 1), nullptr)));
triggers.push_back(
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(
new TriggerNode("mind freeze", NextAction::array(0, new NextAction("mind freeze", ACTION_HIGH + 1), nullptr)));
triggers.push_back(

View File

@@ -153,6 +153,8 @@ void GenericDruidNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& trig
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)));
}
GenericDruidBuffStrategy::GenericDruidBuffStrategy(PlayerbotAI* botAI) : NonCombatStrategy(botAI)

View File

@@ -122,7 +122,8 @@ void GenericDruidStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(new TriggerNode("combat party member dead",
NextAction::array(0, new NextAction("rebirth", ACTION_HIGH + 9), NULL)));
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)

View File

@@ -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)));
triggers.push_back(
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("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)));
}
ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI)
@@ -184,7 +186,9 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas
supported.push_back("qi");
supported.push_back("unlock items");
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("glyph equip"); // Added for custom Glyphs
supported.push_back("pet");
supported.push_back("pet attack");
}

View File

@@ -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("not facing target",
NextAction::array(0, new NextAction("set facing", ACTION_MOVE + 7), nullptr)));
triggers.push_back(
new TriggerNode("pet attack", NextAction::array(0, new NextAction("pet attack", 40.0f), nullptr)));
// triggers.push_back(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),
// new NextAction("repop", 0.8f), nullptr)));
}

View File

@@ -63,6 +63,8 @@ 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("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(

View File

@@ -61,7 +61,8 @@ void FrostMageStrategy::InitTriggers(std::vector<TriggerNode*>& 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("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("being attacked", NextAction::array(0, new NextAction("ice barrier", 29.0f), nullptr)));

View File

@@ -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("being attacked",
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)

View File

@@ -56,6 +56,8 @@ void PriestNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
triggers.push_back(
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)

View File

@@ -133,6 +133,8 @@ void GenericShamanStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
// 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("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)
@@ -187,4 +189,4 @@ void ShamanHealerDpsStrategy::InitTriggers(std::vector<TriggerNode*>& triggers)
new TriggerNode("medium aoe and healer should attack",
NextAction::array(0,
new NextAction("chain lightning", ACTION_DEFAULT + 0.3f), nullptr)));
}
}

View File

@@ -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)));
triggers.push_back(new TriggerNode("party member cure disease",
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)

View File

@@ -133,9 +133,11 @@ public:
creators["calc"] = &ChatTriggerContext::calc;
creators["qi"] = &ChatTriggerContext::qi;
creators["wipe"] = &ChatTriggerContext::wipe;
creators["pet"] = &ChatTriggerContext::pet;
creators["tame"] = &ChatTriggerContext::tame;
creators["glyphs"] = &ChatTriggerContext::glyphs; // Added for custom Glyphs
creators["glyph equip"] = &ChatTriggerContext::glyph_equip; // Added for custom Glyphs
creators["pet"] = &ChatTriggerContext::pet;
creators["pet attack"] = &ChatTriggerContext::pet_attack;
}
private:
@@ -248,9 +250,11 @@ private:
static Trigger* calc(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "calc"); }
static Trigger* qi(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "qi"); }
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* 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"); }
};
#endif

View File

@@ -20,6 +20,8 @@
#include "TemporarySummon.h"
#include "ThreatMgr.h"
#include "Timer.h"
#include "PlayerbotAI.h"
#include "Player.h"
bool LowManaTrigger::IsActive()
{
@@ -685,3 +687,44 @@ bool AmmoCountTrigger::IsActive()
return ItemCountTrigger::IsActive();
}
bool NewPetTrigger::IsActive()
{
// Get the bot player object from the AI
Player* bot = botAI->GetBot();
// Try to get the current pet; initialize guardian and GUID to null/empty
Pet* pet = bot ? bot->GetPet() : nullptr;
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 ? bot->GetGuardianPet() : nullptr;
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;
}

View File

@@ -943,4 +943,16 @@ public:
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

View File

@@ -227,6 +227,7 @@ public:
creators["do quest status"] = &TriggerContext::do_quest_status;
creators["travel flight status"] = &TriggerContext::travel_flight_status;
creators["can self resurrect"] = &TriggerContext::can_self_resurrect;
creators["new pet"] = &TriggerContext::new_pet;
}
private:
@@ -425,6 +426,7 @@ private:
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* can_self_resurrect(PlayerbotAI* ai) { return new SelfResurrectTrigger(ai); }
static Trigger* new_pet(PlayerbotAI* ai) { return new NewPetTrigger(ai); }
};
#endif

View File

@@ -79,6 +79,7 @@ void GenericWarlockNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& tr
{
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("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 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)));