mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-16 07:30:42 +01:00
Core/ChatCommands: C++17 cleanup (again) (PR #25323)
This commit is contained in:
@@ -33,199 +33,106 @@
|
||||
class ChatHandler;
|
||||
class CommandArgs;
|
||||
|
||||
template <typename T>
|
||||
struct CommandArgsConsumerSingle
|
||||
namespace Trinity::Impl::ChatCommands
|
||||
{
|
||||
using arginfo = Trinity::ChatCommands::ArgInfo<T>;
|
||||
static char const* TryConsumeTo(T& val, char const* args)
|
||||
template <typename T>
|
||||
struct SingleConsumer
|
||||
{
|
||||
return arginfo::TryConsume(val, args);
|
||||
}
|
||||
};
|
||||
|
||||
struct CommandArgsVariantConsumer
|
||||
{
|
||||
template <typename V, typename T1, typename... Ts>
|
||||
static char const* TryConsumeTo(V& val, char const* args)
|
||||
{
|
||||
T1 v;
|
||||
if (char const* next = CommandArgsConsumerSingle<T1>::TryConsumeTo(v, args))
|
||||
static Optional<std::string_view> TryConsumeTo(T& val, std::string_view args)
|
||||
{
|
||||
val = std::move(v);
|
||||
return next;
|
||||
return ArgInfo<T>::TryConsume(val, args);
|
||||
}
|
||||
else if constexpr (sizeof...(Ts) > 0)
|
||||
return TryConsumeTo<V, Ts...>(val, args);
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
struct CommandArgsConsumerSingle<Trinity::ChatCommands::Variant<Ts...>>
|
||||
{
|
||||
static char const* TryConsumeTo(Trinity::ChatCommands::Variant<Ts...>& val, char const* args)
|
||||
template <typename... Ts>
|
||||
struct SingleConsumer<Trinity::ChatCommands::Variant<Ts...>>
|
||||
{
|
||||
return CommandArgsVariantConsumer::TryConsumeTo<Trinity::ChatCommands::Variant<Ts...>, Ts...>(val, args);
|
||||
}
|
||||
};
|
||||
using V = std::variant<Ts...>;
|
||||
static constexpr size_t N = std::variant_size_v<V>;
|
||||
|
||||
template <typename T>
|
||||
struct CommandArgsConsumerSingle<std::vector<T>>
|
||||
{
|
||||
static char const* TryConsumeTo(std::vector<T>& val, char const* args)
|
||||
{
|
||||
char const* last;
|
||||
val.clear();
|
||||
|
||||
do val.emplace_back();
|
||||
while ((args = CommandArgsConsumerSingle<T>::TryConsumeTo(val.back(), (last = args))));
|
||||
|
||||
val.pop_back();
|
||||
return last;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, std::size_t C>
|
||||
struct CommandArgsConsumerSingle<std::array<T, C>>
|
||||
{
|
||||
static char const* TryConsumeTo(std::array<T, C>& val, char const* args)
|
||||
{
|
||||
for (T& t : val)
|
||||
template <size_t I>
|
||||
static Optional<std::string_view> TryAtIndex(Trinity::ChatCommands::Variant<Ts...>& val, [[maybe_unused]] std::string_view args)
|
||||
{
|
||||
args = CommandArgsConsumerSingle<T>::TryConsumeTo(t, args);
|
||||
|
||||
if (!args)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct CommandArgsConsumerSingle<CommandArgs*>
|
||||
{
|
||||
static char const* TryConsumeTo(CommandArgs*&, char const* args) { return args; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct CommandArgsConsumerSingle<char const*>
|
||||
{
|
||||
static char const* TryConsumeTo(char const*&, char const* args) { return args; }
|
||||
};
|
||||
|
||||
template <typename T, size_t offset>
|
||||
struct CommandArgsConsumerNext;
|
||||
|
||||
template <typename Tuple, typename NextType, size_t offset>
|
||||
struct CommandArgsConsumerMulti
|
||||
{
|
||||
static char const* TryConsumeTo(Tuple& tuple, char const* args)
|
||||
{
|
||||
if (char const* next = CommandArgsConsumerSingle<NextType>::TryConsumeTo(std::get<offset>(tuple), args))
|
||||
return CommandArgsConsumerNext<Tuple, offset+1>::GoNext(tuple, next);
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Tuple, typename NestedNextType, size_t offset>
|
||||
struct CommandArgsConsumerMulti<Tuple, Optional<NestedNextType>, offset>
|
||||
{
|
||||
static char const* TryConsumeTo(Tuple& tuple, char const* args)
|
||||
{
|
||||
// try with the argument
|
||||
auto& myArg = std::get<offset>(tuple);
|
||||
myArg.emplace();
|
||||
if (char const* next = CommandArgsConsumerSingle<NestedNextType>::TryConsumeTo(myArg.value(), args))
|
||||
if ((next = CommandArgsConsumerNext<Tuple, offset+1>::GoNext(tuple, next)))
|
||||
return next;
|
||||
// try again omitting the argument
|
||||
myArg = std::nullopt;
|
||||
if (char const* next = CommandArgsConsumerNext<Tuple, offset+1>::GoNext(tuple, args))
|
||||
return next;
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
template <size_t offset, typename... Ts>
|
||||
struct CommandArgsConsumerNext<std::tuple<Ts...>, offset>
|
||||
{
|
||||
using tuple_type = std::tuple<Ts...>;
|
||||
|
||||
template <bool C = (offset < sizeof...(Ts))>
|
||||
static std::enable_if_t<C, char const*> GoNext(tuple_type& tuple, char const* args)
|
||||
{
|
||||
return CommandArgsConsumerMulti<tuple_type, std::tuple_element_t<offset, tuple_type>, offset>::TryConsumeTo(tuple, args);
|
||||
}
|
||||
|
||||
template <bool C = (offset < sizeof...(Ts))>
|
||||
static std::enable_if_t<!C, char const*> GoNext(tuple_type&, char const* args)
|
||||
{
|
||||
return args;
|
||||
}
|
||||
};
|
||||
|
||||
class TC_GAME_API CommandArgs
|
||||
{
|
||||
public:
|
||||
CommandArgs(char const* args) : _original(args), _args(args) {}
|
||||
|
||||
template <typename T1, typename T2, typename... Ts>
|
||||
auto TryConsume()
|
||||
{
|
||||
Optional<std::tuple<advstd::remove_cvref_t<T1>, advstd::remove_cvref_t<T2>, advstd::remove_cvref_t<Ts>...>> rv;
|
||||
rv.emplace();
|
||||
if (!TryConsumeToTuple<0>(rv.value()))
|
||||
rv = std::nullopt;
|
||||
return rv;
|
||||
}
|
||||
|
||||
template <typename T1>
|
||||
auto TryConsume()
|
||||
{
|
||||
using T = advstd::remove_cvref_t<T1>;
|
||||
Optional<T> rv;
|
||||
rv.emplace();
|
||||
if (char const* next = CommandArgsConsumerSingle<T>::TryConsumeTo(rv.value(), _args))
|
||||
_args = next;
|
||||
else
|
||||
rv = std::nullopt;
|
||||
return rv;
|
||||
}
|
||||
|
||||
template <size_t offset = 0, typename T>
|
||||
bool TryConsumeToTuple(T& tuple)
|
||||
{
|
||||
if (char const* next = CommandArgsConsumerNext<T, offset>::GoNext(tuple, _args))
|
||||
if constexpr (I < N)
|
||||
{
|
||||
_args = next;
|
||||
return true;
|
||||
if (Optional<std::string_view> next = ArgInfo<std::variant_alternative_t<I, V>>::TryConsume(val.template emplace<I>(), args))
|
||||
return next;
|
||||
else
|
||||
return TryAtIndex<I+1>(val, args);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void Reset() { _args = _original; }
|
||||
static Optional<std::string_view> TryConsumeTo(Trinity::ChatCommands::Variant<Ts...>& val, std::string_view args)
|
||||
{
|
||||
return TryAtIndex<0>(val, args);
|
||||
}
|
||||
};
|
||||
|
||||
char const* GetFullArgs() const { return _original; }
|
||||
char const* GetRemainingArgs() const { return _args; }
|
||||
/*
|
||||
for backwards compatibility, consumes the rest of the string
|
||||
new code should use the Tail/WTail tags defined in ChatCommandTags
|
||||
*/
|
||||
template <>
|
||||
struct SingleConsumer<char const*>
|
||||
{
|
||||
static Optional<std::string_view> TryConsumeTo(char const*& arg, std::string_view args) { arg = args.data(); return std::string_view(); }
|
||||
};
|
||||
|
||||
bool IsEmpty() const { return !!*_args; }
|
||||
explicit operator bool() const { return IsEmpty(); }
|
||||
|
||||
private:
|
||||
char const* const _original;
|
||||
char const* _args;
|
||||
};
|
||||
// forward declaration
|
||||
// ConsumeFromOffset contains the bounds check for offset, then hands off to MultiConsumer
|
||||
// the call stack is MultiConsumer -> ConsumeFromOffset -> MultiConsumer -> ConsumeFromOffset etc
|
||||
// MultiConsumer calls SingleConsumer in each iteration
|
||||
template <typename Tuple, size_t offset>
|
||||
Optional<std::string_view> ConsumeFromOffset(Tuple&, std::string_view args);
|
||||
|
||||
template <typename T> struct ChatCommandHandlerToTuple { static_assert(!std::is_same_v<T,T>, "Invalid command handler signature"); };
|
||||
template <typename... Ts> struct ChatCommandHandlerToTuple<bool(*)(ChatHandler*, Ts...)> { using type = std::tuple<ChatHandler*, advstd::remove_cvref_t<Ts>...>; };
|
||||
template <typename Tuple, typename NextType, size_t offset>
|
||||
struct MultiConsumer
|
||||
{
|
||||
static Optional<std::string_view> TryConsumeTo(Tuple& tuple, std::string_view args)
|
||||
{
|
||||
if (Optional<std::string_view> next = SingleConsumer<NextType>::TryConsumeTo(std::get<offset>(tuple), args))
|
||||
return ConsumeFromOffset<Tuple, offset + 1>(tuple, *next);
|
||||
else
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Tuple, typename NestedNextType, size_t offset>
|
||||
struct MultiConsumer<Tuple, Optional<NestedNextType>, offset>
|
||||
{
|
||||
static Optional<std::string_view> TryConsumeTo(Tuple& tuple, std::string_view args)
|
||||
{
|
||||
// try with the argument
|
||||
auto& myArg = std::get<offset>(tuple);
|
||||
myArg.emplace();
|
||||
if (Optional<std::string_view> next = SingleConsumer<NestedNextType>::TryConsumeTo(myArg.value(), args))
|
||||
if ((next = ConsumeFromOffset<Tuple, offset + 1>(tuple, *next)))
|
||||
return next;
|
||||
// try again omitting the argument
|
||||
myArg = std::nullopt;
|
||||
return ConsumeFromOffset<Tuple, offset + 1>(tuple, args);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Tuple, size_t offset>
|
||||
Optional<std::string_view> ConsumeFromOffset(Tuple& tuple, std::string_view args)
|
||||
{
|
||||
if constexpr (offset < std::tuple_size_v<Tuple>)
|
||||
return MultiConsumer<Tuple, std::tuple_element_t<offset, Tuple>, offset>::TryConsumeTo(tuple, args);
|
||||
else if (!args.empty()) /* the entire string must be consumed */
|
||||
return std::nullopt;
|
||||
else
|
||||
return args;
|
||||
}
|
||||
|
||||
template <typename T> struct HandlerToTuple { static_assert(Trinity::dependant_false_v<T>, "Invalid command handler signature"); };
|
||||
template <typename... Ts> struct HandlerToTuple<bool(*)(ChatHandler*, Ts...)> { using type = std::tuple<ChatHandler*, advstd::remove_cvref_t<Ts>...>; };
|
||||
template <typename T> using TupleType = typename HandlerToTuple<T>::type;
|
||||
}
|
||||
|
||||
template <typename T> struct ChatCommandStoreLastArg { static void store(T&, CommandArgs&) {} };
|
||||
template <> struct ChatCommandStoreLastArg<char const*> { static void store(char const*& arg, CommandArgs& args) { arg = args.GetRemainingArgs(); } };
|
||||
template <> struct ChatCommandStoreLastArg<CommandArgs*> { static void store(CommandArgs*& arg, CommandArgs& args) { arg = &args; } };
|
||||
|
||||
class TC_GAME_API ChatCommand
|
||||
{
|
||||
@@ -238,18 +145,12 @@ class TC_GAME_API ChatCommand
|
||||
{
|
||||
_wrapper = [](void* handler, ChatHandler* chatHandler, char const* argsStr)
|
||||
{
|
||||
using tuple_type = typename ChatCommandHandlerToTuple<TypedHandler>::type;
|
||||
using Tuple = Trinity::Impl::ChatCommands::TupleType<TypedHandler>;
|
||||
|
||||
tuple_type arguments;
|
||||
Tuple arguments;
|
||||
std::get<0>(arguments) = chatHandler;
|
||||
|
||||
CommandArgs args(argsStr);
|
||||
if (args.TryConsumeToTuple<1>(arguments))
|
||||
{
|
||||
auto& last = std::get<std::tuple_size_v<tuple_type>-1>(arguments);
|
||||
ChatCommandStoreLastArg<advstd::remove_cvref_t<decltype(last)>>::store(last, args);
|
||||
if (Trinity::Impl::ChatCommands::ConsumeFromOffset<Tuple, 1>(arguments, argsStr))
|
||||
return std::apply(reinterpret_cast<TypedHandler>(handler), std::move(arguments));
|
||||
}
|
||||
else
|
||||
return false;
|
||||
};
|
||||
@@ -272,7 +173,7 @@ class TC_GAME_API ChatCommand
|
||||
bool HasHandler() const { return !!_handler; }
|
||||
|
||||
char const* Name;
|
||||
uint32 Permission; // function pointer required correct align (use uint32)
|
||||
uint32 Permission;
|
||||
bool AllowConsole;
|
||||
std::string Help;
|
||||
std::vector<ChatCommand> ChildCommands;
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "ChatCommand.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "Util.h"
|
||||
|
||||
using namespace Trinity::ChatCommands;
|
||||
|
||||
@@ -29,12 +30,13 @@ struct AchievementVisitor
|
||||
value_type operator()(Hyperlink<achievement> achData) const { return achData->Achievement; }
|
||||
value_type operator()(uint32 achId) const { return sAchievementMgr->GetAchievement(achId); }
|
||||
};
|
||||
char const* Trinity::ChatCommands::ArgInfo<AchievementEntry const*>::TryConsume(AchievementEntry const*& data, char const* args)
|
||||
Optional<std::string_view> Trinity::Impl::ChatCommands::ArgInfo<AchievementEntry const*>::TryConsume(AchievementEntry const*& data, std::string_view args)
|
||||
{
|
||||
Variant<Hyperlink<achievement>, uint32> val;
|
||||
if ((args = CommandArgsConsumerSingle<decltype(val)>::TryConsumeTo(val, args)))
|
||||
Optional<std::string_view> next = SingleConsumer<decltype(val)>::TryConsumeTo(val, args);
|
||||
if (next)
|
||||
data = val.visit(AchievementVisitor());
|
||||
return args;
|
||||
return next;
|
||||
}
|
||||
|
||||
struct GameTeleVisitor
|
||||
@@ -43,12 +45,13 @@ struct GameTeleVisitor
|
||||
value_type operator()(Hyperlink<tele> tele) const { return sObjectMgr->GetGameTele(tele); }
|
||||
value_type operator()(std::string const& tele) const { return sObjectMgr->GetGameTele(tele); }
|
||||
};
|
||||
char const* Trinity::ChatCommands::ArgInfo<GameTele const*>::TryConsume(GameTele const*& data, char const* args)
|
||||
Optional<std::string_view> Trinity::Impl::ChatCommands::ArgInfo<GameTele const*>::TryConsume(GameTele const*& data, std::string_view args)
|
||||
{
|
||||
Variant<Hyperlink<tele>, std::string> val;
|
||||
if ((args = CommandArgsConsumerSingle<decltype(val)>::TryConsumeTo(val, args)))
|
||||
Optional<std::string_view> next = SingleConsumer<decltype(val)>::TryConsumeTo(val, args);
|
||||
if (next)
|
||||
data = val.visit(GameTeleVisitor());
|
||||
return args;
|
||||
return next;
|
||||
}
|
||||
|
||||
struct SpellInfoVisitor
|
||||
@@ -65,26 +68,11 @@ struct SpellInfoVisitor
|
||||
|
||||
value_type operator()(uint32 spellId) const { return sSpellMgr->GetSpellInfo(spellId); }
|
||||
};
|
||||
char const* Trinity::ChatCommands::ArgInfo<SpellInfo const*>::TryConsume(SpellInfo const*& data, char const* args)
|
||||
Optional<std::string_view> Trinity::Impl::ChatCommands::ArgInfo<SpellInfo const*>::TryConsume(SpellInfo const*& data, std::string_view args)
|
||||
{
|
||||
Variant<Hyperlink<enchant>, Hyperlink<glyph>, Hyperlink<spell>, Hyperlink<talent>, Hyperlink<trade>, uint32> val;
|
||||
if ((args = CommandArgsConsumerSingle<decltype(val)>::TryConsumeTo(val, args)))
|
||||
Optional<std::string_view> next = SingleConsumer<decltype(val)>::TryConsumeTo(val, args);
|
||||
if (next)
|
||||
data = val.visit(SpellInfoVisitor());
|
||||
return args;
|
||||
}
|
||||
|
||||
char const* Trinity::ChatCommands::ArgInfo<bool>::TryConsume(bool& data, char const* args)
|
||||
{
|
||||
std::string val;
|
||||
if ((args = CommandArgsConsumerSingle<std::string>::TryConsumeTo(val, args)))
|
||||
{
|
||||
strToLower(val);
|
||||
if (val == "on" || val == "yes" || val == "true" || val == "1" || val == "y")
|
||||
data = true;
|
||||
else if (val == "off" || val == "no" || val == "false" || val == "0" || val == "n")
|
||||
data = false;
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
return args;
|
||||
return next;
|
||||
}
|
||||
|
||||
@@ -30,17 +30,15 @@
|
||||
|
||||
struct GameTele;
|
||||
|
||||
namespace Trinity
|
||||
{
|
||||
namespace ChatCommands
|
||||
namespace Trinity::Impl::ChatCommands
|
||||
{
|
||||
|
||||
/************************** ARGUMENT HANDLERS *******************************************\
|
||||
|* Define how to extract contents of a certain requested type from a string *|
|
||||
|* Must implement the following: *|
|
||||
|* - TryConsume: T&, char const* -> char const* *|
|
||||
|* returns nullptr if no match, otherwise pointer to first character of next token *|
|
||||
|* - if nullptr is returned, state of T& is indeterminate *|
|
||||
|* - TryConsume: T&, std::string_view -> Optional<std::string_view> *|
|
||||
|* returns nullopt if no match, otherwise tail of argument string *|
|
||||
|* - if nullopt is returned, state of T& is indeterminate *|
|
||||
|* - otherwise, T& should be initialized to the intended return value *|
|
||||
|* *|
|
||||
\****************************************************************************************/
|
||||
@@ -51,16 +49,17 @@ struct ArgInfo { static_assert(!std::is_same_v<T,T>, "Invalid command parameter
|
||||
template <typename T>
|
||||
struct ArgInfo<T, std::enable_if_t<std::is_integral_v<T>>>
|
||||
{
|
||||
static char const* TryConsume(T& val, char const* args)
|
||||
static Optional<std::string_view> TryConsume(T& val, std::string_view args)
|
||||
{
|
||||
char const* next = args;
|
||||
std::string_view token(args, Trinity::Impl::ChatCommands::tokenize(next));
|
||||
auto [token, tail] = tokenize(args);
|
||||
if (token.empty())
|
||||
return std::nullopt;
|
||||
|
||||
if (Optional<T> v = StringTo<T>(token, 0))
|
||||
val = *v;
|
||||
else
|
||||
return nullptr;
|
||||
return next;
|
||||
return std::nullopt;
|
||||
return tail;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -68,20 +67,26 @@ struct ArgInfo<T, std::enable_if_t<std::is_integral_v<T>>>
|
||||
template <typename T>
|
||||
struct ArgInfo<T, std::enable_if_t<std::is_floating_point_v<T>>>
|
||||
{
|
||||
static char const* TryConsume(T& val, char const* args)
|
||||
static Optional<std::string_view> TryConsume(T& val, std::string_view args)
|
||||
{
|
||||
char const* next = args;
|
||||
std::string token(args, Trinity::Impl::ChatCommands::tokenize(next));
|
||||
auto [token, tail] = tokenize(args);
|
||||
if (token.empty())
|
||||
return std::nullopt;
|
||||
|
||||
try
|
||||
{
|
||||
// @todo replace this once libc++ supports double args to from_chars for required minimum
|
||||
size_t processedChars = 0;
|
||||
val = std::stold(token, &processedChars);
|
||||
val = std::stold(std::string(token), &processedChars);
|
||||
if (processedChars != token.length())
|
||||
return nullptr;
|
||||
return std::nullopt;
|
||||
}
|
||||
catch (...) { return nullptr; }
|
||||
return std::isfinite(val) ? next : nullptr;
|
||||
catch (...) { return std::nullopt; }
|
||||
|
||||
if (std::isfinite(val))
|
||||
return tail;
|
||||
else
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -89,16 +94,13 @@ struct ArgInfo<T, std::enable_if_t<std::is_floating_point_v<T>>>
|
||||
template <>
|
||||
struct ArgInfo<std::string_view, void>
|
||||
{
|
||||
static char const* TryConsume(std::string_view& val, char const* args)
|
||||
static Optional<std::string_view> TryConsume(std::string_view& val, std::string_view args)
|
||||
{
|
||||
char const* next = args;
|
||||
if (size_t len = Trinity::Impl::ChatCommands::tokenize(next))
|
||||
{
|
||||
val = std::string_view(args, len);
|
||||
return next;
|
||||
}
|
||||
else
|
||||
return nullptr;
|
||||
auto [token, next] = tokenize(args);
|
||||
if (token.empty())
|
||||
return std::nullopt;
|
||||
val = token;
|
||||
return next;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -106,13 +108,13 @@ struct ArgInfo<std::string_view, void>
|
||||
template <>
|
||||
struct ArgInfo<std::string, void>
|
||||
{
|
||||
static char const* TryConsume(std::string& val, char const* args)
|
||||
static Optional<std::string_view> TryConsume(std::string& val, std::string_view args)
|
||||
{
|
||||
std::string_view view;
|
||||
args = ArgInfo<std::string_view>::TryConsume(view, args);
|
||||
if (args)
|
||||
Optional<std::string_view> next = ArgInfo<std::string_view>::TryConsume(view, args);
|
||||
if (next)
|
||||
val.assign(view);
|
||||
return args;
|
||||
return next;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -120,18 +122,15 @@ struct ArgInfo<std::string, void>
|
||||
template <>
|
||||
struct ArgInfo<std::wstring, void>
|
||||
{
|
||||
static char const* TryConsume(std::wstring& val, char const* args)
|
||||
static Optional<std::string_view> TryConsume(std::wstring& val, std::string_view args)
|
||||
{
|
||||
std::string_view utf8view;
|
||||
char const* ret = ArgInfo<std::string_view>::TryConsume(utf8view, args);
|
||||
Optional<std::string_view> next = ArgInfo<std::string_view>::TryConsume(utf8view, args);
|
||||
|
||||
if (!ret)
|
||||
return nullptr;
|
||||
|
||||
if (!Utf8toWStr(utf8view, val))
|
||||
return nullptr;
|
||||
|
||||
return ret;
|
||||
if (next && Utf8toWStr(utf8view, val))
|
||||
return next;
|
||||
else
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -189,32 +188,31 @@ struct ArgInfo<T, std::enable_if_t<std::is_enum_v<T>>>
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static char const* TryConsume(T& val, char const* args)
|
||||
static Optional<std::string_view> TryConsume(T& val, std::string_view args)
|
||||
{
|
||||
std::string strVal;
|
||||
char const* ret = ArgInfo<std::string>::TryConsume(strVal, args);
|
||||
Optional<std::string_view> next = ArgInfo<std::string>::TryConsume(strVal, args);
|
||||
|
||||
if (!ret)
|
||||
return nullptr;
|
||||
|
||||
if (T const* tmpVal = Match(strVal))
|
||||
if (next)
|
||||
{
|
||||
val = *tmpVal;
|
||||
return ret;
|
||||
if (T const* match = Match(strVal))
|
||||
{
|
||||
val = *match;
|
||||
return next;
|
||||
}
|
||||
}
|
||||
|
||||
// Value not found. Try to parse arg as underlying type and cast it to enum type
|
||||
using U = std::underlying_type_t<T>;
|
||||
U uVal = 0;
|
||||
ret = ArgInfo<U>::TryConsume(uVal, args);
|
||||
if (ret)
|
||||
next = ArgInfo<U>::TryConsume(uVal, args);
|
||||
if (next && EnumUtils::IsValid<T>(uVal))
|
||||
{
|
||||
val = static_cast<T>(uVal);
|
||||
if (!EnumUtils::IsValid(val))
|
||||
return nullptr;
|
||||
return next;
|
||||
}
|
||||
|
||||
return ret;
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -222,41 +220,65 @@ struct ArgInfo<T, std::enable_if_t<std::is_enum_v<T>>>
|
||||
template <typename T>
|
||||
struct ArgInfo<T, std::enable_if_t<std::is_base_of_v<ContainerTag, T>>>
|
||||
{
|
||||
static char const* TryConsume(T& tag, char const* args)
|
||||
static Optional<std::string_view> TryConsume(T& tag, std::string_view args)
|
||||
{
|
||||
return tag.TryConsume(args);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct ArgInfo<std::vector<T>, void>
|
||||
{
|
||||
static Optional<std::string_view> TryConsume(std::vector<T>& val, std::string_view args)
|
||||
{
|
||||
val.clear();
|
||||
Optional<std::string_view> next = ArgInfo<T>::TryConsume(val.emplace_back(), args);
|
||||
|
||||
if (!next)
|
||||
return std::nullopt;
|
||||
|
||||
while (Optional<std::string_view> next2 = ArgInfo<T>::TryConsume(val.emplace_back(), *next))
|
||||
next = next2;
|
||||
|
||||
val.pop_back();
|
||||
return next;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, size_t N>
|
||||
struct ArgInfo<std::array<T, N>, void>
|
||||
{
|
||||
static Optional<std::string_view> TryConsume(std::array<T, N>& val, std::string_view args)
|
||||
{
|
||||
Optional<std::string_view> next = args;
|
||||
for (T& t : val)
|
||||
if (!(next = ArgInfo<T>::TryConsume(t, *next)))
|
||||
return std::nullopt;
|
||||
return next;
|
||||
}
|
||||
};
|
||||
|
||||
// AchievementEntry* from numeric id or link
|
||||
template <>
|
||||
struct TC_GAME_API ArgInfo<AchievementEntry const*>
|
||||
{
|
||||
static char const* TryConsume(AchievementEntry const*&, char const*);
|
||||
static Optional<std::string_view> TryConsume(AchievementEntry const*&, std::string_view);
|
||||
};
|
||||
|
||||
// GameTele* from string name or link
|
||||
template <>
|
||||
struct TC_GAME_API ArgInfo<GameTele const*>
|
||||
{
|
||||
static char const* TryConsume(GameTele const*&, char const*);
|
||||
static Optional<std::string_view> TryConsume(GameTele const*&, std::string_view);
|
||||
};
|
||||
|
||||
// SpellInfo const* from spell id or link
|
||||
template <>
|
||||
struct TC_GAME_API ArgInfo<SpellInfo const*>
|
||||
{
|
||||
static char const* TryConsume(SpellInfo const*&, char const*);
|
||||
static Optional<std::string_view> TryConsume(SpellInfo const*&, std::string_view);
|
||||
};
|
||||
|
||||
// bool from 1/0 or on/off
|
||||
template <>
|
||||
struct TC_GAME_API ArgInfo<bool>
|
||||
{
|
||||
static char const* TryConsume(bool&, char const*);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -18,10 +18,15 @@
|
||||
#ifndef TRINITY_CHATCOMMANDHELPERS_H
|
||||
#define TRINITY_CHATCOMMANDHELPERS_H
|
||||
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
namespace Trinity::ChatCommands
|
||||
namespace Trinity::Impl::ChatCommands
|
||||
{
|
||||
/***************** HELPERS *************************\
|
||||
|* These really aren't for outside use... *|
|
||||
\***************************************************/
|
||||
|
||||
static constexpr char COMMAND_DELIMITER = ' ';
|
||||
|
||||
template <typename T, typename = void>
|
||||
@@ -32,19 +37,26 @@ namespace Trinity::ChatCommands
|
||||
|
||||
template <typename T>
|
||||
using tag_base_t = typename tag_base<T>::type;
|
||||
}
|
||||
|
||||
namespace Trinity::Impl::ChatCommands
|
||||
{
|
||||
/***************** HELPERS *************************\
|
||||
|* These really aren't for outside use... *|
|
||||
\***************************************************/
|
||||
inline std::size_t tokenize(char const*& end)
|
||||
struct TokenizeResult {
|
||||
explicit operator bool() { return !token.empty(); }
|
||||
std::string_view token;
|
||||
std::string_view tail;
|
||||
};
|
||||
|
||||
inline TokenizeResult tokenize(std::string_view args)
|
||||
{
|
||||
std::size_t len = 0;
|
||||
for (; *end && *end != Trinity::ChatCommands::COMMAND_DELIMITER; ++end, ++len);
|
||||
for (; *end && *end == Trinity::ChatCommands::COMMAND_DELIMITER; ++end);
|
||||
return len;
|
||||
TokenizeResult result;
|
||||
if (size_t delimPos = args.find(COMMAND_DELIMITER); delimPos != std::string_view::npos)
|
||||
{
|
||||
result.token = args.substr(0, delimPos);
|
||||
if (size_t tailPos = args.find_first_not_of(COMMAND_DELIMITER, delimPos); tailPos != std::string_view::npos)
|
||||
result.tail = args.substr(tailPos);
|
||||
}
|
||||
else
|
||||
result.token = args;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T, typename... Ts>
|
||||
|
||||
@@ -22,59 +22,95 @@
|
||||
#include "ChatCommandHelpers.h"
|
||||
#include "Hyperlinks.h"
|
||||
#include "Optional.h"
|
||||
#include "Util.h"
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
namespace Trinity::ChatCommands
|
||||
namespace Trinity::Impl::ChatCommands
|
||||
{
|
||||
/************************** CONTAINER TAGS **********************************************\
|
||||
|* Simple holder classes to differentiate between extraction methods *|
|
||||
|* Should inherit from ContainerTag for template identification *|
|
||||
|* Must implement the following: *|
|
||||
|* - TryConsume: char const* -> char const* *|
|
||||
|* returns nullptr if no match, otherwise pointer to first character of next token *|
|
||||
|* - typedef value_type of type that is contained within the tag *|
|
||||
|* - cast operator to value_type *|
|
||||
|* *|
|
||||
\****************************************************************************************/
|
||||
struct ContainerTag {};
|
||||
template <typename T>
|
||||
struct tag_base<T, std::enable_if_t<std::is_base_of_v<ContainerTag, T>>>
|
||||
{
|
||||
using type = typename T::value_type;
|
||||
};
|
||||
}
|
||||
|
||||
namespace Trinity::ChatCommands
|
||||
{
|
||||
/************************** CONTAINER TAGS **********************************************\
|
||||
|* Simple holder classes to differentiate between extraction methods *|
|
||||
|* Must inherit from Trinity::Impl::ChatCommands::ContainerTag *|
|
||||
|* Must implement the following: *|
|
||||
|* - TryConsume: std::string_view -> Optional<std::string_view> *|
|
||||
|* returns nullopt if no match, otherwise the tail of the provided argument string *|
|
||||
|* - typedef value_type of type that is contained within the tag *|
|
||||
|* - cast operator to value_type *|
|
||||
|* *|
|
||||
\****************************************************************************************/
|
||||
|
||||
template <char c1, char... chars>
|
||||
struct ExactSequence : public ContainerTag
|
||||
struct ExactSequence : Trinity::Impl::ChatCommands::ContainerTag
|
||||
{
|
||||
using value_type = void;
|
||||
|
||||
static char const* _TryConsume(char const* pos)
|
||||
static constexpr size_t N = (sizeof...(chars) + 1);
|
||||
|
||||
static bool Match(char const* pos)
|
||||
{
|
||||
if (*(pos++) == c1)
|
||||
{
|
||||
if constexpr (sizeof...(chars) > 0)
|
||||
return ExactSequence<chars...>::_TryConsume(pos);
|
||||
else if (Trinity::Impl::ChatCommands::tokenize(pos)) /* we did not consume the entire token */
|
||||
return nullptr;
|
||||
else
|
||||
return pos;
|
||||
}
|
||||
if (*(pos++) != c1)
|
||||
return false;
|
||||
else if constexpr (sizeof...(chars) > 0)
|
||||
return ExactSequence<chars...>::Match(pos);
|
||||
else
|
||||
return nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
char const* TryConsume(char const* pos) const { return ExactSequence::_TryConsume(pos); }
|
||||
Optional<std::string_view> TryConsume(std::string_view args) const
|
||||
{
|
||||
if ((N <= args.length()) && ExactSequence::Match(args.data()))
|
||||
{
|
||||
auto [remainingToken, tail] = Trinity::Impl::ChatCommands::tokenize(args.substr(N));
|
||||
if (remainingToken.empty()) // if this is not empty, then we did not consume the full token
|
||||
return tail;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
struct Tail : std::string_view, Trinity::Impl::ChatCommands::ContainerTag
|
||||
{
|
||||
using value_type = std::string_view;
|
||||
|
||||
Optional<std::string_view> TryConsume(std::string_view args)
|
||||
{
|
||||
std::string_view::operator=(args);
|
||||
return std::string_view();
|
||||
}
|
||||
};
|
||||
|
||||
struct WTail : std::wstring, Trinity::Impl::ChatCommands::ContainerTag
|
||||
{
|
||||
using value_type = std::wstring;
|
||||
|
||||
Optional<std::string_view> TryConsume(std::string_view args)
|
||||
{
|
||||
if (Utf8toWStr(args, *this))
|
||||
return std::string_view();
|
||||
else
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename linktag>
|
||||
struct Hyperlink : public ContainerTag
|
||||
struct Hyperlink : Trinity::Impl::ChatCommands::ContainerTag
|
||||
{
|
||||
using value_type = typename linktag::value_type;
|
||||
using storage_type = advstd::remove_cvref_t<value_type>;
|
||||
@@ -84,29 +120,27 @@ namespace Trinity::ChatCommands
|
||||
value_type operator*() const { return val; }
|
||||
storage_type const* operator->() const { return &val; }
|
||||
|
||||
char const* TryConsume(char const* pos)
|
||||
Optional<std::string_view> TryConsume(std::string_view args)
|
||||
{
|
||||
Trinity::Hyperlinks::HyperlinkInfo info = Trinity::Hyperlinks::ParseHyperlink(pos);
|
||||
Trinity::Hyperlinks::HyperlinkInfo info = Trinity::Hyperlinks::ParseSingleHyperlink(args);
|
||||
// invalid hyperlinks cannot be consumed
|
||||
if (!info)
|
||||
return nullptr;
|
||||
return std::nullopt;
|
||||
|
||||
// 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;
|
||||
if (info.tag != linktag::tag())
|
||||
return std::nullopt;
|
||||
|
||||
// store value
|
||||
if (!linktag::StoreTo(val, info.data.first, info.data.second))
|
||||
return nullptr;
|
||||
if (!linktag::StoreTo(val, info.data))
|
||||
return std::nullopt;
|
||||
|
||||
// finally, skip to end of token
|
||||
pos = info.next;
|
||||
Trinity::Impl::ChatCommands::tokenize(pos);
|
||||
|
||||
// return final pos
|
||||
return pos;
|
||||
// finally, skip any potential delimiters
|
||||
auto [token, next] = Trinity::Impl::ChatCommands::tokenize(info.tail);
|
||||
if (token.empty()) /* empty token = first character is delimiter, skip past it */
|
||||
return next;
|
||||
else
|
||||
return info.tail;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -137,8 +171,8 @@ namespace Trinity::ChatCommands
|
||||
{
|
||||
using base = std::variant<T1, Ts...>;
|
||||
|
||||
using first_type = tag_base_t<T1>;
|
||||
static constexpr bool have_operators = Trinity::Impl::ChatCommands::are_all_assignable<first_type, tag_base_t<Ts>...>::value;
|
||||
using first_type = Trinity::Impl::ChatCommands::tag_base_t<T1>;
|
||||
static constexpr bool have_operators = Trinity::Impl::ChatCommands::are_all_assignable<first_type, Trinity::Impl::ChatCommands::tag_base_t<Ts>...>::value;
|
||||
|
||||
template <bool C = have_operators>
|
||||
std::enable_if_t<C, first_type> operator*() const
|
||||
|
||||
@@ -26,37 +26,38 @@ static constexpr char HYPERLINK_DATA_DELIMITER = ':';
|
||||
class HyperlinkDataTokenizer
|
||||
{
|
||||
public:
|
||||
HyperlinkDataTokenizer(char const* pos, size_t len) : _pos(pos), _len(len), _empty(false) {}
|
||||
HyperlinkDataTokenizer(std::string_view str) : _str(str) {}
|
||||
|
||||
template <typename T>
|
||||
bool TryConsumeTo(T& val)
|
||||
{
|
||||
if (_empty)
|
||||
return false;
|
||||
template <typename T>
|
||||
bool TryConsumeTo(T& val)
|
||||
{
|
||||
if (IsEmpty())
|
||||
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 (size_t off = _str.find(HYPERLINK_DATA_DELIMITER); off != std::string_view::npos)
|
||||
{
|
||||
if (!Trinity::Hyperlinks::LinkTags::base_tag::StoreTo(val, _str.substr(0, off)))
|
||||
return false;
|
||||
_str = _str.substr(off+1);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!Trinity::Hyperlinks::LinkTags::base_tag::StoreTo(val, _str))
|
||||
return false;
|
||||
_str = std::string_view();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return Trinity::Hyperlinks::LinkTags::base_tag::StoreTo(val, firstPos, thisLen);
|
||||
}
|
||||
|
||||
bool IsEmpty() { return _empty; }
|
||||
bool IsEmpty() { return _str.empty(); }
|
||||
|
||||
private:
|
||||
char const* _pos;
|
||||
size_t _len;
|
||||
bool _empty;
|
||||
std::string_view _str;
|
||||
};
|
||||
|
||||
bool Trinity::Hyperlinks::LinkTags::achievement::StoreTo(AchievementLinkData& val, char const* pos, size_t len)
|
||||
bool Trinity::Hyperlinks::LinkTags::achievement::StoreTo(AchievementLinkData& val, std::string_view text)
|
||||
{
|
||||
HyperlinkDataTokenizer t(pos, len);
|
||||
HyperlinkDataTokenizer t(text);
|
||||
uint32 achievementId;
|
||||
if (!t.TryConsumeTo(achievementId))
|
||||
return false;
|
||||
@@ -66,18 +67,18 @@ bool Trinity::Hyperlinks::LinkTags::achievement::StoreTo(AchievementLinkData& va
|
||||
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)
|
||||
bool Trinity::Hyperlinks::LinkTags::enchant::StoreTo(SpellInfo const*& val, std::string_view text)
|
||||
{
|
||||
HyperlinkDataTokenizer t(pos, len);
|
||||
HyperlinkDataTokenizer t(text);
|
||||
uint32 spellId;
|
||||
if (!(t.TryConsumeTo(spellId) && t.IsEmpty()))
|
||||
return false;
|
||||
return (val = sSpellMgr->GetSpellInfo(spellId)) && val->HasAttribute(SPELL_ATTR0_TRADESPELL);
|
||||
}
|
||||
|
||||
bool Trinity::Hyperlinks::LinkTags::glyph::StoreTo(GlyphLinkData& val, char const* pos, size_t len)
|
||||
bool Trinity::Hyperlinks::LinkTags::glyph::StoreTo(GlyphLinkData& val, std::string_view text)
|
||||
{
|
||||
HyperlinkDataTokenizer t(pos, len);
|
||||
HyperlinkDataTokenizer t(text);
|
||||
uint32 slot, prop;
|
||||
if (!(t.TryConsumeTo(slot) && t.TryConsumeTo(prop) && t.IsEmpty()))
|
||||
return false;
|
||||
@@ -88,9 +89,9 @@ bool Trinity::Hyperlinks::LinkTags::glyph::StoreTo(GlyphLinkData& val, char cons
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Trinity::Hyperlinks::LinkTags::item::StoreTo(ItemLinkData& val, char const* pos, size_t len)
|
||||
bool Trinity::Hyperlinks::LinkTags::item::StoreTo(ItemLinkData& val, std::string_view text)
|
||||
{
|
||||
HyperlinkDataTokenizer t(pos, len);
|
||||
HyperlinkDataTokenizer t(text);
|
||||
uint32 itemId, dummy;
|
||||
if (!t.TryConsumeTo(itemId))
|
||||
return false;
|
||||
@@ -100,27 +101,27 @@ bool Trinity::Hyperlinks::LinkTags::item::StoreTo(ItemLinkData& val, char const*
|
||||
t.TryConsumeTo(val.RenderLevel) && t.IsEmpty() && !dummy;
|
||||
}
|
||||
|
||||
bool Trinity::Hyperlinks::LinkTags::quest::StoreTo(QuestLinkData& val, char const* pos, size_t len)
|
||||
bool Trinity::Hyperlinks::LinkTags::quest::StoreTo(QuestLinkData& val, std::string_view text)
|
||||
{
|
||||
HyperlinkDataTokenizer t(pos, len);
|
||||
HyperlinkDataTokenizer t(text);
|
||||
uint32 questId;
|
||||
if (!t.TryConsumeTo(questId))
|
||||
return false;
|
||||
return (val.Quest = sObjectMgr->GetQuestTemplate(questId)) && t.TryConsumeTo(val.QuestLevel) && t.IsEmpty();
|
||||
}
|
||||
|
||||
bool Trinity::Hyperlinks::LinkTags::spell::StoreTo(SpellInfo const*& val, char const* pos, size_t len)
|
||||
bool Trinity::Hyperlinks::LinkTags::spell::StoreTo(SpellInfo const*& val, std::string_view text)
|
||||
{
|
||||
HyperlinkDataTokenizer t(pos, len);
|
||||
HyperlinkDataTokenizer t(text);
|
||||
uint32 spellId;
|
||||
if (!(t.TryConsumeTo(spellId) && t.IsEmpty()))
|
||||
return false;
|
||||
return !!(val = sSpellMgr->GetSpellInfo(spellId));
|
||||
}
|
||||
|
||||
bool Trinity::Hyperlinks::LinkTags::talent::StoreTo(TalentLinkData& val, char const* pos, size_t len)
|
||||
bool Trinity::Hyperlinks::LinkTags::talent::StoreTo(TalentLinkData& val, std::string_view text)
|
||||
{
|
||||
HyperlinkDataTokenizer t(pos, len);
|
||||
HyperlinkDataTokenizer t(text);
|
||||
uint32 talentId;
|
||||
int8 rank; // talent links contain <learned rank>-1, we store <learned rank>
|
||||
if (!(t.TryConsumeTo(talentId) && t.TryConsumeTo(rank) && t.IsEmpty()))
|
||||
@@ -135,9 +136,9 @@ bool Trinity::Hyperlinks::LinkTags::talent::StoreTo(TalentLinkData& val, char co
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Trinity::Hyperlinks::LinkTags::trade::StoreTo(TradeskillLinkData& val, char const* pos, size_t len)
|
||||
bool Trinity::Hyperlinks::LinkTags::trade::StoreTo(TradeskillLinkData& val, std::string_view text)
|
||||
{
|
||||
HyperlinkDataTokenizer t(pos, len);
|
||||
HyperlinkDataTokenizer t(text);
|
||||
uint32 spellId;
|
||||
if (!t.TryConsumeTo(spellId))
|
||||
return false;
|
||||
|
||||
@@ -31,82 +31,90 @@ 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)
|
||||
HyperlinkInfo Trinity::Hyperlinks::ParseSingleHyperlink(std::string_view str)
|
||||
{
|
||||
//color tag
|
||||
if (*(pos++) != '|' || *(pos++) != 'c')
|
||||
return nullptr;
|
||||
uint32 color = 0;
|
||||
std::string_view tag;
|
||||
std::string_view data;
|
||||
std::string_view text;
|
||||
|
||||
//color tag
|
||||
if (str.substr(0, 2) != "|c")
|
||||
return {};
|
||||
str.remove_prefix(2);
|
||||
|
||||
if (str.length() < 8)
|
||||
return {};
|
||||
|
||||
for (uint8 i = 0; i < 8; ++i)
|
||||
{
|
||||
if (uint8 hex = toHex(*(pos++)))
|
||||
if (uint8 hex = toHex(str[i]))
|
||||
color = (color << 4) | (hex & 0xf);
|
||||
else
|
||||
return nullptr;
|
||||
return {};
|
||||
}
|
||||
// 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)
|
||||
str.remove_prefix(8);
|
||||
|
||||
if (str.substr(0, 2) != "|H")
|
||||
return {};
|
||||
str.remove_prefix(2);
|
||||
|
||||
// tag+data part follows
|
||||
if (size_t delimPos = str.find('|'); delimPos != std::string_view::npos)
|
||||
{
|
||||
if (*pos == '|')
|
||||
return nullptr;
|
||||
if (*(pos++) == ']')
|
||||
break;
|
||||
++textLength;
|
||||
tag = str.substr(0, delimPos);
|
||||
str.remove_prefix(delimPos+1);
|
||||
}
|
||||
// link end tag
|
||||
if (*(pos++) != '|' || *(pos++) != 'h' || *(pos++) != '|' || *(pos++) != 'r')
|
||||
return nullptr;
|
||||
else
|
||||
return {};
|
||||
|
||||
// split tag if : is present (data separator)
|
||||
if (size_t dataStart = tag.find(':'); dataStart != std::string_view::npos)
|
||||
{
|
||||
data = tag.substr(dataStart+1);
|
||||
tag = tag.substr(0, dataStart);
|
||||
}
|
||||
|
||||
// ok, next should be link data end tag...
|
||||
if (str.substr(0, 1) != "h")
|
||||
return {};
|
||||
str.remove_prefix(1);
|
||||
// skip to final |
|
||||
if (size_t end = str.find('|'); end != std::string_view::npos)
|
||||
{
|
||||
// check end tag
|
||||
if (str.substr(end, 4) != "|h|r")
|
||||
return {};
|
||||
// check text brackets
|
||||
if ((str[0] != '[') || (str[end - 1] != ']'))
|
||||
return {};
|
||||
text = str.substr(1, end - 2);
|
||||
// tail
|
||||
str = str.substr(end + 4);
|
||||
}
|
||||
else
|
||||
return {};
|
||||
|
||||
// ok, valid hyperlink, return info
|
||||
return { pos, color, tagStart, tagLength, dataStart, dataLength, textStart, textLength };
|
||||
return { str, color, tag, data, text };
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct LinkValidator
|
||||
{
|
||||
static bool IsTextValid(typename T::value_type, char const*, size_t) { return true; }
|
||||
static bool IsTextValid(typename T::value_type, std::string_view) { 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)
|
||||
static bool IsTextValid(AchievementLinkData const& data, std::string_view text)
|
||||
{
|
||||
if (!len)
|
||||
if (text.empty())
|
||||
return false;
|
||||
for (uint8 i = 0; i < TOTAL_LOCALES; ++i)
|
||||
if (equal_with_len(data.Achievement->Title[i], pos, len))
|
||||
if (text == data.Achievement->Title[i])
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
@@ -120,22 +128,22 @@ struct LinkValidator<LinkTags::achievement>
|
||||
template <>
|
||||
struct LinkValidator<LinkTags::item>
|
||||
{
|
||||
static bool IsTextValid(ItemLinkData const& data, char const* pos, size_t len)
|
||||
static bool IsTextValid(ItemLinkData const& data, std::string_view text)
|
||||
{
|
||||
ItemLocale const* locale = sObjectMgr->GetItemLocale(data.Item->ItemId);
|
||||
|
||||
char const* const* randomSuffix = nullptr;
|
||||
char const* const* randomSuffixes = nullptr; // this is a c-style array of c strings (and i don't want to touch DBCStructure.h right now)
|
||||
if (data.RandomPropertyId < 0)
|
||||
{
|
||||
if (ItemRandomSuffixEntry const* suffixEntry = sItemRandomSuffixStore.LookupEntry(-data.RandomPropertyId))
|
||||
randomSuffix = suffixEntry->Name;
|
||||
randomSuffixes = suffixEntry->Name;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
else if (data.RandomPropertyId > 0)
|
||||
{
|
||||
if (ItemRandomPropertiesEntry const* propEntry = sItemRandomPropertiesStore.LookupEntry(data.RandomPropertyId))
|
||||
randomSuffix = propEntry->Name;
|
||||
randomSuffixes = propEntry->Name;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
@@ -147,15 +155,18 @@ struct LinkValidator<LinkTags::item>
|
||||
std::string const& name = (i == DEFAULT_LOCALE) ? data.Item->Name1 : locale->Name[i];
|
||||
if (name.empty())
|
||||
continue;
|
||||
if (randomSuffix)
|
||||
if (randomSuffixes)
|
||||
{
|
||||
if (len > name.length() + 1 &&
|
||||
(strncmp(name.c_str(), pos, name.length()) == 0) &&
|
||||
(*(pos + name.length()) == ' ') &&
|
||||
equal_with_len(randomSuffix[i], pos + name.length() + 1, len - name.length() - 1))
|
||||
std::string_view randomSuffix(randomSuffixes[i]);
|
||||
if (
|
||||
(text.length() == (name.length() + 1 + randomSuffix.length())) &&
|
||||
(text.substr(0, name.length()) == name) &&
|
||||
(text[name.length()] == ' ') &&
|
||||
(text.substr(name.length() + 1) == randomSuffix)
|
||||
)
|
||||
return true;
|
||||
}
|
||||
else if (equal_with_len(name.c_str(), pos, len))
|
||||
else if (text == name)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -170,18 +181,18 @@ struct LinkValidator<LinkTags::item>
|
||||
template <>
|
||||
struct LinkValidator<LinkTags::quest>
|
||||
{
|
||||
static bool IsTextValid(QuestLinkData const& data, char const* pos, size_t len)
|
||||
static bool IsTextValid(QuestLinkData const& data, std::string_view text)
|
||||
{
|
||||
QuestLocale const* locale = sObjectMgr->GetQuestLocale(data.Quest->GetQuestId());
|
||||
if (!locale)
|
||||
return equal_with_len(data.Quest->GetTitle().c_str(), pos, len);
|
||||
return text == data.Quest->GetTitle();
|
||||
|
||||
for (uint8 i = 0; i < TOTAL_LOCALES; ++i)
|
||||
{
|
||||
std::string const& name = (i == DEFAULT_LOCALE) ? data.Quest->GetTitle() : locale->Title[i];
|
||||
if (name.empty())
|
||||
continue;
|
||||
if (equal_with_len(name.c_str(), pos, len))
|
||||
if (text == name)
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -200,10 +211,10 @@ struct LinkValidator<LinkTags::quest>
|
||||
template <>
|
||||
struct LinkValidator<LinkTags::spell>
|
||||
{
|
||||
static bool IsTextValid(SpellInfo const* info, char const* pos, size_t len)
|
||||
static bool IsTextValid(SpellInfo const* info, std::string_view text)
|
||||
{
|
||||
for (uint8 i = 0; i < TOTAL_LOCALES; ++i)
|
||||
if (equal_with_len(info->SpellName[i], pos, len))
|
||||
if (text == info->SpellName[i])
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
@@ -217,9 +228,9 @@ struct LinkValidator<LinkTags::spell>
|
||||
template <>
|
||||
struct LinkValidator<LinkTags::enchant>
|
||||
{
|
||||
static bool IsTextValid(SpellInfo const* info, char const* pos, size_t len)
|
||||
static bool IsTextValid(SpellInfo const* info, std::string_view text)
|
||||
{
|
||||
if (LinkValidator<LinkTags::spell>::IsTextValid(info, pos, len))
|
||||
if (LinkValidator<LinkTags::spell>::IsTextValid(info, text))
|
||||
return true;
|
||||
SkillLineAbilityMapBounds bounds = sSpellMgr->GetSkillLineAbilityMapBounds(info->Id);
|
||||
if (bounds.first == bounds.second)
|
||||
@@ -233,12 +244,15 @@ struct LinkValidator<LinkTags::enchant>
|
||||
|
||||
for (uint8 i = 0; i < TOTAL_LOCALES; ++i)
|
||||
{
|
||||
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;
|
||||
std::string_view spellName = info->SpellName[i];
|
||||
std::string_view skillName = skill->DisplayName[i];
|
||||
// alternate form [Skill Name: Spell Name]
|
||||
return (
|
||||
(text.length() == (spellName.length() + 2 + skillName.length())) &&
|
||||
(text.substr(0, spellName.length()) == spellName) &&
|
||||
(text.substr(spellName.length(), 2) == ": ") &&
|
||||
(text.substr(spellName.length() + 2) == skillName)
|
||||
);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -253,10 +267,10 @@ struct LinkValidator<LinkTags::enchant>
|
||||
template <>
|
||||
struct LinkValidator<LinkTags::glyph>
|
||||
{
|
||||
static bool IsTextValid(GlyphLinkData const& data, char const* pos, size_t len)
|
||||
static bool IsTextValid(GlyphLinkData const& data, std::string_view text)
|
||||
{
|
||||
if (SpellInfo const* info = sSpellMgr->GetSpellInfo(data.Glyph->SpellID))
|
||||
return LinkValidator<LinkTags::spell>::IsTextValid(info, pos, len);
|
||||
return LinkValidator<LinkTags::spell>::IsTextValid(info, text);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -269,10 +283,10 @@ struct LinkValidator<LinkTags::glyph>
|
||||
template <>
|
||||
struct LinkValidator<LinkTags::talent>
|
||||
{
|
||||
static bool IsTextValid(TalentLinkData const& data, char const* pos, size_t len)
|
||||
static bool IsTextValid(TalentLinkData const& data, std::string_view text)
|
||||
{
|
||||
if (SpellInfo const* info = sSpellMgr->GetSpellInfo(data.Talent->SpellRank[0]))
|
||||
return LinkValidator<LinkTags::spell>::IsTextValid(info, pos, len);
|
||||
return LinkValidator<LinkTags::spell>::IsTextValid(info, text);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -285,9 +299,9 @@ struct LinkValidator<LinkTags::talent>
|
||||
template <>
|
||||
struct LinkValidator<LinkTags::trade>
|
||||
{
|
||||
static bool IsTextValid(TradeskillLinkData const& data, char const* pos, size_t len)
|
||||
static bool IsTextValid(TradeskillLinkData const& data, std::string_view text)
|
||||
{
|
||||
return LinkValidator<LinkTags::spell>::IsTextValid(data.Spell, pos, len);
|
||||
return LinkValidator<LinkTags::spell>::IsTextValid(data.Spell, text);
|
||||
}
|
||||
|
||||
static bool IsColorValid(TradeskillLinkData const&, HyperlinkColor c)
|
||||
@@ -298,17 +312,16 @@ struct LinkValidator<LinkTags::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()))) \
|
||||
static_assert(LinkTags::tagname::tag() == #tagname); \
|
||||
if (info.tag == LinkTags::tagname::tag()) \
|
||||
{ \
|
||||
advstd::remove_cvref_t<typename LinkTags::tagname::value_type> t; \
|
||||
if (!LinkTags::tagname::StoreTo(t, info.data.first, info.data.second)) \
|
||||
if (!LinkTags::tagname::StoreTo(t, info.data)) \
|
||||
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)) \
|
||||
if (!LinkValidator<LinkTags::tagname>::IsTextValid(t, info.text)) \
|
||||
return false; \
|
||||
return true; \
|
||||
} \
|
||||
@@ -341,16 +354,19 @@ static bool ValidateLinkInfo(HyperlinkInfo const& info)
|
||||
}
|
||||
|
||||
// Validates all hyperlinks and control sequences contained in str
|
||||
bool Trinity::Hyperlinks::CheckAllLinks(std::string const& str)
|
||||
bool Trinity::Hyperlinks::CheckAllLinks(std::string_view str)
|
||||
{
|
||||
// Step 1: Disallow all control sequences except ||, |H, |h, |c and |r
|
||||
{
|
||||
std::string::size_type pos = 0;
|
||||
std::string_view::size_type pos = 0;
|
||||
while ((pos = str.find('|', pos)) != std::string::npos)
|
||||
{
|
||||
char next = str[pos + 1];
|
||||
++pos;
|
||||
if (pos == str.length())
|
||||
return false;
|
||||
char next = str[pos];
|
||||
if (next == 'H' || next == 'h' || next == 'c' || next == 'r' || next == '|')
|
||||
pos += 2;
|
||||
++pos;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
@@ -363,21 +379,21 @@ bool Trinity::Hyperlinks::CheckAllLinks(std::string const& str)
|
||||
// - <linkdata> is arbitrary length, no | contained
|
||||
// - <linktext> is printable
|
||||
{
|
||||
std::string::size_type pos = 0;
|
||||
while ((pos = str.find('|', pos)) != std::string::npos)
|
||||
std::string::size_type pos;
|
||||
while ((pos = str.find('|')) != std::string::npos)
|
||||
{
|
||||
if (str[pos + 1] == '|') // this is an escaped pipe character (||)
|
||||
{
|
||||
pos += 2;
|
||||
str = str.substr(pos + 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
HyperlinkInfo info = ParseHyperlink(str.c_str() + pos);
|
||||
HyperlinkInfo info = ParseSingleHyperlink(str.substr(pos));
|
||||
if (!info || !ValidateLinkInfo(info))
|
||||
return false;
|
||||
|
||||
// tag is fine, find the next one
|
||||
pos = info.next - str.c_str();
|
||||
str = info.tail;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "ObjectGuid.h"
|
||||
#include "StringConvert.h"
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
@@ -89,26 +90,32 @@ namespace Trinity::Hyperlinks
|
||||
|* 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* *|
|
||||
|* - MUST expose static ::tag method, void -> std::string_view *|
|
||||
|* - this method SHOULD be constexpr *|
|
||||
|* - returns identifier string for the link ("creature", "creature_entry", "item") *|
|
||||
|* - MUST expose static ::StoreTo method, (storage&, char const*, size_t) *|
|
||||
|* - assign value_type& based on content of std::string(char const*, size_t) *|
|
||||
|* - MUST expose static ::StoreTo method, (storage&, std::string_view) *|
|
||||
|* - assign value_type& based on content of std::string_view *|
|
||||
|* - return value indicates success/failure *|
|
||||
|* - for integral/string types this can be achieved by extending base_tag *|
|
||||
\****************************************************************************************/
|
||||
struct base_tag
|
||||
{
|
||||
static bool StoreTo(std::string& val, char const* pos, size_t len)
|
||||
static bool StoreTo(std::string_view& val, std::string_view data)
|
||||
{
|
||||
val.assign(pos, len);
|
||||
val = data;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool StoreTo(std::string& val, std::string_view data)
|
||||
{
|
||||
val.assign(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static std::enable_if_t<std::is_integral_v<T>, bool> StoreTo(T& val, char const* pos, size_t len)
|
||||
static std::enable_if_t<std::is_integral_v<T>, bool> StoreTo(T& val, std::string_view data)
|
||||
{
|
||||
if (Optional<T> res = Trinity::StringTo<T>(std::string_view(pos, len)))
|
||||
if (Optional<T> res = Trinity::StringTo<T>(data))
|
||||
{
|
||||
val = *res;
|
||||
return true;
|
||||
@@ -117,9 +124,9 @@ namespace Trinity::Hyperlinks
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool StoreTo(ObjectGuid& val, char const* pos, size_t len)
|
||||
static bool StoreTo(ObjectGuid& val, std::string_view data)
|
||||
{
|
||||
if (Optional<uint64> res = Trinity::StringTo<uint64>(std::string_view(pos, len), 16))
|
||||
if (Optional<uint64> res = Trinity::StringTo<uint64>(data, 16))
|
||||
{
|
||||
val.Set(*res);
|
||||
return true;
|
||||
@@ -129,7 +136,7 @@ namespace Trinity::Hyperlinks
|
||||
}
|
||||
};
|
||||
|
||||
#define make_base_tag(ltag, type) struct ltag : public base_tag { using value_type = type; static constexpr char const* tag() { return #ltag; } }
|
||||
#define make_base_tag(ltag, type) struct ltag : public base_tag { using value_type = type; static constexpr std::string_view tag() { return #ltag; } }
|
||||
make_base_tag(area, uint32);
|
||||
make_base_tag(areatrigger, uint32);
|
||||
make_base_tag(creature, ObjectGuid::LowType);
|
||||
@@ -148,64 +155,64 @@ namespace Trinity::Hyperlinks
|
||||
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);
|
||||
static constexpr std::string_view tag() { return "achievement"; }
|
||||
static bool StoreTo(AchievementLinkData& val, std::string_view data);
|
||||
};
|
||||
|
||||
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);
|
||||
static constexpr std::string_view tag() { return "enchant"; }
|
||||
static bool StoreTo(SpellInfo const*& val, std::string_view data);
|
||||
};
|
||||
|
||||
struct TC_GAME_API glyph
|
||||
{
|
||||
using value_type = GlyphLinkData const&;
|
||||
static constexpr char const* tag() { return "glyph"; };
|
||||
static bool StoreTo(GlyphLinkData& val, char const* pos, size_t len);
|
||||
static constexpr std::string_view tag() { return "glyph"; };
|
||||
static bool StoreTo(GlyphLinkData& val, std::string_view data);
|
||||
};
|
||||
|
||||
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);
|
||||
static constexpr std::string_view tag() { return "item"; }
|
||||
static bool StoreTo(ItemLinkData& val, std::string_view data);
|
||||
};
|
||||
|
||||
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);
|
||||
static constexpr std::string_view tag() { return "quest"; }
|
||||
static bool StoreTo(QuestLinkData& val, std::string_view data);
|
||||
};
|
||||
|
||||
struct TC_GAME_API spell
|
||||
{
|
||||
using value_type = SpellInfo const*;
|
||||
static constexpr char const* tag() { return "spell"; }
|
||||
static bool StoreTo(SpellInfo const*& val, char const* pos, size_t len);
|
||||
static constexpr std::string_view tag() { return "spell"; }
|
||||
static bool StoreTo(SpellInfo const*& val, std::string_view data);
|
||||
};
|
||||
|
||||
struct TC_GAME_API talent
|
||||
{
|
||||
using value_type = TalentLinkData const&;
|
||||
static constexpr char const* tag() { return "talent"; }
|
||||
static bool StoreTo(TalentLinkData& val, char const* pos, size_t len);
|
||||
static constexpr std::string_view tag() { return "talent"; }
|
||||
static bool StoreTo(TalentLinkData& val, std::string_view data);
|
||||
};
|
||||
|
||||
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);
|
||||
static constexpr std::string_view tag() { return "trade"; }
|
||||
static bool StoreTo(TradeskillLinkData& val, std::string_view data);
|
||||
};
|
||||
}
|
||||
|
||||
struct HyperlinkColor
|
||||
{
|
||||
HyperlinkColor(uint32 c) : r(c >> 16), g(c >> 8), b(c), a(c >> 24) {}
|
||||
uint8 r, g, b, a;
|
||||
uint8 const r, g, b, a;
|
||||
bool operator==(uint32 c) const
|
||||
{
|
||||
if ((c & 0xff) ^ b)
|
||||
@@ -222,18 +229,20 @@ namespace Trinity::Hyperlinks
|
||||
|
||||
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) {}
|
||||
HyperlinkInfo() : ok(false), color(0) {}
|
||||
HyperlinkInfo(std::string_view t, uint32 c, std::string_view ta, std::string_view d, std::string_view te) :
|
||||
ok(true), tail(t), color(c), tag(ta), data(d), text(te) {}
|
||||
|
||||
explicit operator bool() { return next; }
|
||||
char const* const next;
|
||||
explicit operator bool() { return ok; }
|
||||
bool const ok;
|
||||
std::string_view const tail;
|
||||
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;
|
||||
std::string_view const tag;
|
||||
std::string_view const data;
|
||||
std::string_view const text;
|
||||
};
|
||||
HyperlinkInfo TC_GAME_API ParseHyperlink(char const* pos);
|
||||
bool TC_GAME_API CheckAllLinks(std::string const&);
|
||||
HyperlinkInfo TC_GAME_API ParseSingleHyperlink(std::string_view str);
|
||||
bool TC_GAME_API CheckAllLinks(std::string_view str);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1706,7 +1706,7 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleDebugOutOfBounds([[maybe_unused]] ChatHandler* handler, CommandArgs* /*args*/)
|
||||
static bool HandleDebugOutOfBounds([[maybe_unused]] ChatHandler* handler)
|
||||
{
|
||||
#ifdef ASAN
|
||||
uint8 stack_array[10] = {};
|
||||
@@ -1852,7 +1852,7 @@ public:
|
||||
handler->PSendSysMessage("Entry: %u Count: %u", p.first, p.second);
|
||||
}
|
||||
|
||||
static bool HandleDebugDummyCommand(ChatHandler* handler, CommandArgs* /*args*/)
|
||||
static bool HandleDebugDummyCommand(ChatHandler* handler)
|
||||
{
|
||||
handler->SendSysMessage("This command does nothing right now. Edit your local core (cs_debug.cpp) to make it do whatever you need for testing.");
|
||||
return true;
|
||||
|
||||
@@ -127,60 +127,60 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleNameAnnounceCommand(ChatHandler* handler, CommandArgs* args)
|
||||
static bool HandleNameAnnounceCommand(ChatHandler* handler, Tail message)
|
||||
{
|
||||
if (!*args)
|
||||
if (message.empty())
|
||||
return false;
|
||||
|
||||
std::string name("Console");
|
||||
if (WorldSession* session = handler->GetSession())
|
||||
name = session->GetPlayer()->GetName();
|
||||
|
||||
sWorld->SendWorldText(LANG_ANNOUNCE_COLOR, name.c_str(), args->GetFullArgs());
|
||||
sWorld->SendWorldText(LANG_ANNOUNCE_COLOR, name.c_str(), message.data());
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleGMNameAnnounceCommand(ChatHandler* handler, CommandArgs* args)
|
||||
static bool HandleGMNameAnnounceCommand(ChatHandler* handler, Tail message)
|
||||
{
|
||||
if (!*args)
|
||||
if (message.empty())
|
||||
return false;
|
||||
|
||||
std::string name("Console");
|
||||
if (WorldSession* session = handler->GetSession())
|
||||
name = session->GetPlayer()->GetName();
|
||||
|
||||
sWorld->SendGMText(LANG_GM_ANNOUNCE_COLOR, name.c_str(), args->GetFullArgs());
|
||||
sWorld->SendGMText(LANG_GM_ANNOUNCE_COLOR, name.c_str(), message.data());
|
||||
return true;
|
||||
}
|
||||
|
||||
// global announce
|
||||
static bool HandleAnnounceCommand(ChatHandler* handler, char const* args)
|
||||
static bool HandleAnnounceCommand(ChatHandler* handler, Tail message)
|
||||
{
|
||||
if (!*args)
|
||||
if (message.empty())
|
||||
return false;
|
||||
|
||||
sWorld->SendServerMessage(SERVER_MSG_STRING, Trinity::StringFormat(handler->GetTrinityString(LANG_SYSTEMMESSAGE), args).c_str());
|
||||
sWorld->SendServerMessage(SERVER_MSG_STRING, Trinity::StringFormat(handler->GetTrinityString(LANG_SYSTEMMESSAGE), message.data()).c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
// announce to logged in GMs
|
||||
static bool HandleGMAnnounceCommand(ChatHandler* /*handler*/, CommandArgs* args)
|
||||
static bool HandleGMAnnounceCommand(ChatHandler* /*handler*/, Tail message)
|
||||
{
|
||||
if (!*args)
|
||||
if (message.empty())
|
||||
return false;
|
||||
|
||||
sWorld->SendGMText(LANG_GM_BROADCAST, args->GetFullArgs());
|
||||
sWorld->SendGMText(LANG_GM_BROADCAST, message.data());
|
||||
return true;
|
||||
}
|
||||
|
||||
// send on-screen notification to players
|
||||
static bool HandleNotifyCommand(ChatHandler* handler, CommandArgs* args)
|
||||
static bool HandleNotifyCommand(ChatHandler* handler, Tail message)
|
||||
{
|
||||
if (!*args)
|
||||
if (message.empty())
|
||||
return false;
|
||||
|
||||
std::string str = handler->GetTrinityString(LANG_GLOBAL_NOTIFY);
|
||||
str += args->GetFullArgs();
|
||||
str += message;
|
||||
|
||||
WorldPacket data(SMSG_NOTIFICATION, (str.size() + 1));
|
||||
data << str;
|
||||
@@ -190,13 +190,13 @@ public:
|
||||
}
|
||||
|
||||
// send on-screen notification to GMs
|
||||
static bool HandleGMNotifyCommand(ChatHandler* handler, CommandArgs* args)
|
||||
static bool HandleGMNotifyCommand(ChatHandler* handler, Tail message)
|
||||
{
|
||||
if (!*args)
|
||||
if (message.empty())
|
||||
return false;
|
||||
|
||||
std::string str = handler->GetTrinityString(LANG_GM_NOTIFY);
|
||||
str += args->GetFullArgs();
|
||||
str += message;
|
||||
|
||||
WorldPacket data(SMSG_NOTIFICATION, (str.size() + 1));
|
||||
data << str;
|
||||
|
||||
@@ -27,7 +27,7 @@ static void TestChatCommand(char const* c, F f, Optional<bool> expected = true)
|
||||
{
|
||||
bool r = ChatCommand("", 0, false, +f, "")(nullptr, c);
|
||||
if (expected)
|
||||
ASSERT(r == *expected);
|
||||
REQUIRE(r == *expected);
|
||||
}
|
||||
|
||||
TEST_CASE("Command return pass-through", "[ChatCommand]")
|
||||
@@ -38,27 +38,51 @@ TEST_CASE("Command return pass-through", "[ChatCommand]")
|
||||
|
||||
TEST_CASE("Command argument parsing", "[ChatCommand]")
|
||||
{
|
||||
TestChatCommand("42", [](ChatHandler*, uint32 u)
|
||||
SECTION("Single uint32 argument")
|
||||
{
|
||||
REQUIRE(u == 42);
|
||||
return true;
|
||||
});
|
||||
|
||||
TestChatCommand("true", [](ChatHandler*, uint32) { return true; }, false);
|
||||
|
||||
TestChatCommand("1 2 3 4 5 6 7 8 9 10", [](ChatHandler*, std::vector<uint8> v)
|
||||
{
|
||||
REQUIRE(v.size() == 10);
|
||||
for (size_t i = 0; i < 10; ++i)
|
||||
REQUIRE(v[i] == (i + 1));
|
||||
return true;
|
||||
});
|
||||
|
||||
TestChatCommand("|cffff0000|Hplayer:Test|h[Test]|h|r",
|
||||
[](ChatHandler*, Hyperlink<player> player)
|
||||
TestChatCommand("42", [](ChatHandler*, uint32 u)
|
||||
{
|
||||
REQUIRE("Test"sv == *player);
|
||||
REQUIRE(u == 42);
|
||||
return true;
|
||||
}
|
||||
);
|
||||
});
|
||||
TestChatCommand("true", [](ChatHandler*, uint32) { return true; }, false);
|
||||
}
|
||||
|
||||
SECTION("std::vector<uint8>")
|
||||
{
|
||||
TestChatCommand("1 2 3 4 5 6 7 8 9 10", [](ChatHandler*, std::vector<uint8> v)
|
||||
{
|
||||
REQUIRE(v.size() == 10);
|
||||
for (size_t i = 0; i < 10; ++i)
|
||||
REQUIRE(v[i] == (i + 1));
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
SECTION("Hyperlink<player>")
|
||||
{
|
||||
TestChatCommand("|cffff0000|Hplayer:Test|h[Test]|h|r",
|
||||
[](ChatHandler*, Hyperlink<player> player)
|
||||
{
|
||||
REQUIRE("Test"sv == *player);
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
SECTION("Two strings")
|
||||
{
|
||||
TestChatCommand("two strings", [](ChatHandler*, std::string_view v1, std::string_view v2)
|
||||
{
|
||||
REQUIRE(v1 == "two");
|
||||
REQUIRE(v2 == "strings");
|
||||
return true;
|
||||
});
|
||||
TestChatCommand("two strings", [](ChatHandler*, std::string_view) { return true; }, false);
|
||||
TestChatCommand("two strings", [](ChatHandler*, Tail t)
|
||||
{
|
||||
REQUIRE(t == "two strings");
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user