diff --git a/data/sql/updates/pending_db_auth/rev_1777755418998123400.sql b/data/sql/updates/pending_db_auth/rev_1777755418998123400.sql new file mode 100644 index 0000000000..dbe8b9f9ab --- /dev/null +++ b/data/sql/updates/pending_db_auth/rev_1777755418998123400.sql @@ -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); diff --git a/data/sql/updates/pending_db_world/rev_1777755418998123400.sql b/data/sql/updates/pending_db_world/rev_1777755418998123400.sql new file mode 100644 index 0000000000..2ef2c198d8 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1777755418998123400.sql @@ -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}).'); diff --git a/src/server/game/Accounts/RBAC.h b/src/server/game/Accounts/RBAC.h index c4af7947e0..de2419a2f1 100644 --- a/src/server/game/Accounts/RBAC.h +++ b/src/server/game/Accounts/RBAC.h @@ -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 }; diff --git a/src/server/game/Miscellaneous/Language.h b/src/server/game/Miscellaneous/Language.h index de55d37cd9..9ab22993ab 100644 --- a/src/server/game/Miscellaneous/Language.h +++ b/src/server/game/Miscellaneous/Language.h @@ -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 diff --git a/src/server/scripts/Commands/cs_pet.cpp b/src/server/scripts/Commands/cs_pet.cpp index 42137e3674..ed46d29823 100644 --- a/src/server/scripts/Commands/cs_pet.cpp +++ b/src/server/scripts/Commands/cs_pet.cpp @@ -17,6 +17,8 @@ #include "Chat.h" #include "CommandScript.h" +#include "DatabaseEnv.h" +#include #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& 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(); + std::string name = fields[1].Get(); + + std::string creatureName = ""; + 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 entry = fields[1].Get(); + uint8 level = fields[2].Get(); + uint8 slot = fields[3].Get(); + std::string name = fields[4].Get(); + uint8 petType = fields[5].Get(); + + std::string creatureName = ""; + 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)