diff --git a/common/database.cpp b/common/database.cpp index a3405970dd..f1d3a3757b 100644 --- a/common/database.cpp +++ b/common/database.cpp @@ -50,6 +50,7 @@ #include "../common/repositories/raid_members_repository.h" #include "../common/repositories/reports_repository.h" #include "../common/repositories/variables_repository.h" +#include "../common/repositories/character_pet_name_repository.h" #include "../common/events/player_event_logs.h" // Disgrace: for windows compile @@ -313,6 +314,12 @@ bool Database::ReserveName(uint32 account_id, const std::string& name) return false; } + const auto& p = CharacterPetNameRepository::GetWhere(*this, where_filter); + if (!p.empty()) { + LogInfo("Account [{}] requested name [{}] but name is already taken by an Pet", account_id, name); + return false; + } + auto e = CharacterDataRepository::NewEntity(); e.account_id = account_id; diff --git a/common/database/database_update_manifest.cpp b/common/database/database_update_manifest.cpp index 582864c434..57819b700a 100644 --- a/common/database/database_update_manifest.cpp +++ b/common/database/database_update_manifest.cpp @@ -6320,6 +6320,19 @@ ALTER TABLE `data_buckets` ADD KEY `idx_account_id_key` (`account_id`, `key`); )", .content_schema_update = false }, + ManifestEntry{ + .version = 9293, + .description = "2025_01_10_create_pet_names_table.sql", + .check = "SHOW TABLES LIKE 'character_pet_name'", + .condition = "empty", + .match = "", + .sql = R"( +CREATE TABLE `character_pet_name` ( + `character_id` INT(11) NOT NULL PRIMARY KEY, + `name` VARCHAR(64) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +)", + }, // -- template; copy/paste this when you need to create a new entry // ManifestEntry{ // .version = 9228, diff --git a/common/database_schema.h b/common/database_schema.h index b86e16f448..9f23001b18 100644 --- a/common/database_schema.h +++ b/common/database_schema.h @@ -64,6 +64,7 @@ namespace DatabaseSchema { {"character_pet_buffs", "char_id"}, {"character_pet_info", "char_id"}, {"character_pet_inventory", "char_id"}, + {"character_pet_name", "character_id"}, {"character_peqzone_flags", "id"}, {"character_potionbelt", "id"}, {"character_skills", "id"}, diff --git a/common/emu_oplist.h b/common/emu_oplist.h index 6b5ab12ab9..eefcdfee87 100644 --- a/common/emu_oplist.h +++ b/common/emu_oplist.h @@ -77,6 +77,7 @@ N(OP_CashReward), N(OP_CastSpell), N(OP_ChangeSize), N(OP_ChannelMessage), +N(OP_ChangePetName), N(OP_CharacterCreate), N(OP_CharacterCreateRequest), N(OP_CharInventory), @@ -284,6 +285,8 @@ N(OP_InspectMessageUpdate), N(OP_InspectRequest), N(OP_InstillDoubt), N(OP_InterruptCast), +N(OP_InvokeChangePetName), +N(OP_InvokeChangePetNameImmediate), N(OP_ItemLinkClick), N(OP_ItemLinkResponse), N(OP_ItemLinkText), diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index bfe5abf442..f6e2b9ed1b 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -5819,6 +5819,21 @@ struct ChangeSize_Struct /*16*/ }; +struct ChangePetName_Struct { +/*00*/ char new_pet_name[64]; +/*40*/ char pet_owner_name[64]; +/*80*/ int response_code; +}; + +enum ChangePetNameResponse : int { + Denied = 0, // 5167 You have requested an invalid name or a Customer Service Representative has denied your name request. Please try another name. + Accepted = 1, // 5976 Your request for a name change was successful. + Timeout = -3, // 5979 You must wait longer before submitting another name request. Please try again in a few minutes. + NotEligible = -4, // 5980 Your character is not eligible for a name change. + Pending = -5, // 5193 You already have a name change pending. Please wait until it is fully processed before attempting another name change. + Unhandled = -1 +}; + // New OpCode/Struct for SoD+ struct GroupMakeLeader_Struct { diff --git a/common/repositories/base/base_character_pet_name_repository.h b/common/repositories/base/base_character_pet_name_repository.h new file mode 100644 index 0000000000..59a8a572f2 --- /dev/null +++ b/common/repositories/base/base_character_pet_name_repository.h @@ -0,0 +1,392 @@ +/** + * DO NOT MODIFY THIS FILE + * + * This repository was automatically generated and is NOT to be modified directly. + * Any repository modifications are meant to be made to the repository extending the base. + * Any modifications to base repositories are to be made by the generator only + * + * @generator ./utils/scripts/generators/repository-generator.pl + * @docs https://docs.eqemu.io/developer/repositories + */ + +#ifndef EQEMU_BASE_CHARACTER_PET_NAME_REPOSITORY_H +#define EQEMU_BASE_CHARACTER_PET_NAME_REPOSITORY_H + +#include "../../database.h" +#include "../../strings.h" +#include + +class BaseCharacterPetNameRepository { +public: + struct CharacterPetName { + int32_t character_id; + std::string name; + }; + + static std::string PrimaryKey() + { + return std::string("character_id"); + } + + static std::vector Columns() + { + return { + "character_id", + "name", + }; + } + + static std::vector SelectColumns() + { + return { + "character_id", + "name", + }; + } + + static std::string ColumnsRaw() + { + return std::string(Strings::Implode(", ", Columns())); + } + + static std::string SelectColumnsRaw() + { + return std::string(Strings::Implode(", ", SelectColumns())); + } + + static std::string TableName() + { + return std::string("character_pet_name"); + } + + static std::string BaseSelect() + { + return fmt::format( + "SELECT {} FROM {}", + SelectColumnsRaw(), + TableName() + ); + } + + static std::string BaseInsert() + { + return fmt::format( + "INSERT INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static CharacterPetName NewEntity() + { + CharacterPetName e{}; + + e.character_id = 0; + e.name = ""; + + return e; + } + + static CharacterPetName GetCharacterPetName( + const std::vector &character_pet_names, + int character_pet_name_id + ) + { + for (auto &character_pet_name : character_pet_names) { + if (character_pet_name.character_id == character_pet_name_id) { + return character_pet_name; + } + } + + return NewEntity(); + } + + static CharacterPetName FindOne( + Database& db, + int character_pet_name_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {} = {} LIMIT 1", + BaseSelect(), + PrimaryKey(), + character_pet_name_id + ) + ); + + auto row = results.begin(); + if (results.RowCount() == 1) { + CharacterPetName e{}; + + e.character_id = row[0] ? static_cast(atoi(row[0])) : 0; + e.name = row[1] ? row[1] : ""; + + return e; + } + + return NewEntity(); + } + + static int DeleteOne( + Database& db, + int character_pet_name_id + ) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {} = {}", + TableName(), + PrimaryKey(), + character_pet_name_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int UpdateOne( + Database& db, + const CharacterPetName &e + ) + { + std::vector v; + + auto columns = Columns(); + + v.push_back(columns[0] + " = " + std::to_string(e.character_id)); + v.push_back(columns[1] + " = '" + Strings::Escape(e.name) + "'"); + + auto results = db.QueryDatabase( + fmt::format( + "UPDATE {} SET {} WHERE {} = {}", + TableName(), + Strings::Implode(", ", v), + PrimaryKey(), + e.character_id + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static CharacterPetName InsertOne( + Database& db, + CharacterPetName e + ) + { + std::vector v; + + v.push_back(std::to_string(e.character_id)); + v.push_back("'" + Strings::Escape(e.name) + "'"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseInsert(), + Strings::Implode(",", v) + ) + ); + + if (results.Success()) { + e.character_id = results.LastInsertedID(); + return e; + } + + e = NewEntity(); + + return e; + } + + static int InsertMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.character_id)); + v.push_back("'" + Strings::Escape(e.name) + "'"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseInsert(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static std::vector All(Database& db) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{}", + BaseSelect() + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + CharacterPetName e{}; + + e.character_id = row[0] ? static_cast(atoi(row[0])) : 0; + e.name = row[1] ? row[1] : ""; + + all_entries.push_back(e); + } + + return all_entries; + } + + static std::vector GetWhere(Database& db, const std::string &where_filter) + { + std::vector all_entries; + + auto results = db.QueryDatabase( + fmt::format( + "{} WHERE {}", + BaseSelect(), + where_filter + ) + ); + + all_entries.reserve(results.RowCount()); + + for (auto row = results.begin(); row != results.end(); ++row) { + CharacterPetName e{}; + + e.character_id = row[0] ? static_cast(atoi(row[0])) : 0; + e.name = row[1] ? row[1] : ""; + + all_entries.push_back(e); + } + + return all_entries; + } + + static int DeleteWhere(Database& db, const std::string &where_filter) + { + auto results = db.QueryDatabase( + fmt::format( + "DELETE FROM {} WHERE {}", + TableName(), + where_filter + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int Truncate(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "TRUNCATE TABLE {}", + TableName() + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int64 GetMaxId(Database& db) + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COALESCE(MAX({}), 0) FROM {}", + PrimaryKey(), + TableName() + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static int64 Count(Database& db, const std::string &where_filter = "") + { + auto results = db.QueryDatabase( + fmt::format( + "SELECT COUNT(*) FROM {} {}", + TableName(), + (where_filter.empty() ? "" : "WHERE " + where_filter) + ) + ); + + return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0); + } + + static std::string BaseReplace() + { + return fmt::format( + "REPLACE INTO {} ({}) ", + TableName(), + ColumnsRaw() + ); + } + + static int ReplaceOne( + Database& db, + const CharacterPetName &e + ) + { + std::vector v; + + v.push_back(std::to_string(e.character_id)); + v.push_back("'" + Strings::Escape(e.name) + "'"); + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES ({})", + BaseReplace(), + Strings::Implode(",", v) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } + + static int ReplaceMany( + Database& db, + const std::vector &entries + ) + { + std::vector insert_chunks; + + for (auto &e: entries) { + std::vector v; + + v.push_back(std::to_string(e.character_id)); + v.push_back("'" + Strings::Escape(e.name) + "'"); + + insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); + } + + std::vector v; + + auto results = db.QueryDatabase( + fmt::format( + "{} VALUES {}", + BaseReplace(), + Strings::Implode(",", insert_chunks) + ) + ); + + return (results.Success() ? results.RowsAffected() : 0); + } +}; + +#endif //EQEMU_BASE_CHARACTER_PET_NAME_REPOSITORY_H diff --git a/common/repositories/character_pet_name_repository.h b/common/repositories/character_pet_name_repository.h new file mode 100644 index 0000000000..8fbb1e94bf --- /dev/null +++ b/common/repositories/character_pet_name_repository.h @@ -0,0 +1,13 @@ +#ifndef EQEMU_CHARACTER_PET_NAME_REPOSITORY_H +#define EQEMU_CHARACTER_PET_NAME_REPOSITORY_H + +#include "../database.h" +#include "../strings.h" +#include "base/base_character_pet_name_repository.h" + +class CharacterPetNameRepository: public BaseCharacterPetNameRepository { +public: + // Custom extended repository methods here +}; + +#endif //EQEMU_CHARACTER_PET_NAME_REPOSITORY_H diff --git a/common/version.h b/common/version.h index 671a0c921d..a99b965751 100644 --- a/common/version.h +++ b/common/version.h @@ -42,7 +42,7 @@ * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9292 +#define CURRENT_BINARY_DATABASE_VERSION 9293 #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9045 #endif diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf index 93d6f04637..a4e6d68ed9 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -735,3 +735,12 @@ OP_PickZone=0xaaba #evolve item related OP_EvolveItem=0x7cfb + +# This is bugged in ROF2 +# OP_InvokeChangePetNameImmediate is supposed to write DisablePetNameChangeReminder=0 to reset the 'nag reminder' +# It actually sets DisableNameChangeReminder=0 (player name change nag reminder). Additionally, clicking the +# 'Don't remind me later' button sets DisableNameChangeReminder=1 +# This can be fixed with a client patch. +OP_InvokeChangePetNameImmediate=0x046d +OP_InvokeChangePetName=0x4506 +OP_ChangePetName=0x5dab \ No newline at end of file diff --git a/zone/client.cpp b/zone/client.cpp index 697d7c752a..1c577c08d2 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -66,6 +66,7 @@ extern volatile bool RunLoops; #include "../common/repositories/character_spells_repository.h" #include "../common/repositories/character_disciplines_repository.h" #include "../common/repositories/character_data_repository.h" +#include "../common/repositories/character_pet_name_repository.h" #include "../common/repositories/discovered_items_repository.h" #include "../common/repositories/inventory_repository.h" #include "../common/repositories/keyring_repository.h" @@ -1828,7 +1829,7 @@ void Client::FriendsWho(char *FriendsString) { Message(0, "Error: World server disconnected"); else { auto pack = - new ServerPacket(ServerOP_FriendsWho, sizeof(ServerFriendsWho_Struct) + strlen(FriendsString)); + new ServerPacket(ServerOP_FriendsWho, sizeof(ServerFriendsWho_Struct) + strlen(FriendsString)); ServerFriendsWho_Struct* FriendsWho = (ServerFriendsWho_Struct*) pack->pBuffer; FriendsWho->FromID = GetID(); strcpy(FriendsWho->FromName, GetName()); @@ -2209,7 +2210,7 @@ void Client::Stand() { } void Client::Sit() { - SetAppearance(eaSitting, false); + SetAppearance(eaSitting, false); } void Client::ChangeLastName(std::string last_name) { @@ -4392,6 +4393,83 @@ void Client::KeyRingList() } } +bool Client::IsPetNameChangeAllowed() { + DataBucketKey k = GetScopedBucketKeys(); + k.key = "PetNameChangesAllowed"; + + auto b = DataBucket::GetData(k); + if (!b.value.empty()) { + return true; + } + + return false; +} + +void Client::InvokeChangePetName(bool immediate) { + if (!IsPetNameChangeAllowed()) { + return; + } + + auto packet_op = immediate ? OP_InvokeChangePetNameImmediate : OP_InvokeChangePetName; + + auto outapp = new EQApplicationPacket(packet_op, 0); + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::GrantPetNameChange() { + DataBucketKey k = GetScopedBucketKeys(); + k.key = "PetNameChangesAllowed"; + k.value = "true"; + DataBucket::SetData(k); + + InvokeChangePetName(true); +} + +void Client::ClearPetNameChange() { + DataBucketKey k = GetScopedBucketKeys(); + k.key = "PetNameChangesAllowed"; + + DataBucket::DeleteData(k); +} + +bool Client::ChangePetName(std::string new_name) +{ + if (new_name.empty()) { + return false; + } + + if (!IsPetNameChangeAllowed()) { + return false; + } + + auto pet = GetPet(); + if (!pet) { + return false; + } + + if (pet->GetName() == new_name) { + return false; + } + + if (!database.CheckNameFilter(new_name) || database.IsNameUsed(new_name)) { + return false; + } + + CharacterPetNameRepository::ReplaceOne( + database, + CharacterPetNameRepository::CharacterPetName{ + .character_id = static_cast(CharacterID()), + .name = new_name + } + ); + + pet->TempName(new_name.c_str()); + + ClearPetNameChange(); + return true; +} + bool Client::IsDiscovered(uint32 item_id) { const auto& l = DiscoveredItemsRepository::GetWhere( database, @@ -5628,13 +5706,13 @@ bool Client::TryReward(uint32 claim_id) if (amt == 1) { query = StringFormat("DELETE FROM account_rewards " - "WHERE account_id = %i AND reward_id = %i", - AccountID(), claim_id); + "WHERE account_id = %i AND reward_id = %i", + AccountID(), claim_id); auto results = database.QueryDatabase(query); } else { query = StringFormat("UPDATE account_rewards SET amount = (amount-1) " - "WHERE account_id = %i AND reward_id = %i", - AccountID(), claim_id); + "WHERE account_id = %i AND reward_id = %i", + AccountID(), claim_id); auto results = database.QueryDatabase(query); } @@ -8367,7 +8445,7 @@ int Client::GetQuiverHaste(int delay) for (int r = EQ::invslot::GENERAL_BEGIN; r <= EQ::invslot::GENERAL_END; r++) { pi = GetInv().GetItem(r); if (pi && pi->IsClassBag() && pi->GetItem()->BagType == EQ::item::BagTypeQuiver && - pi->GetItem()->BagWR > 0) + pi->GetItem()->BagWR > 0) break; if (r == EQ::invslot::GENERAL_END) // we will get here if we don't find a valid quiver @@ -9841,7 +9919,7 @@ const ExpeditionLockoutTimer* Client::GetExpeditionLockout( for (const auto& expedition_lockout : m_expedition_lockouts) { if ((include_expired || !expedition_lockout.IsExpired()) && - expedition_lockout.IsSameLockout(expedition_name, event_name)) + expedition_lockout.IsSameLockout(expedition_name, event_name)) { return &expedition_lockout; } @@ -9856,7 +9934,7 @@ std::vector Client::GetExpeditionLockouts( for (const auto& lockout : m_expedition_lockouts) { if ((include_expired || !lockout.IsExpired()) && - lockout.GetExpeditionName() == expedition_name) + lockout.GetExpeditionName() == expedition_name) { lockouts.emplace_back(lockout); } diff --git a/zone/client.h b/zone/client.h index 146cabd34d..d7a61f6622 100644 --- a/zone/client.h +++ b/zone/client.h @@ -307,6 +307,11 @@ class Client : public Mob void KeyRingAdd(uint32 item_id); bool KeyRingCheck(uint32 item_id); void KeyRingList(); + bool IsPetNameChangeAllowed(); + void GrantPetNameChange(); + void ClearPetNameChange(); + void InvokeChangePetName(bool immediate = true); + bool ChangePetName(std::string new_name); bool IsClient() const override { return true; } bool IsOfClientBot() const override { return true; } bool IsOfClientBotMerc() const override { return true; } @@ -2267,6 +2272,7 @@ class Client : public Mob const std::string &GetMailKeyFull() const; const std::string &GetMailKey() const; void ShowZoneShardMenu(); + void Handle_OP_ChangePetName(const EQApplicationPacket *app); }; #endif diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 066cb49cf5..95a9a970ec 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -66,6 +66,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "../common/repositories/character_corpses_repository.h" #include "../common/repositories/guild_tributes_repository.h" #include "../common/repositories/buyer_buy_lines_repository.h" +#include "../common/repositories/character_pet_name_repository.h" #include "../common/events/player_event_logs.h" #include "../common/repositories/character_stats_record_repository.h" @@ -160,6 +161,7 @@ void MapOpcodes() ConnectedOpcodes[OP_CancelTrade] = &Client::Handle_OP_CancelTrade; ConnectedOpcodes[OP_CastSpell] = &Client::Handle_OP_CastSpell; ConnectedOpcodes[OP_ChannelMessage] = &Client::Handle_OP_ChannelMessage; + ConnectedOpcodes[OP_ChangePetName] = &Client::Handle_OP_ChangePetName; ConnectedOpcodes[OP_ClearBlockedBuffs] = &Client::Handle_OP_ClearBlockedBuffs; ConnectedOpcodes[OP_ClearNPCMarks] = &Client::Handle_OP_ClearNPCMarks; ConnectedOpcodes[OP_ClearSurname] = &Client::Handle_OP_ClearSurname; @@ -820,6 +822,10 @@ void Client::CompleteConnect() CharacterID() ) ); + + if (IsPetNameChangeAllowed()) { + InvokeChangePetName(false); + } } if(ClientVersion() == EQ::versions::ClientVersion::RoF2 && RuleB(Parcel, EnableParcelMerchants)) { @@ -4568,6 +4574,27 @@ void Client::Handle_OP_ChannelMessage(const EQApplicationPacket *app) return; } +void Client::Handle_OP_ChangePetName(const EQApplicationPacket *app) { + if (app->size != sizeof(ChangePetName_Struct)) { + LogError("Got OP_ChangePetName of incorrect size. Expected [{}], got [{}].", sizeof(ChangePetName_Struct), app->size); + return; + } + + auto p = (ChangePetName_Struct *) app->pBuffer; + if (!IsPetNameChangeAllowed()) { + p->response_code = ChangePetNameResponse::NotEligible; + QueuePacket(app); + return; + } + + p->response_code = ChangePetNameResponse::Denied; + if (ChangePetName(p->new_pet_name)) { + p->response_code = ChangePetNameResponse::Accepted; + } + + QueuePacket(app); +} + void Client::Handle_OP_ClearBlockedBuffs(const EQApplicationPacket *app) { if (!RuleB(Spells, EnableBlockedBuffs)) diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index 4be953bb40..83e2a3fb3e 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -3458,6 +3458,12 @@ void Lua_Client::ShowZoneShardMenu() self->ShowZoneShardMenu(); } +void Lua_Client::GrantPetNameChange() +{ + Lua_Safe_Call_Void(); + self->GrantPetNameChange(); +} + void Lua_Client::SetAAEXPPercentage(uint8 percentage) { Lua_Safe_Call_Void(); @@ -3568,6 +3574,7 @@ luabind::scope lua_register_client() { .def("CanHaveSkill", (bool(Lua_Client::*)(int))&Lua_Client::CanHaveSkill) .def("CashReward", &Lua_Client::CashReward) .def("ChangeLastName", (void(Lua_Client::*)(std::string))&Lua_Client::ChangeLastName) + .def("GrantPetNameChange", &Lua_Client::GrantPetNameChange) .def("CharacterID", (uint32(Lua_Client::*)(void))&Lua_Client::CharacterID) .def("CheckIncreaseSkill", (void(Lua_Client::*)(int,Lua_Mob))&Lua_Client::CheckIncreaseSkill) .def("CheckIncreaseSkill", (void(Lua_Client::*)(int,Lua_Mob,int))&Lua_Client::CheckIncreaseSkill) diff --git a/zone/lua_client.h b/zone/lua_client.h index 082504fc06..ed02e65927 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -597,6 +597,7 @@ class Lua_Client : public Lua_Mob bool ReloadDataBuckets(); void ShowZoneShardMenu(); + void GrantPetNameChange(); Lua_Expedition CreateExpedition(luabind::object expedition_info); Lua_Expedition CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players); diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index c4a66ec2d3..3cf1ad0d75 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -3229,6 +3229,11 @@ perl::array Perl_Client_GetInventorySlots(Client* self) return result; } +void Perl_Client_GrantPetNameChange(Client* self) +{ + self->GrantPetNameChange(); +} + void Perl_Client_SetAAEXPPercentage(Client* self, uint8 percentage) { self->SetAAEXPPercentage(percentage); @@ -3335,6 +3340,7 @@ void perl_register_client() package.add("CanHaveSkill", &Perl_Client_CanHaveSkill); package.add("CashReward", &Perl_Client_CashReward); package.add("ChangeLastName", &Perl_Client_ChangeLastName); + package.add("GrantPetNameChange", &Perl_Client_GrantPetNameChange); package.add("CharacterID", &Perl_Client_CharacterID); package.add("CheckIncreaseSkill", (bool(*)(Client*, int))&Perl_Client_CheckIncreaseSkill); package.add("CheckIncreaseSkill", (bool(*)(Client*, int, int))&Perl_Client_CheckIncreaseSkill); diff --git a/zone/pets.cpp b/zone/pets.cpp index 08fc523c15..da84ca4e14 100644 --- a/zone/pets.cpp +++ b/zone/pets.cpp @@ -22,6 +22,7 @@ #include "../common/repositories/pets_repository.h" #include "../common/repositories/pets_beastlord_data_repository.h" +#include "../common/repositories/character_pet_name_repository.h" #include "entity.h" #include "client.h" @@ -164,6 +165,12 @@ void Mob::MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower, // 4 - Keep DB name // 5 - `s ward + if (IsClient() && !petname) { + const auto vanity_name = CharacterPetNameRepository::FindOne(database, CastToClient()->CharacterID()); + if (!vanity_name.name.empty()) { + petname = vanity_name.name.c_str(); + } + } if (petname != nullptr) { // Name was provided, use it.