diff options
Diffstat (limited to 'src/server/game/Chat/Chat.cpp')
-rw-r--r-- | src/server/game/Chat/Chat.cpp | 555 |
1 files changed, 59 insertions, 496 deletions
diff --git a/src/server/game/Chat/Chat.cpp b/src/server/game/Chat/Chat.cpp index 014c78124d..80d6104dd4 100644 --- a/src/server/game/Chat/Chat.cpp +++ b/src/server/game/Chat/Chat.cpp @@ -15,10 +15,9 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include "Chat.h" #include "AccountMgr.h" #include "CellImpl.h" -#include "Chat.h" -#include "ChatLink.h" #include "Common.h" #include "DatabaseEnv.h" #include "GridNotifiersImpl.h" @@ -30,6 +29,7 @@ #include "Realm.h" #include "ScriptMgr.h" #include "SpellMgr.h" +#include "Tokenize.h" #include "UpdateMask.h" #include "World.h" #include "WorldPacket.h" @@ -39,45 +39,9 @@ #include "LuaEngine.h" #endif -bool ChatHandler::load_command_table = true; - -std::vector<ChatCommand> const& ChatHandler::getCommandTable() +Player* ChatHandler::GetPlayer() const { - static std::vector<ChatCommand> commandTableCache; - - if (LoadCommandTable()) - { - SetLoadCommandTable(false); - - std::vector<ChatCommand> cmds = sScriptMgr->GetChatCommands(); - commandTableCache.swap(cmds); - - WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_SEL_COMMANDS); - PreparedQueryResult result = WorldDatabase.Query(stmt); - if (result) - { - do - { - Field* fields = result->Fetch(); - std::string name = fields[0].GetString(); - - SetDataForCommandInTable(commandTableCache, name.c_str(), fields[1].GetUInt8(), fields[2].GetString(), name); - } while (result->NextRow()); - } - } - - return commandTableCache; -} - -std::string ChatHandler::PGetParseString(uint32 entry, ...) const -{ - const char* format = GetAcoreString(entry); - char str[1024]; - va_list ap; - va_start(ap, entry); - vsnprintf(str, 1024, format, ap); - va_end(ap); - return std::string(str); + return m_session ? m_session->GetPlayer() : nullptr; } char const* ChatHandler::GetAcoreString(uint32 entry) const @@ -85,10 +49,10 @@ char const* ChatHandler::GetAcoreString(uint32 entry) const return m_session->GetAcoreString(entry); } -bool ChatHandler::isAvailable(ChatCommand const& cmd) const +bool ChatHandler::IsAvailable(uint32 securityLevel) const { // check security level only for simple command (without child commands) - return m_session->GetSecurity() >= AccountTypes(cmd.SecurityLevel); + return m_session->GetSecurity() >= AccountTypes(securityLevel); } bool ChatHandler::HasLowerSecurity(Player* target, ObjectGuid guid, bool strong) @@ -141,83 +105,50 @@ bool ChatHandler::HasLowerSecurityAccount(WorldSession* target, uint32 target_ac return false; } -bool ChatHandler::hasStringAbbr(const char* name, const char* part) +void ChatHandler::SendSysMessage(std::string_view str, bool escapeCharacters) { - // non "" command - if (*name) + std::string msg{ str }; + + // Replace every "|" with "||" in msg + if (escapeCharacters && msg.find('|') != std::string::npos) { - // "" part from non-"" command - if (!*part) - return false; + std::vector<std::string_view> tokens = Acore::Tokenize(msg, '|', true); + std::ostringstream stream; - while (true) - { - if (!*part) - return true; - else if (!*name) - return false; - else if (tolower(*name) != tolower(*part)) - return false; - ++name; - ++part; - } - } - // allow with any for "" + for (size_t i = 0; i < tokens.size() - 1; ++i) + stream << tokens[i] << "||"; - return true; -} + stream << tokens[tokens.size() - 1]; -void ChatHandler::SendSysMessage(const char* str) -{ - WorldPacket data; - - // need copy to prevent corruption by strtok call in LineFromMessage original string - char* buf = strdup(str); - char* pos = buf; + msg = stream.str(); + } - while (char* line = LineFromMessage(pos)) + WorldPacket data; + for (std::string_view line : Acore::Tokenize(str, '\n', true)) { BuildChatPacket(data, CHAT_MSG_SYSTEM, LANG_UNIVERSAL, nullptr, nullptr, line); m_session->SendPacket(&data); } - - free(buf); } void ChatHandler::SendGlobalSysMessage(const char* str) { - // Chat output WorldPacket data; - - // need copy to prevent corruption by strtok call in LineFromMessage original string - char* buf = strdup(str); - char* pos = buf; - - while (char* line = LineFromMessage(pos)) + for (std::string_view line : Acore::Tokenize(str, '\n', true)) { BuildChatPacket(data, CHAT_MSG_SYSTEM, LANG_UNIVERSAL, nullptr, nullptr, line); sWorld->SendGlobalMessage(&data); } - - free(buf); } void ChatHandler::SendGlobalGMSysMessage(const char* str) { - // Chat output WorldPacket data; - - // need copy to prevent corruption by strtok call in LineFromMessage original string - char* buf = strdup(str); - char* pos = buf; - - while (char* line = LineFromMessage(pos)) + for (std::string_view line : Acore::Tokenize(str, '\n', true)) { BuildChatPacket(data, CHAT_MSG_SYSTEM, LANG_UNIVERSAL, nullptr, nullptr, line); sWorld->SendGlobalGMMessage(&data); } - - free(buf); } void ChatHandler::SendSysMessage(uint32 entry) @@ -225,388 +156,42 @@ void ChatHandler::SendSysMessage(uint32 entry) SendSysMessage(GetAcoreString(entry)); } -void ChatHandler::PSendSysMessage(uint32 entry, ...) -{ - const char* format = GetAcoreString(entry); - va_list ap; - char str [2048]; - va_start(ap, entry); - vsnprintf(str, 2048, format, ap); - va_end(ap); - SendSysMessage(str); -} - -void ChatHandler::PSendSysMessage(const char* format, ...) -{ - va_list ap; - char str [2048]; - va_start(ap, format); - vsnprintf(str, 2048, format, ap); - va_end(ap); - SendSysMessage(str); -} - -bool ChatHandler::ExecuteCommandInTable(std::vector<ChatCommand> const& table, const char* text, std::string const& fullcmd) -{ - char const* oldtext = text; - std::string cmd = ""; - - while (*text != ' ' && *text != '\0') - { - cmd += *text; - ++text; - } - - while (*text == ' ') ++text; - - for (uint32 i = 0; i < table.size(); ++i) - { - if (!hasStringAbbr(table[i].Name, cmd.c_str())) - continue; - - bool match = false; - if (strlen(table[i].Name) > cmd.length()) - { - for (uint32 j = 0; j < table.size(); ++j) - { - if (!hasStringAbbr(table[j].Name, cmd.c_str())) - continue; - - if (strcmp(table[j].Name, cmd.c_str()) == 0) - { - match = true; - break; - } - } - } - if (match) - continue; - - // select subcommand from child commands list - if (!table[i].ChildCommands.empty()) - { - if (!ExecuteCommandInTable(table[i].ChildCommands, text, fullcmd.c_str())) - { -#ifdef ELUNA - if (!sEluna->OnCommand(GetSession() ? GetSession()->GetPlayer() : nullptr, oldtext)) - return true; -#endif - if (text[0] != '\0') - SendSysMessage(LANG_NO_SUBCMD); - else - SendSysMessage(LANG_CMD_SYNTAX); - - ShowHelpForCommand(table[i].ChildCommands, text); - } - - return true; - } - - // must be available and have handler - if (!table[i].Handler || !isAvailable(table[i])) - continue; - - SetSentErrorMessage(false); - // table[i].Name == "" is special case: send original command to handler - if ((table[i].Handler)(this, table[i].Name[0] != '\0' ? text : oldtext)) - { - if (!m_session) // ignore console - return true; - - Player* player = m_session->GetPlayer(); - if (!AccountMgr::IsPlayerAccount(m_session->GetSecurity())) - { - ObjectGuid guid = player->GetTarget(); - uint32 areaId = player->GetAreaId(); - uint32 zoneId = player->GetZoneId(); - std::string areaName = "Unknown"; - std::string zoneName = "Unknown"; - int locale = GetSessionDbcLocale(); - - if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(areaId)) - { - areaName = area->area_name[locale]; - } - - if (AreaTableEntry const* zone = sAreaTableStore.LookupEntry(zoneId)) - { - zoneName = zone->area_name[locale]; - } - - LOG_GM(m_session->GetAccountId(), "Command: %s [Player: %s (%s) (Account: %u) X: %f Y: %f Z: %f Map: %u (%s) Area: %u (%s) Zone: %u (%s) Selected: %s (%s)]", - fullcmd.c_str(), player->GetName().c_str(), player->GetGUID().ToString().c_str(), - m_session->GetAccountId(), player->GetPositionX(), player->GetPositionY(), - player->GetPositionZ(), player->GetMapId(), - player->GetMap()->GetMapName(), - areaId, areaName.c_str(), zoneId, zoneName.c_str(), - (player->GetSelectedUnit()) ? player->GetSelectedUnit()->GetName().c_str() : "", - guid.ToString().c_str()); - } - } - // some commands have custom error messages. Don't send the default one in these cases. - else if (!HasSentErrorMessage()) - { - if (!table[i].Help.empty()) - SendSysMessage(table[i].Help.c_str()); - else - SendSysMessage(LANG_CMD_SYNTAX); - } - - return true; - } - - return false; -} - -bool ChatHandler::SetDataForCommandInTable(std::vector<ChatCommand>& table, char const* text, uint32 security, std::string const& help, std::string const& fullcommand) +bool ChatHandler::_ParseCommands(std::string_view text) { - std::string cmd = ""; - - while (*text != ' ' && *text != '\0') - { - cmd += *text; - ++text; - } - - while (*text == ' ') ++text; - - for (uint32 i = 0; i < table.size(); i++) - { - // for data fill use full explicit command names - if (table[i].Name != cmd) - continue; - - // select subcommand from child commands list (including "") - if (!table[i].ChildCommands.empty()) - { - if (SetDataForCommandInTable(table[i].ChildCommands, text, security, help, fullcommand)) - return true; - else if (*text) - return false; - - // fail with "" subcommands, then use normal level up command instead - } - // expected subcommand by full name DB content - else if (*text) - { - LOG_ERROR("sql.sql", "Table `command` have unexpected subcommand '%s' in command '%s', skip.", text, fullcommand.c_str()); - return false; - } - - table[i].SecurityLevel = security; - table[i].Help = help; + if (Acore::ChatCommands::TryExecuteCommand(*this, text)) return true; - } - - // in case "" command let process by caller - if (!cmd.empty()) - { - if (&table == &getCommandTable()) - LOG_ERROR("sql.sql", "Table `command` have non-existing command '%s', skip.", cmd.c_str()); - else - LOG_ERROR("sql.sql", "Table `command` have non-existing subcommand '%s' in command '%s', skip.", cmd.c_str(), fullcommand.c_str()); - } - - return false; -} - -bool ChatHandler::ParseCommands(char const* text) -{ - ASSERT(text); - ASSERT(*text); - - std::string fullcmd = text; + // Pretend commands don't exist for regular players if (m_session && AccountMgr::IsPlayerAccount(m_session->GetSecurity()) && !sWorld->getBoolConfig(CONFIG_ALLOW_PLAYER_COMMANDS)) return false; - /// chat case (.command or !command format) - if (m_session) - { - if (text[0] != '!' && text[0] != '.') - return false; - } - - /// ignore single . and ! in line - if (strlen(text) < 2) - return false; - // original `text` can't be used. It content destroyed in command code processing. - - /// ignore messages staring from many dots. - if ((text[0] == '.' && text[1] == '.') || (text[0] == '!' && text[1] == '!')) - return false; - - /// skip first . or ! (in console allowed use command with . and ! and without its) - if (text[0] == '!' || text[0] == '.') - ++text; - - if (!ExecuteCommandInTable(getCommandTable(), text, fullcmd)) - { -#ifdef ELUNA - if (!sEluna->OnCommand(GetSession() ? GetSession()->GetPlayer() : nullptr, text)) - return true; -#endif - if (m_session && AccountMgr::IsPlayerAccount(m_session->GetSecurity())) - return false; - - SendSysMessage(LANG_NO_CMD); - } + // Send error message for GMs + PSendSysMessage(LANG_CMD_INVALID, STRING_VIEW_FMT_ARG(text)); + SetSentErrorMessage(true); return true; } -bool ChatHandler::isValidChatMessage(char const* message) +bool ChatHandler::ParseCommands(std::string_view text) { - /* - Valid examples: - |cffa335ee|Hitem:812:0:0:0:0:0:0:0:70|h[Glowing Brightwood Staff]|h|r - |cff808080|Hquest:2278:47|h[The Platinum Discs]|h|r - |cffffd000|Htrade:4037:1:150:1:6AAAAAAAAAAAAAAAAAAAAAAOAADAAAAAAAAAAAAAAAAIAAAAAAAAA|h[Engineering]|h|r - |cff4e96f7|Htalent:2232:-1|h[Taste for Blood]|h|r - |cff71d5ff|Hspell:21563|h[Command]|h|r - |cffffd000|Henchant:3919|h[Engineering: Rough Dynamite]|h|r - |cffffff00|Hachievement:546:0000000000000001:0:0:0:-1:0:0:0:0|h[Safe Deposit]|h|r - |cff66bbff|Hglyph:21:762|h[Glyph of Bladestorm]|h|r - - | will be escaped to || - */ - - if (strlen(message) > 255) - return false; - - // more simple checks - if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_SEVERITY) < 3) - { - const char validSequence[6] = "cHhhr"; - const char* validSequenceIterator = validSequence; - const std::string validCommands = "cHhr|"; + ASSERT(!text.empty()); - while (*message) - { - // find next pipe command - message = strchr(message, '|'); - - if (!message) - return true; - - ++message; - char commandChar = *message; - if (validCommands.find(commandChar) == std::string::npos) - return false; - - ++message; - // validate sequence - if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_SEVERITY) == 2) - { - if (commandChar == *validSequenceIterator) - { - if (validSequenceIterator == validSequence + 4) - validSequenceIterator = validSequence; - else - ++validSequenceIterator; - } - else - return false; - } - } - return true; - } - - return LinkExtractor(message).IsValidMessage(); -} - -bool ChatHandler::ShowHelpForSubCommands(std::vector<ChatCommand> const& table, char const* cmd, char const* subcmd) -{ - std::string list; - for (uint32 i = 0; i < table.size(); ++i) - { - // must be available (ignore handler existence for show command with possible available subcommands) - if (!isAvailable(table[i])) - continue; - - // for empty subcmd show all available - if (*subcmd && !hasStringAbbr(table[i].Name, subcmd)) - continue; - - if (m_session) - list += "\n |- "; - else - list += "\n\r |- "; - - list += table[i].Name; - - if (!table[i].ChildCommands.empty()) - list += " ..."; - } - - if (list.empty()) + // chat case (.command or !command format) + if ((text[0] != '!') && (text[0] != '.')) return false; - if (&table == &getCommandTable()) - { - SendSysMessage(LANG_AVIABLE_CMD); - PSendSysMessage("%s", list.c_str()); - } - else - PSendSysMessage(LANG_SUBCMDS_LIST, cmd, list.c_str()); - - return true; -} - -bool ChatHandler::ShowHelpForCommand(std::vector<ChatCommand> const& table, const char* cmd) -{ - if (*cmd) - { - for (uint32 i = 0; i < table.size(); ++i) - { - // must be available (ignore handler existence for show command with possible available subcommands) - if (!isAvailable(table[i])) - continue; - - if (!hasStringAbbr(table[i].Name, cmd)) - continue; - - // have subcommand - char const* subcmd = (*cmd) ? strtok(nullptr, " ") : ""; - - if (!table[i].ChildCommands.empty() && subcmd && *subcmd) - { - if (ShowHelpForCommand(table[i].ChildCommands, subcmd)) - return true; - } - - if (!table[i].Help.empty()) - SendSysMessage(table[i].Help.c_str()); - - if (!table[i].ChildCommands.empty()) - if (ShowHelpForSubCommands(table[i].ChildCommands, table[i].Name, subcmd ? subcmd : "")) - return true; - - return !table[i].Help.empty(); - } - } - else - { - for (uint32 i = 0; i < table.size(); ++i) - { - // must be available (ignore handler existence for show command with possible available subcommands) - if (!isAvailable(table[i])) - continue; - - if (strlen(table[i].Name)) - continue; - - if (!table[i].Help.empty()) - SendSysMessage(table[i].Help.c_str()); + // ignore single . and ! in line + if (text.length() < 2) + return false; - if (!table[i].ChildCommands.empty()) - if (ShowHelpForSubCommands(table[i].ChildCommands, "", "")) - return true; + // ignore messages staring from many dots. + if (text[1] == text[0]) + return false; - return !table[i].Help.empty(); - } - } + // ignore messages with separator after . + if (text[1] == Acore::Impl::ChatCommands::COMMAND_DELIMITER) + return false; - return ShowHelpForSubCommands(table, "", cmd); + return _ParseCommands(text.substr(1)); } size_t ChatHandler::BuildChatPacket(WorldPacket& data, ChatMsg chatType, Language language, ObjectGuid senderGUID, ObjectGuid receiverGUID, std::string_view message, uint8 chatTag, @@ -1009,21 +594,6 @@ uint32 ChatHandler::extractSpellIdFromLink(char* text) return 0; } -GameTele const* ChatHandler::extractGameTeleFromLink(char* text) -{ - // id, or string, or [name] Shift-click form |color|Htele:id|h[name]|h|r - char* cId = extractKeyFromLink(text, "Htele"); - if (!cId) - return nullptr; - - // id case (explicit or from shift link) - if (cId[0] >= '0' || cId[0] <= '9') - if (uint32 id = atoi(cId)) - return sObjectMgr->GetGameTele(id); - - return sObjectMgr->GetGameTele(cId); -} - enum GuidLinkType { SPELL_LINK_PLAYER = 0, // must be first for selection in not link case @@ -1140,10 +710,15 @@ bool ChatHandler::extractPlayerTarget(char* args, Player** player, ObjectGuid* p } else { + // populate strtok buffer to prevent crashes + static char dummy[1] = ""; + strtok(dummy, ""); + Player* pl = getSelectedPlayer(); // if allowed player pointer if (player) *player = pl; + // if allowed player guid (if no then only online players allowed) if (player_guid) *player_guid = pl ? pl->GetGUID() : ObjectGuid::Empty; @@ -1163,24 +738,6 @@ bool ChatHandler::extractPlayerTarget(char* args, Player** player, ObjectGuid* p return true; } -void ChatHandler::extractOptFirstArg(char* args, char** arg1, char** arg2) -{ - char* p1 = strtok(args, " "); - char* p2 = strtok(nullptr, " "); - - if (!p2) - { - p2 = p1; - p1 = nullptr; - } - - if (arg1) - *arg1 = p1; - - if (arg2) - *arg2 = p2; -} - char* ChatHandler::extractQuotedArg(char* args) { if (!*args) @@ -1247,16 +804,22 @@ char const* CliHandler::GetAcoreString(uint32 entry) const return sObjectMgr->GetAcoreStringForDBCLocale(entry); } -bool CliHandler::isAvailable(ChatCommand const& cmd) const +void CliHandler::SendSysMessage(std::string_view str, bool /*escapeCharacters*/) { - // skip non-console commands in console case - return cmd.AllowConsole; + m_print(m_callbackArg, str); + m_print(m_callbackArg, "\r\n"); } -void CliHandler::SendSysMessage(const char* str) +bool CliHandler::ParseCommands(std::string_view str) { - m_print(m_callbackArg, str); - m_print(m_callbackArg, "\r\n"); + if (str.empty()) + return false; + + // Console allows using commands both with and without leading indicator + if (str[0] == '.' || str[0] == '!') + str = str.substr(1); + + return _ParseCommands(str); } std::string CliHandler::GetNameLink() const |