diff --git a/data/sql/updates/pending_db_characters/rev_1677013247854223400.sql b/data/sql/updates/pending_db_characters/rev_1677013247854223400.sql new file mode 100644 index 000000000..93da5ac19 --- /dev/null +++ b/data/sql/updates/pending_db_characters/rev_1677013247854223400.sql @@ -0,0 +1,6 @@ +-- +DROP TABLE IF EXISTS `profanity_name`; +CREATE TABLE `profanity_name` ( + `name` VARCHAR(12) NOT NULL, + PRIMARY KEY (`name`) +) ENGINE=InnoDB; diff --git a/data/sql/updates/pending_db_world/rev_1677012340406860800.sql b/data/sql/updates/pending_db_world/rev_1677012340406860800.sql new file mode 100644 index 000000000..52372b074 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1677012340406860800.sql @@ -0,0 +1,4 @@ +-- +DELETE FROM `acore_string` WHERE `entry` = 187; +INSERT INTO `acore_string` (`entry`, `content_default`) VALUE +(187, 'This name is profane, choose another one'); diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index 749f46056..c3e4bb02a 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -596,6 +596,7 @@ void CharacterDatabaseConnection::DoPrepareStatements() // Character names PrepareStatement(CHAR_INS_RESERVED_PLAYER_NAME, "INSERT IGNORE INTO reserved_name (name) VALUES (?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_PROFANITY_PLAYER_NAME, "INSERT IGNORE INTO profanity_name (name) VALUES (?)", CONNECTION_ASYNC); // Character settings PrepareStatement(CHAR_SEL_CHAR_SETTINGS, "SELECT source, data FROM character_settings WHERE guid = ?", CONNECTION_ASYNC); diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h index 939f1d863..6a85c7aba 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.h +++ b/src/server/database/Database/Implementation/CharacterDatabase.h @@ -510,6 +510,7 @@ enum CharacterDatabaseStatements : uint32 CHAR_SEL_ARENAPOINTS, CHAR_INS_RESERVED_PLAYER_NAME, + CHAR_INS_PROFANITY_PLAYER_NAME, CHAR_SEL_CHAR_SETTINGS, CHAR_REP_CHAR_SETTINGS, diff --git a/src/server/game/Battlegrounds/ArenaTeam.cpp b/src/server/game/Battlegrounds/ArenaTeam.cpp index 2d326dcd0..c19f66833 100644 --- a/src/server/game/Battlegrounds/ArenaTeam.cpp +++ b/src/server/game/Battlegrounds/ArenaTeam.cpp @@ -281,7 +281,7 @@ bool ArenaTeam::LoadMembersFromDB(QueryResult result) bool ArenaTeam::SetName(std::string const& name) { - if (TeamName == name || name.empty() || name.length() > 24 || sObjectMgr->IsReservedName(name) || !ObjectMgr::IsValidCharterName(name)) + if (TeamName == name || name.empty() || name.length() > 24 || sObjectMgr->IsReservedName(name) || sObjectMgr->IsProfanityName(name) || !ObjectMgr::IsValidCharterName(name)) return false; TeamName = name; diff --git a/src/server/game/Entities/Player/PlayerStorage.cpp b/src/server/game/Entities/Player/PlayerStorage.cpp index 9d55b21e4..0637a4a3a 100644 --- a/src/server/game/Entities/Player/PlayerStorage.cpp +++ b/src/server/game/Entities/Player/PlayerStorage.cpp @@ -4990,7 +4990,7 @@ bool Player::LoadFromDB(ObjectGuid playerGuid, CharacterDatabaseQueryHolder cons // check name limitations if (ObjectMgr::CheckPlayerName(m_name) != CHAR_NAME_SUCCESS || - (AccountMgr::IsPlayerAccount(GetSession()->GetSecurity()) && sObjectMgr->IsReservedName(m_name))) + (AccountMgr::IsPlayerAccount(GetSession()->GetSecurity()) && (sObjectMgr->IsReservedName(m_name) || sObjectMgr->IsProfanityName(m_name)))) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ADD_AT_LOGIN_FLAG); stmt->SetData(0, uint16(AT_LOGIN_RENAME)); diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index a9fe13080..e471dfa79 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -8204,6 +8204,81 @@ void ObjectMgr::AddReservedPlayerName(std::string const& name) } } +void ObjectMgr::LoadProfanityPlayersNames() +{ + uint32 oldMSTime = getMSTime(); + + _profanityNamesStore.clear(); // need for reload case + + QueryResult result = CharacterDatabase.Query("SELECT name FROM profanity_name"); + + if (!result) + { + LOG_WARN("server.loading", ">> Loaded 0 profanity player names. DB table `profanity_name` is empty!"); + LOG_INFO("server.loading", " "); + return; + } + + uint32 count = 0; + + Field* fields; + do + { + fields = result->Fetch(); + std::string name = fields[0].Get(); + + std::wstring wstr; + if (!Utf8toWStr (name, wstr)) + { + LOG_ERROR("sql.sql", "Table `profanity_name` have invalid name: {}", name); + continue; + } + + wstrToLower(wstr); + + _profanityNamesStore.insert(wstr); + ++count; + } while (result->NextRow()); + + LOG_INFO("server.loading", ">> Loaded {} profanity player names in {} ms", count, GetMSTimeDiffToNow(oldMSTime)); + LOG_INFO("server.loading", " "); +} + +bool ObjectMgr::IsProfanityName(std::string_view name) const +{ + // pussywizard + if (name.size() >= 2 && (name[name.size() - 2] == 'G' || name[name.size() - 2] == 'g') && (name[name.size() - 1] == 'M' || name[name.size() - 1] == 'm')) + return true; + + std::wstring wstr; + if (!Utf8toWStr (name, wstr)) + return false; + + wstrToLower(wstr); + + return _profanityNamesStore.find(wstr) != _profanityNamesStore.end(); +} + +void ObjectMgr::AddProfanityPlayerName(std::string const& name) +{ + if (!IsProfanityName(name)) + { + std::wstring wstr; + if (!Utf8toWStr(name, wstr)) + { + LOG_ERROR("server", "Could not add invalid name to profanity player names: {}", name); + return; + } + wstrToLower(wstr); + + _profanityNamesStore.insert(wstr); + + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PROFANITY_PLAYER_NAME); + stmt->SetData(0, name); + CharacterDatabase.Execute(stmt); + } +} + enum LanguageType { LT_BASIC_LATIN = 0x0000, @@ -8311,6 +8386,11 @@ uint8 ObjectMgr::CheckPlayerName(std::string_view name, bool create) return CHAR_NAME_RESERVED; } + if (sObjectMgr->IsProfanityName(name)) + { + return CHAR_NAME_PROFANE; + } + // Check for Reserved Name from DBC if (sWorld->getBoolConfig(CONFIG_STRICT_NAMES_RESERVED)) { diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h index 28b13f10c..0e40a20b0 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -1337,6 +1337,11 @@ public: [[nodiscard]] bool IsReservedName(std::string_view name) const; void AddReservedPlayerName(std::string const& name); + // profanity names + void LoadProfanityPlayersNames(); + [[nodiscard]] bool IsProfanityName(std::string_view name) const; + void AddProfanityPlayerName(std::string const& name); + // name with valid structure and symbols static uint8 CheckPlayerName(std::string_view name, bool create = false); static PetNameInvalidReason CheckPetName(std::string_view name); @@ -1506,6 +1511,10 @@ private: typedef std::set ReservedNamesContainer; ReservedNamesContainer _reservedNamesStore; + //character profanity names + typedef std::set ProfanityNamesContainer; + ReservedNamesContainer _profanityNamesStore; + GameTeleContainer _gameTeleStore; ScriptNameContainer _scriptNamesStore; diff --git a/src/server/game/Guilds/Guild.cpp b/src/server/game/Guilds/Guild.cpp index 849f75487..e04b70411 100644 --- a/src/server/game/Guilds/Guild.cpp +++ b/src/server/game/Guilds/Guild.cpp @@ -1184,7 +1184,7 @@ void Guild::OnPlayerStatusChange(Player* player, uint32 flag, bool state) bool Guild::SetName(std::string_view const& name) { - if (m_name == name || name.empty() || name.length() > 24 || sObjectMgr->IsReservedName(name) || !ObjectMgr::IsValidCharterName(name)) + if (m_name == name || name.empty() || name.length() > 24 || sObjectMgr->IsReservedName(name) || sObjectMgr->IsProfanityName(name) || !ObjectMgr::IsValidCharterName(name)) { return false; } diff --git a/src/server/game/Handlers/PetHandler.cpp b/src/server/game/Handlers/PetHandler.cpp index ca040451d..71c8c3cbc 100644 --- a/src/server/game/Handlers/PetHandler.cpp +++ b/src/server/game/Handlers/PetHandler.cpp @@ -861,6 +861,12 @@ void WorldSession::HandlePetRename(WorldPacket& recvData) return; } + if (sObjectMgr->IsProfanityName(name)) + { + SendPetNameInvalid(PET_NAME_PROFANE, name, nullptr); + return; + } + pet->SetName(name); Unit* owner = pet->GetOwner(); diff --git a/src/server/game/Handlers/PetitionsHandler.cpp b/src/server/game/Handlers/PetitionsHandler.cpp index e8cc33152..0f159d1fe 100644 --- a/src/server/game/Handlers/PetitionsHandler.cpp +++ b/src/server/game/Handlers/PetitionsHandler.cpp @@ -137,7 +137,7 @@ void WorldSession::HandlePetitionBuyOpcode(WorldPacket& recvData) return; } - if (sObjectMgr->IsReservedName(name) || !ObjectMgr::IsValidCharterName(name)) + if (sObjectMgr->IsReservedName(name) || sObjectMgr->IsProfanityName(name) || !ObjectMgr::IsValidCharterName(name)) { Guild::SendCommandResult(this, GUILD_COMMAND_CREATE, ERR_GUILD_NAME_INVALID, name); return; @@ -150,7 +150,7 @@ void WorldSession::HandlePetitionBuyOpcode(WorldPacket& recvData) SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, name, "", ERR_ARENA_TEAM_NAME_EXISTS_S); return; } - if (sObjectMgr->IsReservedName(name) || !ObjectMgr::IsValidCharterName(name)) + if (sObjectMgr->IsReservedName(name) || sObjectMgr->IsProfanityName(name) || !ObjectMgr::IsValidCharterName(name)) { SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, name, "", ERR_ARENA_TEAM_NAME_INVALID); return; @@ -351,7 +351,7 @@ void WorldSession::HandlePetitionRenameOpcode(WorldPacket& recvData) Guild::SendCommandResult(this, GUILD_COMMAND_CREATE, ERR_GUILD_NAME_EXISTS_S, newName); return; } - if (sObjectMgr->IsReservedName(newName) || !ObjectMgr::IsValidCharterName(newName)) + if (sObjectMgr->IsReservedName(newName) || sObjectMgr->IsProfanityName(newName) || !ObjectMgr::IsValidCharterName(newName)) { Guild::SendCommandResult(this, GUILD_COMMAND_CREATE, ERR_GUILD_NAME_INVALID, newName); return; @@ -364,7 +364,7 @@ void WorldSession::HandlePetitionRenameOpcode(WorldPacket& recvData) SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, newName, "", ERR_ARENA_TEAM_NAME_EXISTS_S); return; } - if (sObjectMgr->IsReservedName(newName) || !ObjectMgr::IsValidCharterName(newName)) + if (sObjectMgr->IsReservedName(newName) || sObjectMgr->IsProfanityName(newName) || !ObjectMgr::IsValidCharterName(newName)) { SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, newName, "", ERR_ARENA_TEAM_NAME_INVALID); return; diff --git a/src/server/game/Miscellaneous/Language.h b/src/server/game/Miscellaneous/Language.h index 587abd97c..8f314fa38 100644 --- a/src/server/game/Miscellaneous/Language.h +++ b/src/server/game/Miscellaneous/Language.h @@ -218,7 +218,7 @@ enum AcoreStrings LANG_GRID_POSITION = 178, // 179-185 used in other client versions LANG_TRANSPORT_POSITION = 186, - // 187 + LANG_PROFANITY_NAME = 187, LANG_2FA_SECRET_TOO_LONG = 188, LANG_2FA_SECRET_INVALID = 189, LANG_2FA_SECRET_SET_COMPLETE = 190, diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 9a74e7494..59936d0a2 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -1911,9 +1911,12 @@ void World::SetInitialWorldSettings() LOG_INFO("server.loading", "Loading Groups..."); sGroupMgr->LoadGroups(); - LOG_INFO("server.loading", "Loading ReservedNames..."); + LOG_INFO("server.loading", "Loading Reserved Names..."); sObjectMgr->LoadReservedPlayersNames(); + LOG_INFO("server.loading", "Loading Profanity Names..."); + sObjectMgr->LoadProfanityPlayersNames(); + LOG_INFO("server.loading", "Loading GameObjects for Quests..."); sObjectMgr->LoadGameObjectForQuests(); diff --git a/src/server/scripts/Commands/cs_character.cpp b/src/server/scripts/Commands/cs_character.cpp index e1a065b75..6192b83bc 100644 --- a/src/server/scripts/Commands/cs_character.cpp +++ b/src/server/scripts/Commands/cs_character.cpp @@ -363,6 +363,13 @@ public: return false; } + if (sObjectMgr->IsProfanityName(newName)) + { + handler->SendSysMessage(LANG_PROFANITY_NAME); + handler->SetSentErrorMessage(true); + return false; + } + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHECK_NAME); stmt->SetData(0, newName); PreparedQueryResult result = CharacterDatabase.Query(stmt); diff --git a/src/server/scripts/Commands/cs_guild.cpp b/src/server/scripts/Commands/cs_guild.cpp index 45efab856..b5f40ba10 100644 --- a/src/server/scripts/Commands/cs_guild.cpp +++ b/src/server/scripts/Commands/cs_guild.cpp @@ -100,7 +100,7 @@ public: return false; } - if (sObjectMgr->IsReservedName(guildName) || !sObjectMgr->IsValidCharterName(guildName)) + if (sObjectMgr->IsReservedName(guildName) || sObjectMgr->IsProfanityName(guildName) || !sObjectMgr->IsValidCharterName(guildName)) { handler->SendSysMessage(LANG_BAD_VALUE); handler->SetSentErrorMessage(true); diff --git a/src/server/scripts/Commands/cs_reload.cpp b/src/server/scripts/Commands/cs_reload.cpp index a0d2fad31..b7265e89c 100644 --- a/src/server/scripts/Commands/cs_reload.cpp +++ b/src/server/scripts/Commands/cs_reload.cpp @@ -143,6 +143,7 @@ public: { "quest_template", HandleReloadQuestTemplateCommand, SEC_ADMINISTRATOR, Console::Yes }, { "reference_loot_template", HandleReloadLootTemplatesReferenceCommand, SEC_ADMINISTRATOR, Console::Yes }, { "reserved_name", HandleReloadReservedNameCommand, SEC_ADMINISTRATOR, Console::Yes }, + { "profanity_name", HandleReloadProfanityNameCommand, SEC_ADMINISTRATOR, Console::Yes }, { "reputation_reward_rate", HandleReloadReputationRewardRateCommand, SEC_ADMINISTRATOR, Console::Yes }, { "reputation_spillover_template", HandleReloadReputationRewardRateCommand, SEC_ADMINISTRATOR, Console::Yes }, { "skill_discovery_template", HandleReloadSkillDiscoveryTemplateCommand, SEC_ADMINISTRATOR, Console::Yes }, @@ -204,6 +205,7 @@ public: HandleReloadMailServerTemplateCommand(handler); HandleReloadCommandCommand(handler); HandleReloadReservedNameCommand(handler); + HandleReloadProfanityNameCommand(handler); HandleReloadAcoreStringCommand(handler); HandleReloadGameTeleCommand(handler); HandleReloadCreatureMovementOverrideCommand(handler); @@ -779,9 +781,17 @@ public: static bool HandleReloadReservedNameCommand(ChatHandler* handler) { - LOG_INFO("server.loading", "Loading ReservedNames... (`reserved_name`)"); + LOG_INFO("server.loading", "Re-Loading `reserved_player` Table!"); sObjectMgr->LoadReservedPlayersNames(); - handler->SendGlobalGMSysMessage("DB table `reserved_name` (player reserved names) reloaded."); + handler->SendGlobalGMSysMessage("DB table `reserved_name` reloaded."); + return true; + } + + static bool HandleReloadProfanityNameCommand(ChatHandler* handler) + { + LOG_INFO("server.loading", "Re-Loading `profanity_player` Table!"); + sObjectMgr->LoadProfanityPlayersNames(); + handler->SendGlobalGMSysMessage("DB table `profanity_player` reloaded."); return true; }