diff --git a/data/sql/updates/pending_db_world/rev_1721853644831454300.sql b/data/sql/updates/pending_db_world/rev_1721853644831454300.sql new file mode 100644 index 000000000..375c15d98 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1721853644831454300.sql @@ -0,0 +1,21 @@ +-- +DROP TABLE IF EXISTS `module_string`; +CREATE TABLE IF NOT EXISTS `module_string` ( + `module` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'module dir name, eg mod-cfbg', + `id` int unsigned NOT NULL, + `string` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`module`, `id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +DROP TABLE IF EXISTS `module_string_locale`; +CREATE TABLE IF NOT EXISTS `module_string_locale` ( + `module` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'Corresponds to an existing entry in module_string', + `id` int unsigned NOT NULL COMMENT 'Corresponds to an existing entry in module_string', + `locale` ENUM('koKR', 'frFR', 'deDE', 'zhCN', 'zhTW', 'esES', 'esMX', 'ruRU') NOT NULL, + `string` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`module`, `id`, `locale`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +DELETE FROM `command` WHERE `name` = 'reload module_string'; +INSERT INTO `command` (`name`, `security`, `help`) VALUES +('reload module_string', 3, 'Syntax: .reload module_string'); diff --git a/src/server/game/Chat/Chat.cpp b/src/server/game/Chat/Chat.cpp index b147b5f4b..7ffce0fc2 100644 --- a/src/server/game/Chat/Chat.cpp +++ b/src/server/game/Chat/Chat.cpp @@ -45,6 +45,11 @@ char const* ChatHandler::GetAcoreString(uint32 entry) const return m_session->GetAcoreString(entry); } +std::string const* ChatHandler::GetModuleString(std::string module, uint32 id) const +{ + return m_session->GetModuleString(module, id); +} + bool ChatHandler::IsAvailable(uint32 securityLevel) const { // check security level only for simple command (without child commands) diff --git a/src/server/game/Chat/Chat.h b/src/server/game/Chat/Chat.h index be908e935..8ef21176e 100644 --- a/src/server/game/Chat/Chat.h +++ b/src/server/game/Chat/Chat.h @@ -158,6 +158,21 @@ public: return Acore::StringFormatFmt(GetAcoreString(entry), std::forward(args)...); } + std::string const* GetModuleString(std::string module, uint32 id) const; + + template + void PSendModuleSysMessage(std::string module, uint32 id, Args&&... args) + { + if (HasSession()) + SendSysMessage(PGetParseModuleString(module, id, std::forward(args)...)); + } + + template + std::string PGetParseModuleString(std::string module, uint32 id, Args&&... args) const + { + return Acore::StringFormatFmt(GetModuleString(module, id)->c_str(), std::forward(args)...); + } + void SendErrorMessage(uint32 entry); void SendErrorMessage(std::string_view str, bool escapeCharacters); diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index 2b07f99ba..a21794a09 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -8544,6 +8544,98 @@ void ObjectMgr::LoadGameObjectForQuests() LOG_INFO("server.loading", " "); } +bool ObjectMgr::LoadModuleStrings() +{ + uint32 oldMSTime = getMSTime(); + + _moduleStringStore.clear(); // for reload case + QueryResult result = WorldDatabase.Query("SELECT module, id, string FROM module_string"); + if (!result) + { + LOG_WARN("server.loading", ">> Loaded 0 module strings. DB table `module_string` is empty."); + LOG_INFO("server.loading", " "); + return false; + } + + do + { + Field* fields = result->Fetch(); + + std::string module = fields[0].Get(); + uint32 id = fields[1].Get(); + + std::pair pairKey = std::make_pair(module, id); + ModuleString& data = _moduleStringStore[pairKey]; + + AddLocaleString(fields[2].Get(), LOCALE_enUS, data.Content); + } while (result->NextRow()); + + LOG_INFO("server.loading", ">> Loaded {} Module Strings in {} ms", _moduleStringStore.size(), GetMSTimeDiffToNow(oldMSTime)); + LOG_INFO("server.loading", " "); + + return true; +} + +bool ObjectMgr::LoadModuleStringsLocale() +{ + uint32 oldMSTime = getMSTime(); + + QueryResult result = WorldDatabase.Query("SELECT module, id, locale, string FROM module_string_locale"); + if (!result) + { + LOG_WARN("server.loading", ">> Loaded 0 module strings locale. DB table `module_string_locale` is empty."); + LOG_INFO("server.loading", " "); + return false; + } + + uint32 localeCount = 0; + do + { + Field* fields = result->Fetch(); + + std::string module = fields[0].Get(); + uint32 id = fields[1].Get(); + + std::pair pairKey = std::make_pair(module, id); + ModuleString& data = _moduleStringStore[pairKey]; + + ModuleStringContainer::iterator ms = _moduleStringStore.find(pairKey); + if (ms == _moduleStringStore.end()) + { + LOG_ERROR("sql.sql", "ModuleString (Module: {} Id: {}) found in table `module_string_locale` but does not exist in `module_string`. Skipped!", module, id); + continue; + } + + LocaleConstant locale = GetLocaleByName(fields[2].Get()); + if (locale == LOCALE_enUS) + continue; + + AddLocaleString(fields[3].Get(), locale, data.Content); + localeCount++; + } while (result->NextRow()); + + LOG_INFO("server.loading", ">> Loaded {} Module Strings Locales in {} ms", localeCount, GetMSTimeDiffToNow(oldMSTime)); + LOG_INFO("server.loading", " "); + + return true; +} + +std::string const* ObjectMgr::GetModuleString(std::string module, uint32 id, LocaleConstant locale) const +{ + ModuleString const* ms = GetModuleString(module, id); + if (ms->Content.size()) + { + if (ms->Content.size() > size_t(locale) && !ms->Content[locale].empty()) + return &ms->Content[locale]; + + return &ms->Content[DEFAULT_LOCALE]; + } + + LOG_ERROR("sql.sql", "Module string module {} id {} not found in DB.", module, id); + + return (std::string*)"error"; +} + bool ObjectMgr::LoadAcoreStrings() { uint32 oldMSTime = getMSTime(); diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h index de9c44089..399711e88 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -493,6 +493,11 @@ typedef std::unordered_map Content; +}; + struct AcoreString { std::vector Content; @@ -511,6 +516,7 @@ typedef std::unordered_map QuestOfferRewardLocal typedef std::unordered_map QuestRequestItemsLocaleContainer; typedef std::unordered_map NpcTextLocaleContainer; typedef std::unordered_map PageTextLocaleContainer; +typedef std::map, ModuleString> ModuleStringContainer; typedef std::unordered_map AcoreStringContainer; typedef std::unordered_map GossipMenuItemsLocaleContainer; typedef std::unordered_map PointOfInterestLocaleContainer; @@ -1012,6 +1018,8 @@ public: void ValidateSpellScripts(); void InitializeSpellInfoPrecomputedData(); + bool LoadModuleStrings(); + bool LoadModuleStringsLocale(); bool LoadAcoreStrings(); void LoadBroadcastTexts(); void LoadBroadcastTextLocales(); @@ -1310,6 +1318,17 @@ public: GameObjectData& NewGOData(ObjectGuid::LowType guid) { return _gameObjectDataStore[guid]; } void DeleteGOData(ObjectGuid::LowType guid); + [[nodiscard]] ModuleString const* GetModuleString(std::string module, uint32 id) const + { + std::pair pairKey = std::make_pair(module, id); + ModuleStringContainer::const_iterator itr = _moduleStringStore.find(pairKey); + if (itr == _moduleStringStore.end()) + return nullptr; + + return &itr->second; + } + [[nodiscard]] std::string const* GetModuleString(std::string module, uint32 id, LocaleConstant locale) const; + [[nodiscard]] AcoreString const* GetAcoreString(uint32 entry) const { AcoreStringContainer::const_iterator itr = _acoreStringStore.find(entry); @@ -1598,6 +1617,7 @@ private: QuestRequestItemsLocaleContainer _questRequestItemsLocaleStore; NpcTextLocaleContainer _npcTextLocaleStore; PageTextLocaleContainer _pageTextLocaleStore; + ModuleStringContainer _moduleStringStore; AcoreStringContainer _acoreStringStore; GossipMenuItemsLocaleContainer _gossipMenuItemsLocaleStore; PointOfInterestLocaleContainer _pointOfInterestLocaleStore; diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index af1e0c15b..55ab51c00 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -792,6 +792,11 @@ char const* WorldSession::GetAcoreString(uint32 entry) const return sObjectMgr->GetAcoreString(entry, GetSessionDbLocaleIndex()); } +std::string const* WorldSession::GetModuleString(std::string module, uint32 id) const +{ + return sObjectMgr->GetModuleString(module, id, GetSessionDbLocaleIndex()); +} + void WorldSession::Handle_NULL(WorldPacket& null) { LOG_ERROR("network.opcode", "Received unhandled opcode {} from {}", diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index 777176afb..31e9e566d 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -497,6 +497,7 @@ public: LocaleConstant GetSessionDbcLocale() const { return m_sessionDbcLocale; } LocaleConstant GetSessionDbLocaleIndex() const { return m_sessionDbLocaleIndex; } char const* GetAcoreString(uint32 entry) const; + std::string const* GetModuleString(std::string module, uint32 id) const; uint32 GetLatency() const { return m_latency; } void SetLatency(uint32 latency) { m_latency = latency; } diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 4f3c88ac3..e35a5a3ab 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -1542,6 +1542,11 @@ void World::SetInitialWorldSettings() if (!sObjectMgr->LoadAcoreStrings()) exit(1); // Error message displayed in function already + LOG_INFO("server.loading", "Loading Module Strings..."); + sObjectMgr->LoadModuleStrings(); + LOG_INFO("server.loading", "Loading Module Strings Locale..."); + sObjectMgr->LoadModuleStringsLocale(); + ///- Update the realm entry in the database with the realm type from the config file //No SQL injection as values are treated as integers diff --git a/src/server/scripts/Commands/cs_reload.cpp b/src/server/scripts/Commands/cs_reload.cpp index 81f3e2639..c179db040 100644 --- a/src/server/scripts/Commands/cs_reload.cpp +++ b/src/server/scripts/Commands/cs_reload.cpp @@ -166,6 +166,7 @@ public: { "spell_threats", HandleReloadSpellThreatsCommand, SEC_ADMINISTRATOR, Console::Yes }, { "spell_group_stack_rules", HandleReloadSpellGroupStackRulesCommand, SEC_ADMINISTRATOR, Console::Yes }, { "player_loot_template", HandleReloadLootTemplatesPlayerCommand, SEC_ADMINISTRATOR, Console::Yes }, + { "module_string", HandleReloadModuleStringCommand, SEC_ADMINISTRATOR, Console::Yes }, { "acore_string", HandleReloadAcoreStringCommand, SEC_ADMINISTRATOR, Console::Yes }, { "warden_action", HandleReloadWardenactionCommand, SEC_ADMINISTRATOR, Console::Yes }, { "waypoint_scripts", HandleReloadWpScriptsCommand, SEC_ADMINISTRATOR, Console::Yes }, @@ -716,6 +717,17 @@ public: return true; } + static bool HandleReloadModuleStringCommand(ChatHandler* handler) + { + LOG_INFO("server.loading", "Reloading module_string Table!"); + sObjectMgr->LoadModuleStrings(); + handler->SendGlobalGMSysMessage("DB table `module_string` reloaded."); + LOG_INFO("server.loading", "Reloading module_string_locale Table!"); + sObjectMgr->LoadModuleStringsLocale(); + handler->SendGlobalGMSysMessage("DB table `module_string_locale` reloaded."); + return true; + } + static bool HandleReloadAcoreStringCommand(ChatHandler* handler) { LOG_INFO("server.loading", "Reloading acore_string Table!");