aboutsummaryrefslogtreecommitdiff
path: root/src/server/game/Chat/ChatCommands
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/game/Chat/ChatCommands')
-rw-r--r--src/server/game/Chat/ChatCommands/ChatCommand.cpp466
-rw-r--r--src/server/game/Chat/ChatCommands/ChatCommand.h152
-rw-r--r--src/server/game/Chat/ChatCommands/ChatCommandArgs.h2
3 files changed, 587 insertions, 33 deletions
diff --git a/src/server/game/Chat/ChatCommands/ChatCommand.cpp b/src/server/game/Chat/ChatCommands/ChatCommand.cpp
new file mode 100644
index 00000000000..865979999d7
--- /dev/null
+++ b/src/server/game/Chat/ChatCommands/ChatCommand.cpp
@@ -0,0 +1,466 @@
+/*
+ * 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 "ChatCommand.h"
+
+#include "AccountMgr.h"
+#include "Chat.h"
+#include "DatabaseEnv.h"
+#include "DB2Stores.h"
+#include "Log.h"
+#include "Map.h"
+#include "Player.h"
+#include "ScriptMgr.h"
+#include "WorldSession.h"
+
+using ChatSubCommandMap = std::map<std::string_view, Trinity::Impl::ChatCommands::ChatCommandNode, StringCompareLessI_T>;
+
+void Trinity::Impl::ChatCommands::ChatCommandNode::LoadFromBuilder(ChatCommandBuilder const& builder)
+{
+ if (std::holds_alternative<ChatCommandBuilder::InvokerEntry>(builder._data))
+ {
+ ASSERT(!_invoker, "Duplicate blank sub-command.");
+ std::tie(_invoker, _permission) = std::get<ChatCommandBuilder::InvokerEntry>(builder._data);
+ }
+ else
+ LoadCommandsIntoMap(this, _subCommands, std::get<ChatCommandBuilder::SubCommandEntry>(builder._data));
+}
+
+/*static*/ void Trinity::Impl::ChatCommands::ChatCommandNode::LoadCommandsIntoMap(ChatCommandNode* blank, ChatSubCommandMap& map, Trinity::ChatCommands::ChatCommandTable const& commands)
+{
+ for (ChatCommandBuilder const& builder : commands)
+ {
+ if (builder._name.empty())
+ {
+ ASSERT(blank, "Empty name command at top level is not permitted.");
+ blank->LoadFromBuilder(builder);
+ }
+ else
+ {
+ std::vector<std::string_view> const tokens = Trinity::Tokenize(builder._name, COMMAND_DELIMITER, false);
+ ASSERT(!tokens.empty(), "Invalid command name '" STRING_VIEW_FMT "'.", STRING_VIEW_FMT_ARG(builder._name));
+ ChatSubCommandMap* subMap = &map;
+ for (size_t i = 0, n = (tokens.size() - 1); i < n; ++i)
+ subMap = &((*subMap)[tokens[i]]._subCommands);
+ ((*subMap)[tokens.back()]).LoadFromBuilder(builder);
+ }
+ }
+}
+
+static ChatSubCommandMap COMMAND_MAP;
+/*static*/ ChatSubCommandMap const& Trinity::Impl::ChatCommands::ChatCommandNode::GetTopLevelMap()
+{
+ if (COMMAND_MAP.empty())
+ LoadCommandMap();
+ return COMMAND_MAP;
+}
+/*static*/ void Trinity::Impl::ChatCommands::ChatCommandNode::InvalidateCommandMap() { COMMAND_MAP.clear(); }
+/*static*/ void Trinity::Impl::ChatCommands::ChatCommandNode::LoadCommandMap()
+{
+ InvalidateCommandMap();
+ LoadCommandsIntoMap(nullptr, COMMAND_MAP, sScriptMgr->GetChatCommands());
+
+ if (PreparedQueryResult result = WorldDatabase.Query(WorldDatabase.GetPreparedStatement(WORLD_SEL_COMMANDS)))
+ {
+ do
+ {
+ Field* fields = result->Fetch();
+ std::string_view const name = fields[0].GetStringView();
+ uint16 const permission = fields[1].GetUInt16();
+ std::string_view const help = fields[2].GetStringView();
+
+ ChatCommandNode* cmd = nullptr;
+ ChatSubCommandMap* map = &COMMAND_MAP;
+ for (std::string_view key : Trinity::Tokenize(name, COMMAND_DELIMITER, false))
+ {
+ auto it = map->find(key);
+ if (it != map->end())
+ {
+ cmd = &it->second;
+ map = &cmd->_subCommands;
+ }
+ else
+ {
+ TC_LOG_ERROR("sql.sql", "Table `command` contains data for non-existant command '" STRING_VIEW_FMT "'. Skipped.", STRING_VIEW_FMT_ARG(name));
+ cmd = nullptr;
+ break;
+ }
+ }
+
+ if (!cmd)
+ continue;
+
+ if (cmd->_invoker && (cmd->_permission.RequiredPermission != permission))
+ {
+ TC_LOG_WARN("sql.sql", "Table `command` has permission %u for '" STRING_VIEW_FMT "' which does not match the core (%u). Overriding.",
+ permission, STRING_VIEW_FMT_ARG(name), cmd->_permission.RequiredPermission);
+
+ cmd->_permission.RequiredPermission = static_cast<rbac::RBACPermissions>(permission);
+ }
+
+ cmd->_help.assign(help);
+ } while (result->NextRow());
+ }
+
+ for (auto const& [name, cmd] : COMMAND_MAP)
+ cmd.AssertCommandHelp(name);
+}
+
+void Trinity::Impl::ChatCommands::ChatCommandNode::AssertCommandHelp(std::string_view name) const
+{
+ if (_invoker && _help.empty())
+ TC_LOG_WARN("sql.sql", "Table `command` is missing help text for (sub-)command '" STRING_VIEW_FMT "'.", STRING_VIEW_FMT_ARG(name));
+
+ for (auto const& [name, cmd] : _subCommands)
+ cmd.AssertCommandHelp(name);
+}
+
+static void LogCommandUsage(WorldSession const& session, uint32 permission, std::string_view cmdStr)
+{
+ if (AccountMgr::IsPlayerAccount(session.GetSecurity()))
+ return;
+
+ if (sAccountMgr->GetRBACPermission(rbac::RBAC_ROLE_PLAYER)->GetLinkedPermissions().count(permission))
+ return;
+
+ Player* player = session.GetPlayer();
+ ObjectGuid targetGuid = player->GetTarget();
+ uint32 areaId = player->GetAreaId();
+ std::string areaName = "Unknown";
+ std::string zoneName = "Unknown";
+ if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(areaId))
+ {
+ LocaleConstant locale = session.GetSessionDbcLocale();
+ areaName = area->AreaName[locale];
+ if (AreaTableEntry const* zone = sAreaTableStore.LookupEntry(area->ParentAreaID))
+ zoneName = zone->AreaName[locale];
+ }
+
+ sLog->outCommand(session.GetAccountId(), "Command: " STRING_VIEW_FMT " [Player: %s (%s) (Account: %u) X: %f Y: %f Z: %f Map: %u (%s) Area: %u (%s) Zone: %s Selected: %s (%s)]",
+ STRING_VIEW_FMT_ARG(cmdStr), player->GetName().c_str(), player->GetGUID().ToString().c_str(),
+ session.GetAccountId(), player->GetPositionX(), player->GetPositionY(),
+ player->GetPositionZ(), player->GetMapId(),
+ player->FindMap() ? player->FindMap()->GetMapName() : "Unknown",
+ areaId, areaName.c_str(), zoneName.c_str(),
+ (player->GetSelectedUnit()) ? player->GetSelectedUnit()->GetName().c_str() : "",
+ targetGuid.ToString().c_str());
+}
+
+void Trinity::Impl::ChatCommands::ChatCommandNode::SendCommandHelp(ChatHandler& handler) const
+{
+ if (IsInvokerVisible(handler))
+ handler.SendSysMessage(_help);
+ bool header = false;
+ for (auto it = _subCommands.begin(); it != _subCommands.end(); ++it)
+ {
+ bool const subCommandHasSubCommand = it->second.HasVisibleSubCommands(handler);
+ if (!subCommandHasSubCommand && !it->second.IsInvokerVisible(handler))
+ continue;
+ if (!header)
+ {
+ handler.SendSysMessage(LANG_SUBCMDS_LIST);
+ header = true;
+ }
+ handler.PSendSysMessage(subCommandHasSubCommand ? LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS : LANG_SUBCMDS_LIST_ENTRY, STRING_VIEW_FMT_ARG(it->first));
+ }
+}
+
+namespace Trinity::Impl::ChatCommands
+{
+ struct FilteredCommandListIterator
+ {
+ public:
+ FilteredCommandListIterator(ChatSubCommandMap const& map, ChatHandler const& handler, std::string_view token)
+ : _handler{ handler }, _token{ token }, _it{ map.lower_bound(token) }, _end{ map.end() }
+ {
+ _skip();
+ }
+
+ decltype(auto) operator*() const { return _it.operator*(); }
+ decltype(auto) operator->() const { return _it.operator->(); }
+ FilteredCommandListIterator& operator++()
+ {
+ ++_it;
+ _skip();
+ return *this;
+ }
+ explicit operator bool() const { return (_it != _end); }
+ bool operator!() const { return !static_cast<bool>(*this); }
+
+ private:
+ void _skip()
+ {
+ if ((_it != _end) && !StringStartsWithI(_it->first, _token))
+ _it = _end;
+ while ((_it != _end) && !_it->second.IsVisible(_handler))
+ {
+ ++_it;
+ if ((_it != _end) && !StringStartsWithI(_it->first, _token))
+ _it = _end;
+ }
+ }
+ ChatHandler const& _handler;
+ std::string_view const _token;
+ ChatSubCommandMap::const_iterator _it, _end;
+
+ };
+}
+
+/*static*/ bool Trinity::Impl::ChatCommands::ChatCommandNode::TryExecuteCommand(ChatHandler& handler, std::string_view cmdStr)
+{
+ ChatCommandNode const* cmd = nullptr;
+ ChatSubCommandMap const* map = &GetTopLevelMap();
+
+ while (!cmdStr.empty() && (cmdStr.front() == COMMAND_DELIMITER))
+ cmdStr.remove_prefix(1);
+ while (!cmdStr.empty() && (cmdStr.back() == COMMAND_DELIMITER))
+ cmdStr.remove_suffix(1);
+ std::string_view oldTail = cmdStr;
+ while (!oldTail.empty())
+ {
+ /* oldTail = token DELIMITER newTail */
+ auto [token, newTail] = tokenize(oldTail);
+ ASSERT(!token.empty());
+ FilteredCommandListIterator it1(*map, handler, token);
+ if (!it1)
+ break; /* no matching subcommands found */
+
+ if (!StringEqualI(it1->first, token))
+ { /* ok, so it1 points at a partially matching subcommand - let's see if there are others */
+ auto it2 = it1;
+ ++it2;
+
+ if (it2)
+ { /* there are multiple matching subcommands - print possibilities and return */
+ if (cmd)
+ handler.PSendSysMessage(LANG_SUBCMD_AMBIGUOUS, STRING_VIEW_FMT_ARG(token));
+ else
+ handler.PSendSysMessage(LANG_CMD_AMBIGUOUS, STRING_VIEW_FMT_ARG(token));
+ handler.PSendSysMessage(it1->second.HasVisibleSubCommands(handler) ? LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS : LANG_SUBCMDS_LIST_ENTRY, STRING_VIEW_FMT_ARG(it1->first));
+ do
+ {
+ handler.PSendSysMessage(it2->second.HasVisibleSubCommands(handler) ? LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS : LANG_SUBCMDS_LIST_ENTRY, STRING_VIEW_FMT_ARG(it2->first));
+ } while (++it2);
+
+ return true;
+ }
+ }
+
+ /* now we matched exactly one subcommand, and it1 points to it; go down the rabbit hole */
+ cmd = &it1->second;
+ map = &cmd->_subCommands;
+
+ oldTail = newTail;
+ }
+
+ if (cmd)
+ { /* if we matched a command at some point, invoke it */
+ handler.SetSentErrorMessage(false);
+ if (cmd->IsInvokerVisible(handler) && cmd->_invoker(&handler, oldTail))
+ { /* invocation succeeded, log this */
+ if (!handler.IsConsole())
+ LogCommandUsage(*handler.GetSession(), cmd->_permission.RequiredPermission, cmdStr);
+ }
+ else if (!handler.HasSentErrorMessage())
+ { /* invocation failed, we should show usage */
+ cmd->SendCommandHelp(handler);
+ handler.SetSentErrorMessage(true);
+ }
+ return true;
+ }
+
+ return false;
+}
+
+/*static*/ void Trinity::Impl::ChatCommands::ChatCommandNode::SendCommandHelpFor(ChatHandler& handler, std::string_view cmdStr)
+{
+ ChatCommandNode const* cmd = nullptr;
+ ChatSubCommandMap const* map = &GetTopLevelMap();
+ for (std::string_view token : Trinity::Tokenize(cmdStr, COMMAND_DELIMITER, false))
+ {
+ FilteredCommandListIterator it1(*map, handler, token);
+ if (!it1)
+ { /* no matching subcommands found */
+ if (cmd)
+ {
+ cmd->SendCommandHelp(handler);
+ handler.PSendSysMessage(LANG_SUBCMD_INVALID, STRING_VIEW_FMT_ARG(token));
+ }
+ else
+ handler.PSendSysMessage(LANG_CMD_INVALID, STRING_VIEW_FMT_ARG(token));
+ return;
+ }
+
+ if (!StringEqualI(it1->first, token))
+ { /* ok, so it1 points at a partially matching subcommand - let's see if there are others */
+ auto it2 = it1;
+ ++it2;
+
+ if (it2)
+ { /* there are multiple matching subcommands - print possibilities and return */
+ handler.PSendSysMessage(cmd ? LANG_SUBCMD_AMBIGUOUS : LANG_CMD_AMBIGUOUS, STRING_VIEW_FMT_ARG(token));
+ handler.PSendSysMessage(it1->second.HasVisibleSubCommands(handler) ? LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS : LANG_SUBCMDS_LIST_ENTRY, STRING_VIEW_FMT_ARG(it1->first));
+ do
+ {
+ handler.PSendSysMessage(it2->second.HasVisibleSubCommands(handler) ? LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS : LANG_SUBCMDS_LIST_ENTRY, STRING_VIEW_FMT_ARG(it2->first));
+ } while (++it2);
+
+ return;
+ }
+ }
+
+ cmd = &it1->second;
+ map = &cmd->_subCommands;
+ }
+
+ if (cmd)
+ cmd->SendCommandHelp(handler);
+ else if (cmdStr.empty())
+ {
+ FilteredCommandListIterator it(*map, handler, "");
+ if (!it)
+ return;
+ handler.SendSysMessage(LANG_AVAILABLE_CMDS);
+ do
+ {
+ handler.PSendSysMessage(it->second.HasVisibleSubCommands(handler) ? LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS : LANG_SUBCMDS_LIST_ENTRY, STRING_VIEW_FMT_ARG(it->first));
+ } while (++it);
+ }
+ else
+ handler.PSendSysMessage(LANG_CMD_INVALID, STRING_VIEW_FMT_ARG(cmdStr));
+}
+
+/*static*/ std::vector<std::string> Trinity::Impl::ChatCommands::ChatCommandNode::GetAutoCompletionsFor(ChatHandler const& handler, std::string_view cmdStr)
+{
+ std::string path;
+ ChatCommandNode const* cmd = nullptr;
+ ChatSubCommandMap const* map = &GetTopLevelMap();
+
+ while (!cmdStr.empty() && (cmdStr.front() == COMMAND_DELIMITER))
+ cmdStr.remove_prefix(1);
+ while (!cmdStr.empty() && (cmdStr.back() == COMMAND_DELIMITER))
+ cmdStr.remove_suffix(1);
+ std::string_view oldTail = cmdStr;
+ while (!oldTail.empty())
+ {
+ /* oldTail = token DELIMITER newTail */
+ auto [token, newTail] = tokenize(oldTail);
+ ASSERT(!token.empty());
+ FilteredCommandListIterator it1(*map, handler, token);
+ if (!it1)
+ break; /* no matching subcommands found */
+
+ if (!StringEqualI(it1->first, token))
+ { /* ok, so it1 points at a partially matching subcommand - let's see if there are others */
+ auto it2 = it1;
+ ++it2;
+
+ if (it2)
+ { /* there are multiple matching subcommands - terminate here and show possibilities */
+ std::vector<std::string> vec;
+ auto possibility = ([prefix = std::string_view(path), suffix = std::string_view(newTail)](std::string_view match)
+ {
+ if (prefix.empty())
+ {
+ return Trinity::StringFormat(STRING_VIEW_FMT "%c" STRING_VIEW_FMT,
+ STRING_VIEW_FMT_ARG(match), COMMAND_DELIMITER, STRING_VIEW_FMT_ARG(suffix));
+ }
+ else
+ {
+ return Trinity::StringFormat(STRING_VIEW_FMT "%c" STRING_VIEW_FMT "%c" STRING_VIEW_FMT,
+ STRING_VIEW_FMT_ARG(prefix), COMMAND_DELIMITER, STRING_VIEW_FMT_ARG(match), COMMAND_DELIMITER, STRING_VIEW_FMT_ARG(suffix));
+ }
+ });
+
+ vec.emplace_back(possibility(it1->first));
+
+ do vec.emplace_back(possibility(it2->first));
+ while (++it2);
+
+ return vec;
+ }
+ }
+
+ /* now we matched exactly one subcommand, and it1 points to it; go down the rabbit hole */
+ if (path.empty())
+ path.assign(it1->first);
+ else
+ {
+ path = Trinity::StringFormat(STRING_VIEW_FMT "%c" STRING_VIEW_FMT,
+ STRING_VIEW_FMT_ARG(path), COMMAND_DELIMITER, STRING_VIEW_FMT_ARG(it1->first));
+ }
+ cmd = &it1->second;
+ map = &cmd->_subCommands;
+
+ oldTail = newTail;
+ }
+
+ if (!oldTail.empty())
+ { /* there is some trailing text, leave it as is */
+ if (cmd)
+ { /* if we matched a command at some point, auto-complete it */
+ return {
+ Trinity::StringFormat(STRING_VIEW_FMT "%c" STRING_VIEW_FMT,
+ STRING_VIEW_FMT_ARG(path), COMMAND_DELIMITER, STRING_VIEW_FMT_ARG(oldTail))
+ };
+ }
+ else
+ return {};
+ }
+ else
+ { /* offer all subcommands */
+ auto possibility = ([prefix = std::string_view(path)](std::string_view match)
+ {
+ if (prefix.empty())
+ return std::string(match);
+ else
+ {
+ return Trinity::StringFormat(STRING_VIEW_FMT "%c" STRING_VIEW_FMT,
+ STRING_VIEW_FMT_ARG(prefix), COMMAND_DELIMITER, STRING_VIEW_FMT_ARG(match));
+ }
+ });
+
+ std::vector<std::string> vec;
+ for (FilteredCommandListIterator it(*map, handler, ""); it; ++it)
+ vec.emplace_back(possibility(it->first));
+ return vec;
+ }
+}
+
+bool Trinity::Impl::ChatCommands::ChatCommandNode::IsInvokerVisible(ChatHandler const& who) const
+{
+ if (!_invoker)
+ return false;
+ if (who.IsConsole() && (_permission.AllowConsole == Trinity::ChatCommands::Console::No))
+ return false;
+ return who.HasPermission(_permission.RequiredPermission);
+}
+
+bool Trinity::Impl::ChatCommands::ChatCommandNode::HasVisibleSubCommands(ChatHandler const& who) const
+{
+ for (auto it = _subCommands.begin(); it != _subCommands.end(); ++it)
+ if (it->second.IsVisible(who))
+ return true;
+ return false;
+}
+
+void Trinity::ChatCommands::LoadCommandMap() { Trinity::Impl::ChatCommands::ChatCommandNode::LoadCommandMap(); }
+void Trinity::ChatCommands::InvalidateCommandMap() { Trinity::Impl::ChatCommands::ChatCommandNode::InvalidateCommandMap(); }
+bool Trinity::ChatCommands::TryExecuteCommand(ChatHandler& handler, std::string_view cmd) { return Trinity::Impl::ChatCommands::ChatCommandNode::TryExecuteCommand(handler, cmd); }
+void Trinity::ChatCommands::SendCommandHelpFor(ChatHandler& handler, std::string_view cmd) { Trinity::Impl::ChatCommands::ChatCommandNode::SendCommandHelpFor(handler, cmd); }
+std::vector<std::string> Trinity::ChatCommands::GetAutoCompletionsFor(ChatHandler const& handler, std::string_view cmd) { return Trinity::Impl::ChatCommands::ChatCommandNode::GetAutoCompletionsFor(handler, cmd); }
+
diff --git a/src/server/game/Chat/ChatCommands/ChatCommand.h b/src/server/game/Chat/ChatCommands/ChatCommand.h
index 41be7e74c1a..25fcff415c1 100644
--- a/src/server/game/Chat/ChatCommands/ChatCommand.h
+++ b/src/server/game/Chat/ChatCommands/ChatCommand.h
@@ -24,14 +24,31 @@
#include "Define.h"
#include "Errors.h"
#include "Optional.h"
+#include "RBAC.h"
#include "StringFormat.h"
+#include "Util.h"
#include <cstddef>
+#include <map>
+#include <utility>
#include <tuple>
#include <type_traits>
+#include <variant>
#include <vector>
class ChatHandler;
+namespace Trinity::ChatCommands
+{
+ enum class Console : bool
+ {
+ No = false,
+ Yes = true
+ };
+
+ struct ChatCommandBuilder;
+ using ChatCommandTable = std::vector<ChatCommandBuilder>;
+}
+
namespace Trinity::Impl::ChatCommands
{
// forward declaration
@@ -97,63 +114,134 @@ namespace Trinity::Impl::ChatCommands
}
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... 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;
-}
-
-class TC_GAME_API ChatCommand
-{
- using wrapper_func = bool(void*, ChatHandler*, char const*);
-
- public:
+ struct CommandInvoker
+ {
+ CommandInvoker() : _wrapper(nullptr), _handler(nullptr) {}
template <typename TypedHandler>
- ChatCommand(char const* name, uint32 permission, bool allowConsole, TypedHandler handler, std::string help)
- : Name(ASSERT_NOTNULL(name)), Permission(permission), AllowConsole(allowConsole), Help(std::move(help)), ChildCommands({})
+ CommandInvoker(TypedHandler& handler)
{
- _wrapper = [](void* handler, ChatHandler* chatHandler, char const* argsStr)
+ _wrapper = [](void* handler, ChatHandler* chatHandler, std::string_view argsStr)
{
- using Tuple = Trinity::Impl::ChatCommands::TupleType<TypedHandler>;
+ using Tuple = TupleType<TypedHandler>;
Tuple arguments;
std::get<0>(arguments) = chatHandler;
- Trinity::Impl::ChatCommands::ChatCommandResult result = Trinity::Impl::ChatCommands::ConsumeFromOffset<Tuple, 1>(arguments, chatHandler, argsStr);
+ ChatCommandResult result = ConsumeFromOffset<Tuple, 1>(arguments, chatHandler, argsStr);
if (result)
- return std::apply(reinterpret_cast<TypedHandler>(handler), std::move(arguments));
+ return std::apply(reinterpret_cast<TypedHandler*>(handler), std::move(arguments));
else
{
if (result.HasErrorMessage())
- Trinity::Impl::ChatCommands::SendErrorMessageToHandler(chatHandler, result.GetErrorMessage());
+ SendErrorMessageToHandler(chatHandler, result.GetErrorMessage());
return false;
}
};
_handler = reinterpret_cast<void*>(handler);
}
- ChatCommand(char const* name, uint32 permission, bool allowConsole, std::nullptr_t, std::string help, std::vector<ChatCommand> childCommands = {})
- : Name(ASSERT_NOTNULL(name)), Permission(permission), AllowConsole(allowConsole), Help(std::move(help)), ChildCommands(std::move(childCommands))
- {
- _wrapper = nullptr;
- _handler = nullptr;
- }
-
- bool operator()(ChatHandler* chatHandler, char const* args) const
+ explicit operator bool() const { return (_wrapper != nullptr); }
+ bool operator()(ChatHandler* chatHandler, std::string_view args) const
{
ASSERT(_wrapper && _handler);
return _wrapper(_handler, chatHandler, args);
}
- bool HasHandler() const { return !!_handler; }
-
- char const* Name;
- uint32 Permission;
- bool AllowConsole;
- std::string Help;
- std::vector<ChatCommand> ChildCommands;
-
private:
+ using wrapper_func = bool(void*, ChatHandler*, std::string_view);
wrapper_func* _wrapper;
void* _handler;
-};
+ };
+
+ struct CommandPermissions
+ {
+ CommandPermissions() : RequiredPermission{}, AllowConsole{} {}
+ CommandPermissions(rbac::RBACPermissions perm, Trinity::ChatCommands::Console console) : RequiredPermission{ perm }, AllowConsole{ console } {}
+ rbac::RBACPermissions RequiredPermission;
+ Trinity::ChatCommands::Console AllowConsole;
+ };
+
+ class ChatCommandNode
+ {
+ friend struct FilteredCommandListIterator;
+ using ChatCommandBuilder = Trinity::ChatCommands::ChatCommandBuilder;
+
+ public:
+ static void LoadCommandMap();
+ static void InvalidateCommandMap();
+ static bool TryExecuteCommand(ChatHandler& handler, std::string_view cmd);
+ static void SendCommandHelpFor(ChatHandler& handler, std::string_view cmd);
+ static std::vector<std::string> GetAutoCompletionsFor(ChatHandler const& handler, std::string_view cmd);
+
+ ChatCommandNode() : _invoker{}, _permission{}, _help{}, _subCommands{} {}
+
+ private:
+ static std::map<std::string_view, ChatCommandNode, StringCompareLessI_T> const& GetTopLevelMap();
+ static void LoadCommandsIntoMap(ChatCommandNode* blank, std::map<std::string_view, ChatCommandNode, StringCompareLessI_T>& map, Trinity::ChatCommands::ChatCommandTable const& commands);
+
+ void LoadFromBuilder(ChatCommandBuilder const& builder);
+ ChatCommandNode(ChatCommandNode&& other) = default;
+
+ void AssertCommandHelp(std::string_view name) const;
+ 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;
+
+ CommandInvoker _invoker;
+ CommandPermissions _permission;
+ std::string _help;
+ std::map<std::string_view, ChatCommandNode, StringCompareLessI_T> _subCommands;
+ };
+}
+
+namespace Trinity::ChatCommands
+{
+ struct ChatCommandBuilder
+ {
+ friend class Trinity::Impl::ChatCommands::ChatCommandNode;
+ using InvokerEntry = std::pair<Trinity::Impl::ChatCommands::CommandInvoker, Trinity::Impl::ChatCommands::CommandPermissions>;
+ using SubCommandEntry = std::reference_wrapper<std::vector<ChatCommandBuilder> const>;
+
+ template <typename TypedHandler>
+ ChatCommandBuilder(char const* name, TypedHandler& handler, rbac::RBACPermissions permission, Trinity::ChatCommands::Console allowConsole)
+ : _name{ ASSERT_NOTNULL(name) }, _data{ std::in_place_type<InvokerEntry>, std::piecewise_construct, std::forward_as_tuple(handler), std::forward_as_tuple(permission, allowConsole) }
+ {}
+ ChatCommandBuilder(char const* name, std::vector<ChatCommandBuilder> const& subCommands)
+ : _name{ ASSERT_NOTNULL(name) }, _data{ std::in_place_type<SubCommandEntry>, subCommands }
+ {}
+ ChatCommandBuilder(ChatCommandBuilder const&) = default;
+
+ /* deprecated: char const* parameters to command handlers */
+ [[deprecated]] ChatCommandBuilder(char const* name, bool(&handler)(ChatHandler*, char const*), rbac::RBACPermissions permission, Trinity::ChatCommands::Console allowConsole)
+ : _name{ ASSERT_NOTNULL(name) }, _data{ std::in_place_type<InvokerEntry>, std::piecewise_construct, std::forward_as_tuple(handler), std::forward_as_tuple(permission, allowConsole) }
+ {}
+
+ /* deprecated: old-style command table format */
+ template <typename TypedHandler>
+ [[deprecated]] ChatCommandBuilder(char const* name, rbac::RBACPermissions permission, bool console, TypedHandler* handler, char const*)
+ : _name{ ASSERT_NOTNULL(name) }, _data{ std::in_place_type<InvokerEntry>, std::piecewise_construct, std::forward_as_tuple(*handler), std::forward_as_tuple(permission, static_cast<Trinity::ChatCommands::Console>(console)) }
+ {}
+ [[deprecated]] ChatCommandBuilder(char const* name, rbac::RBACPermissions, bool, std::nullptr_t, char const*, std::vector <ChatCommandBuilder> const& sub)
+ : _name{ ASSERT_NOTNULL(name) }, _data { std::in_place_type<SubCommandEntry>, sub }
+ {}
+
+ private:
+ std::string_view _name;
+ std::variant<InvokerEntry, SubCommandEntry> _data;
+ };
+
+ TC_GAME_API void LoadCommandMap();
+ TC_GAME_API void InvalidateCommandMap();
+ TC_GAME_API bool TryExecuteCommand(ChatHandler& handler, std::string_view cmd);
+ TC_GAME_API void SendCommandHelpFor(ChatHandler& handler, std::string_view cmd);
+ TC_GAME_API std::vector<std::string> GetAutoCompletionsFor(ChatHandler const& handler, std::string_view cmd);
+}
+
+// backwards compatibility with old patches
+using ChatCommand [[deprecated]] = Trinity::ChatCommands::ChatCommandBuilder;
#endif
diff --git a/src/server/game/Chat/ChatCommands/ChatCommandArgs.h b/src/server/game/Chat/ChatCommands/ChatCommandArgs.h
index 8d2af4e7930..5d51e7c6da6 100644
--- a/src/server/game/Chat/ChatCommands/ChatCommandArgs.h
+++ b/src/server/game/Chat/ChatCommands/ChatCommandArgs.h
@@ -81,7 +81,7 @@ namespace Trinity::Impl::ChatCommands
template <>
struct ArgInfo<char const*, void>
{
- static ChatCommandResult TryConsume(char const*& arg, ChatHandler const*, std::string_view args) { arg = args.data(); return std::string_view(); }
+ static ChatCommandResult TryConsume(char const*& arg, ChatHandler const*, std::string_view args) { arg = (args.empty() ? "" : args.data()); return std::string_view(); }
};
// string_view