/* * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Affero General Public License as published by the * Free Software Foundation; either version 3 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #ifndef _CHATCOMMAND_H #define _CHATCOMMAND_H #include "ChatCommandArgs.h" #include "ChatCommandTags.h" #include "Define.h" #include "Errors.h" #include "Language.h" #include "ObjectGuid.h" #include "Optional.h" #include "StringFormat.h" #include "Util.h" #include "advstd.h" #include #include #include #include #include #include #include class ChatHandler; namespace Acore::ChatCommands { enum class Console : bool { No = false, Yes = true }; struct ChatCommandBuilder; using ChatCommandTable = std::vector; } namespace Acore::Impl::ChatCommands { // forward declaration // ConsumeFromOffset contains the bounds check for offset, then hands off to MultiConsumer // the call stack is MultiConsumer -> ConsumeFromOffset -> MultiConsumer -> ConsumeFromOffset etc // MultiConsumer goes into ArgInfo for parsing on each iteration template ChatCommandResult ConsumeFromOffset(Tuple&, ChatHandler const* handler, std::string_view args); template struct MultiConsumer { static ChatCommandResult TryConsumeTo(Tuple& tuple, ChatHandler const* handler, std::string_view args) { ChatCommandResult next = ArgInfo::TryConsume(std::get(tuple), handler, args); if (next) return ConsumeFromOffset(tuple, handler, *next); else return next; } }; template struct MultiConsumer, offset> { static ChatCommandResult TryConsumeTo(Tuple& tuple, ChatHandler const* handler, std::string_view args) { // try with the argument auto& myArg = std::get(tuple); myArg.emplace(); ChatCommandResult result1 = ArgInfo::TryConsume(myArg.value(), handler, args); if (result1) if ((result1 = ConsumeFromOffset(tuple, handler, *result1))) return result1; // try again omitting the argument myArg = std::nullopt; ChatCommandResult result2 = ConsumeFromOffset(tuple, handler, args); if (result2) return result2; if (result1.HasErrorMessage() && result2.HasErrorMessage()) { return Acore::StringFormatFmt("{} \"{}\"\n{} \"{}\"", GetAcoreString(handler, LANG_CMDPARSER_EITHER), result2.GetErrorMessage(), GetAcoreString(handler, LANG_CMDPARSER_OR), result1.GetErrorMessage()); } else if (result1.HasErrorMessage()) return result1; else return result2; } }; template ChatCommandResult ConsumeFromOffset([[maybe_unused]] Tuple& tuple, [[maybe_unused]] ChatHandler const* handler, std::string_view args) { if constexpr (offset < std::tuple_size_v) return MultiConsumer, offset>::TryConsumeTo(tuple, handler, args); else if (!args.empty()) /* the entire string must be consumed */ return std::nullopt; else return args; } template struct HandlerToTuple { static_assert(Acore::dependant_false_v, "Invalid command handler signature"); }; template struct HandlerToTuple { using type = std::tuple...>; }; template using TupleType = typename HandlerToTuple::type; struct CommandInvoker { CommandInvoker() : _wrapper(nullptr), _handler(nullptr) {} template CommandInvoker(TypedHandler& handler) { _wrapper = [](void* handler, ChatHandler* chatHandler, std::string_view argsStr) { using Tuple = TupleType; Tuple arguments; std::get<0>(arguments) = chatHandler; ChatCommandResult result = ConsumeFromOffset(arguments, chatHandler, argsStr); if (result) return std::apply(reinterpret_cast(handler), std::move(arguments)); else { if (result.HasErrorMessage()) SendErrorMessageToHandler(chatHandler, result.GetErrorMessage()); return false; } }; _handler = reinterpret_cast(handler); } CommandInvoker(bool(&handler)(ChatHandler*, char const*)) { _wrapper = [](void* handler, ChatHandler* chatHandler, std::string_view argsStr) { // make a copy of the argument string // legacy handlers can destroy input strings with strtok std::string argsStrCopy(argsStr); return reinterpret_cast(handler)(chatHandler, argsStrCopy.c_str()); }; _handler = reinterpret_cast(handler); } explicit operator bool() const { return (_wrapper != nullptr); } bool operator()(ChatHandler* chatHandler, std::string_view args) const { ASSERT(_wrapper && _handler); return _wrapper(_handler, chatHandler, args); } private: using wrapper_func = bool(void*, ChatHandler*, std::string_view); wrapper_func* _wrapper; void* _handler; }; struct CommandPermissions { CommandPermissions() : RequiredLevel{}, AllowConsole{} { } CommandPermissions(uint32 securityLevel, Acore::ChatCommands::Console console) : RequiredLevel{ securityLevel }, AllowConsole{ console } {} uint32 RequiredLevel; Acore::ChatCommands::Console AllowConsole; }; class ChatCommandNode { friend struct FilteredCommandListIterator; using ChatCommandBuilder = Acore::ChatCommands::ChatCommandBuilder; public: static void LoadCommandMap(); static void InvalidateCommandMap(); static bool TryExecuteCommand(ChatHandler& handler, std::string_view cmd); static void SendCommandHelpFor(ChatHandler& handler, std::string_view cmd); static std::vector GetAutoCompletionsFor(ChatHandler const& handler, std::string_view cmd); ChatCommandNode() : _name{}, _invoker {}, _permission{}, _help{}, _subCommands{} { } private: static std::map const& GetTopLevelMap(); static void LoadCommandsIntoMap(ChatCommandNode* blank, std::map& map, Acore::ChatCommands::ChatCommandTable const& commands); void LoadFromBuilder(ChatCommandBuilder const& builder); ChatCommandNode(ChatCommandNode&& other) = default; void ResolveNames(std::string name); void SendCommandHelp(ChatHandler& handler) const; bool IsVisible(ChatHandler const& who) const { return (IsInvokerVisible(who) || HasVisibleSubCommands(who)); } bool IsInvokerVisible(ChatHandler const& who) const; bool HasVisibleSubCommands(ChatHandler const& who) const; std::string _name; CommandInvoker _invoker; CommandPermissions _permission; std::variant _help; std::map _subCommands; }; } namespace Acore::ChatCommands { struct ChatCommandBuilder { friend class Acore::Impl::ChatCommands::ChatCommandNode; struct InvokerEntry { template InvokerEntry(T& handler, AcoreStrings help, uint32 securityLevel, Acore::ChatCommands::Console allowConsole) : _invoker{ handler }, _help{ help }, _permissions{ securityLevel, allowConsole } { } InvokerEntry(InvokerEntry const&) = default; InvokerEntry(InvokerEntry&&) = default; Acore::Impl::ChatCommands::CommandInvoker _invoker; AcoreStrings _help; Acore::Impl::ChatCommands::CommandPermissions _permissions; auto operator*() const { return std::tie(_invoker, _help, _permissions); } }; using SubCommandEntry = std::reference_wrapper const>; ChatCommandBuilder(ChatCommandBuilder&&) = default; ChatCommandBuilder(ChatCommandBuilder const&) = default; template ChatCommandBuilder(char const* name, TypedHandler& handler, AcoreStrings help, uint32 securityLevel, Acore::ChatCommands::Console allowConsole) : _name{ ASSERT_NOTNULL(name) }, _data{ std::in_place_type, handler, help, securityLevel, allowConsole } { } template ChatCommandBuilder(char const* name, TypedHandler& handler, uint32 securityLevel, Acore::ChatCommands::Console allowConsole) : ChatCommandBuilder(name, handler, AcoreStrings(), securityLevel, allowConsole) { } ChatCommandBuilder(char const* name, std::vector const& subCommands) : _name{ ASSERT_NOTNULL(name) }, _data{ std::in_place_type, subCommands } { } [[deprecated("char const* parameters to command handlers are deprecated; convert this to a typed argument handler instead")]] ChatCommandBuilder(char const* name, bool(&handler)(ChatHandler*, char const*), uint32 securityLevel, Acore::ChatCommands::Console allowConsole) : ChatCommandBuilder(name, handler, AcoreStrings(), securityLevel, allowConsole) { } template [[deprecated("you are using the old-style command format; convert this to the new format ({ name, handler (not a pointer!), permission, Console::(Yes/No) })")]] ChatCommandBuilder(char const* name, uint32 securityLevel, bool console, TypedHandler* handler, char const*) : ChatCommandBuilder(name, *handler, AcoreStrings(), securityLevel, static_cast(console)) { } [[deprecated("you are using the old-style command format; convert this to the new format ({ name, subCommands })")]] ChatCommandBuilder(char const* name, uint32, bool, std::nullptr_t, char const*, std::vector const& sub) : ChatCommandBuilder(name, sub) { } private: std::string_view _name; std::variant _data; }; AC_GAME_API void LoadCommandMap(); AC_GAME_API void InvalidateCommandMap(); AC_GAME_API bool TryExecuteCommand(ChatHandler& handler, std::string_view cmd); AC_GAME_API void SendCommandHelpFor(ChatHandler& handler, std::string_view cmd); AC_GAME_API std::vector GetAutoCompletionsFor(ChatHandler const& handler, std::string_view cmd); } // backwards compatibility with old patches using ChatCommand [[deprecated("std::vector should be ChatCommandTable! (using namespace Acore::ChatCommands)")]] = Acore::ChatCommands::ChatCommandBuilder; #endif