diff options
90 files changed, 4268 insertions, 2496 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json index 5f14844b00..e48b4f25d1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,8 @@ { "files.associations": { + "*.dist": "properties", + "*.crash": "properties", + "*.wtf": "properties", "array": "cpp", "atomic": "cpp", "bit": "cpp", @@ -62,7 +65,8 @@ "thread": "cpp", "cfenv": "cpp", "typeinfo": "cpp", - "codecvt": "cpp" + "codecvt": "cpp", + "xstring": "cpp" }, "deno.enable": true, "deno.path": "deps/deno/bin/deno", diff --git a/data/sql/updates/pending_db_world/rev_1622929985554698000.sql b/data/sql/updates/pending_db_world/rev_1622929985554698000.sql new file mode 100644 index 0000000000..12a4aa93d2 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1622929985554698000.sql @@ -0,0 +1,37 @@ +INSERT INTO `version_db_world` (`sql_rev`) VALUES ('1622929985554698000'); + +DELETE FROM `acore_string` WHERE `entry` BETWEEN 1500 AND 1515; +DELETE FROM `acore_string` WHERE `entry` IN (6, 7, 8, 191, 192, 193, 194, 195, 196); +INSERT INTO `acore_string` (`entry`,`content_default`) VALUES +( 6, 'Command \'%.*s\' does not exist'), +( 7, 'Subcommand \'%.*s%c%.*s\' is ambiguous:'), +( 8, 'Possible subcommands:'), +(191, '|- %.*s'), +(192, '|- %.*s ...'), +(193, 'Subcommand \'%.*s%c%.*s\' does not exist.'), +(194, 'Command \'%.*s\' is ambiguous:'), +(195, '### USAGE: .%.*s ...'), +(196, 'There is no detailed usage information associated with \'%.*s\'. +This should never occur for stock AzerothCore commands - if it does, report this as a bug.'), +(1500, 'Either:'), +(1501, 'Or: '), +(1502, 'Value \'%.*s\' is not valid for type %s.'), +(1503, 'Invalid UTF-8 sequences found in string.'), +(1504, 'Provided link has invalid link data.'), +(1505, 'Account \'%.*s\' does not exist.'), +(1506, 'Account ID %u does not exist.'), +(1507, '%s does not exist.'), +(1508, 'Character \'%.*s\' does not exist.'), +(1509, '\'%.*s\' is not a valid character name.'), +(1510, 'Achievement ID %u does not exist.'), +(1511, 'Teleport location %u does not exist.'), +(1512, 'Teleport location \'%.*s\' does not exist.'), +(1513, 'Item ID %u does not exist.'), +(1514, 'Spell ID %u does not exist.'), +(1515, 'Expected \'%.*s\', got \'%.*s\' instead.'); + +-- Correct +UPDATE `command` SET `security`='3' WHERE `name` = 'debug anim'; +UPDATE `command` SET `security`='4' WHERE `name` = 'modify spell'; +UPDATE `command` SET `security`='3' WHERE `name` = 'npc set allowmove'; +UPDATE `command` SET `security`='3' WHERE `name` LIKE '%unban%'; diff --git a/src/common/Define.h b/src/common/Define.h index d065370836..ab007ee89c 100644 --- a/src/common/Define.h +++ b/src/common/Define.h @@ -103,6 +103,9 @@ #define SZFMTD "%" PRIuPTR +#define STRING_VIEW_FMT "%.*s" +#define STRING_VIEW_FMT_ARG(str) static_cast<int>((str).length()), (str).data() + typedef std::int64_t int64; typedef std::int32_t int32; typedef std::int16_t int16; diff --git a/src/common/Utilities/Util.cpp b/src/common/Utilities/Util.cpp index ad0024085e..a942fd9a95 100644 --- a/src/common/Utilities/Util.cpp +++ b/src/common/Utilities/Util.cpp @@ -27,6 +27,7 @@ #include <iomanip> #include <sstream> #include <string> +#include <boost/core/demangle.hpp> #include <utf8.h> Tokenizer::Tokenizer(const std::string& src, const char sep, uint32 vectorReserve) @@ -409,12 +410,12 @@ bool Utf8toWStr(char const* utf8str, size_t csize, wchar_t* wstr, size_t& wsize) return true; } -bool Utf8toWStr(const std::string& utf8str, std::wstring& wstr) +bool Utf8toWStr(std::string_view utf8str, std::wstring& wstr) { wstr.clear(); try { - utf8::utf8to16(utf8str.c_str(), utf8str.c_str() + utf8str.size(), std::back_inserter(wstr)); + utf8::utf8to16(utf8str.begin(), utf8str.end(), std::back_inserter(wstr)); } catch (std::exception const&) { @@ -425,18 +426,19 @@ bool Utf8toWStr(const std::string& utf8str, std::wstring& wstr) return true; } -bool WStrToUtf8(wchar_t* wstr, size_t size, std::string& utf8str) +bool WStrToUtf8(wchar_t const* wstr, size_t size, std::string& utf8str) { try { std::string utf8str2; - utf8str2.resize(size * 4); // allocate for most long case + utf8str2.resize(size * 4); // allocate for most long case if (size) { char* oend = utf8::utf16to8(wstr, wstr + size, &utf8str2[0]); - utf8str2.resize(oend - (&utf8str2[0])); // remove unused tail + utf8str2.resize(oend - (&utf8str2[0])); // remove unused tail } + utf8str = utf8str2; } catch (std::exception const&) @@ -448,18 +450,19 @@ bool WStrToUtf8(wchar_t* wstr, size_t size, std::string& utf8str) return true; } -bool WStrToUtf8(std::wstring const& wstr, std::string& utf8str) +bool WStrToUtf8(std::wstring_view wstr, std::string& utf8str) { try { std::string utf8str2; - utf8str2.resize(wstr.size() * 4); // allocate for most long case + utf8str2.resize(wstr.size() * 4); // allocate for most long case - if (wstr.size()) + if (!wstr.empty()) { - char* oend = utf8::utf16to8(wstr.c_str(), wstr.c_str() + wstr.size(), &utf8str2[0]); - utf8str2.resize(oend - (&utf8str2[0])); // remove unused tail + char* oend = utf8::utf16to8(wstr.begin(), wstr.end(), &utf8str2[0]); + utf8str2.resize(oend - (&utf8str2[0])); // remove unused tail } + utf8str = utf8str2; } catch (std::exception const&) @@ -471,17 +474,10 @@ bool WStrToUtf8(std::wstring const& wstr, std::string& utf8str) return true; } -typedef wchar_t const* const* wstrlist; - -void wstrToUpper(std::wstring& str) -{ - std::transform(str.begin(), str.end(), str.begin(), wcharToUpper); -} - -void wstrToLower(std::wstring& str) -{ - std::transform(str.begin(), str.end(), str.begin(), wcharToLower); -} +void wstrToUpper(std::wstring& str) { std::transform(std::begin(str), std::end(str), std::begin(str), wcharToUpper); } +void wstrToLower(std::wstring& str) { std::transform(std::begin(str), std::end(str), std::begin(str), wcharToLower); } +void strToUpper(std::string& str) { std::transform(std::begin(str), std::end(str), std::begin(str), charToUpper); } +void strToLower(std::string& str) { std::transform(std::begin(str), std::end(str), std::begin(str), charToLower); } std::wstring GetMainPartOfName(std::wstring const& wname, uint32 declension) { @@ -539,7 +535,7 @@ std::wstring GetMainPartOfName(std::wstring const& wname, uint32 declension) return wname; } -bool utf8ToConsole(const std::string& utf8str, std::string& conStr) +bool utf8ToConsole(std::string_view utf8str, std::string& conStr) { #if AC_PLATFORM == AC_PLATFORM_WINDOWS std::wstring wstr; @@ -558,7 +554,7 @@ bool utf8ToConsole(const std::string& utf8str, std::string& conStr) return true; } -bool consoleToUtf8(const std::string& conStr, std::string& utf8str) +bool consoleToUtf8(std::string_view conStr, std::string& utf8str) { #if AC_PLATFORM == AC_PLATFORM_WINDOWS std::wstring wstr; @@ -573,7 +569,7 @@ bool consoleToUtf8(const std::string& conStr, std::string& utf8str) #endif } -bool Utf8FitTo(const std::string& str, std::wstring const& search) +bool Utf8FitTo(std::string_view str, std::wstring_view search) { std::wstring temp; @@ -661,7 +657,7 @@ std::string Acore::Impl::ByteArrayToHexStr(uint8 const* bytes, size_t arrayLen, return ss.str(); } -void Acore::Impl::HexStrToByteArray(std::string const& str, uint8* out, size_t outlen, bool reverse /*= false*/) +void Acore::Impl::HexStrToByteArray(std::string_view str, uint8* out, size_t outlen, bool reverse /*= false*/) { ASSERT(str.size() == (2 * outlen)); @@ -684,13 +680,23 @@ void Acore::Impl::HexStrToByteArray(std::string const& str, uint8* out, size_t o } } -bool StringContainsStringI(std::string const& haystack, std::string const& needle) +bool StringEqualI(std::string_view a, std::string_view b) +{ + return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](char c1, char c2) { return std::tolower(c1) == std::tolower(c2); }); +} + +bool StringContainsStringI(std::string_view haystack, std::string_view needle) { return haystack.end() != - std::search(haystack.begin(), haystack.end(), needle.begin(), needle.end(), [](char c1, char c2) { return std::toupper(c1) == std::toupper(c2); }); + std::search(haystack.begin(), haystack.end(), needle.begin(), needle.end(), [](char c1, char c2) { return std::tolower(c1) == std::tolower(c2); }); } -bool StringEqualI(std::string_view a, std::string_view b) +bool StringCompareLessI(std::string_view a, std::string_view b) { - return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](char c1, char c2) { return std::tolower(c1) == std::tolower(c2); }); + return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(), [](char c1, char c2) { return std::tolower(c1) < std::tolower(c2); }); +} + +std::string GetTypeName(std::type_info const& info) +{ + return boost::core::demangle(info.name()); } diff --git a/src/common/Utilities/Util.h b/src/common/Utilities/Util.h index 116c67f3b5..4fbe541080 100644 --- a/src/common/Utilities/Util.h +++ b/src/common/Utilities/Util.h @@ -119,19 +119,20 @@ inline T RoundToInterval(T& num, T floor, T ceil) } // UTF8 handling -bool Utf8toWStr(const std::string& utf8str, std::wstring& wstr); +AC_COMMON_API bool Utf8toWStr(std::string_view utf8str, std::wstring& wstr); // in wsize==max size of buffer, out wsize==real string size -bool Utf8toWStr(char const* utf8str, size_t csize, wchar_t* wstr, size_t& wsize); +AC_COMMON_API bool Utf8toWStr(char const* utf8str, size_t csize, wchar_t* wstr, size_t& wsize); -inline bool Utf8toWStr(const std::string& utf8str, wchar_t* wstr, size_t& wsize) +inline bool Utf8toWStr(std::string_view utf8str, wchar_t* wstr, size_t& wsize) { - return Utf8toWStr(utf8str.c_str(), utf8str.size(), wstr, wsize); + return Utf8toWStr(utf8str.data(), utf8str.size(), wstr, wsize); } -bool WStrToUtf8(std::wstring const& wstr, std::string& utf8str); +AC_COMMON_API bool WStrToUtf8(std::wstring_view wstr, std::string& utf8str); + // size==real string size -bool WStrToUtf8(wchar_t* wstr, size_t size, std::string& utf8str); +AC_COMMON_API bool WStrToUtf8(wchar_t const* wstr, size_t size, std::string& utf8str); // set string to "" if invalid utf8 sequence size_t utf8length(std::string& utf8str); @@ -263,7 +264,7 @@ inline bool isNumericOrSpace(wchar_t wchar) return isNumeric(wchar) || wchar == L' '; } -inline bool isBasicLatinString(const std::wstring& wstr, bool numericOrSpace) +inline bool isBasicLatinString(std::wstring_view wstr, bool numericOrSpace) { for (wchar_t i : wstr) if (!isBasicLatinCharacter(i) && (!numericOrSpace || !isNumericOrSpace(i))) @@ -273,7 +274,7 @@ inline bool isBasicLatinString(const std::wstring& wstr, bool numericOrSpace) return true; } -inline bool isExtendedLatinString(const std::wstring& wstr, bool numericOrSpace) +inline bool isExtendedLatinString(std::wstring_view wstr, bool numericOrSpace) { for (wchar_t i : wstr) if (!isExtendedLatinCharacter(i) && (!numericOrSpace || !isNumericOrSpace(i))) @@ -283,7 +284,7 @@ inline bool isExtendedLatinString(const std::wstring& wstr, bool numericOrSpace) return true; } -inline bool isCyrillicString(const std::wstring& wstr, bool numericOrSpace) +inline bool isCyrillicString(std::wstring_view wstr, bool numericOrSpace) { for (wchar_t i : wstr) if (!isCyrillicCharacter(i) && (!numericOrSpace || !isNumericOrSpace(i))) @@ -293,7 +294,7 @@ inline bool isCyrillicString(const std::wstring& wstr, bool numericOrSpace) return true; } -inline bool isEastAsianString(const std::wstring& wstr, bool numericOrSpace) +inline bool isEastAsianString(std::wstring_view wstr, bool numericOrSpace) { for (wchar_t i : wstr) if (!isEastAsianCharacter(i) && (!numericOrSpace || !isNumericOrSpace(i))) @@ -303,6 +304,9 @@ inline bool isEastAsianString(const std::wstring& wstr, bool numericOrSpace) return true; } +inline char charToUpper(char c) { return std::toupper(c); } +inline char charToLower(char c) { return std::tolower(c); } + inline wchar_t wcharToUpper(wchar_t wchar) { if (wchar >= L'a' && wchar <= L'z') // LATIN SMALL LETTER A - LATIN SMALL LETTER Z @@ -387,24 +391,22 @@ void wstrToLower(std::wstring& str); std::wstring GetMainPartOfName(std::wstring const& wname, uint32 declension); -bool utf8ToConsole(const std::string& utf8str, std::string& conStr); -bool consoleToUtf8(const std::string& conStr, std::string& utf8str); -bool Utf8FitTo(const std::string& str, std::wstring const& search); -void utf8printf(FILE* out, const char* str, ...); -void vutf8printf(FILE* out, const char* str, va_list* ap); -bool Utf8ToUpperOnlyLatin(std::string& utf8String); +AC_COMMON_API bool utf8ToConsole(std::string_view utf8str, std::string& conStr); +AC_COMMON_API bool consoleToUtf8(std::string_view conStr, std::string& utf8str); +AC_COMMON_API bool Utf8FitTo(std::string_view str, std::wstring_view search); +AC_COMMON_API void utf8printf(FILE* out, const char* str, ...); +AC_COMMON_API void vutf8printf(FILE* out, const char* str, va_list* ap); +AC_COMMON_API bool Utf8ToUpperOnlyLatin(std::string& utf8String); bool IsIPAddress(char const* ipaddress); uint32 CreatePIDFile(const std::string& filename); uint32 GetPID(); -bool StringEqualI(std::string_view str1, std::string_view str2); - namespace Acore::Impl { - std::string ByteArrayToHexStr(uint8 const* bytes, size_t length, bool reverse = false); - void HexStrToByteArray(std::string const& str, uint8* out, size_t outlen, bool reverse = false); + AC_COMMON_API std::string ByteArrayToHexStr(uint8 const* bytes, size_t length, bool reverse = false); + AC_COMMON_API void HexStrToByteArray(std::string_view str, uint8* out, size_t outlen, bool reverse = false); } template<typename Container> @@ -414,29 +416,36 @@ std::string ByteArrayToHexStr(Container const& c, bool reverse = false) } template<size_t Size> -void HexStrToByteArray(std::string const& str, std::array<uint8, Size>& buf, bool reverse = false) +void HexStrToByteArray(std::string_view str, std::array<uint8, Size>& buf, bool reverse = false) { Acore::Impl::HexStrToByteArray(str, buf.data(), Size, reverse); } + template<size_t Size> -std::array<uint8, Size> HexStrToByteArray(std::string const& str, bool reverse = false) +std::array<uint8, Size> HexStrToByteArray(std::string_view str, bool reverse = false) { std::array<uint8, Size> arr; HexStrToByteArray(str, arr, reverse); return arr; } -bool StringContainsStringI(std::string const& haystack, std::string const& needle); +AC_COMMON_API bool StringEqualI(std::string_view str1, std::string_view str2); +inline bool StringStartsWith(std::string_view haystack, std::string_view needle) { return (haystack.substr(0, needle.length()) == needle); } +inline bool StringStartsWithI(std::string_view haystack, std::string_view needle) { return StringEqualI(haystack.substr(0, needle.length()), needle); } +AC_COMMON_API bool StringContainsStringI(std::string_view haystack, std::string_view needle); + template <typename T> -inline bool ValueContainsStringI(std::pair<T, std::string> const& haystack, std::string const& needle) +inline bool ValueContainsStringI(std::pair<T, std::string_view> const& haystack, std::string_view needle) { return StringContainsStringI(haystack.second, needle); } -#endif -//handler for operations on large flags -#ifndef _FLAG96 -#define _FLAG96 +AC_COMMON_API bool StringCompareLessI(std::string_view a, std::string_view b); + +struct StringCompareLessI_T +{ + bool operator()(std::string_view a, std::string_view b) const { return StringCompareLessI(a, b); } +}; // simple class for not-modifyable list template <typename T> @@ -984,10 +993,27 @@ private: }; template<typename E> -typename std::underlying_type<E>::type AsUnderlyingType(E enumValue) +constexpr typename std::underlying_type<E>::type AsUnderlyingType(E enumValue) { static_assert(std::is_enum<E>::value, "AsUnderlyingType can only be used with enums"); return static_cast<typename std::underlying_type<E>::type>(enumValue); } +template<typename Ret, typename T1, typename... T> +Ret* Coalesce(T1* first, T*... rest) +{ + if constexpr (sizeof...(T) > 0) + return (first ? static_cast<Ret*>(first) : Coalesce<Ret>(rest...)); + else + return static_cast<Ret*>(first); +} + +AC_COMMON_API std::string GetTypeName(std::type_info const&); + +template <typename T> +std::string GetTypeName() { return GetTypeName(typeid(T)); } + +template <typename T> +std::enable_if_t<!std::is_same_v<std::decay_t<T>, std::type_info>, std::string> GetTypeName(T&& v) { return GetTypeName(typeid(v)); } + #endif diff --git a/src/server/game/AuctionHouse/AuctionHouseMgr.cpp b/src/server/game/AuctionHouse/AuctionHouseMgr.cpp index c214d4aa08..c46f59dad2 100644 --- a/src/server/game/AuctionHouse/AuctionHouseMgr.cpp +++ b/src/server/game/AuctionHouse/AuctionHouseMgr.cpp @@ -639,19 +639,19 @@ bool AuctionHouseObject::BuildListAuctionItems(WorldPacket& data, Player* player // These are found in ItemRandomSuffix.dbc and ItemRandomProperties.dbc // even though the DBC name seems misleading - char* const* suffix = nullptr; + std::array<char const*, 16> const* suffix = nullptr; if (propRefID < 0) { const ItemRandomSuffixEntry* itemRandEntry = sItemRandomSuffixStore.LookupEntry(-item->GetItemRandomPropertyId()); if (itemRandEntry) - suffix = itemRandEntry->nameSuffix; + suffix = &itemRandEntry->Name; } else { const ItemRandomPropertiesEntry* itemRandEntry = sItemRandomPropertiesStore.LookupEntry(item->GetItemRandomPropertyId()); if (itemRandEntry) - suffix = itemRandEntry->nameSuffix; + suffix = &itemRandEntry->Name; } // dbc local name @@ -660,7 +660,7 @@ bool AuctionHouseObject::BuildListAuctionItems(WorldPacket& data, Player* player // Append the suffix (ie: of the Monkey) to the name using localization // or default enUS if localization is invalid name += ' '; - name += suffix[locdbc_idx >= 0 ? locdbc_idx : LOCALE_enUS]; + name += (*suffix)[locdbc_idx >= 0 ? locdbc_idx : LOCALE_enUS]; } } diff --git a/src/server/game/Battlegrounds/ArenaTeam.h b/src/server/game/Battlegrounds/ArenaTeam.h index 1e25ffb850..6fce979c6e 100644 --- a/src/server/game/Battlegrounds/ArenaTeam.h +++ b/src/server/game/Battlegrounds/ArenaTeam.h @@ -90,6 +90,7 @@ ERR_ARENA_TEAM_TOO_MANY_MEMBERS_S ERR_ARENA_TEAM_LEVEL_TOO_LOW_I */ +// EnumUtils: DESCRIBE THIS enum ArenaTeamTypes { ARENA_TEAM_2v2 = 2, diff --git a/src/server/game/Battlegrounds/BattlegroundMgr.h b/src/server/game/Battlegrounds/BattlegroundMgr.h index 77e13384ef..77d6d4a0ed 100644 --- a/src/server/game/Battlegrounds/BattlegroundMgr.h +++ b/src/server/game/Battlegrounds/BattlegroundMgr.h @@ -42,7 +42,7 @@ struct CreateBattlegroundData uint32 MaxPlayersPerTeam; uint32 LevelMin; uint32 LevelMax; - char* BattlegroundName; + char const* BattlegroundName; uint32 MapID; float Team1StartLocX; float Team1StartLocY; diff --git a/src/server/game/Battlegrounds/enuminfo_ArenaTeam.cpp b/src/server/game/Battlegrounds/enuminfo_ArenaTeam.cpp new file mode 100644 index 0000000000..da18ca8b2d --- /dev/null +++ b/src/server/game/Battlegrounds/enuminfo_ArenaTeam.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license: https://github.com/azerothcore/azerothcore-wotlk/blob/master/LICENSE-AGPL3 + * Copyright (C) 2021+ WarheadCore <https://github.com/WarheadCore> + * Copyright (C) 2008-2021 TrinityCore <http://www.trinitycore.org/> + */ + +#include "ArenaTeam.h" +#include "Define.h" +#include "SmartEnum.h" +#include <stdexcept> + +namespace Acore::Impl::EnumUtilsImpl +{ + +/******************************************************************\ +|* data for enum 'ArenaTeamTypes' in 'ArenaTeam.h' auto-generated *| +\******************************************************************/ +template <> +AC_API_EXPORT EnumText EnumUtils<ArenaTeamTypes>::ToString(ArenaTeamTypes value) +{ + switch (value) + { + case ARENA_TEAM_2v2: return { "ARENA_TEAM_2v2", "ARENA_TEAM_2v2", "" }; + case ARENA_TEAM_3v3: return { "ARENA_TEAM_3v3", "ARENA_TEAM_3v3", "" }; + case ARENA_TEAM_5v5: return { "ARENA_TEAM_5v5", "ARENA_TEAM_5v5", "" }; + default: throw std::out_of_range("value"); + } +} + +template <> +AC_API_EXPORT size_t EnumUtils<ArenaTeamTypes>::Count() { return 3; } + +template <> +AC_API_EXPORT ArenaTeamTypes EnumUtils<ArenaTeamTypes>::FromIndex(size_t index) +{ + switch (index) + { + case 0: return ARENA_TEAM_2v2; + case 1: return ARENA_TEAM_3v3; + case 2: return ARENA_TEAM_5v5; + default: throw std::out_of_range("index"); + } +} + +template <> +AC_API_EXPORT size_t EnumUtils<ArenaTeamTypes>::ToIndex(ArenaTeamTypes value) +{ + switch (value) + { + case ARENA_TEAM_2v2: return 0; + case ARENA_TEAM_3v3: return 1; + case ARENA_TEAM_5v5: return 2; + default: throw std::out_of_range("value"); + } +} +} 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 diff --git a/src/server/game/Chat/Chat.h b/src/server/game/Chat/Chat.h index 08c0d1886a..4b9f3ce215 100644 --- a/src/server/game/Chat/Chat.h +++ b/src/server/game/Chat/Chat.h @@ -18,6 +18,7 @@ #ifndef AZEROTHCORE_CHAT_H #define AZEROTHCORE_CHAT_H +#include "ChatCommand.h" #include "SharedDefines.h" #include "Errors.h" #include "WorldSession.h" @@ -33,26 +34,9 @@ class WorldObject; struct GameTele; -class ChatCommand -{ - typedef bool(*pHandler)(ChatHandler*, char const*); - -public: - ChatCommand(char const* name, uint32 securityLevel, bool allowConsole, pHandler handler, std::string help, std::vector<ChatCommand> childCommands = std::vector<ChatCommand>()) - : Name(ASSERT_NOTNULL(name)), SecurityLevel(securityLevel), AllowConsole(allowConsole), Handler(handler), Help(std::move(help)), ChildCommands(std::move(childCommands)) { } - - char const* Name; - uint32 SecurityLevel; - bool AllowConsole; - pHandler Handler; - std::string Help; - std::vector<ChatCommand> ChildCommands; -}; - -class ChatHandler +class AC_GAME_API ChatHandler { public: - WorldSession* GetSession() { return m_session; } explicit ChatHandler(WorldSession* session) : m_session(session), sentErrorMessage(false) {} virtual ~ChatHandler() { } @@ -68,24 +52,35 @@ public: // function with different implementation for chat/console virtual char const* GetAcoreString(uint32 entry) const; - virtual void SendSysMessage(char const* str); + virtual void SendSysMessage(std::string_view str, bool escapeCharacters = false); void SendSysMessage(uint32 entry); - void PSendSysMessage(char const* format, ...) ATTR_PRINTF(2, 3); - void PSendSysMessage(uint32 entry, ...); - std::string PGetParseString(uint32 entry, ...) const; - bool ParseCommands(const char* text); + template<typename... Args> + void PSendSysMessage(char const* fmt, Args&&... args) + { + SendSysMessage(Acore::StringFormat(fmt, std::forward<Args>(args)...).c_str()); + } - static std::vector<ChatCommand> const& getCommandTable(); + template<typename... Args> + void PSendSysMessage(uint32 entry, Args&&... args) + { + SendSysMessage(PGetParseString(entry, std::forward<Args>(args)...).c_str()); + } - bool isValidChatMessage(const char* msg); - void SendGlobalSysMessage(const char* str); + template<typename... Args> + std::string PGetParseString(uint32 entry, Args&&... args) const + { + return Acore::StringFormat(GetAcoreString(entry), std::forward<Args>(args)...); + } + + bool _ParseCommands(std::string_view text); + virtual bool ParseCommands(std::string_view text); - bool hasStringAbbr(const char* name, const char* part); + void SendGlobalSysMessage(const char* str); // function with different implementation for chat/console - virtual bool isAvailable(ChatCommand const& cmd) const; + virtual bool IsHumanReadable() const { return true; } virtual std::string GetNameLink() const { return GetNameLink(m_session->GetPlayer()); } virtual bool needReportToTarget(Player* chr) const; virtual LocaleConstant GetSessionDbcLocale() const; @@ -102,16 +97,12 @@ public: // Returns either the selected player or self if there is no selected player Player* getSelectedPlayerOrSelf(); - char* extractKeyFromLink(char* text, char const* linkType, char** something1 = nullptr); - char* extractKeyFromLink(char* text, char const* const* linkTypes, int* found_idx, char** something1 = nullptr); - - // if args have single value then it return in arg2 and arg1 == nullptr - void extractOptFirstArg(char* args, char** arg1, char** arg2); - char* extractQuotedArg(char* args); + char* extractKeyFromLink(char* text, char const* linkType, char** something1 = nullptr); + char* extractKeyFromLink(char* text, char const* const* linkTypes, int* found_idx, char** something1 = nullptr); + char* extractQuotedArg(char* args); uint32 extractSpellIdFromLink(char* text); ObjectGuid::LowType extractLowGuidFromLink(char* text, HighGuid& guidHigh); - GameTele const* extractGameTeleFromLink(char* text); bool GetPlayerGroupAndGUIDByName(const char* cname, Player*& player, Group*& group, ObjectGuid& guid, bool offline = false); std::string extractPlayerNameFromLink(char* text); // select by arg (name/link) or in-game selection online/offline player @@ -125,34 +116,31 @@ public: Creature* GetCreatureFromPlayerMapByDbGuid(ObjectGuid::LowType lowguid); bool HasSentErrorMessage() const { return sentErrorMessage; } void SetSentErrorMessage(bool val) { sentErrorMessage = val; } - static bool LoadCommandTable() { return load_command_table; } - static void SetLoadCommandTable(bool val) { load_command_table = val; } - bool ShowHelpForCommand(std::vector<ChatCommand> const& table, const char* cmd); + bool IsConsole() const { return (m_session == nullptr); } + Player* GetPlayer() const; + WorldSession* GetSession() { return m_session; } + bool IsAvailable(uint32 securityLevel) const; protected: explicit ChatHandler() : m_session(nullptr), sentErrorMessage(false) {} // for CLI subclass - static bool SetDataForCommandInTable(std::vector<ChatCommand>& table, const char* text, uint32 securityLevel, std::string const& help, std::string const& fullcommand); - bool ExecuteCommandInTable(std::vector<ChatCommand> const& table, const char* text, std::string const& fullcmd); - bool ShowHelpForSubCommands(std::vector<ChatCommand> const& table, char const* cmd, char const* subcmd); private: WorldSession* m_session; // != nullptr for chat command call and nullptr for CLI command // common global flag - static bool load_command_table; bool sentErrorMessage; }; -class CliHandler : public ChatHandler +class AC_GAME_API CliHandler : public ChatHandler { public: - typedef void Print(void*, char const*); - explicit CliHandler(void* callbackArg, Print* zprint) : m_callbackArg(callbackArg), m_print(zprint) {} + using Print = void(void*, std::string_view); + explicit CliHandler(void* callbackArg, Print* zprint) : m_callbackArg(callbackArg), m_print(zprint) { } // overwrite functions char const* GetAcoreString(uint32 entry) const override; - bool isAvailable(ChatCommand const& cmd) const override; - void SendSysMessage(const char* str) override; + void SendSysMessage(std::string_view, bool escapeCharacters) override; + bool ParseCommands(std::string_view str) override; std::string GetNameLink() const override; bool needReportToTarget(Player* chr) const override; LocaleConstant GetSessionDbcLocale() const override; diff --git a/src/server/game/Chat/ChatCommands/ChatCommand.cpp b/src/server/game/Chat/ChatCommands/ChatCommand.cpp new file mode 100644 index 0000000000..ea647c8651 --- /dev/null +++ b/src/server/game/Chat/ChatCommands/ChatCommand.cpp @@ -0,0 +1,534 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "ChatCommand.h" +#include "AccountMgr.h" +#include "Chat.h" +#include "DBCStores.h" +#include "DatabaseEnv.h" +#include "Log.h" +#include "Map.h" +#include "Player.h" +#include "ScriptMgr.h" +#include "StringFormat.h" +#include "Tokenize.h" +#include "WorldSession.h" + +using ChatSubCommandMap = std::map<std::string_view, Acore::Impl::ChatCommands::ChatCommandNode, StringCompareLessI_T>; + +void Acore::Impl::ChatCommands::ChatCommandNode::LoadFromBuilder(ChatCommandBuilder const& builder) +{ + if (std::holds_alternative<ChatCommandBuilder::InvokerEntry>(builder._data)) + { + ASSERT(!_invoker, "Duplicate blank sub-command."); + AcoreStrings help; + std::tie(_invoker, help, _permission) = *(std::get<ChatCommandBuilder::InvokerEntry>(builder._data)); + if (help) + _help.emplace<AcoreStrings>(help); + } + else + LoadCommandsIntoMap(this, _subCommands, std::get<ChatCommandBuilder::SubCommandEntry>(builder._data)); +} + +/*static*/ void Acore::Impl::ChatCommands::ChatCommandNode::LoadCommandsIntoMap(ChatCommandNode* blank, + std::map<std::string_view, Acore::Impl::ChatCommands::ChatCommandNode, StringCompareLessI_T>& map, Acore::ChatCommands::ChatCommandTable const& commands) +{ + for (ChatCommandBuilder const& builder : commands) + { + if (builder._name.empty()) + { + ASSERT(blank, "Empty name command at top level is not permitted."); + blank->LoadFromBuilder(builder); + } + else + { + std::vector<std::string_view> const tokens = Acore::Tokenize(builder._name, COMMAND_DELIMITER, false); + ASSERT(!tokens.empty(), "Invalid command name '" STRING_VIEW_FMT "'.", STRING_VIEW_FMT_ARG(builder._name)); + ChatSubCommandMap* subMap = ↦ + for (size_t i = 0, n = (tokens.size() - 1); i < n; ++i) + subMap = &((*subMap)[tokens[i]]._subCommands); + ((*subMap)[tokens.back()]).LoadFromBuilder(builder); + } + } +} + +static ChatSubCommandMap COMMAND_MAP; +/*static*/ ChatSubCommandMap const& Acore::Impl::ChatCommands::ChatCommandNode::GetTopLevelMap() +{ + if (COMMAND_MAP.empty()) + LoadCommandMap(); + + return COMMAND_MAP; +} +/*static*/ void Acore::Impl::ChatCommands::ChatCommandNode::InvalidateCommandMap() +{ + COMMAND_MAP.clear(); +} + +/*static*/ void Acore::Impl::ChatCommands::ChatCommandNode::LoadCommandMap() +{ + InvalidateCommandMap(); + LoadCommandsIntoMap(nullptr, COMMAND_MAP, sScriptMgr->GetChatCommands()); + + if (PreparedQueryResult result = WorldDatabase.Query(WorldDatabase.GetPreparedStatement(WORLD_SEL_COMMANDS))) + { + do + { + Field* fields = result->Fetch(); + std::string_view const name = fields[0].GetStringView(); + std::string_view const help = fields[2].GetStringView(); + uint32 const secLevel = fields[1].GetUInt8(); + + ChatCommandNode* cmd = nullptr; + ChatSubCommandMap* map = &COMMAND_MAP; + + for (std::string_view key : Acore::Tokenize(name, COMMAND_DELIMITER, false)) + { + auto it = map->find(key); + if (it != map->end()) + { + cmd = &it->second; + map = &cmd->_subCommands; + } + else + { + LOG_ERROR("sql.sql", "Table `command` contains data for non-existant command '" STRING_VIEW_FMT "'. Skipped.", STRING_VIEW_FMT_ARG(name)); + cmd = nullptr; + break; + } + } + + if (!cmd) + continue; + + if (cmd->_invoker && (cmd->_permission.RequiredLevel != secLevel)) + { + LOG_WARN("sql.sql", "Table `command` has permission %u for '" STRING_VIEW_FMT "' which does not match the core (%u). Overriding.", + secLevel, STRING_VIEW_FMT_ARG(name), cmd->_permission.RequiredLevel); + + cmd->_permission.RequiredLevel = secLevel; + } + + if (std::holds_alternative<std::string>(cmd->_help)) + LOG_ERROR("sql.sql", "Table `command` contains duplicate data for command '" STRING_VIEW_FMT "'. Skipped.", STRING_VIEW_FMT_ARG(name)); + + if (std::holds_alternative<std::monostate>(cmd->_help)) + cmd->_help.emplace<std::string>(help); + else + LOG_ERROR("sql.sql", "Table `command` contains legacy help text for command '" STRING_VIEW_FMT "', which uses `trinity_string`. Skipped.", STRING_VIEW_FMT_ARG(name)); + } while (result->NextRow()); + } + + for (auto& [name, cmd] : COMMAND_MAP) + cmd.ResolveNames(std::string(name)); +} + +void Acore::Impl::ChatCommands::ChatCommandNode::ResolveNames(std::string name) +{ + if (_invoker && std::holds_alternative<std::monostate>(_help)) + LOG_WARN("sql.sql", "Table `command` is missing help text for command '" STRING_VIEW_FMT "'.", STRING_VIEW_FMT_ARG(name)); + + _name = name; + + for (auto& [subToken, cmd] : _subCommands) + { + std::string subName(name); + subName.push_back(COMMAND_DELIMITER); + subName.append(subToken); + cmd.ResolveNames(subName); + } +} + +static void LogCommandUsage(WorldSession const& session, std::string_view cmdStr) +{ + if (AccountMgr::IsPlayerAccount(session.GetSecurity())) + return; + + Player* player = session.GetPlayer(); + ObjectGuid targetGuid = player->GetTarget(); + uint32 areaId = player->GetAreaId(); + uint32 zoneId = player->GetZoneId(); + std::string areaName = "Unknown"; + std::string zoneName = "Unknown"; + LocaleConstant locale = sWorld->GetDefaultDbcLocale(); + + if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(areaId)) + { + areaName = area->area_name[locale]; + } + + if (AreaTableEntry const* zone = sAreaTableStore.LookupEntry(zoneId)) + { + zoneName = zone->area_name[locale]; + } + + std::string logMessage = Acore::StringFormatFmt("Command: {} [Player: {} ({}) (Account: {}) X: {} Y: {} Z: {} Map: {} ({}) Area: {} ({}) Zone: {} ({}) Selected: {} ({})]", + cmdStr, player->GetName(), player->GetGUID().ToString(), + session.GetAccountId(), + player->GetPositionX(), player->GetPositionY(), player->GetPositionZ(), player->GetMapId(), + player->FindMap() ? player->FindMap()->GetMapName() : "Unknown", + areaId, areaName, zoneId, zoneName, + (player->GetSelectedUnit()) ? player->GetSelectedUnit()->GetName() : "", + targetGuid.ToString()); + + LOG_GM(session.GetAccountId(), logMessage); +} + +void Acore::Impl::ChatCommands::ChatCommandNode::SendCommandHelp(ChatHandler& handler) const +{ + bool const hasInvoker = IsInvokerVisible(handler); + if (hasInvoker) + { + if (std::holds_alternative<AcoreStrings>(_help)) + handler.SendSysMessage(std::get<AcoreStrings>(_help)); + else if (std::holds_alternative<std::string>(_help)) + handler.SendSysMessage(std::get<std::string>(_help)); + else + { + handler.PSendSysMessage(LANG_CMD_HELP_GENERIC, STRING_VIEW_FMT_ARG(_name)); + handler.PSendSysMessage(LANG_CMD_NO_HELP_AVAILABLE, STRING_VIEW_FMT_ARG(_name)); + } + } + + bool header = false; + + for (auto it = _subCommands.begin(); it != _subCommands.end(); ++it) + { + bool const subCommandHasSubCommand = it->second.HasVisibleSubCommands(handler); + + if (!subCommandHasSubCommand && !it->second.IsInvokerVisible(handler)) + { + continue; + } + + if (!header) + { + if (!hasInvoker) + { + handler.PSendSysMessage(LANG_CMD_HELP_GENERIC, STRING_VIEW_FMT_ARG(_name)); + } + + handler.SendSysMessage(LANG_SUBCMDS_LIST); + header = true; + } + + handler.PSendSysMessage(subCommandHasSubCommand ? LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS : LANG_SUBCMDS_LIST_ENTRY, STRING_VIEW_FMT_ARG(it->second._name)); + } +} + +namespace Acore::Impl::ChatCommands +{ + struct FilteredCommandListIterator + { + public: + FilteredCommandListIterator(ChatSubCommandMap const& map, ChatHandler const& handler, std::string_view token) + : _handler{ handler }, _token{ token }, _it{ map.lower_bound(token) }, _end{ map.end() } + { + _skip(); + } + + decltype(auto) operator*() const { return _it.operator*(); } + decltype(auto) operator->() const { return _it.operator->(); } + FilteredCommandListIterator& operator++() + { + ++_it; + _skip(); + return *this; + } + explicit operator bool() const { return (_it != _end); } + bool operator!() const { return !static_cast<bool>(*this); } + + private: + void _skip() + { + if ((_it != _end) && !StringStartsWithI(_it->first, _token)) + _it = _end; + while ((_it != _end) && !_it->second.IsVisible(_handler)) + { + ++_it; + if ((_it != _end) && !StringStartsWithI(_it->first, _token)) + _it = _end; + } + } + + ChatHandler const& _handler; + std::string_view const _token; + ChatSubCommandMap::const_iterator _it, _end; + + }; +} + +/*static*/ bool Acore::Impl::ChatCommands::ChatCommandNode::TryExecuteCommand(ChatHandler& handler, std::string_view cmdStr) +{ + ChatCommandNode const* cmd = nullptr; + ChatSubCommandMap const* map = &GetTopLevelMap(); + + while (!cmdStr.empty() && (cmdStr.front() == COMMAND_DELIMITER)) + cmdStr.remove_prefix(1); + + while (!cmdStr.empty() && (cmdStr.back() == COMMAND_DELIMITER)) + cmdStr.remove_suffix(1); + + std::string_view oldTail = cmdStr; + while (!oldTail.empty()) + { + /* oldTail = token DELIMITER newTail */ + auto [token, newTail] = tokenize(oldTail); + ASSERT(!token.empty()); + + FilteredCommandListIterator it1(*map, handler, token); + if (!it1) + break; /* no matching subcommands found */ + + if (!StringEqualI(it1->first, token)) + { /* ok, so it1 points at a partially matching subcommand - let's see if there are others */ + auto it2 = it1; + ++it2; + + if (it2) + { /* there are multiple matching subcommands - print possibilities and return */ + if (cmd) + handler.PSendSysMessage(LANG_SUBCMD_AMBIGUOUS, STRING_VIEW_FMT_ARG(cmd->_name), COMMAND_DELIMITER, STRING_VIEW_FMT_ARG(token)); + else + handler.PSendSysMessage(LANG_CMD_AMBIGUOUS, STRING_VIEW_FMT_ARG(token)); + + handler.PSendSysMessage(it1->second.HasVisibleSubCommands(handler) ? LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS : LANG_SUBCMDS_LIST_ENTRY, STRING_VIEW_FMT_ARG(it1->first)); + do + { + handler.PSendSysMessage(it2->second.HasVisibleSubCommands(handler) ? LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS : LANG_SUBCMDS_LIST_ENTRY, STRING_VIEW_FMT_ARG(it2->first)); + } while (++it2); + + return true; + } + } + + /* now we matched exactly one subcommand, and it1 points to it; go down the rabbit hole */ + cmd = &it1->second; + map = &cmd->_subCommands; + + oldTail = newTail; + } + + if (cmd) + { /* if we matched a command at some point, invoke it */ + handler.SetSentErrorMessage(false); + if (cmd->IsInvokerVisible(handler) && cmd->_invoker(&handler, oldTail)) + { /* invocation succeeded, log this */ + if (!handler.IsConsole()) + LogCommandUsage(*handler.GetSession(), cmdStr); + } + else if (!handler.HasSentErrorMessage()) + { /* invocation failed, we should show usage */ + cmd->SendCommandHelp(handler); + handler.SetSentErrorMessage(true); + } + return true; + } + + return false; +} + +/*static*/ void Acore::Impl::ChatCommands::ChatCommandNode::SendCommandHelpFor(ChatHandler& handler, std::string_view cmdStr) +{ + ChatCommandNode const* cmd = nullptr; + ChatSubCommandMap const* map = &GetTopLevelMap(); + + for (std::string_view token : Acore::Tokenize(cmdStr, COMMAND_DELIMITER, false)) + { + FilteredCommandListIterator it1(*map, handler, token); + if (!it1) + { /* no matching subcommands found */ + if (cmd) + { + cmd->SendCommandHelp(handler); + handler.PSendSysMessage(LANG_SUBCMD_INVALID, STRING_VIEW_FMT_ARG(cmd->_name), COMMAND_DELIMITER, STRING_VIEW_FMT_ARG(token)); + } + else + handler.PSendSysMessage(LANG_CMD_INVALID, STRING_VIEW_FMT_ARG(token)); + return; + } + + if (!StringEqualI(it1->first, token)) + { /* ok, so it1 points at a partially matching subcommand - let's see if there are others */ + auto it2 = it1; + ++it2; + + if (it2) + { /* there are multiple matching subcommands - print possibilities and return */ + if (cmd) + handler.PSendSysMessage(LANG_SUBCMD_AMBIGUOUS, STRING_VIEW_FMT_ARG(cmd->_name), COMMAND_DELIMITER, STRING_VIEW_FMT_ARG(token)); + else + handler.PSendSysMessage(LANG_CMD_AMBIGUOUS, STRING_VIEW_FMT_ARG(token)); + + handler.PSendSysMessage(it1->second.HasVisibleSubCommands(handler) ? LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS : LANG_SUBCMDS_LIST_ENTRY, STRING_VIEW_FMT_ARG(it1->first)); + do + { + handler.PSendSysMessage(it2->second.HasVisibleSubCommands(handler) ? LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS : LANG_SUBCMDS_LIST_ENTRY, STRING_VIEW_FMT_ARG(it2->first)); + } while (++it2); + + return; + } + } + + cmd = &it1->second; + map = &cmd->_subCommands; + } + + if (cmd) + cmd->SendCommandHelp(handler); + else if (cmdStr.empty()) + { + FilteredCommandListIterator it(*map, handler, ""); + if (!it) + return; + handler.SendSysMessage(LANG_AVAILABLE_CMDS); + do + { + handler.PSendSysMessage(it->second.HasVisibleSubCommands(handler) ? LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS : LANG_SUBCMDS_LIST_ENTRY, STRING_VIEW_FMT_ARG(it->second._name)); + } while (++it); + } + else + handler.PSendSysMessage(LANG_CMD_INVALID, STRING_VIEW_FMT_ARG(cmdStr)); +} + +/*static*/ std::vector<std::string> Acore::Impl::ChatCommands::ChatCommandNode::GetAutoCompletionsFor(ChatHandler const& handler, std::string_view cmdStr) +{ + std::string path; + ChatCommandNode const* cmd = nullptr; + ChatSubCommandMap const* map = &GetTopLevelMap(); + + while (!cmdStr.empty() && (cmdStr.front() == COMMAND_DELIMITER)) + cmdStr.remove_prefix(1); + + while (!cmdStr.empty() && (cmdStr.back() == COMMAND_DELIMITER)) + cmdStr.remove_suffix(1); + + std::string_view oldTail = cmdStr; + while (!oldTail.empty()) + { + /* oldTail = token DELIMITER newTail */ + auto [token, newTail] = tokenize(oldTail); + ASSERT(!token.empty()); + FilteredCommandListIterator it1(*map, handler, token); + if (!it1) + break; /* no matching subcommands found */ + + if (!StringEqualI(it1->first, token)) + { /* ok, so it1 points at a partially matching subcommand - let's see if there are others */ + auto it2 = it1; + ++it2; + + if (it2) + { /* there are multiple matching subcommands - terminate here and show possibilities */ + std::vector<std::string> vec; + auto possibility = ([prefix = std::string_view(path), suffix = std::string_view(newTail)](std::string_view match) + { + if (prefix.empty()) + { + return Acore::StringFormat(STRING_VIEW_FMT "%c" STRING_VIEW_FMT, + STRING_VIEW_FMT_ARG(match), COMMAND_DELIMITER, STRING_VIEW_FMT_ARG(suffix)); + } + else + { + return Acore::StringFormat(STRING_VIEW_FMT "%c" STRING_VIEW_FMT "%c" STRING_VIEW_FMT, + STRING_VIEW_FMT_ARG(prefix), COMMAND_DELIMITER, STRING_VIEW_FMT_ARG(match), COMMAND_DELIMITER, STRING_VIEW_FMT_ARG(suffix)); + } + }); + + vec.emplace_back(possibility(it1->first)); + + do vec.emplace_back(possibility(it2->first)); + while (++it2); + + return vec; + } + } + + /* now we matched exactly one subcommand, and it1 points to it; go down the rabbit hole */ + if (path.empty()) + path.assign(it1->first); + else + { + path = Acore::StringFormat(STRING_VIEW_FMT "%c" STRING_VIEW_FMT, + STRING_VIEW_FMT_ARG(path), COMMAND_DELIMITER, STRING_VIEW_FMT_ARG(it1->first)); + } + cmd = &it1->second; + map = &cmd->_subCommands; + + oldTail = newTail; + } + + if (!oldTail.empty()) + { /* there is some trailing text, leave it as is */ + if (cmd) + { /* if we matched a command at some point, auto-complete it */ + return { + Acore::StringFormat(STRING_VIEW_FMT "%c" STRING_VIEW_FMT, + STRING_VIEW_FMT_ARG(path), COMMAND_DELIMITER, STRING_VIEW_FMT_ARG(oldTail)) + }; + } + else + return {}; + } + else + { /* offer all subcommands */ + auto possibility = ([prefix = std::string_view(path)](std::string_view match) + { + if (prefix.empty()) + return std::string(match); + else + { + return Acore::StringFormat(STRING_VIEW_FMT "%c" STRING_VIEW_FMT, + STRING_VIEW_FMT_ARG(prefix), COMMAND_DELIMITER, STRING_VIEW_FMT_ARG(match)); + } + }); + + std::vector<std::string> vec; + for (FilteredCommandListIterator it(*map, handler, ""); it; ++it) + vec.emplace_back(possibility(it->first)); + return vec; + } +} + +bool Acore::Impl::ChatCommands::ChatCommandNode::IsInvokerVisible(ChatHandler const& who) const +{ + if (!_invoker) + return false; + + if (who.IsConsole() && (_permission.AllowConsole == Acore::ChatCommands::Console::No)) + return false; + + if (who.IsConsole() && (_permission.AllowConsole == Acore::ChatCommands::Console::Yes)) + return true; + + return !who.IsConsole() && who.IsAvailable(_permission.RequiredLevel); +} + +bool Acore::Impl::ChatCommands::ChatCommandNode::HasVisibleSubCommands(ChatHandler const& who) const +{ + for (auto it = _subCommands.begin(); it != _subCommands.end(); ++it) + if (it->second.IsVisible(who)) + return true; + + return false; +} + +void Acore::ChatCommands::LoadCommandMap() { Acore::Impl::ChatCommands::ChatCommandNode::LoadCommandMap(); } +void Acore::ChatCommands::InvalidateCommandMap() { Acore::Impl::ChatCommands::ChatCommandNode::InvalidateCommandMap(); } +bool Acore::ChatCommands::TryExecuteCommand(ChatHandler& handler, std::string_view cmd) { return Acore::Impl::ChatCommands::ChatCommandNode::TryExecuteCommand(handler, cmd); } +void Acore::ChatCommands::SendCommandHelpFor(ChatHandler& handler, std::string_view cmd) { Acore::Impl::ChatCommands::ChatCommandNode::SendCommandHelpFor(handler, cmd); } +std::vector<std::string> Acore::ChatCommands::GetAutoCompletionsFor(ChatHandler const& handler, std::string_view cmd) { return Acore::Impl::ChatCommands::ChatCommandNode::GetAutoCompletionsFor(handler, cmd); } diff --git a/src/server/game/Chat/ChatCommands/ChatCommand.h b/src/server/game/Chat/ChatCommands/ChatCommand.h new file mode 100644 index 0000000000..7caf208817 --- /dev/null +++ b/src/server/game/Chat/ChatCommands/ChatCommand.h @@ -0,0 +1,280 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _CHATCOMMAND_H +#define _CHATCOMMAND_H + +#include "advstd.h" +#include "ChatCommandArgs.h" +#include "ChatCommandTags.h" +#include "Define.h" +#include "Errors.h" +#include "Language.h" +#include "ObjectGuid.h" +#include "Optional.h" +#include "StringFormat.h" +#include "Util.h" +#include <cstddef> +#include <map> +#include <utility> +#include <tuple> +#include <type_traits> +#include <variant> +#include <vector> + +class ChatHandler; + +namespace Acore::ChatCommands +{ + enum class Console : bool + { + No = false, + Yes = true + }; + + struct ChatCommandBuilder; + using ChatCommandTable = std::vector<ChatCommandBuilder>; +} + +namespace Acore::Impl::ChatCommands +{ + // forward declaration + // ConsumeFromOffset contains the bounds check for offset, then hands off to MultiConsumer + // the call stack is MultiConsumer -> ConsumeFromOffset -> MultiConsumer -> ConsumeFromOffset etc + // MultiConsumer goes into ArgInfo for parsing on each iteration + template <typename Tuple, size_t offset> + ChatCommandResult ConsumeFromOffset(Tuple&, ChatHandler const* handler, std::string_view args); + + template <typename Tuple, typename NextType, size_t offset> + struct MultiConsumer + { + static ChatCommandResult TryConsumeTo(Tuple& tuple, ChatHandler const* handler, std::string_view args) + { + ChatCommandResult next = ArgInfo<NextType>::TryConsume(std::get<offset>(tuple), handler, args); + if (next) + return ConsumeFromOffset<Tuple, offset + 1>(tuple, handler, *next); + else + return next; + } + }; + + template <typename Tuple, typename NestedNextType, size_t offset> + struct MultiConsumer<Tuple, Optional<NestedNextType>, offset> + { + static ChatCommandResult TryConsumeTo(Tuple& tuple, ChatHandler const* handler, std::string_view args) + { + // try with the argument + auto& myArg = std::get<offset>(tuple); + myArg.emplace(); + + ChatCommandResult result1 = ArgInfo<NestedNextType>::TryConsume(myArg.value(), handler, args); + if (result1) + if ((result1 = ConsumeFromOffset<Tuple, offset + 1>(tuple, handler, *result1))) + return result1; + // try again omitting the argument + myArg = std::nullopt; + ChatCommandResult result2 = ConsumeFromOffset<Tuple, offset + 1>(tuple, handler, args); + if (result2) + return result2; + if (result1.HasErrorMessage() && result2.HasErrorMessage()) + { + return Acore::StringFormat("%s \"%s\"\n%s \"%s\"", + GetAcoreString(handler, LANG_CMDPARSER_EITHER), result2.GetErrorMessage().c_str(), + GetAcoreString(handler, LANG_CMDPARSER_OR), result1.GetErrorMessage().c_str()); + } + else if (result1.HasErrorMessage()) + return result1; + else + return result2; + } + }; + + template <typename Tuple, size_t offset> + ChatCommandResult ConsumeFromOffset([[maybe_unused]] Tuple& tuple, [[maybe_unused]] ChatHandler const* handler, std::string_view args) + { + if constexpr (offset < std::tuple_size_v<Tuple>) + return MultiConsumer<Tuple, std::tuple_element_t<offset, Tuple>, offset>::TryConsumeTo(tuple, handler, args); + else if (!args.empty()) /* the entire string must be consumed */ + return std::nullopt; + else + return args; + } + + template <typename T> struct HandlerToTuple { static_assert(Acore::dependant_false_v<T>, "Invalid command handler signature"); }; + template <typename... Ts> struct HandlerToTuple<bool(ChatHandler*, Ts...)> { using type = std::tuple<ChatHandler*, advstd::remove_cvref_t<Ts>...>; }; + template <typename T> using TupleType = typename HandlerToTuple<T>::type; + + struct CommandInvoker + { + CommandInvoker() : _wrapper(nullptr), _handler(nullptr) {} + template <typename TypedHandler> + CommandInvoker(TypedHandler& handler) + { + _wrapper = [](void* handler, ChatHandler* chatHandler, std::string_view argsStr) + { + using Tuple = TupleType<TypedHandler>; + + Tuple arguments; + std::get<0>(arguments) = chatHandler; + ChatCommandResult result = ConsumeFromOffset<Tuple, 1>(arguments, chatHandler, argsStr); + if (result) + return std::apply(reinterpret_cast<TypedHandler*>(handler), std::move(arguments)); + else + { + if (result.HasErrorMessage()) + SendErrorMessageToHandler(chatHandler, result.GetErrorMessage()); + return false; + } + }; + _handler = reinterpret_cast<void*>(handler); + } + CommandInvoker(bool(&handler)(ChatHandler*, char const*)) + { + _wrapper = [](void* handler, ChatHandler* chatHandler, std::string_view argsStr) + { + // make a copy of the argument string + // legacy handlers can destroy input strings with strtok + std::string argsStrCopy(argsStr); + return reinterpret_cast<bool(*)(ChatHandler*, char const*)>(handler)(chatHandler, argsStrCopy.c_str()); + }; + _handler = reinterpret_cast<void*>(handler); + } + + explicit operator bool() const { return (_wrapper != nullptr); } + bool operator()(ChatHandler* chatHandler, std::string_view args) const + { + ASSERT(_wrapper && _handler); + return _wrapper(_handler, chatHandler, args); + } + + private: + using wrapper_func = bool(void*, ChatHandler*, std::string_view); + wrapper_func* _wrapper; + void* _handler; + }; + + struct CommandPermissions + { + CommandPermissions() : RequiredLevel{}, AllowConsole{} { } + CommandPermissions(uint32 securityLevel, Acore::ChatCommands::Console console) : RequiredLevel{ securityLevel }, AllowConsole{ console } {} + uint32 RequiredLevel; + Acore::ChatCommands::Console AllowConsole; + }; + + class ChatCommandNode + { + friend struct FilteredCommandListIterator; + using ChatCommandBuilder = Acore::ChatCommands::ChatCommandBuilder; + + public: + static void LoadCommandMap(); + static void InvalidateCommandMap(); + static bool TryExecuteCommand(ChatHandler& handler, std::string_view cmd); + static void SendCommandHelpFor(ChatHandler& handler, std::string_view cmd); + static std::vector<std::string> GetAutoCompletionsFor(ChatHandler const& handler, std::string_view cmd); + + ChatCommandNode() : _name{}, _invoker {}, _permission{}, _help{}, _subCommands{} { } + + private: + static std::map<std::string_view, ChatCommandNode, StringCompareLessI_T> const& GetTopLevelMap(); + static void LoadCommandsIntoMap(ChatCommandNode* blank, std::map<std::string_view, Acore::Impl::ChatCommands::ChatCommandNode, StringCompareLessI_T>& map, Acore::ChatCommands::ChatCommandTable const& commands); + + void LoadFromBuilder(ChatCommandBuilder const& builder); + ChatCommandNode(ChatCommandNode&& other) = default; + + void ResolveNames(std::string name); + void SendCommandHelp(ChatHandler& handler) const; + + bool IsVisible(ChatHandler const& who) const { return (IsInvokerVisible(who) || HasVisibleSubCommands(who)); } + bool IsInvokerVisible(ChatHandler const& who) const; + bool HasVisibleSubCommands(ChatHandler const& who) const; + + std::string _name; + CommandInvoker _invoker; + CommandPermissions _permission; + std::variant<std::monostate, AcoreStrings, std::string> _help; + std::map<std::string_view, ChatCommandNode, StringCompareLessI_T> _subCommands; + }; +} + +namespace Acore::ChatCommands +{ + struct ChatCommandBuilder + { + friend class Acore::Impl::ChatCommands::ChatCommandNode; + + struct InvokerEntry + { + template <typename T> + InvokerEntry(T& handler, AcoreStrings help, uint32 securityLevel, Acore::ChatCommands::Console allowConsole) + : _invoker{ handler }, _help{ help }, _permissions{ securityLevel, allowConsole } { } + + InvokerEntry(InvokerEntry const&) = default; + InvokerEntry(InvokerEntry&&) = default; + + Acore::Impl::ChatCommands::CommandInvoker _invoker; + AcoreStrings _help; + Acore::Impl::ChatCommands::CommandPermissions _permissions; + + auto operator*() const { return std::tie(_invoker, _help, _permissions); } + }; + + using SubCommandEntry = std::reference_wrapper<std::vector<ChatCommandBuilder> const>; + + ChatCommandBuilder(ChatCommandBuilder&&) = default; + ChatCommandBuilder(ChatCommandBuilder const&) = default; + + template <typename TypedHandler> + ChatCommandBuilder(char const* name, TypedHandler& handler, AcoreStrings help, uint32 securityLevel, Acore::ChatCommands::Console allowConsole) + : _name{ ASSERT_NOTNULL(name) }, _data{ std::in_place_type<InvokerEntry>, handler, help, securityLevel, allowConsole } { } + + template <typename TypedHandler> + ChatCommandBuilder(char const* name, TypedHandler& handler, uint32 securityLevel, Acore::ChatCommands::Console allowConsole) + : ChatCommandBuilder(name, handler, AcoreStrings(), securityLevel, allowConsole) { } + + ChatCommandBuilder(char const* name, std::vector<ChatCommandBuilder> const& subCommands) + : _name{ ASSERT_NOTNULL(name) }, _data{ std::in_place_type<SubCommandEntry>, subCommands } { } + + [[deprecated("char const* parameters to command handlers are deprecated; convert this to a typed argument handler instead")]] + ChatCommandBuilder(char const* name, bool(&handler)(ChatHandler*, char const*), uint32 securityLevel, Acore::ChatCommands::Console allowConsole) + : ChatCommandBuilder(name, handler, AcoreStrings(), securityLevel, allowConsole) { } + + template <typename TypedHandler> + [[deprecated("you are using the old-style command format; convert this to the new format ({ name, handler (not a pointer!), permission, Console::(Yes/No) })")]] + ChatCommandBuilder(char const* name, uint32 securityLevel, bool console, TypedHandler* handler, char const*) + : ChatCommandBuilder(name, *handler, AcoreStrings(), securityLevel, static_cast<Acore::ChatCommands::Console>(console)) { } + + [[deprecated("you are using the old-style command format; convert this to the new format ({ name, subCommands })")]] + ChatCommandBuilder(char const* name, uint32, bool, std::nullptr_t, char const*, std::vector <ChatCommandBuilder> const& sub) + : ChatCommandBuilder(name, sub) { } + + private: + std::string_view _name; + std::variant<InvokerEntry, SubCommandEntry> _data; + }; + + AC_GAME_API void LoadCommandMap(); + AC_GAME_API void InvalidateCommandMap(); + AC_GAME_API bool TryExecuteCommand(ChatHandler& handler, std::string_view cmd); + AC_GAME_API void SendCommandHelpFor(ChatHandler& handler, std::string_view cmd); + AC_GAME_API std::vector<std::string> GetAutoCompletionsFor(ChatHandler const& handler, std::string_view cmd); +} + +// backwards compatibility with old patches +using ChatCommand [[deprecated("std::vector<ChatCommand> should be ChatCommandTable! (using namespace Acore::ChatCommands)")]] = Acore::ChatCommands::ChatCommandBuilder; + +#endif diff --git a/src/server/game/Chat/ChatCommands/ChatCommandArgs.cpp b/src/server/game/Chat/ChatCommands/ChatCommandArgs.cpp new file mode 100644 index 0000000000..1aae781a38 --- /dev/null +++ b/src/server/game/Chat/ChatCommands/ChatCommandArgs.cpp @@ -0,0 +1,118 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "ChatCommandArgs.h" +#include "AchievementMgr.h" +#include "ChatCommand.h" +#include "ObjectMgr.h" +#include "SpellMgr.h" +#include "Util.h" + +using namespace Acore::ChatCommands; +using ChatCommandResult = Acore::Impl::ChatCommands::ChatCommandResult; + +struct AchievementVisitor +{ + using value_type = AchievementEntry const*; + value_type operator()(Hyperlink<achievement> achData) const { return achData->Achievement; } + value_type operator()(uint32 achId) const { return sAchievementMgr->GetAchievement(achId); } +}; + +ChatCommandResult Acore::Impl::ChatCommands::ArgInfo<AchievementEntry const*>::TryConsume(AchievementEntry const*& data, ChatHandler const* handler, std::string_view args) +{ + Variant<Hyperlink<achievement>, uint32> val; + ChatCommandResult result = ArgInfo<decltype(val)>::TryConsume(val, handler, args); + + if (!result || (data = val.visit(AchievementVisitor()))) + return result; + + if (uint32* id = std::get_if<uint32>(&val)) + return FormatAcoreString(handler, LANG_CMDPARSER_ACHIEVEMENT_NO_EXIST, *id); + + return std::nullopt; +} + +struct GameTeleVisitor +{ + using value_type = GameTele const*; + value_type operator()(Hyperlink<tele> tele) const { return sObjectMgr->GetGameTele(tele); } + value_type operator()(std::string_view tele) const { return sObjectMgr->GetGameTele(tele); } +}; + +ChatCommandResult Acore::Impl::ChatCommands::ArgInfo<GameTele const*>::TryConsume(GameTele const*& data, ChatHandler const* handler, std::string_view args) +{ + Variant<Hyperlink<tele>, std::string_view> val; + ChatCommandResult result = ArgInfo<decltype(val)>::TryConsume(val, handler, args); + + if (!result || (data = val.visit(GameTeleVisitor()))) + return result; + + if (val.holds_alternative<Hyperlink<tele>>()) + return FormatAcoreString(handler, LANG_CMDPARSER_GAME_TELE_ID_NO_EXIST, static_cast<uint32>(std::get<Hyperlink<tele>>(val))); + else + return FormatAcoreString(handler, LANG_CMDPARSER_GAME_TELE_NO_EXIST, STRING_VIEW_FMT_ARG(std::get<std::string_view>(val))); +} + +struct ItemTemplateVisitor +{ + using value_type = ItemTemplate const*; + value_type operator()(Hyperlink<item> item) const { return item->Item; } + value_type operator()(uint32 item) { return sObjectMgr->GetItemTemplate(item); } +}; + +ChatCommandResult Acore::Impl::ChatCommands::ArgInfo<ItemTemplate const*>::TryConsume(ItemTemplate const*& data, ChatHandler const* handler, std::string_view args) +{ + Variant<Hyperlink<item>, uint32> val; + ChatCommandResult result = ArgInfo<decltype(val)>::TryConsume(val, handler, args); + + if (!result || (data = val.visit(ItemTemplateVisitor()))) + return result; + + if (uint32* id = std::get_if<uint32>(&val)) + return FormatAcoreString(handler, LANG_CMDPARSER_ITEM_NO_EXIST, *id); + + return std::nullopt; +} + +struct SpellInfoVisitor +{ + using value_type = SpellInfo const*; + value_type operator()(Hyperlink<enchant> enchant) const { return enchant; }; + value_type operator()(Hyperlink<glyph> glyph) const { return operator()(glyph->Glyph->SpellId); }; + value_type operator()(Hyperlink<spell> spell) const { return *spell; } + value_type operator()(Hyperlink<talent> talent) const + { + return operator()(talent->Talent->RankID[talent->Rank - 1]); + }; + value_type operator()(Hyperlink<trade> trade) const { return trade->Spell; }; + + value_type operator()(uint32 spellId) const { return sSpellMgr->GetSpellInfo(spellId); } +}; + +ChatCommandResult Acore::Impl::ChatCommands::ArgInfo<SpellInfo const*>::TryConsume(SpellInfo const*& data, ChatHandler const* handler, std::string_view args) +{ + Variant<Hyperlink<enchant>, Hyperlink<glyph>, Hyperlink<spell>, Hyperlink<talent>, Hyperlink<trade>, uint32> val; + ChatCommandResult result = ArgInfo<decltype(val)>::TryConsume(val, handler, args); + + if (!result || (data = val.visit(SpellInfoVisitor()))) + return result; + + if (uint32* id = std::get_if<uint32>(&val)) + return FormatAcoreString(handler, LANG_CMDPARSER_SPELL_NO_EXIST, *id); + + return std::nullopt; +} diff --git a/src/server/game/Chat/ChatCommands/ChatCommandArgs.h b/src/server/game/Chat/ChatCommands/ChatCommandArgs.h new file mode 100644 index 0000000000..ec83b73753 --- /dev/null +++ b/src/server/game/Chat/ChatCommands/ChatCommandArgs.h @@ -0,0 +1,324 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _CHATCOMMANDARGS_H +#define _CHATCOMMANDARGS_H + +#include "ChatCommandHelpers.h" +#include "ChatCommandTags.h" +#include "SmartEnum.h" +#include "StringConvert.h" +#include "StringFormat.h" +#include "Util.h" +#include <charconv> +#include <map> +#include <string> +#include <string_view> + +struct GameTele; + +namespace Acore::Impl::ChatCommands +{ + + /************************** ARGUMENT HANDLERS *******************************************\ + |* Define how to extract contents of a certain requested type from a string *| + |* Must implement the following: *| + |* - TryConsume: T&, ChatHandler const*, std::string_view -> ChatCommandResult *| + |* - on match, returns tail of the provided argument string (as std::string_view) *| + |* - on specific error, returns error message (as std::string&& or char const*) *| + |* - on generic error, returns std::nullopt (this will print command usage) *| + |* *| + |* - if a match is returned, T& should be initialized to the matched value *| + |* - otherwise, the state of T& is indeterminate and caller will not use it *| + |* *| + \****************************************************************************************/ + template <typename T, typename = void> + struct ArgInfo { static_assert(Acore::dependant_false_v<T>, "Invalid command parameter type - see ChatCommandArgs.h for possible types"); }; + + // catch-all for number types + template <typename T> + struct ArgInfo<T, std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T>>> + { + static ChatCommandResult TryConsume(T& val, ChatHandler const* handler, std::string_view args) + { + auto [token, tail] = tokenize(args); + if (token.empty()) + return std::nullopt; + + if (Optional<T> v = StringTo<T>(token, 0)) + val = *v; + else + return FormatAcoreString(handler, LANG_CMDPARSER_STRING_VALUE_INVALID, STRING_VIEW_FMT_ARG(token), GetTypeName<T>().c_str()); + + if constexpr (std::is_floating_point_v<T>) + { + if (!std::isfinite(val)) + return FormatAcoreString(handler, LANG_CMDPARSER_STRING_VALUE_INVALID, STRING_VIEW_FMT_ARG(token), GetTypeName<T>().c_str()); + } + + return tail; + } + }; + + // string_view + template <> + struct ArgInfo<std::string_view, void> + { + static ChatCommandResult TryConsume(std::string_view& val, ChatHandler const*, std::string_view args) + { + auto [token, next] = tokenize(args); + if (token.empty()) + return std::nullopt; + val = token; + return next; + } + }; + + // string + template <> + struct ArgInfo<std::string, void> + { + static ChatCommandResult TryConsume(std::string& val, ChatHandler const* handler, std::string_view args) + { + std::string_view view; + ChatCommandResult next = ArgInfo<std::string_view>::TryConsume(view, handler, args); + if (next) + val.assign(view); + return next; + } + }; + + // wstring + template <> + struct ArgInfo<std::wstring, void> + { + static ChatCommandResult TryConsume(std::wstring& val, ChatHandler const* handler, std::string_view args) + { + std::string_view utf8view; + ChatCommandResult next = ArgInfo<std::string_view>::TryConsume(utf8view, handler, args); + + if (next) + { + if (Utf8toWStr(utf8view, val)) + return next; + else + return GetAcoreString(handler, LANG_CMDPARSER_INVALID_UTF8); + } + else + return std::nullopt; + } + }; + + // enum + template <typename T> + struct ArgInfo<T, std::enable_if_t<std::is_enum_v<T>>> + { + using SearchMap = std::map<std::string_view, Optional<T>, StringCompareLessI_T>; + static SearchMap MakeSearchMap() + { + SearchMap map; + for (T val : EnumUtils::Iterate<T>()) + { + EnumText text = EnumUtils::ToString(val); + + std::string_view title(text.Title); + std::string_view constant(text.Constant); + + auto [constantIt, constantNew] = map.try_emplace(title, val); + if (!constantNew) + constantIt->second = std::nullopt; + + if (title != constant) + { + auto [titleIt, titleNew] = map.try_emplace(title, val); + if (!titleNew) + titleIt->second = std::nullopt; + } + } + return map; + } + + static inline SearchMap const _map = MakeSearchMap(); + + static T const* Match(std::string_view s) + { + auto it = _map.lower_bound(s); + if (it == _map.end() || !StringStartsWithI(it->first, s)) // not a match + return nullptr; + + if (!StringEqualI(it->first, s)) // we don't have an exact match - check if it is unique + { + auto it2 = it; + ++it2; + if ((it2 != _map.end()) && StringStartsWithI(it2->first, s)) // not unique + return nullptr; + } + + if (it->second) + return &*it->second; + else + return nullptr; + } + + static ChatCommandResult TryConsume(T& val, ChatHandler const* handler, std::string_view args) + { + std::string_view strVal; + ChatCommandResult next1 = ArgInfo<std::string_view>::TryConsume(strVal, handler, args); + if (next1) + { + if (T const* match = Match(strVal)) + { + val = *match; + return next1; + } + } + + // Value not found. Try to parse arg as underlying type and cast it to enum type + using U = std::underlying_type_t<T>; + U uVal = 0; + if (ChatCommandResult next2 = ArgInfo<U>::TryConsume(uVal, handler, args)) + { + if (EnumUtils::IsValid<T>(uVal)) + { + val = static_cast<T>(uVal); + return next2; + } + } + + if (next1) + return FormatAcoreString(handler, LANG_CMDPARSER_STRING_VALUE_INVALID, STRING_VIEW_FMT_ARG(strVal), GetTypeName<T>().c_str()); + else + return next1; + } + }; + + // a container tag + template <typename T> + struct ArgInfo<T, std::enable_if_t<std::is_base_of_v<ContainerTag, T>>> + { + static ChatCommandResult TryConsume(T& tag, ChatHandler const* handler, std::string_view args) + { + return tag.TryConsume(handler, args); + } + }; + + // non-empty vector + template <typename T> + struct ArgInfo<std::vector<T>, void> + { + static ChatCommandResult TryConsume(std::vector<T>& val, ChatHandler const* handler, std::string_view args) + { + val.clear(); + ChatCommandResult next = ArgInfo<T>::TryConsume(val.emplace_back(), handler, args); + + if (!next) + return next; + + while (ChatCommandResult next2 = ArgInfo<T>::TryConsume(val.emplace_back(), handler, *next)) + next = std::move(next2); + + val.pop_back(); + return next; + } + }; + + // fixed-size array + template <typename T, size_t N> + struct ArgInfo<std::array<T, N>, void> + { + static ChatCommandResult TryConsume(std::array<T, N>& val, ChatHandler const* handler, std::string_view args) + { + ChatCommandResult next = args; + for (T& t : val) + if (!(next = ArgInfo<T>::TryConsume(t, handler, *next))) + break; + return next; + } + }; + + // variant + template <typename... Ts> + struct ArgInfo<Acore::ChatCommands::Variant<Ts...>> + { + using V = std::variant<Ts...>; + static constexpr size_t N = std::variant_size_v<V>; + + template <size_t I> + static ChatCommandResult TryAtIndex([[maybe_unused]] Acore::ChatCommands::Variant<Ts...>& val, [[maybe_unused]] ChatHandler const* handler, [[maybe_unused]] std::string_view args) + { + if constexpr (I < N) + { + ChatCommandResult thisResult = ArgInfo<std::variant_alternative_t<I, V>>::TryConsume(val.template emplace<I>(), handler, args); + if (thisResult) + return thisResult; + else + { + ChatCommandResult nestedResult = TryAtIndex<I + 1>(val, handler, args); + if (nestedResult || !thisResult.HasErrorMessage()) + return nestedResult; + if (!nestedResult.HasErrorMessage()) + return thisResult; + if (StringStartsWith(nestedResult.GetErrorMessage(), "\"")) + return Acore::StringFormat("\"%s\"\n%s %s", thisResult.GetErrorMessage().c_str(), GetAcoreString(handler, LANG_CMDPARSER_OR), nestedResult.GetErrorMessage().c_str()); + else + return Acore::StringFormat("\"%s\"\n%s \"%s\"", thisResult.GetErrorMessage().c_str(), GetAcoreString(handler, LANG_CMDPARSER_OR), nestedResult.GetErrorMessage().c_str()); + } + } + else + return std::nullopt; + } + + static ChatCommandResult TryConsume(Acore::ChatCommands::Variant<Ts...>& val, ChatHandler const* handler, std::string_view args) + { + ChatCommandResult result = TryAtIndex<0>(val, handler, args); + if (result.HasErrorMessage() && (result.GetErrorMessage().find('\n') != std::string::npos)) + return Acore::StringFormat("%s %s", GetAcoreString(handler, LANG_CMDPARSER_EITHER), result.GetErrorMessage().c_str()); + return result; + } + }; + + // AchievementEntry* from numeric id or link + template <> + struct AC_GAME_API ArgInfo<AchievementEntry const*> + { + static ChatCommandResult TryConsume(AchievementEntry const*&, ChatHandler const*, std::string_view); + }; + + // GameTele* from string name or link + template <> + struct AC_GAME_API ArgInfo<GameTele const*> + { + static ChatCommandResult TryConsume(GameTele const*&, ChatHandler const*, std::string_view); + }; + + // ItemTemplate* from numeric id or link + template <> + struct AC_GAME_API ArgInfo<ItemTemplate const*> + { + static ChatCommandResult TryConsume(ItemTemplate const*&, ChatHandler const*, std::string_view); + }; + + // SpellInfo const* from spell id or link + template <> + struct AC_GAME_API ArgInfo<SpellInfo const*> + { + static ChatCommandResult TryConsume(SpellInfo const*&, ChatHandler const*, std::string_view); + }; + +} + +#endif diff --git a/src/server/game/Chat/ChatCommands/ChatCommandHelpers.cpp b/src/server/game/Chat/ChatCommands/ChatCommandHelpers.cpp new file mode 100644 index 0000000000..f8e0efe07e --- /dev/null +++ b/src/server/game/Chat/ChatCommands/ChatCommandHelpers.cpp @@ -0,0 +1,31 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "ChatCommandHelpers.h" +#include "Chat.h" +#include "ObjectMgr.h" + +void Acore::Impl::ChatCommands::SendErrorMessageToHandler(ChatHandler* handler, std::string_view str) +{ + handler->SendSysMessage(str); + handler->SetSentErrorMessage(true); +} + +char const* Acore::Impl::ChatCommands::GetAcoreString(ChatHandler const* handler, AcoreStrings which) +{ + return handler->GetAcoreString(which); +} diff --git a/src/server/game/Chat/ChatCommands/ChatCommandHelpers.h b/src/server/game/Chat/ChatCommands/ChatCommandHelpers.h new file mode 100644 index 0000000000..6ead517316 --- /dev/null +++ b/src/server/game/Chat/ChatCommands/ChatCommandHelpers.h @@ -0,0 +1,132 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _CHATCOMMAND_HELPERS_H_ +#define _CHATCOMMAND_HELPERS_H_ + +#include "Define.h" +#include "Language.h" +#include "StringFormat.h" +#include <optional> +#include <string> +#include <string_view> +#include <type_traits> +#include <variant> + +class ChatHandler; + +namespace Acore::Impl::ChatCommands +{ + /***************** HELPERS *************************\ + |* These really aren't for outside use... *| + \***************************************************/ + + static constexpr char COMMAND_DELIMITER = ' '; + + template <typename T, typename = void> + struct tag_base + { + using type = T; + }; + + template <typename T> + using tag_base_t = typename tag_base<T>::type; + + struct TokenizeResult { + explicit operator bool() { return !token.empty(); } + std::string_view token; + std::string_view tail; + }; + + inline TokenizeResult tokenize(std::string_view args) + { + TokenizeResult result; + if (size_t delimPos = args.find(COMMAND_DELIMITER); delimPos != std::string_view::npos) + { + result.token = args.substr(0, delimPos); + if (size_t tailPos = args.find_first_not_of(COMMAND_DELIMITER, delimPos); tailPos != std::string_view::npos) + result.tail = args.substr(tailPos); + } + else + result.token = args; + + return result; + } + + template <typename T, typename... Ts> + struct are_all_assignable + { + static constexpr bool value = (std::is_assignable_v<T&, Ts> && ...); + }; + + template <typename... Ts> + struct are_all_assignable<void, Ts...> + { + static constexpr bool value = false; + }; + + template <std::size_t index, typename T1, typename... Ts> + struct get_nth : get_nth<index-1, Ts...> { }; + + template <typename T1, typename... Ts> + struct get_nth<0, T1, Ts...> + { + using type = T1; + }; + + template <std::size_t index, typename... Ts> + using get_nth_t = typename get_nth<index, Ts...>::type; + + // this essentially models std::optional<std::string_view>, except it can also hold an error message + // it has std::string_view's bool conversion and dereference operators + // + // monostate <-> unspecified error, typically end-of-string reached or parsing failed + // std::string <-> specified error, typically character-not-found or invalid item link + // std::string_view <-> success, string_view is remaining argument string + struct ChatCommandResult + { + ChatCommandResult(std::nullopt_t) : _storage() {} + ChatCommandResult(std::string const&) = delete; + ChatCommandResult(std::string&& s) : _storage(std::in_place_type<std::string>, std::forward<std::string>(s)) {} + ChatCommandResult(char const* c) : _storage(std::in_place_type<std::string>, c) {} + ChatCommandResult(std::string_view s) : _storage(std::in_place_type<std::string_view>, s) {} + + ChatCommandResult(ChatCommandResult const&) = delete; + ChatCommandResult(ChatCommandResult&&) = default; + ChatCommandResult& operator=(ChatCommandResult const&) = delete; + ChatCommandResult& operator=(ChatCommandResult&&) = default; + + std::string_view operator*() const { return std::get<std::string_view>(_storage); } + bool IsSuccessful() const { return std::holds_alternative<std::string_view>(_storage); } + explicit operator bool() const { return IsSuccessful(); } + bool HasErrorMessage() const { return std::holds_alternative<std::string>(_storage); } + std::string const& GetErrorMessage() const { return std::get<std::string>(_storage); } + + private: + std::variant<std::monostate, std::string_view, std::string> _storage; + }; + + AC_GAME_API void SendErrorMessageToHandler(ChatHandler* handler, std::string_view str); + AC_GAME_API char const* GetAcoreString(ChatHandler const* handler, AcoreStrings which); + template <typename... Ts> + std::string FormatAcoreString(ChatHandler const* handler, AcoreStrings which, Ts&&... args) + { + return Acore::StringFormat(GetAcoreString(handler, which), std::forward<Ts>(args)...); + } +} + +#endif diff --git a/src/server/game/Chat/ChatCommands/ChatCommandTags.cpp b/src/server/game/Chat/ChatCommands/ChatCommandTags.cpp new file mode 100644 index 0000000000..f9527a570a --- /dev/null +++ b/src/server/game/Chat/ChatCommands/ChatCommandTags.cpp @@ -0,0 +1,145 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "ChatCommandTags.h" +#include "AccountMgr.h" +#include "Chat.h" +#include "ChatCommandArgs.h" +#include "ObjectAccessor.h" +#include "ObjectMgr.h" +#include "Player.h" + +using namespace Acore::Impl::ChatCommands; + +ChatCommandResult Acore::ChatCommands::QuotedString::TryConsume(ChatHandler const* handler, std::string_view args) +{ + if (args.empty()) + return std::nullopt; + + if ((args[0] != '"') && (args[0] != '\'')) + return ArgInfo<std::string>::TryConsume(*this, handler, args); + + char const QUOTE = args[0]; + for (size_t i = 1; i < args.length(); ++i) + { + if (args[i] == QUOTE) + { + auto [remainingToken, tail] = tokenize(args.substr(i + 1)); + if (remainingToken.empty()) // if this is not empty, then we did not consume the full token + return tail; + else + return std::nullopt; + } + + if (args[i] == '\\') + { + ++i; + if (!(i < args.length())) + break; + } + std::string::push_back(args[i]); + } + + // if we reach this, we did not find a closing quote + return std::nullopt; +} + +ChatCommandResult Acore::ChatCommands::AccountIdentifier::TryConsume(ChatHandler const* handler, std::string_view args) +{ + std::string_view text; + ChatCommandResult next = ArgInfo<std::string_view>::TryConsume(text, handler, args); + if (!next) + return next; + + // first try parsing as account name + _name.assign(text); + if (!Utf8ToUpperOnlyLatin(_name)) + return GetAcoreString(handler, LANG_CMDPARSER_INVALID_UTF8); + + _id = AccountMgr::GetId(_name); + if (_id) // account with name exists, we are done + return next; + + // try parsing as account id instead + Optional<uint32> id = Acore::StringTo<uint32>(text, 10); + if (!id) + return FormatAcoreString(handler, LANG_CMDPARSER_ACCOUNT_NAME_NO_EXIST, STRING_VIEW_FMT_ARG(_name)); + + _id = *id; + + if (AccountMgr::GetName(_id, _name)) + return next; + else + return FormatAcoreString(handler, LANG_CMDPARSER_ACCOUNT_ID_NO_EXIST, _id); +} + +ChatCommandResult Acore::ChatCommands::PlayerIdentifier::TryConsume(ChatHandler const* handler, std::string_view args) +{ + Variant<Hyperlink<player>, ObjectGuid::LowType, std::string_view> val; + ChatCommandResult next = ArgInfo<decltype(val)>::TryConsume(val, handler, args); + if (!next) + return next; + + if (val.holds_alternative<ObjectGuid::LowType>()) + { + _guid = ObjectGuid::Create<HighGuid::Player>(val.get<ObjectGuid::LowType>()); + + if ((_player = ObjectAccessor::FindPlayerByLowGUID(_guid.GetCounter()))) + _name = _player->GetName(); + else if (!sObjectMgr->GetPlayerNameByGUID(_guid.GetCounter(), _name)) + return FormatAcoreString(handler, LANG_CMDPARSER_CHAR_GUID_NO_EXIST, _guid.ToString().c_str()); + + return next; + } + else + { + if (val.holds_alternative<Hyperlink<player>>()) + _name.assign(static_cast<std::string_view>(val.get<Hyperlink<player>>())); + else + _name.assign(val.get<std::string_view>()); + + if (!normalizePlayerName(_name)) + return FormatAcoreString(handler, LANG_CMDPARSER_CHAR_NAME_INVALID, STRING_VIEW_FMT_ARG(_name)); + + if ((_player = ObjectAccessor::FindPlayerByName(_name))) + _guid = _player->GetGUID(); + else if (!(_guid = sObjectMgr->GetPlayerGUIDByName(_name))) + return FormatAcoreString(handler, LANG_CMDPARSER_CHAR_NAME_NO_EXIST, STRING_VIEW_FMT_ARG(_name)); + + return next; + } +} + +Acore::ChatCommands::PlayerIdentifier::PlayerIdentifier(Player& player) + : _name(player.GetName()), _guid(player.GetGUID()), _player(&player) {} + +/*static*/ Optional<Acore::ChatCommands::PlayerIdentifier> Acore::ChatCommands::PlayerIdentifier::FromTarget(ChatHandler* handler) +{ + if (Player* player = handler->GetPlayer()) + if (Player* target = player->GetSelectedPlayer()) + return { *target }; + + return std::nullopt; +} + +/*static*/ Optional<Acore::ChatCommands::PlayerIdentifier> Acore::ChatCommands::PlayerIdentifier::FromSelf(ChatHandler* handler) +{ + if (Player* player = handler->GetPlayer()) + return { *player }; + + return std::nullopt; +} diff --git a/src/server/game/Chat/ChatCommands/ChatCommandTags.h b/src/server/game/Chat/ChatCommands/ChatCommandTags.h new file mode 100644 index 0000000000..70e86c305a --- /dev/null +++ b/src/server/game/Chat/ChatCommands/ChatCommandTags.h @@ -0,0 +1,313 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _CHATCOMMANDTAGS_H +#define _CHATCOMMANDTAGS_H + +#include "advstd.h" +#include "ChatCommandHelpers.h" +#include "Hyperlinks.h" +#include "ObjectGuid.h" +#include "Optional.h" +#include "Util.h" +#include <boost/preprocessor/repetition/repeat.hpp> +#include <boost/preprocessor/punctuation/comma_if.hpp> +#include <cmath> +#include <cstring> +#include <iostream> +#include <string> +#include <string_view> +#include <tuple> +#include <type_traits> +#include <utility> +#include <variant> + +class ChatHandler; +class Player; + +namespace Acore::Impl::ChatCommands +{ + struct ContainerTag + { + using ChatCommandResult = Acore::Impl::ChatCommands::ChatCommandResult; + }; + + template <typename T> + struct tag_base<T, std::enable_if_t<std::is_base_of_v<ContainerTag, T>>> + { + using type = typename T::value_type; + }; + + template <size_t N> + inline constexpr char GetChar(char const (&s)[N], size_t i) + { + static_assert(N <= 25, "The EXACT_SEQUENCE macro can only be used with up to 25 character long literals. Specify them char-by-char (null terminated) as parameters to ExactSequence<> instead."); + return i >= N ? '\0' : s[i]; + } + +#define CHATCOMMANDS_IMPL_SPLIT_LITERAL_EXTRACT_CHAR(z, i, strliteral) \ + BOOST_PP_COMMA_IF(i) Acore::Impl::ChatCommands::GetChar(strliteral, i) + +#define CHATCOMMANDS_IMPL_SPLIT_LITERAL_CONSTRAINED(maxlen, strliteral) \ + BOOST_PP_REPEAT(maxlen, CHATCOMMANDS_IMPL_SPLIT_LITERAL_EXTRACT_CHAR, strliteral) + + // this creates always 25 elements - "abc" -> 'a', 'b', 'c', '\0', '\0', ... up to 25 +#define CHATCOMMANDS_IMPL_SPLIT_LITERAL(strliteral) CHATCOMMANDS_IMPL_SPLIT_LITERAL_CONSTRAINED(25, strliteral) +} + +namespace Acore::ChatCommands +{ + /************************** CONTAINER TAGS **********************************************\ + |* Simple holder classes to differentiate between extraction methods *| + |* Must inherit from Acore::Impl::ChatCommands::ContainerTag *| + |* Must implement the following: *| + |* - TryConsume: ChatHandler const*, std::string_view -> ChatCommandResult *| + |* - on match, returns tail of the provided argument string (as std::string_view) *| + |* - on specific error, returns error message (as std::string&& or char const*) *| + |* - on generic error, returns std::nullopt (this will print command usage) *| + |* *| + |* - typedef value_type of type that is contained within the tag *| + |* - cast operator to value_type *| + |* *| + \****************************************************************************************/ + + template <char... chars> + struct ExactSequence : Acore::Impl::ChatCommands::ContainerTag + { + using value_type = void; + + ChatCommandResult TryConsume(ChatHandler const* handler, std::string_view args) const + { + if (args.empty()) + return std::nullopt; + std::string_view start = args.substr(0, _string.length()); + if (StringEqualI(start, _string)) + { + auto [remainingToken, tail] = Acore::Impl::ChatCommands::tokenize(args.substr(_string.length())); + if (remainingToken.empty()) // if this is not empty, then we did not consume the full token + return tail; + start = args.substr(0, _string.length() + remainingToken.length()); + } + return Acore::Impl::ChatCommands::FormatAcoreString(handler, LANG_CMDPARSER_EXACT_SEQ_MISMATCH, STRING_VIEW_FMT_ARG(_string), STRING_VIEW_FMT_ARG(start)); + } + + private: + static constexpr std::array<char, sizeof...(chars)> _storage = { chars... }; + static_assert(!_storage.empty() && (_storage.back() == '\0'), "ExactSequence parameters must be null terminated! Use the EXACT_SEQUENCE macro to make this easier!"); + static constexpr std::string_view _string = { _storage.data(), std::string_view::traits_type::length(_storage.data()) }; + }; + +#define EXACT_SEQUENCE(str) Acore::ChatCommands::ExactSequence<CHATCOMMANDS_IMPL_SPLIT_LITERAL(str)> + + struct Tail : std::string_view, Acore::Impl::ChatCommands::ContainerTag + { + using value_type = std::string_view; + + using std::string_view::operator=; + + ChatCommandResult TryConsume(ChatHandler const*, std::string_view args) + { + std::string_view::operator=(args); + return std::string_view(); + } + }; + + struct WTail : std::wstring, Acore::Impl::ChatCommands::ContainerTag + { + using value_type = std::wstring; + + using std::wstring::operator=; + + ChatCommandResult TryConsume(ChatHandler const* handler, std::string_view args) + { + if (Utf8toWStr(args, *this)) + return std::string_view(); + else + return Acore::Impl::ChatCommands::GetAcoreString(handler, LANG_CMDPARSER_INVALID_UTF8); + } + }; + + struct QuotedString : std::string, Acore::Impl::ChatCommands::ContainerTag + { + using value_type = std::string; + + AC_GAME_API ChatCommandResult TryConsume(ChatHandler const* handler, std::string_view args); + }; + + struct AC_GAME_API AccountIdentifier : Acore::Impl::ChatCommands::ContainerTag + { + using value_type = uint32; + + operator uint32() const { return _id; } + operator std::string const& () const { return _name; } + operator std::string_view() const { return { _name }; } + + uint32 GetID() const { return _id; } + std::string const& GetName() const { return _name; } + + ChatCommandResult TryConsume(ChatHandler const* handler, std::string_view args); + + private: + uint32 _id; + std::string _name; + }; + + struct AC_GAME_API PlayerIdentifier : Acore::Impl::ChatCommands::ContainerTag + { + using value_type = Player*; + + PlayerIdentifier() : _name(), _guid(), _player(nullptr) {} + PlayerIdentifier(Player& player); + + operator ObjectGuid() const { return _guid; } + operator std::string const&() const { return _name; } + operator std::string_view() const { return _name; } + + std::string const& GetName() const { return _name; } + ObjectGuid GetGUID() const { return _guid; } + bool IsConnected() const { return (_player != nullptr); } + Player* GetConnectedPlayer() const { return _player; } + + ChatCommandResult TryConsume(ChatHandler const* handler, std::string_view args); + + static Optional<PlayerIdentifier> FromTarget(ChatHandler* handler); + static Optional<PlayerIdentifier> FromSelf(ChatHandler* handler); + static Optional<PlayerIdentifier> FromTargetOrSelf(ChatHandler* handler) + { + if (Optional<PlayerIdentifier> fromTarget = FromTarget(handler)) + return fromTarget; + else + return FromSelf(handler); + } + + private: + std::string _name; + ObjectGuid _guid; + Player* _player; + }; + + template <typename linktag> + struct Hyperlink : Acore::Impl::ChatCommands::ContainerTag + { + using value_type = typename linktag::value_type; + using storage_type = advstd::remove_cvref_t<value_type>; + + operator value_type() const { return val; } + value_type operator*() const { return val; } + storage_type const* operator->() const { return &val; } + + ChatCommandResult TryConsume(ChatHandler const* handler, std::string_view args) + { + Acore::Hyperlinks::HyperlinkInfo info = Acore::Hyperlinks::ParseSingleHyperlink(args); + // invalid hyperlinks cannot be consumed + if (!info) + return std::nullopt; + + // check if we got the right tag + if (info.tag != linktag::tag()) + return std::nullopt; + + // store value + if (!linktag::StoreTo(val, info.data)) + return Acore::Impl::ChatCommands::GetAcoreString(handler, LANG_CMDPARSER_LINKDATA_INVALID); + + // finally, skip any potential delimiters + auto [token, next] = Acore::Impl::ChatCommands::tokenize(info.tail); + if (token.empty()) /* empty token = first character is delimiter, skip past it */ + return next; + else + return info.tail; + } + + private: + storage_type val; + }; + + // pull in link tags for user convenience + using namespace ::Acore::Hyperlinks::LinkTags; +} + +namespace Acore::Impl +{ + template <typename T> + struct CastToVisitor + { + template <typename U> + T operator()(U const& v) const { return v; } + }; +} + +namespace Acore::ChatCommands +{ + template <typename T1, typename... Ts> + struct Variant : public std::variant<T1, Ts...> + { + using base = std::variant<T1, Ts...>; + + using first_type = Acore::Impl::ChatCommands::tag_base_t<T1>; + static constexpr bool have_operators = Acore::Impl::ChatCommands::are_all_assignable<first_type, Acore::Impl::ChatCommands::tag_base_t<Ts>...>::value; + + template <bool C = have_operators> + std::enable_if_t<C, first_type> operator*() const + { + return visit(Acore::Impl::CastToVisitor<first_type>()); + } + + template <bool C = have_operators> + operator std::enable_if_t<C, first_type>() const + { + return operator*(); + } + + template<bool C = have_operators> + operator std::enable_if_t<C && !std::is_same_v<first_type, size_t> && std::is_convertible_v<first_type, size_t>, size_t>() const + { + return operator*(); + } + + template <bool C = have_operators> + std::enable_if_t<C, bool> operator!() const { return !**this; } + + template <typename T> + Variant& operator=(T&& arg) { base::operator=(std::forward<T>(arg)); return *this; } + + template <size_t index> + constexpr decltype(auto) get() { return std::get<index>(static_cast<base&>(*this)); } + template <size_t index> + constexpr decltype(auto) get() const { return std::get<index>(static_cast<base const&>(*this)); } + template <typename type> + constexpr decltype(auto) get() { return std::get<type>(static_cast<base&>(*this)); } + template <typename type> + constexpr decltype(auto) get() const { return std::get<type>(static_cast<base const&>(*this)); } + + template <typename T> + constexpr decltype(auto) visit(T&& arg) { return std::visit(std::forward<T>(arg), static_cast<base&>(*this)); } + template <typename T> + constexpr decltype(auto) visit(T&& arg) const { return std::visit(std::forward<T>(arg), static_cast<base const&>(*this)); } + + template <typename T> + constexpr bool holds_alternative() const { return std::holds_alternative<T>(static_cast<base const&>(*this)); } + + template <bool C = have_operators> + friend std::enable_if_t<C, std::ostream&> operator<<(std::ostream& os, Acore::ChatCommands::Variant<T1, Ts...> const& v) + { + return (os << *v); + } + }; +} + +#endif diff --git a/src/server/game/Chat/ChatLink.cpp b/src/server/game/Chat/ChatLink.cpp deleted file mode 100644 index 490305a0a0..0000000000 --- a/src/server/game/Chat/ChatLink.cpp +++ /dev/null @@ -1,714 +0,0 @@ -/* - * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by the - * Free Software Foundation; either version 3 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "ChatLink.h" -#include "DBCStores.h" -#include "ObjectMgr.h" -#include "SpellInfo.h" -#include "SpellMgr.h" - -// Supported shift-links (client generated and server side) -// |color|Hachievement:achievement_id:player_guid:0:0:0:0:0:0:0:0|h[name]|h|r -// - client, item icon shift click, not used in server currently -// |color|Harea:area_id|h[name]|h|r -// |color|Hcreature:creature_guid|h[name]|h|r -// |color|Hcreature_entry:creature_id|h[name]|h|r -// |color|Henchant:recipe_spell_id|h[prof_name: recipe_name]|h|r - client, at shift click in recipes list dialog -// |color|Hgameevent:id|h[name]|h|r -// |color|Hgameobject:go_guid|h[name]|h|r -// |color|Hgameobject_entry:go_id|h[name]|h|r -// |color|Hglyph:glyph_slot_id:glyph_prop_id|h[%s]|h|r - client, at shift click in glyphs dialog, GlyphSlot.dbc, GlyphProperties.dbc -// |color|Hitem:item_id:perm_ench_id:gem1:gem2:gem3:0:0:0:0:reporter_level|h[name]|h|r -// - client, item icon shift click -// |color|Hitemset:itemset_id|h[name]|h|r -// |color|Hplayer:name|h[name]|h|r - client, in some messages, at click copy only name instead link -// |color|Hquest:quest_id:quest_level|h[name]|h|r - client, quest list name shift-click -// |color|Hskill:skill_id|h[name]|h|r -// |color|Hspell:spell_id|h[name]|h|r - client, spellbook spell icon shift-click -// |color|Htalent:talent_id, rank|h[name]|h|r - client, talent icon shift-click -// |color|Htaxinode:id|h[name]|h|r -// |color|Htele:id|h[name]|h|r -// |color|Htitle:id|h[name]|h|r -// |color|Htrade:spell_id:cur_value:max_value:unk3int:unk3str|h[name]|h|r - client, spellbook profession icon shift-click - -inline bool ReadUInt32(std::istringstream& iss, uint32& res) -{ - iss >> std::dec >> res; - return !iss.fail() && !iss.eof(); -} - -inline bool ReadInt32(std::istringstream& iss, int32& res) -{ - iss >> std::dec >> res; - return !iss.fail() && !iss.eof(); -} - -inline std::string ReadSkip(std::istringstream& iss, char term) -{ - std::string res; - char c = iss.peek(); - while (c != term && c != '\0') - { - res += c; - iss.ignore(1); - c = iss.peek(); - } - return res; -} - -inline bool CheckDelimiter(std::istringstream& iss, char delimiter, const char* context) -{ - (void)context; // used only with EXTRA_LOGS - char c = iss.peek(); - if (c != delimiter) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): invalid %s link structure ('%c' expected, '%c' found)", iss.str().c_str(), context, delimiter, c); - return false; - } - iss.ignore(1); - return true; -} - -inline bool ReadHex(std::istringstream& iss, uint32& res, uint32 length) -{ - std::istringstream::pos_type pos = iss.tellg(); - iss >> std::hex >> res; - //uint32 size = uint32(iss.gcount()); - if (length && uint32(iss.tellg() - pos) != length) - return false; - return !iss.fail() && !iss.eof(); -} - -#define DELIMITER ':' -#define PIPE_CHAR '|' - -bool ChatLink::ValidateName(char* buffer, const char* /*context*/) -{ - _name = buffer; - return true; -} - -// |color|Hitem:item_id:perm_ench_id:gem1:gem2:gem3:0:random_property:0:reporter_level|h[name]|h|r -// |cffa335ee|Hitem:812:0:0:0:0:0:0:0:70|h[Glowing Brightwood Staff]|h|r -bool ItemChatLink::Initialize(std::istringstream& iss) -{ - // Read item entry - uint32 itemEntry = 0; - if (!ReadUInt32(iss, itemEntry)) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item entry", iss.str().c_str()); - return false; - } - // Validate item - _item = sObjectMgr->GetItemTemplate(itemEntry); - if (!_item) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): got invalid itemEntry %u in |item command", iss.str().c_str(), itemEntry); - return false; - } - // Validate item's color - if (_color != ItemQualityColors[_item->Quality]) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): linked item has color %u, but user claims %u", iss.str().c_str(), ItemQualityColors[_item->Quality], _color); - return false; - } - // Number of various item properties after item entry - const uint8 propsCount = 8; - const uint8 randomPropertyPosition = 5; - for (uint8 index = 0; index < propsCount; ++index) - { - if (!CheckDelimiter(iss, DELIMITER, "item")) - return false; - - int32 id = 0; - if (!ReadInt32(iss, id)) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item property (%u)", iss.str().c_str(), index); - return false; - } - if (id && (index == randomPropertyPosition)) - { - // Validate random property - if (id > 0) - { - _property = sItemRandomPropertiesStore.LookupEntry(id); - if (!_property) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): got invalid item property id %u in |item command", iss.str().c_str(), id); - return false; - } - } - else if (id < 0) - { - _suffix = sItemRandomSuffixStore.LookupEntry(-id); - if (!_suffix) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): got invalid item suffix id %u in |item command", iss.str().c_str(), -id); - return false; - } - } - } - _data[index] = id; - } - return true; -} - -inline std::string ItemChatLink::FormatName(uint8 index, ItemLocale const* locale, char* const* suffixStrings) const -{ - std::stringstream ss; - if (locale == nullptr || index >= locale->Name.size()) - ss << _item->Name1; - else - ss << locale->Name[index]; - if (suffixStrings) - ss << ' ' << suffixStrings[index]; - return ss.str(); -} - -bool ItemChatLink::ValidateName(char* buffer, const char* context) -{ - ChatLink::ValidateName(buffer, context); - - char* const* suffixStrings = _suffix ? _suffix->nameSuffix : (_property ? _property->nameSuffix : nullptr); - - bool res = (FormatName(LOCALE_enUS, nullptr, suffixStrings) == buffer); - - if (!res) - { - ItemLocale const* il = sObjectMgr->GetItemLocale(_item->ItemId); - for (uint8 index = LOCALE_koKR; index < TOTAL_LOCALES; ++index) - { - if (FormatName(index, il, suffixStrings) == buffer) - { - res = true; - break; - } - } - } - - if (!res) - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): linked item (id: %u) name wasn't found in any localization", context, _item->ItemId); - return res; -} - -// |color|Hquest:quest_id:quest_level|h[name]|h|r -// |cff808080|Hquest:2278:47|h[The Platinum Discs]|h|r -bool QuestChatLink::Initialize(std::istringstream& iss) -{ - // Read quest id - uint32 questId = 0; - if (!ReadUInt32(iss, questId)) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading quest entry", iss.str().c_str()); - return false; - } - // Validate quest - _quest = sObjectMgr->GetQuestTemplate(questId); - if (!_quest) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): quest template %u not found", iss.str().c_str(), questId); - return false; - } - // Check delimiter - if (!CheckDelimiter(iss, DELIMITER, "quest")) - return false; - // Read quest level - if (!ReadInt32(iss, _questLevel)) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading quest level", iss.str().c_str()); - return false; - } - // Validate quest level - if (_questLevel >= STRONG_MAX_LEVEL) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): quest level %d is too big", iss.str().c_str(), _questLevel); - return false; - } - return true; -} - -bool QuestChatLink::ValidateName(char* buffer, const char* context) -{ - ChatLink::ValidateName(buffer, context); - - bool res = (_quest->GetTitle() == buffer); - if (!res) - if (QuestLocale const* ql = sObjectMgr->GetQuestLocale(_quest->GetQuestId())) - for (uint8 i = 0; i < ql->Title.size(); i++) - if (ql->Title[i] == buffer) - { - res = true; - break; - } - - if (!res) - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): linked quest (id: %u) title wasn't found in any localization", context, _quest->GetQuestId()); - return res; -} - -// |color|Hspell:spell_id|h[name]|h|r -// |cff71d5ff|Hspell:21563|h[Command]|h|r -bool SpellChatLink::Initialize(std::istringstream& iss) -{ - if (_color != CHAT_LINK_COLOR_SPELL) - return false; - // Read spell id - uint32 spellId = 0; - if (!ReadUInt32(iss, spellId)) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading spell entry", iss.str().c_str()); - return false; - } - // Validate spell - _spell = sSpellMgr->GetSpellInfo(spellId); - if (!_spell) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): got invalid spell id %u in |spell command", iss.str().c_str(), spellId); - return false; - } - return true; -} - -bool SpellChatLink::ValidateName(char* buffer, const char* context) -{ - ChatLink::ValidateName(buffer, context); - - // spells with that flag have a prefix of "$PROFESSION: " - if (_spell->HasAttribute(SPELL_ATTR0_IS_TRADESKILL)) - { - SkillLineAbilityMapBounds bounds = sSpellMgr->GetSkillLineAbilityMapBounds(_spell->Id); - if (bounds.first == bounds.second) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): skill line not found for spell %u", context, _spell->Id); - return false; - } - SkillLineAbilityEntry const* skillInfo = bounds.first->second; - if (!skillInfo) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): skill line ability not found for spell %u", context, _spell->Id); - return false; - } - SkillLineEntry const* skillLine = sSkillLineStore.LookupEntry(skillInfo->SkillLine); - if (!skillLine) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): skill line not found for skill %u", context, skillInfo->SkillLine); - return false; - } - - for (uint8 i = 0; i < TOTAL_LOCALES; ++i) - { - uint32 skillLineNameLength = strlen(skillLine->name[i]); - if (skillLineNameLength > 0 && strncmp(skillLine->name[i], buffer, skillLineNameLength) == 0) - { - // found the prefix, remove it to perform spellname validation below - // -2 = strlen(": ") - uint32 spellNameLength = (strlen(buffer) <= skillLineNameLength + 2) ? 0 : strlen(buffer) - skillLineNameLength - 2; - memmove(buffer, buffer + skillLineNameLength + 2, spellNameLength + 1); - break; - } - } - } - - bool res = false; - for (uint8 i = 0; i < TOTAL_LOCALES; ++i) - if (*_spell->SpellName[i] && strcmp(_spell->SpellName[i], buffer) == 0) - { - res = true; - break; - } - - if (!res) - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): linked spell (id: %u) name wasn't found in any localization", context, _spell->Id); - return res; -} - -// |color|Hachievement:achievement_id:player_guid:0:0:0:0:0:0:0:0|h[name]|h|r -// |cffffff00|Hachievement:546:0000000000000001:0:0:0:-1:0:0:0:0|h[Safe Deposit]|h|r -bool AchievementChatLink::Initialize(std::istringstream& iss) -{ - if (_color != CHAT_LINK_COLOR_ACHIEVEMENT) - return false; - // Read achievemnt Id - uint32 achievementId = 0; - if (!ReadUInt32(iss, achievementId)) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading achievement entry", iss.str().c_str()); - return false; - } - // Validate achievement - _achievement = sAchievementStore.LookupEntry(achievementId); - if (!_achievement) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): got invalid achivement id %u in |achievement command", iss.str().c_str(), achievementId); - return false; - } - // Check delimiter - if (!CheckDelimiter(iss, DELIMITER, "achievement")) - return false; - // Read HEX - if (!ReadHex(iss, _guid, 0)) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): invalid hexadecimal number while reading char's guid", iss.str().c_str()); - return false; - } - // Skip progress - const uint8 propsCount = 8; - for (uint8 index = 0; index < propsCount; ++index) - { - if (!CheckDelimiter(iss, DELIMITER, "achievement")) - return false; - - if (!ReadUInt32(iss, _data[index])) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading achievement property (%u)", iss.str().c_str(), index); - return false; - } - } - return true; -} - -bool AchievementChatLink::ValidateName(char* buffer, const char* context) -{ - ChatLink::ValidateName(buffer, context); - - bool res = false; - for (uint8 i = 0; i < TOTAL_LOCALES; ++i) - if (*_achievement->name[i] && strcmp(_achievement->name[i], buffer) == 0) - { - res = true; - break; - } - - if (!res) - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): linked achievement (id: %u) name wasn't found in any localization", context, _achievement->ID); - return res; -} - -// |color|Htrade:spell_id:cur_value:max_value:player_guid:base64_data|h[name]|h|r -// |cffffd000|Htrade:4037:1:150:1:6AAAAAAAAAAAAAAAAAAAAAAOAADAAAAAAAAAAAAAAAAIAAAAAAAAA|h[Engineering]|h|r -bool TradeChatLink::Initialize(std::istringstream& iss) -{ - if (_color != CHAT_LINK_COLOR_TRADE) - return false; - // Spell Id - uint32 spellId = 0; - if (!ReadUInt32(iss, spellId)) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading achievement entry", iss.str().c_str()); - return false; - } - // Validate spell - _spell = sSpellMgr->GetSpellInfo(spellId); - if (!_spell) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): got invalid spell id %u in |trade command", iss.str().c_str(), spellId); - return false; - } - // Check delimiter - if (!CheckDelimiter(iss, DELIMITER, "trade")) - return false; - // Minimum talent level - if (!ReadInt32(iss, _minSkillLevel)) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading minimum talent level", iss.str().c_str()); - return false; - } - // Check delimiter - if (!CheckDelimiter(iss, DELIMITER, "trade")) - return false; - // Maximum talent level - if (!ReadInt32(iss, _maxSkillLevel)) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading maximum talent level", iss.str().c_str()); - return false; - } - // Check delimiter - if (!CheckDelimiter(iss, DELIMITER, "trade")) - return false; - // Something hexadecimal - if (!ReadHex(iss, _guid, 0)) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading achievement's owner guid", iss.str().c_str()); - return false; - } - // Skip base64 encoded stuff - _base64 = ReadSkip(iss, PIPE_CHAR); - return true; -} - -// |color|Htalent:talent_id:rank|h[name]|h|r -// |cff4e96f7|Htalent:2232:-1|h[Taste for Blood]|h|r -bool TalentChatLink::Initialize(std::istringstream& iss) -{ - if (_color != CHAT_LINK_COLOR_TALENT) - return false; - // Read talent entry - if (!ReadUInt32(iss, _talentId)) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading talent entry", iss.str().c_str()); - return false; - } - // Validate talent - TalentEntry const* talentInfo = sTalentStore.LookupEntry(_talentId); - if (!talentInfo) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): got invalid talent id %u in |talent command", iss.str().c_str(), _talentId); - return false; - } - // Validate talent's spell - _spell = sSpellMgr->GetSpellInfo(talentInfo->RankID[0]); - if (!_spell) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): got invalid spell id %u in |trade command", iss.str().c_str(), talentInfo->RankID[0]); - return false; - } - // Delimiter - if (!CheckDelimiter(iss, DELIMITER, "talent")) - return false; - // Rank - if (!ReadInt32(iss, _rankId)) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading talent rank", iss.str().c_str()); - return false; - } - return true; -} - -// |color|Henchant:recipe_spell_id|h[prof_name: recipe_name]|h|r -// |cffffd000|Henchant:3919|h[Engineering: Rough Dynamite]|h|r -bool EnchantmentChatLink::Initialize(std::istringstream& iss) -{ - if (_color != CHAT_LINK_COLOR_ENCHANT) - return false; - // Spell Id - uint32 spellId = 0; - if (!ReadUInt32(iss, spellId)) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading enchantment spell entry", iss.str().c_str()); - return false; - } - // Validate spell - _spell = sSpellMgr->GetSpellInfo(spellId); - if (!_spell) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): got invalid spell id %u in |enchant command", iss.str().c_str(), spellId); - return false; - } - return true; -} - -// |color|Hglyph:glyph_slot_id:glyph_prop_id|h[%s]|h|r -// |cff66bbff|Hglyph:21:762|h[Glyph of Bladestorm]|h|r -bool GlyphChatLink::Initialize(std::istringstream& iss) -{ - if (_color != CHAT_LINK_COLOR_GLYPH) - return false; - // Slot - if (!ReadUInt32(iss, _slotId)) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading slot id", iss.str().c_str()); - return false; - } - // Check delimiter - if (!CheckDelimiter(iss, DELIMITER, "glyph")) - return false; - // Glyph Id - uint32 glyphId = 0; - if (!ReadUInt32(iss, glyphId)) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading glyph entry", iss.str().c_str()); - return false; - } - // Validate glyph - _glyph = sGlyphPropertiesStore.LookupEntry(glyphId); - if (!_glyph) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): got invalid glyph id %u in |glyph command", iss.str().c_str(), glyphId); - return false; - } - // Validate glyph's spell - _spell = sSpellMgr->GetSpellInfo(_glyph->SpellId); - if (!_spell) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): got invalid spell id %u in |glyph command", iss.str().c_str(), _glyph->SpellId); - return false; - } - return true; -} - -LinkExtractor::LinkExtractor(const char* msg) : _iss(msg) -{ -} - -LinkExtractor::~LinkExtractor() -{ - for (Links::iterator itr = _links.begin(); itr != _links.end(); ++itr) - delete *itr; - _links.clear(); -} - -bool LinkExtractor::IsValidMessage() -{ - const char validSequence[6] = "cHhhr"; - const char* validSequenceIterator = validSequence; - - char buffer[256]; - - std::istringstream::pos_type startPos = 0; - uint32 color = 0; - - ChatLink* link = nullptr; - while (!_iss.eof()) - { - if (validSequence == validSequenceIterator) - { - link = nullptr; - _iss.ignore(255, PIPE_CHAR); - startPos = _iss.tellg() - std::istringstream::pos_type(1); - } - else if (_iss.get() != PIPE_CHAR) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence aborted unexpectedly", _iss.str().c_str()); - return false; - } - - // pipe has always to be followed by at least one char - if (_iss.peek() == '\0') - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): pipe followed by '\\0'", _iss.str().c_str()); - return false; - } - - // no further pipe commands - if (_iss.eof()) - break; - - char commandChar; - _iss.get(commandChar); - - // | in normal messages is escaped by || - if (commandChar != PIPE_CHAR) - { - if (commandChar == *validSequenceIterator) - { - if (validSequenceIterator == validSequence + 4) - validSequenceIterator = validSequence; - else - ++validSequenceIterator; - } - else - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): invalid sequence, expected '%c' but got '%c'", _iss.str().c_str(), *validSequenceIterator, commandChar); - return false; - } - } - else if (validSequence != validSequenceIterator) - { - // no escaped pipes in sequences - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): got escaped pipe in sequence", _iss.str().c_str()); - return false; - } - - switch (commandChar) - { - case 'c': - if (!ReadHex(_iss, color, 8)) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): invalid hexadecimal number while reading color", _iss.str().c_str()); - return false; - } - break; - case 'H': - // read chars up to colon = link type - _iss.getline(buffer, 256, DELIMITER); - if (_iss.eof()) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly", _iss.str().c_str()); - return false; - } - - if (strcmp(buffer, "item") == 0) - link = new ItemChatLink(); - else if (strcmp(buffer, "quest") == 0) - link = new QuestChatLink(); - else if (strcmp(buffer, "trade") == 0) - link = new TradeChatLink(); - else if (strcmp(buffer, "talent") == 0) - link = new TalentChatLink(); - else if (strcmp(buffer, "spell") == 0) - link = new SpellChatLink(); - else if (strcmp(buffer, "enchant") == 0) - link = new EnchantmentChatLink(); - else if (strcmp(buffer, "achievement") == 0) - link = new AchievementChatLink(); - else if (strcmp(buffer, "glyph") == 0) - link = new GlyphChatLink(); - else - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): user sent unsupported link type '%s'", _iss.str().c_str(), buffer); - return false; - } - _links.push_back(link); - link->SetColor(color); - if (!link->Initialize(_iss)) - return false; - break; - case 'h': - // if h is next element in sequence, this one must contain the linked text :) - if (*validSequenceIterator == 'h') - { - // links start with '[' - if (_iss.get() != '[') - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): link caption doesn't start with '['", _iss.str().c_str()); - return false; - } - _iss.getline(buffer, 256, ']'); - if (_iss.eof()) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly", _iss.str().c_str()); - return false; - } - - if (!link) - return false; - - if (!link->ValidateName(buffer, _iss.str().c_str())) - return false; - } - break; - case 'r': - if (link) - link->SetBounds(startPos, _iss.tellg()); - case '|': - // no further payload - break; - default: - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): got invalid command |%c", _iss.str().c_str(), commandChar); - return false; - } - } - - // check if every opened sequence was also closed properly - if (validSequence != validSequenceIterator) - { - LOG_DEBUG("chat.system", "ChatHandler::isValidChatMessage('%s'): EOF in active sequence", _iss.str().c_str()); - return false; - } - - return true; -} diff --git a/src/server/game/Chat/ChatLink.h b/src/server/game/Chat/ChatLink.h deleted file mode 100644 index d7f454a4a6..0000000000 --- a/src/server/game/Chat/ChatLink.h +++ /dev/null @@ -1,176 +0,0 @@ -/* - * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by the - * Free Software Foundation; either version 3 of the License, or (at your - * option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef AZEROTHCORE_CHATLINK_H -#define AZEROTHCORE_CHATLINK_H - -#include "SharedDefines.h" -#include <cstring> -#include <list> -#include <sstream> - -struct ItemLocale; -struct ItemTemplate; -struct ItemRandomSuffixEntry; -struct ItemRandomPropertiesEntry; -class SpellInfo; -struct AchievementEntry; -struct GlyphPropertiesEntry; -class Quest; - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// ChatLink - abstract base class for various links -class ChatLink -{ -public: - ChatLink() : _color(0), _startPos(0), _endPos(0) { } - virtual ~ChatLink() { } - void SetColor(uint32 color) { _color = color; } - // This will allow to extract the whole link string from the message, if necessary. - void SetBounds(std::istringstream::pos_type startPos, std::istringstream::pos_type endPos) { _startPos = startPos; _endPos = endPos; } - - virtual bool Initialize(std::istringstream& iss) = 0; - virtual bool ValidateName(char* buffer, const char* context) = 0; - -protected: - uint32 _color; - std::string _name; - std::istringstream::pos_type _startPos; - std::istringstream::pos_type _endPos; -}; - -// ItemChatLink - link to item -class ItemChatLink : public ChatLink -{ -public: - ItemChatLink() : ChatLink(), _item(nullptr), _suffix(nullptr), _property(nullptr) - { - memset(_data, 0, sizeof(_data)); - } - bool Initialize(std::istringstream& iss) override; - bool ValidateName(char* buffer, const char* context) override; - -protected: - std::string FormatName(uint8 index, ItemLocale const* locale, char* const* suffixStrings) const; - - ItemTemplate const* _item; - int32 _data[8]; - ItemRandomSuffixEntry const* _suffix; - ItemRandomPropertiesEntry const* _property; -}; - -// QuestChatLink - link to quest -class QuestChatLink : public ChatLink -{ -public: - QuestChatLink() : ChatLink(), _quest(nullptr), _questLevel(0) { } - bool Initialize(std::istringstream& iss) override; - bool ValidateName(char* buffer, const char* context) override; - -protected: - Quest const* _quest; - int32 _questLevel; -}; - -// SpellChatLink - link to quest -class SpellChatLink : public ChatLink -{ -public: - SpellChatLink() : ChatLink(), _spell(nullptr) { } - bool Initialize(std::istringstream& iss) override; - bool ValidateName(char* buffer, const char* context) override; - -protected: - SpellInfo const* _spell; -}; - -// AchievementChatLink - link to quest -class AchievementChatLink : public ChatLink -{ -public: - AchievementChatLink() : ChatLink(), _guid(0), _achievement(nullptr) - { - memset(_data, 0, sizeof(_data)); - } - bool Initialize(std::istringstream& iss) override; - bool ValidateName(char* buffer, const char* context) override; - -protected: - uint32 _guid; - AchievementEntry const* _achievement; - uint32 _data[8]; -}; - -// TradeChatLink - link to trade info -class TradeChatLink : public SpellChatLink -{ -public: - TradeChatLink() : SpellChatLink(), _minSkillLevel(0), _maxSkillLevel(0), _guid(0) { } - bool Initialize(std::istringstream& iss) override; -private: - int32 _minSkillLevel; - int32 _maxSkillLevel; - uint32 _guid; - std::string _base64; -}; - -// TalentChatLink - link to talent -class TalentChatLink : public SpellChatLink -{ -public: - TalentChatLink() : SpellChatLink(), _talentId(0), _rankId(0) { } - bool Initialize(std::istringstream& iss) override; - -private: - uint32 _talentId; - int32 _rankId; -}; - -// EnchantmentChatLink - link to enchantment -class EnchantmentChatLink : public SpellChatLink -{ -public: - EnchantmentChatLink() : SpellChatLink() { } - bool Initialize(std::istringstream& iss) override; -}; - -// GlyphChatLink - link to glyph -class GlyphChatLink : public SpellChatLink -{ -public: - GlyphChatLink() : SpellChatLink(), _slotId(0), _glyph(nullptr) { } - bool Initialize(std::istringstream& iss) override; -private: - uint32 _slotId; - GlyphPropertiesEntry const* _glyph; -}; - -class LinkExtractor -{ -public: - explicit LinkExtractor(const char* msg); - ~LinkExtractor(); - - bool IsValidMessage(); - -private: - typedef std::list<ChatLink*> Links; - Links _links; - std::istringstream _iss; -}; - -#endif diff --git a/src/server/game/Chat/HyperlinkTags.cpp b/src/server/game/Chat/HyperlinkTags.cpp new file mode 100644 index 0000000000..9d2fe62f10 --- /dev/null +++ b/src/server/game/Chat/HyperlinkTags.cpp @@ -0,0 +1,261 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "Hyperlinks.h" +#include "AchievementMgr.h" +#include "ObjectMgr.h" +#include "SpellInfo.h" +#include "SpellMgr.h" +#include <limits> + +static constexpr char HYPERLINK_DATA_DELIMITER = ':'; + +class HyperlinkDataTokenizer +{ + public: + HyperlinkDataTokenizer(std::string_view str) : _str(str) {} + + template <typename T> + bool TryConsumeTo(T& val) + { + if (IsEmpty()) + return false; + + if (size_t off = _str.find(HYPERLINK_DATA_DELIMITER); off != std::string_view::npos) + { + if (!Acore::Hyperlinks::LinkTags::base_tag::StoreTo(val, _str.substr(0, off))) + return false; + _str = _str.substr(off+1); + } + else + { + if (!Acore::Hyperlinks::LinkTags::base_tag::StoreTo(val, _str)) + return false; + _str = std::string_view(); + } + + return true; + } + + bool IsEmpty() { return _str.empty(); } + + private: + std::string_view _str; +}; + +bool Acore::Hyperlinks::LinkTags::achievement::StoreTo(AchievementLinkData& val, std::string_view text) +{ + HyperlinkDataTokenizer t(text); + uint32 achievementId; + + if (!t.TryConsumeTo(achievementId)) + return false; + + val.Achievement = sAchievementMgr->GetAchievement(achievementId); + + if (!(val.Achievement && t.TryConsumeTo(val.CharacterId) && t.TryConsumeTo(val.IsFinished) && t.TryConsumeTo(val.Month) && t.TryConsumeTo(val.Day))) + return false; + + if ((12 < val.Month) || (31 < val.Day)) + return false; + + int8 year; + + if (!t.TryConsumeTo(year)) + return false; + + if (val.IsFinished) // if finished, year must be >= 0 + { + if (year < 0) + return false; + val.Year = static_cast<uint8>(year); + } + else + val.Year = 0; + + return (t.TryConsumeTo(val.Criteria[0]) && t.TryConsumeTo(val.Criteria[1]) && t.TryConsumeTo(val.Criteria[2]) && t.TryConsumeTo(val.Criteria[3]) && t.IsEmpty()); +} + +bool Acore::Hyperlinks::LinkTags::enchant::StoreTo(SpellInfo const*& val, std::string_view text) +{ + HyperlinkDataTokenizer t(text); + uint32 spellId; + + if (!(t.TryConsumeTo(spellId) && t.IsEmpty())) + return false; + + return (val = sSpellMgr->GetSpellInfo(spellId)) && val->HasAttribute(SPELL_ATTR0_IS_TRADESKILL); +} + +bool Acore::Hyperlinks::LinkTags::glyph::StoreTo(GlyphLinkData& val, std::string_view text) +{ + HyperlinkDataTokenizer t(text); + uint32 slot, prop; + + if (!(t.TryConsumeTo(slot) && t.TryConsumeTo(prop) && t.IsEmpty())) + return false; + + if (!(val.Slot = sGlyphSlotStore.LookupEntry(slot))) + return false; + + if (!(val.Glyph = sGlyphPropertiesStore.LookupEntry(prop))) + return false; + + return true; +} + +bool Acore::Hyperlinks::LinkTags::item::StoreTo(ItemLinkData& val, std::string_view text) +{ + HyperlinkDataTokenizer t(text); + uint32 itemId, dummy; + + if (!t.TryConsumeTo(itemId)) + return false; + + val.Item = sObjectMgr->GetItemTemplate(itemId); + val.IsBuggedInspectLink = false; + + // randomPropertyId is actually a int16 in the client + // positive values index ItemRandomSuffix.dbc, while negative values index ItemRandomProperties.dbc + // however, there is also a client bug in inspect packet handling that causes a int16 to be cast to uint16, then int32 (dropping sign extension along the way) + // this results in the wrong value being sent in the link; DBC lookup clientside fails, so it sends the link without suffix + // to detect and allow these invalid links, we first read randomPropertyId as a full int32 + int32 randomPropertyId; + if (!(val.Item && t.TryConsumeTo(val.EnchantId) && t.TryConsumeTo(val.GemEnchantId[0]) && t.TryConsumeTo(val.GemEnchantId[1]) && + t.TryConsumeTo(val.GemEnchantId[2]) && t.TryConsumeTo(dummy) && t.TryConsumeTo(randomPropertyId) && t.TryConsumeTo(val.RandomSuffixBaseAmount) && + t.TryConsumeTo(val.RenderLevel) && t.IsEmpty() && !dummy)) + return false; + + if ((static_cast<int32>(std::numeric_limits<int16>::max()) < randomPropertyId) && (randomPropertyId <= std::numeric_limits<uint16>::max())) + { // this is the bug case, the id we received is actually static_cast<uint16>(i16RandomPropertyId) + randomPropertyId = static_cast<int16>(randomPropertyId); + val.IsBuggedInspectLink = true; + } + + if (randomPropertyId < 0) + { + if (!val.Item->RandomSuffix) + return false; + + if (randomPropertyId < -static_cast<int32>(sItemRandomSuffixStore.GetNumRows())) + return false; + + if (ItemRandomSuffixEntry const* suffixEntry = sItemRandomSuffixStore.LookupEntry(-randomPropertyId)) + { + val.RandomSuffix = suffixEntry; + val.RandomProperty = nullptr; + } + else + return false; + } + else if (randomPropertyId > 0) + { + if (!val.Item->RandomProperty) + return false; + + if (ItemRandomPropertiesEntry const* propEntry = sItemRandomPropertiesStore.LookupEntry(randomPropertyId)) + { + val.RandomSuffix = nullptr; + val.RandomProperty = propEntry; + } + else + return false; + } + else + { + val.RandomSuffix = nullptr; + val.RandomProperty = nullptr; + } + + if ((val.RandomSuffix && !val.RandomSuffixBaseAmount) || (val.RandomSuffixBaseAmount && !val.RandomSuffix)) + return false; + + return true; +} + +bool Acore::Hyperlinks::LinkTags::quest::StoreTo(QuestLinkData& val, std::string_view text) +{ + HyperlinkDataTokenizer t(text); + uint32 questId; + + if (!t.TryConsumeTo(questId)) + return false; + + return (val.Quest = sObjectMgr->GetQuestTemplate(questId)) && t.TryConsumeTo(val.QuestLevel) && (val.QuestLevel >= -1) && t.IsEmpty(); +} + +bool Acore::Hyperlinks::LinkTags::spell::StoreTo(SpellInfo const*& val, std::string_view text) +{ + HyperlinkDataTokenizer t(text); + uint32 spellId; + + if (!(t.TryConsumeTo(spellId) && t.IsEmpty())) + return false; + + return !!(val = sSpellMgr->GetSpellInfo(spellId)); +} + +bool Acore::Hyperlinks::LinkTags::talent::StoreTo(TalentLinkData& val, std::string_view text) +{ + HyperlinkDataTokenizer t(text); + uint32 talentId; + int8 rank; // talent links contain <learned rank>-1, we store <learned rank> + + if (!(t.TryConsumeTo(talentId) && t.TryConsumeTo(rank) && t.IsEmpty())) + return false; + + if (rank < -1 || rank >= MAX_TALENT_RANK) + return false; + + val.Talent = sTalentStore.LookupEntry(talentId); + val.Rank = rank+1; + + if (!val.Talent) + return false; + + if (val.Rank > 0) + { + uint32 const spellId = val.Talent->RankID[val.Rank - 1]; + if (!spellId) + return false; + + val.Spell = sSpellMgr->GetSpellInfo(spellId); + + if (!val.Spell) + return false; + } + else + { + val.Spell = nullptr; + } + + return true; +} + +bool Acore::Hyperlinks::LinkTags::trade::StoreTo(TradeskillLinkData& val, std::string_view text) +{ + HyperlinkDataTokenizer t(text); + uint32 spellId; + + if (!t.TryConsumeTo(spellId)) + return false; + + val.Spell = sSpellMgr->GetSpellInfo(spellId); + + return (val.Spell && val.Spell->Effects[0].Effect == SPELL_EFFECT_TRADE_SKILL && t.TryConsumeTo(val.CurValue) && + t.TryConsumeTo(val.MaxValue) && t.TryConsumeTo(val.Owner) && t.TryConsumeTo(val.KnownRecipes) && t.IsEmpty()); +} diff --git a/src/server/game/Chat/Hyperlinks.cpp b/src/server/game/Chat/Hyperlinks.cpp new file mode 100644 index 0000000000..b296242961 --- /dev/null +++ b/src/server/game/Chat/Hyperlinks.cpp @@ -0,0 +1,424 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "Hyperlinks.h" +#include "advstd.h" +#include "Common.h" +#include "DBCStores.h" +#include "Errors.h" +#include "ObjectMgr.h" +#include "SharedDefines.h" +#include "SpellInfo.h" +#include "SpellMgr.h" +#include "QuestDef.h" + +using namespace Acore::Hyperlinks; + +inline uint8 toHex(char c) { return (c >= '0' && c <= '9') ? c - '0' + 0x10 : (c >= 'a' && c <= 'f') ? c - 'a' + 0x1a : 0x00; } + +// Validates a single hyperlink +HyperlinkInfo Acore::Hyperlinks::ParseSingleHyperlink(std::string_view str) +{ + uint32 color = 0; + std::string_view tag; + std::string_view data; + std::string_view text; + + //color tag + if (str.substr(0, 2) != "|c") + return {}; + + str.remove_prefix(2); + + if (str.length() < 8) + return {}; + + for (uint8 i = 0; i < 8; ++i) + { + if (uint8 hex = toHex(str[i])) + color = (color << 4) | (hex & 0xf); + else + return {}; + } + + str.remove_prefix(8); + + if (str.substr(0, 2) != "|H") + return {}; + + str.remove_prefix(2); + + // tag+data part follows + if (size_t delimPos = str.find('|'); delimPos != std::string_view::npos) + { + tag = str.substr(0, delimPos); + str.remove_prefix(delimPos+1); + } + else + return {}; + + // split tag if : is present (data separator) + if (size_t dataStart = tag.find(':'); dataStart != std::string_view::npos) + { + data = tag.substr(dataStart+1); + tag = tag.substr(0, dataStart); + } + + // ok, next should be link data end tag... + if (str.substr(0, 1) != "h") + return {}; + str.remove_prefix(1); + // skip to final | + if (size_t end = str.find('|'); end != std::string_view::npos) + { + // check end tag + if (str.substr(end, 4) != "|h|r") + return {}; + // check text brackets + if ((str[0] != '[') || (str[end - 1] != ']')) + return {}; + text = str.substr(1, end - 2); + // tail + str = str.substr(end + 4); + } + else + return {}; + + // ok, valid hyperlink, return info + return { str, color, tag, data, text }; +} + +template <typename T> +struct LinkValidator +{ + static bool IsTextValid(typename T::value_type, std::string_view) { return true; } + static bool IsColorValid(typename T::value_type, HyperlinkColor) { return true; } +}; + +template <> +struct LinkValidator<LinkTags::achievement> +{ + static bool IsTextValid(AchievementLinkData const& data, std::string_view text) + { + if (text.empty()) + return false; + + for (uint8 i = 0; i < TOTAL_LOCALES; ++i) + if (text == data.Achievement->name[i]) + return true; + + return false; + } + + static bool IsColorValid(AchievementLinkData const&, HyperlinkColor c) + { + return c == CHAT_LINK_COLOR_ACHIEVEMENT; + } +}; + +template <> +struct LinkValidator<LinkTags::item> +{ + static bool IsTextValid(ItemLinkData const& data, std::string_view text) + { + ItemLocale const* locale = sObjectMgr->GetItemLocale(data.Item->ItemId); + + std::array<char const*, 16> const* randomSuffixes = nullptr; + + if (data.RandomProperty) + randomSuffixes = &data.RandomProperty->Name; + else if (data.RandomSuffix) + randomSuffixes = &data.RandomSuffix->Name; + + if (data.IsBuggedInspectLink) /* DBC lookup will have failed on the client, so the link should've arrived without suffix */ + randomSuffixes = nullptr; + + for (uint8 i = 0; i < TOTAL_LOCALES; ++i) + { + if (!locale && i != DEFAULT_LOCALE) + continue; + + std::string_view name = (i == DEFAULT_LOCALE) ? data.Item->Name1 : ObjectMgr::GetLocaleString(locale->Name, i); + if (name.empty()) + continue; + + if (randomSuffixes) + { + std::string_view randomSuffix((*randomSuffixes)[i]); + if ((!randomSuffix.empty()) && + (text.length() == (name.length() + 1 + randomSuffix.length())) && + (text.substr(0, name.length()) == name) && + (text[name.length()] == ' ') && + (text.substr(name.length() + 1) == randomSuffix)) + return true; + } + else if (text == name) + return true; + } + return false; + } + + static bool IsColorValid(ItemLinkData const& data, HyperlinkColor c) + { + return c == ItemQualityColors[data.Item->Quality]; + } +}; + +template <> +struct LinkValidator<LinkTags::quest> +{ + static bool IsTextValid(QuestLinkData const& data, std::string_view text) + { + if (text.empty()) + return false; + + if (text == data.Quest->GetTitle()) + return true; + + QuestLocale const* locale = sObjectMgr->GetQuestLocale(data.Quest->GetQuestId()); + if (!locale) + return false; + + for (uint8 i = 0; i < TOTAL_LOCALES; ++i) + { + if (i == DEFAULT_LOCALE) + continue; + + std::string_view name = ObjectMgr::GetLocaleString(locale->Title, i); + if (!name.empty() && (text == name)) + return true; + } + + return false; + } + + static bool IsColorValid(QuestLinkData const&, HyperlinkColor c) + { + for (uint8 i = 0; i < MAX_QUEST_DIFFICULTY; ++i) + if (c == QuestDifficultyColors[i]) + return true; + + return false; + } +}; + +template <> +struct LinkValidator<LinkTags::spell> +{ + static bool IsTextValid(SpellInfo const* info, std::string_view text) + { + for (uint8 i = 0; i < TOTAL_LOCALES; ++i) + if (text == info->SpellName[i]) + return true; + return false; + } + + static bool IsColorValid(SpellInfo const*, HyperlinkColor c) + { + return c == CHAT_LINK_COLOR_SPELL; + } +}; + +template <> +struct LinkValidator<LinkTags::enchant> +{ + static bool IsTextValid(SpellInfo const* info, std::string_view text) + { + if (LinkValidator<LinkTags::spell>::IsTextValid(info, text)) + return true; + + SkillLineAbilityMapBounds bounds = sSpellMgr->GetSkillLineAbilityMapBounds(info->Id); + if (bounds.first == bounds.second) + return false; + + for (auto pair = bounds.first; pair != bounds.second; ++pair) + { + SkillLineEntry const* skill = sSkillLineStore.LookupEntry(pair->second->SkillLine); + if (!skill) + return false; + + for (uint8 i = 0; i < TOTAL_LOCALES; ++i) + { + std::string_view skillName = skill->name[i]; + std::string_view spellName = info->SpellName[i]; + // alternate form [Skill Name: Spell Name] + if ((text.length() == (skillName.length() + 2 + spellName.length())) && + (text.substr(0, skillName.length()) == skillName) && + (text.substr(skillName.length(), 2) == ": ") && + (text.substr(skillName.length() + 2) == spellName)) + return true; + } + } + return false; + } + + static bool IsColorValid(SpellInfo const*, HyperlinkColor c) + { + return c == CHAT_LINK_COLOR_ENCHANT; + } +}; + +template <> +struct LinkValidator<LinkTags::glyph> +{ + static bool IsTextValid(GlyphLinkData const& data, std::string_view text) + { + if (SpellInfo const* info = sSpellMgr->GetSpellInfo(data.Glyph->SpellId)) + return LinkValidator<LinkTags::spell>::IsTextValid(info, text); + + return false; + } + + static bool IsColorValid(GlyphLinkData const&, HyperlinkColor c) + { + return c == CHAT_LINK_COLOR_GLYPH; + } +}; + +template <> +struct LinkValidator<LinkTags::talent> +{ + static bool IsTextValid(TalentLinkData const& data, std::string_view text) + { + SpellInfo const* info = data.Spell; + if (!info) + info = sSpellMgr->GetSpellInfo(data.Talent->RankID[0]); + + if (!info) + return false; + + return LinkValidator<LinkTags::spell>::IsTextValid(info, text); + } + + static bool IsColorValid(TalentLinkData const&, HyperlinkColor c) + { + return c == CHAT_LINK_COLOR_TALENT; + } +}; + +template <> +struct LinkValidator<LinkTags::trade> +{ + static bool IsTextValid(TradeskillLinkData const& data, std::string_view text) + { + return LinkValidator<LinkTags::spell>::IsTextValid(data.Spell, text); + } + + static bool IsColorValid(TradeskillLinkData const&, HyperlinkColor c) + { + return c == CHAT_LINK_COLOR_TRADE; + } +}; + +template <typename TAG> +static bool ValidateAs(HyperlinkInfo const& info) +{ + std::decay_t<typename TAG::value_type> t; + if (!TAG::StoreTo(t, info.data)) + return false; + + int32 const severity = static_cast<int32>(sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_SEVERITY)); + if (severity >= 0) + { + if (!LinkValidator<TAG>::IsColorValid(t, info.color)) + return false; + + if (severity >= 1) + { + if (!LinkValidator<TAG>::IsTextValid(t, info.text)) + return false; + } + } + return true; +} + +#define TryValidateAs(T) do { if (info.tag == T::tag()) return ValidateAs<T>(info); } while (0); + +static bool ValidateLinkInfo(HyperlinkInfo const& info) +{ + using namespace LinkTags; + TryValidateAs(achievement); + TryValidateAs(area); + TryValidateAs(areatrigger); + TryValidateAs(creature); + TryValidateAs(creature_entry); + TryValidateAs(enchant); + TryValidateAs(gameevent); + TryValidateAs(gameobject); + TryValidateAs(gameobject_entry); + TryValidateAs(glyph); + TryValidateAs(item); + TryValidateAs(itemset); + TryValidateAs(player); + TryValidateAs(quest); + TryValidateAs(skill); + TryValidateAs(spell); + TryValidateAs(talent); + TryValidateAs(taxinode); + TryValidateAs(tele); + TryValidateAs(title); + TryValidateAs(trade); + return false; +} + +// Validates all hyperlinks and control sequences contained in str +bool Acore::Hyperlinks::CheckAllLinks(std::string_view str) +{ + // Step 1: Disallow all control sequences except ||, |H, |h, |c and |r + { + std::string_view::size_type pos = 0; + while ((pos = str.find('|', pos)) != std::string::npos) + { + ++pos; + if (pos == str.length()) + return false; + char next = str[pos]; + if (next == 'H' || next == 'h' || next == 'c' || next == 'r' || next == '|') + ++pos; + else + return false; + } + } + + // Step 2: Parse all link sequences + // They look like this: |c<color>|H<linktag>:<linkdata>|h[<linktext>]|h|r + // - <color> is 8 hex characters AARRGGBB + // - <linktag> is arbitrary length [a-z_] + // - <linkdata> is arbitrary length, no | contained + // - <linktext> is printable + { + std::string::size_type pos; + while ((pos = str.find('|')) != std::string::npos) + { + if (str[pos + 1] == '|') // this is an escaped pipe character (||) + { + str = str.substr(pos + 2); + continue; + } + + HyperlinkInfo info = ParseSingleHyperlink(str.substr(pos)); + if (!info || !ValidateLinkInfo(info)) + return false; + + // tag is fine, find the next one + str = info.tail; + } + } + + // all tags are valid + return true; +} diff --git a/src/server/game/Chat/Hyperlinks.h b/src/server/game/Chat/Hyperlinks.h new file mode 100644 index 0000000000..07c8f901ca --- /dev/null +++ b/src/server/game/Chat/Hyperlinks.h @@ -0,0 +1,255 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _HYPERLINKS_H_ +#define _HYPERLINKS_H_ + +#include "ObjectGuid.h" +#include "StringConvert.h" +#include <array> +#include <string> +#include <string_view> +#include <type_traits> +#include <utility> + +struct AchievementEntry; +struct GlyphPropertiesEntry; +struct GlyphSlotEntry; +struct ItemRandomPropertiesEntry; +struct ItemRandomSuffixEntry; +struct ItemTemplate; +class SpellInfo; +class Quest; +struct TalentEntry; + +namespace Acore::Hyperlinks +{ + + struct AchievementLinkData + { + AchievementEntry const* Achievement; + ObjectGuid CharacterId; + bool IsFinished; + uint8 Year; + uint8 Month; + uint8 Day; + std::array<uint32, 4> Criteria; + }; + + struct GlyphLinkData + { + GlyphPropertiesEntry const* Glyph; + GlyphSlotEntry const* Slot; + }; + + struct ItemLinkData + { + ItemTemplate const* Item; + uint32 EnchantId; + std::array<uint32, 3> GemEnchantId; + ItemRandomPropertiesEntry const* RandomProperty; + ItemRandomSuffixEntry const* RandomSuffix; + uint32 RandomSuffixBaseAmount; /* ITEM_FIELD_PROPERTY_SEED - only nonzero for RandomSuffix items, AllocationPct from DBC are multiplied with this, then floored, to get stat value */ + uint8 RenderLevel; + bool IsBuggedInspectLink; + }; + + struct QuestLinkData + { + ::Quest const* Quest; + int16 QuestLevel; + }; + + struct TalentLinkData + { + TalentEntry const* Talent; + uint8 Rank; + SpellInfo const* Spell; + }; + + struct TradeskillLinkData + { + SpellInfo const* Spell; + uint16 CurValue; + uint16 MaxValue; + ObjectGuid Owner; + std::string KnownRecipes; + }; + + namespace LinkTags { + + /************************** LINK TAGS ***************************************************\ + |* Link tags must abide by the following: *| + |* - MUST expose ::value_type typedef *| + |* - storage type is remove_cvref_t<value_type> *| + |* - MUST expose static ::tag method, void -> std::string_view *| + |* - this method SHOULD be constexpr *| + |* - returns identifier string for the link ("creature", "creature_entry", "item") *| + |* - MUST expose static ::StoreTo method, (storage&, std::string_view) *| + |* - assign storage& based on content of std::string_view *| + |* - return value indicates success/failure *| + |* - for integral/string types this can be achieved by extending base_tag *| + \****************************************************************************************/ + struct base_tag + { + static bool StoreTo(std::string_view& val, std::string_view data) + { + val = data; + return true; + } + + static bool StoreTo(std::string& val, std::string_view data) + { + val.assign(data); + return true; + } + + template <typename T> + static std::enable_if_t<std::is_integral_v<T>, bool> StoreTo(T& val, std::string_view data) + { + if (Optional<T> res = Acore::StringTo<T>(data)) + { + val = *res; + return true; + } + else + return false; + } + + static bool StoreTo(ObjectGuid& val, std::string_view data) + { + if (Optional<uint64> res = Acore::StringTo<uint64>(data, 16)) + { + val.Set(*res); + return true; + } + else + return false; + } + }; + + #define make_base_tag(ltag, type) struct ltag : public base_tag { using value_type = type; static constexpr std::string_view tag() { return #ltag; } } + make_base_tag(area, uint32); + make_base_tag(areatrigger, uint32); + make_base_tag(creature, ObjectGuid::LowType); + make_base_tag(creature_entry, uint32); + make_base_tag(gameevent, uint16); + make_base_tag(gameobject, ObjectGuid::LowType); + make_base_tag(gameobject_entry, uint32); + make_base_tag(itemset, uint32); + make_base_tag(player, std::string_view); + make_base_tag(skill, uint32); + make_base_tag(taxinode, uint32); + make_base_tag(tele, uint32); + make_base_tag(title, uint32); + #undef make_base_tag + + struct AC_GAME_API achievement + { + using value_type = AchievementLinkData const&; + static constexpr std::string_view tag() { return "achievement"; } + static bool StoreTo(AchievementLinkData& val, std::string_view data); + }; + + struct AC_GAME_API enchant + { + using value_type = SpellInfo const*; + static constexpr std::string_view tag() { return "enchant"; } + static bool StoreTo(SpellInfo const*& val, std::string_view data); + }; + + struct AC_GAME_API glyph + { + using value_type = GlyphLinkData const&; + static constexpr std::string_view tag() { return "glyph"; }; + static bool StoreTo(GlyphLinkData& val, std::string_view data); + }; + + struct AC_GAME_API item + { + using value_type = ItemLinkData const&; + static constexpr std::string_view tag() { return "item"; } + static bool StoreTo(ItemLinkData& val, std::string_view data); + }; + + struct AC_GAME_API quest + { + using value_type = QuestLinkData const&; + static constexpr std::string_view tag() { return "quest"; } + static bool StoreTo(QuestLinkData& val, std::string_view data); + }; + + struct AC_GAME_API spell + { + using value_type = SpellInfo const*; + static constexpr std::string_view tag() { return "spell"; } + static bool StoreTo(SpellInfo const*& val, std::string_view data); + }; + + struct AC_GAME_API talent + { + using value_type = TalentLinkData const&; + static constexpr std::string_view tag() { return "talent"; } + static bool StoreTo(TalentLinkData& val, std::string_view data); + }; + + struct AC_GAME_API trade + { + using value_type = TradeskillLinkData const&; + static constexpr std::string_view tag() { return "trade"; } + static bool StoreTo(TradeskillLinkData& val, std::string_view data); + }; + } + + struct HyperlinkColor + { + HyperlinkColor(uint32 c) : r(c >> 16), g(c >> 8), b(c), a(c >> 24) {} + uint8 const r, g, b, a; + bool operator==(uint32 c) const + { + if ((c & 0xff) ^ b) + return false; + if (((c >>= 8) & 0xff) ^ g) + return false; + if (((c >>= 8) & 0xff) ^ r) + return false; + if ((c >>= 8) ^ a) + return false; + return true; + } + }; + + struct HyperlinkInfo + { + HyperlinkInfo() : ok(false), color(0) {} + HyperlinkInfo(std::string_view t, uint32 c, std::string_view ta, std::string_view d, std::string_view te) : + ok(true), tail(t), color(c), tag(ta), data(d), text(te) {} + + explicit operator bool() { return ok; } + bool const ok; + std::string_view const tail; + HyperlinkColor const color; + std::string_view const tag; + std::string_view const data; + std::string_view const text; + }; + HyperlinkInfo AC_GAME_API ParseSingleHyperlink(std::string_view str); + bool AC_GAME_API CheckAllLinks(std::string_view str); + +} + +#endif diff --git a/src/server/game/DataStores/DBCStores.cpp b/src/server/game/DataStores/DBCStores.cpp index 9a42b884c1..fd4af3f559 100644 --- a/src/server/game/DataStores/DBCStores.cpp +++ b/src/server/game/DataStores/DBCStores.cpp @@ -903,7 +903,7 @@ SimpleFactionsList const* GetFactionTeamList(uint32 faction) return nullptr; } -char* GetPetName(uint32 petfamily, uint32 dbclang) +char const* GetPetName(uint32 petfamily, uint32 dbclang) { if (!petfamily) return nullptr; @@ -912,7 +912,7 @@ char* GetPetName(uint32 petfamily, uint32 dbclang) if (!pet_family) return nullptr; - return pet_family->Name[dbclang] ? pet_family->Name[dbclang] : nullptr; + return pet_family->Name[dbclang]; } TalentSpellPos const* GetTalentSpellPos(uint32 spellId) diff --git a/src/server/game/DataStores/DBCStores.h b/src/server/game/DataStores/DBCStores.h index f5dc07d736..2ff299c991 100644 --- a/src/server/game/DataStores/DBCStores.h +++ b/src/server/game/DataStores/DBCStores.h @@ -29,7 +29,7 @@ typedef std::vector<FlyByCamera> FlyByCameraCollection; SimpleFactionsList const* GetFactionTeamList(uint32 faction); -char* GetPetName(uint32 petfamily, uint32 dbclang); +char const* GetPetName(uint32 petfamily, uint32 dbclang); uint32 GetTalentSpellCost(uint32 spellId); TalentSpellPos const* GetTalentSpellPos(uint32 spellId); diff --git a/src/server/game/Entities/Item/Item.cpp b/src/server/game/Entities/Item/Item.cpp index 2191709574..cb27a9b2e2 100644 --- a/src/server/game/Entities/Item/Item.cpp +++ b/src/server/game/Entities/Item/Item.cpp @@ -644,7 +644,7 @@ void Item::SetItemRandomProperties(int32 randomPropId) SetState(ITEM_CHANGED, GetOwner()); } for (uint32 i = PROP_ENCHANTMENT_SLOT_0; i < MAX_ENCHANTMENT_SLOT; ++i) - SetEnchantment(EnchantmentSlot(i), item_rand->enchant_id[i - PROP_ENCHANTMENT_SLOT_0], 0, 0); + SetEnchantment(EnchantmentSlot(i), item_rand->Enchantment[i - PROP_ENCHANTMENT_SLOT_0], 0, 0); } } else @@ -661,7 +661,7 @@ void Item::SetItemRandomProperties(int32 randomPropId) } for (uint32 i = PROP_ENCHANTMENT_SLOT_0; i < MAX_ENCHANTMENT_SLOT; ++i) - SetEnchantment(EnchantmentSlot(i), item_rand->enchant_id[i - PROP_ENCHANTMENT_SLOT_0], 0, 0); + SetEnchantment(EnchantmentSlot(i), item_rand->Enchantment[i - PROP_ENCHANTMENT_SLOT_0], 0, 0); } } } diff --git a/src/server/game/Entities/Player/PlayerStorage.cpp b/src/server/game/Entities/Player/PlayerStorage.cpp index b2aa832898..28722b45fb 100644 --- a/src/server/game/Entities/Player/PlayerStorage.cpp +++ b/src/server/game/Entities/Player/PlayerStorage.cpp @@ -4420,9 +4420,9 @@ void Player::ApplyEnchantment(Item* item, EnchantmentSlot slot, bool apply, bool // Search enchant_amount for (int k = 0; k < MAX_ITEM_ENCHANTMENT_EFFECTS; ++k) { - if (item_rand->enchant_id[k] == enchant_id) + if (item_rand->Enchantment[k] == enchant_id) { - basepoints = int32((item_rand->prefix[k] * item->GetItemSuffixFactor()) / 10000); + basepoints = int32((item_rand->AllocationPct[k] * item->GetItemSuffixFactor()) / 10000); break; } } @@ -4446,9 +4446,9 @@ void Player::ApplyEnchantment(Item* item, EnchantmentSlot slot, bool apply, bool { for (int k = 0; k < MAX_ITEM_ENCHANTMENT_EFFECTS; ++k) { - if (item_rand->enchant_id[k] == enchant_id) + if (item_rand->Enchantment[k] == enchant_id) { - enchant_amount = uint32((item_rand->prefix[k] * item->GetItemSuffixFactor()) / 10000); + enchant_amount = uint32((item_rand->AllocationPct[k] * item->GetItemSuffixFactor()) / 10000); break; } } @@ -4466,9 +4466,9 @@ void Player::ApplyEnchantment(Item* item, EnchantmentSlot slot, bool apply, bool { for (int k = 0; k < MAX_ITEM_ENCHANTMENT_EFFECTS; ++k) { - if (item_rand_suffix->enchant_id[k] == enchant_id) + if (item_rand_suffix->Enchantment[k] == enchant_id) { - enchant_amount = uint32((item_rand_suffix->prefix[k] * item->GetItemSuffixFactor()) / 10000); + enchant_amount = uint32((item_rand_suffix->AllocationPct[k] * item->GetItemSuffixFactor()) / 10000); break; } } @@ -6721,7 +6721,7 @@ void Player::PrettyPrintRequirementsAchievementsList(const std::vector<const Pro continue; } - std::string name = *achievementEntry->name; + std::string name = achievementEntry->name[sObjectMgr->GetDBCLocaleIndex()]; std::stringstream stream; stream << "|cffff7c0a|Hachievement:"; diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index 5c0ee91ea1..c7d4a8a06e 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -6981,7 +6981,7 @@ std::string ObjectMgr::GeneratePetName(uint32 entry) if (list0.empty() || list1.empty()) { CreatureTemplate const* cinfo = GetCreatureTemplate(entry); - char* petname = GetPetName(cinfo->family, sWorld->GetDefaultDbcLocale()); + char const* petname = GetPetName(cinfo->family, sWorld->GetDefaultDbcLocale()); if (!petname) return cinfo->Name; @@ -8124,7 +8124,7 @@ void ObjectMgr::LoadGameTele() LOG_INFO("server.loading", " "); } -GameTele const* ObjectMgr::GetGameTele(const std::string& name) const +GameTele const* ObjectMgr::GetGameTele(std::string_view name) const { // explicit name case std::wstring wname; @@ -8180,7 +8180,7 @@ bool ObjectMgr::AddGameTele(GameTele& tele) return true; } -bool ObjectMgr::DeleteGameTele(const std::string& name) +bool ObjectMgr::DeleteGameTele(std::string_view name) { // explicit name case std::wstring wname; diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h index a807d2c84c..4fb01e6d0e 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -1287,10 +1287,10 @@ public: if (itr == _gameTeleStore.end()) return nullptr; return &itr->second; } - [[nodiscard]] GameTele const* GetGameTele(std::string const& name) const; + [[nodiscard]] GameTele const* GetGameTele(std::string_view name) const; [[nodiscard]] GameTeleContainer const& GetGameTeleMap() const { return _gameTeleStore; } bool AddGameTele(GameTele& data); - bool DeleteGameTele(std::string const& name); + bool DeleteGameTele(std::string_view name); [[nodiscard]] TrainerSpellData const* GetNpcTrainerSpells(uint32 entry) const { @@ -1344,6 +1344,13 @@ public: } static void AddLocaleString(std::string&& s, LocaleConstant locale, std::vector<std::string>& data); + static std::string_view GetLocaleString(std::vector<std::string> const& data, size_t locale) + { + if (locale < data.size()) + return data[locale]; + else + return {}; + } static inline void GetLocaleString(const std::vector<std::string>& data, int loc_idx, std::string& value) { if (data.size() > size_t(loc_idx) && !data[loc_idx].empty()) diff --git a/src/server/game/Handlers/ChannelHandler.cpp b/src/server/game/Handlers/ChannelHandler.cpp index bb6e1c0668..4e16b5fde4 100644 --- a/src/server/game/Handlers/ChannelHandler.cpp +++ b/src/server/game/Handlers/ChannelHandler.cpp @@ -46,10 +46,10 @@ void WorldSession::HandleJoinChannel(WorldPacket& recvPacket) if (isdigit(channelName[0])) return; - // pussywizard: restrict allowed characters in channel name to avoid |0 and possibly other exploits - //if (!ObjectMgr::IsValidChannelName(channelName)) - if (channelName.find("|") != std::string::npos || channelName.size() >= 100) + if (channelName.size() >= 100 || !DisallowHyperlinksAndMaybeKick(channelName)) + { return; + } if (ChannelMgr* cMgr = ChannelMgr::forTeam(GetPlayer()->GetTeamId())) { diff --git a/src/server/game/Handlers/ChatHandler.cpp b/src/server/game/Handlers/ChatHandler.cpp index d301b81d99..df7e5c6879 100644 --- a/src/server/game/Handlers/ChatHandler.cpp +++ b/src/server/game/Handlers/ChatHandler.cpp @@ -304,20 +304,6 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recvData) SendNotification(GetAcoreString(LANG_WAIT_BEFORE_SPEAKING), timeStr.c_str()); return; } - - if (lang != LANG_ADDON) - { - if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_SEVERITY) && !ChatHandler(this).isValidChatMessage(msg.c_str())) - { - //LOG_ERROR("network.opcode", "Player %s (%s) sent a chatmessage with an invalid link: %s", GetPlayer()->GetName().c_str(), - // GetPlayer()->GetGUID().ToString().c_str(), msg.c_str()); - - if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_KICK)) - KickPlayer("CONFIG_CHAT_STRICT_LINK_CHECKING_KICK"); - - return; - } - } } // do message validity checks @@ -352,29 +338,14 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recvData) auto end = std::unique(msg.begin(), msg.end(), [](char c1, char c2) { return (c1 == ' ') && (c2 == ' '); }); msg.erase(end, msg.end()); } - } - // exploit - size_t found1 = msg.find("|Hquest"); - if (found1 != std::string::npos) - { - size_t found2 = msg.find(":", found1 + 8); - size_t found3 = msg.find("|", found1 + 8); - if (found3 != std::string::npos) + // Validate hyperlinks + if (!ValidateHyperlinksAndMaybeKick(msg)) { - if (found2 == std::string::npos) - return; - if (found2 > found3) - return; + return; } } - // prevent crash player - if (msg.find("| |Hquest") != std::string::npos) - { - return; - } - sScriptMgr->OnBeforeSendChatMessage(_player, type, lang, msg); switch (type) diff --git a/src/server/game/Handlers/TicketHandler.cpp b/src/server/game/Handlers/TicketHandler.cpp index 17ebabb333..b7485443a8 100644 --- a/src/server/game/Handlers/TicketHandler.cpp +++ b/src/server/game/Handlers/TicketHandler.cpp @@ -61,6 +61,11 @@ void WorldSession::HandleGMTicketCreateOpcode(WorldPacket& recvData) recvData >> x >> y >> z; recvData >> message; + if (!ValidateHyperlinksAndMaybeKick(message)) + { + return; + } + recvData >> needResponse; recvData >> needMoreHelp; @@ -96,6 +101,11 @@ void WorldSession::HandleGMTicketCreateOpcode(WorldPacket& recvData) recvData.rfinish(); // Will still have compressed data in buffer. } + if (!chatLog.empty() && !ValidateHyperlinksAndMaybeKick(chatLog)) + { + return; + } + ticket = new GmTicket(GetPlayer()); ticket->SetPosition(mapId, x, y, z); ticket->SetMessage(message); @@ -122,6 +132,11 @@ void WorldSession::HandleGMTicketUpdateOpcode(WorldPacket& recv_data) std::string message; recv_data >> message; + if (!ValidateHyperlinksAndMaybeKick(message)) + { + return; + } + GMTicketResponse response = GMTICKET_RESPONSE_UPDATE_ERROR; if (GmTicket* ticket = sTicketMgr->GetTicketByPlayer(GetPlayer()->GetGUID())) { @@ -187,6 +202,7 @@ void WorldSession::HandleGMSurveySubmit(WorldPacket& recv_data) std::unordered_set<uint32> surveyIds; CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); + // sub_survey1, r1, comment1, sub_survey2, r2, comment2, sub_survey3, r3, comment3, sub_survey4, r4, comment4, sub_survey5, r5, comment5, sub_survey6, r6, comment6, sub_survey7, r7, comment7, sub_survey8, r8, comment8, sub_survey9, r9, comment9, sub_survey10, r10, comment10, for (uint8 i = 0; i < 10; i++) { @@ -200,6 +216,11 @@ void WorldSession::HandleGMSurveySubmit(WorldPacket& recv_data) std::string comment; // comment ("Usage: GMSurveyAnswerSubmit(question, rank, comment)") recv_data >> comment; + if (!ValidateHyperlinksAndMaybeKick(comment)) + { + return; + } + // make sure the same sub survey is not added to DB twice if (!surveyIds.insert(subSurveyId).second) continue; @@ -215,6 +236,11 @@ void WorldSession::HandleGMSurveySubmit(WorldPacket& recv_data) std::string comment; // just a guess recv_data >> comment; + if (!ValidateHyperlinksAndMaybeKick(comment)) + { + return; + } + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_GM_SURVEY); stmt->setUInt32(0, GetPlayer()->GetGUID().GetCounter()); stmt->setUInt32(1, nextSurveyID); diff --git a/src/server/game/Miscellaneous/Language.h b/src/server/game/Miscellaneous/Language.h index a344bac872..17e7b115bb 100644 --- a/src/server/game/Miscellaneous/Language.h +++ b/src/server/game/Miscellaneous/Language.h @@ -35,10 +35,10 @@ enum AcoreStrings LANG_SYSTEMMESSAGE = 3, LANG_EVENTMESSAGE = 4, LANG_NO_HELP_CMD = 5, - LANG_NO_CMD = 6, - LANG_NO_SUBCMD = 7, + LANG_CMD_INVALID = 6, + LANG_SUBCMD_AMBIGUOUS = 7, LANG_SUBCMDS_LIST = 8, - LANG_AVIABLE_CMD = 9, + LANG_AVAILABLE_CMDS = 9, LANG_CMD_SYNTAX = 10, LANG_ACCOUNT_LEVEL = 11, LANG_CONNECTED_USERS = 12, @@ -222,7 +222,13 @@ enum AcoreStrings LANG_2FA_SECRET_TOO_LONG = 188, LANG_2FA_SECRET_INVALID = 189, LANG_2FA_SECRET_SET_COMPLETE = 190, - // free 191 - 199 + LANG_SUBCMDS_LIST_ENTRY = 191, + LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS = 192, + LANG_SUBCMD_INVALID = 193, + LANG_CMD_AMBIGUOUS = 194, + LANG_CMD_HELP_GENERIC = 195, + LANG_CMD_NO_HELP_AVAILABLE = 196, + // Room for more level 1 197-199 not used // level 2 chat LANG_NO_SELECTION = 200, @@ -1089,7 +1095,26 @@ enum AcoreStrings //Player Ticket Strings LANG_TICKET_CLOSED = 1334, LANG_TICKET_COMPLETED = 1335, - // FREE IDS 1336-1999 + // FREE IDS 1336-1499 + + // Command argument parsers + LANG_CMDPARSER_EITHER = 1500, + LANG_CMDPARSER_OR = 1501, + LANG_CMDPARSER_STRING_VALUE_INVALID = 1502, + LANG_CMDPARSER_INVALID_UTF8 = 1503, + LANG_CMDPARSER_LINKDATA_INVALID = 1504, + LANG_CMDPARSER_ACCOUNT_NAME_NO_EXIST = 1505, + LANG_CMDPARSER_ACCOUNT_ID_NO_EXIST = 1506, + LANG_CMDPARSER_CHAR_GUID_NO_EXIST = 1507, + LANG_CMDPARSER_CHAR_NAME_NO_EXIST = 1508, + LANG_CMDPARSER_CHAR_NAME_INVALID = 1509, + LANG_CMDPARSER_ACHIEVEMENT_NO_EXIST = 1510, + LANG_CMDPARSER_GAME_TELE_ID_NO_EXIST = 1511, + LANG_CMDPARSER_GAME_TELE_NO_EXIST = 1512, + LANG_CMDPARSER_ITEM_NO_EXIST = 1513, + LANG_CMDPARSER_SPELL_NO_EXIST = 1514, + LANG_CMDPARSER_EXACT_SEQ_MISMATCH = 1515, + // FREE IDS 1516-1499 // Ticket Strings 2000-2030 LANG_COMMAND_TICKETNEW = 2000, diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index 113229b1c2..e74b2b51e9 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -1056,22 +1056,16 @@ OutdoorPvP* ScriptMgr::CreateOutdoorPvP(OutdoorPvPData const* data) return tmpscript->GetOutdoorPvP(); } -std::vector<ChatCommand> ScriptMgr::GetChatCommands() +Acore::ChatCommands::ChatCommandTable ScriptMgr::GetChatCommands() { - std::vector<ChatCommand> table; + Acore::ChatCommands::ChatCommandTable table; - FOR_SCRIPTS_RET(CommandScript, itr, end, table) + FOR_SCRIPTS(CommandScript, itr, end) { - std::vector<ChatCommand> cmds = itr->second->GetCommands(); - table.insert(table.end(), cmds.begin(), cmds.end()); + Acore::ChatCommands::ChatCommandTable cmds = itr->second->GetCommands(); + std::move(cmds.begin(), cmds.end(), std::back_inserter(table)); } - // Sort commands in alphabetical order - std::sort(table.begin(), table.end(), [](const ChatCommand & a, const ChatCommand & b) - { - return strcmp(a.Name, b.Name) < 0; - }); - return table; } diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index 532ff0a75c..0b5cae72b6 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -41,7 +41,6 @@ class Battleground; class BattlegroundMap; class BattlegroundQueue; class Channel; -class ChatCommand; class Creature; class CreatureAI; class DynamicObject; @@ -81,6 +80,11 @@ struct OutdoorPvPData; struct GroupQueueInfo; struct TargetInfo; +namespace Acore::ChatCommands +{ + struct ChatCommandBuilder; +} + #define VISIBLE_RANGE 166.0f //MAX visible range (size of grid) // Check out our guide on how to create new hooks in our wiki! https://www.azerothcore.org/wiki/hooks-script @@ -579,7 +583,7 @@ protected: public: // Should return a pointer to a valid command table (ChatCommand array) to be used by ChatHandler. - [[nodiscard]] virtual std::vector<ChatCommand> GetCommands() const = 0; + [[nodiscard]] virtual std::vector<Acore::ChatCommands::ChatCommandBuilder> GetCommands() const = 0; }; class WeatherScript : public ScriptObject, public UpdatableScript<Weather> @@ -1566,7 +1570,7 @@ public: /* OutdoorPvPScript */ OutdoorPvP* CreateOutdoorPvP(OutdoorPvPData const* data); public: /* CommandScript */ - std::vector<ChatCommand> GetChatCommands(); + std::vector<Acore::ChatCommands::ChatCommandBuilder> GetChatCommands(); public: /* WeatherScript */ void OnWeatherChange(Weather* weather, WeatherState state, float grade); diff --git a/src/server/game/Server/Packets/PacketUtilities.cpp b/src/server/game/Server/Packets/PacketUtilities.cpp index 91d1d5f27f..ba1753e47a 100644 --- a/src/server/game/Server/Packets/PacketUtilities.cpp +++ b/src/server/game/Server/Packets/PacketUtilities.cpp @@ -16,7 +16,7 @@ */ #include "PacketUtilities.h" -//#include "Hyperlinks.h" +#include "Hyperlinks.h" #include "Errors.h" #include <utf8.h> #include <sstream> @@ -41,13 +41,13 @@ bool WorldPackets::Strings::Utf8::Validate(std::string const& value) return true; } -//bool WorldPackets::Strings::Hyperlinks::Validate(std::string const& value) -//{ -// if (!Acore::Hyperlinks::CheckAllLinks(value)) -// throw InvalidHyperlinkException(value); -// -// return true; -//} +bool WorldPackets::Strings::Hyperlinks::Validate(std::string const& value) +{ + if (!Acore::Hyperlinks::CheckAllLinks(value)) + throw InvalidHyperlinkException(value); + + return true; +} bool WorldPackets::Strings::NoHyperlinks::Validate(std::string const& value) { diff --git a/src/server/game/Server/Packets/PacketUtilities.h b/src/server/game/Server/Packets/PacketUtilities.h index bc44247f18..bb00547f5b 100644 --- a/src/server/game/Server/Packets/PacketUtilities.h +++ b/src/server/game/Server/Packets/PacketUtilities.h @@ -59,7 +59,7 @@ namespace WorldPackets template<std::size_t MaxBytesWithoutNullTerminator> struct ByteSize { static bool Validate(std::string const& value) { return value.size() <= MaxBytesWithoutNullTerminator; } }; struct Utf8 { static bool Validate(std::string const& value); }; - //struct Hyperlinks { static bool Validate(std::string const& value); }; + struct Hyperlinks { static bool Validate(std::string const& value); }; struct NoHyperlinks { static bool Validate(std::string const& value); }; } diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index cf304e15e7..0cc33d3308 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -27,6 +27,7 @@ #include "Group.h" #include "Guild.h" #include "GuildMgr.h" +#include "Hyperlinks.h" #include "Log.h" #include "MapMgr.h" #include "ObjectAccessor.h" @@ -389,6 +390,26 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) break; } } + catch (WorldPackets::InvalidHyperlinkException const& ihe) + { + LOG_ERROR("network", "%s sent %s with an invalid link:\n%s", GetPlayerInfo().c_str(), + GetOpcodeNameForLogging(static_cast<OpcodeClient>(packet->GetOpcode())).c_str(), ihe.GetInvalidValue().c_str()); + + if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_KICK)) + { + KickPlayer("WorldSession::Update Invalid chat link"); + } + } + catch (WorldPackets::IllegalHyperlinkException const& ihe) + { + LOG_ERROR("network", "%s sent %s which illegally contained a hyperlink:\n%s", GetPlayerInfo().c_str(), + GetOpcodeNameForLogging(static_cast<OpcodeClient>(packet->GetOpcode())).c_str(), ihe.GetInvalidValue().c_str()); + + if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_KICK)) + { + KickPlayer("WorldSession::Update Illegal chat link"); + } + } catch (WorldPackets::PacketArrayMaxCapacityException const& pamce) { LOG_ERROR("network", "PacketArrayMaxCapacityException: %s while parsing %s from %s.", @@ -698,6 +719,34 @@ void WorldSession::KickPlayer(std::string const& reason, bool setKicked) SetKicked(true); // pussywizard: the session won't be left ingame for 60 seconds and to also kick offline session } +bool WorldSession::ValidateHyperlinksAndMaybeKick(std::string_view str) +{ + if (Acore::Hyperlinks::CheckAllLinks(str)) + return true; + + LOG_ERROR("network", "Player %s%s sent a message with an invalid link:\n%.*s", GetPlayer()->GetName().c_str(), + GetPlayer()->GetGUID().ToString().c_str(), STRING_VIEW_FMT_ARG(str)); + + if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_KICK)) + KickPlayer("WorldSession::ValidateHyperlinksAndMaybeKick Invalid chat link"); + + return false; +} + +bool WorldSession::DisallowHyperlinksAndMaybeKick(std::string_view str) +{ + if (str.find('|') == std::string_view::npos) + return true; + + LOG_ERROR("network", "Player %s %s sent a message which illegally contained a hyperlink:\n%.*s", GetPlayer()->GetName().c_str(), + GetPlayer()->GetGUID().ToString().c_str(), STRING_VIEW_FMT_ARG(str)); + + if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_KICK)) + KickPlayer("WorldSession::DisallowHyperlinksAndMaybeKick Illegal chat link"); + + return false; +} + void WorldSession::SendNotification(const char* format, ...) { if (format) diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index b28ae8b6b2..c955b5eb1d 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -309,6 +309,13 @@ public: void KickPlayer(bool setKicked = true) { return this->KickPlayer("Unknown reason", setKicked); } void KickPlayer(std::string const& reason, bool setKicked = true); + // Returns true if all contained hyperlinks are valid + // May kick player on false depending on world config (handler should abort) + bool ValidateHyperlinksAndMaybeKick(std::string_view str); + // Returns true if the message contains no hyperlinks + // May kick player on false depending on world config (handler should abort) + bool DisallowHyperlinksAndMaybeKick(std::string_view str); + void QueuePacket(WorldPacket* new_packet); bool Update(uint32 diff, PacketFilter& updater); diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp index 992d2fb66c..6c169bc4dc 100644 --- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp +++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp @@ -466,13 +466,13 @@ int32 AuraEffect::CalculateAmount(Unit* caster) { for (uint8 k = 0; k < MAX_ITEM_ENCHANTMENT_EFFECTS; k++) { - SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(item_rand_suffix->enchant_id[k]); + SpellItemEnchantmentEntry const* pEnchant = sSpellItemEnchantmentStore.LookupEntry(item_rand_suffix->Enchantment[k]); if (pEnchant) { for (uint8 t = 0; t < MAX_SPELL_ITEM_ENCHANTMENT_EFFECTS; t++) if (pEnchant->spellid[t] == m_spellInfo->Id) { - amount = uint32((item_rand_suffix->prefix[k] * castItem->GetItemSuffixFactor()) / 10000); + amount = uint32((item_rand_suffix->AllocationPct[k] * castItem->GetItemSuffixFactor()) / 10000); break; } } diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 67780c75fa..086c52c3c3 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -936,13 +936,18 @@ void Spell::SelectEffectImplicitTargets(SpellEffIndex effIndex, SpellImplicitTar case TARGET_SELECT_CATEGORY_NEARBY: case TARGET_SELECT_CATEGORY_CONE: case TARGET_SELECT_CATEGORY_AREA: + { // targets for effect already selected if (effectMask & processedEffectMask) + { return; + } + + auto const& effects = GetSpellInfo()->Effects; + // choose which targets we can select at once for (uint32 j = effIndex + 1; j < MAX_SPELL_EFFECTS; ++j) { - SpellEffectInfo const* effects = GetSpellInfo()->Effects; if (effects[j].IsEffect() && effects[effIndex].TargetA.GetTarget() == effects[j].TargetA.GetTarget() && effects[effIndex].TargetB.GetTarget() == effects[j].TargetB.GetTarget() && @@ -955,6 +960,7 @@ void Spell::SelectEffectImplicitTargets(SpellEffIndex effIndex, SpellImplicitTar } processedEffectMask |= effectMask; break; + } default: break; } diff --git a/src/server/game/Spells/SpellInfo.cpp b/src/server/game/Spells/SpellInfo.cpp index 7cd2c5a029..ea68396fd7 100644 --- a/src/server/game/Spells/SpellInfo.cpp +++ b/src/server/game/Spells/SpellInfo.cpp @@ -207,8 +207,8 @@ uint32 SpellImplicitTargetInfo::GetExplicitTargetMask(bool& srcSet, bool& dstSet return targetMask; } -SpellImplicitTargetInfo::StaticData SpellImplicitTargetInfo::_data[TOTAL_SPELL_TARGETS] = -{ +std::array<SpellImplicitTargetInfo::StaticData, TOTAL_SPELL_TARGETS> SpellImplicitTargetInfo::_data = +{ { {TARGET_OBJECT_TYPE_NONE, TARGET_REFERENCE_TYPE_NONE, TARGET_SELECT_CATEGORY_NYI, TARGET_CHECK_DEFAULT, TARGET_DIR_NONE}, // {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_DEFAULT, TARGET_CHECK_DEFAULT, TARGET_DIR_NONE}, // 1 TARGET_UNIT_CASTER {TARGET_OBJECT_TYPE_UNIT, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_NEARBY, TARGET_CHECK_ENEMY, TARGET_DIR_NONE}, // 2 TARGET_UNIT_NEARBY_ENEMY @@ -320,7 +320,7 @@ SpellImplicitTargetInfo::StaticData SpellImplicitTargetInfo::_data[TOTAL_SPELL_ {TARGET_OBJECT_TYPE_GOBJ, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_CONE, TARGET_CHECK_DEFAULT, TARGET_DIR_FRONT}, // 108 TARGET_GAMEOBJECT_CONE {TARGET_OBJECT_TYPE_NONE, TARGET_REFERENCE_TYPE_NONE, TARGET_SELECT_CATEGORY_NYI, TARGET_CHECK_DEFAULT, TARGET_DIR_NONE}, // 109 {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_NONE, TARGET_SELECT_CATEGORY_NYI, TARGET_CHECK_DEFAULT, TARGET_DIR_NONE}, // 110 TARGET_DEST_UNK_110 -}; +} }; SpellEffectInfo::SpellEffectInfo(SpellEntry const* spellEntry, SpellInfo const* spellInfo, uint8 effIndex) { @@ -598,8 +598,8 @@ SpellTargetObjectTypes SpellEffectInfo::GetUsedTargetObjectType() const return _data[Effect].UsedTargetObjectType; } -SpellEffectInfo::StaticData SpellEffectInfo::_data[TOTAL_SPELL_EFFECTS] = -{ +std::array<SpellEffectInfo::StaticData, TOTAL_SPELL_EFFECTS> SpellEffectInfo::_data = +{ { // implicit target type used target object type {EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 0 {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 1 SPELL_EFFECT_INSTAKILL @@ -766,7 +766,7 @@ SpellEffectInfo::StaticData SpellEffectInfo::_data[TOTAL_SPELL_EFFECTS] = {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 162 SPELL_EFFECT_TALENT_SPEC_SELECT {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 163 SPELL_EFFECT_163 {EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 164 SPELL_EFFECT_REMOVE_AURA -}; +} }; SpellInfo::SpellInfo(SpellEntry const* spellEntry) { @@ -822,25 +822,18 @@ SpellInfo::SpellInfo(SpellEntry const* spellEntry) RangeEntry = spellEntry->RangeIndex ? sSpellRangeStore.LookupEntry(spellEntry->RangeIndex) : nullptr; Speed = spellEntry->Speed; StackAmount = spellEntry->StackAmount; - for (uint8 i = 0; i < 2; ++i) - Totem[i] = spellEntry->Totem[i]; - for (uint8 i = 0; i < MAX_SPELL_REAGENTS; ++i) - Reagent[i] = spellEntry->Reagent[i]; - for (uint8 i = 0; i < MAX_SPELL_REAGENTS; ++i) - ReagentCount[i] = spellEntry->ReagentCount[i]; + Totem = spellEntry->Totem; + Reagent = spellEntry->Reagent; + ReagentCount = spellEntry->ReagentCount; EquippedItemClass = spellEntry->EquippedItemClass; EquippedItemSubClassMask = spellEntry->EquippedItemSubClassMask; EquippedItemInventoryTypeMask = spellEntry->EquippedItemInventoryTypeMask; - for (uint8 i = 0; i < 2; ++i) - TotemCategory[i] = spellEntry->TotemCategory[i]; - for (uint8 i = 0; i < 2; ++i) - SpellVisual[i] = spellEntry->SpellVisual[i]; + TotemCategory = spellEntry->TotemCategory; + SpellVisual = spellEntry->SpellVisual; SpellIconID = spellEntry->SpellIconID; ActiveIconID = spellEntry->ActiveIconID; - for (uint8 i = 0; i < 16; ++i) - SpellName[i] = spellEntry->SpellName[i]; - for (uint8 i = 0; i < 16; ++i) - Rank[i] = spellEntry->Rank[i]; + SpellName = spellEntry->SpellName; + Rank = spellEntry->Rank; MaxTargetLevel = spellEntry->MaxTargetLevel; MaxAffectedTargets = spellEntry->MaxAffectedTargets; SpellFamilyName = spellEntry->SpellFamilyName; diff --git a/src/server/game/Spells/SpellInfo.h b/src/server/game/Spells/SpellInfo.h index 6ce42ef52c..b0d9840813 100644 --- a/src/server/game/Spells/SpellInfo.h +++ b/src/server/game/Spells/SpellInfo.h @@ -236,7 +236,8 @@ private: SpellTargetCheckTypes SelectionCheckType; // defines selection criteria SpellTargetDirectionTypes DirectionType; // direction for cone and dest targets }; - static StaticData _data[TOTAL_SPELL_TARGETS]; + + static std::array<StaticData, TOTAL_SPELL_TARGETS> _data; }; class SpellEffectInfo @@ -302,7 +303,8 @@ private: SpellEffectImplicitTargetTypes ImplicitTargetType; // defines what target can be added to effect target list if there's no valid target type provided for effect SpellTargetObjectTypes UsedTargetObjectType; // defines valid target object type for spell effect }; - static StaticData _data[TOTAL_SPELL_EFFECTS]; + + static std::array<StaticData, TOTAL_SPELL_EFFECTS> _data; }; class SpellInfo @@ -360,18 +362,18 @@ public: SpellRangeEntry const* RangeEntry; float Speed; uint32 StackAmount; - uint32 Totem[2]; - int32 Reagent[MAX_SPELL_REAGENTS]; - uint32 ReagentCount[MAX_SPELL_REAGENTS]; + std::array<uint32, 2> Totem; + std::array<int32, MAX_SPELL_REAGENTS> Reagent; + std::array<uint32, MAX_SPELL_REAGENTS> ReagentCount; int32 EquippedItemClass; int32 EquippedItemSubClassMask; int32 EquippedItemInventoryTypeMask; - uint32 TotemCategory[2]; - uint32 SpellVisual[2]; + std::array<uint32, 2> TotemCategory; + std::array<uint32, 2> SpellVisual; uint32 SpellIconID; uint32 ActiveIconID; - char* SpellName[16]; - char* Rank[16]; + std::array<char const*, 16> SpellName; + std::array<char const*, 16> Rank; uint32 MaxTargetLevel; uint32 MaxAffectedTargets; uint32 SpellFamilyName; @@ -380,7 +382,7 @@ public: uint32 PreventionType; int32 AreaGroupId; uint32 SchoolMask; - SpellEffectInfo Effects[MAX_SPELL_EFFECTS]; + std::array<SpellEffectInfo, MAX_SPELL_EFFECTS> Effects; uint32 ExplicitTargetMask; SpellChainNode const* ChainEntry; diff --git a/src/server/game/World/IWorld.h b/src/server/game/World/IWorld.h index 97c820e7bf..45649fe673 100644 --- a/src/server/game/World/IWorld.h +++ b/src/server/game/World/IWorld.h @@ -35,28 +35,22 @@ class WorldSession; class Player; /// Storage class for commands issued for delayed execution -struct CliCommandHolder +struct AC_GAME_API CliCommandHolder { - typedef void Print(void*, const char*); - typedef void CommandFinished(void*, bool success); + using Print = void(*)(void*, std::string_view); + using CommandFinished = void(*)(void*, bool success); void* m_callbackArg; char* m_command; - Print* m_print; + Print m_print; + CommandFinished m_commandFinished; - CommandFinished* m_commandFinished; + CliCommandHolder(void* callbackArg, char const* command, Print zprint, CommandFinished commandFinished); + ~CliCommandHolder(); - CliCommandHolder(void* callbackArg, const char* command, Print* zprint, CommandFinished* commandFinished) - : m_callbackArg(callbackArg), m_print(zprint), m_commandFinished(commandFinished) - { - // TODO: fix Codacy warning - // "Does not handle strings that are not \0-terminated; if given one it may perform an over-read (it could cause a crash if unprotected) (CWE-126)." - size_t len = strlen(command) + 1; - m_command = new char[len]; - memcpy(m_command, command, len); - } - - ~CliCommandHolder() { delete[] m_command; } +private: + CliCommandHolder(CliCommandHolder const& right) = delete; + CliCommandHolder& operator=(CliCommandHolder const& right) = delete; }; typedef std::unordered_map<uint32, WorldSession*> SessionMap; diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 7f73365f6d..af9bdaf151 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -1944,6 +1944,9 @@ void World::SetInitialWorldSettings() LOG_INFO("server.loading", " "); sObjectMgr->InitializeSpellInfoPrecomputedData(); + LOG_INFO("server.loading", "Initialize commands..."); + Acore::ChatCommands::LoadCommandMap(); + ///- Initialize game time and timers LOG_INFO("server.loading", "Initialize game time and timers"); LOG_INFO("server.loading", " "); @@ -2760,7 +2763,7 @@ void World::UpdateSessions(uint32 diff) // This handles the issued and queued CLI commands void World::ProcessCliCommands() { - CliCommandHolder::Print* zprint = nullptr; + CliCommandHolder::Print zprint = nullptr; void* callbackArg = nullptr; CliCommandHolder* command = nullptr; while (cliCmdQueue.next(command)) @@ -3505,3 +3508,13 @@ void World::FinalizePlayerWorldSession(WorldSession* session) session->SendClientCacheVersion(cacheVersion); session->SendTutorialsData(); } + +CliCommandHolder::CliCommandHolder(void* callbackArg, char const* command, Print zprint, CommandFinished commandFinished) + : m_callbackArg(callbackArg), m_command(strdup(command)), m_print(zprint), m_commandFinished(commandFinished) +{ +} + +CliCommandHolder::~CliCommandHolder() +{ + free(m_command); +} diff --git a/src/server/scripts/Commands/cs_account.cpp b/src/server/scripts/Commands/cs_account.cpp index 2b9dab60f7..cde632cb48 100644 --- a/src/server/scripts/Commands/cs_account.cpp +++ b/src/server/scripts/Commands/cs_account.cpp @@ -38,14 +38,20 @@ EndScriptData */ #include <openssl/rand.h> #include <unordered_map> +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class account_commandscript : public CommandScript { public: account_commandscript() : CommandScript("account_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> accountSetCommandTable = + static ChatCommandTable accountSetCommandTable = { { "addon", SEC_GAMEMASTER, true, &HandleAccountSetAddonCommand, "" }, { "gmlevel", SEC_CONSOLE, true, &HandleAccountSetGmLevelCommand, "" }, @@ -53,24 +59,24 @@ public: { "2fa", SEC_PLAYER, true, &HandleAccountSet2FACommand, "" } }; - static std::vector<ChatCommand> accountLockCommandTable + static ChatCommandTable accountLockCommandTable { { "country", SEC_PLAYER, true, &HandleAccountLockCountryCommand, "" }, { "ip", SEC_PLAYER, true, &HandleAccountLockIpCommand, "" } }; - static std::vector<ChatCommand> account2faCommandTable + static ChatCommandTable account2faCommandTable { { "setup", SEC_PLAYER, false, &HandleAccount2FASetupCommand, "" }, { "remove", SEC_PLAYER, false, &HandleAccount2FARemoveCommand, "" }, }; - static std::vector<ChatCommand> accountRemoveCommandTable + static ChatCommandTable accountRemoveCommandTable { { "country", SEC_ADMINISTRATOR, true, &HandleAccountRemoveLockCountryCommand, "" } }; - static std::vector<ChatCommand> accountCommandTable = + static ChatCommandTable accountCommandTable = { { "2fa", SEC_PLAYER, true, nullptr, "", account2faCommandTable }, { "addon", SEC_MODERATOR, false, &HandleAccountAddonCommand, "" }, @@ -84,7 +90,7 @@ public: { "", SEC_PLAYER, false, &HandleAccountCommand, "" } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "account", SEC_PLAYER, true, nullptr, "", accountCommandTable } }; diff --git a/src/server/scripts/Commands/cs_achievement.cpp b/src/server/scripts/Commands/cs_achievement.cpp index 81b253a3ea..e2dcd36c93 100644 --- a/src/server/scripts/Commands/cs_achievement.cpp +++ b/src/server/scripts/Commands/cs_achievement.cpp @@ -27,19 +27,25 @@ EndScriptData */ #include "Player.h" #include "ScriptMgr.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class achievement_commandscript : public CommandScript { public: achievement_commandscript() : CommandScript("achievement_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> achievementCommandTable = + static ChatCommandTable achievementCommandTable = { { "add", SEC_GAMEMASTER, false, &HandleAchievementAddCommand, "" }, { "checkall", SEC_ADMINISTRATOR, false, &HandleAchievementCheckAllCommand, "" } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "achievement", SEC_GAMEMASTER, false, nullptr, "", achievementCommandTable } }; diff --git a/src/server/scripts/Commands/cs_arena.cpp b/src/server/scripts/Commands/cs_arena.cpp index 95fb28aa26..b24326695e 100644 --- a/src/server/scripts/Commands/cs_arena.cpp +++ b/src/server/scripts/Commands/cs_arena.cpp @@ -29,99 +29,73 @@ EndScriptData */ #include "Player.h" #include "ScriptMgr.h" +using namespace Acore::ChatCommands; + class arena_commandscript : public CommandScript { public: arena_commandscript() : CommandScript("arena_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> arenaCommandTable = - { - { "create", SEC_ADMINISTRATOR, true, &HandleArenaCreateCommand, "" }, - { "disband", SEC_ADMINISTRATOR, true, &HandleArenaDisbandCommand, "" }, - { "rename", SEC_ADMINISTRATOR, true, &HandleArenaRenameCommand, "" }, - { "captain", SEC_ADMINISTRATOR, false, &HandleArenaCaptainCommand, "" }, - { "info", SEC_GAMEMASTER, true, &HandleArenaInfoCommand, "" }, - { "lookup", SEC_GAMEMASTER, false, &HandleArenaLookupCommand, "" }, + static ChatCommandTable arenaCommandTable = + { + { "create", HandleArenaCreateCommand, SEC_ADMINISTRATOR, Console::Yes }, + { "disband", HandleArenaDisbandCommand, SEC_ADMINISTRATOR, Console::Yes }, + { "rename", HandleArenaRenameCommand, SEC_ADMINISTRATOR, Console::Yes }, + { "captain", HandleArenaCaptainCommand, SEC_ADMINISTRATOR, Console::No }, + { "info", HandleArenaInfoCommand, SEC_GAMEMASTER, Console::Yes }, + { "lookup", HandleArenaLookupCommand, SEC_GAMEMASTER, Console::No }, }; - static std::vector<ChatCommand> commandTable = + + static ChatCommandTable commandTable = { - { "arena", SEC_GAMEMASTER, false, nullptr, "", arenaCommandTable }, + { "arena", arenaCommandTable } }; + return commandTable; } - static bool HandleArenaCreateCommand(ChatHandler* handler, char const* args) + static bool HandleArenaCreateCommand(ChatHandler* handler, Optional<PlayerIdentifier> captain, QuotedString name, ArenaTeamTypes type) { - if (!*args) - return false; - - Player* target; - if (!handler->extractPlayerTarget(*args != '"' ? (char*)args : nullptr, &target)) - return false; - - char* tailStr = *args != '"' ? strtok(nullptr, "") : (char*)args; - if (!tailStr) + if (sArenaTeamMgr->GetArenaTeamByName(name)) + { + handler->PSendSysMessage(LANG_ARENA_ERROR_NAME_EXISTS, name.c_str()); + handler->SetSentErrorMessage(true); return false; + } - char* name = handler->extractQuotedArg(tailStr); - if (!name) - return false; + if (!captain) + captain = PlayerIdentifier::FromTargetOrSelf(handler); - char* typeStr = strtok(nullptr, ""); - if (!typeStr) + if (!captain) return false; - int8 type = atoi(typeStr); - if (sArenaTeamMgr->GetArenaTeamByName(name)) + if (Player::GetArenaTeamIdFromDB(captain->GetGUID(), type) != 0) { - handler->PSendSysMessage(LANG_ARENA_ERROR_NAME_EXISTS, name); + handler->PSendSysMessage(LANG_ARENA_ERROR_SIZE, captain->GetName().c_str()); handler->SetSentErrorMessage(true); return false; } - if (type == 2 || type == 3 || type == 5 ) - { - if (Player::GetArenaTeamIdFromDB(target->GetGUID(), type) != 0) - { - handler->PSendSysMessage(LANG_ARENA_ERROR_SIZE, target->GetName().c_str()); - handler->SetSentErrorMessage(true); - return false; - } + ArenaTeam* arena = new ArenaTeam(); - ArenaTeam* arena = new ArenaTeam(); - - if (!arena->Create(target->GetGUID(), type, name, 4293102085, 101, 4293253939, 4, 4284049911)) - { - delete arena; - handler->SendSysMessage(LANG_BAD_VALUE); - handler->SetSentErrorMessage(true); - return false; - } - - sArenaTeamMgr->AddArenaTeam(arena); - handler->PSendSysMessage(LANG_ARENA_CREATE, arena->GetName().c_str(), arena->GetId(), arena->GetType(), arena->GetCaptain()); - } - else + if (!arena->Create(captain->GetGUID(), type, name, 4293102085, 101, 4293253939, 4, 4284049911)) { + delete arena; handler->SendSysMessage(LANG_BAD_VALUE); handler->SetSentErrorMessage(true); return false; } + sArenaTeamMgr->AddArenaTeam(arena); + handler->PSendSysMessage(LANG_ARENA_CREATE, arena->GetName().c_str(), arena->GetId(), arena->GetType(), arena->GetCaptain().GetCounter()); + return true; } - static bool HandleArenaDisbandCommand(ChatHandler* handler, char const* args) + static bool HandleArenaDisbandCommand(ChatHandler* handler, uint32 teamId) { - if (!*args) - return false; - - uint32 teamId = atoi((char*)args); - if (!teamId) - return false; - ArenaTeam* arena = sArenaTeamMgr->GetArenaTeamById(teamId); if (!arena) @@ -147,40 +121,19 @@ public: return true; } - static bool HandleArenaRenameCommand(ChatHandler* handler, char const* _args) + static bool HandleArenaRenameCommand(ChatHandler* handler, QuotedString oldName, QuotedString newName) { - if (!*_args) - return false; - - char* args = (char*)_args; - - char const* oldArenaStr = handler->extractQuotedArg(args); - if (!oldArenaStr) - { - handler->SendSysMessage(LANG_BAD_VALUE); - handler->SetSentErrorMessage(true); - return false; - } - - char const* newArenaStr = handler->extractQuotedArg(strtok(nullptr, "")); - if (!newArenaStr) - { - handler->SendSysMessage(LANG_BAD_VALUE); - handler->SetSentErrorMessage(true); - return false; - } - - ArenaTeam* arena = sArenaTeamMgr->GetArenaTeamByName(oldArenaStr); + ArenaTeam* arena = sArenaTeamMgr->GetArenaTeamByName(oldName); if (!arena) { - handler->PSendSysMessage(LANG_AREAN_ERROR_NAME_NOT_FOUND, oldArenaStr); + handler->PSendSysMessage(LANG_AREAN_ERROR_NAME_NOT_FOUND, oldName.c_str()); handler->SetSentErrorMessage(true); return false; } - if (sArenaTeamMgr->GetArenaTeamByName(newArenaStr)) + if (sArenaTeamMgr->GetArenaTeamByName(newName)) { - handler->PSendSysMessage(LANG_ARENA_ERROR_NAME_EXISTS, oldArenaStr); + handler->PSendSysMessage(LANG_ARENA_ERROR_NAME_EXISTS, oldName.c_str()); handler->SetSentErrorMessage(true); return false; } @@ -192,40 +145,21 @@ public: return false; } - if (!arena->SetName(newArenaStr)) + if (!arena->SetName(newName)) { handler->SendSysMessage(LANG_BAD_VALUE); handler->SetSentErrorMessage(true); return false; } - handler->PSendSysMessage(LANG_ARENA_RENAME, arena->GetId(), oldArenaStr, newArenaStr); + handler->PSendSysMessage(LANG_ARENA_RENAME, arena->GetId(), oldName.c_str(), newName.c_str()); return true; } - static bool HandleArenaCaptainCommand(ChatHandler* handler, char const* args) + static bool HandleArenaCaptainCommand(ChatHandler* handler, uint32 teamId, Optional<PlayerIdentifier> target) { - if (!*args) - return false; - - char* idStr; - char* nameStr; - handler->extractOptFirstArg((char*)args, &idStr, &nameStr); - if (!idStr) - return false; - - uint32 teamId = atoi(idStr); - if (!teamId) - return false; - - Player* target; - ObjectGuid targetGuid; - if (!handler->extractPlayerTarget(nameStr, &target, &targetGuid)) - return false; - ArenaTeam* arena = sArenaTeamMgr->GetArenaTeamById(teamId); - if (!arena) { handler->PSendSysMessage(LANG_ARENA_ERROR_NOT_FOUND, teamId); @@ -233,13 +167,6 @@ public: return false; } - if (!target) - { - handler->PSendSysMessage(LANG_PLAYER_NOT_EXIST_OR_OFFLINE, nameStr); - handler->SetSentErrorMessage(true); - return false; - } - if (arena->IsFighting()) { handler->SendSysMessage(LANG_ARENA_ERROR_COMBAT); @@ -247,40 +174,38 @@ public: return false; } - if (!arena->IsMember(targetGuid)) + if (!target) + target = PlayerIdentifier::FromTargetOrSelf(handler); + + if (!target) + return false; + + if (!arena->IsMember(target->GetGUID())) { - handler->PSendSysMessage(LANG_ARENA_ERROR_NOT_MEMBER, nameStr, arena->GetName().c_str()); + handler->PSendSysMessage(LANG_ARENA_ERROR_NOT_MEMBER, target->GetName().c_str(), arena->GetName().c_str()); handler->SetSentErrorMessage(true); return false; } - if (arena->GetCaptain() == targetGuid) + if (arena->GetCaptain() == target->GetGUID()) { - handler->PSendSysMessage(LANG_ARENA_ERROR_CAPTAIN, nameStr, arena->GetName().c_str()); + handler->PSendSysMessage(LANG_ARENA_ERROR_CAPTAIN, target->GetName().c_str(), arena->GetName().c_str()); handler->SetSentErrorMessage(true); return false; } std::string oldCaptainName; sObjectMgr->GetPlayerNameByGUID(arena->GetCaptain().GetCounter(), oldCaptainName); - arena->SetCaptain(targetGuid); + arena->SetCaptain(target->GetGUID()); handler->PSendSysMessage(LANG_ARENA_CAPTAIN, arena->GetName().c_str(), arena->GetId(), oldCaptainName.c_str(), target->GetName().c_str()); return true; } - static bool HandleArenaInfoCommand(ChatHandler* handler, char const* args) + static bool HandleArenaInfoCommand(ChatHandler* handler, uint32 teamId) { - if (!*args) - return false; - - uint32 teamId = atoi((char*)args); - if (!teamId) - return false; - ArenaTeam* arena = sArenaTeamMgr->GetArenaTeamById(teamId); - if (!arena) { handler->PSendSysMessage(LANG_ARENA_ERROR_NOT_FOUND, teamId); @@ -289,36 +214,26 @@ public: } handler->PSendSysMessage(LANG_ARENA_INFO_HEADER, arena->GetName().c_str(), arena->GetId(), arena->GetRating(), arena->GetType(), arena->GetType()); - for (ArenaTeam::MemberList::iterator itr = arena->m_membersBegin(); itr != arena->m_membersEnd(); ++itr) - handler->PSendSysMessage(LANG_ARENA_INFO_MEMBERS, itr->Name.c_str(), itr->Guid.GetCounter(), itr->PersonalRating, (arena->GetCaptain() == itr->Guid ? "- Captain" : "")); + + for (auto const& itr : arena->GetMembers()) + handler->PSendSysMessage(LANG_ARENA_INFO_MEMBERS, itr.Name.c_str(), itr.Guid.GetCounter(), itr.PersonalRating, (arena->GetCaptain() == itr.Guid ? "- Captain" : "")); return true; } - static bool HandleArenaLookupCommand(ChatHandler* handler, char const* args) + static bool HandleArenaLookupCommand(ChatHandler* handler, Tail needle) { - if (!*args) + if (needle.empty()) return false; - std::string namepart = args; - std::wstring wnamepart; - - if (!Utf8toWStr(namepart, wnamepart)) - return false; - - wstrToLower(wnamepart); - bool found = false; - ArenaTeamMgr::ArenaTeamContainer::const_iterator i = sArenaTeamMgr->GetArenaTeamMapBegin(); - for (; i != sArenaTeamMgr->GetArenaTeamMapEnd(); ++i) + for (auto [teamId, team] : sArenaTeamMgr->GetArenaTeams()) { - ArenaTeam* arena = i->second; - - if (Utf8FitTo(arena->GetName(), wnamepart)) + if (StringContainsStringI(team->GetName(), needle)) { if (handler->GetSession()) { - handler->PSendSysMessage(LANG_ARENA_LOOKUP, arena->GetName().c_str(), arena->GetId(), arena->GetType(), arena->GetType()); + handler->PSendSysMessage(LANG_ARENA_LOOKUP, team->GetName().c_str(), team->GetId(), team->GetType(), team->GetType()); found = true; continue; } @@ -326,7 +241,7 @@ public: } if (!found) - handler->PSendSysMessage(LANG_AREAN_ERROR_NAME_NOT_FOUND, namepart.c_str()); + handler->PSendSysMessage(LANG_AREAN_ERROR_NAME_NOT_FOUND, std::string(needle).c_str()); return true; } diff --git a/src/server/scripts/Commands/cs_ban.cpp b/src/server/scripts/Commands/cs_ban.cpp index 9172a8c7e5..9a09c62a98 100644 --- a/src/server/scripts/Commands/cs_ban.cpp +++ b/src/server/scripts/Commands/cs_ban.cpp @@ -39,36 +39,42 @@ enum BanMode BAN_IP }; +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class ban_commandscript : public CommandScript { public: ban_commandscript() : CommandScript("ban_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> unbanCommandTable = + static ChatCommandTable unbanCommandTable = { - { "account", SEC_GAMEMASTER, true, &HandleUnBanAccountCommand, "" }, - { "character", SEC_GAMEMASTER, true, &HandleUnBanCharacterCommand, "" }, - { "playeraccount", SEC_GAMEMASTER, true, &HandleUnBanAccountByCharCommand, "" }, - { "ip", SEC_GAMEMASTER, true, &HandleUnBanIPCommand, "" } + { "account", SEC_ADMINISTRATOR, true, &HandleUnBanAccountCommand, "" }, + { "character", SEC_ADMINISTRATOR, true, &HandleUnBanCharacterCommand, "" }, + { "playeraccount", SEC_ADMINISTRATOR, true, &HandleUnBanAccountByCharCommand, "" }, + { "ip", SEC_ADMINISTRATOR, true, &HandleUnBanIPCommand, "" } }; - static std::vector<ChatCommand> banlistCommandTable = + static ChatCommandTable banlistCommandTable = { { "account", SEC_GAMEMASTER, true, &HandleBanListAccountCommand, "" }, { "character", SEC_GAMEMASTER, true, &HandleBanListCharacterCommand, "" }, { "ip", SEC_GAMEMASTER, true, &HandleBanListIPCommand, "" } }; - static std::vector<ChatCommand> baninfoCommandTable = + static ChatCommandTable baninfoCommandTable = { { "account", SEC_GAMEMASTER, true, &HandleBanInfoAccountCommand, "" }, { "character", SEC_GAMEMASTER, true, &HandleBanInfoCharacterCommand, "" }, { "ip", SEC_GAMEMASTER, true, &HandleBanInfoIPCommand, "" } }; - static std::vector<ChatCommand> banCommandTable = + static ChatCommandTable banCommandTable = { { "account", SEC_GAMEMASTER, true, &HandleBanAccountCommand, "" }, { "character", SEC_GAMEMASTER, true, &HandleBanCharacterCommand, "" }, @@ -76,12 +82,12 @@ public: { "ip", SEC_GAMEMASTER, true, &HandleBanIPCommand, "" } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { - { "ban", SEC_GAMEMASTER, true, nullptr, "", banCommandTable }, - { "baninfo", SEC_GAMEMASTER, true, nullptr, "", baninfoCommandTable }, - { "banlist", SEC_GAMEMASTER, true, nullptr, "", banlistCommandTable }, - { "unban", SEC_GAMEMASTER, true, nullptr, "", unbanCommandTable } + { "ban", SEC_GAMEMASTER, true, nullptr, "", banCommandTable }, + { "baninfo", SEC_GAMEMASTER, true, nullptr, "", baninfoCommandTable }, + { "banlist", SEC_GAMEMASTER, true, nullptr, "", banlistCommandTable }, + { "unban", SEC_ADMINISTRATOR, true, nullptr, "", unbanCommandTable } }; return commandTable; diff --git a/src/server/scripts/Commands/cs_bf.cpp b/src/server/scripts/Commands/cs_bf.cpp index cb2ed92968..c38710f760 100644 --- a/src/server/scripts/Commands/cs_bf.cpp +++ b/src/server/scripts/Commands/cs_bf.cpp @@ -26,14 +26,20 @@ EndScriptData */ #include "Chat.h" #include "ScriptMgr.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class bf_commandscript : public CommandScript { public: bf_commandscript() : CommandScript("bf_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> battlefieldcommandTable = + static ChatCommandTable battlefieldcommandTable = { { "start", SEC_ADMINISTRATOR, false, &HandleBattlefieldStart, "" }, { "stop", SEC_ADMINISTRATOR, false, &HandleBattlefieldEnd, "" }, @@ -41,7 +47,7 @@ public: { "timer", SEC_ADMINISTRATOR, false, &HandleBattlefieldTimer, "" }, { "enable", SEC_ADMINISTRATOR, false, &HandleBattlefieldEnable, "" } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "bf", SEC_ADMINISTRATOR, false, nullptr, "", battlefieldcommandTable } }; diff --git a/src/server/scripts/Commands/cs_cast.cpp b/src/server/scripts/Commands/cs_cast.cpp index 1f68a8b576..5c56775b53 100644 --- a/src/server/scripts/Commands/cs_cast.cpp +++ b/src/server/scripts/Commands/cs_cast.cpp @@ -29,14 +29,20 @@ EndScriptData */ #include "ScriptMgr.h" #include "SpellInfo.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class cast_commandscript : public CommandScript { public: cast_commandscript() : CommandScript("cast_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> castCommandTable = + static ChatCommandTable castCommandTable = { { "back", SEC_GAMEMASTER, false, &HandleCastBackCommand, "" }, { "dist", SEC_GAMEMASTER, false, &HandleCastDistCommand, "" }, @@ -45,7 +51,7 @@ public: { "dest", SEC_GAMEMASTER, false, &HandleCastDestCommand, "" }, { "", SEC_GAMEMASTER, false, &HandleCastCommand, "" } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "cast", SEC_GAMEMASTER, false, nullptr, "", castCommandTable } }; diff --git a/src/server/scripts/Commands/cs_character.cpp b/src/server/scripts/Commands/cs_character.cpp index 77950b5b65..4bb6228bbd 100644 --- a/src/server/scripts/Commands/cs_character.cpp +++ b/src/server/scripts/Commands/cs_character.cpp @@ -31,54 +31,61 @@ EndScriptData */ #include "ReputationMgr.h" #include "ScriptMgr.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class character_commandscript : public CommandScript { public: character_commandscript() : CommandScript("character_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> pdumpCommandTable = + static ChatCommandTable pdumpCommandTable = { - { "load", SEC_ADMINISTRATOR, true, &HandlePDumpLoadCommand, "" }, - { "write", SEC_ADMINISTRATOR, true, &HandlePDumpWriteCommand, "" } + { "load", HandlePDumpLoadCommand, SEC_ADMINISTRATOR, Console::Yes }, + { "write", HandlePDumpWriteCommand, SEC_ADMINISTRATOR, Console::Yes } }; - static std::vector<ChatCommand> characterDeletedCommandTable = + static ChatCommandTable characterDeletedCommandTable = { - { "delete", SEC_CONSOLE, true, &HandleCharacterDeletedDeleteCommand, "" }, - { "list", SEC_ADMINISTRATOR, true, &HandleCharacterDeletedListCommand, "" }, - { "restore", SEC_ADMINISTRATOR, true, &HandleCharacterDeletedRestoreCommand, "" }, - { "purge", SEC_CONSOLE, true, &HandleCharacterDeletedPurgeCommand, "" }, + { "delete", HandleCharacterDeletedDeleteCommand, SEC_CONSOLE, Console::Yes }, + { "list", HandleCharacterDeletedListCommand, SEC_ADMINISTRATOR, Console::Yes }, + { "restore", HandleCharacterDeletedRestoreCommand, SEC_ADMINISTRATOR, Console::Yes }, + { "purge", HandleCharacterDeletedPurgeCommand, SEC_CONSOLE, Console::Yes } }; - static std::vector<ChatCommand> characterCheckCommandTable = + static ChatCommandTable characterCheckCommandTable = { - { "bank", SEC_GAMEMASTER, false, &HandleCharacterCheckBankCommand, "" }, - { "bag", SEC_GAMEMASTER, false, &HandleCharacterCheckBagCommand, "" }, - { "profession", SEC_GAMEMASTER, false, &HandleCharacterCheckProfessionCommand, "" }, + { "bank", HandleCharacterCheckBankCommand, SEC_GAMEMASTER, Console::Yes }, + { "bag", HandleCharacterCheckBagCommand, SEC_GAMEMASTER, Console::Yes }, + { "profession", HandleCharacterCheckProfessionCommand, SEC_GAMEMASTER, Console::Yes } }; - static std::vector<ChatCommand> characterCommandTable = + static ChatCommandTable characterCommandTable = { - { "customize", SEC_GAMEMASTER, true, &HandleCharacterCustomizeCommand, "" }, - { "changefaction", SEC_GAMEMASTER, true, &HandleCharacterChangeFactionCommand, "" }, - { "changerace", SEC_GAMEMASTER, true, &HandleCharacterChangeRaceCommand, "" }, - { "check", SEC_GAMEMASTER, false, nullptr, "", characterCheckCommandTable }, - { "erase", SEC_CONSOLE, true, &HandleCharacterEraseCommand, "" }, - { "deleted", SEC_ADMINISTRATOR, true, nullptr, "", characterDeletedCommandTable }, - { "level", SEC_GAMEMASTER, true, &HandleCharacterLevelCommand, "" }, - { "rename", SEC_GAMEMASTER, true, &HandleCharacterRenameCommand, "" }, - { "reputation", SEC_GAMEMASTER, true, &HandleCharacterReputationCommand, "" }, - { "titles", SEC_GAMEMASTER, true, &HandleCharacterTitlesCommand, "" } + { "customize", HandleCharacterCustomizeCommand, SEC_GAMEMASTER, Console::Yes }, + { "changefaction", HandleCharacterChangeFactionCommand, SEC_GAMEMASTER, Console::Yes }, + { "changerace", HandleCharacterChangeRaceCommand, SEC_GAMEMASTER, Console::Yes }, + { "check", characterCheckCommandTable }, + { "erase", HandleCharacterEraseCommand, SEC_CONSOLE, Console::Yes }, + { "deleted", characterDeletedCommandTable }, + { "level", HandleCharacterLevelCommand, SEC_GAMEMASTER, Console::Yes }, + { "rename", HandleCharacterRenameCommand, SEC_GAMEMASTER, Console::Yes }, + { "reputation", HandleCharacterReputationCommand, SEC_GAMEMASTER, Console::Yes }, + { "titles", HandleCharacterTitlesCommand, SEC_GAMEMASTER, Console::Yes } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { - { "character", SEC_GAMEMASTER, true, nullptr, "", characterCommandTable }, - { "levelup", SEC_GAMEMASTER, false, &HandleLevelUpCommand, "" }, - { "pdump", SEC_ADMINISTRATOR, true, nullptr, "", pdumpCommandTable } + { "character", characterCommandTable }, + { "levelup", HandleLevelUpCommand, SEC_GAMEMASTER, Console::No }, + { "pdump", pdumpCommandTable } }; + return commandTable; } @@ -222,7 +229,7 @@ public: return; } - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UDP_RESTORE_DELETE_INFO); + auto* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UDP_RESTORE_DELETE_INFO); stmt->setString(0, delInfo.name); stmt->setUInt32(1, delInfo.accountId); stmt->setUInt32(2, delInfo.lowGuid); @@ -255,7 +262,7 @@ public: else { // Update level and reset XP, everything else will be updated at login - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_LEVEL); + auto* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_LEVEL); stmt->setUInt8(0, uint8(newLevel)); stmt->setUInt32(1, playerGuid.GetCounter()); CharacterDatabase.Execute(stmt); @@ -347,7 +354,7 @@ public: std::string oldNameLink = handler->playerLink(targetName); handler->PSendSysMessage(LANG_RENAME_PLAYER_GUID, oldNameLink.c_str(), targetGuid.GetCounter()); - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ADD_AT_LOGIN_FLAG); + auto* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ADD_AT_LOGIN_FLAG); stmt->setUInt16(0, uint16(AT_LOGIN_RENAME)); stmt->setUInt32(1, targetGuid.GetCounter()); CharacterDatabase.Execute(stmt); @@ -361,29 +368,15 @@ public: return true; } - static bool HandleCharacterLevelCommand(ChatHandler* handler, char const* args) + static bool HandleCharacterLevelCommand(ChatHandler* handler, Optional<PlayerIdentifier> player, int16 newlevel) { - char* nameStr; - char* levelStr; - handler->extractOptFirstArg((char*)args, &nameStr, &levelStr); - if (!levelStr) - return false; - - // exception opt second arg: .character level $name - if (isalpha(levelStr[0])) - { - nameStr = levelStr; - levelStr = nullptr; // current level will used - } + if (!player) + player = PlayerIdentifier::FromTargetOrSelf(handler); - Player* target; - ObjectGuid targetGuid; - std::string targetName; - if (!handler->extractPlayerTarget(nameStr, &target, &targetGuid, &targetName)) + if (!player) return false; - int32 oldlevel = target ? target->getLevel() : Player::GetLevelFromStorage(targetGuid.GetCounter()); - int32 newlevel = levelStr ? atoi(levelStr) : oldlevel; + uint8 oldlevel = player->IsConnected() ? player->GetConnectedPlayer()->getLevel() : static_cast<uint8>(Player::GetLevelFromStorage(player->GetGUID().GetCounter())); if (newlevel < 1) return false; // invalid level @@ -391,12 +384,10 @@ public: if (newlevel > DEFAULT_MAX_LEVEL) // hardcoded maximum level newlevel = DEFAULT_MAX_LEVEL; - HandleCharacterLevel(target, targetGuid, oldlevel, newlevel, handler); - if (!handler->GetSession() || handler->GetSession()->GetPlayer() != target) // including player == nullptr - { - std::string nameLink = handler->playerLink(targetName); - handler->PSendSysMessage(LANG_YOU_CHANGE_LVL, nameLink.c_str(), newlevel); - } + HandleCharacterLevel(player->GetConnectedPlayer(), player->GetGUID(), oldlevel, newlevel, handler); + + if (!handler->GetSession() || (handler->GetSession()->GetPlayer() != player->GetConnectedPlayer())) // including chr == NULL + handler->PSendSysMessage(LANG_YOU_CHANGE_LVL, handler->playerLink(*player).c_str(), newlevel); return true; } @@ -410,7 +401,7 @@ public: if (!handler->extractPlayerTarget((char*)args, &target, &targetGuid, &targetName)) return false; - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ADD_AT_LOGIN_FLAG); + auto* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ADD_AT_LOGIN_FLAG); stmt->setUInt16(0, uint16(AT_LOGIN_CUSTOMIZE)); if (target) { @@ -438,7 +429,7 @@ public: if (!handler->extractPlayerTarget((char*)args, &target, &targetGuid, &targetName)) return false; - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ADD_AT_LOGIN_FLAG); + auto* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ADD_AT_LOGIN_FLAG); stmt->setUInt16(0, uint16(AT_LOGIN_CHANGE_FACTION)); if (target) { @@ -465,7 +456,7 @@ public: if (!handler->extractPlayerTarget((char*)args, &target, &targetGuid, &targetName)) return false; - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ADD_AT_LOGIN_FLAG); + auto* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ADD_AT_LOGIN_FLAG); stmt->setUInt16(0, uint16(AT_LOGIN_CHANGE_RACE)); if (target) { @@ -745,28 +736,16 @@ public: return true; } - static bool HandleLevelUpCommand(ChatHandler* handler, char const* args) + static bool HandleLevelUpCommand(ChatHandler* handler, Optional<PlayerIdentifier> player, int16 level) { - char* nameStr; - char* levelStr; - handler->extractOptFirstArg((char*)args, &nameStr, &levelStr); - - // exception opt second arg: .character level $name - if (levelStr && isalpha(levelStr[0])) - { - nameStr = levelStr; - levelStr = nullptr; // current level will used - } + if (!player) + player = PlayerIdentifier::FromTargetOrSelf(handler); - Player* target; - ObjectGuid targetGuid; - std::string targetName; - if (!handler->extractPlayerTarget(nameStr, &target, &targetGuid, &targetName)) + if (!player) return false; - int32 oldlevel = target ? target->getLevel() : Player::GetLevelFromStorage(targetGuid.GetCounter()); - int32 addlevel = levelStr ? atoi(levelStr) : 1; - int32 newlevel = oldlevel + addlevel; + uint8 oldlevel = static_cast<uint8>(player->IsConnected() ? player->GetConnectedPlayer()->getLevel() : Player::GetLevelFromStorage(player->GetGUID().GetCounter())); + int16 newlevel = static_cast<int16>(oldlevel) + level; if (newlevel < 1) newlevel = 1; @@ -774,13 +753,10 @@ public: if (newlevel > STRONG_MAX_LEVEL) // hardcoded maximum level newlevel = STRONG_MAX_LEVEL; - HandleCharacterLevel(target, targetGuid, oldlevel, newlevel, handler); + HandleCharacterLevel(player->GetConnectedPlayer(), player->GetGUID(), oldlevel, newlevel, handler); - if (!handler->GetSession() || handler->GetSession()->GetPlayer() != target) // including chr == nullptr - { - std::string nameLink = handler->playerLink(targetName); - handler->PSendSysMessage(LANG_YOU_CHANGE_LVL, nameLink.c_str(), newlevel); - } + if (!handler->GetSession() || (handler->GetSession()->GetPlayer() != player->GetConnectedPlayer())) // including chr == NULL + handler->PSendSysMessage(LANG_YOU_CHANGE_LVL, handler->playerLink(*player).c_str(), newlevel); return true; } diff --git a/src/server/scripts/Commands/cs_cheat.cpp b/src/server/scripts/Commands/cs_cheat.cpp index c3a8efe3e1..6a34e2e3de 100644 --- a/src/server/scripts/Commands/cs_cheat.cpp +++ b/src/server/scripts/Commands/cs_cheat.cpp @@ -20,14 +20,20 @@ #include "Player.h" #include "ScriptMgr.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class cheat_commandscript : public CommandScript { public: cheat_commandscript() : CommandScript("cheat_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> cheatCommandTable = + static ChatCommandTable cheatCommandTable = { { "god", SEC_GAMEMASTER, false, &HandleGodModeCheatCommand, "" }, { "casttime", SEC_GAMEMASTER, false, &HandleCasttimeCheatCommand, "" }, @@ -39,7 +45,7 @@ public: { "explore", SEC_GAMEMASTER, false, &HandleExploreCheatCommand, "" }, }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "cheat", SEC_GAMEMASTER, false, nullptr, "", cheatCommandTable }, }; diff --git a/src/server/scripts/Commands/cs_debug.cpp b/src/server/scripts/Commands/cs_debug.cpp index 169fcfccf2..32535e86f3 100644 --- a/src/server/scripts/Commands/cs_debug.cpp +++ b/src/server/scripts/Commands/cs_debug.cpp @@ -34,20 +34,26 @@ EndScriptData */ #include "ScriptMgr.h" #include <fstream> +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class debug_commandscript : public CommandScript { public: debug_commandscript() : CommandScript("debug_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> debugPlayCommandTable = + static ChatCommandTable debugPlayCommandTable = { { "cinematic", SEC_MODERATOR, false, &HandleDebugPlayCinematicCommand, "" }, { "movie", SEC_MODERATOR, false, &HandleDebugPlayMovieCommand, "" }, { "sound", SEC_MODERATOR, false, &HandleDebugPlaySoundCommand, "" } }; - static std::vector<ChatCommand> debugSendCommandTable = + static ChatCommandTable debugSendCommandTable = { { "buyerror", SEC_ADMINISTRATOR, false, &HandleDebugSendBuyErrorCommand, "" }, { "channelnotify", SEC_ADMINISTRATOR, false, &HandleDebugSendChannelNotifyCommand, "" }, @@ -61,7 +67,7 @@ public: { "setphaseshift", SEC_ADMINISTRATOR, false, &HandleDebugSendSetPhaseShiftCommand, "" }, { "spellfail", SEC_ADMINISTRATOR, false, &HandleDebugSendSpellFailCommand, "" } }; - static std::vector<ChatCommand> debugCommandTable = + static ChatCommandTable debugCommandTable = { { "setbit", SEC_ADMINISTRATOR, false, &HandleDebugSet32BitCommand, "" }, { "threat", SEC_ADMINISTRATOR, false, &HandleDebugThreatListCommand, "" }, @@ -91,7 +97,7 @@ public: { "moveflags", SEC_ADMINISTRATOR, false, &HandleDebugMoveflagsCommand, "" }, { "unitstate", SEC_ADMINISTRATOR, false, &HandleDebugUnitStateCommand, "" } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "debug", SEC_GAMEMASTER, true, nullptr, "", debugCommandTable }, { "wpgps", SEC_ADMINISTRATOR, false, &HandleWPGPSCommand, "", } diff --git a/src/server/scripts/Commands/cs_deserter.cpp b/src/server/scripts/Commands/cs_deserter.cpp index e2af2a643e..30bb9334a9 100644 --- a/src/server/scripts/Commands/cs_deserter.cpp +++ b/src/server/scripts/Commands/cs_deserter.cpp @@ -28,32 +28,38 @@ enum Spells BG_SPELL_DESERTER = 26013 }; +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class deserter_commandscript : public CommandScript { public: deserter_commandscript() : CommandScript("deserter_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> deserterinstanceCommandTable = + static ChatCommandTable deserterinstanceCommandTable = { { "add", SEC_ADMINISTRATOR, false, &HandleDeserterInstanceAdd, "" }, { "remove", SEC_ADMINISTRATOR, false, &HandleDeserterInstanceRemove, "" } }; - static std::vector<ChatCommand> deserterBGCommandTable = + static ChatCommandTable deserterBGCommandTable = { { "add", SEC_ADMINISTRATOR, false, &HandleDeserterBGAdd, "" }, { "remove", SEC_ADMINISTRATOR, false, &HandleDeserterBGRemove, "" } }; - static std::vector<ChatCommand> deserterCommandTable = + static ChatCommandTable deserterCommandTable = { { "instance", SEC_ADMINISTRATOR, false, nullptr, "", deserterinstanceCommandTable }, { "bg", SEC_ADMINISTRATOR, false, nullptr, "", deserterBGCommandTable } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "deserter", SEC_ADMINISTRATOR, false, nullptr, "", deserterCommandTable } }; diff --git a/src/server/scripts/Commands/cs_disable.cpp b/src/server/scripts/Commands/cs_disable.cpp index e1548a5489..60104a49f4 100644 --- a/src/server/scripts/Commands/cs_disable.cpp +++ b/src/server/scripts/Commands/cs_disable.cpp @@ -32,14 +32,20 @@ EndScriptData */ #include "ScriptMgr.h" #include "SpellMgr.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class disable_commandscript : public CommandScript { public: disable_commandscript() : CommandScript("disable_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> removeDisableCommandTable = + static ChatCommandTable removeDisableCommandTable = { { "spell", SEC_ADMINISTRATOR, true, &HandleRemoveDisableSpellCommand, "" }, { "quest", SEC_ADMINISTRATOR, true, &HandleRemoveDisableQuestCommand, "" }, @@ -48,7 +54,7 @@ public: { "outdoorpvp", SEC_ADMINISTRATOR, true, &HandleRemoveDisableOutdoorPvPCommand, "" }, { "vmap", SEC_ADMINISTRATOR, true, &HandleRemoveDisableVmapCommand, "" }, }; - static std::vector<ChatCommand> addDisableCommandTable = + static ChatCommandTable addDisableCommandTable = { { "spell", SEC_ADMINISTRATOR, true, &HandleAddDisableSpellCommand, "" }, { "quest", SEC_ADMINISTRATOR, true, &HandleAddDisableQuestCommand, "" }, @@ -57,12 +63,12 @@ public: { "outdoorpvp", SEC_ADMINISTRATOR, true, &HandleAddDisableOutdoorPvPCommand, "" }, { "vmap", SEC_ADMINISTRATOR, true, &HandleAddDisableVmapCommand, "" }, }; - static std::vector<ChatCommand> disableCommandTable = + static ChatCommandTable disableCommandTable = { { "add", SEC_ADMINISTRATOR, true, nullptr, "", addDisableCommandTable }, { "remove", SEC_ADMINISTRATOR, true, nullptr, "", removeDisableCommandTable }, }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "disable", SEC_ADMINISTRATOR, false, nullptr, "", disableCommandTable }, }; diff --git a/src/server/scripts/Commands/cs_event.cpp b/src/server/scripts/Commands/cs_event.cpp index 2df1bb4c84..9ae1577814 100644 --- a/src/server/scripts/Commands/cs_event.cpp +++ b/src/server/scripts/Commands/cs_event.cpp @@ -28,21 +28,27 @@ EndScriptData */ #include "Player.h" #include "ScriptMgr.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class event_commandscript : public CommandScript { public: event_commandscript() : CommandScript("event_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> eventCommandTable = + static ChatCommandTable eventCommandTable = { { "activelist", SEC_GAMEMASTER, true, &HandleEventActiveListCommand, "" }, { "start", SEC_GAMEMASTER, true, &HandleEventStartCommand, "" }, { "stop", SEC_GAMEMASTER, true, &HandleEventStopCommand, "" }, { "", SEC_GAMEMASTER, true, &HandleEventInfoCommand, "" } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "event", SEC_GAMEMASTER, false, nullptr, "", eventCommandTable } }; diff --git a/src/server/scripts/Commands/cs_gm.cpp b/src/server/scripts/Commands/cs_gm.cpp index 8f7a731bc2..e8bf762dcf 100644 --- a/src/server/scripts/Commands/cs_gm.cpp +++ b/src/server/scripts/Commands/cs_gm.cpp @@ -32,23 +32,29 @@ EndScriptData */ #include "ScriptMgr.h" #include "World.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class gm_commandscript : public CommandScript { public: gm_commandscript() : CommandScript("gm_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> gmCommandTable = + static ChatCommandTable gmCommandTable = { { "chat", SEC_GAMEMASTER, false, &HandleGMChatCommand, "" }, { "fly", SEC_GAMEMASTER, false, &HandleGMFlyCommand, "" }, { "ingame", SEC_PLAYER, true, &HandleGMListIngameCommand, "" }, { "list", SEC_GAMEMASTER, true, &HandleGMListFullCommand, "" }, { "visible", SEC_GAMEMASTER, false, &HandleGMVisibleCommand, "" }, - { "", SEC_GAMEMASTER, false, &HandleGMCommand, "" } + { "", SEC_MODERATOR, false, &HandleGMCommand, "" } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "gm", SEC_MODERATOR, false, nullptr, "", gmCommandTable } }; diff --git a/src/server/scripts/Commands/cs_go.cpp b/src/server/scripts/Commands/cs_go.cpp index 91cb8619ef..8b6caa437c 100644 --- a/src/server/scripts/Commands/cs_go.cpp +++ b/src/server/scripts/Commands/cs_go.cpp @@ -31,14 +31,20 @@ EndScriptData */ #include "ScriptMgr.h" #include "TicketMgr.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class go_commandscript : public CommandScript { public: go_commandscript() : CommandScript("go_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> goCommandTable = + static ChatCommandTable goCommandTable = { { "creature", SEC_MODERATOR, false, &HandleGoCreatureCommand, "" }, { "graveyard", SEC_MODERATOR, false, &HandleGoGraveyardCommand, "" }, @@ -53,7 +59,7 @@ public: { "", SEC_MODERATOR, false, &HandleGoXYZCommand, "" } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "go", SEC_MODERATOR, false, nullptr, "", goCommandTable } }; diff --git a/src/server/scripts/Commands/cs_gobject.cpp b/src/server/scripts/Commands/cs_gobject.cpp index b7c6ec3abd..2d30e37dd4 100644 --- a/src/server/scripts/Commands/cs_gobject.cpp +++ b/src/server/scripts/Commands/cs_gobject.cpp @@ -34,26 +34,32 @@ EndScriptData */ #include "ScriptMgr.h" #include "Transport.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class gobject_commandscript : public CommandScript { public: gobject_commandscript() : CommandScript("gobject_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> gobjectAddCommandTable = + static ChatCommandTable gobjectAddCommandTable = { { "temp", SEC_GAMEMASTER, false, &HandleGameObjectAddTempCommand, "" }, { "", SEC_ADMINISTRATOR, false, &HandleGameObjectAddCommand, "" } }; - static std::vector<ChatCommand> gobjectSetCommandTable = + static ChatCommandTable gobjectSetCommandTable = { { "phase", SEC_ADMINISTRATOR, false, &HandleGameObjectSetPhaseCommand, "" }, { "state", SEC_ADMINISTRATOR, false, &HandleGameObjectSetStateCommand, "" } }; - static std::vector<ChatCommand> gobjectCommandTable = + static ChatCommandTable gobjectCommandTable = { - { "activate", SEC_ADMINISTRATOR, false, &HandleGameObjectActivateCommand, "" }, + { "activate", SEC_GAMEMASTER, false, &HandleGameObjectActivateCommand, "" }, { "delete", SEC_ADMINISTRATOR, false, &HandleGameObjectDeleteCommand, "" }, { "info", SEC_MODERATOR, false, &HandleGameObjectInfoCommand, "" }, { "move", SEC_ADMINISTRATOR, false, &HandleGameObjectMoveCommand, "" }, @@ -63,7 +69,7 @@ public: { "add", SEC_ADMINISTRATOR, false, nullptr, "", gobjectAddCommandTable }, { "set", SEC_ADMINISTRATOR, false, nullptr, "", gobjectSetCommandTable } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "gobject", SEC_MODERATOR, false, nullptr, "", gobjectCommandTable } }; diff --git a/src/server/scripts/Commands/cs_guild.cpp b/src/server/scripts/Commands/cs_guild.cpp index 675bbc658b..89b867965a 100644 --- a/src/server/scripts/Commands/cs_guild.cpp +++ b/src/server/scripts/Commands/cs_guild.cpp @@ -29,14 +29,20 @@ EndScriptData */ #include "ObjectAccessor.h" #include "ScriptMgr.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class guild_commandscript : public CommandScript { public: guild_commandscript() : CommandScript("guild_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> guildCommandTable = + static ChatCommandTable guildCommandTable = { { "create", SEC_GAMEMASTER, true, &HandleGuildCreateCommand, "" }, { "delete", SEC_GAMEMASTER, true, &HandleGuildDeleteCommand, "" }, @@ -45,7 +51,7 @@ public: { "rank", SEC_GAMEMASTER, true, &HandleGuildRankCommand, "" }, { "info", SEC_GAMEMASTER, true, &HandleGuildInfoCommand, "" } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "guild", SEC_GAMEMASTER, true, nullptr, "", guildCommandTable } }; @@ -182,21 +188,15 @@ public: return true; } - static bool HandleGuildRankCommand(ChatHandler* handler, char const* args) + static bool HandleGuildRankCommand(ChatHandler* handler, Optional<PlayerIdentifier> player, uint8 rank) { - char* nameStr; - char* rankStr; - handler->extractOptFirstArg((char*)args, &nameStr, &rankStr); - if (!rankStr) - return false; + if (!player) + player = PlayerIdentifier::FromTargetOrSelf(handler); - Player* target; - ObjectGuid targetGuid; - std::string target_name; - if (!handler->extractPlayerTarget(nameStr, &target, &targetGuid, &target_name)) + if (!player) return false; - uint32 guildId = target ? target->GetGuildId() : Player::GetGuildIdFromStorage(targetGuid.GetCounter()); + uint32 guildId = player->IsConnected() ? player->GetConnectedPlayer()->GetGuildId() : Player::GetGuildIdFromStorage(player->GetGUID().GetCounter()); if (!guildId) return false; @@ -204,8 +204,7 @@ public: if (!targetGuild) return false; - uint8 newRank = uint8(atoi(rankStr)); - return targetGuild->ChangeMemberRank(targetGuid, newRank); + return targetGuild->ChangeMemberRank(player->GetGUID(), rank); } static bool HandleGuildInfoCommand(ChatHandler* handler, char const* args) diff --git a/src/server/scripts/Commands/cs_honor.cpp b/src/server/scripts/Commands/cs_honor.cpp index 6450b81e1e..0f63183964 100644 --- a/src/server/scripts/Commands/cs_honor.cpp +++ b/src/server/scripts/Commands/cs_honor.cpp @@ -28,26 +28,32 @@ EndScriptData */ #include "Player.h" #include "ScriptMgr.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class honor_commandscript : public CommandScript { public: honor_commandscript() : CommandScript("honor_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> honorAddCommandTable = + static ChatCommandTable honorAddCommandTable = { { "kill", SEC_GAMEMASTER, false, &HandleHonorAddKillCommand, "" }, { "", SEC_GAMEMASTER, false, &HandleHonorAddCommand, "" } }; - static std::vector<ChatCommand> honorCommandTable = + static ChatCommandTable honorCommandTable = { { "add", SEC_GAMEMASTER, false, nullptr, "", honorAddCommandTable }, { "update", SEC_GAMEMASTER, false, &HandleHonorUpdateCommand, "" } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "honor", SEC_GAMEMASTER, false, nullptr, "", honorCommandTable } }; diff --git a/src/server/scripts/Commands/cs_instance.cpp b/src/server/scripts/Commands/cs_instance.cpp index 8823aa5564..2b4b026f51 100644 --- a/src/server/scripts/Commands/cs_instance.cpp +++ b/src/server/scripts/Commands/cs_instance.cpp @@ -31,14 +31,20 @@ EndScriptData */ #include "Player.h" #include "ScriptMgr.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class instance_commandscript : public CommandScript { public: instance_commandscript() : CommandScript("instance_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> instanceCommandTable = + static ChatCommandTable instanceCommandTable = { { "listbinds", SEC_MODERATOR, false, &HandleInstanceListBindsCommand, "" }, { "unbind", SEC_GAMEMASTER, false, &HandleInstanceUnbindCommand, "" }, @@ -48,7 +54,7 @@ public: { "getbossstate", SEC_MODERATOR, true, &HandleInstanceGetBossStateCommand, "" } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "instance", SEC_MODERATOR, true, nullptr, "", instanceCommandTable } }; diff --git a/src/server/scripts/Commands/cs_learn.cpp b/src/server/scripts/Commands/cs_learn.cpp index b585b3eb73..a76c88221e 100644 --- a/src/server/scripts/Commands/cs_learn.cpp +++ b/src/server/scripts/Commands/cs_learn.cpp @@ -32,14 +32,20 @@ EndScriptData */ #include "SpellInfo.h" #include "SpellMgr.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class learn_commandscript : public CommandScript, public PlayerCommand { public: learn_commandscript() : CommandScript("learn_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> learnAllMyCommandTable = + static ChatCommandTable learnAllMyCommandTable = { { "class", SEC_GAMEMASTER, false, &HandleLearnAllMyClassCommand, "" }, { "pettalents", SEC_GAMEMASTER, false, &HandleLearnAllMyPetTalentsCommand, "" }, @@ -47,7 +53,7 @@ public: { "talents", SEC_GAMEMASTER, false, &HandleLearnAllMyTalentsCommand, "" } }; - static std::vector<ChatCommand> learnAllCommandTable = + static ChatCommandTable learnAllCommandTable = { { "my", SEC_GAMEMASTER, false, nullptr, "", learnAllMyCommandTable }, { "gm", SEC_GAMEMASTER, false, &HandleLearnAllGMCommand, "" }, @@ -57,13 +63,13 @@ public: { "recipes", SEC_GAMEMASTER, false, &HandleLearnAllRecipesCommand, "" } }; - static std::vector<ChatCommand> learnCommandTable = + static ChatCommandTable learnCommandTable = { { "all", SEC_GAMEMASTER, false, nullptr, "", learnAllCommandTable }, { "", SEC_GAMEMASTER, false, &HandleLearnCommand, "" } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "learn", SEC_GAMEMASTER, false, nullptr, "", learnCommandTable }, { "unlearn", SEC_GAMEMASTER, false, &HandleUnLearnCommand, "" } diff --git a/src/server/scripts/Commands/cs_lfg.cpp b/src/server/scripts/Commands/cs_lfg.cpp index 06114f61e1..7653c446eb 100644 --- a/src/server/scripts/Commands/cs_lfg.cpp +++ b/src/server/scripts/Commands/cs_lfg.cpp @@ -36,14 +36,20 @@ void GetPlayerInfo(ChatHandler* handler, Player* player) lfg::GetRolesString(sLFGMgr->GetRoles(guid)).c_str(), sLFGMgr->GetComment(guid).c_str()); } +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class lfg_commandscript : public CommandScript { public: lfg_commandscript() : CommandScript("lfg_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> lfgCommandTable = + static ChatCommandTable lfgCommandTable = { { "player", SEC_MODERATOR, false, &HandleLfgPlayerInfoCommand, "" }, { "group", SEC_MODERATOR, false, &HandleLfgGroupInfoCommand, "" }, @@ -52,7 +58,7 @@ public: { "options", SEC_GAMEMASTER, false, &HandleLfgOptionsCommand, "" }, }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "lfg", SEC_GAMEMASTER, false, nullptr, "", lfgCommandTable }, }; diff --git a/src/server/scripts/Commands/cs_list.cpp b/src/server/scripts/Commands/cs_list.cpp index 539ea79247..a8a1cef9f6 100644 --- a/src/server/scripts/Commands/cs_list.cpp +++ b/src/server/scripts/Commands/cs_list.cpp @@ -29,14 +29,20 @@ EndScriptData */ #include "ScriptMgr.h" #include "SpellAuraEffects.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class list_commandscript : public CommandScript { public: list_commandscript() : CommandScript("list_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> listCommandTable = + static ChatCommandTable listCommandTable = { { "creature", SEC_MODERATOR, true, &HandleListCreatureCommand, "" }, { "item", SEC_MODERATOR, true, &HandleListItemCommand, "" }, @@ -44,7 +50,7 @@ public: { "gobject", SEC_MODERATOR, true, &HandleListObjectCommand, "" }, { "auras", SEC_MODERATOR, false, &HandleListAurasCommand, "" } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "list", SEC_MODERATOR, true, nullptr, "", listCommandTable } }; diff --git a/src/server/scripts/Commands/cs_lookup.cpp b/src/server/scripts/Commands/cs_lookup.cpp index e1734acf45..0417159c17 100644 --- a/src/server/scripts/Commands/cs_lookup.cpp +++ b/src/server/scripts/Commands/cs_lookup.cpp @@ -32,27 +32,33 @@ EndScriptData */ #include "ScriptMgr.h" #include "SpellInfo.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class lookup_commandscript : public CommandScript { public: lookup_commandscript() : CommandScript("lookup_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> lookupPlayerCommandTable = + static ChatCommandTable lookupPlayerCommandTable = { { "ip", SEC_GAMEMASTER, true, &HandleLookupPlayerIpCommand, "" }, { "account", SEC_GAMEMASTER, true, &HandleLookupPlayerAccountCommand, "" }, { "email", SEC_GAMEMASTER, true, &HandleLookupPlayerEmailCommand, "" } }; - static std::vector<ChatCommand> lookupSpellCommandTable = + static ChatCommandTable lookupSpellCommandTable = { { "id", SEC_MODERATOR, true, &HandleLookupSpellIdCommand, "" }, { "", SEC_MODERATOR, true, &HandleLookupSpellCommand, "" } }; - static std::vector<ChatCommand> lookupCommandTable = + static ChatCommandTable lookupCommandTable = { { "area", SEC_MODERATOR, true, &HandleLookupAreaCommand, "" }, { "creature", SEC_MODERATOR, true, &HandleLookupCreatureCommand, "" }, @@ -72,7 +78,7 @@ public: { "spell", SEC_MODERATOR, true, nullptr, "", lookupSpellCommandTable } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "lookup", SEC_MODERATOR, true, nullptr, "", lookupCommandTable } }; diff --git a/src/server/scripts/Commands/cs_message.cpp b/src/server/scripts/Commands/cs_message.cpp index 86fbc896a2..ff1c00bb54 100644 --- a/src/server/scripts/Commands/cs_message.cpp +++ b/src/server/scripts/Commands/cs_message.cpp @@ -28,14 +28,20 @@ EndScriptData */ #include "Player.h" #include "ScriptMgr.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class message_commandscript : public CommandScript { public: message_commandscript() : CommandScript("message_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "nameannounce", SEC_GAMEMASTER, true, &HandleNameAnnounceCommand, "" }, { "gmnameannounce", SEC_GAMEMASTER, true, &HandleGMNameAnnounceCommand, "" }, diff --git a/src/server/scripts/Commands/cs_misc.cpp b/src/server/scripts/Commands/cs_misc.cpp index ff2efdf70e..2086024440 100644 --- a/src/server/scripts/Commands/cs_misc.cpp +++ b/src/server/scripts/Commands/cs_misc.cpp @@ -41,14 +41,20 @@ #include "TargetedMovementGenerator.h" #include "WeatherMgr.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class misc_commandscript : public CommandScript { public: misc_commandscript() : CommandScript("misc_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> groupCommandTable = + static ChatCommandTable groupCommandTable = { { "leader", SEC_GAMEMASTER, false, &HandleGroupLeaderCommand, "" }, { "disband", SEC_GAMEMASTER, false, &HandleGroupDisbandCommand, "" }, @@ -56,25 +62,25 @@ public: { "join", SEC_GAMEMASTER, false, &HandleGroupJoinCommand, "" }, { "list", SEC_GAMEMASTER, false, &HandleGroupListCommand, "" } }; - static std::vector<ChatCommand> petCommandTable = + static ChatCommandTable petCommandTable = { { "create", SEC_GAMEMASTER, false, &HandleCreatePetCommand, "" }, { "learn", SEC_GAMEMASTER, false, &HandlePetLearnCommand, "" }, { "unlearn", SEC_GAMEMASTER, false, &HandlePetUnlearnCommand, "" } }; - static std::vector<ChatCommand> sendCommandTable = + static ChatCommandTable sendCommandTable = { { "items", SEC_GAMEMASTER, true, &HandleSendItemsCommand, "" }, { "mail", SEC_GAMEMASTER, true, &HandleSendMailCommand, "" }, { "message", SEC_ADMINISTRATOR, true, &HandleSendMessageCommand, "" }, { "money", SEC_GAMEMASTER, true, &HandleSendMoneyCommand, "" } }; - static std::vector<ChatCommand> gearCommandTable = + static ChatCommandTable gearCommandTable = { { "repair", SEC_GAMEMASTER, false, &HandleGearRepairCommand, "" }, { "stats", SEC_PLAYER, false, &HandleGearStatsCommand, "" } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "dev", SEC_ADMINISTRATOR, false, &HandleDevCommand, "" }, { "gps", SEC_MODERATOR, false, &HandleGPSCommand, "" }, @@ -894,7 +900,7 @@ public: static bool HandleCommandsCommand(ChatHandler* handler, char const* /*args*/) { - handler->ShowHelpForCommand(handler->getCommandTable(), ""); + SendCommandHelpFor(*handler, ""); return true; } @@ -993,19 +999,12 @@ public: return true; } - static bool HandleHelpCommand(ChatHandler* handler, char const* args) + static bool HandleHelpCommand(ChatHandler* handler, Tail cmd) { - char const* cmd = strtok((char*)args, " "); - if (!cmd) - { - handler->ShowHelpForCommand(handler->getCommandTable(), "help"); - handler->ShowHelpForCommand(handler->getCommandTable(), ""); - } - else - { - if (!handler->ShowHelpForCommand(handler->getCommandTable(), cmd)) - handler->SendSysMessage(LANG_NO_HELP_CMD); - } + Acore::ChatCommands::SendCommandHelpFor(*handler, cmd); + + if (cmd.empty()) + Acore::ChatCommands::SendCommandHelpFor(*handler, "help"); return true; } @@ -2242,36 +2241,33 @@ public: return true; } // mute player for some times - static bool HandleMuteCommand(ChatHandler* handler, char const* args) + static bool HandleMuteCommand(ChatHandler* handler, Optional<PlayerIdentifier> player, uint32 notSpeakTime, Tail muteReason) { - char* nameStr; - char* delayStr; - handler->extractOptFirstArg((char*)args, &nameStr, &delayStr); - if (!delayStr) - return false; + std::string muteReasonStr{ muteReason }; - char const* muteReason = strtok(nullptr, "\r"); - std::string muteReasonStr = handler->GetAcoreString(LANG_NO_REASON); - if (muteReason != nullptr) - muteReasonStr = muteReason; + if (muteReason.empty()) + muteReasonStr = handler->GetAcoreString(LANG_NO_REASON); - Player* target; - ObjectGuid targetGuid; - std::string targetName; - if (!handler->extractPlayerTarget(nameStr, &target, &targetGuid, &targetName)) + if (!player) + player = PlayerIdentifier::FromTarget(handler); + + if (!player) + { + handler->SendSysMessage(LANG_PLAYER_NOT_FOUND); + handler->SetSentErrorMessage(true); return false; + } - uint32 accountId = target ? target->GetSession()->GetAccountId() : sObjectMgr->GetPlayerAccountIdByGUID(targetGuid.GetCounter()); + Player* target = player->GetConnectedPlayer(); + uint32 accountId = target ? target->GetSession()->GetAccountId() : sObjectMgr->GetPlayerAccountIdByGUID(player->GetGUID().GetCounter()); // find only player from same account if any if (!target) if (WorldSession* session = sWorld->FindSession(accountId)) target = session->GetPlayer(); - uint32 notSpeakTime = uint32(atoi(delayStr)); - // must have strong lesser security level - if (handler->HasLowerSecurity (target, targetGuid, true)) + if (handler->HasLowerSecurity(target, player->GetGUID(), true)) return false; LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_UPD_MUTE_TIME); @@ -2287,7 +2283,7 @@ public: int64 muteTime = time(nullptr) + notSpeakTime * MINUTE; target->GetSession()->m_muteTime = muteTime; stmt->setInt64(0, muteTime); - std::string nameLink = handler->playerLink(targetName); + std::string nameLink = handler->playerLink(player->GetName()); if (sWorld->getBoolConfig(CONFIG_SHOW_MUTE_IN_WORLD)) sWorld->SendWorldText(LANG_COMMAND_MUTEMESSAGE_WORLD, muteBy.c_str(), nameLink.c_str(), notSpeakTime, muteReasonStr.c_str()); @@ -2297,21 +2293,22 @@ public: else { // Target is offline, mute will be in effect starting from the next login. - int32 muteTime = -int32(notSpeakTime * MINUTE); - stmt->setInt64(0, muteTime); + stmt->setInt32(0, -int32(notSpeakTime * MINUTE)); } stmt->setString(1, muteReasonStr); stmt->setString(2, muteBy); stmt->setUInt32(3, accountId); LoginDatabase.Execute(stmt); + stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_ACCOUNT_MUTE); stmt->setUInt32(0, accountId); stmt->setUInt32(1, notSpeakTime); stmt->setString(2, muteBy); stmt->setString(3, muteReasonStr); LoginDatabase.Execute(stmt); - std::string nameLink = handler->playerLink(targetName); + + std::string nameLink = handler->playerLink(player->GetName()); if (sWorld->getBoolConfig(CONFIG_SHOW_MUTE_IN_WORLD) && !target) sWorld->SendWorldText(LANG_COMMAND_MUTEMESSAGE_WORLD, muteBy.c_str(), nameLink.c_str(), notSpeakTime, muteReasonStr.c_str()); @@ -2322,7 +2319,8 @@ public: HashMapHolder<Player>::MapType const& m = ObjectAccessor::GetPlayers(); for (HashMapHolder<Player>::MapType::const_iterator itr = m.begin(); itr != m.end(); ++itr) if (itr->second->GetSession()->GetSecurity()) - ChatHandler(itr->second->GetSession()).PSendSysMessage(target ? LANG_YOU_DISABLE_CHAT : LANG_COMMAND_DISABLE_CHAT_DELAYED, (handler->GetSession() ? handler->GetSession()->GetPlayerName().c_str() : handler->GetAcoreString(LANG_CONSOLE)), nameLink.c_str(), notSpeakTime, muteReasonStr.c_str()); + ChatHandler(itr->second->GetSession()).PSendSysMessage(target ? LANG_YOU_DISABLE_CHAT : LANG_COMMAND_DISABLE_CHAT_DELAYED, + (handler->GetSession() ? handler->GetSession()->GetPlayerName().c_str() : handler->GetAcoreString(LANG_CONSOLE)), nameLink.c_str(), notSpeakTime, muteReasonStr.c_str()); } return true; diff --git a/src/server/scripts/Commands/cs_mmaps.cpp b/src/server/scripts/Commands/cs_mmaps.cpp index 84ea4a4930..e89d800171 100644 --- a/src/server/scripts/Commands/cs_mmaps.cpp +++ b/src/server/scripts/Commands/cs_mmaps.cpp @@ -36,14 +36,20 @@ #include "ScriptMgr.h" #include "TargetedMovementGenerator.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class mmaps_commandscript : public CommandScript { public: mmaps_commandscript() : CommandScript("mmaps_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> mmapCommandTable = + static ChatCommandTable mmapCommandTable = { { "loadedtiles", SEC_ADMINISTRATOR, false, &HandleMmapLoadedTilesCommand, "" }, { "loc", SEC_ADMINISTRATOR, false, &HandleMmapLocCommand, "" }, @@ -52,7 +58,7 @@ public: { "testarea", SEC_ADMINISTRATOR, false, &HandleMmapTestArea, "" }, }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "mmap", SEC_ADMINISTRATOR, true, nullptr, "", mmapCommandTable }, }; diff --git a/src/server/scripts/Commands/cs_modify.cpp b/src/server/scripts/Commands/cs_modify.cpp index aeda44699a..b035528280 100644 --- a/src/server/scripts/Commands/cs_modify.cpp +++ b/src/server/scripts/Commands/cs_modify.cpp @@ -32,14 +32,20 @@ EndScriptData */ #include "ScriptMgr.h" #include "StringConvert.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class modify_commandscript : public CommandScript { public: modify_commandscript() : CommandScript("modify_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> modifyspeedCommandTable = + static ChatCommandTable modifyspeedCommandTable = { { "fly", SEC_GAMEMASTER, false, &HandleModifyFlyCommand, "" }, { "all", SEC_GAMEMASTER, false, &HandleModifyASpeedCommand, "" }, @@ -49,7 +55,7 @@ public: { "", SEC_GAMEMASTER, false, &HandleModifyASpeedCommand, "" } }; - static std::vector<ChatCommand> modifyCommandTable = + static ChatCommandTable modifyCommandTable = { { "hp", SEC_GAMEMASTER, false, &HandleModifyHPCommand, "" }, { "mana", SEC_GAMEMASTER, false, &HandleModifyManaCommand, "" }, @@ -60,7 +66,7 @@ public: { "scale", SEC_GAMEMASTER, false, &HandleModifyScaleCommand, "" }, { "bit", SEC_GAMEMASTER, false, &HandleModifyBitCommand, "" }, { "faction", SEC_ADMINISTRATOR, false, &HandleModifyFactionCommand, "" }, - { "spell", SEC_GAMEMASTER, false, &HandleModifySpellCommand, "" }, + { "spell", SEC_CONSOLE, false, &HandleModifySpellCommand, "" }, { "talentpoints", SEC_GAMEMASTER, false, &HandleModifyTalentCommand, "" }, { "mount", SEC_GAMEMASTER, false, &HandleModifyMountCommand, "" }, { "honor", SEC_GAMEMASTER, false, &HandleModifyHonorCommand, "" }, @@ -73,13 +79,13 @@ public: { "speed", SEC_GAMEMASTER, false, nullptr, "", modifyspeedCommandTable } }; - static std::vector<ChatCommand> morphCommandTable = + static ChatCommandTable morphCommandTable = { { "reset", SEC_MODERATOR, false, &HandleMorphResetCommand, "" }, { "target", SEC_MODERATOR, false, &HandleMorphTargetCommand, "" } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "morph", SEC_MODERATOR, false, nullptr, "", morphCommandTable }, { "modify", SEC_GAMEMASTER, false, nullptr, "", modifyCommandTable } diff --git a/src/server/scripts/Commands/cs_npc.cpp b/src/server/scripts/Commands/cs_npc.cpp index 316064707e..bacc8ab711 100644 --- a/src/server/scripts/Commands/cs_npc.cpp +++ b/src/server/scripts/Commands/cs_npc.cpp @@ -34,6 +34,12 @@ EndScriptData */ #include "Transport.h" #include <string> +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + struct NpcFlagText { uint32 flag; @@ -136,9 +142,9 @@ class npc_commandscript : public CommandScript public: npc_commandscript() : CommandScript("npc_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> npcAddCommandTable = + static ChatCommandTable npcAddCommandTable = { { "formation", SEC_ADMINISTRATOR, false, &HandleNpcAddFormationCommand, "" }, { "item", SEC_ADMINISTRATOR, false, &HandleNpcAddVendorItemCommand, "" }, @@ -149,25 +155,25 @@ public: //} { "", SEC_ADMINISTRATOR, false, &HandleNpcAddCommand, "" } }; - static std::vector<ChatCommand> npcDeleteCommandTable = + static ChatCommandTable npcDeleteCommandTable = { { "item", SEC_ADMINISTRATOR, false, &HandleNpcDeleteVendorItemCommand, "" }, { "", SEC_ADMINISTRATOR, false, &HandleNpcDeleteCommand, "" } }; - static std::vector<ChatCommand> npcFollowCommandTable = + static ChatCommandTable npcFollowCommandTable = { { "stop", SEC_GAMEMASTER, false, &HandleNpcUnFollowCommand, "" }, { "", SEC_GAMEMASTER, false, &HandleNpcFollowCommand, "" } }; - static std::vector<ChatCommand> npcFactionCommandTable = + static ChatCommandTable npcFactionCommandTable = { { "permanent", SEC_ADMINISTRATOR, false, &HandleNpcSetFactionIdCommand, "" }, { "temp", SEC_ADMINISTRATOR, false, &HandleNpcSetFactionTempIdCommand, "" }, { "original", SEC_ADMINISTRATOR, false, &HandleNpcSetOriginalFaction, "" } }; - static std::vector<ChatCommand> npcSetCommandTable = + static ChatCommandTable npcSetCommandTable = { { "allowmove", SEC_ADMINISTRATOR, false, &HandleNpcSetAllowMovementCommand, "" }, { "entry", SEC_ADMINISTRATOR, false, &HandleNpcSetEntryCommand, "" }, @@ -186,7 +192,7 @@ public: { "subname", SEC_ADMINISTRATOR, false, &HandleNpcSetSubNameCommand, "" } //} }; - static std::vector<ChatCommand> npcCommandTable = + static ChatCommandTable npcCommandTable = { { "info", SEC_MODERATOR, false, &HandleNpcInfoCommand, "" }, { "near", SEC_GAMEMASTER, false, &HandleNpcNearCommand, "" }, @@ -202,7 +208,7 @@ public: { "follow", SEC_GAMEMASTER, false, nullptr, "", npcFollowCommandTable }, { "set", SEC_ADMINISTRATOR, false, nullptr, "", npcSetCommandTable } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "npc", SEC_MODERATOR, false, nullptr, "", npcCommandTable } }; @@ -705,7 +711,7 @@ public: creature->AI()->SetData(data_1, data_2); std::string AIorScript = creature->GetAIName() != "" ? "AI type: " + creature->GetAIName() : (creature->GetScriptName() != "" ? "Script Name: " + creature->GetScriptName() : "No AI or Script Name Set"); - handler->PSendSysMessage(LANG_NPC_SETDATA, creature->GetGUID(), creature->GetEntry(), creature->GetName().c_str(), data_1, data_2, AIorScript.c_str()); + handler->PSendSysMessage(LANG_NPC_SETDATA, creature->GetGUID().GetCounter(), creature->GetEntry(), creature->GetName().c_str(), data_1, data_2, AIorScript.c_str()); return true; } diff --git a/src/server/scripts/Commands/cs_player.cpp b/src/server/scripts/Commands/cs_player.cpp index a5f2ce2d8e..46c82a927d 100644 --- a/src/server/scripts/Commands/cs_player.cpp +++ b/src/server/scripts/Commands/cs_player.cpp @@ -21,20 +21,26 @@ #include "PlayerCommand.h" #include "ScriptMgr.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class player_commandscript : public CommandScript, public PlayerCommand { public: player_commandscript() : CommandScript("player_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> playerCommandTable = + static ChatCommandTable playerCommandTable = { { "learn", SEC_GAMEMASTER, true, &HandlePlayerLearnCommand, "" }, { "unlearn", SEC_GAMEMASTER, true, &HandlePlayerUnLearnCommand, "" } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "player", SEC_GAMEMASTER, true, nullptr, "", playerCommandTable } }; diff --git a/src/server/scripts/Commands/cs_quest.cpp b/src/server/scripts/Commands/cs_quest.cpp index baa8d99680..bfe9a1c22d 100644 --- a/src/server/scripts/Commands/cs_quest.cpp +++ b/src/server/scripts/Commands/cs_quest.cpp @@ -28,21 +28,27 @@ EndScriptData */ #include "ReputationMgr.h" #include "ScriptMgr.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class quest_commandscript : public CommandScript { public: quest_commandscript() : CommandScript("quest_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> questCommandTable = + static ChatCommandTable questCommandTable = { { "add", SEC_GAMEMASTER, false, &HandleQuestAdd, "" }, { "complete", SEC_GAMEMASTER, false, &HandleQuestComplete, "" }, { "remove", SEC_GAMEMASTER, false, &HandleQuestRemove, "" }, { "reward", SEC_GAMEMASTER, false, &HandleQuestReward, "" }, }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "quest", SEC_GAMEMASTER, false, nullptr, "", questCommandTable }, }; diff --git a/src/server/scripts/Commands/cs_reload.cpp b/src/server/scripts/Commands/cs_reload.cpp index 603d79a902..49639bdd10 100644 --- a/src/server/scripts/Commands/cs_reload.cpp +++ b/src/server/scripts/Commands/cs_reload.cpp @@ -44,14 +44,20 @@ EndScriptData */ #include "StringConvert.h" #include "Tokenize.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class reload_commandscript : public CommandScript { public: reload_commandscript() : CommandScript("reload_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> reloadAllCommandTable = + static ChatCommandTable reloadAllCommandTable = { { "achievement", SEC_ADMINISTRATOR, true, &HandleReloadAllAchievementCommand, "" }, { "area", SEC_ADMINISTRATOR, true, &HandleReloadAllAreaCommand, "" }, @@ -65,7 +71,7 @@ public: { "spell", SEC_ADMINISTRATOR, true, &HandleReloadAllSpellCommand, "" }, { "", SEC_ADMINISTRATOR, true, &HandleReloadAllCommand, "" } }; - static std::vector<ChatCommand> reloadCommandTable = + static ChatCommandTable reloadCommandTable = { { "auctions", SEC_ADMINISTRATOR, true, &HandleReloadAuctionsCommand, "" }, { "dungeon_access_template", SEC_ADMINISTRATOR, true, &HandleReloadDungeonAccessCommand, "" }, @@ -85,7 +91,7 @@ public: { "config", SEC_ADMINISTRATOR, true, &HandleReloadConfigCommand, "" }, { "creature_text", SEC_ADMINISTRATOR, true, &HandleReloadCreatureText, "" }, { "creature_questender", SEC_ADMINISTRATOR, true, &HandleReloadCreatureQuestEnderCommand, "" }, - { "creature_linked_respawn", SEC_GAMEMASTER, true, &HandleReloadLinkedRespawnCommand, "" }, + { "creature_linked_respawn", SEC_ADMINISTRATOR, true, &HandleReloadLinkedRespawnCommand, "" }, { "creature_loot_template", SEC_ADMINISTRATOR, true, &HandleReloadLootTemplatesCreatureCommand, "" }, { "creature_onkill_reputation", SEC_ADMINISTRATOR, true, &HandleReloadOnKillReputationCommand, "" }, { "creature_queststarter", SEC_ADMINISTRATOR, true, &HandleReloadCreatureQuestStarterCommand, "" }, @@ -161,7 +167,7 @@ public: { "vehicle_accessory", SEC_ADMINISTRATOR, true, &HandleReloadVehicleAccessoryCommand, "" }, { "vehicle_template_accessory", SEC_ADMINISTRATOR, true, &HandleReloadVehicleTemplateAccessoryCommand, "" } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "reload", SEC_ADMINISTRATOR, true, nullptr, "", reloadCommandTable } }; @@ -405,9 +411,13 @@ public: static bool HandleReloadCommandCommand(ChatHandler* handler, const char* /*args*/) { - handler->SetLoadCommandTable(true); - handler->SendGlobalGMSysMessage("DB table `command` will be reloaded at next chat command use."); - return true; + LOG_INFO("server.loading", "Reloading .command information..."); + Acore::ChatCommands::LoadCommandMap(); + handler->SendGlobalGMSysMessage("DB table `command` reloaded."); + + // do not log this invocation, otherwise we might crash (the command table we used to get here is no longer valid!) + handler->SetSentErrorMessage(true); + return false; } static bool HandleReloadOnKillReputationCommand(ChatHandler* handler, const char* /*args*/) diff --git a/src/server/scripts/Commands/cs_reset.cpp b/src/server/scripts/Commands/cs_reset.cpp index b22ca5ab33..f1c1e86076 100644 --- a/src/server/scripts/Commands/cs_reset.cpp +++ b/src/server/scripts/Commands/cs_reset.cpp @@ -30,14 +30,20 @@ EndScriptData */ #include "Player.h" #include "ScriptMgr.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class reset_commandscript : public CommandScript { public: reset_commandscript() : CommandScript("reset_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> resetCommandTable = + static ChatCommandTable resetCommandTable = { { "achievements", SEC_CONSOLE, true, &HandleResetAchievementsCommand, "" }, { "honor", SEC_ADMINISTRATOR, true, &HandleResetHonorCommand, "" }, @@ -47,7 +53,7 @@ public: { "talents", SEC_ADMINISTRATOR, true, &HandleResetTalentsCommand, "" }, { "all", SEC_CONSOLE, true, &HandleResetAllCommand, "" } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "reset", SEC_ADMINISTRATOR, true, nullptr, "", resetCommandTable } }; diff --git a/src/server/scripts/Commands/cs_server.cpp b/src/server/scripts/Commands/cs_server.cpp index 55f6ca2c02..9dae990b3a 100644 --- a/src/server/scripts/Commands/cs_server.cpp +++ b/src/server/scripts/Commands/cs_server.cpp @@ -41,38 +41,44 @@ EndScriptData */ #include <openssl/opensslv.h> #include <numeric> +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class server_commandscript : public CommandScript { public: server_commandscript() : CommandScript("server_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> serverIdleRestartCommandTable = + static ChatCommandTable serverIdleRestartCommandTable = { - { "cancel", SEC_ADMINISTRATOR, true, &HandleServerShutDownCancelCommand, "" }, - { "", SEC_ADMINISTRATOR, true, &HandleServerIdleRestartCommand, "" } + { "cancel", SEC_ADMINISTRATOR, true, &HandleServerShutDownCancelCommand, "" }, + { "", SEC_CONSOLE, true, &HandleServerIdleRestartCommand, "" } }; - static std::vector<ChatCommand> serverIdleShutdownCommandTable = + static ChatCommandTable serverIdleShutdownCommandTable = { - { "cancel", SEC_ADMINISTRATOR, true, &HandleServerShutDownCancelCommand, "" }, - { "", SEC_ADMINISTRATOR, true, &HandleServerIdleShutDownCommand, "" } + { "cancel", SEC_ADMINISTRATOR, true, &HandleServerShutDownCancelCommand, "" }, + { "", SEC_CONSOLE, true, &HandleServerIdleShutDownCommand, "" } }; - static std::vector<ChatCommand> serverRestartCommandTable = + static ChatCommandTable serverRestartCommandTable = { - { "cancel", SEC_ADMINISTRATOR, true, &HandleServerShutDownCancelCommand, "" }, - { "", SEC_ADMINISTRATOR, true, &HandleServerRestartCommand, "" } + { "cancel", SEC_ADMINISTRATOR, true, &HandleServerShutDownCancelCommand, "" }, + { "", SEC_ADMINISTRATOR, true, &HandleServerRestartCommand, "" } }; - static std::vector<ChatCommand> serverShutdownCommandTable = + static ChatCommandTable serverShutdownCommandTable = { - { "cancel", SEC_ADMINISTRATOR, true, &HandleServerShutDownCancelCommand, "" }, - { "", SEC_ADMINISTRATOR, true, &HandleServerShutDownCommand, "" } + { "cancel", SEC_ADMINISTRATOR, true, &HandleServerShutDownCancelCommand, "" }, + { "", SEC_ADMINISTRATOR, true, &HandleServerShutDownCommand, "" } }; - static std::vector<ChatCommand> serverSetCommandTable = + static ChatCommandTable serverSetCommandTable = { { "difftime", SEC_CONSOLE, true, &HandleServerSetDiffTimeCommand, "" }, { "loglevel", SEC_CONSOLE, true, &HandleServerSetLogLevelCommand, "" }, @@ -80,7 +86,7 @@ public: { "closed", SEC_CONSOLE, true, &HandleServerSetClosedCommand, "" } }; - static std::vector<ChatCommand> serverCommandTable = + static ChatCommandTable serverCommandTable = { { "corpses", SEC_GAMEMASTER, true, &HandleServerCorpsesCommand, "" }, { "debug", SEC_ADMINISTRATOR, true, &HandleServerDebugCommand, "" }, @@ -94,7 +100,7 @@ public: { "set", SEC_ADMINISTRATOR, true, nullptr, "", serverSetCommandTable } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "server", SEC_PLAYER, true, nullptr, "", serverCommandTable } }; diff --git a/src/server/scripts/Commands/cs_spectator.cpp b/src/server/scripts/Commands/cs_spectator.cpp index 9b63f29283..b5d71f9fbb 100644 --- a/src/server/scripts/Commands/cs_spectator.cpp +++ b/src/server/scripts/Commands/cs_spectator.cpp @@ -23,25 +23,31 @@ #include "ScriptMgr.h" #include "World.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class spectator_commandscript : public CommandScript { public: spectator_commandscript() : CommandScript("spectator_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> spectatorCommandTable = + static ChatCommandTable spectatorCommandTable = { - { "version", SEC_CONSOLE, false, &HandleSpectatorVersionCommand, "" }, - { "reset", SEC_CONSOLE, false, &HandleSpectatorResetCommand, "" }, - { "spectate", SEC_CONSOLE, false, &HandleSpectatorSpectateCommand, "" }, - { "watch", SEC_CONSOLE, false, &HandleSpectatorWatchCommand, "" }, - { "leave", SEC_CONSOLE, false, &HandleSpectatorLeaveCommand, "" }, - { "", SEC_CONSOLE, false, &HandleSpectatorCommand, "" } + { "version", SEC_PLAYER, false, &HandleSpectatorVersionCommand, "" }, + { "reset", SEC_PLAYER, false, &HandleSpectatorResetCommand, "" }, + { "spectate", SEC_PLAYER, false, &HandleSpectatorSpectateCommand, "" }, + { "watch", SEC_PLAYER, false, &HandleSpectatorWatchCommand, "" }, + { "leave", SEC_PLAYER, false, &HandleSpectatorLeaveCommand, "" }, + { "", SEC_PLAYER, false, &HandleSpectatorCommand, "" } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { - { "spect", SEC_CONSOLE, false, nullptr, "", spectatorCommandTable } + { "spect", SEC_PLAYER, false, nullptr, "", spectatorCommandTable } }; return commandTable; } diff --git a/src/server/scripts/Commands/cs_tele.cpp b/src/server/scripts/Commands/cs_tele.cpp index aafc767547..7866f955cf 100644 --- a/src/server/scripts/Commands/cs_tele.cpp +++ b/src/server/scripts/Commands/cs_tele.cpp @@ -30,14 +30,20 @@ EndScriptData */ #include "Player.h" #include "ScriptMgr.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class tele_commandscript : public CommandScript { public: tele_commandscript() : CommandScript("tele_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> teleCommandTable = + static ChatCommandTable teleCommandTable = { { "add", SEC_ADMINISTRATOR, false, &HandleTeleAddCommand, "" }, { "del", SEC_ADMINISTRATOR, true, &HandleTeleDelCommand, "" }, @@ -45,7 +51,7 @@ public: { "group", SEC_GAMEMASTER, false, &HandleTeleGroupCommand, "" }, { "", SEC_MODERATOR, false, &HandleTeleCommand, "" } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "teleport", SEC_MODERATOR, false, nullptr, "", teleCommandTable } }; @@ -92,82 +98,37 @@ public: return true; } - static bool HandleTeleDelCommand(ChatHandler* handler, const char* args) + static bool HandleTeleDelCommand(ChatHandler* handler, GameTele const* tele) { - if (!*args) - return false; - - // id, or string, or [name] Shift-click form |color|Htele:id|h[name]|h|r - GameTele const* tele = handler->extractGameTeleFromLink((char*)args); if (!tele) { handler->SendSysMessage(LANG_COMMAND_TELE_NOTFOUND); handler->SetSentErrorMessage(true); return false; } + std::string name = tele->name; sObjectMgr->DeleteGameTele(name); handler->SendSysMessage(LANG_COMMAND_TP_DELETED); return true; } - // teleport player to given game_tele.entry - static bool HandleTeleNameCommand(ChatHandler* handler, const char* args) + static bool DoNameTeleport(ChatHandler* handler, PlayerIdentifier player, uint32 mapId, Position const& pos, std::string const& locationName) { - char* nameStr; - char* teleStr; - handler->extractOptFirstArg((char*)args, &nameStr, &teleStr); - if (!teleStr) - return false; - - Player* target; - ObjectGuid target_guid; - std::string target_name; - if (!handler->extractPlayerTarget(nameStr, &target, &target_guid, &target_name)) - return false; - - if (strcmp(teleStr, "$home") == 0) // References target's homebind + if (!MapMgr::IsValidMapCoord(mapId, pos) || sObjectMgr->IsTransportMap(mapId)) { - if (target) - target->TeleportTo(target->m_homebindMapId, target->m_homebindX, target->m_homebindY, target->m_homebindZ, target->GetOrientation()); - else - { - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_HOMEBIND); - stmt->setUInt32(0, target_guid.GetCounter()); - PreparedQueryResult resultDB = CharacterDatabase.Query(stmt); - - if (resultDB) - { - Field* fieldsDB = resultDB->Fetch(); - uint32 mapId = fieldsDB[0].GetUInt16(); - uint32 zoneId = fieldsDB[1].GetUInt16(); - float posX = fieldsDB[2].GetFloat(); - float posY = fieldsDB[3].GetFloat(); - float posZ = fieldsDB[4].GetFloat(); - - Player::SavePositionInDB(mapId, posX, posY, posZ, 0, zoneId, target_guid); - } - } - - return true; - } - - // id, or string, or [name] Shift-click form |color|Htele:id|h[name]|h|r - GameTele const* tele = handler->extractGameTeleFromLink(teleStr); - if (!tele) - { - handler->SendSysMessage(LANG_COMMAND_TELE_NOTFOUND); + handler->PSendSysMessage(LANG_INVALID_TARGET_COORD, pos.GetPositionX(), pos.GetPositionY(), mapId); handler->SetSentErrorMessage(true); return false; } - if (target) + if (Player* target = player.GetConnectedPlayer()) { // check online security - if (handler->HasLowerSecurity(target)) + if (handler->HasLowerSecurity(target, ObjectGuid::Empty)) return false; - std::string chrNameLink = handler->playerLink(target_name); + std::string chrNameLink = handler->playerLink(target->GetName()); if (target->IsBeingTeleported()) { @@ -176,9 +137,9 @@ public: return false; } - handler->PSendSysMessage(LANG_TELEPORTING_TO, chrNameLink.c_str(), "", tele->name.c_str()); + handler->PSendSysMessage(LANG_TELEPORTING_TO, chrNameLink.c_str(), "", locationName.c_str()); if (handler->needReportToTarget(target)) - (ChatHandler(target->GetSession())).PSendSysMessage(LANG_TELEPORTED_TO_BY, handler->GetNameLink().c_str()); + ChatHandler(target->GetSession()).PSendSysMessage(LANG_TELEPORTED_TO_BY, handler->GetNameLink().c_str()); // stop flight if need if (target->IsInFlight()) @@ -186,33 +147,73 @@ public: target->GetMotionMaster()->MovementExpired(); target->CleanupAfterTaxiFlight(); } - // save only in non-flight case - else + else // save only in non-flight case target->SaveRecallPosition(); - target->TeleportTo(tele->mapId, tele->position_x, tele->position_y, tele->position_z, tele->orientation); + target->TeleportTo({ mapId, pos }); } else { // check offline security - if (handler->HasLowerSecurity(nullptr, target_guid)) + if (handler->HasLowerSecurity(nullptr, player.GetGUID())) return false; - std::string nameLink = handler->playerLink(target_name); + std::string nameLink = handler->playerLink(player.GetName()); + + handler->PSendSysMessage(LANG_TELEPORTING_TO, nameLink.c_str(), handler->GetAcoreString(LANG_OFFLINE), locationName.c_str()); - handler->PSendSysMessage(LANG_TELEPORTING_TO, nameLink.c_str(), handler->GetAcoreString(LANG_OFFLINE), tele->name.c_str()); - Player::SavePositionInDB(tele->mapId, tele->position_x, tele->position_y, tele->position_z, tele->orientation, - sMapMgr->GetZoneId(PHASEMASK_NORMAL, tele->mapId, tele->position_x, tele->position_y, tele->position_z), target_guid); + Player::SavePositionInDB({mapId, pos}, sMapMgr->GetZoneId(PHASEMASK_NORMAL, {mapId, pos}), player.GetGUID(), nullptr); } return true; } + // teleport player to given game_tele.entry + static bool HandleTeleNameCommand(ChatHandler* handler, Optional<PlayerIdentifier> player, Variant<GameTele const*, EXACT_SEQUENCE("$home")> where) + { + if (!player) + player = PlayerIdentifier::FromTargetOrSelf(handler); + + if (!player) + return false; + + if (where.index() == 1) // References target's homebind + { + if (Player* target = player->GetConnectedPlayer()) + target->TeleportTo(target->m_homebindMapId, target->m_homebindX, target->m_homebindY, target->m_homebindZ, target->GetOrientation()); + else + { + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_HOMEBIND); + stmt->setUInt32(0, player->GetGUID().GetCounter()); + PreparedQueryResult resultDB = CharacterDatabase.Query(stmt); + + if (resultDB) + { + Field* fieldsDB = resultDB->Fetch(); + WorldLocation loc(fieldsDB[0].GetUInt16(), fieldsDB[2].GetFloat(), fieldsDB[3].GetFloat(), fieldsDB[4].GetFloat(), 0.0f); + uint32 zoneId = fieldsDB[1].GetUInt16(); + + Player::SavePositionInDB(loc, zoneId, player->GetGUID(), nullptr); + } + } + + return true; + } + + // id, or string, or [name] Shift-click form |color|Htele:id|h[name]|h|r + GameTele const* tele = where.get<GameTele const*>(); + return DoNameTeleport(handler, *player, tele->mapId, { tele->position_x, tele->position_y, tele->position_z, tele->orientation }, tele->name); + } + //Teleport group to given game_tele.entry - static bool HandleTeleGroupCommand(ChatHandler* handler, const char* args) + static bool HandleTeleGroupCommand(ChatHandler* handler, GameTele const* tele) { - if (!*args) + if (!tele) + { + handler->SendSysMessage(LANG_COMMAND_TELE_NOTFOUND); + handler->SetSentErrorMessage(true); return false; + } Player* target = handler->getSelectedPlayer(); if (!target) @@ -226,15 +227,6 @@ public: if (handler->HasLowerSecurity(target)) return false; - // id, or string, or [name] Shift-click form |color|Htele:id|h[name]|h|r - GameTele const* tele = handler->extractGameTeleFromLink((char*)args); - if (!tele) - { - handler->SendSysMessage(LANG_COMMAND_TELE_NOTFOUND); - handler->SetSentErrorMessage(true); - return false; - } - MapEntry const* map = sMapStore.LookupEntry(tele->mapId); if (!map || map->IsBattlegroundOrArena()) { @@ -292,16 +284,17 @@ public: return true; } - static bool HandleTeleCommand(ChatHandler* handler, const char* args) + static bool HandleTeleCommand(ChatHandler* handler, GameTele const* tele) { - if (!*args) + if (!tele) + { + handler->SendSysMessage(LANG_COMMAND_TELE_NOTFOUND); + handler->SetSentErrorMessage(true); return false; + } Player* me = handler->GetSession()->GetPlayer(); - // id, or string, or [name] Shift-click form |color|Htele:id|h[name]|h|r - GameTele const* tele = handler->extractGameTeleFromLink((char*)args); - if (!tele) { handler->SendSysMessage(LANG_COMMAND_TELE_NOTFOUND); diff --git a/src/server/scripts/Commands/cs_ticket.cpp b/src/server/scripts/Commands/cs_ticket.cpp index fc91ecca71..cc4d2af9fa 100644 --- a/src/server/scripts/Commands/cs_ticket.cpp +++ b/src/server/scripts/Commands/cs_ticket.cpp @@ -32,19 +32,25 @@ EndScriptData */ #include "ScriptMgr.h" #include "TicketMgr.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class ticket_commandscript : public CommandScript { public: ticket_commandscript() : CommandScript("ticket_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> ticketResponseCommandTable = + static ChatCommandTable ticketResponseCommandTable = { { "append", SEC_GAMEMASTER, true, &HandleGMTicketResponseAppendCommand, "" }, { "appendln", SEC_GAMEMASTER, true, &HandleGMTicketResponseAppendLnCommand, "" } }; - static std::vector<ChatCommand> ticketCommandTable = + static ChatCommandTable ticketCommandTable = { { "assign", SEC_GAMEMASTER, true, &HandleGMTicketAssignToCommand, "" }, { "close", SEC_GAMEMASTER, true, &HandleGMTicketCloseByIdCommand, "" }, @@ -63,7 +69,7 @@ public: { "viewid", SEC_GAMEMASTER, true, &HandleGMTicketGetByIdCommand, "" }, { "viewname", SEC_GAMEMASTER, true, &HandleGMTicketGetByNameCommand, "" } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "ticket", SEC_GAMEMASTER, false, nullptr, "", ticketCommandTable } }; diff --git a/src/server/scripts/Commands/cs_titles.cpp b/src/server/scripts/Commands/cs_titles.cpp index 368910bf65..55f497948b 100644 --- a/src/server/scripts/Commands/cs_titles.cpp +++ b/src/server/scripts/Commands/cs_titles.cpp @@ -28,25 +28,31 @@ EndScriptData */ #include "Player.h" #include "ScriptMgr.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class titles_commandscript : public CommandScript { public: titles_commandscript() : CommandScript("titles_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> titlesSetCommandTable = + static ChatCommandTable titlesSetCommandTable = { { "mask", SEC_GAMEMASTER, false, &HandleTitlesSetMaskCommand, "" } }; - static std::vector<ChatCommand> titlesCommandTable = + static ChatCommandTable titlesCommandTable = { { "add", SEC_GAMEMASTER, false, &HandleTitlesAddCommand, "" }, { "current", SEC_GAMEMASTER, false, &HandleTitlesCurrentCommand, "" }, { "remove", SEC_GAMEMASTER, false, &HandleTitlesRemoveCommand, "" }, { "set", SEC_GAMEMASTER, false, nullptr, "", titlesSetCommandTable } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "titles", SEC_GAMEMASTER, false, nullptr, "", titlesCommandTable } }; diff --git a/src/server/scripts/Commands/cs_wp.cpp b/src/server/scripts/Commands/cs_wp.cpp index 9b215ee93a..0fe661f3c2 100644 --- a/src/server/scripts/Commands/cs_wp.cpp +++ b/src/server/scripts/Commands/cs_wp.cpp @@ -29,14 +29,20 @@ EndScriptData */ #include "ScriptMgr.h" #include "WaypointMgr.h" +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + class wp_commandscript : public CommandScript { public: wp_commandscript() : CommandScript("wp_commandscript") { } - std::vector<ChatCommand> GetCommands() const override + ChatCommandTable GetCommands() const override { - static std::vector<ChatCommand> wpCommandTable = + static ChatCommandTable wpCommandTable = { { "add", SEC_ADMINISTRATOR, false, &HandleWpAddCommand, "" }, { "event", SEC_ADMINISTRATOR, false, &HandleWpEventCommand, "" }, @@ -46,7 +52,7 @@ public: { "reload", SEC_ADMINISTRATOR, false, &HandleWpReloadCommand, "" }, { "show", SEC_ADMINISTRATOR, false, &HandleWpShowCommand, "" } }; - static std::vector<ChatCommand> commandTable = + static ChatCommandTable commandTable = { { "wp", SEC_ADMINISTRATOR, false, nullptr, "", wpCommandTable } }; diff --git a/src/server/shared/DataStores/DBCStructure.h b/src/server/shared/DataStores/DBCStructure.h index 4f45dd5d2a..6db18d0343 100644 --- a/src/server/shared/DataStores/DBCStructure.h +++ b/src/server/shared/DataStores/DBCStructure.h @@ -41,7 +41,7 @@ struct AchievementEntry int32 requiredFaction; // 1 -1=all, 0=horde, 1=alliance int32 mapID; // 2 -1=none //uint32 parentAchievement; // 3 its Achievement parent (can`t start while parent uncomplete, use its Criteria if don`t have own, use its progress on begin) - char* name[16]; // 4-19 + std::array<char const*, 16> name; // 4-19 //uint32 name_flags; // 20 //char *description[16]; // 21-36 //uint32 desc_flags; // 37 @@ -58,8 +58,8 @@ struct AchievementEntry struct AchievementCategoryEntry { - int32 ID; // 0 - int32 parentCategory; // 1 -1 for main category + int32 ID; // 0 + int32 parentCategory; // 1 -1 for main category //char *name[16]; // 2-17 //uint32 name_flags; // 18 //uint32 sortOrder; // 19 @@ -258,7 +258,7 @@ struct AchievementCriteriaEntry struct { uint32 teamtype; // 3 {2, 3, 5} - uint32 PersonalRating; // 4 + uint32 PersonalRating; // 4 } highest_personal_rating; // ACHIEVEMENT_CRITERIA_TYPE_LEARN_SKILL_LEVEL = 40 @@ -503,7 +503,7 @@ struct AchievementCriteriaEntry uint32 additionalRequirement_value; } additionalRequirements[MAX_CRITERIA_REQUIREMENTS]; - //char* name[16]; // 9-24 + //char const* name[16]; // 9-24 //uint32 name_flags; // 25 uint32 flags; // 26 uint32 timedType; // 27 @@ -523,7 +523,7 @@ struct AreaTableEntry uint32 flags; // 4, unknown value but 312 for all cities // 5-9 unused int32 area_level; // 10 - char* area_name[16]; // 11-26 + char const* area_name[16]; // 11-26 // 27, string flags, unused uint32 team; // 28 uint32 LiquidTypeOverride[4]; // 29-32 liquid override by type @@ -553,20 +553,20 @@ struct AreaGroupEntry struct AreaPOIEntry { - uint32 id; //0 - uint32 icon[11]; //1-11 - float x; //12 - float y; //13 - float z; //14 - uint32 mapId; //15 - //uint32 val1; //16 - uint32 zoneId; //17 - //char* name[16]; //18-33 - //uint32 name_flag; //34 - //char* name2[16]; //35-50 - //uint32 name_flag2; //51 - uint32 worldState; //52 - //uint32 val2; //53 + uint32 id; //0 + uint32 icon[11]; //1-11 + float x; //12 + float y; //13 + float z; //14 + uint32 mapId; //15 + //uint32 val1; //16 + uint32 zoneId; //17 + //char const* name[16]; //18-33 + //uint32 name_flag; //34 + //char const* name2[16]; //35-50 + //uint32 name_flag2; //51 + uint32 worldState; //52 + //uint32 val2; //53 }; struct AuctionHouseEntry @@ -575,7 +575,7 @@ struct AuctionHouseEntry uint32 faction; // 1 id of faction.dbc for player factions associated with city uint32 depositPercent; // 2 1/3 from real uint32 cutPercent; // 3 - //char* name[16]; // 4-19 + //char const* name[16]; // 4-19 // 20 string flag, unused }; @@ -589,7 +589,7 @@ struct BarberShopStyleEntry { uint32 Id; // 0 uint32 type; // 1 value 0 -> hair, value 2 -> facialhair - //char* name[16]; // 2-17 name of hair style + //char const* name[16]; // 2-17 name of hair style //uint32 name_flags; // 18 //uint32 unk_name[16]; // 19-34, all empty //uint32 unk_flags; // 35 @@ -605,7 +605,7 @@ struct BattlemasterListEntry int32 mapid[8]; // 1-8 mapid uint32 type; // 9 (3 - BG, 4 - arena) //uint32 canJoinAsGroup; // 10 (0 or 1) - char* name[16]; // 11-26 + char const* name[16]; // 11-26 //uint32 nameFlags // 27 string flag, unused uint32 maxGroupSize; // 28 maxGroupSize, used for checking if queue as group uint32 HolidayWorldStateId; // 29 new 3.1 @@ -631,9 +631,9 @@ struct CharTitlesEntry { uint32 ID; // 0, title ids, for example in Quest::GetCharTitleId() //uint32 unk1; // 1 flags? - char* nameMale[16]; // 2-17 + char const* nameMale[16]; // 2-17 // 18 string flag, unused - char* nameFemale[16]; // 19-34 + char const* nameFemale[16]; // 19-34 // 35 string flag, unused uint32 bit_index; // 36 used in PLAYER_CHOSEN_TITLE and 1<<index in PLAYER__FIELD_KNOWN_TITLES }; @@ -642,9 +642,9 @@ struct ChatChannelsEntry { uint32 ChannelID; // 0 uint32 flags; // 1 - char* pattern[16]; // 3-18 + char const* pattern[16]; // 3-18 // 19 string flags, unused - //char* name[16]; // 20-35 unused + //char const* name[16]; // 20-35 unused // 36 string flag, unused }; @@ -654,17 +654,17 @@ struct ChrClassesEntry // 1, unused uint32 powerType; // 2 // 3-4, unused - char* name[16]; // 5-20 unused + char const* name[16]; // 5-20 unused // 21 string flag, unused - //char* nameFemale[16]; // 21-36 unused, if different from base (male) case + //char const* nameFemale[16]; // 21-36 unused, if different from base (male) case // 37 string flag, unused - //char* nameNeutralGender[16]; // 38-53 unused, if different from base (male) case + //char const* nameNeutralGender[16]; // 38-53 unused, if different from base (male) case // 54 string flag, unused // 55, unused uint32 spellfamily; // 56 // 57, unused uint32 CinematicSequence; // 58 id from CinematicSequences.dbc - uint32 expansion; // 59 (0 - original race, 1 - tbc addon, ...) + uint32 expansion; // 59 (0 - original race, 1 - tbc addon, ...) }; struct ChrRacesEntry @@ -680,11 +680,11 @@ struct ChrRacesEntry // 8-11 unused uint32 CinematicSequence; // 12 id from CinematicSequences.dbc //uint32 unk_322; // 13 faction (0 alliance, 1 horde, 2 not available?) - char* name[16]; // 14-29 used for DBC language detection/selection + char const* name[16]; // 14-29 used for DBC language detection/selection // 30 string flags, unused - //char* nameFemale[16]; // 31-46, if different from base (male) case + //char const* nameFemale[16]; // 31-46, if different from base (male) case // 47 string flags, unused - //char* nameNeutralGender[16]; // 48-63, if different from base (male) case + //char const* nameNeutralGender[16]; // 48-63, if different from base (male) case // 64 string flags, unused // 65-67 unused uint32 expansion; // 68 (0 - original race, 1 - tbc addon, ...) @@ -693,7 +693,7 @@ struct ChrRacesEntry struct CinematicCameraEntry { uint32 id; // 0 index - char* filename; // 1 + char const* filename; // 1 uint32 soundid; // 2 in SoundEntries.dbc or 0 float base_x; // 3 float base_y; // 4 @@ -738,7 +738,7 @@ struct CreatureFamilyEntry uint32 petFoodMask; // 7 m_petFoodMask int32 petTalentType; // 8 m_petTalentType // 9 m_categoryEnumID - char* Name[16]; // 10-25 m_name_lang + char const* Name[16]; // 10-25 m_name_lang // 26 string flags // 27 m_iconFile }; @@ -747,7 +747,7 @@ struct CreatureModelDataEntry { uint32 Id; //uint32 Flags; - //char* ModelPath[16] + //char const* ModelPath[16] //uint32 Unk1; float Scale; // Used in calculation of unit collision data //int32 Unk2 @@ -777,7 +777,7 @@ struct CreatureSpellDataEntry struct CreatureTypeEntry { uint32 ID; // 0 m_ID - //char* Name[16]; // 1-16 name + //char const* Name[16]; // 1-16 name // 17 string flags //uint32 no_expirience; // 18 no exp? critters, non-combat pets, gas cloud. }; @@ -787,7 +787,7 @@ struct CurrencyCategoryEntry { uint32 ID; // 0 uint32 Unk1; // 1 0 for known categories and 3 for unknown one (3.0.9) - char* Name[16]; // 2-17 name + char const* Name[16]; // 2-17 name // // 18 string flags }; */ @@ -830,7 +830,7 @@ struct DungeonEncounterEntry uint32 difficulty; // 2 instance mode //uint32 unk0; // 3 uint32 encounterIndex; // 4 encounter index for creating completed mask - char* encounterName[16]; // 5-20 encounter name + char const* encounterName[16]; // 5-20 encounter name //uint32 nameFlags; // 21 //uint32 unk1; // 22 }; @@ -850,7 +850,7 @@ struct DurabilityQualityEntry struct EmotesEntry { uint32 Id; // 0 - //char* Name; // 1, internal name + //char const* Name; // 1, internal name //uint32 AnimationId; // 2, ref to animationData uint32 Flags; // 3, bitmask, may be unit_flags uint32 EmoteType; // 4, Can be 0, 1 or 2 (determine how emote are shown) @@ -877,9 +877,9 @@ struct FactionEntry float spilloverRateOut; // 20 Faction outputs rep * spilloverRateOut as spillover reputation uint32 spilloverMaxRankIn; // 21 The highest rank the faction will profit from incoming spillover //uint32 spilloverRank_unk; // 22 It does not seem to be the max standing at which a faction outputs spillover ...so no idea - char* name[16]; // 23-38 m_name_lang + char const* name[16]; // 23-38 m_name_lang // 39 string flags - //char* description[16]; // 40-55 m_description_lang + //char const* description[16]; // 40-55 m_description_lang // 56 string flags // helpers @@ -953,15 +953,15 @@ struct FactionTemplateEntry struct GameObjectDisplayInfoEntry { uint32 Displayid; // 0 m_ID - char* filename; // 1 - //uint32 unk1[10]; //2-11 + char const* filename; // 1 + //uint32 unk1[10]; //2-11 float minX; float minY; float minZ; float maxX; float maxY; float maxZ; - //uint32 transport; //18 + //uint32 transport; //18 }; struct GemPropertiesEntry @@ -1056,7 +1056,7 @@ struct GtRegenMPPerSptEntry struct HolidayDescriptionsEntry { uint32 ID; // 0, this is NOT holiday id - //char* name[16] // 1-16 m_name_lang + //char const* name[16] // 1-16 m_name_lang // 17 name flags }; */ @@ -1065,7 +1065,7 @@ struct HolidayDescriptionsEntry struct HolidayNamesEntry { uint32 ID; // 0, this is NOT holiday id - //char* name[16] // 1-16 m_name_lang + //char const* name[16] // 1-16 m_name_lang // 17 name flags }; */ @@ -1084,7 +1084,7 @@ struct HolidaysEntry uint32 CalendarFlags[MAX_HOLIDAY_FLAGS]; // 39-48 m_calendarFlags //uint32 holidayNameId; // 49 m_holidayNameID (HolidayNames.dbc) //uint32 holidayDescriptionId; // 50 m_holidayDescriptionID (HolidayDescriptions.dbc) - char* TextureFilename; // 51 m_textureFilename + char const* TextureFilename; // 51 m_textureFilename uint32 Priority; // 52 m_priority int32 CalendarFilterType; // 53 m_calendarFilterType (-1 = Fishing Contest, 0 = Unk, 1 = Darkmoon Festival, 2 = Yearly holiday) //uint32 flags; // 54 m_flags (0 = Darkmoon Faire, Fishing Contest and Wotlk Launch, rest is 1) @@ -1093,7 +1093,7 @@ struct HolidaysEntry struct ItemBagFamilyEntry { uint32 ID; // 0 - //char* name[16] // 1-16 m_name_lang + //char const* name[16] // 1-16 m_name_lang // // 17 name flags }; @@ -1102,7 +1102,7 @@ struct ItemDisplayInfoEntry uint32 ID; // 0 m_ID // 1 m_modelName[2] // 2 m_modelTexture[2] - char* inventoryIcon; // 3 m_inventoryIcon + char const* inventoryIcon; // 3 m_inventoryIcon // 4 m_geosetGroup[3] // 5 m_flags // 6 m_spellVisualID @@ -1137,7 +1137,7 @@ struct ItemExtendedCostEntry struct ItemLimitCategoryEntry { uint32 ID; // 0 Id - //char* name[16] // 1-16 m_name_lang + //char const* name[16] // 1-16 m_name_lang // 17 name flags uint32 maxCount; // 18, max allowed equipped as item or in gem slot uint32 mode; // 19, 0 = have, 1 = equip (enum ItemLimitCategoryMode) @@ -1147,21 +1147,24 @@ struct ItemLimitCategoryEntry struct ItemRandomPropertiesEntry { - uint32 ID; // 0 m_ID - //char* internalName // 1 m_Name - uint32 enchant_id[MAX_ITEM_ENCHANTMENT_EFFECTS]; // 2-6 m_Enchantment - char* nameSuffix[16]; // 7-22 m_name_lang - // 23 name flags + uint32 ID; // 0 + //char const* InternalName; // 1 + std::array<uint32, MAX_ITEM_ENCHANTMENT_EFFECTS> Enchantment; // 2-4 + //std::array<uint32, 2> UnusedEnchantment; // 5-6 + std::array<char const*, 16> Name; // 7-22 + //uint32 Name_lang_mask; // 23 }; struct ItemRandomSuffixEntry { - uint32 ID; // 0 m_ID - char* nameSuffix[16]; // 1-16 m_name_lang - // 17, name flags - // 18 m_internalName - uint32 enchant_id[MAX_ITEM_ENCHANTMENT_EFFECTS]; // 19-23 m_enchantment - uint32 prefix[MAX_ITEM_ENCHANTMENT_EFFECTS]; // 24-28 m_allocationPct + uint32 ID; // 0 + std::array<char const*, 16> Name; // 1-16 + //uint32 Name_lang_mask; // 17 + //char const* InternalName; // 18 + std::array<uint32, MAX_ITEM_ENCHANTMENT_EFFECTS> Enchantment; // 19-21 + //std::array<uint32, 2> UnusedEnchantment; // 22-23 + std::array<uint32, MAX_ITEM_ENCHANTMENT_EFFECTS> AllocationPct; // 24-26 + //std::array<uint32, 2> UnusedAllocationPct; // 27-28 }; #define MAX_ITEM_SET_ITEMS 10 @@ -1170,7 +1173,7 @@ struct ItemRandomSuffixEntry struct ItemSetEntry { //uint32 id // 0 m_ID - char* name[16]; // 1-16 m_name_lang + char const* name[16]; // 1-16 m_name_lang // 17 string flags, unused uint32 itemId[MAX_ITEM_SET_ITEMS]; // 18-27 m_itemID //uint32 unknown[7]; // 28-34 unk, all 0 @@ -1183,7 +1186,7 @@ struct ItemSetEntry struct LFGDungeonEntry { uint32 ID; // 0 - char* name[16]; // 1-17 Name lang + char const* name[16]; // 1-17 Name lang uint32 minlevel; // 18 uint32 maxlevel; // 19 uint32 reclevel; // 20 @@ -1194,11 +1197,11 @@ struct LFGDungeonEntry uint32 flags; // 25 uint32 type; // 26 //uint32 unk; // 27 - //char* iconname; // 28 + //char const* iconname; // 28 uint32 expansion; // 29 //uint32 unk4; // 30 uint32 grouptype; // 31 - //char* desc[16]; // 32-47 Description + //char const* desc[16]; // 32-47 Description // Helpers [[nodiscard]] uint32 Entry() const { return ID + (type << 24); } }; @@ -1225,7 +1228,7 @@ struct LightEntry struct LiquidTypeEntry { uint32 Id; - //char* Name; + //char const* Name; //uint32 Flags; uint32 Type; //uint32 SoundId; @@ -1239,7 +1242,7 @@ struct LiquidTypeEntry //uint32 ParticleMovement; //uint32 ParticleTexSlots; //uint32 LiquidMaterialID; - //char* Texture[6]; + //char const* Texture[6]; //uint32 Color[2]; //float Unk1[18]; //uint32 Unk2[4]; @@ -1259,24 +1262,24 @@ struct LockEntry struct MailTemplateEntry { uint32 ID; // 0 - //char* subject[16]; // 1-16 + //char const* subject[16]; // 1-16 // 17 name flags, unused - char* content[16]; // 18-33 + char const* content[16]; // 18-33 }; struct MapEntry { uint32 MapID; // 0 - //char* internalname; // 1 unused + //char const* internalname; // 1 unused uint32 map_type; // 2 uint32 Flags; // 3 // 4 0 or 1 for battlegrounds (not arenas) - char* name[16]; // 5-20 + char const* name[16]; // 5-20 // 21 name flags, unused uint32 linked_zone; // 22 common zone for instance and continent map - //char* hordeIntro[16]; // 23-38 text for PvP Zones + //char const* hordeIntro[16]; // 23-38 text for PvP Zones // 39 intro text flags - //char* allianceIntro[16]; // 40-55 text for PvP Zones + //char const* allianceIntro[16]; // 40-55 text for PvP Zones // 56 intro text flags uint32 multimap_id; // 57 //float BattlefieldMapIconScale; // 58 @@ -1323,17 +1326,17 @@ struct MapDifficultyEntry //uint32 Id; // 0 uint32 MapId; // 1 uint32 Difficulty; // 2 (for arenas: arena slot) - char* areaTriggerText; // 3-18 text showed when transfer to map failed (missing requirements) + char const* areaTriggerText; // 3-18 text showed when transfer to map failed (missing requirements) //uint32 textFlags; // 19 uint32 resetTime; // 20 uint32 maxPlayers; // 21 - //char* difficultyString; // 22 + //char const* difficultyString; // 22 }; struct MovieEntry { uint32 Id; // 0 index - //char* filename; // 1 + //char const* filename; // 1 //uint32 unk2; // 2 always 100 }; @@ -1350,7 +1353,7 @@ struct PowerDisplayEntry { uint32 Id; // 0 uint32 PowerType; // 1 - //char* Name; // 2 + //char const* Name; // 2 //uint32 R; // 3 //uint32 G; // 4 //uint32 B; // 5 @@ -1373,7 +1376,7 @@ struct PvPDifficultyEntry struct QuestSortEntry { uint32 id; // 0 m_ID - //char* name[16]; // 1-16 m_SortName_lang + //char const* name[16]; // 1-16 m_SortName_lang // 17 name flags }; @@ -1479,7 +1482,7 @@ struct ScalingStatValuesEntry //struct SkillLineCategoryEntry{ // uint32 id; // 0 m_ID -// char* name[16]; // 1-17 m_name_lang +// char const* name[16]; // 1-17 m_name_lang // // 18 string flag // uint32 displayOrder; // 19 m_sortIndex //}; @@ -1503,12 +1506,12 @@ struct SkillLineEntry uint32 id; // 0 m_ID int32 categoryId; // 1 m_categoryID //uint32 skillCostID; // 2 m_skillCostsID - char* name[16]; // 3-18 m_displayName_lang + char const* name[16]; // 3-18 m_displayName_lang // 19 string flags - //char* description[16]; // 20-35 m_description_lang + //char const* description[16]; // 20-35 m_description_lang // 36 string flags uint32 spellIcon; // 37 m_spellIconID - //char* alternateVerb[16]; // 38-53 m_alternateVerb_lang + //char const* alternateVerb[16]; // 38-53 m_alternateVerb_lang // 54 string flags uint32 canLink; // 55 m_canLink (prof. with recipes }; @@ -1541,10 +1544,10 @@ struct SoundEntriesEntry { uint32 Id; // 0 m_ID //uint32 Type; // 1 m_soundType - //char* InternalName; // 2 m_name - //char* FileName[10]; // 3-12 m_File[10] + //char const* InternalName; // 2 m_name + //char const* FileName[10]; // 3-12 m_File[10] //uint32 Unk13[10]; // 13-22 m_Freq[10] - //char* Path; // 23 m_DirectoryBase + //char const* Path; // 23 m_DirectoryBase // 24 m_volumeFloat // 25 m_flags // 26 m_minDistance @@ -1559,115 +1562,115 @@ struct SoundEntriesEntry struct SpellEntry { - uint32 Id; // 0 m_ID - uint32 Category; // 1 m_category - uint32 Dispel; // 2 m_dispelType - uint32 Mechanic; // 3 m_mechanic - uint32 Attributes; // 4 m_attributes - uint32 AttributesEx; // 5 m_attributesEx - uint32 AttributesEx2; // 6 m_attributesExB - uint32 AttributesEx3; // 7 m_attributesExC - uint32 AttributesEx4; // 8 m_attributesExD - uint32 AttributesEx5; // 9 m_attributesExE - uint32 AttributesEx6; // 10 m_attributesExF - uint32 AttributesEx7; // 11 m_attributesExG - uint32 Stances; // 12 m_shapeshiftMask - // uint32 unk_320_2; // 13 3.2.0 - uint32 StancesNot; // 14 m_shapeshiftExclude - // uint32 unk_320_3; // 15 3.2.0 - uint32 Targets; // 16 m_targets - uint32 TargetCreatureType; // 17 m_targetCreatureType - uint32 RequiresSpellFocus; // 18 m_requiresSpellFocus - uint32 FacingCasterFlags; // 19 m_facingCasterFlags - uint32 CasterAuraState; // 20 m_casterAuraState - uint32 TargetAuraState; // 21 m_targetAuraState - uint32 CasterAuraStateNot; // 22 m_excludeCasterAuraState - uint32 TargetAuraStateNot; // 23 m_excludeTargetAuraState - uint32 CasterAuraSpell; // 24 m_casterAuraSpell - uint32 TargetAuraSpell; // 25 m_targetAuraSpell - uint32 ExcludeCasterAuraSpell; // 26 m_excludeCasterAuraSpell - uint32 ExcludeTargetAuraSpell; // 27 m_excludeTargetAuraSpell - uint32 CastingTimeIndex; // 28 m_castingTimeIndex - uint32 RecoveryTime; // 29 m_recoveryTime - uint32 CategoryRecoveryTime; // 30 m_categoryRecoveryTime - uint32 InterruptFlags; // 31 m_interruptFlags - uint32 AuraInterruptFlags; // 32 m_auraInterruptFlags - uint32 ChannelInterruptFlags; // 33 m_channelInterruptFlags - uint32 ProcFlags; // 34 m_procTypeMask - uint32 ProcChance; // 35 m_procChance - uint32 ProcCharges; // 36 m_procCharges - uint32 MaxLevel; // 37 m_maxLevel - uint32 BaseLevel; // 38 m_baseLevel - uint32 SpellLevel; // 39 m_spellLevel - uint32 DurationIndex; // 40 m_durationIndex - uint32 PowerType; // 41 m_powerType - uint32 ManaCost; // 42 m_manaCost - uint32 ManaCostPerlevel; // 43 m_manaCostPerLevel - uint32 ManaPerSecond; // 44 m_manaPerSecond - uint32 ManaPerSecondPerLevel; // 45 m_manaPerSecondPerLeve - uint32 RangeIndex; // 46 m_rangeIndex - float Speed; // 47 m_speed - //uint32 ModalNextSpell; // 48 m_modalNextSpell not used - uint32 StackAmount; // 49 m_cumulativeAura - uint32 Totem[2]; // 50-51 m_totem - int32 Reagent[MAX_SPELL_REAGENTS]; // 52-59 m_reagent - uint32 ReagentCount[MAX_SPELL_REAGENTS]; // 60-67 m_reagentCount - int32 EquippedItemClass; // 68 m_equippedItemClass (value) - int32 EquippedItemSubClassMask; // 69 m_equippedItemSubclass (mask) - int32 EquippedItemInventoryTypeMask; // 70 m_equippedItemInvTypes (mask) - uint32 Effect[MAX_SPELL_EFFECTS]; // 71-73 m_effect - int32 EffectDieSides[MAX_SPELL_EFFECTS]; // 74-76 m_effectDieSides - float EffectRealPointsPerLevel[MAX_SPELL_EFFECTS]; // 77-79 m_effectRealPointsPerLevel - int32 EffectBasePoints[MAX_SPELL_EFFECTS]; // 80-82 m_effectBasePoints (must not be used in spell/auras explicitly, must be used cached Spell::m_currentBasePoints) - uint32 EffectMechanic[MAX_SPELL_EFFECTS]; // 83-85 m_effectMechanic - uint32 EffectImplicitTargetA[MAX_SPELL_EFFECTS]; // 86-88 m_implicitTargetA - uint32 EffectImplicitTargetB[MAX_SPELL_EFFECTS]; // 89-91 m_implicitTargetB - uint32 EffectRadiusIndex[MAX_SPELL_EFFECTS]; // 92-94 m_effectRadiusIndex - spellradius.dbc - uint32 EffectApplyAuraName[MAX_SPELL_EFFECTS]; // 95-97 m_effectAura - uint32 EffectAmplitude[MAX_SPELL_EFFECTS]; // 98-100 m_effectAuraPeriod - float EffectValueMultiplier[MAX_SPELL_EFFECTS]; // 101-103 - uint32 EffectChainTarget[MAX_SPELL_EFFECTS]; // 104-106 m_effectChainTargets - uint32 EffectItemType[MAX_SPELL_EFFECTS]; // 107-109 m_effectItemType - int32 EffectMiscValue[MAX_SPELL_EFFECTS]; // 110-112 m_effectMiscValue - int32 EffectMiscValueB[MAX_SPELL_EFFECTS]; // 113-115 m_effectMiscValueB - uint32 EffectTriggerSpell[MAX_SPELL_EFFECTS]; // 116-118 m_effectTriggerSpell - float EffectPointsPerComboPoint[MAX_SPELL_EFFECTS]; // 119-121 m_effectPointsPerCombo - flag96 EffectSpellClassMask[MAX_SPELL_EFFECTS]; // 122-130 - uint32 SpellVisual[2]; // 131-132 m_spellVisualID - uint32 SpellIconID; // 133 m_spellIconID - uint32 ActiveIconID; // 134 m_activeIconID - //uint32 SpellPriority; // 135 not used - char* SpellName[16]; // 136-151 m_name_lang - //uint32 SpellNameFlag; // 152 not used - char* Rank[16]; // 153-168 m_nameSubtext_lang - //uint32 RankFlags; // 169 not used - //char* Description[16]; // 170-185 m_description_lang not used - //uint32 DescriptionFlags; // 186 not used - //char* ToolTip[16]; // 187-202 m_auraDescription_lang not used - //uint32 ToolTipFlags; // 203 not used - uint32 ManaCostPercentage; // 204 m_manaCostPct - uint32 StartRecoveryCategory; // 205 m_startRecoveryCategory - uint32 StartRecoveryTime; // 206 m_startRecoveryTime - uint32 MaxTargetLevel; // 207 m_maxTargetLevel - uint32 SpellFamilyName; // 208 m_spellClassSet - flag96 SpellFamilyFlags; // 209-211 - uint32 MaxAffectedTargets; // 212 m_maxTargets - uint32 DmgClass; // 213 m_defenseType - uint32 PreventionType; // 214 m_preventionType - //uint32 StanceBarOrder; // 215 m_stanceBarOrder not used - float EffectDamageMultiplier[MAX_SPELL_EFFECTS]; // 216-218 m_effectChainAmplitude - //uint32 MinFactionId; // 219 m_minFactionID not used - //uint32 MinReputation; // 220 m_minReputation not used - //uint32 RequiredAuraVision; // 221 m_requiredAuraVision not used - uint32 TotemCategory[2]; // 222-223 m_requiredTotemCategoryID - int32 AreaGroupId; // 224 m_requiredAreaGroupId - uint32 SchoolMask; // 225 m_schoolMask - uint32 RuneCostID; // 226 m_runeCostID - //uint32 SpellMissileID; // 227 m_spellMissileID not used - //uint32 PowerDisplayId; // 228 PowerDisplay.dbc, new in 3.1 - float EffectBonusMultiplier[MAX_SPELL_EFFECTS]; // 229-231 3.2.0 - //uint32 SpellDescriptionVariableID; // 232 3.2.0 - //uint32 SpellDifficultyId; // 233 3.3.0 + uint32 Id; // 0 m_ID + uint32 Category; // 1 m_category + uint32 Dispel; // 2 m_dispelType + uint32 Mechanic; // 3 m_mechanic + uint32 Attributes; // 4 m_attributes + uint32 AttributesEx; // 5 m_attributesEx + uint32 AttributesEx2; // 6 m_attributesExB + uint32 AttributesEx3; // 7 m_attributesExC + uint32 AttributesEx4; // 8 m_attributesExD + uint32 AttributesEx5; // 9 m_attributesExE + uint32 AttributesEx6; // 10 m_attributesExF + uint32 AttributesEx7; // 11 m_attributesExG + uint32 Stances; // 12 m_shapeshiftMask + // uint32 unk_320_2; // 13 3.2.0 + uint32 StancesNot; // 14 m_shapeshiftExclude + // uint32 unk_320_3; // 15 3.2.0 + uint32 Targets; // 16 m_targets + uint32 TargetCreatureType; // 17 m_targetCreatureType + uint32 RequiresSpellFocus; // 18 m_requiresSpellFocus + uint32 FacingCasterFlags; // 19 m_facingCasterFlags + uint32 CasterAuraState; // 20 m_casterAuraState + uint32 TargetAuraState; // 21 m_targetAuraState + uint32 CasterAuraStateNot; // 22 m_excludeCasterAuraState + uint32 TargetAuraStateNot; // 23 m_excludeTargetAuraState + uint32 CasterAuraSpell; // 24 m_casterAuraSpell + uint32 TargetAuraSpell; // 25 m_targetAuraSpell + uint32 ExcludeCasterAuraSpell; // 26 m_excludeCasterAuraSpell + uint32 ExcludeTargetAuraSpell; // 27 m_excludeTargetAuraSpell + uint32 CastingTimeIndex; // 28 m_castingTimeIndex + uint32 RecoveryTime; // 29 m_recoveryTime + uint32 CategoryRecoveryTime; // 30 m_categoryRecoveryTime + uint32 InterruptFlags; // 31 m_interruptFlags + uint32 AuraInterruptFlags; // 32 m_auraInterruptFlags + uint32 ChannelInterruptFlags; // 33 m_channelInterruptFlags + uint32 ProcFlags; // 34 m_procTypeMask + uint32 ProcChance; // 35 m_procChance + uint32 ProcCharges; // 36 m_procCharges + uint32 MaxLevel; // 37 m_maxLevel + uint32 BaseLevel; // 38 m_baseLevel + uint32 SpellLevel; // 39 m_spellLevel + uint32 DurationIndex; // 40 m_durationIndex + uint32 PowerType; // 41 m_powerType + uint32 ManaCost; // 42 m_manaCost + uint32 ManaCostPerlevel; // 43 m_manaCostPerLevel + uint32 ManaPerSecond; // 44 m_manaPerSecond + uint32 ManaPerSecondPerLevel; // 45 m_manaPerSecondPerLeve + uint32 RangeIndex; // 46 m_rangeIndex + float Speed; // 47 m_speed + //uint32 ModalNextSpell; // 48 m_modalNextSpell not used + uint32 StackAmount; // 49 m_cumulativeAura + std::array<uint32, 2> Totem; // 50-51 m_totem + std::array<int32, MAX_SPELL_REAGENTS> Reagent; // 52-59 m_reagent + std::array<uint32, MAX_SPELL_REAGENTS> ReagentCount; // 60-67 m_reagentCount + int32 EquippedItemClass; // 68 m_equippedItemClass (value) + int32 EquippedItemSubClassMask; // 69 m_equippedItemSubclass (mask) + int32 EquippedItemInventoryTypeMask; // 70 m_equippedItemInvTypes (mask) + std::array<uint32, MAX_SPELL_EFFECTS> Effect; // 71-73 m_effect + std::array<int32, MAX_SPELL_EFFECTS> EffectDieSides; // 74-76 m_effectDieSides + std::array<float, MAX_SPELL_EFFECTS> EffectRealPointsPerLevel; // 77-79 m_effectRealPointsPerLevel + std::array<int32, MAX_SPELL_EFFECTS> EffectBasePoints; // 80-82 m_effectBasePoints (must not be used in spell/auras explicitly, must be used cached Spell::m_currentBasePoints) + std::array<uint32, MAX_SPELL_EFFECTS> EffectMechanic; // 83-85 m_effectMechanic + std::array<uint32, MAX_SPELL_EFFECTS> EffectImplicitTargetA; // 86-88 m_implicitTargetA + std::array<uint32, MAX_SPELL_EFFECTS> EffectImplicitTargetB; // 89-91 m_implicitTargetB + std::array<uint32, MAX_SPELL_EFFECTS> EffectRadiusIndex; // 92-94 m_effectRadiusIndex - spellradius.dbc + std::array<uint32, MAX_SPELL_EFFECTS> EffectApplyAuraName; // 95-97 m_effectAura + std::array<uint32, MAX_SPELL_EFFECTS> EffectAmplitude; // 98-100 m_effectAuraPeriod + std::array<float, MAX_SPELL_EFFECTS> EffectValueMultiplier; // 101-103 + std::array<uint32, MAX_SPELL_EFFECTS> EffectChainTarget; // 104-106 m_effectChainTargets + std::array<uint32, MAX_SPELL_EFFECTS> EffectItemType; // 107-109 m_effectItemType + std::array<int32, MAX_SPELL_EFFECTS> EffectMiscValue; // 110-112 m_effectMiscValue + std::array<int32, MAX_SPELL_EFFECTS> EffectMiscValueB; // 113-115 m_effectMiscValueB + std::array<uint32, MAX_SPELL_EFFECTS> EffectTriggerSpell; // 116-118 m_effectTriggerSpell + std::array<float, MAX_SPELL_EFFECTS> EffectPointsPerComboPoint; // 119-121 m_effectPointsPerCombo + std::array<flag96, MAX_SPELL_EFFECTS> EffectSpellClassMask; // 122-130 + std::array<uint32, 2> SpellVisual; // 131-132 m_spellVisualID + uint32 SpellIconID; // 133 m_spellIconID + uint32 ActiveIconID; // 134 m_activeIconID + //uint32 SpellPriority; // 135 not used + std::array<char const*, 16> SpellName; // 136-151 m_name_lang + //uint32 SpellNameFlag; // 152 not used + std::array<char const*, 16> Rank; // 153-168 m_nameSubtext_lang + //uint32 RankFlags; // 169 not used + //char const* Description[16]; // 170-185 m_description_lang not used + //uint32 DescriptionFlags; // 186 not used + //char const* ToolTip[16]; // 187-202 m_auraDescription_lang not used + //uint32 ToolTipFlags; // 203 not used + uint32 ManaCostPercentage; // 204 m_manaCostPct + uint32 StartRecoveryCategory; // 205 m_startRecoveryCategory + uint32 StartRecoveryTime; // 206 m_startRecoveryTime + uint32 MaxTargetLevel; // 207 m_maxTargetLevel + uint32 SpellFamilyName; // 208 m_spellClassSet + flag96 SpellFamilyFlags; // 209-211 + uint32 MaxAffectedTargets; // 212 m_maxTargets + uint32 DmgClass; // 213 m_defenseType + uint32 PreventionType; // 214 m_preventionType + //uint32 StanceBarOrder; // 215 m_stanceBarOrder not used + std::array<float, MAX_SPELL_EFFECTS> EffectDamageMultiplier; // 216-218 m_effectChainAmplitude + //uint32 MinFactionId; // 219 m_minFactionID not used + //uint32 MinReputation; // 220 m_minReputation not used + //uint32 RequiredAuraVision; // 221 m_requiredAuraVision not used + std::array<uint32, 2> TotemCategory; // 222-223 m_requiredTotemCategoryID + int32 AreaGroupId; // 224 m_requiredAreaGroupId + uint32 SchoolMask; // 225 m_schoolMask + uint32 RuneCostID; // 226 m_runeCostID + //uint32 SpellMissileID; // 227 m_spellMissileID not used + //uint32 PowerDisplayId; // 228 PowerDisplay.dbc, new in 3.1 + std::array<float, MAX_SPELL_EFFECTS> EffectBonusMultiplier; // 229-231 3.2.0 + //uint32 SpellDescriptionVariableID; // 232 3.2.0 + //uint32 SpellDifficultyId; // 233 3.3.0 }; typedef std::set<std::pair<bool, uint32>> SpellCategorySet; @@ -1698,7 +1701,7 @@ struct SpellDifficultyEntry struct SpellFocusObjectEntry { uint32 ID; // 0 - //char* Name[16]; // 1-15 unused + //char const* Name[16]; // 1-15 unused // 16 string flags, unused }; @@ -1718,9 +1721,9 @@ struct SpellRangeEntry float maxRangeHostile; float maxRangeFriend; uint32 type; - //char* Name[16]; // 7-23 unused + //char const* Name[16]; // 7-23 unused // 24 string flags, unused - //char* Name2[16]; // 25-40 unused + //char const* Name2[16]; // 25-40 unused // 41 string flags, unused }; @@ -1740,7 +1743,7 @@ struct SpellShapeshiftEntry { uint32 ID; // 0 //uint32 buttonPosition; // 1 unused - //char* Name[16]; // 2-17 unused + //char const* Name[16]; // 2-17 unused //uint32 NameFlags; // 18 unused uint32 flags1; // 19 int32 creatureType; // 20 <= 0 humanoid, other normal creature types @@ -1769,7 +1772,7 @@ struct SpellItemEnchantmentEntry uint32 amount[MAX_SPELL_ITEM_ENCHANTMENT_EFFECTS]; // 5-7 m_effectPointsMin[MAX_SPELL_ITEM_ENCHANTMENT_EFFECTS] //uint32 amount2[MAX_SPELL_ITEM_ENCHANTMENT_EFFECTS] // 8-10 m_effectPointsMax[MAX_SPELL_ITEM_ENCHANTMENT_EFFECTS] uint32 spellid[MAX_SPELL_ITEM_ENCHANTMENT_EFFECTS]; // 11-13 m_effectArg[MAX_SPELL_ITEM_ENCHANTMENT_EFFECTS] - char* description[16]; // 14-29 m_name_lang[16] + char const* description[16]; // 14-29 m_name_lang[16] //uint32 descriptionFlags; // 30 name flags uint32 aura_id; // 31 m_itemVisual uint32 slot; // 32 m_flags @@ -1849,7 +1852,7 @@ struct TalentEntry uint32 TalentTab; // 1 index in TalentTab.dbc (TalentTabEntry) uint32 Row; // 2 uint32 Col; // 3 - uint32 RankID[MAX_TALENT_RANK]; // 4-8 + std::array<uint32, MAX_TALENT_RANK> RankID; // 4-8 // 9-12 not used, always 0, maybe not used high ranks uint32 DependsOn; // 13 index in Talent.dbc (TalentEntry) // 14-15 not used @@ -1863,14 +1866,14 @@ struct TalentEntry struct TalentTabEntry { uint32 TalentTabID; // 0 - //char* name[16]; // 1-16, unused + //char const* name[16]; // 1-16, unused //uint32 nameFlags; // 17, unused //unit32 spellicon; // 18 // 19 not used uint32 ClassMask; // 20 uint32 petTalentMask; // 21 uint32 tabpage; // 22 - //char* internalname; // 23 + //char const* internalname; // 23 }; struct TaxiNodesEntry @@ -1880,7 +1883,7 @@ struct TaxiNodesEntry float x; // 2 m_x float y; // 3 m_y float z; // 4 m_z - char* name[16]; // 5-21 m_Name_lang + char const* name[16]; // 5-21 m_Name_lang // 22 string flags uint32 MountCreatureID[2]; // 23-24 m_MountCreatureID[2] }; @@ -1917,7 +1920,7 @@ struct TeamContributionPointsEntry struct TotemCategoryEntry { uint32 ID; // 0 - //char* name[16]; // 1-16 + //char const* name[16]; // 1-16 // 17 string flags, unused uint32 categoryType; // 18 (one for specialization) uint32 categoryMask; // 19 (compatibility mask for same type: different for totems, compatible from high to low for rods) @@ -1960,8 +1963,8 @@ struct VehicleEntry float m_cameraFadeDistScalarMin; // 15 float m_cameraFadeDistScalarMax; // 16 float m_cameraPitchOffset; // 17 - //int32 m_powerType[3]; // removed in 3.1 - //int32 m_powerToken[3]; // removed in 3.1 + //int32 m_powerType[3]; // removed in 3.1 + //int32 m_powerToken[3]; // removed in 3.1 float m_facingLimitRight; // 18 float m_facingLimitLeft; // 19 float m_msslTrgtTurnLingering; // 20 @@ -1972,9 +1975,9 @@ struct VehicleEntry float m_msslTrgtArcRepeat; // 25 float m_msslTrgtArcWidth; // 26 float m_msslTrgtImpactRadius[2]; // 27-28 - char* m_msslTrgtArcTexture; // 29 - char* m_msslTrgtImpactTexture; // 30 - char* m_msslTrgtImpactModel[2]; // 31-32 + char const* m_msslTrgtArcTexture; // 29 + char const* m_msslTrgtImpactTexture; // 30 + char const* m_msslTrgtImpactModel[2]; // 31-32 float m_cameraYawOffset; // 33 uint32 m_uiLocomotionType; // 34 float m_msslTrgtImpactTexRadius; // 35 @@ -2073,7 +2076,7 @@ struct WorldMapAreaEntry //uint32 ID; // 0 uint32 map_id; // 1 uint32 area_id; // 2 index (continent 0 areas ignored) - //char* internal_name // 3 + //char const* internal_name // 3 float y1; // 4 float y2; // 5 float x1; // 6 @@ -2091,7 +2094,7 @@ struct WorldMapOverlayEntry //uint32 worldMapAreaId; // 1 idx in WorldMapArea.dbc uint32 areatableID[MAX_WORLD_MAP_OVERLAY_AREA_IDX]; // 2-5 // 6-7 always 0, possible part of areatableID[] - //char* internal_name // 8 + //char const* internal_name // 8 // 9-16 some ints }; @@ -2118,9 +2121,9 @@ struct WorldStateUI uint32 zone; // 2 Can be zero for "everywhere". uint32 phaseMask; // 3 Phase this WorldState is avaliable in uint32 icon; // 4 The icon that is used in the interface. - char* textureFilename; // 5 - char* text; // 6-21 The worldstate text - char* description; // 22-38 Text shown when hovering mouse on icon + char const* textureFilename; // 5 + char const* text; // 6-21 The worldstate text + char const* description; // 22-38 Text shown when hovering mouse on icon uint32 worldstateID; // 39 This is the actual ID used uint32 type; // 40 0 = unknown, 1 = unknown, 2 = not shown in ui, 3 = wintergrasp uint32 unk1; // 41 diff --git a/src/server/shared/SharedDefines.h b/src/server/shared/SharedDefines.h index 55ae573a49..c19f209b80 100644 --- a/src/server/shared/SharedDefines.h +++ b/src/server/shared/SharedDefines.h @@ -278,6 +278,16 @@ const uint32 ItemQualityColors[MAX_ITEM_QUALITY] = 0xffe6cc80 //LIGHT YELLOW }; +size_t constexpr MAX_QUEST_DIFFICULTY = 5; +uint32 constexpr QuestDifficultyColors[MAX_QUEST_DIFFICULTY] = +{ + 0xff40c040, + 0xff808080, + 0xffffff00, + 0xffff8040, + 0xffff2020 +}; + // *********************************** // Spell Attributes definitions // *********************************** diff --git a/src/server/worldserver/ACSoap/ACSoap.h b/src/server/worldserver/ACSoap/ACSoap.h index 38e2cc0bbd..fd62012b58 100644 --- a/src/server/worldserver/ACSoap/ACSoap.h +++ b/src/server/worldserver/ACSoap/ACSoap.h @@ -33,7 +33,7 @@ public: ~SOAPCommand() { } - void appendToPrintBuffer(char const* msg) + void appendToPrintBuffer(std::string_view msg) { m_printBuffer += msg; } @@ -49,7 +49,7 @@ public: return m_success; } - static void print(void* callbackArg, char const* msg) + static void print(void* callbackArg, std::string_view msg) { ((SOAPCommand*)callbackArg)->appendToPrintBuffer(msg); } diff --git a/src/server/worldserver/CommandLine/CliRunnable.cpp b/src/server/worldserver/CommandLine/CliRunnable.cpp index 598b964b35..c75c439dfe 100644 --- a/src/server/worldserver/CommandLine/CliRunnable.cpp +++ b/src/server/worldserver/CommandLine/CliRunnable.cpp @@ -19,19 +19,22 @@ /// @{ /// \file -#include "AccountMgr.h" -#include "Chat.h" -#include "CliRunnable.h" #include "Common.h" +#include "Errors.h" +#include "ObjectMgr.h" +#include "World.h" #include "Config.h" -#include "Language.h" +#include "CliRunnable.h" #include "Log.h" -#include "MapMgr.h" -#include "ObjectMgr.h" -#include "Player.h" #include "Util.h" -#include "World.h" -#include "WorldSession.h" + +#if AC_PLATFORM != AC_PLATFORM_WINDOWS +#include "Chat.h" +#include "ChatCommand.h" +#include <cstring> +#include <readline/readline.h> +#include <readline/history.h> +#endif static constexpr char CLI_PREFIX[] = "AC> "; @@ -41,75 +44,49 @@ static inline void PrintCliPrefix() } #if AC_PLATFORM != AC_PLATFORM_WINDOWS -#include <readline/readline.h> -#include <readline/history.h> - -char* command_finder(const char* text, int state) +namespace Acore::Impl::Readline { - static size_t idx, len; - const char* ret; - std::vector<ChatCommand> const& cmd = ChatHandler::getCommandTable(); - - if (!state) + static std::vector<std::string> vec; + char* cli_unpack_vector(char const*, int state) { - idx = 0; - len = strlen(text); + static size_t i=0; + if (!state) + i = 0; + if (i < vec.size()) + return strdup(vec[i++].c_str()); + else + return nullptr; } - while (idx < cmd.size()) + char** cli_completion(char const* text, int /*start*/, int /*end*/) { - ret = cmd[idx].Name; - if (!cmd[idx].AllowConsole) - { - ++idx; - continue; - } - - ++idx; - //printf("Checking %s \n", cmd[idx].Name); - if (strncmp(ret, text, len) == 0) - return strdup(ret); + ::rl_attempted_completion_over = 1; + vec = Acore::ChatCommands::GetAutoCompletionsFor(CliHandler(nullptr,nullptr), text); + return ::rl_completion_matches(text, &cli_unpack_vector); } - return ((char*)nullptr); -} - -char** cli_completion(const char* text, int start, int /*end*/) -{ - char** matches = nullptr; - - if (start) - rl_bind_key('\t', rl_abort); - else - matches = rl_completion_matches((char*)text, &command_finder); - return matches; -} - -int cli_hook_func() -{ - if (World::IsStopped()) - rl_done = 1; - return 0; + int cli_hook_func() + { + if (World::IsStopped()) + ::rl_done = 1; + return 0; + } } - #endif -void utf8print(void* /*arg*/, const char* str) +void utf8print(void* /*arg*/, std::string_view str) { #if AC_PLATFORM == AC_PLATFORM_WINDOWS - wchar_t wtemp_buf[6000]; - size_t wtemp_len = 6000 - 1; - if (!Utf8toWStr(str, strlen(str), wtemp_buf, wtemp_len)) + std::wstring wbuf; + if (!Utf8toWStr(str, wbuf)) return; - char temp_buf[6000]; - CharToOemBuffW(&wtemp_buf[0], &temp_buf[0], wtemp_len + 1); - printf(temp_buf); + wprintf(L"%s", wbuf.c_str()); #else - { - printf("%s", str); - fflush(stdout); - } +{ + printf(STRING_VIEW_FMT, STRING_VIEW_FMT_ARG(str)); + fflush(stdout); +} #endif } @@ -129,7 +106,7 @@ int kb_hit_return() tv.tv_usec = 0; FD_ZERO(&fds); FD_SET(STDIN_FILENO, &fds); - select(STDIN_FILENO + 1, &fds, nullptr, nullptr, &tv); + select(STDIN_FILENO+1, &fds, nullptr, nullptr, &tv); return FD_ISSET(STDIN_FILENO, &fds); } #endif @@ -137,15 +114,21 @@ int kb_hit_return() /// %Thread start void CliThread() { - ///- Display the list of available CLI functions then beep - //TC_LOG_INFO("server.worldserver", ""); -#if AC_PLATFORM != AC_PLATFORM_WINDOWS - rl_attempted_completion_function = cli_completion; - rl_event_hook = cli_hook_func; +#if AC_PLATFORM == AC_PLATFORM_WINDOWS + // print this here the first time + // later it will be printed after command queue updates + PrintCliPrefix(); +#else + ::rl_attempted_completion_function = &Acore::Impl::Readline::cli_completion; + { + static char BLANK = '\0'; + ::rl_completer_word_break_characters = &BLANK; + } + ::rl_event_hook = &Acore::Impl::Readline::cli_hook_func; #endif if (sConfigMgr->GetOption<bool>("BeepAtStart", true)) - printf("\a"); // \a = Alert + printf("\a"); // \a = Alert #if AC_PLATFORM == AC_PLATFORM_WINDOWS if (sConfigMgr->GetOption<bool>("FlashAtStart", true)) @@ -160,60 +143,53 @@ void CliThread() } #endif - // print this here the first time - // later it will be printed after command queue updates - PrintCliPrefix(); - ///- As long as the World is running (no World::m_stopEvent), get the command line and handle it while (!World::IsStopped()) { fflush(stdout); - char* command_str ; // = fgets(commandbuf, sizeof(commandbuf), stdin); + std::string command; #if AC_PLATFORM == AC_PLATFORM_WINDOWS - char commandbuf[256]; - command_str = fgets(commandbuf, sizeof(commandbuf), stdin); -#else - command_str = readline(CLI_PREFIX); - rl_bind_key('\t', rl_complete); -#endif - - if (command_str != nullptr) + wchar_t commandbuf[256]; + if (fgetws(commandbuf, sizeof(commandbuf), stdin)) { - for (int x = 0; command_str[x]; ++x) - if (command_str[x] == '\r' || command_str[x] == '\n') - { - command_str[x] = 0; - break; - } - - if (!*command_str) + if (!WStrToUtf8(commandbuf, wcslen(commandbuf), command)) { -#if AC_PLATFORM == AC_PLATFORM_WINDOWS PrintCliPrefix(); -#else - free(command_str); -#endif continue; } + } +#else + char* command_str = readline(CLI_PREFIX); + ::rl_bind_key('\t', ::rl_complete); + if (command_str != nullptr) + { + command = command_str; + free(command_str); + } +#endif - std::string command; - if (!consoleToUtf8(command_str, command)) // convert from console encoding to utf8 + if (!command.empty()) + { + std::size_t nextLineIndex = command.find_first_of("\r\n"); + if (nextLineIndex != std::string::npos) { + if (nextLineIndex == 0) + { #if AC_PLATFORM == AC_PLATFORM_WINDOWS - PrintCliPrefix(); -#else - free(command_str); + PrintCliPrefix(); #endif - continue; + continue; + } + + command.erase(nextLineIndex); } fflush(stdout); sWorld->QueueCliCommand(new CliCommandHolder(nullptr, command.c_str(), &utf8print, &commandFinished)); #if AC_PLATFORM != AC_PLATFORM_WINDOWS add_history(command.c_str()); - free(command_str); #endif } else if (feof(stdin)) diff --git a/src/server/worldserver/RemoteAccess/RASession.cpp b/src/server/worldserver/RemoteAccess/RASession.cpp index 728d984bd2..920445cb15 100644 --- a/src/server/worldserver/RemoteAccess/RASession.cpp +++ b/src/server/worldserver/RemoteAccess/RASession.cpp @@ -91,7 +91,7 @@ void RASession::Start() _socket.close(); } -int RASession::Send(const char* data) +int RASession::Send(std::string_view data) { std::ostream os(&_writeBuffer); os << data; @@ -206,9 +206,9 @@ bool RASession::ProcessCommand(std::string& command) return false; } -void RASession::CommandPrint(void* callbackArg, const char* text) +void RASession::CommandPrint(void* callbackArg, std::string_view text) { - if (!text || !*text) + if (text.empty()) { return; } diff --git a/src/server/worldserver/RemoteAccess/RASession.h b/src/server/worldserver/RemoteAccess/RASession.h index 201b45528a..c4dd6d861f 100644 --- a/src/server/worldserver/RemoteAccess/RASession.h +++ b/src/server/worldserver/RemoteAccess/RASession.h @@ -40,13 +40,13 @@ public: unsigned short GetRemotePort() const { return _socket.remote_endpoint().port(); } private: - int Send(const char* data); + int Send(std::string_view data); std::string ReadString(); bool CheckAccessLevel(const std::string& user); bool CheckPassword(const std::string& user, const std::string& pass); bool ProcessCommand(std::string& command); - static void CommandPrint(void* callbackArg, const char* text); + static void CommandPrint(void* callbackArg, std::string_view text); static void CommandFinished(void* callbackArg, bool); tcp::socket _socket; diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index 801bdcd1da..845e26c42c 100644 --- a/src/server/worldserver/worldserver.conf.dist +++ b/src/server/worldserver/worldserver.conf.dist @@ -1788,30 +1788,34 @@ NpcRegenHPTimeIfTargetIsUnreachable = 10 # Collapses multiple subsequent whitespaces into a single whitespace. # Not applied to the addon language, but may break old addons that use # "normal" chat messages for sending data to other clients. -# Default: 0 - (Disabled) -# 1 - (Enabled) +# Default: 1 - (Enabled, Blizzlike) +# 0 - (Disabled) +# -ChatFakeMessagePreventing = 0 +ChatFakeMessagePreventing = 1 # # ChatStrictLinkChecking.Severity # Description: Check chat messages for in-game links to spells, items, quests, etc. -# Default: 0 - (Disabled) -# 1 - (Enabled, Check if only valid pipe commands are used, Prevents posting -# pictures.) -# 2 - (Enabled, Verify that pipe commands are used in a correct order) -# 3 - (Check if color, entry and name don't contradict each other. For this to -# work correctly, please assure that you have extracted locale DBCs of -# every language specific client playing on this server) +# -1 - (Only verify validity of link data, but permit use of custom colors) +# Default: 0 - (Only verify that link data and color are valid without checking text) +# 1 - (Additionally verifies that the link text matches the provided data) +# +# Note: If this is set to '1', you must additionally provide .dbc files for all +# client locales that are in use on your server. +# If any files are missing, messages with links from clients using those +# locales will likely be blocked by the server. +# ChatStrictLinkChecking.Severity = 0 # # ChatStrictLinkChecking.Kick -# Description: Defines what should be done if a message is considered to contain invalid -# pipe commands. +# Description: Defines what should be done if a message containing invalid control characters +# is received. # Default: 0 - (Silently ignore message) -# 1 - (Disconnect players who sent malformed messages) +# 1 - (Ignore message and kick player) +# ChatStrictLinkChecking.Kick = 0 |