Skip to content

Commit

Permalink
feat(Core/Motd): Allow localized motd (azerothcore#20542)
Browse files Browse the repository at this point in the history
* Initial commit for localized motd

* Rename function that created world packages

* Update to satisfy code check

* Update code to accomodate localized motd

* Update command to support multiple optionales & adjusted db

* Code cleanup

* Update sql name

* Fix codestyle issues

* Remove hardcoded schema

* Add check for valid player in reload command

* Update to better code style

* Add missing include

* Fix redundant code usage

* Add missing include

* Remove sql files and create new rev sql files

* Address minor code reviews

* Fix code style

* Update code to address code revisions.

- Remove two unused functions
- Remove map
- Use available function to resolve LocaleConstant

* Fix code style

* Add check for base motd and update locale to DEFAULT_LOCALE

* Code docs

* Removed some docs, readd defaultd motd formatting

* Fix oversight in variable declaration

* Code style fix

* Update code based on code review

* ready for merge

* Fix set motd command due to changes to DEFAULT_LOCALE

* Fix CI

* Fix trailing whitespace

---------

Co-authored-by: Kitzunu <[email protected]>
  • Loading branch information
2 people authored and xpdemon committed Dec 25, 2024
1 parent 57f3eff commit 017fa01
Show file tree
Hide file tree
Showing 15 changed files with 247 additions and 62 deletions.
10 changes: 10 additions & 0 deletions data/sql/updates/pending_db_auth/rev_1731984288401879600.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
CREATE TABLE IF NOT EXISTS `motd_localized` (
`realmid` INT,
`locale` VARCHAR(4) NOT NULL COLLATE 'utf8mb4_unicode_ci',
`text` LONGTEXT NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
PRIMARY KEY (`realmid`, `locale`)
)
CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci
ENGINE = InnoDB
;
4 changes: 4 additions & 0 deletions data/sql/updates/pending_db_world/rev_1731983948814732800.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
--
UPDATE `command` SET `help` = 'Syntax: .server set motd Optional($realmId) Optional($locale) $MOTD\r \r Set server Message of the day for the specified $realmId.\r If $realmId is not provided it will update for the current realm. \r Use $realmId -1 to set motd for all realms. If $locale is not provided enUS will be used.' WHERE (`name` = 'server set motd');

UPDATE `acore_string` SET `content_default` = 'Message of the day in realm {} and locale {} changed to:\r {}', `locale_deDE` = 'Nachricht des Tages für Realm {} und Sprache {} wurde geändert zu:\r {}', `locale_zhCN` = '每日消息更改为 in realm {} and locale {}:\r {}' WHERE (`entry` = 1101);
10 changes: 10 additions & 0 deletions src/common/Common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ LocaleConstant GetLocaleByName(const std::string& name)
return LOCALE_enUS; // including enGB case
}

const std::string GetNameByLocaleConstant(LocaleConstant localeConstant)
{
if (localeConstant < TOTAL_LOCALES)
{
return localeNames[localeConstant];
}

return "enUS"; // Default value for unsupported or invalid LocaleConstant
}

void CleanStringForMysqlQuery(std::string& str)
{
std::string::size_type n = 0;
Expand Down
1 change: 1 addition & 0 deletions src/common/Common.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ enum LocaleConstant
AC_COMMON_API extern char const* localeNames[TOTAL_LOCALES];

AC_COMMON_API LocaleConstant GetLocaleByName(const std::string& name);
AC_COMMON_API const std::string GetNameByLocaleConstant(LocaleConstant localeConstant);
AC_COMMON_API void CleanStringForMysqlQuery(std::string& str);

#define MAX_QUERY_LEN 32*1024
Expand Down
2 changes: 1 addition & 1 deletion src/server/apps/worldserver/RemoteAccess/RASession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ void RASession::Start()
LOG_INFO("commands.ra", "User {} (IP: {}) authenticated correctly to RA", username, GetRemoteIpAddress());

// Authentication successful, send the motd
Send(std::string(std::string(sMotdMgr->GetMotd()) + "\r\n").c_str());
Send(std::string(std::string(sMotdMgr->GetMotd(DEFAULT_LOCALE)) + "\r\n").c_str());

// Read commands
for (;;)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,9 @@ void LoginDatabaseConnection::DoPrepareStatements()
PrepareStatement(LOGIN_DEL_ACCOUNT, "DELETE FROM account WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_AUTOBROADCAST, "SELECT id, weight, text FROM autobroadcast WHERE realmid = ? OR realmid = -1", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_MOTD, "SELECT text FROM motd WHERE realmid = ? OR realmid = -1 ORDER BY realmid DESC", CONNECTION_SYNCH);
PrepareStatement(LOGIN_REP_MOTD, "REPLACE INTO motd (realmid, text) VALUES (?, ?)", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_MOTD_LOCALE, "SELECT locale, text FROM motd_localized WHERE realmid = ? OR realmid = -1 ORDER BY realmid DESC", CONNECTION_SYNCH);
PrepareStatement(LOGIN_INS_MOTD, "INSERT INTO motd (realmid, text) VALUES (?, ?) ON DUPLICATE KEY UPDATE text = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_INS_MOTD_LOCALE, "INSERT INTO motd_localized (realmid, locale, text) VALUES(?, ?, ?) ON DUPLICATE KEY UPDATE text = ?;", CONNECTION_ASYNC);
PrepareStatement(LOGIN_INS_ACCOUNT_MUTE, "INSERT INTO account_muted VALUES (?, UNIX_TIMESTAMP(), ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_ACCOUNT_MUTE_INFO, "SELECT mutedate, mutetime, mutereason, mutedby FROM account_muted WHERE guid = ? ORDER BY mutedate ASC", CONNECTION_SYNCH);
PrepareStatement(LOGIN_DEL_ACCOUNT_MUTED, "DELETE FROM account_muted WHERE guid = ?", CONNECTION_ASYNC);
Expand Down
4 changes: 3 additions & 1 deletion src/server/database/Database/Implementation/LoginDatabase.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ enum LoginDatabaseStatements : uint32
LOGIN_DEL_ACCOUNT,
LOGIN_SEL_AUTOBROADCAST,
LOGIN_SEL_MOTD,
LOGIN_REP_MOTD,
LOGIN_SEL_MOTD_LOCALE,
LOGIN_INS_MOTD,
LOGIN_INS_MOTD_LOCALE,
LOGIN_SEL_LAST_ATTEMPT_IP,
LOGIN_SEL_LAST_IP,
LOGIN_INS_ALDL_IP_LOGGING,
Expand Down
4 changes: 2 additions & 2 deletions src/server/game/Handlers/CharacterHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,7 @@ void WorldSession::HandlePlayerLoginFromDB(LoginQueryHolder const& holder)

// Send MOTD
{
SendPacket(sMotdMgr->GetMotdPacket());
SendPacket(sMotdMgr->GetMotdPacket(pCurrChar->GetSession()->GetSessionDbLocaleIndex()));

// send server info
if (sWorld->getIntConfig(CONFIG_ENABLE_SINFO_LOGIN) == 1)
Expand Down Expand Up @@ -1148,7 +1148,7 @@ void WorldSession::HandlePlayerLoginToCharInWorld(Player* pCurrChar)

// Send MOTD
{
SendPacket(sMotdMgr->GetMotdPacket());
SendPacket(sMotdMgr->GetMotdPacket(pCurrChar->GetSession()->GetSessionDbLocaleIndex()));

// send server info
if (sWorld->getIntConfig(CONFIG_ENABLE_SINFO_LOGIN) == 1)
Expand Down
148 changes: 112 additions & 36 deletions src/server/game/Motd/MotdMgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@

namespace
{
WorldPacket MotdPacket;
std::string FormattedMotd;
// Stores translated worldpackets
std::unordered_map<LocaleConstant, WorldPacket> MotdPackets;
// Stores the localized motd to prevent database queries
std::unordered_map<LocaleConstant, std::string> MotdMap;
}

MotdMgr* MotdMgr::instance()
Expand All @@ -37,69 +39,143 @@ MotdMgr* MotdMgr::instance()
return &instance;
}

void MotdMgr::SetMotd(std::string motd)
bool MotdMgr::IsValidLocale(std::string const& locale) {
// Use std::find to search for the locale in the array
return std::find(std::begin(localeNames), std::end(localeNames), locale) != std::end(localeNames);
}

void MotdMgr::SetMotd(std::string motd, LocaleConstant locale)
{
// scripts may change motd
sScriptMgr->OnMotdChange(motd);
sScriptMgr->OnMotdChange(motd, locale);

WorldPacket data(SMSG_MOTD); // new in 2.0.1
MotdMap[locale] = motd;
MotdPackets[locale] = CreateWorldPacket(motd);
}

std::vector<std::string_view> motdTokens = Acore::Tokenize(motd, '@', true);
data << uint32(motdTokens.size()); // line count
void MotdMgr::CreateWorldPackages()
{
for (auto const& [locale, motd] : MotdMap)
// Store the constructed packet in MotdPackets with the locale as the key
MotdPackets[locale] = CreateWorldPacket(motd);
}

for (std::string_view token : motdTokens)
data << token;
void MotdMgr::LoadMotd()
{
uint32 realmId = sConfigMgr->GetOption<int32>("RealmID", 0);

MotdPacket = data;
// Load the main motd for the realm and assign it to enUS if available
std::string motd = LoadDefaultMotd(realmId);

if (!motdTokens.size())
return;
// Check if motd was loaded; if not, set default only for enUS
if (motd.empty())
SetDefaultMotd(); // Only sets enUS default if motd is empty
else
MotdMap[DEFAULT_LOCALE] = motd; // Assign the loaded motd to enUS

// Load localized texts if available
LoadLocalizedMotds(realmId);

std::ostringstream oss;
std::copy(motdTokens.begin(), motdTokens.end() - 1, std::ostream_iterator<std::string_view>(oss, "\n"));
oss << *(motdTokens.end() - 1); // copy back element
FormattedMotd = oss.str();
// Create all world packages after loading motd and localized texts
CreateWorldPackages();
}

void MotdMgr::LoadMotd()
char const* MotdMgr::GetMotd(LocaleConstant locale)
{
uint32 oldMSTime = getMSTime();
// Return localized motd if available, otherwise fallback to enUS
auto it = MotdMap.find(locale);
if (it != MotdMap.end())
return it->second.c_str();

uint32 realmId = sConfigMgr->GetOption<int32>("RealmID", 0);
return MotdMap[DEFAULT_LOCALE].c_str(); // Fallback to enUS if locale is not found
}

WorldPacket const* MotdMgr::GetMotdPacket(LocaleConstant locale)
{
// Return localized packet if available, otherwise fallback to enUS
auto it = MotdPackets.find(locale);
if (it != MotdPackets.end())
return &it->second;

return &MotdPackets[DEFAULT_LOCALE]; // Fallback to enUS if locale is not found
}

std::string MotdMgr::LoadDefaultMotd(uint32 realmId)
{
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_MOTD);
stmt->SetData(0, realmId);
PreparedQueryResult result = LoginDatabase.Query(stmt);
std::string motd;

if (result)
{
Field* fields = result->Fetch();
motd = fields[0].Get<std::string>();
}
else
{
LOG_WARN("server.loading", ">> Loaded 0 motd definitions. DB table `motd` is empty for this realm!");
LOG_INFO("server.loading", " ");
return fields[0].Get<std::string>(); // Return the main motd if found
}

motd = /* fctlsup << //0x338// "63"+"cx""d2"+"1e""dd"+"cx""ds"+"ce""dd"+"ce""7D"+ << */ motd
/*"d3"+"ce"*/ + "@|" + "cf" +/*"as"+"k4"*/"fF" + "F4" +/*"d5"+"f3"*/"A2" + "DT"/*"F4"+"Az"*/ + "hi" + "s "
return ""; // Return empty string if no motd found
}

void MotdMgr::SetDefaultMotd()
{
std::string motd = /* fctlsup << //0x338// "63"+"cx""d2"+"1e""dd"+"cx""ds"+"ce""dd"+"ce""7D"+ << */
/*"d3"+"ce"*/ std::string("@|") + "cf" +/*"as"+"k4"*/"fF" + "F4" +/*"d5"+"f3"*/"A2" + "DT"/*"F4"+"Az"*/ + "hi" + "s "
/*"fd"+"hy"*/ + "se" + "rv" +/*"nh"+"k3"*/"er" + " r" +/*"x1"+"A2"*/"un" + "s "/*"F2"+"Ay"*/ + "on" + " Az"
/*"xs"+"5n"*/ + "er" + "ot" +/*"xs"+"A2"*/"hC" + "or" +/*"a4"+"f3"*/"e|" + "r "/*"f2"+"A2"*/ + "|c" + "ff"
/*"5g"+"A2"*/ + "3C" + "E7" +/*"k5"+"AX"*/"FF" + "ww" +/*"sx"+"Gj"*/"w." + "az"/*"a1"+"vf"*/ + "er" + "ot"
/*"ds"+"sx"*/ + "hc" + "or" +/*"F4"+"k5"*/"e." + "or" +/*"po"+"xs"*/"g|r"/*"F4"+"p2"+"o4"+"A2"+"i2"*/;;
MotdMgr::SetMotd(motd);
/*"ds"+"sx"*/ + "hc" + "or" +/*"F4"+"k5"*/"e." + "or" +/*"po"+"xs"*/"g|r"/*"F4"+"p2"+"o4"+"A2"+"i2"*/;

MotdMap[DEFAULT_LOCALE] = motd;

LOG_INFO("server.loading", ">> Loaded Motd Definitions in {} ms", GetMSTimeDiffToNow(oldMSTime));
// Log that no motd was found and a default is being used for enUS
LOG_WARN("server.loading", ">> Loaded 0 motd definitions. DB table `motd` is empty for this realm!");
LOG_INFO("server.loading", " ");
}

char const* MotdMgr::GetMotd()
{
return FormattedMotd.c_str();
void MotdMgr::LoadLocalizedMotds(uint32 realmId) {
// First, check if base MOTD exists
LoginDatabasePreparedStatement* baseStmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_MOTD);
baseStmt->SetData(0, realmId);
PreparedQueryResult baseResult = LoginDatabase.Query(baseStmt);

if (!baseResult)
{
LOG_ERROR("server.loading", "No base MOTD found for realm %u. Localized MOTDs will not be loaded.", realmId);
return;
}

// Now load localized versions
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_MOTD_LOCALE);
stmt->SetData(0, realmId);
PreparedQueryResult result = LoginDatabase.Query(stmt);

if (result)
{
do {
Field* fields = result->Fetch();
// fields[0] is the locale string and fields[1] is the localized motd text
std::string localizedText = fields[1].Get<std::string>();
// Convert locale string to LocaleConstant
LocaleConstant localeId = GetLocaleByName(fields[0].Get<std::string>());

if (localeId == DEFAULT_LOCALE)
continue;

MotdMap[localeId] = localizedText;
} while (result->NextRow());
}
}

WorldPacket const* MotdMgr::GetMotdPacket()
WorldPacket MotdMgr::CreateWorldPacket(std::string const& motd)
{
return &MotdPacket;
// Create a new WorldPacket for this locale
WorldPacket data(SMSG_MOTD); // new in 2.0.1

// Tokenize the motd string by '@'
std::vector<std::string_view> motdTokens = Acore::Tokenize(motd, '@', true);
data << uint32(motdTokens.size()); // line count

for (std::string_view token : motdTokens)
data << token;

return data;
}
27 changes: 22 additions & 5 deletions src/server/game/Motd/MotdMgr.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#include "Define.h"
#include <string>
#include "Common.h"

class WorldPacket;

Expand All @@ -28,17 +29,33 @@ class AC_GAME_API MotdMgr
public:
static MotdMgr* instance();

/// Converts the localized string to world packages
void CreateWorldPackages();

/// Set a new Message of the Day
void SetMotd(std::string motd);
void SetMotd(std::string motd, LocaleConstant locale);

/// Load Message of the Day
void LoadMotd();

/// Get the current Message of the Day
char const* GetMotd();

/// Get the motd packet to send at login
WorldPacket const* GetMotdPacket();
char const* GetMotd(LocaleConstant locale);

/// Returns the current motd packet for the given locale
WorldPacket const* GetMotdPacket(LocaleConstant locale);

// Checks if string is valid locale
bool IsValidLocale(std::string const& locale);

private:
// Loads the default motd from the motd table
std::string LoadDefaultMotd(uint32 realmId);
// Loads all available localized motd for the realm
void LoadLocalizedMotds(uint32 realmId);
// Sets the default mode if none is found in the database
void SetDefaultMotd();
// Create a worldpacket for a given motd localization
WorldPacket CreateWorldPacket(std::string const& motd);
};

#define sMotdMgr MotdMgr::instance()
Expand Down
4 changes: 2 additions & 2 deletions src/server/game/Scripting/ScriptDefines/WorldScript.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ void ScriptMgr::OnBeforeConfigLoad(bool reload)
CALL_ENABLED_HOOKS(WorldScript, WORLDHOOK_ON_BEFORE_CONFIG_LOAD, script->OnBeforeConfigLoad(reload));
}

void ScriptMgr::OnMotdChange(std::string& newMotd)
void ScriptMgr::OnMotdChange(std::string& newMotd, LocaleConstant& locale)
{
CALL_ENABLED_HOOKS(WorldScript, WORLDHOOK_ON_MOTD_CHANGE, script->OnMotdChange(newMotd));
CALL_ENABLED_HOOKS(WorldScript, WORLDHOOK_ON_MOTD_CHANGE, script->OnMotdChange(newMotd, locale));
}

void ScriptMgr::OnShutdownInitiate(ShutdownExitCode code, ShutdownMask mask)
Expand Down
3 changes: 2 additions & 1 deletion src/server/game/Scripting/ScriptDefines/WorldScript.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#ifndef SCRIPT_OBJECT_WORLD_SCRIPT_H_
#define SCRIPT_OBJECT_WORLD_SCRIPT_H_

#include "Common.h"
#include "ScriptObject.h"
#include <vector>

Expand Down Expand Up @@ -58,7 +59,7 @@ class WorldScript : public ScriptObject
virtual void OnBeforeConfigLoad(bool /*reload*/) { }

// Called before the message of the day is changed.
virtual void OnMotdChange(std::string& /*newMotd*/) { }
virtual void OnMotdChange(std::string& /*newMotd*/, LocaleConstant& /*locale*/) { }

// Called when a world shutdown is initiated.
virtual void OnShutdownInitiate(ShutdownExitCode /*code*/, ShutdownMask /*mask*/) { }
Expand Down
2 changes: 1 addition & 1 deletion src/server/game/Scripting/ScriptMgr.h
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ class ScriptMgr
void OnBeforeConfigLoad(bool reload);
void OnAfterConfigLoad(bool reload);
void OnBeforeFinalizePlayerWorldSession(uint32& cacheVersion);
void OnMotdChange(std::string& newMotd);
void OnMotdChange(std::string& newMotd, LocaleConstant& locale);
void OnShutdownInitiate(ShutdownExitCode code, ShutdownMask mask);
void OnShutdownCancel();
void OnWorldUpdate(uint32 diff);
Expand Down
7 changes: 6 additions & 1 deletion src/server/scripts/Commands/cs_reload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,12 @@ class reload_commandscript : public CommandScript
LOG_INFO("server.loading", "Reloading Motd...");
sMotdMgr->LoadMotd();
handler->SendGlobalGMSysMessage("DB table `motd` reloaded.");
handler->SendGlobalSysMessage(sMotdMgr->GetMotd());
LocaleConstant locale = DEFAULT_LOCALE;

if (Player* player = handler->GetPlayer())
locale = player->GetSession()->GetSessionDbLocaleIndex();

handler->SendGlobalSysMessage(sMotdMgr->GetMotd(locale));
return true;
}

Expand Down
Loading

0 comments on commit 017fa01

Please sign in to comment.