mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-15 23:20:36 +01:00
Core/Chat: Unify chat hyperlink parsing (PR #22417)
- Validate most link properties all the time
- If enabled, also validate link text (needs locale)
- Instead of blocking the entire message, sanitize it
- Apply filtering to DND/AFK messages. Closes #22399
(cherry picked from commit f27284594b)
This commit is contained in:
@@ -68,40 +68,6 @@ Tokenizer::Tokenizer(const std::string &src, const char sep, uint32 vectorReserv
|
||||
}
|
||||
}
|
||||
|
||||
void stripLineInvisibleChars(std::string &str)
|
||||
{
|
||||
static std::string const invChars = " \t\7\n";
|
||||
|
||||
size_t wpos = 0;
|
||||
|
||||
bool space = false;
|
||||
for (size_t pos = 0; pos < str.size(); ++pos)
|
||||
{
|
||||
if (invChars.find(str[pos])!=std::string::npos)
|
||||
{
|
||||
if (!space)
|
||||
{
|
||||
str[wpos++] = ' ';
|
||||
space = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (wpos!=pos)
|
||||
str[wpos++] = str[pos];
|
||||
else
|
||||
++wpos;
|
||||
space = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (wpos < str.size())
|
||||
str.erase(wpos, str.size());
|
||||
if (str.find("|TInterface")!=std::string::npos)
|
||||
str.clear();
|
||||
|
||||
}
|
||||
|
||||
#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__))
|
||||
struct tm* localtime_r(time_t const* time, struct tm *result)
|
||||
{
|
||||
|
||||
@@ -55,8 +55,6 @@ private:
|
||||
StorageType m_storage;
|
||||
};
|
||||
|
||||
TC_COMMON_API void stripLineInvisibleChars(std::string &src);
|
||||
|
||||
TC_COMMON_API int64 MoneyStringToMoney(std::string const& moneyString);
|
||||
|
||||
TC_COMMON_API struct tm* localtime_r(time_t const* time, struct tm *result);
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include "AccountMgr.h"
|
||||
#include "CellImpl.h"
|
||||
#include "CharacterCache.h"
|
||||
#include "ChatLink.h"
|
||||
#include "ChatPackets.h"
|
||||
#include "Common.h"
|
||||
#include "DatabaseEnv.h"
|
||||
@@ -37,6 +36,7 @@
|
||||
#include "World.h"
|
||||
#include "WorldSession.h"
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
#include <sstream>
|
||||
|
||||
// Lazy loading of the command table cache from commands and the
|
||||
// ScriptMgr should be thread safe since the player commands,
|
||||
@@ -445,66 +445,6 @@ bool ChatHandler::ParseCommands(char const* text)
|
||||
return _ParseCommands(text+1);
|
||||
}
|
||||
|
||||
bool ChatHandler::isValidChatMessage(char const* message)
|
||||
{
|
||||
/*
|
||||
Valid examples:
|
||||
|cffa335ee|Hitem:812:0:0:0:0:0:0:0:70|h[Glowing Brightwood Staff]|h|r
|
||||
|cffffff00|Hquest:51101:-1:110:120:5|h[The Wounded King]|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|";
|
||||
|
||||
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;
|
||||
|
||||
@@ -76,7 +76,6 @@ class TC_GAME_API ChatHandler
|
||||
static std::vector<ChatCommand> const& getCommandTable();
|
||||
static void invalidateCommandTable();
|
||||
|
||||
bool isValidChatMessage(const char* msg);
|
||||
void SendGlobalSysMessage(const char *str);
|
||||
|
||||
bool hasStringAbbr(const char* name, const char* part);
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
#include "ChatCommandArgs.h"
|
||||
#include "ChatCommand.h"
|
||||
#include "ChatCommandHyperlinks.h"
|
||||
#include "DB2Stores.h"
|
||||
#include "ObjectMgr.h"
|
||||
|
||||
@@ -26,7 +25,7 @@ using namespace Trinity::ChatCommands;
|
||||
struct AchievementVisitor
|
||||
{
|
||||
using value_type = AchievementEntry const*;
|
||||
value_type operator()(Hyperlink<achievement> achData) const { return achData->achievement; }
|
||||
value_type operator()(Hyperlink<achievement> achData) const { return achData->Achievement; }
|
||||
value_type operator()(uint32 achId) const { return sAchievementStore.LookupEntry(achId); }
|
||||
};
|
||||
char const* Trinity::ChatCommands::ArgInfo<AchievementEntry const*>::TryConsume(AchievementEntry const*& data, char const* args)
|
||||
|
||||
@@ -20,12 +20,13 @@
|
||||
|
||||
#include "ChatCommandHelpers.h"
|
||||
#include "ChatCommandTags.h"
|
||||
#include "ChatCommandHyperlinks.h"
|
||||
|
||||
struct GameTele;
|
||||
|
||||
namespace Trinity {
|
||||
namespace ChatCommands {
|
||||
namespace Trinity
|
||||
{
|
||||
namespace ChatCommands
|
||||
{
|
||||
|
||||
/************************** ARGUMENT HANDLERS *******************************************\
|
||||
|* Define how to extract contents of a certain requested type from a string *|
|
||||
@@ -129,6 +130,7 @@ struct TC_GAME_API ArgInfo<bool>
|
||||
static char const* TryConsume(bool&, char const*);
|
||||
};
|
||||
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -21,8 +21,10 @@
|
||||
#include "advstd.h"
|
||||
#include <type_traits>
|
||||
|
||||
namespace Trinity {
|
||||
namespace ChatCommands {
|
||||
namespace Trinity
|
||||
{
|
||||
namespace ChatCommands
|
||||
{
|
||||
|
||||
static constexpr char COMMAND_DELIMITER = ' ';
|
||||
|
||||
@@ -70,6 +72,7 @@ struct get_nth<0, T1, Ts...>
|
||||
template <size_t index, typename... Ts>
|
||||
using get_nth_t = typename get_nth<index, Ts...>::type;
|
||||
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 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 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 "ChatCommandHyperlinks.h"
|
||||
#include "DB2Stores.h"
|
||||
|
||||
static constexpr char HYPERLINK_DELIMITER = ':';
|
||||
|
||||
class AchievementLinkTokenizer
|
||||
{
|
||||
public:
|
||||
AchievementLinkTokenizer(char const* pos, size_t len) : _pos(pos), _len(len), _empty(false) {}
|
||||
|
||||
template <typename T>
|
||||
bool TryConsumeTo(T& val)
|
||||
{
|
||||
if (_empty)
|
||||
return false;
|
||||
|
||||
char const* firstPos = _pos;
|
||||
size_t thisLen = 0;
|
||||
// find next delimiter
|
||||
for (; _len && *_pos != HYPERLINK_DELIMITER; --_len, ++_pos, ++thisLen);
|
||||
if (_len)
|
||||
--_len, ++_pos; // skip the delimiter
|
||||
else
|
||||
_empty = true;
|
||||
|
||||
return Trinity::ChatCommands::base_tag::StoreTo(val, firstPos, thisLen);
|
||||
}
|
||||
|
||||
bool IsEmpty() { return _empty; }
|
||||
|
||||
private:
|
||||
char const* _pos;
|
||||
size_t _len;
|
||||
bool _empty;
|
||||
};
|
||||
|
||||
bool Trinity::ChatCommands::achievement::StoreTo(AchievementLinkData& val, char const* pos, size_t len)
|
||||
{
|
||||
AchievementLinkTokenizer t(pos, len);
|
||||
uint32 achievementId;
|
||||
if (!t.TryConsumeTo(achievementId))
|
||||
return false;
|
||||
val.achievement = sAchievementStore.LookupEntry(achievementId);
|
||||
return val.achievement && t.TryConsumeTo(val.characterId) && t.TryConsumeTo(val.isFinished) &&
|
||||
t.TryConsumeTo(val.month) && t.TryConsumeTo(val.day) && t.TryConsumeTo(val.year) && t.TryConsumeTo(val.criteria[0]) &&
|
||||
t.TryConsumeTo(val.criteria[1]) && t.TryConsumeTo(val.criteria[2]) && t.TryConsumeTo(val.criteria[3]) && t.IsEmpty();
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 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 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 TRINITY_CHATCOMMANDHYPERLINKS_H
|
||||
#define TRINITY_CHATCOMMANDHYPERLINKS_H
|
||||
|
||||
#include "ChatCommandHelpers.h"
|
||||
#include "ChatCommandTags.h"
|
||||
#include "ObjectGuid.h"
|
||||
|
||||
struct AchievementEntry;
|
||||
|
||||
namespace Trinity {
|
||||
namespace ChatCommands {
|
||||
|
||||
// for details on what ContainerTag has to expose, see ChatCommandTags.h
|
||||
template <typename linktag>
|
||||
struct Hyperlink : public ContainerTag
|
||||
{
|
||||
typedef typename linktag::value_type value_type;
|
||||
typedef advstd::remove_cvref_t<value_type> storage_type;
|
||||
|
||||
public:
|
||||
operator value_type() const { return val; }
|
||||
value_type const& operator*() const { return val; }
|
||||
value_type const* operator->() const { return &val; }
|
||||
|
||||
char const* TryConsume(char const* pos)
|
||||
{
|
||||
//color tag
|
||||
if (*(pos++) != '|' || *(pos++) != 'c')
|
||||
return nullptr;
|
||||
for (uint8 i = 0; i < 8; ++i)
|
||||
if (!*(pos++)) // make sure we don't overrun a terminator
|
||||
return nullptr;
|
||||
// link data start tag
|
||||
if (*(pos++) != '|' || *(pos++) != 'H')
|
||||
return nullptr;
|
||||
// link tag, should match argument
|
||||
char const* tag = linktag::tag();
|
||||
while (*tag)
|
||||
if (*(pos++) != *(tag++))
|
||||
return nullptr;
|
||||
// separator
|
||||
if (*(pos++) != ':')
|
||||
return nullptr;
|
||||
// ok, link data, let's figure out how long it is
|
||||
char const* datastart = pos;
|
||||
size_t datalength = 0;
|
||||
while (*pos && *(pos++) != '|')
|
||||
++datalength;
|
||||
// ok, next should be link data end tag...
|
||||
if (*(pos++) != 'h')
|
||||
return nullptr;
|
||||
// then visible link text, skip to next '|', should be '|h|r' terminator
|
||||
while (*pos && *(pos++) != '|');
|
||||
if (*(pos++) != 'h' || *(pos++) != '|' || *(pos++) != 'r')
|
||||
return nullptr;
|
||||
// finally, skip to end of token
|
||||
tokenize(pos);
|
||||
// store value
|
||||
if (!linktag::StoreTo(val, datastart, datalength))
|
||||
return nullptr;
|
||||
|
||||
// return final pos
|
||||
return pos;
|
||||
}
|
||||
|
||||
private:
|
||||
storage_type val;
|
||||
};
|
||||
|
||||
/************************** 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 -> const char* *|
|
||||
|* - this method SHOULD be constexpr *|
|
||||
|* - returns identifier string for the link ("creature", "creature_entry", "item") *|
|
||||
|* - MUST expose static ::StoreTo method, (storage&, char const*, size_t) *|
|
||||
|* - assign value_type& based on content of std::string(char const*, size_t) *|
|
||||
|* - 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& val, char const* pos, size_t len)
|
||||
{
|
||||
val.assign(pos, len);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool StoreTo(ObjectGuid& val, char const* pos, size_t len)
|
||||
{
|
||||
val = ObjectGuid::FromString(std::string(pos, len));
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static std::enable_if_t<advstd::is_integral_v<T> && advstd::is_unsigned_v<T>, bool> StoreTo(T& val, char const* pos, size_t len)
|
||||
{
|
||||
try { val = std::stoull(std::string(pos, len)); }
|
||||
catch (...) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static std::enable_if_t<advstd::is_integral_v<T> && advstd::is_signed_v<T>, bool> StoreTo(T& val, char const* pos, size_t len)
|
||||
{
|
||||
try { val = std::stoll(std::string(pos, len)); }
|
||||
catch (...) { return false; }
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
#define make_base_tag(ltag, type) struct ltag : public base_tag { using value_type = type; static constexpr char const* tag() { return #ltag; } }
|
||||
make_base_tag(areatrigger, uint32);
|
||||
make_base_tag(creature, ObjectGuid::LowType);
|
||||
make_base_tag(creature_entry, uint32);
|
||||
make_base_tag(gameobject, ObjectGuid::LowType);
|
||||
make_base_tag(taxinode, uint32);
|
||||
make_base_tag(tele, uint32);
|
||||
#undef make_base_tag
|
||||
|
||||
struct AchievementLinkData
|
||||
{
|
||||
AchievementEntry const* achievement;
|
||||
ObjectGuid characterId;
|
||||
bool isFinished;
|
||||
uint16 year;
|
||||
uint8 month;
|
||||
uint8 day;
|
||||
uint32 criteria[4];
|
||||
};
|
||||
|
||||
struct TC_GAME_API achievement
|
||||
{
|
||||
using value_type = AchievementLinkData;
|
||||
static constexpr char const* tag() { return "achievement"; }
|
||||
static bool StoreTo(AchievementLinkData& val, char const* pos, size_t len);
|
||||
};
|
||||
|
||||
}}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#define TRINITY_CHATCOMMANDTAGS_H
|
||||
|
||||
#include "ChatCommandHelpers.h"
|
||||
#include "Hyperlinks.h"
|
||||
#include "Optional.h"
|
||||
#include <boost/variant.hpp>
|
||||
#include <cmath>
|
||||
@@ -28,8 +29,10 @@
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace Trinity {
|
||||
namespace ChatCommands {
|
||||
namespace Trinity
|
||||
{
|
||||
namespace ChatCommands
|
||||
{
|
||||
/************************** CONTAINER TAGS **********************************************\
|
||||
|* Simple holder classes to differentiate between extraction methods *|
|
||||
|* Should inherit from ContainerTag for template identification *|
|
||||
@@ -75,6 +78,49 @@ struct ExactSequence : public ContainerTag
|
||||
char const* TryConsume(char const* pos) const { return ExactSequence::_TryConsume(pos); }
|
||||
};
|
||||
|
||||
template <typename linktag>
|
||||
struct Hyperlink : public ContainerTag
|
||||
{
|
||||
typedef typename linktag::value_type value_type;
|
||||
typedef advstd::remove_cvref_t<value_type> storage_type;
|
||||
|
||||
public:
|
||||
operator value_type() const { return val; }
|
||||
value_type operator*() const { return val; }
|
||||
storage_type const* operator->() const { return &val; }
|
||||
|
||||
char const* TryConsume(char const* pos)
|
||||
{
|
||||
Trinity::Hyperlinks::HyperlinkInfo info = Trinity::Hyperlinks::ParseHyperlink(pos);
|
||||
// invalid hyperlinks cannot be consumed
|
||||
if (!info)
|
||||
return nullptr;
|
||||
|
||||
// check if we got the right tag
|
||||
if (info.tag.second != strlen(linktag::tag()))
|
||||
return nullptr;
|
||||
if (strncmp(info.tag.first, linktag::tag(), strlen(linktag::tag())) != 0)
|
||||
return nullptr;
|
||||
|
||||
// store value
|
||||
if (!linktag::StoreTo(val, info.data.first, info.data.second))
|
||||
return nullptr;
|
||||
|
||||
// finally, skip to end of token
|
||||
pos = info.next;
|
||||
tokenize(pos);
|
||||
|
||||
// return final pos
|
||||
return pos;
|
||||
}
|
||||
|
||||
private:
|
||||
storage_type val;
|
||||
};
|
||||
|
||||
// pull in link tags for user convenience
|
||||
using namespace ::Trinity::Hyperlinks::LinkTags;
|
||||
|
||||
/************************** VARIANT TAG LOGIC *********************************\
|
||||
|* This has some special handling over in ChatCommand.h *|
|
||||
\******************************************************************************/
|
||||
@@ -118,6 +164,7 @@ struct Variant : public boost::variant<T1, Ts...>
|
||||
decltype(auto) get() const { return boost::get<get_nth_t<index, T1, Ts...>>(*this); }
|
||||
};
|
||||
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,947 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore 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 General Public License as published by the
|
||||
* Free Software Foundation; either version 2 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 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 "AchievementMgr.h"
|
||||
#include "DB2Stores.h"
|
||||
#include "Item.h"
|
||||
#include "Log.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "QuestDef.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:min_level:max_level:scaling_faction|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, char const* context)
|
||||
{
|
||||
char c = iss.peek();
|
||||
if (c != delimiter)
|
||||
{
|
||||
TC_LOG_TRACE("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, char const* /*context*/)
|
||||
{
|
||||
_name = buffer;
|
||||
return true;
|
||||
}
|
||||
|
||||
// |color|Hitem:item_id:perm_ench_id:gem1:gem2:gem3:0:random_property:property_seed:reporter_level:reporter_spec:modifiers_mask:context:numBonusListIDs:bonusListIDs(%d):numModifiers:(modifierType(%d):modifierValue(%d)):gem1numBonusListIDs:gem1bonusListIDs(%d):gem2numBonusListIDs:gem2bonusListIDs(%d):gem3numBonusListIDs:gem3bonusListIDs(%d):creator:use_enchant_id|h[name]|h|r
|
||||
// |cffa335ee|Hitem:124382:0:0:0:0:0:0:0:0:0:0:0:4:42:562:565:567|h[Edict of Argus]|h|r");
|
||||
bool ItemChatLink::Initialize(std::istringstream& iss)
|
||||
{
|
||||
// Read item entry
|
||||
uint32 itemEntry = 0;
|
||||
if (!ReadUInt32(iss, itemEntry))
|
||||
{
|
||||
TC_LOG_TRACE("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)
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): got invalid itemEntry %u in |item command", iss.str().c_str(), itemEntry);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate item's color
|
||||
uint32 colorQuality = _item->GetQuality();
|
||||
if (_item->GetFlags3() & ITEM_FLAG3_DISPLAY_AS_HEIRLOOM)
|
||||
colorQuality = ITEM_QUALITY_HEIRLOOM;
|
||||
|
||||
if (_color != ItemQualityColors[colorQuality])
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): linked item has color %u, but user claims %u", iss.str().c_str(), ItemQualityColors[colorQuality], _color);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckDelimiter(iss, DELIMITER, "item"))
|
||||
return false;
|
||||
|
||||
if (HasValue(iss) && !ReadInt32(iss, _enchantId))
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item enchantId", iss.str().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckDelimiter(iss, DELIMITER, "item"))
|
||||
return false;
|
||||
|
||||
if (HasValue(iss) && !ReadInt32(iss, _gemItemId[0]))
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item gem id 1", iss.str().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckDelimiter(iss, DELIMITER, "item"))
|
||||
return false;
|
||||
|
||||
if (HasValue(iss) && !ReadInt32(iss, _gemItemId[1]))
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item gem id 2", iss.str().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckDelimiter(iss, DELIMITER, "item"))
|
||||
return false;
|
||||
|
||||
if (HasValue(iss) && !ReadInt32(iss, _gemItemId[2]))
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item gem id 3", iss.str().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckDelimiter(iss, DELIMITER, "item"))
|
||||
return false;
|
||||
|
||||
int32 zero = 0;
|
||||
if (HasValue(iss) && !ReadInt32(iss, zero))
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading zero", iss.str().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckDelimiter(iss, DELIMITER, "item"))
|
||||
return false;
|
||||
|
||||
if (HasValue(iss) && !ReadInt32(iss, zero))
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item random property id", iss.str().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckDelimiter(iss, DELIMITER, "item"))
|
||||
return false;
|
||||
|
||||
if (HasValue(iss) && !ReadInt32(iss, zero))
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item random property seed", iss.str().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckDelimiter(iss, DELIMITER, "item"))
|
||||
return false;
|
||||
|
||||
if (HasValue(iss) && !ReadInt32(iss, _reporterLevel))
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item owner level", iss.str().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckDelimiter(iss, DELIMITER, "item"))
|
||||
return false;
|
||||
|
||||
if (HasValue(iss) && !ReadInt32(iss, _reporterSpec))
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item owner spec", iss.str().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckDelimiter(iss, DELIMITER, "item"))
|
||||
return false;
|
||||
|
||||
int32 modifiersMask = 0;
|
||||
if (HasValue(iss) && !ReadInt32(iss, modifiersMask))
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item modifiers mask", iss.str().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckDelimiter(iss, DELIMITER, "item"))
|
||||
return false;
|
||||
|
||||
if (HasValue(iss) && !ReadInt32(iss, _context))
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item context", iss.str().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckDelimiter(iss, DELIMITER, "item"))
|
||||
return false;
|
||||
|
||||
uint32 numBonusListIDs = 0;
|
||||
if (HasValue(iss) && !ReadUInt32(iss, numBonusListIDs))
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item bonus lists size", iss.str().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32 const maxBonusListIDs = 16;
|
||||
if (numBonusListIDs > maxBonusListIDs)
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): too many item bonus list IDs %u in |item command", iss.str().c_str(), numBonusListIDs);
|
||||
return false;
|
||||
}
|
||||
|
||||
_bonusListIDs.resize(numBonusListIDs);
|
||||
for (uint32 index = 0; index < numBonusListIDs; ++index)
|
||||
{
|
||||
if (!CheckDelimiter(iss, DELIMITER, "item"))
|
||||
return false;
|
||||
|
||||
int32 id = 0;
|
||||
if (!ReadInt32(iss, id))
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item bonus list id (index %u)", iss.str().c_str(), index);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sDB2Manager.GetItemBonusList(id))
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): got invalid item bonus list id %d in |item command", iss.str().c_str(), id);
|
||||
return false;
|
||||
}
|
||||
|
||||
_bonusListIDs[index] = id;
|
||||
}
|
||||
|
||||
if (!CheckDelimiter(iss, DELIMITER, "item"))
|
||||
return false;
|
||||
|
||||
uint32 numModifiers = 0;
|
||||
if (HasValue(iss) && !ReadUInt32(iss, numModifiers))
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item modifiers size", iss.str().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (numModifiers > MAX_ITEM_MODIFIERS)
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): too many item modifiers %u in |item command", iss.str().c_str(), numBonusListIDs);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint32 i = 0; i < numModifiers; ++i)
|
||||
{
|
||||
if (!CheckDelimiter(iss, DELIMITER, "item"))
|
||||
return false;
|
||||
|
||||
int32 type = 0;
|
||||
if (!ReadInt32(iss, type))
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item modifier type (index %u)", iss.str().c_str(), i);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type > MAX_ITEM_MODIFIERS)
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): invalid item modifier type %u (index %u)", iss.str().c_str(), type, i);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckDelimiter(iss, DELIMITER, "item"))
|
||||
return false;
|
||||
|
||||
int32 id = 0;
|
||||
if (!ReadInt32(iss, id))
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item modifier value (index %u)", iss.str().c_str(), i);
|
||||
return false;
|
||||
}
|
||||
|
||||
_modifiers.emplace_back(type, id);
|
||||
}
|
||||
|
||||
for (uint32 i = 0; i < MAX_ITEM_PROTO_SOCKETS; ++i)
|
||||
{
|
||||
if (!CheckDelimiter(iss, DELIMITER, "item"))
|
||||
return false;
|
||||
|
||||
numBonusListIDs = 0;
|
||||
if (HasValue(iss) && !ReadUInt32(iss, numBonusListIDs))
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item bonus lists size for gem %u", iss.str().c_str(), i);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (numBonusListIDs > maxBonusListIDs)
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): too many item bonus list IDs %u in |item command for gem %u", iss.str().c_str(), numBonusListIDs, i);
|
||||
return false;
|
||||
}
|
||||
|
||||
_gemBonusListIDs[i].resize(numBonusListIDs);
|
||||
for (uint32 index = 0; index < numBonusListIDs; ++index)
|
||||
{
|
||||
if (!CheckDelimiter(iss, DELIMITER, "item"))
|
||||
return false;
|
||||
|
||||
int32 id = 0;
|
||||
if (!ReadInt32(iss, id))
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading item bonus list id (index %u) for gem %u", iss.str().c_str(), index, i);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sDB2Manager.GetItemBonusList(id))
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): got invalid item bonus list id %d in |item command for gem %u", iss.str().c_str(), id, i);
|
||||
return false;
|
||||
}
|
||||
|
||||
_gemBonusListIDs[i][index] = id;
|
||||
}
|
||||
}
|
||||
|
||||
if (!CheckDelimiter(iss, DELIMITER, "item"))
|
||||
return false;
|
||||
|
||||
// guid as string
|
||||
if (HasValue(iss))
|
||||
{
|
||||
std::array<char, 128> guidBuffer = { };
|
||||
if (!iss.getline(guidBuffer.data(), 128, DELIMITER))
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading creator guid string", iss.str().c_str());
|
||||
return false;
|
||||
}
|
||||
iss.unget(); // put next : back into stream
|
||||
}
|
||||
|
||||
if (!CheckDelimiter(iss, DELIMITER, "item"))
|
||||
return false;
|
||||
|
||||
if (HasValue(iss) && !ReadInt32(iss, _useEnchantId))
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading on use enchatment id", iss.str().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string ItemChatLink::FormatName(uint8 index, LocalizedString* suffixStrings) const
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << _item->GetName(LocaleConstant(index));
|
||||
|
||||
if (!(_item->GetFlags3() & ITEM_FLAG3_HIDE_NAME_SUFFIX))
|
||||
if (suffixStrings)
|
||||
ss << ' ' << suffixStrings->Str[index];
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
// item links are compacted to remove all zero values
|
||||
bool ItemChatLink::HasValue(std::istringstream& iss) const
|
||||
{
|
||||
char next = iss.peek();
|
||||
return next != DELIMITER && next != PIPE_CHAR;
|
||||
}
|
||||
|
||||
bool ItemChatLink::ValidateName(char* buffer, char const* context)
|
||||
{
|
||||
ChatLink::ValidateName(buffer, context);
|
||||
|
||||
// TODO: use suffix from ItemNameDescription
|
||||
LocalizedString* suffixStrings = nullptr;
|
||||
|
||||
for (uint8 locale = LOCALE_enUS; locale < TOTAL_LOCALES; ++locale)
|
||||
{
|
||||
if (locale == LOCALE_none)
|
||||
continue;
|
||||
|
||||
if (FormatName(locale, suffixStrings) == buffer)
|
||||
return true;
|
||||
}
|
||||
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): linked item (id: %u) name wasn't found in any localization", context, _item->GetId());
|
||||
return false;
|
||||
}
|
||||
|
||||
// |color|Hquest:quest_id:quest_level:min_level:max_level:scaling_faction|h[name]|h|r
|
||||
// |cffffff00|Hquest:51101:-1:110:120:5|h[The Wounded King]|h|r
|
||||
bool QuestChatLink::Initialize(std::istringstream& iss)
|
||||
{
|
||||
// Read quest id
|
||||
uint32 questId = 0;
|
||||
if (!ReadUInt32(iss, questId))
|
||||
{
|
||||
TC_LOG_TRACE("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)
|
||||
{
|
||||
TC_LOG_TRACE("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))
|
||||
{
|
||||
TC_LOG_TRACE("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)
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): quest level %d is too big", iss.str().c_str(), _questLevel);
|
||||
return false;
|
||||
}
|
||||
if (!ReadInt32(iss, _minLevel))
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading quest min level", iss.str().c_str());
|
||||
return false;
|
||||
}
|
||||
if (!ReadInt32(iss, _maxLevel))
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading quest max level", iss.str().c_str());
|
||||
return false;
|
||||
}
|
||||
if (!ReadInt32(iss, _scalingFaction))
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading quest scaling faction", iss.str().c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QuestChatLink::ValidateName(char* buffer, char const* context)
|
||||
{
|
||||
ChatLink::ValidateName(buffer, context);
|
||||
|
||||
bool res = (_quest->GetLogTitle() == buffer);
|
||||
if (!res)
|
||||
if (QuestTemplateLocale const* ql = sObjectMgr->GetQuestLocale(_quest->GetQuestId()))
|
||||
for (uint8 i = 0; i < ql->LogTitle.size(); i++)
|
||||
if (ql->LogTitle[i] == buffer)
|
||||
{
|
||||
res = true;
|
||||
break;
|
||||
}
|
||||
if (!res)
|
||||
TC_LOG_TRACE("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))
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading spell entry", iss.str().c_str());
|
||||
return false;
|
||||
}
|
||||
// Validate spell
|
||||
_spell = sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE);
|
||||
if (!_spell)
|
||||
{
|
||||
TC_LOG_TRACE("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, char const* context)
|
||||
{
|
||||
ChatLink::ValidateName(buffer, context);
|
||||
|
||||
// spells with that flag have a prefix of "$PROFESSION: "
|
||||
if (_spell->HasAttribute(SPELL_ATTR0_TRADESPELL))
|
||||
{
|
||||
SkillLineAbilityMapBounds bounds = sSpellMgr->GetSkillLineAbilityMapBounds(_spell->Id);
|
||||
if (bounds.first == bounds.second)
|
||||
{
|
||||
TC_LOG_TRACE("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)
|
||||
{
|
||||
TC_LOG_TRACE("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)
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): skill line not found for skill %u", context, skillInfo->SkillLine);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1))
|
||||
{
|
||||
uint32 skillLineNameLength = strlen(skillLine->DisplayName[i]);
|
||||
if (skillLineNameLength > 0 && strncmp(skillLine->DisplayName[i], buffer, skillLineNameLength) == 0)
|
||||
{
|
||||
// found the prefix, remove it to perform spellname validation below
|
||||
// -2 = strlen(": ")
|
||||
uint32 spellNameLength = strlen(buffer) - skillLineNameLength - 2;
|
||||
memmove(buffer, buffer + skillLineNameLength + 2, spellNameLength + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (uint8 i = 0; i < TOTAL_LOCALES; ++i)
|
||||
if (*_spell->SpellName->Str[i] && strcmp(_spell->SpellName->Str[i], buffer) == 0)
|
||||
return true;
|
||||
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): linked spell (id: %u) name wasn't found in any localization", context, _spell->Id);
|
||||
return false;
|
||||
}
|
||||
|
||||
// |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))
|
||||
{
|
||||
TC_LOG_TRACE("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)
|
||||
{
|
||||
TC_LOG_TRACE("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))
|
||||
{
|
||||
TC_LOG_TRACE("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]))
|
||||
{
|
||||
TC_LOG_TRACE("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, char const* context)
|
||||
{
|
||||
ChatLink::ValidateName(buffer, context);
|
||||
|
||||
for (LocaleConstant locale = LOCALE_enUS; locale < TOTAL_LOCALES; locale = LocaleConstant(locale + 1))
|
||||
{
|
||||
if (locale == LOCALE_none)
|
||||
continue;
|
||||
|
||||
if (strcmp(_achievement->Title[locale], buffer) == 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): linked achievement (id: %u) name wasn't found in any localization", context, _achievement->ID);
|
||||
return false;
|
||||
}
|
||||
|
||||
// |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))
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): sequence finished unexpectedly while reading achievement entry", iss.str().c_str());
|
||||
return false;
|
||||
}
|
||||
// Validate spell
|
||||
_spell = sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE);
|
||||
if (!_spell)
|
||||
{
|
||||
TC_LOG_TRACE("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))
|
||||
{
|
||||
TC_LOG_TRACE("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))
|
||||
{
|
||||
TC_LOG_TRACE("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))
|
||||
{
|
||||
TC_LOG_TRACE("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))
|
||||
{
|
||||
TC_LOG_TRACE("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)
|
||||
{
|
||||
TC_LOG_TRACE("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->SpellID, DIFFICULTY_NONE);
|
||||
if (!_spell)
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): got invalid spell id %u in |trade command", iss.str().c_str(), talentInfo->SpellID);
|
||||
return false;
|
||||
}
|
||||
// Delimiter
|
||||
if (!CheckDelimiter(iss, DELIMITER, "talent"))
|
||||
return false;
|
||||
// Rank
|
||||
if (!ReadInt32(iss, _rankId))
|
||||
{
|
||||
TC_LOG_TRACE("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))
|
||||
{
|
||||
TC_LOG_TRACE("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, DIFFICULTY_NONE);
|
||||
if (!_spell)
|
||||
{
|
||||
TC_LOG_TRACE("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))
|
||||
{
|
||||
TC_LOG_TRACE("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))
|
||||
{
|
||||
TC_LOG_TRACE("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)
|
||||
{
|
||||
TC_LOG_TRACE("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, DIFFICULTY_NONE);
|
||||
if (!_spell)
|
||||
{
|
||||
TC_LOG_TRACE("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(char const* 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";
|
||||
char const* 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)
|
||||
{
|
||||
TC_LOG_TRACE("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')
|
||||
{
|
||||
TC_LOG_TRACE("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
|
||||
{
|
||||
TC_LOG_TRACE("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
|
||||
TC_LOG_TRACE("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))
|
||||
{
|
||||
TC_LOG_TRACE("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())
|
||||
{
|
||||
TC_LOG_TRACE("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
|
||||
{
|
||||
TC_LOG_TRACE("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() != '[')
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): link caption doesn't start with '['", _iss.str().c_str());
|
||||
return false;
|
||||
}
|
||||
_iss.getline(buffer, 256, ']');
|
||||
if (_iss.eof())
|
||||
{
|
||||
TC_LOG_TRACE("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:
|
||||
TC_LOG_TRACE("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)
|
||||
{
|
||||
TC_LOG_TRACE("chat.system", "ChatHandler::isValidChatMessage('%s'): EOF in active sequence", _iss.str().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore 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 General Public License as published by the
|
||||
* Free Software Foundation; either version 2 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 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 TRINITYCORE_CHATLINK_H
|
||||
#define TRINITYCORE_CHATLINK_H
|
||||
|
||||
#include "SharedDefines.h"
|
||||
#include "Common.h"
|
||||
#include <list>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
|
||||
struct ItemLocale;
|
||||
struct ItemTemplate;
|
||||
struct ItemRandomSuffixEntry;
|
||||
struct ItemRandomPropertiesEntry;
|
||||
class SpellInfo;
|
||||
struct AchievementEntry;
|
||||
struct GlyphPropertiesEntry;
|
||||
class Quest;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ChatLink - abstract base class for various links
|
||||
class TC_GAME_API 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 TC_GAME_API ItemChatLink : public ChatLink
|
||||
{
|
||||
public:
|
||||
ItemChatLink() : ChatLink(), _item(nullptr), _enchantId(0), _reporterLevel(0), _reporterSpec(0), _context(0)
|
||||
{
|
||||
memset(_gemItemId, 0, sizeof(_gemItemId));
|
||||
}
|
||||
virtual bool Initialize(std::istringstream& iss) override;
|
||||
virtual bool ValidateName(char* buffer, const char* context) override;
|
||||
|
||||
protected:
|
||||
std::string FormatName(uint8 index, LocalizedString* suffixStrings) const;
|
||||
bool HasValue(std::istringstream& iss) const;
|
||||
|
||||
ItemTemplate const* _item;
|
||||
int32 _enchantId;
|
||||
int32 _gemItemId[3];
|
||||
int32 _reporterLevel;
|
||||
int32 _reporterSpec;
|
||||
int32 _context;
|
||||
int32 _useEnchantId;
|
||||
std::vector<int32> _bonusListIDs;
|
||||
std::vector<std::pair<uint32, int32>> _modifiers;
|
||||
std::vector<int32> _gemBonusListIDs[3];
|
||||
};
|
||||
|
||||
// QuestChatLink - link to quest
|
||||
class TC_GAME_API QuestChatLink : public ChatLink
|
||||
{
|
||||
public:
|
||||
QuestChatLink() : ChatLink(), _quest(nullptr), _questLevel(0), _minLevel(0), _maxLevel(0), _scalingFaction(0) { }
|
||||
virtual bool Initialize(std::istringstream& iss) override;
|
||||
virtual bool ValidateName(char* buffer, const char* context) override;
|
||||
|
||||
protected:
|
||||
Quest const* _quest;
|
||||
int32 _questLevel;
|
||||
int32 _minLevel;
|
||||
int32 _maxLevel;
|
||||
int32 _scalingFaction;
|
||||
};
|
||||
|
||||
// SpellChatLink - link to quest
|
||||
class TC_GAME_API SpellChatLink : public ChatLink
|
||||
{
|
||||
public:
|
||||
SpellChatLink() : ChatLink(), _spell(nullptr) { }
|
||||
virtual bool Initialize(std::istringstream& iss) override;
|
||||
virtual bool ValidateName(char* buffer, const char* context) override;
|
||||
|
||||
protected:
|
||||
SpellInfo const* _spell;
|
||||
};
|
||||
|
||||
// AchievementChatLink - link to quest
|
||||
class TC_GAME_API AchievementChatLink : public ChatLink
|
||||
{
|
||||
public:
|
||||
AchievementChatLink() : ChatLink(), _guid(0), _achievement(nullptr)
|
||||
{
|
||||
memset(_data, 0, sizeof(_data));
|
||||
}
|
||||
virtual bool Initialize(std::istringstream& iss) override;
|
||||
virtual bool ValidateName(char* buffer, const char* context) override;
|
||||
|
||||
protected:
|
||||
uint32 _guid;
|
||||
AchievementEntry const* _achievement;
|
||||
uint32 _data[8];
|
||||
};
|
||||
|
||||
// TradeChatLink - link to trade info
|
||||
class TC_GAME_API TradeChatLink : public SpellChatLink
|
||||
{
|
||||
public:
|
||||
TradeChatLink() : SpellChatLink(), _minSkillLevel(0), _maxSkillLevel(0), _guid(0) { }
|
||||
virtual bool Initialize(std::istringstream& iss) override;
|
||||
private:
|
||||
int32 _minSkillLevel;
|
||||
int32 _maxSkillLevel;
|
||||
uint32 _guid;
|
||||
std::string _base64;
|
||||
};
|
||||
|
||||
// TalentChatLink - link to talent
|
||||
class TC_GAME_API TalentChatLink : public SpellChatLink
|
||||
{
|
||||
public:
|
||||
TalentChatLink() : SpellChatLink(), _talentId(0), _rankId(0) { }
|
||||
virtual bool Initialize(std::istringstream& iss) override;
|
||||
|
||||
private:
|
||||
uint32 _talentId;
|
||||
int32 _rankId;
|
||||
};
|
||||
|
||||
// EnchantmentChatLink - link to enchantment
|
||||
class TC_GAME_API EnchantmentChatLink : public SpellChatLink
|
||||
{
|
||||
public:
|
||||
EnchantmentChatLink() : SpellChatLink() { }
|
||||
virtual bool Initialize(std::istringstream& iss) override;
|
||||
};
|
||||
|
||||
// GlyphChatLink - link to glyph
|
||||
class TC_GAME_API GlyphChatLink : public SpellChatLink
|
||||
{
|
||||
public:
|
||||
GlyphChatLink() : SpellChatLink(), _slotId(0), _glyph(nullptr) { }
|
||||
virtual bool Initialize(std::istringstream& iss) override;
|
||||
private:
|
||||
uint32 _slotId;
|
||||
GlyphPropertiesEntry const* _glyph;
|
||||
};
|
||||
|
||||
class TC_GAME_API LinkExtractor
|
||||
{
|
||||
public:
|
||||
explicit LinkExtractor(const char* msg);
|
||||
~LinkExtractor();
|
||||
|
||||
bool IsValidMessage();
|
||||
|
||||
private:
|
||||
typedef std::list<ChatLink*> Links;
|
||||
Links _links;
|
||||
std::istringstream _iss;
|
||||
};
|
||||
|
||||
|
||||
#endif // TRINITYCORE_CHATLINK_H
|
||||
187
src/server/game/Chat/HyperlinkTags.cpp
Normal file
187
src/server/game/Chat/HyperlinkTags.cpp
Normal file
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 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 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 "DB2Stores.h"
|
||||
#include "Item.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "SpellInfo.h"
|
||||
#include "SpellMgr.h"
|
||||
|
||||
static constexpr char HYPERLINK_DATA_DELIMITER = ':';
|
||||
|
||||
class HyperlinkDataTokenizer
|
||||
{
|
||||
public:
|
||||
HyperlinkDataTokenizer(char const* pos, size_t len, bool allowEmptyTokens = false) : _pos(pos), _len(len), _allowEmptyTokens(allowEmptyTokens), _empty(false) {}
|
||||
|
||||
template <typename T>
|
||||
bool TryConsumeTo(T& val)
|
||||
{
|
||||
if (_empty)
|
||||
return false;
|
||||
|
||||
char const* firstPos = _pos;
|
||||
size_t thisLen = 0;
|
||||
// find next delimiter
|
||||
for (; _len && *_pos != HYPERLINK_DATA_DELIMITER; --_len, ++_pos, ++thisLen);
|
||||
if (_len)
|
||||
--_len, ++_pos; // skip the delimiter
|
||||
else
|
||||
_empty = true;
|
||||
|
||||
if (_allowEmptyTokens && !thisLen)
|
||||
{
|
||||
val = T();
|
||||
return true;
|
||||
}
|
||||
|
||||
return Trinity::Hyperlinks::LinkTags::base_tag::StoreTo(val, firstPos, thisLen);
|
||||
}
|
||||
|
||||
bool IsEmpty() { return _empty; }
|
||||
|
||||
private:
|
||||
char const* _pos;
|
||||
size_t _len;
|
||||
bool _allowEmptyTokens;
|
||||
bool _empty;
|
||||
};
|
||||
|
||||
bool Trinity::Hyperlinks::LinkTags::achievement::StoreTo(AchievementLinkData& val, char const* pos, size_t len)
|
||||
{
|
||||
HyperlinkDataTokenizer t(pos, len);
|
||||
uint32 achievementId;
|
||||
if (!t.TryConsumeTo(achievementId))
|
||||
return false;
|
||||
val.Achievement = sAchievementStore.LookupEntry(achievementId);
|
||||
return val.Achievement && t.TryConsumeTo(val.CharacterId) && t.TryConsumeTo(val.IsFinished) &&
|
||||
t.TryConsumeTo(val.Month) && t.TryConsumeTo(val.Day) && t.TryConsumeTo(val.Year) && t.TryConsumeTo(val.Criteria[0]) &&
|
||||
t.TryConsumeTo(val.Criteria[1]) && t.TryConsumeTo(val.Criteria[2]) && t.TryConsumeTo(val.Criteria[3]) && t.IsEmpty();
|
||||
}
|
||||
|
||||
bool Trinity::Hyperlinks::LinkTags::enchant::StoreTo(SpellInfo const*& val, char const* pos, size_t len)
|
||||
{
|
||||
HyperlinkDataTokenizer t(pos, len);
|
||||
uint32 spellId;
|
||||
if (!(t.TryConsumeTo(spellId) && t.IsEmpty()))
|
||||
return false;
|
||||
return (val = sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE)) && val->HasAttribute(SPELL_ATTR0_TRADESPELL);
|
||||
}
|
||||
|
||||
bool Trinity::Hyperlinks::LinkTags::item::StoreTo(ItemLinkData& val, char const* pos, size_t len)
|
||||
{
|
||||
HyperlinkDataTokenizer t(pos, len, true);
|
||||
uint32 itemId, dummy, numBonusListIDs;
|
||||
if (!t.TryConsumeTo(itemId))
|
||||
return false;
|
||||
val.Item = sObjectMgr->GetItemTemplate(itemId);
|
||||
if (!(val.Item && t.TryConsumeTo(val.EnchantId) && t.TryConsumeTo(val.GemItemId[0]) && t.TryConsumeTo(val.GemItemId[1]) &&
|
||||
t.TryConsumeTo(val.GemItemId[2]) && t.TryConsumeTo(dummy) && !dummy && t.TryConsumeTo(dummy) && !dummy && t.TryConsumeTo(dummy) && !dummy &&
|
||||
t.TryConsumeTo(val.RenderLevel) && t.TryConsumeTo(val.RenderSpecialization) && t.TryConsumeTo(dummy) && !dummy &&
|
||||
t.TryConsumeTo(val.Context) && t.TryConsumeTo(numBonusListIDs)))
|
||||
return false;
|
||||
|
||||
constexpr uint32 maxBonusListIDs = 16;
|
||||
if (numBonusListIDs > maxBonusListIDs)
|
||||
return false;
|
||||
|
||||
BonusData evaluatedBonus;
|
||||
evaluatedBonus.Initialize(val.Item);
|
||||
|
||||
val.ItemBonusListIDs.resize(numBonusListIDs);
|
||||
for (int32& itemBonusListID : val.ItemBonusListIDs)
|
||||
{
|
||||
if (!t.TryConsumeTo(itemBonusListID) || !sDB2Manager.GetItemBonusList(itemBonusListID))
|
||||
return false;
|
||||
|
||||
evaluatedBonus.AddBonusList(itemBonusListID);
|
||||
}
|
||||
|
||||
val.Quality = evaluatedBonus.Quality;
|
||||
val.Suffix = sItemNameDescriptionStore.LookupEntry(evaluatedBonus.Suffix);
|
||||
if (evaluatedBonus.Suffix && !val.Suffix)
|
||||
return false;
|
||||
|
||||
uint32 numModifiers;
|
||||
if (!t.TryConsumeTo(numModifiers))
|
||||
return false;
|
||||
|
||||
if (numModifiers > MAX_ITEM_MODIFIERS)
|
||||
return false;
|
||||
|
||||
val.Modifiers.resize(numModifiers);
|
||||
for (ItemLinkData::Modifier& modifier : val.Modifiers)
|
||||
if (!(t.TryConsumeTo(modifier.Type) && modifier.Type < MAX_ITEM_MODIFIERS && t.TryConsumeTo(modifier.Value)))
|
||||
return false;
|
||||
|
||||
for (uint32 i = 0; i < MAX_ITEM_PROTO_SOCKETS; ++i)
|
||||
{
|
||||
if (!t.TryConsumeTo(numBonusListIDs) || numBonusListIDs > maxBonusListIDs)
|
||||
return false;
|
||||
|
||||
val.GemItemBonusListIDs[i].resize(numBonusListIDs);
|
||||
for (int32& itemBonusListID : val.GemItemBonusListIDs[i])
|
||||
if (!t.TryConsumeTo(itemBonusListID) || !sDB2Manager.GetItemBonusList(itemBonusListID))
|
||||
return false;
|
||||
}
|
||||
|
||||
return t.TryConsumeTo(val.Creator) && t.TryConsumeTo(val.UseEnchantId) && t.IsEmpty();
|
||||
}
|
||||
|
||||
bool Trinity::Hyperlinks::LinkTags::quest::StoreTo(QuestLinkData& val, char const* pos, size_t len)
|
||||
{
|
||||
HyperlinkDataTokenizer t(pos, len);
|
||||
uint32 questId;
|
||||
if (!t.TryConsumeTo(questId))
|
||||
return false;
|
||||
return (val.Quest = sObjectMgr->GetQuestTemplate(questId)) && t.TryConsumeTo(val.ContentTuningId) && t.IsEmpty();
|
||||
}
|
||||
|
||||
bool Trinity::Hyperlinks::LinkTags::spell::StoreTo(SpellLinkData& val, char const* pos, size_t len)
|
||||
{
|
||||
HyperlinkDataTokenizer t(pos, len);
|
||||
uint32 spellId, glyphPropertiesId;
|
||||
if (!(t.TryConsumeTo(spellId) && t.TryConsumeTo(glyphPropertiesId) && t.IsEmpty()))
|
||||
return false;
|
||||
return !!(val.Spell = sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE))
|
||||
&& (!glyphPropertiesId || !!(val.Glyph = sGlyphPropertiesStore.LookupEntry(glyphPropertiesId)));
|
||||
}
|
||||
|
||||
bool Trinity::Hyperlinks::LinkTags::talent::StoreTo(TalentEntry const*& val, char const* pos, size_t len)
|
||||
{
|
||||
HyperlinkDataTokenizer t(pos, len);
|
||||
uint32 talentId;
|
||||
if (!(t.TryConsumeTo(talentId) && t.IsEmpty()))
|
||||
return false;
|
||||
if (!(val = sTalentStore.LookupEntry(talentId)))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Trinity::Hyperlinks::LinkTags::trade::StoreTo(TradeskillLinkData& val, char const* pos, size_t len)
|
||||
{
|
||||
HyperlinkDataTokenizer t(pos, len);
|
||||
uint32 spellId, skillId;
|
||||
if (!t.TryConsumeTo(val.Owner) || !t.TryConsumeTo(spellId) || !t.TryConsumeTo(skillId) || !t.IsEmpty())
|
||||
return false;
|
||||
val.Spell = sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE);
|
||||
val.Skill = sSkillLineStore.LookupEntry(skillId);
|
||||
if (!val.Spell || !val.Spell->HasEffect(SPELL_EFFECT_TRADE_SKILL) || !val.Skill)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
383
src/server/game/Chat/Hyperlinks.cpp
Normal file
383
src/server/game/Chat/Hyperlinks.cpp
Normal file
@@ -0,0 +1,383 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 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 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 "Common.h"
|
||||
#include "DB2Stores.h"
|
||||
#include "Errors.h"
|
||||
#include "ItemTemplate.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "QuestDef.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "SpellInfo.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "World.h"
|
||||
#include "advstd.h"
|
||||
|
||||
using namespace Trinity::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 Trinity::Hyperlinks::ParseHyperlink(char const* pos)
|
||||
{
|
||||
//color tag
|
||||
if (*(pos++) != '|' || *(pos++) != 'c')
|
||||
return nullptr;
|
||||
uint32 color = 0;
|
||||
for (uint8 i = 0; i < 8; ++i)
|
||||
if (uint8 hex = toHex(*(pos++)))
|
||||
color = (color << 4) | (hex & 0xf);
|
||||
// link data start tag
|
||||
if (*(pos++) != '|' || *(pos++) != 'H')
|
||||
return nullptr;
|
||||
// link tag, find next : or |
|
||||
char const* tagStart = pos;
|
||||
size_t tagLength = 0;
|
||||
while (*pos && *pos != '|' && *(pos++) != ':') // we only advance pointer to one past if the last thing is : (not for |), this is intentional!
|
||||
++tagLength;
|
||||
// ok, link data, skip to next |
|
||||
char const* dataStart = pos;
|
||||
size_t dataLength = 0;
|
||||
while (*pos && *(pos++) != '|')
|
||||
++dataLength;
|
||||
// ok, next should be link data end tag...
|
||||
if (*(pos++) != 'h')
|
||||
return nullptr;
|
||||
// then visible link text, starts with [
|
||||
if (*(pos++) != '[')
|
||||
return nullptr;
|
||||
// skip until we hit the next ], abort on unexpected |
|
||||
char const* textStart = pos;
|
||||
size_t textLength = 0;
|
||||
while (*pos)
|
||||
{
|
||||
if (*pos == '|')
|
||||
return nullptr;
|
||||
if (*(pos++) == ']')
|
||||
break;
|
||||
++textLength;
|
||||
}
|
||||
// link end tag
|
||||
if (*(pos++) != '|' || *(pos++) != 'h' || *(pos++) != '|' || *(pos++) != 'r')
|
||||
return nullptr;
|
||||
// ok, valid hyperlink, return info
|
||||
return { pos, color, tagStart, tagLength, dataStart, dataLength, textStart, textLength };
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct LinkValidator
|
||||
{
|
||||
static bool IsTextValid(typename T::value_type, char const*, size_t) { return true; }
|
||||
static bool IsColorValid(typename T::value_type, HyperlinkColor) { return true; }
|
||||
};
|
||||
|
||||
// str1 is null-terminated, str2 is length-terminated, check if they are exactly equal
|
||||
static bool equal_with_len(char const* str1, char const* str2, size_t len)
|
||||
{
|
||||
if (!*str1)
|
||||
return false;
|
||||
while (len && *str1 && *(str1++) == *(str2++))
|
||||
--len;
|
||||
return !len && !*str1;
|
||||
}
|
||||
|
||||
template <>
|
||||
struct LinkValidator<LinkTags::achievement>
|
||||
{
|
||||
static bool IsTextValid(AchievementLinkData const& data, char const* pos, size_t len)
|
||||
{
|
||||
if (!len)
|
||||
return false;
|
||||
for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1))
|
||||
if (equal_with_len(data.Achievement->Title[i], pos, len))
|
||||
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, char const* pos, size_t len)
|
||||
{
|
||||
LocalizedString const* suffixStrings = nullptr;
|
||||
if (!(data.Item->GetFlags3() & ITEM_FLAG3_HIDE_NAME_SUFFIX) && data.Suffix)
|
||||
suffixStrings = &data.Suffix->Description;
|
||||
|
||||
for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1))
|
||||
{
|
||||
std::string name = data.Item->GetName(i);
|
||||
if (name.empty())
|
||||
continue;
|
||||
if (suffixStrings)
|
||||
{
|
||||
if (len > name.length() + 1 &&
|
||||
(strncmp(name.c_str(), pos, name.length()) == 0) &&
|
||||
(*(pos + name.length()) == ' ') &&
|
||||
equal_with_len((*suffixStrings)[i], pos + name.length() + 1, len - name.length() - 1))
|
||||
return true;
|
||||
}
|
||||
else if (equal_with_len(name.c_str(), pos, len))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool IsColorValid(ItemLinkData const& data, HyperlinkColor c)
|
||||
{
|
||||
return c == ItemQualityColors[data.Quality];
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct LinkValidator<LinkTags::quest>
|
||||
{
|
||||
static bool IsTextValid(QuestLinkData const& data, char const* pos, size_t len)
|
||||
{
|
||||
QuestTemplateLocale const* locale = sObjectMgr->GetQuestLocale(data.Quest->GetQuestId());
|
||||
if (!locale)
|
||||
return equal_with_len(data.Quest->GetLogTitle().c_str(), pos, len);
|
||||
|
||||
for (uint8 i = 0; i < TOTAL_LOCALES; ++i)
|
||||
{
|
||||
std::string const& name = (i == DEFAULT_LOCALE) ? data.Quest->GetLogTitle() : locale->LogTitle[i];
|
||||
if (name.empty())
|
||||
continue;
|
||||
if (equal_with_len(name.c_str(), pos, len))
|
||||
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(SpellLinkData const& data, char const* pos, size_t len)
|
||||
{
|
||||
return IsTextValid(data.Spell, pos, len);
|
||||
}
|
||||
|
||||
static bool IsTextValid(SpellInfo const* info, char const* pos, size_t len)
|
||||
{
|
||||
for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1))
|
||||
if (equal_with_len((*info->SpellName)[i], pos, len))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool IsColorValid(SpellLinkData const&, HyperlinkColor c)
|
||||
{
|
||||
return c == CHAT_LINK_COLOR_SPELL;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct LinkValidator<LinkTags::enchant>
|
||||
{
|
||||
static bool IsTextValid(SpellInfo const* info, char const* pos, size_t len)
|
||||
{
|
||||
SkillLineAbilityMapBounds bounds = sSpellMgr->GetSkillLineAbilityMapBounds(info->Id);
|
||||
if (bounds.first == bounds.second)
|
||||
return LinkValidator<LinkTags::spell>::IsTextValid(info, pos, len);
|
||||
|
||||
SkillLineEntry const* skill = sSkillLineStore.LookupEntry(bounds.first->second->SkillupSkillLineID
|
||||
? bounds.first->second->SkillupSkillLineID
|
||||
: bounds.first->second->SkillLine);
|
||||
if (!skill)
|
||||
return false;
|
||||
|
||||
for (LocaleConstant i = LOCALE_enUS; i < TOTAL_LOCALES; i = LocaleConstant(i + 1))
|
||||
{
|
||||
char const* skillName = skill->DisplayName[i];
|
||||
size_t skillLen = strlen(skillName);
|
||||
if (len > skillLen + 2 && // or of form [Skill Name: Spell Name]
|
||||
!strncmp(pos, skillName, skillLen) && !strncmp(pos + skillLen, ": ", 2) &&
|
||||
equal_with_len((*info->SpellName)[i], pos + (skillLen + 2), len - (skillLen + 2)))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool IsColorValid(SpellInfo const*, HyperlinkColor c)
|
||||
{
|
||||
return c == CHAT_LINK_COLOR_ENCHANT;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct LinkValidator<LinkTags::talent>
|
||||
{
|
||||
static bool IsTextValid(TalentEntry const* talent, char const* pos, size_t len)
|
||||
{
|
||||
if (SpellInfo const* info = sSpellMgr->GetSpellInfo(talent->SpellID, DIFFICULTY_NONE))
|
||||
return LinkValidator<LinkTags::spell>::IsTextValid(info, pos, len);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool IsColorValid(TalentEntry const*, HyperlinkColor c)
|
||||
{
|
||||
return c == CHAT_LINK_COLOR_TALENT;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct LinkValidator<LinkTags::trade>
|
||||
{
|
||||
static bool IsTextValid(TradeskillLinkData const& data, char const* pos, size_t len)
|
||||
{
|
||||
return LinkValidator<LinkTags::spell>::IsTextValid(data.Spell, pos, len);
|
||||
}
|
||||
|
||||
static bool IsColorValid(TradeskillLinkData const&, HyperlinkColor c)
|
||||
{
|
||||
return c == CHAT_LINK_COLOR_TRADE;
|
||||
}
|
||||
};
|
||||
|
||||
#define TryValidateAs(tagname) \
|
||||
{ \
|
||||
ASSERT(!strcmp(LinkTags::tagname::tag(), #tagname)); \
|
||||
if (info.tag.second == strlen(LinkTags::tagname::tag()) && \
|
||||
!strncmp(info.tag.first, LinkTags::tagname::tag(), strlen(LinkTags::tagname::tag()))) \
|
||||
{ \
|
||||
advstd::remove_cvref_t<typename LinkTags::tagname::value_type> t; \
|
||||
if (!LinkTags::tagname::StoreTo(t, info.data.first, info.data.second)) \
|
||||
return false; \
|
||||
if (!LinkValidator<LinkTags::tagname>::IsColorValid(t, info.color)) \
|
||||
return false; \
|
||||
if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_SEVERITY)) \
|
||||
if (!LinkValidator<LinkTags::tagname>::IsTextValid(t, info.text.first, info.text.second)) \
|
||||
return false; \
|
||||
return true; \
|
||||
} \
|
||||
}
|
||||
|
||||
static bool ValidateLinkInfo(HyperlinkInfo const& info)
|
||||
{
|
||||
TryValidateAs(achievement);
|
||||
TryValidateAs(area);
|
||||
TryValidateAs(areatrigger);
|
||||
TryValidateAs(creature);
|
||||
TryValidateAs(creature_entry);
|
||||
TryValidateAs(enchant);
|
||||
TryValidateAs(gameevent);
|
||||
TryValidateAs(gameobject);
|
||||
TryValidateAs(gameobject_entry);
|
||||
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 Trinity::Hyperlinks::ValidateLinks(std::string& str)
|
||||
{
|
||||
bool allValid = true;
|
||||
std::string::size_type pos = std::string::npos;
|
||||
// Step 1: Strip all control sequences except ||, |H, |h, |c and |r
|
||||
do
|
||||
{
|
||||
if ((pos = str.rfind('|', pos)) == std::string::npos)
|
||||
break;
|
||||
if (pos && str[pos - 1] == '|')
|
||||
{
|
||||
--pos;
|
||||
continue;
|
||||
}
|
||||
char next = str[pos + 1];
|
||||
if (next == 'H' || next == 'h' || next == 'c' || next == 'r')
|
||||
continue;
|
||||
|
||||
allValid = false;
|
||||
str.erase(pos, 2);
|
||||
} while (pos--);
|
||||
|
||||
// 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
|
||||
pos = 0;
|
||||
while (pos < str.size() && (pos = str.find('|', pos)) != std::string::npos)
|
||||
{
|
||||
if (str[pos + 1] == '|') // this is an escaped pipe character (||)
|
||||
{
|
||||
pos += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
HyperlinkInfo info = ParseHyperlink(str.c_str() + pos);
|
||||
if (!info)
|
||||
{ // cannot be parsed at all, so we'll need to cut it out entirely
|
||||
// find the next start of a link
|
||||
std::string::size_type next = str.find("|c", pos + 1);
|
||||
// then backtrack to the previous return control sequence
|
||||
std::string::size_type end = str.rfind("|r", next);
|
||||
if (end == std::string::npos || end <= pos) // there is no potential end tag, remove everything after pos (up to next, if available)
|
||||
{
|
||||
if (next == std::string::npos)
|
||||
str.erase(pos);
|
||||
else
|
||||
str.erase(pos, next - pos);
|
||||
}
|
||||
else
|
||||
str.erase(pos, end - pos + 2);
|
||||
|
||||
allValid = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// ok, link parsed successfully - now validate it based on the tag
|
||||
if (!ValidateLinkInfo(info))
|
||||
{
|
||||
// invalid link info, replace with just text
|
||||
str.replace(pos, (info.next - str.c_str()) - pos, str, info.text.first - str.c_str(), info.text.second);
|
||||
allValid = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// tag is fine, find the next one
|
||||
pos = info.next - str.c_str();
|
||||
}
|
||||
|
||||
// all tags validated
|
||||
return allValid;
|
||||
}
|
||||
242
src/server/game/Chat/Hyperlinks.h
Normal file
242
src/server/game/Chat/Hyperlinks.h
Normal file
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 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 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 TRINITY_HYPERLINKS_H
|
||||
#define TRINITY_HYPERLINKS_H
|
||||
|
||||
#include "advstd.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
struct AchievementEntry;
|
||||
struct GlyphPropertiesEntry;
|
||||
struct ItemNameDescriptionEntry;
|
||||
struct ItemTemplate;
|
||||
class Quest;
|
||||
struct SkillLineEntry;
|
||||
class SpellInfo;
|
||||
struct TalentEntry;
|
||||
|
||||
namespace Trinity
|
||||
{
|
||||
namespace Hyperlinks
|
||||
{
|
||||
|
||||
struct AchievementLinkData
|
||||
{
|
||||
AchievementEntry const* Achievement;
|
||||
ObjectGuid CharacterId;
|
||||
bool IsFinished;
|
||||
uint16 Year;
|
||||
uint8 Month;
|
||||
uint8 Day;
|
||||
uint32 Criteria[4];
|
||||
};
|
||||
|
||||
struct ItemLinkData
|
||||
{
|
||||
ItemTemplate const* Item;
|
||||
uint32 EnchantId;
|
||||
uint32 GemItemId[3];
|
||||
uint8 RenderLevel;
|
||||
uint32 RenderSpecialization;
|
||||
uint8 Context;
|
||||
std::vector<int32> ItemBonusListIDs;
|
||||
|
||||
struct Modifier
|
||||
{
|
||||
uint32 Type;
|
||||
int32 Value;
|
||||
};
|
||||
|
||||
std::vector<Modifier> Modifiers;
|
||||
std::vector<int32> GemItemBonusListIDs[3];
|
||||
ObjectGuid Creator;
|
||||
uint32 UseEnchantId;
|
||||
|
||||
uint32 Quality;
|
||||
ItemNameDescriptionEntry const* Suffix;
|
||||
};
|
||||
|
||||
struct QuestLinkData
|
||||
{
|
||||
::Quest const* Quest;
|
||||
uint32 ContentTuningId;
|
||||
};
|
||||
|
||||
struct SpellLinkData
|
||||
{
|
||||
SpellInfo const* Spell;
|
||||
GlyphPropertiesEntry const* Glyph;
|
||||
};
|
||||
|
||||
struct TradeskillLinkData
|
||||
{
|
||||
ObjectGuid Owner;
|
||||
SpellInfo const* Spell;
|
||||
SkillLineEntry const* Skill;
|
||||
};
|
||||
|
||||
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 -> const char* *|
|
||||
|* - this method SHOULD be constexpr *|
|
||||
|* - returns identifier string for the link ("creature", "creature_entry", "item") *|
|
||||
|* - MUST expose static ::StoreTo method, (storage&, char const*, size_t) *|
|
||||
|* - assign value_type& based on content of std::string(char const*, size_t) *|
|
||||
|* - 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& val, char const* pos, size_t len)
|
||||
{
|
||||
val.assign(pos, len);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool StoreTo(ObjectGuid& val, char const* pos, size_t len)
|
||||
{
|
||||
val = ObjectGuid::FromString(std::string(pos, len));
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static std::enable_if_t<advstd::is_integral_v<T> && advstd::is_unsigned_v<T>, bool> StoreTo(T& val, char const* pos, size_t len)
|
||||
{
|
||||
try { val = std::stoull(std::string(pos, len)); }
|
||||
catch (...) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static std::enable_if_t<advstd::is_integral_v<T> && advstd::is_signed_v<T>, bool> StoreTo(T& val, char const* pos, size_t len)
|
||||
{
|
||||
try { val = std::stoll(std::string(pos, len)); }
|
||||
catch (...) { return false; }
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
#define make_base_tag(ltag, type) struct ltag : public base_tag { using value_type = type; static constexpr char const* 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, uint32);
|
||||
make_base_tag(gameobject, ObjectGuid::LowType);
|
||||
make_base_tag(gameobject_entry, uint32);
|
||||
make_base_tag(itemset, uint32);
|
||||
make_base_tag(player, std::string const&);
|
||||
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 TC_GAME_API achievement
|
||||
{
|
||||
using value_type = AchievementLinkData const&;
|
||||
static constexpr char const* tag() { return "achievement"; }
|
||||
static bool StoreTo(AchievementLinkData& val, char const* pos, size_t len);
|
||||
};
|
||||
|
||||
struct TC_GAME_API enchant
|
||||
{
|
||||
using value_type = SpellInfo const*;
|
||||
static constexpr char const* tag() { return "enchant"; }
|
||||
static bool StoreTo(SpellInfo const*& val, char const* pos, size_t len);
|
||||
};
|
||||
|
||||
struct TC_GAME_API item
|
||||
{
|
||||
using value_type = ItemLinkData const&;
|
||||
static constexpr char const* tag() { return "item"; }
|
||||
static bool StoreTo(ItemLinkData& val, char const* pos, size_t len);
|
||||
};
|
||||
|
||||
struct TC_GAME_API quest
|
||||
{
|
||||
using value_type = QuestLinkData const&;
|
||||
static constexpr char const* tag() { return "quest"; }
|
||||
static bool StoreTo(QuestLinkData& val, char const* pos, size_t len);
|
||||
};
|
||||
|
||||
struct TC_GAME_API spell
|
||||
{
|
||||
using value_type = SpellLinkData const&;
|
||||
static constexpr char const* tag() { return "spell"; }
|
||||
static bool StoreTo(SpellLinkData& val, char const* pos, size_t len);
|
||||
};
|
||||
|
||||
struct TC_GAME_API talent
|
||||
{
|
||||
using value_type = TalentEntry const*&;
|
||||
static constexpr char const* tag() { return "talent"; }
|
||||
static bool StoreTo(TalentEntry const*& val, char const* pos, size_t len);
|
||||
};
|
||||
|
||||
struct TC_GAME_API trade
|
||||
{
|
||||
using value_type = TradeskillLinkData const&;
|
||||
static constexpr char const* tag() { return "trade"; }
|
||||
static bool StoreTo(TradeskillLinkData& val, char const* pos, size_t len);
|
||||
};
|
||||
}
|
||||
|
||||
struct HyperlinkColor
|
||||
{
|
||||
HyperlinkColor(uint32 c) : r(c >> 16), g(c >> 8), b(c), a(c >> 24) {}
|
||||
uint8 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(char const* n = nullptr, uint32 c = 0, char const* tS = nullptr, size_t tL = 0, char const* dS = nullptr, size_t dL = 0, char const* cS = nullptr, size_t cL = 0) :
|
||||
next(n), color(c), tag(tS, tL), data(dS, dL), text(cS, cL) {}
|
||||
|
||||
explicit operator bool() { return next; }
|
||||
char const* const next;
|
||||
HyperlinkColor const color;
|
||||
std::pair<char const*, size_t> const tag;
|
||||
std::pair<char const*, size_t> const data;
|
||||
std::pair<char const*, size_t> const text;
|
||||
};
|
||||
HyperlinkInfo TC_GAME_API ParseHyperlink(char const* pos);
|
||||
bool TC_GAME_API ValidateLinks(std::string&);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -51,7 +51,6 @@ char const* ItemTemplate::GetName(LocaleConstant locale) const
|
||||
return ExtendedData->Display[locale];
|
||||
}
|
||||
|
||||
|
||||
bool ItemTemplate::CanChangeEquipStateInCombat() const
|
||||
{
|
||||
switch (GetInventoryType())
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "Group.h"
|
||||
#include "Guild.h"
|
||||
#include "GuildMgr.h"
|
||||
#include "Hyperlinks.h"
|
||||
#include "Language.h"
|
||||
#include "LanguageMgr.h"
|
||||
#include "Log.h"
|
||||
@@ -42,6 +43,37 @@
|
||||
#include "World.h"
|
||||
#include "WorldPacket.h"
|
||||
|
||||
static void StripInvisibleChars(std::string& str)
|
||||
{
|
||||
static std::string const invChars = " \t\7\n";
|
||||
|
||||
size_t wpos = 0;
|
||||
|
||||
bool space = false;
|
||||
for (size_t pos = 0; pos < str.size(); ++pos)
|
||||
{
|
||||
if (invChars.find(str[pos]) != std::string::npos)
|
||||
{
|
||||
if (!space)
|
||||
{
|
||||
str[wpos++] = ' ';
|
||||
space = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (wpos != pos)
|
||||
str[wpos++] = str[pos];
|
||||
else
|
||||
++wpos;
|
||||
space = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (wpos < str.size())
|
||||
str.erase(wpos, str.size());
|
||||
}
|
||||
|
||||
void WorldSession::HandleChatMessageOpcode(WorldPackets::Chat::ChatMessage& chatMessage)
|
||||
{
|
||||
ChatMsg type;
|
||||
@@ -174,27 +206,32 @@ void WorldSession::HandleChatMessage(ChatMsg type, Language lang, std::string ms
|
||||
return;
|
||||
}
|
||||
|
||||
// Strip invisible characters for non-addon messages
|
||||
if (sWorld->getBoolConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING))
|
||||
StripInvisibleChars(msg);
|
||||
|
||||
if (msg.empty())
|
||||
return;
|
||||
|
||||
if (ChatHandler(this).ParseCommands(msg.c_str()))
|
||||
return;
|
||||
|
||||
// Strip invisible characters for non-addon messages
|
||||
if (sWorld->getBoolConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING))
|
||||
stripLineInvisibleChars(msg);
|
||||
|
||||
if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_SEVERITY) && !ChatHandler(this).isValidChatMessage(msg.c_str()))
|
||||
bool validMessage = Trinity::Hyperlinks::ValidateLinks(msg);
|
||||
if (!validMessage)
|
||||
{
|
||||
TC_LOG_ERROR("network", "Player %s (%s) sent a chatmessage with an invalid link: %s", GetPlayer()->GetName().c_str(),
|
||||
GetPlayer()->GetGUID().ToString().c_str(), msg.c_str());
|
||||
TC_LOG_ERROR("network", "Player %s (%s) sent a chatmessage with an invalid link - corrected", GetPlayer()->GetName().c_str(),
|
||||
GetPlayer()->GetGUID().ToString().c_str());
|
||||
|
||||
if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_KICK))
|
||||
{
|
||||
KickPlayer();
|
||||
|
||||
return;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.length() > 255)
|
||||
return;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case CHAT_MSG_SAY:
|
||||
@@ -431,6 +468,22 @@ void WorldSession::HandleChatAddonMessage(ChatMsg type, std::string prefix, std:
|
||||
if (prefix == AddonChannelCommandHandler::PREFIX && AddonChannelCommandHandler(this).ParseCommands(text.c_str()))
|
||||
return;
|
||||
|
||||
bool validMessage = Trinity::Hyperlinks::ValidateLinks(text);
|
||||
if (!validMessage)
|
||||
{
|
||||
TC_LOG_ERROR("network", "Player %s (%s) sent a chatmessage with an invalid link - corrected", GetPlayer()->GetName().c_str(),
|
||||
GetPlayer()->GetGUID().ToString().c_str());
|
||||
|
||||
if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_KICK))
|
||||
{
|
||||
KickPlayer();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (text.length() > 255)
|
||||
return;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case CHAT_MSG_GUILD:
|
||||
@@ -505,6 +558,26 @@ void WorldSession::HandleChatMessageAFKOpcode(WorldPackets::Chat::ChatMessageAFK
|
||||
if (sender->IsInCombat())
|
||||
return;
|
||||
|
||||
// Strip invisible characters for non-addon messages
|
||||
if (sWorld->getBoolConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING))
|
||||
StripInvisibleChars(chatMessageAFK.Text);
|
||||
|
||||
bool validMessage = Trinity::Hyperlinks::ValidateLinks(chatMessageAFK.Text);
|
||||
if (!validMessage)
|
||||
{
|
||||
TC_LOG_ERROR("network", "Player %s (%s) sent a chatmessage with an invalid link - corrected", GetPlayer()->GetName().c_str(),
|
||||
GetPlayer()->GetGUID().ToString().c_str());
|
||||
|
||||
if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_KICK))
|
||||
{
|
||||
KickPlayer();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (chatMessageAFK.Text.length() > 255)
|
||||
return;
|
||||
|
||||
if (sender->HasAura(GM_SILENCE_AURA))
|
||||
{
|
||||
SendNotification(GetTrinityString(LANG_GM_SILENCE), sender->GetName().c_str());
|
||||
@@ -541,6 +614,26 @@ void WorldSession::HandleChatMessageDNDOpcode(WorldPackets::Chat::ChatMessageDND
|
||||
if (sender->IsInCombat())
|
||||
return;
|
||||
|
||||
// Strip invisible characters for non-addon messages
|
||||
if (sWorld->getBoolConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING))
|
||||
StripInvisibleChars(chatMessageDND.Text);
|
||||
|
||||
bool validMessage = Trinity::Hyperlinks::ValidateLinks(chatMessageDND.Text);
|
||||
if (!validMessage)
|
||||
{
|
||||
TC_LOG_ERROR("network", "Player %s (%s) sent a chatmessage with an invalid link - corrected", GetPlayer()->GetName().c_str(),
|
||||
GetPlayer()->GetGUID().ToString().c_str());
|
||||
|
||||
if (sWorld->getIntConfig(CONFIG_CHAT_STRICT_LINK_CHECKING_KICK))
|
||||
{
|
||||
KickPlayer();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (chatMessageDND.Text.length() > 255)
|
||||
return;
|
||||
|
||||
if (sender->HasAura(GM_SILENCE_AURA))
|
||||
{
|
||||
SendNotification(GetTrinityString(LANG_GM_SILENCE), sender->GetName().c_str());
|
||||
|
||||
@@ -343,11 +343,10 @@ enum ItemQualities
|
||||
ITEM_QUALITY_LEGENDARY = 5, // ORANGE
|
||||
ITEM_QUALITY_ARTIFACT = 6, // LIGHT YELLOW
|
||||
ITEM_QUALITY_HEIRLOOM = 7, // LIGHT BLUE
|
||||
ITEM_QUALITY_WOW_TOKEN = 8 // LIGHT BLUE
|
||||
ITEM_QUALITY_WOW_TOKEN = 8, // LIGHT BLUE
|
||||
MAX_ITEM_QUALITY
|
||||
};
|
||||
|
||||
#define MAX_ITEM_QUALITY 9
|
||||
|
||||
enum SpellCategory
|
||||
{
|
||||
SPELL_CATEGORY_FOOD = 11,
|
||||
@@ -360,7 +359,7 @@ enum SpellVisualKit
|
||||
SPELL_VISUAL_KIT_DRINK = 438
|
||||
};
|
||||
|
||||
const uint32 ItemQualityColors[MAX_ITEM_QUALITY] =
|
||||
uint32 constexpr ItemQualityColors[MAX_ITEM_QUALITY] =
|
||||
{
|
||||
0xff9d9d9d, // GREY
|
||||
0xffffffff, // WHITE
|
||||
@@ -373,6 +372,16 @@ const uint32 ItemQualityColors[MAX_ITEM_QUALITY] =
|
||||
0xff00ccff // LIGHT BLUE
|
||||
};
|
||||
|
||||
size_t constexpr MAX_QUEST_DIFFICULTY = 5;
|
||||
uint32 constexpr QuestDifficultyColors[MAX_QUEST_DIFFICULTY] =
|
||||
{
|
||||
0xff40c040,
|
||||
0xff808080,
|
||||
0xffffff00,
|
||||
0xffff8040,
|
||||
0xffff2020
|
||||
};
|
||||
|
||||
// ***********************************
|
||||
// Spell Attributes definitions
|
||||
// ***********************************
|
||||
@@ -5633,14 +5642,13 @@ enum ChatFlags
|
||||
CHAT_FLAG_MOBILE = 0x40
|
||||
};
|
||||
|
||||
enum ChatLinkColors
|
||||
enum ChatLinkColors : uint32
|
||||
{
|
||||
CHAT_LINK_COLOR_TRADE = 0xffffd000, // orange
|
||||
CHAT_LINK_COLOR_TALENT = 0xff4e96f7, // blue
|
||||
CHAT_LINK_COLOR_SPELL = 0xff71d5ff, // bright blue
|
||||
CHAT_LINK_COLOR_ENCHANT = 0xffffd000, // orange
|
||||
CHAT_LINK_COLOR_ACHIEVEMENT = 0xffffff00,
|
||||
CHAT_LINK_COLOR_GLYPH = 0xff66bbff
|
||||
CHAT_LINK_COLOR_TRADE = 0xffffd000, // orange
|
||||
CHAT_LINK_COLOR_TALENT = 0xff71d5ff, // bright blue
|
||||
CHAT_LINK_COLOR_SPELL = 0xff71d5ff, // bright blue
|
||||
CHAT_LINK_COLOR_ENCHANT = 0xffffd000, // orange
|
||||
CHAT_LINK_COLOR_ACHIEVEMENT = 0xffffff00,
|
||||
};
|
||||
|
||||
// Values from ItemPetFood (power of (value-1) used for compare with CreatureFamilyEntry.petDietMask
|
||||
|
||||
@@ -1940,11 +1940,8 @@ ChatFakeMessagePreventing = 1
|
||||
#
|
||||
# ChatStrictLinkChecking.Severity
|
||||
# Description: Check chat messages for ingame 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
|
||||
# Default: 0 - (Only verify that link format looks valid without checking the text)
|
||||
# 1 - (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)
|
||||
|
||||
@@ -1952,10 +1949,10 @@ ChatStrictLinkChecking.Severity = 0
|
||||
|
||||
#
|
||||
# ChatStrictLinkChecking.Kick
|
||||
# Description: Defines what should be done if a message is considered to contain invalid
|
||||
# pipe commands.
|
||||
# Default: 0 - (Silently ignore message)
|
||||
# 1 - (Disconnect players who sent malformed messages)
|
||||
# Description: Defines what should be done if a message containing invalid control characters
|
||||
# is received.
|
||||
# Default: 0 - (Process sanitized message normally)
|
||||
# 1 - (Ignore message and kick player)
|
||||
|
||||
ChatStrictLinkChecking.Kick = 0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user