diff --git a/common/database.cpp b/common/database.cpp index 44eccb744..cea5ddf2b 100644 --- a/common/database.cpp +++ b/common/database.cpp @@ -955,6 +955,29 @@ bool Database::UpdateName(const std::string& old_name, const std::string& new_na return CharacterDataRepository::UpdateOne(*this, e); } +bool Database::UpdateNameByID(const int character_id, const std::string& new_name) +{ + LogInfo("Renaming [{}] to [{}]", character_id, new_name); + + auto l = CharacterDataRepository::GetWhere( + *this, + fmt::format( + "`id` = {}", + character_id + ) + ); + + if (l.empty()) { + return false; + } + + auto& e = l.front(); + + e.name = new_name; + + return CharacterDataRepository::UpdateOne(*this, e); +} + bool Database::IsNameUsed(const std::string& name) { if (RuleB(Bots, Enabled)) { @@ -982,6 +1005,20 @@ bool Database::IsNameUsed(const std::string& name) return !character_data.empty(); } +// Players cannot have the same name as a pet vanity name, or memory corruption occurs. +bool Database::IsPetNameUsed(const std::string& name) +{ + const auto& pet_name_data = CharacterPetNameRepository::GetWhere( + *this, + fmt::format( + "`name` = '{}'", + Strings::Escape(name) + ) + ); + + return !pet_name_data.empty(); +} + uint32 Database::GetServerType() { const auto& l = VariablesRepository::GetWhere(*this, "`varname` = 'ServerType' LIMIT 1"); diff --git a/common/database.h b/common/database.h index 3d263755c..1928d5025 100644 --- a/common/database.h +++ b/common/database.h @@ -103,6 +103,7 @@ public: bool ReserveName(uint32 account_id, const std::string& name); bool SaveCharacterCreate(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp); bool UpdateName(const std::string& old_name, const std::string& new_name); + bool UpdateNameByID(const int character_id, const std::string& new_name); bool CopyCharacter( const std::string& source_character_name, const std::string& destination_character_name, @@ -116,6 +117,7 @@ public: bool CheckGMIPs(const std::string& login_ip, uint32 account_id); bool CheckNameFilter(const std::string& name, bool surname = false); bool IsNameUsed(const std::string& name); + bool IsPetNameUsed(const std::string& name); uint32 GetAccountIDByChar(const std::string& name, uint32* character_id = 0); uint32 GetAccountIDByChar(uint32 character_id); diff --git a/common/emu_oplist.h b/common/emu_oplist.h index f91bf235f..268fa97cd 100644 --- a/common/emu_oplist.h +++ b/common/emu_oplist.h @@ -287,6 +287,8 @@ N(OP_InstillDoubt), N(OP_InterruptCast), N(OP_InvokeChangePetName), N(OP_InvokeChangePetNameImmediate), +N(OP_InvokeNameChangeImmediate), +N(OP_InvokeNameChangeLazy), N(OP_ItemLinkClick), N(OP_ItemLinkResponse), N(OP_ItemLinkText), diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index caf0e4f63..bf20f6502 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -5832,21 +5832,28 @@ struct ChangeSize_Struct /*16*/ }; +enum ChangeNameResponse : 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 = -1, // 5977: "Your request for a name change has timed out. Please try again later." + ServerError = -2, // 5978: "The server had an error while processing your name request. Please try again later." + RateLimited = -3, // 5979: "You must wait longer before submitting another name request. Please try again in a few minutes." + Ineligible = -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." +}; + +struct AltChangeName_Struct { +/*00*/ char new_name[64]; +/*40*/ char old_name[64]; +/*80*/ int response_code; +}; + 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/ruletypes.h b/common/ruletypes.h index 9f5508fac..79df22248 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -231,6 +231,7 @@ RULE_INT(Character, MendAlwaysSucceedValue, 199, "Value at which mend will alway RULE_BOOL(Character, SneakAlwaysSucceedOver100, false, "When sneak skill is over 100, always succeed sneak/hide. Default: false") RULE_INT(Character, BandolierSwapDelay, 0, "Bandolier swap delay in milliseconds, default is 0") RULE_BOOL(Character, EnableHackedFastCampForGM, false, "Enables hacked fast camp for GM clients, if the GM doesn't have a hacked client they'll camp like normal") +RULE_BOOL(Character, AlwaysAllowNameChange, false, "Enable this option to allow /changename to work without enabling a name change via scripts.") RULE_CATEGORY_END() RULE_CATEGORY(Mercs) diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf index 725d9d947..3533aaa87 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -746,3 +746,6 @@ OP_TradeSkillRecipeInspect=0x4f7e OP_InvokeChangePetNameImmediate=0x046d OP_InvokeChangePetName=0x4506 OP_ChangePetName=0x5dab + +OP_InvokeNameChangeImmediate=0x4fe2 +OP_InvokeNameChangeLazy=0x2f2e diff --git a/zone/client.cpp b/zone/client.cpp index 7e8a8cd7c..b91b4de59 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -2549,40 +2549,59 @@ void Client::ChangeLastName(std::string last_name) { safe_delete(outapp); } -bool Client::ChangeFirstName(const char* in_firstname, const char* gmname) +// Deprecated, this packet does not actually work in ROF2 +bool Client::ChangeFirstName(const std::string in_firstname, const std::string gmname) { - // check duplicate name - bool used_name = database.IsNameUsed((const char*) in_firstname); - if (used_name) { + if (!ChangeFirstName(in_firstname)) { return false; } - // update character_ - if(!database.UpdateName(GetName(), in_firstname)) - return false; - - // update pp - memset(m_pp.name, 0, sizeof(m_pp.name)); - snprintf(m_pp.name, sizeof(m_pp.name), "%s", in_firstname); - strcpy(name, m_pp.name); - Save(); - // send name update packet auto outapp = new EQApplicationPacket(OP_GMNameChange, sizeof(GMName_Struct)); GMName_Struct* gmn=(GMName_Struct*)outapp->pBuffer; - strn0cpy(gmn->gmname,gmname,64); + strn0cpy(gmn->gmname,gmname.c_str(),64); strn0cpy(gmn->oldname,GetName(),64); - strn0cpy(gmn->newname,in_firstname,64); + strn0cpy(gmn->newname,in_firstname.c_str(),64); gmn->unknown[0] = 1; gmn->unknown[1] = 1; gmn->unknown[2] = 1; entity_list.QueueClients(this, outapp, false); safe_delete(outapp); + // success + return true; +} + +bool Client::ChangeFirstName(const std::string in_firstname) +{ + // check duplicate name + bool used_name = database.IsNameUsed(in_firstname) || database.IsPetNameUsed(in_firstname); + if (used_name || !database.CheckNameFilter(in_firstname, false)) { + return false; + } + + // update character_ + if(!database.UpdateNameByID(CharacterID(), in_firstname)) + return false; + + // Send Name Update to Clients + SendRename(this, GetName(), in_firstname.c_str()); + SetName(in_firstname.c_str()); + + // update pp + memset(m_pp.name, 0, sizeof(m_pp.name)); + snprintf(m_pp.name, sizeof(m_pp.name), "%s", in_firstname.c_str()); + strcpy(name, m_pp.name); + Save(); + + // Update the active char in account table + database.UpdateLiveChar(in_firstname, AccountID()); + // finally, update the /who list UpdateWho(); // success + ClearNameChange(); return true; } @@ -4737,6 +4756,57 @@ bool Client::KeyRingRemove(uint32 item_id) ); } +bool Client::IsNameChangeAllowed() { + if (RuleB(Character, AlwaysAllowNameChange)) { + return true; + } + + auto k = GetScopedBucketKeys(); + k.key = "name_change_allowed"; + + auto b = DataBucket::GetData(k); + if (!b.value.empty()) { + return true; + } + + return false; +} + +bool Client::ClearNameChange() { + if (!IsNameChangeAllowed()) { + return false; + } + + auto k = GetScopedBucketKeys(); + k.key = "name_change_allowed"; + + DataBucket::DeleteData(k); + + return true; +} + +void Client::InvokeChangeNameWindow(bool immediate) { + if (!IsNameChangeAllowed()) { + return; + } + + auto packet_op = immediate ? OP_InvokeNameChangeImmediate : OP_InvokeNameChangeLazy; + + auto outapp = new EQApplicationPacket(packet_op, 0); + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::GrantNameChange() { + + auto k = GetScopedBucketKeys(); + k.key = "name_change_allowed"; + k.value = "allowed"; // potentially put a timestamp here + DataBucket::SetData(k); + + InvokeChangeNameWindow(true); +} + bool Client::IsPetNameChangeAllowed() { if (RuleB(Pets, AlwaysAllowPetRename)) { return true; diff --git a/zone/client.h b/zone/client.h index 0bbee8af4..6b6cb15ed 100644 --- a/zone/client.h +++ b/zone/client.h @@ -332,6 +332,10 @@ public: bool KeyRingClear(); bool KeyRingRemove(uint32 item_id); void KeyRingList(); + bool IsNameChangeAllowed(); + void InvokeChangeNameWindow(bool immediate = true); + bool ClearNameChange(); + void GrantNameChange(); bool IsPetNameChangeAllowed(); void GrantPetNameChange(); void ClearPetNameChange(); @@ -511,7 +515,8 @@ public: bool AutoAttackEnabled() const { return auto_attack; } bool AutoFireEnabled() const { return auto_fire; } - bool ChangeFirstName(const char* in_firstname,const char* gmname); + bool ChangeFirstName(const std::string in_firstname,const std::string gmname); + bool ChangeFirstName(const std::string in_firstname); void Duck(); void Stand(); diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index ee8d8777f..5ed9f4ae4 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -826,6 +826,10 @@ void Client::CompleteConnect() if (IsPetNameChangeAllowed() && !RuleB(Pets, AlwaysAllowPetRename)) { InvokeChangePetName(false); } + + if (IsNameChangeAllowed() && !RuleB(Character, AlwaysAllowNameChange)) { + InvokeChangeNameWindow(false); + } } if(ClientVersion() == EQ::versions::ClientVersion::RoF2 && RuleB(Parcel, EnableParcelMerchants)) { @@ -4256,7 +4260,7 @@ void Client::Handle_OP_Camp(const EQApplicationPacket *app) else { OnDisconnect(true); } - + return; } @@ -4548,14 +4552,14 @@ void Client::Handle_OP_ChangePetName(const EQApplicationPacket *app) { auto p = (ChangePetName_Struct *) app->pBuffer; if (!IsPetNameChangeAllowed()) { - p->response_code = ChangePetNameResponse::NotEligible; + p->response_code = ChangeNameResponse::Ineligible; QueuePacket(app); return; } - p->response_code = ChangePetNameResponse::Denied; + p->response_code = ChangeNameResponse::Denied; if (ChangePetName(p->new_pet_name)) { - p->response_code = ChangePetNameResponse::Accepted; + p->response_code = ChangeNameResponse::Accepted; } QueuePacket(app); @@ -6776,6 +6780,21 @@ void Client::Handle_OP_GMLastName(const EQApplicationPacket *app) void Client::Handle_OP_GMNameChange(const EQApplicationPacket *app) { + if (app->size == sizeof(AltChangeName_Struct)) { + auto p = (AltChangeName_Struct *) app->pBuffer; + + if (!IsNameChangeAllowed()) { + p->response_code = ChangeNameResponse::Ineligible; + QueuePacket(app); + return; + } + + p->response_code = ChangeFirstName(p->new_name) ? ChangeNameResponse::Accepted : ChangeNameResponse::Denied; + QueuePacket(app); + + return; + } + if (app->size != sizeof(GMName_Struct)) { LogError("Wrong size: OP_GMNameChange, size=[{}], expected [{}]", app->size, sizeof(GMName_Struct)); return; diff --git a/zone/gm_commands/set/name.cpp b/zone/gm_commands/set/name.cpp index 0e3ca531a..c700060b9 100755 --- a/zone/gm_commands/set/name.cpp +++ b/zone/gm_commands/set/name.cpp @@ -14,7 +14,7 @@ void SetName(Client *c, const Seperator *sep) std::string new_name = sep->arg[2]; std::string old_name = t->GetCleanName(); - if (t->ChangeFirstName(new_name.c_str(), c->GetCleanName())) { + if (t->ChangeFirstName(new_name, c->GetCleanName())) { c->Message( Chat::White, fmt::format( @@ -24,17 +24,13 @@ void SetName(Client *c, const Seperator *sep) ).c_str() ); - c->Message(Chat::White, "Sending player to char select."); - - t->Kick("Name was changed"); - return; } c->Message( Chat::White, fmt::format( - "Unable to rename {}. Check that the new name '{}' isn't already taken.", + "Unable to rename {}. Check that the new name '{}' isn't already taken (Including Pet Names), or isn't invalid", old_name, new_name ).c_str() diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index bd12bdf11..1def6b30a 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -3496,6 +3496,24 @@ std::string Lua_Client::GetAccountBucketRemaining(std::string bucket_name) return self->GetAccountBucketRemaining(bucket_name); } +void Lua_Client::GrantNameChange() +{ + Lua_Safe_Call_Void(); + self->GrantNameChange(); +} + +bool Lua_Client::IsNameChangeAllowed() +{ + Lua_Safe_Call_Bool(); + return self->IsNameChangeAllowed(); +} + +bool Lua_Client::ClearNameChange() +{ + Lua_Safe_Call_Bool(); + return self->ClearNameChange(); +} + std::string Lua_Client::GetBandolierName(uint8 bandolier_slot) { Lua_Safe_Call_String(); @@ -3635,6 +3653,7 @@ luabind::scope lua_register_client() { .def("CashReward", &Lua_Client::CashReward) .def("ChangeLastName", (void(Lua_Client::*)(std::string))&Lua_Client::ChangeLastName) .def("GrantPetNameChange", &Lua_Client::GrantPetNameChange) + .def("ClearNameChange", &Lua_Client::ClearNameChange) .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) @@ -3851,6 +3870,7 @@ luabind::scope lua_register_client() { .def("GrantAllAAPoints", (void(Lua_Client::*)(uint8,bool))&Lua_Client::GrantAllAAPoints) .def("GrantAlternateAdvancementAbility", (bool(Lua_Client::*)(int, int))&Lua_Client::GrantAlternateAdvancementAbility) .def("GrantAlternateAdvancementAbility", (bool(Lua_Client::*)(int, int, bool))&Lua_Client::GrantAlternateAdvancementAbility) + .def("GrantNameChange", &Lua_Client::GrantNameChange) .def("GuildID", (uint32(Lua_Client::*)(void))&Lua_Client::GuildID) .def("GuildRank", (int(Lua_Client::*)(void))&Lua_Client::GuildRank) .def("HasAugmentEquippedByID", (bool(Lua_Client::*)(uint32))&Lua_Client::HasAugmentEquippedByID) @@ -3881,6 +3901,7 @@ luabind::scope lua_register_client() { .def("IsInAGuild", (bool(Lua_Client::*)(void))&Lua_Client::IsInAGuild) .def("IsLD", (bool(Lua_Client::*)(void))&Lua_Client::IsLD) .def("IsMedding", (bool(Lua_Client::*)(void))&Lua_Client::IsMedding) + .def("IsNameChangeAllowed", &Lua_Client::IsNameChangeAllowed) .def("IsRaidGrouped", (bool(Lua_Client::*)(void))&Lua_Client::IsRaidGrouped) .def("IsSitting", (bool(Lua_Client::*)(void))&Lua_Client::IsSitting) .def("IsStanding", (bool(Lua_Client::*)(void))&Lua_Client::IsStanding) diff --git a/zone/lua_client.h b/zone/lua_client.h index 6346df52b..0e197f013 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -609,6 +609,10 @@ public: void ShowZoneShardMenu(); void GrantPetNameChange(); + void GrantNameChange(); + bool IsNameChangeAllowed(); + bool ClearNameChange(); + 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); Lua_Expedition CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players, bool disable_messages); diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index b104d7c70..6ef4eaa45 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -3261,6 +3261,21 @@ std::string Perl_Client_GetAccountBucketRemaining(Client* self, std::string buck return self->GetAccountBucketRemaining(bucket_name); } +void Perl_Client_GrantNameChange(Client* self) +{ + self->GrantNameChange(); +} + +bool Perl_Client_IsNameChangeAllowed(Client* self) +{ + return self->IsNameChangeAllowed(); +} + +bool Perl_Client_ClearNameChange(Client* self) +{ + return self->ClearNameChange(); +} + std::string Perl_Client_GetBandolierName(Client* self, uint8 bandolier_slot) { return self->GetBandolierName(bandolier_slot); @@ -3393,6 +3408,7 @@ void perl_register_client() package.add("CashReward", &Perl_Client_CashReward); package.add("ChangeLastName", &Perl_Client_ChangeLastName); package.add("GrantPetNameChange", &Perl_Client_GrantPetNameChange); + package.add("ClearNameChange", (bool(*)(Client*))&Perl_Client_ClearNameChange); 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); @@ -3607,6 +3623,7 @@ void perl_register_client() package.add("GrantAllAAPoints", (void(*)(Client*, uint8, bool))&Perl_Client_GrantAllAAPoints); package.add("GrantAlternateAdvancementAbility", (bool(*)(Client*, int, int))&Perl_Client_GrantAlternateAdvancementAbility); package.add("GrantAlternateAdvancementAbility", (bool(*)(Client*, int, int, bool))&Perl_Client_GrantAlternateAdvancementAbility); + package.add("GrantNameChange", (void(*)(Client*))&Perl_Client_GrantNameChange); package.add("GuildID", &Perl_Client_GuildID); package.add("GuildRank", &Perl_Client_GuildRank); package.add("HasAugmentEquippedByID", &Perl_Client_HasAugmentEquippedByID); @@ -3637,6 +3654,7 @@ void perl_register_client() package.add("IsInAGuild", &Perl_Client_IsInAGuild); package.add("IsLD", &Perl_Client_IsLD); package.add("IsMedding", &Perl_Client_IsMedding); + package.add("IsNameChangeAllowed", (bool(*)(Client*))&Perl_Client_IsNameChangeAllowed); package.add("IsRaidGrouped", &Perl_Client_IsRaidGrouped); package.add("IsSitting", &Perl_Client_IsSitting); package.add("IsStanding", &Perl_Client_IsStanding); diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index 085557421..bc2244d78 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -1310,15 +1310,14 @@ void QuestManager::rename(std::string name) { QuestManagerCurrentQuestVars(); if (initiator) { std::string current_name = initiator->GetName(); - if (initiator->ChangeFirstName(name.c_str(), current_name.c_str())) { + if (initiator->ChangeFirstName(name)) { initiator->Message( Chat::White, fmt::format( - "Successfully renamed to {}, kicking to character select.", + "Successfully renamed to {}.", name ).c_str() ); - initiator->Kick("Name was changed."); } else { initiator->Message( Chat::Red,