feat(Scripts/Commands): Add .pet list and .pet delete commands (#25692)

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Andrew
2026-05-03 06:24:06 -03:00
committed by GitHub
parent 3a36c1ca8d
commit 685ef839f8
5 changed files with 134 additions and 4 deletions
@@ -0,0 +1,10 @@
-- RBAC permissions for .pet list (sec 1+, Mod role 198) and .pet delete (sec 3+, Admin role 196).
DELETE FROM `rbac_permissions` WHERE `id` IN (914, 915);
INSERT INTO `rbac_permissions` (`id`, `name`) VALUES
(914, 'Command: pet list'),
(915, 'Command: pet delete');
DELETE FROM `rbac_linked_permissions` WHERE `linkedId` IN (914, 915);
INSERT INTO `rbac_linked_permissions` (`id`, `linkedId`) VALUES
(198, 914),
(196, 915);
@@ -0,0 +1,8 @@
-- acore_string entries for .pet list and .pet delete commands.
DELETE FROM `acore_string` WHERE `entry` BETWEEN 35435 AND 35439;
INSERT INTO `acore_string` (`entry`, `content_default`, `locale_koKR`, `locale_frFR`, `locale_deDE`, `locale_zhCN`, `locale_zhTW`, `locale_esES`, `locale_esMX`, `locale_ruRU`) VALUES
(35435, 'Pets owned by {} ({}):', '{}({})이(가) 소유한 펫:', 'Familiers de {} ({}) :', 'Begleiter von {} ({}):', '{} ({}) 拥有的宠物:', '{} ({}) 擁有的寵物:', 'Mascotas de {} ({}):', 'Mascotas de {} ({}):', 'Питомцы игрока {} ({}):'),
(35436, ' PetNumber: {} | Slot: {} | Entry: {} ({}) | Name: {} | Level: {} | Type: {}', ' 펫번호: {} | 슬롯: {} | Entry: {} ({}) | 이름: {} | 레벨: {} | 종류: {}', ' N° Familier : {} | Emplacement : {} | Entrée : {} ({}) | Nom : {} | Niveau : {} | Type : {}', ' Begleiter-Nr.: {} | Slot: {} | Eintrag: {} ({}) | Name: {} | Stufe: {} | Typ: {}', ' 宠物编号: {} | 槽位: {} | 条目: {} ({}) | 名称: {} | 等级: {} | 类型: {}', ' 寵物編號: {} | 槽位: {} | 條目: {} ({}) | 名稱: {} | 等級: {} | 類型: {}', ' N.º mascota: {} | Ranura: {} | Entrada: {} ({}) | Nombre: {} | Nivel: {} | Tipo: {}', ' N.º mascota: {} | Ranura: {} | Entrada: {} ({}) | Nombre: {} | Nivel: {} | Tipo: {}', ' Номер: {} | Слот: {} | Entry: {} ({}) | Имя: {} | Уровень: {} | Тип: {}'),
(35437, 'Player {} ({}) has no pets.', '플레이어 {}({})에게 펫이 없습니다.', "Le joueur {} ({}) n'a aucun familier.", 'Spieler {} ({}) besitzt keine Begleiter.', '玩家 {} ({}) 没有任何宠物。', '玩家 {} ({}) 沒有任何寵物。', 'El jugador {} ({}) no tiene mascotas.', 'El jugador {} ({}) no tiene mascotas.', 'У игрока {} ({}) нет питомцев.'),
(35438, 'No pet with PetNumber {0} found for player {1} ({2}).', '플레이어 {1} ({2})의 펫번호 {0}을(를) 찾을 수 없습니다.', "Aucun familier avec le N° {0} trouvé pour le joueur {1} ({2}).", 'Kein Begleiter mit Nummer {0} für Spieler {1} ({2}) gefunden.', '未找到玩家 {1} ({2}) 的宠物编号 {0}。', '找不到玩家 {1} ({2}) 的寵物編號 {0}。', 'No se encontró ninguna mascota con N.º {0} para el jugador {1} ({2}).', 'No se encontró ninguna mascota con N.º {0} para el jugador {1} ({2}).', 'У игрока {1} ({2}) не найден питомец с номером {0}.'),
(35439, 'Deleted pet {0} ({1}, entry {2} - {3}) from player {4} ({5}).', '플레이어 {4} ({5})로부터 펫 {0} ({1}, entry {2} - {3})을(를) 삭제했습니다.', "Familier {0} ({1}, entrée {2} - {3}) supprimé du joueur {4} ({5}).", 'Begleiter {0} ({1}, Eintrag {2} - {3}) von Spieler {4} ({5}) gelöscht.', '已从玩家 {4} ({5}) 删除宠物 {0} ({1}, 条目 {2} - {3})。', '已從玩家 {4} ({5}) 刪除寵物 {0} ({1}, 條目 {2} - {3})。', 'Eliminada la mascota {0} ({1}, entrada {2} - {3}) del jugador {4} ({5}).', 'Eliminada la mascota {0} ({1}, entrada {2} - {3}) del jugador {4} ({5}).', 'Питомец {0} ({1}, entry {2} - {3}) удалён у игрока {4} ({5}).');
+2
View File
@@ -676,6 +676,8 @@ enum RBACPermissions
RBAC_PERM_COMMAND_CHARACTER_CHECK_PROFESSION = 911,
RBAC_PERM_COMMAND_GOBJECT_LOAD = 912,
RBAC_PERM_COMMAND_BF_QUEUE = 913,
RBAC_PERM_COMMAND_PET_LIST = 914,
RBAC_PERM_COMMAND_PET_DELETE = 915,
// custom permissions 1000+
RBAC_PERM_MAX
};
+10 -1
View File
@@ -1478,6 +1478,15 @@ enum AcoreStrings
LANG_POOL_LOOKUP_IN_POOL = 35431,
LANG_POOL_LOOKUP_NOT_IN_POOL = 35432,
LANG_POOL_LOOKUP_USE_INFO = 35433,
LANG_POOL_LOOKUP_NOTARGET = 35434
LANG_POOL_LOOKUP_NOTARGET = 35434,
// Pet list command
LANG_PET_LIST_HEADER = 35435,
LANG_PET_LIST_ENTRY = 35436,
LANG_PET_LIST_EMPTY = 35437,
// Pet delete command
LANG_PET_DELETE_NOT_FOUND = 35438,
LANG_PET_DELETE_SUCCESS = 35439
};
#endif
+104 -3
View File
@@ -17,6 +17,8 @@
#include "Chat.h"
#include "CommandScript.h"
#include "DatabaseEnv.h"
#include <algorithm>
#include "Language.h"
#include "Log.h"
#include "ObjectMgr.h"
@@ -37,9 +39,11 @@ public:
{
static ChatCommandTable petCommandTable =
{
{ "create", HandlePetCreateCommand, rbac::RBAC_PERM_COMMAND_PET_CREATE, Console::No },
{ "learn", HandlePetLearnCommand, rbac::RBAC_PERM_COMMAND_PET_LEARN, Console::No },
{ "unlearn", HandlePetUnlearnCommand, rbac::RBAC_PERM_COMMAND_PET_UNLEARN, Console::No }
{ "create", HandlePetCreateCommand, rbac::RBAC_PERM_COMMAND_PET_CREATE, Console::No },
{ "delete", HandlePetDeleteCommand, rbac::RBAC_PERM_COMMAND_PET_DELETE, Console::Yes },
{ "learn", HandlePetLearnCommand, rbac::RBAC_PERM_COMMAND_PET_LEARN, Console::No },
{ "list", HandlePetListCommand, rbac::RBAC_PERM_COMMAND_PET_LIST, Console::Yes },
{ "unlearn", HandlePetUnlearnCommand, rbac::RBAC_PERM_COMMAND_PET_UNLEARN, Console::No }
};
static ChatCommandTable commandTable =
@@ -126,6 +130,103 @@ public:
return true;
}
static bool HandlePetDeleteCommand(ChatHandler* handler, PlayerIdentifier owner, uint32 petNumber)
{
ObjectGuid ownerGuid = owner.GetGUID();
QueryResult result = CharacterDatabase.Query(
"SELECT entry, name FROM character_pet WHERE id = {} AND owner = {}",
petNumber, ownerGuid.GetCounter());
if (!result)
{
handler->SendErrorMessage(LANG_PET_DELETE_NOT_FOUND, petNumber, owner.GetName(),
ownerGuid.GetCounter());
return false;
}
if (Player* online = owner.GetConnectedPlayer())
{
if (Pet* activePet = online->GetPet())
{
if (activePet->GetCharmInfo() &&
activePet->GetCharmInfo()->GetPetNumber() == petNumber)
{
online->RemovePet(activePet, PET_SAVE_AS_DELETED);
}
}
// Drop the pet from the in-memory PetStable so the stable UI / call-pet
// logic does not reference a row that no longer exists in the database.
if (PetStable* stable = online->GetPetStable())
{
if (stable->CurrentPet && stable->CurrentPet->PetNumber == petNumber)
stable->CurrentPet.reset();
for (Optional<PetStable::PetInfo>& stabled : stable->StabledPets)
if (stabled && stabled->PetNumber == petNumber)
stabled.reset();
stable->UnslottedPets.erase(
std::remove_if(stable->UnslottedPets.begin(), stable->UnslottedPets.end(),
[petNumber](PetStable::PetInfo const& p) { return p.PetNumber == petNumber; }),
stable->UnslottedPets.end());
}
}
Field* fields = result->Fetch();
uint32 entry = fields[0].Get<uint32>();
std::string name = fields[1].Get<std::string>();
std::string creatureName = "<unknown>";
if (CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(entry))
creatureName = cInfo->Name;
Pet::DeleteFromDB(petNumber);
handler->PSendSysMessage(LANG_PET_DELETE_SUCCESS, petNumber, name, entry, creatureName,
owner.GetName(), ownerGuid.GetCounter());
return true;
}
static bool HandlePetListCommand(ChatHandler* handler, PlayerIdentifier owner)
{
ObjectGuid ownerGuid = owner.GetGUID();
QueryResult result = CharacterDatabase.Query(
"SELECT id, entry, level, slot, name, PetType FROM character_pet "
"WHERE owner = {} ORDER BY slot, id",
ownerGuid.GetCounter());
if (!result)
{
handler->PSendSysMessage(LANG_PET_LIST_EMPTY, owner.GetName(), ownerGuid.GetCounter());
return true;
}
handler->PSendSysMessage(LANG_PET_LIST_HEADER, owner.GetName(), ownerGuid.GetCounter());
do
{
Field* fields = result->Fetch();
uint32 petNumber = fields[0].Get<uint32>();
uint32 entry = fields[1].Get<uint32>();
uint8 level = fields[2].Get<uint8>();
uint8 slot = fields[3].Get<uint8>();
std::string name = fields[4].Get<std::string>();
uint8 petType = fields[5].Get<uint8>();
std::string creatureName = "<unknown>";
if (CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(entry))
creatureName = cInfo->Name;
handler->PSendSysMessage(LANG_PET_LIST_ENTRY, petNumber, uint32(slot), entry,
creatureName, name, uint32(level), uint32(petType));
} while (result->NextRow());
return true;
}
static bool HandlePetUnlearnCommand(ChatHandler* handler, SpellInfo const* spell)
{
if (!spell)