mirror of
https://github.com/mod-playerbots/mod-playerbots
synced 2025-11-29 15:58:20 +08:00
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:
@@ -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
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -333,6 +333,8 @@ public:
|
||||
bool autoPickTalents;
|
||||
bool autoUpgradeEquip;
|
||||
int32 hunterWolfPet;
|
||||
int32 defaultPetStance;
|
||||
int32 petChatCommandDebug;
|
||||
bool autoLearnTrainerSpells;
|
||||
bool autoDoQuests;
|
||||
bool enableNewRpgStrategy;
|
||||
|
||||
@@ -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); }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
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 "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); }
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)));
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)));
|
||||
|
||||
Reference in New Issue
Block a user