mirror of
https://github.com/mod-playerbots/mod-playerbots.git
synced 2026-03-16 13:55:08 +00:00
# Pull Request
Removed unused variables and fixed styling issues.
## How to Test the Changes
- Step-by-step instructions to test the change
- Any required setup (e.g. multiple players, bots, specific
configuration)
- Expected behavior and how to verify it
## Complexity & Impact
- Does this change add new decision branches?
- [x] No
- [] Yes (**explain below**)
- Does this change increase per-bot or per-tick processing?
- [x] No
- [ ] Yes (**describe and justify impact**)
- Could this logic scale poorly under load?
- [x] No
- [ ] Yes (**explain why**)
---
## Defaults & Configuration
- Does this change modify default bot behavior?
- [x] No
- [ ] Yes (**explain why**)
If this introduces more advanced or AI-heavy logic:
- [ ] Lightweight mode remains the default
- [ ] More complex behavior is optional and thereby configurable
---
## AI Assistance
- Was AI assistance (e.g. ChatGPT or similar tools) used while working
on this change?
- [x] No
- [ ] Yes (**explain below**)
---
## Final Checklist
- [x] Stability is not compromised
- [x] Performance impact is understood, tested, and acceptable
- [x] Added logic complexity is justified and explained
- [x] Documentation updated if needed
---
## Notes for Reviewers
This was filtered from the code provided by SmashingQuasar. Eliminated
variables were confirmed to be not used, but unclear at times if that is
due to mistakes in writing.
---------
Co-authored-by: bashermens <31279994+hermensbas@users.noreply.github.com>
498 lines
16 KiB
C++
498 lines
16 KiB
C++
/*
|
|
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
|
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
|
*/
|
|
|
|
#include "TameAction.h"
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <random>
|
|
#include <set>
|
|
#include <sstream>
|
|
#include "DBCStructure.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;
|
|
|
|
// 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 (auto const& name : normalFamilies)
|
|
{
|
|
if (count++ != 0)
|
|
oss << ", ";
|
|
oss << name;
|
|
}
|
|
if (!exoticFamilies.empty())
|
|
{
|
|
if (!normalFamilies.empty())
|
|
oss << " | ";
|
|
oss << "Exotic: ";
|
|
count = 0;
|
|
for (auto const& 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;
|
|
}
|
|
}
|