TrinityCore
Loading...
Searching...
No Matches
ChatCommand.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "ChatCommand.h"
19
20#include "AccountMgr.h"
21#include "Chat.h"
22#include "DatabaseEnv.h"
23#include "DB2Stores.h"
24#include "Log.h"
25#include "Map.h"
26#include "Player.h"
27#include "ScriptMgr.h"
28#include "WorldSession.h"
29
30using ChatSubCommandMap = std::map<std::string_view, Trinity::Impl::ChatCommands::ChatCommandNode, StringCompareLessI_T>;
31
33{
34 switch (builder._data.index())
35 {
36 case 0:
37 {
38 ChatCommandBuilder::InvokerEntry const& invokerEntry = std::get<0>(builder._data);
39 ASSERT(!_invoker, "Duplicate blank sub-command.");
40 _invoker = invokerEntry._invoker;
41 if (invokerEntry._help != TrinityStrings())
42 _help.emplace<TrinityStrings>(invokerEntry._help);
43
44 _permission = invokerEntry._permissions;
45 break;
46 }
47 case 1:
48 {
49 auto [data, size] = std::get<1>(builder._data);
50 LoadCommandsIntoMap(this, _subCommands, { data, size });
51 break;
52 }
53 default:
54 break;
55 }
56}
57
58/*static*/ void Trinity::Impl::ChatCommands::ChatCommandNode::LoadCommandsIntoMap(ChatCommandNode* blank, ChatSubCommandMap& map, std::span<ChatCommandBuilder const> commands)
59{
60 for (ChatCommandBuilder const& builder : commands)
61 {
62 if (builder._name.empty())
63 {
64 ASSERT(blank, "Empty name command at top level is not permitted.");
65 blank->LoadFromBuilder(builder);
66 }
67 else
68 {
69 std::vector<std::string_view> const tokens = Trinity::Tokenize(builder._name, COMMAND_DELIMITER, false);
70 ASSERT(!tokens.empty(), "Invalid command name '" STRING_VIEW_FMT "'.", STRING_VIEW_FMT_ARG(builder._name));
71 ChatSubCommandMap* subMap = &map;
72 for (size_t i = 0, n = (tokens.size() - 1); i < n; ++i)
73 subMap = &((*subMap)[tokens[i]]._subCommands);
74 ((*subMap)[tokens.back()]).LoadFromBuilder(builder);
75 }
76 }
77}
78
81{
82 if (COMMAND_MAP.empty())
83 LoadCommandMap();
84 return COMMAND_MAP;
85}
88{
89 InvalidateCommandMap();
90 LoadCommandsIntoMap(nullptr, COMMAND_MAP, sScriptMgr->GetChatCommands());
91
92 if (PreparedQueryResult result = WorldDatabase.Query(WorldDatabase.GetPreparedStatement(WORLD_SEL_COMMANDS)))
93 {
94 do
95 {
96 Field* fields = result->Fetch();
97 std::string_view const name = fields[0].GetStringView();
98 std::string_view const help = fields[1].GetStringView();
99
100 ChatCommandNode* cmd = nullptr;
102 for (std::string_view key : Trinity::Tokenize(name, COMMAND_DELIMITER, false))
103 {
104 auto it = map->find(key);
105 if (it != map->end())
106 {
107 cmd = &it->second;
108 map = &cmd->_subCommands;
109 }
110 else
111 {
112 TC_LOG_ERROR("sql.sql", "Table `command` contains data for non-existant command '{}'. Skipped.", name);
113 cmd = nullptr;
114 break;
115 }
116 }
117
118 if (!cmd)
119 continue;
120
121 if (std::holds_alternative<std::string>(cmd->_help))
122 TC_LOG_ERROR("sql.sql", "Table `command` contains duplicate data for command '{}'. Skipped.", name);
123
124 if (std::holds_alternative<std::monostate>(cmd->_help))
125 cmd->_help.emplace<std::string>(help);
126 else
127 TC_LOG_ERROR("sql.sql", "Table `command` contains legacy help text for command '{}', which uses `trinity_string`. Skipped.", name);
128 } while (result->NextRow());
129 }
130
131 for (auto& [name, cmd] : COMMAND_MAP)
132 cmd.ResolveNames(std::string(name));
133}
134
136{
137 if (_invoker && std::holds_alternative<std::monostate>(_help))
138 TC_LOG_WARN("sql.sql", "Table `command` is missing help text for command '{}'.", name);
139
140 _name = name;
141 for (auto& [subToken, cmd] : _subCommands)
142 {
143 std::string subName(name);
144 subName.push_back(COMMAND_DELIMITER);
145 subName.append(subToken);
146 cmd.ResolveNames(subName);
147 }
148}
149
150static void LogCommandUsage(WorldSession const& session, uint32 permission, std::string_view cmdStr)
151{
153 return;
154
155 if (sAccountMgr->GetRBACPermission(rbac::RBAC_ROLE_PLAYER)->GetLinkedPermissions().count(permission))
156 return;
157
158 Player* player = session.GetPlayer();
159 ObjectGuid targetGuid = player->GetTarget();
160 uint32 areaId = player->GetAreaId();
161 std::string areaName = "Unknown";
162 std::string zoneName = "Unknown";
163 if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(areaId))
164 {
165 LocaleConstant locale = session.GetSessionDbcLocale();
166 areaName = area->AreaName[locale];
167 if (area->GetFlags().HasFlag(AreaFlags::IsSubzone))
168 if (AreaTableEntry const* zone = sAreaTableStore.LookupEntry(area->ParentAreaID))
169 zoneName = zone->AreaName[locale];
170 }
171
172 sLog->OutCommand(session.GetAccountId(), "Command: {} [Player: {} ({}) (Account: {}) X: {} Y: {} Z: {} Map: {} ({}) Area: {} ({}) Zone: {} Selected: {} ({})]",
173 cmdStr, player->GetName(), player->GetGUID().ToString(),
174 session.GetAccountId(), player->GetPositionX(), player->GetPositionY(),
175 player->GetPositionZ(), player->GetMapId(),
176 player->FindMap() ? player->FindMap()->GetMapName() : "Unknown",
177 areaId, areaName, zoneName,
178 player->GetSelectedUnit() ? player->GetSelectedUnit()->GetName().c_str() : "",
179 targetGuid.ToString());
180}
181
183{
184 bool const hasInvoker = IsInvokerVisible(handler);
185 if (hasInvoker)
186 {
187 if (std::holds_alternative<TrinityStrings>(_help))
188 handler.SendSysMessage(std::get<TrinityStrings>(_help));
189 else if (std::holds_alternative<std::string>(_help))
190 handler.SendSysMessage(std::get<std::string>(_help));
191 else
192 {
195 }
196 }
197
198 bool header = false;
199 for (auto it = _subCommands.begin(); it != _subCommands.end(); ++it)
200 {
201 bool const subCommandHasSubCommand = it->second.HasVisibleSubCommands(handler);
202 if (!subCommandHasSubCommand && !it->second.IsInvokerVisible(handler))
203 continue;
204 if (!header)
205 {
206 if (!hasInvoker)
209 header = true;
210 }
211 handler.PSendSysMessage(subCommandHasSubCommand ? LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS : LANG_SUBCMDS_LIST_ENTRY, STRING_VIEW_FMT_ARG(it->second._name));
212 }
213}
214
216{
218 {
219 public:
220 FilteredCommandListIterator(ChatSubCommandMap const& map, ChatHandler const& handler, std::string_view token)
221 : _handler{ handler }, _token{ token }, _it{ map.lower_bound(token) }, _end{ map.end() }
222 {
223 _skip();
224 }
225
226 decltype(auto) operator*() const { return _it.operator*(); }
227 decltype(auto) operator->() const { return _it.operator->(); }
229 {
230 ++_it;
231 _skip();
232 return *this;
233 }
234 explicit operator bool() const { return (_it != _end); }
235 bool operator!() const { return !static_cast<bool>(*this); }
236
237 private:
238 void _skip()
239 {
240 if ((_it != _end) && !StringStartsWithI(_it->first, _token))
241 _it = _end;
242 while ((_it != _end) && !_it->second.IsVisible(_handler))
243 {
244 ++_it;
245 if ((_it != _end) && !StringStartsWithI(_it->first, _token))
246 _it = _end;
247 }
248 }
250 std::string_view const _token;
251 ChatSubCommandMap::const_iterator _it, _end;
252
253 };
254}
255
256/*static*/ bool Trinity::Impl::ChatCommands::ChatCommandNode::TryExecuteCommand(ChatHandler& handler, std::string_view cmdStr)
257{
258 ChatCommandNode const* cmd = nullptr;
259 ChatSubCommandMap const* map = &GetTopLevelMap();
260
261 while (!cmdStr.empty() && (cmdStr.front() == COMMAND_DELIMITER))
262 cmdStr.remove_prefix(1);
263 while (!cmdStr.empty() && (cmdStr.back() == COMMAND_DELIMITER))
264 cmdStr.remove_suffix(1);
265 std::string_view oldTail = cmdStr;
266 while (!oldTail.empty())
267 {
268 /* oldTail = token DELIMITER newTail */
269 auto [token, newTail] = tokenize(oldTail);
270 ASSERT(!token.empty());
271 FilteredCommandListIterator it1(*map, handler, token);
272 if (!it1)
273 break; /* no matching subcommands found */
274
275 if (!StringEqualI(it1->first, token))
276 { /* ok, so it1 points at a partially matching subcommand - let's see if there are others */
277 auto it2 = it1;
278 ++it2;
279
280 if (it2)
281 { /* there are multiple matching subcommands - print possibilities and return */
282 if (cmd)
284 else
286
287 handler.PSendSysMessage(it1->second.HasVisibleSubCommands(handler) ? LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS : LANG_SUBCMDS_LIST_ENTRY, STRING_VIEW_FMT_ARG(it1->first));
288 do
289 {
290 handler.PSendSysMessage(it2->second.HasVisibleSubCommands(handler) ? LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS : LANG_SUBCMDS_LIST_ENTRY, STRING_VIEW_FMT_ARG(it2->first));
291 } while (++it2);
292
293 return true;
294 }
295 }
296
297 /* now we matched exactly one subcommand, and it1 points to it; go down the rabbit hole */
298 cmd = &it1->second;
299 map = &cmd->_subCommands;
300
301 oldTail = newTail;
302 }
303
304 if (cmd)
305 { /* if we matched a command at some point, invoke it */
306 handler.SetSentErrorMessage(false);
307 if (cmd->IsInvokerVisible(handler) && cmd->_invoker(&handler, oldTail))
308 { /* invocation succeeded, log this */
309 if (!handler.IsConsole())
311 }
312 else if (!handler.HasSentErrorMessage())
313 { /* invocation failed, we should show usage */
314 cmd->SendCommandHelp(handler);
315 handler.SetSentErrorMessage(true);
316 }
317 return true;
318 }
319
320 return false;
321}
322
324{
325 ChatCommandNode const* cmd = nullptr;
326 ChatSubCommandMap const* map = &GetTopLevelMap();
327 for (std::string_view token : Trinity::Tokenize(cmdStr, COMMAND_DELIMITER, false))
328 {
329 FilteredCommandListIterator it1(*map, handler, token);
330 if (!it1)
331 { /* no matching subcommands found */
332 if (cmd)
333 {
334 cmd->SendCommandHelp(handler);
336 }
337 else
339 return;
340 }
341
342 if (!StringEqualI(it1->first, token))
343 { /* ok, so it1 points at a partially matching subcommand - let's see if there are others */
344 auto it2 = it1;
345 ++it2;
346
347 if (it2)
348 { /* there are multiple matching subcommands - print possibilities and return */
349 if (cmd)
351 else
353
354 handler.PSendSysMessage(it1->second.HasVisibleSubCommands(handler) ? LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS : LANG_SUBCMDS_LIST_ENTRY, STRING_VIEW_FMT_ARG(it1->first));
355 do
356 {
357 handler.PSendSysMessage(it2->second.HasVisibleSubCommands(handler) ? LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS : LANG_SUBCMDS_LIST_ENTRY, STRING_VIEW_FMT_ARG(it2->first));
358 } while (++it2);
359
360 return;
361 }
362 }
363
364 cmd = &it1->second;
365 map = &cmd->_subCommands;
366 }
367
368 if (cmd)
369 cmd->SendCommandHelp(handler);
370 else if (cmdStr.empty())
371 {
372 FilteredCommandListIterator it(*map, handler, "");
373 if (!it)
374 return;
376 do
377 {
378 handler.PSendSysMessage(it->second.HasVisibleSubCommands(handler) ? LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS : LANG_SUBCMDS_LIST_ENTRY, STRING_VIEW_FMT_ARG(it->second._name));
379 } while (++it);
380 }
381 else
383}
384
385/*static*/ std::vector<std::string> Trinity::Impl::ChatCommands::ChatCommandNode::GetAutoCompletionsFor(ChatHandler const& handler, std::string_view cmdStr)
386{
387 std::string path;
388 ChatCommandNode const* cmd = nullptr;
389 ChatSubCommandMap const* map = &GetTopLevelMap();
390
391 while (!cmdStr.empty() && (cmdStr.front() == COMMAND_DELIMITER))
392 cmdStr.remove_prefix(1);
393 while (!cmdStr.empty() && (cmdStr.back() == COMMAND_DELIMITER))
394 cmdStr.remove_suffix(1);
395 std::string_view oldTail = cmdStr;
396 while (!oldTail.empty())
397 {
398 /* oldTail = token DELIMITER newTail */
399 auto [token, newTail] = tokenize(oldTail);
400 ASSERT(!token.empty());
401 FilteredCommandListIterator it1(*map, handler, token);
402 if (!it1)
403 break; /* no matching subcommands found */
404
405 if (!StringEqualI(it1->first, token))
406 { /* ok, so it1 points at a partially matching subcommand - let's see if there are others */
407 auto it2 = it1;
408 ++it2;
409
410 if (it2)
411 { /* there are multiple matching subcommands - terminate here and show possibilities */
412 std::vector<std::string> vec;
413 auto possibility = ([prefix = std::string_view(path), suffix = std::string_view(newTail)](std::string_view match)
414 {
415 if (prefix.empty())
416 return Trinity::StringFormat("{}{}{}", match, COMMAND_DELIMITER, suffix);
417 else
418 return Trinity::StringFormat("{}{}{}{}{}", prefix, COMMAND_DELIMITER, match, COMMAND_DELIMITER, suffix);
419 });
420
421 vec.emplace_back(possibility(it1->first));
422
423 do vec.emplace_back(possibility(it2->first));
424 while (++it2);
425
426 return vec;
427 }
428 }
429
430 /* now we matched exactly one subcommand, and it1 points to it; go down the rabbit hole */
431 if (path.empty())
432 path.assign(it1->first);
433 else
434 path = Trinity::StringFormat("{}{}{}", path, COMMAND_DELIMITER, it1->first);
435
436 cmd = &it1->second;
437 map = &cmd->_subCommands;
438
439 oldTail = newTail;
440 }
441
442 if (!oldTail.empty())
443 { /* there is some trailing text, leave it as is */
444 if (cmd)
445 { /* if we matched a command at some point, auto-complete it */
446 return {
447 Trinity::StringFormat("{}{}{}", path, COMMAND_DELIMITER, oldTail)
448 };
449 }
450 else
451 return {};
452 }
453 else
454 { /* offer all subcommands */
455 auto possibility = ([prefix = std::string_view(path)](std::string_view match)
456 {
457 if (prefix.empty())
458 return std::string(match);
459 else
460 {
461 return Trinity::StringFormat("{}{}{}", prefix, COMMAND_DELIMITER, match);
462 }
463 });
464
465 std::vector<std::string> vec;
466 for (FilteredCommandListIterator it(*map, handler, ""); it; ++it)
467 vec.emplace_back(possibility(it->first));
468 return vec;
469 }
470}
471
473{
474 if (!_invoker)
475 return false;
476 if (who.IsConsole() && (_permission.AllowConsole == Trinity::ChatCommands::Console::No))
477 return false;
478 return who.HasPermission(_permission.RequiredPermission);
479}
480
482{
483 for (auto it = _subCommands.begin(); it != _subCommands.end(); ++it)
484 if (it->second.IsVisible(who))
485 return true;
486 return false;
487}
488
490{
491 if (result2.IsSuccessful())
492 result1 = *result2;
493 else if (result2.HasErrorMessage())
494 {
495 if (result1.HasErrorMessage())
496 result1 = Trinity::StringFormat("{} \"{}\"\n{} \"{}\"",
497 GetTrinityString(handler, LANG_CMDPARSER_EITHER), result2.GetErrorMessage(),
498 GetTrinityString(handler, LANG_CMDPARSER_OR), result1.GetErrorMessage());
499 else
500 result1 = std::move(result2).GetErrorMessage();
501 }
502 // else only result1 has error message, don't need to do anything
503}
504
509std::vector<std::string> Trinity::ChatCommands::GetAutoCompletionsFor(ChatHandler const& handler, std::string_view cmd) { return Trinity::Impl::ChatCommands::ChatCommandNode::GetAutoCompletionsFor(handler, cmd); }
#define sAccountMgr
Definition AccountMgr.h:104
static void LogCommandUsage(WorldSession const &session, uint32 permission, std::string_view cmdStr)
static ChatSubCommandMap COMMAND_MAP
std::map< std::string_view, Trinity::Impl::ChatCommands::ChatCommandNode, StringCompareLessI_T > ChatSubCommandMap
LocaleConstant
Definition Common.h:51
DB2Storage< AreaTableEntry > sAreaTableStore("AreaTable.db2", &AreaTableLoadInfo::Instance)
std::shared_ptr< PreparedResultSet > PreparedQueryResult
DatabaseWorkerPool< WorldDatabaseConnection > WorldDatabase
Accessor to the world database.
#define STRING_VIEW_FMT_ARG(str)
Definition Define.h:147
#define STRING_VIEW_FMT
Definition Define.h:146
uint32_t uint32
Definition Define.h:154
#define ASSERT
Definition Errors.h:80
TrinityStrings
Definition Language.h:29
@ LANG_CMD_NO_HELP_AVAILABLE
Definition Language.h:239
@ LANG_SUBCMD_INVALID
Definition Language.h:236
@ LANG_SUBCMDS_LIST
Definition Language.h:40
@ LANG_SUBCMDS_LIST_ENTRY
Definition Language.h:234
@ LANG_AVAILABLE_CMDS
Definition Language.h:41
@ LANG_CMDPARSER_EITHER
Definition Language.h:1007
@ LANG_CMD_HELP_GENERIC
Definition Language.h:238
@ LANG_CMDPARSER_OR
Definition Language.h:1008
@ LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS
Definition Language.h:235
@ LANG_CMD_AMBIGUOUS
Definition Language.h:237
@ LANG_CMD_INVALID
Definition Language.h:38
@ LANG_SUBCMD_AMBIGUOUS
Definition Language.h:39
#define TC_LOG_ERROR(filterType__, message__,...)
Definition Log.h:190
#define sLog
Definition Log.h:156
#define TC_LOG_WARN(filterType__, message__,...)
Definition Log.h:187
#define sScriptMgr
Definition ScriptMgr.h:1449
TC_COMMON_API bool StringEqualI(std::string_view str1, std::string_view str2)
Definition Util.cpp:849
bool StringStartsWithI(std::string_view haystack, std::string_view needle)
Definition Util.h:462
@ WORLD_SEL_COMMANDS
static bool IsPlayerAccount(uint32 gmlevel)
ObjectGuid const & GetGUID() const
Definition BaseEntity.h:163
virtual bool HasPermission(uint32 permission) const
Definition Chat.cpp:51
WorldSession * GetSession()
Definition Chat.h:42
bool HasSentErrorMessage() const
Definition Chat.h:126
void SetSentErrorMessage(bool val)
Definition Chat.h:127
void PSendSysMessage(char const *fmt, Args &&... args)
Definition Chat.h:62
virtual void SendSysMessage(std::string_view str, bool escapeCharacters=false)
Definition Chat.cpp:111
bool IsConsole() const
Definition Chat.h:41
Class used to access individual fields of database query result.
Definition Field.h:94
std::string_view GetStringView() const noexcept
Definition Field.cpp:118
char const * GetMapName() const
Definition Map.cpp:1844
std::string ToString() const
Unit * GetSelectedUnit() const
Definition Player.cpp:24894
void SendCommandHelp(ChatHandler &handler) const
static void LoadCommandsIntoMap(ChatCommandNode *blank, std::map< std::string_view, ChatCommandNode, StringCompareLessI_T > &map, std::span< ChatCommandBuilder const > commands)
std::map< std::string_view, ChatCommandNode, StringCompareLessI_T > _subCommands
std::variant< std::monostate, TrinityStrings, std::string > _help
bool HasVisibleSubCommands(ChatHandler const &who) const
static void SendCommandHelpFor(ChatHandler &handler, std::string_view cmd)
void LoadFromBuilder(ChatCommandBuilder const &builder)
static std::map< std::string_view, ChatCommandNode, StringCompareLessI_T > const & GetTopLevelMap()
bool IsInvokerVisible(ChatHandler const &who) const
static std::vector< std::string > GetAutoCompletionsFor(ChatHandler const &handler, std::string_view cmd)
static bool TryExecuteCommand(ChatHandler &handler, std::string_view cmd)
ObjectGuid GetTarget() const
Definition Unit.h:1831
constexpr uint32 GetMapId() const
Definition Position.h:216
Map * FindMap() const
Definition Object.h:412
std::string const & GetName() const
Definition Object.h:342
uint32 GetAreaId() const
Definition Object.h:333
Player session in the World.
AccountTypes GetSecurity() const
LocaleConstant GetSessionDbcLocale() const
Player * GetPlayer() const
uint32 GetAccountId() const
TC_GAME_API std::vector< std::string > GetAutoCompletionsFor(ChatHandler const &handler, std::string_view cmd)
TC_GAME_API void SendCommandHelpFor(ChatHandler &handler, std::string_view cmd)
TC_GAME_API bool TryExecuteCommand(ChatHandler &handler, std::string_view cmd)
TC_GAME_API void InvalidateCommandMap()
TC_GAME_API void LoadCommandMap()
TC_GAME_API void MergeChatCommandResults(ChatHandler const *handler, ChatCommandResult &result1, ChatCommandResult &result2) noexcept
TokenizeResult tokenize(std::string_view args) noexcept
static constexpr char COMMAND_DELIMITER
TC_GAME_API char const * GetTrinityString(ChatHandler const *handler, TrinityStrings which)
TC_COMMON_API std::vector< std::string_view > Tokenize(std::string_view str, char sep, bool keepEmpty)
Definition Util.cpp:57
std::string StringFormat(FormatString< Args... > fmt, Args &&... args) noexcept
Default TC string format function.
@ RBAC_ROLE_PLAYER
Definition RBAC.h:112
constexpr float GetPositionX() const
Definition Position.h:87
constexpr float GetPositionY() const
Definition Position.h:88
constexpr float GetPositionZ() const
Definition Position.h:89
Trinity::Impl::ChatCommands::CommandInvoker _invoker
Trinity::Impl::ChatCommands::CommandPermissions _permissions
std::variant< InvokerEntry, std::pair< ChatCommandBuilder const *, std::size_t > > _data
FilteredCommandListIterator(ChatSubCommandMap const &map, ChatHandler const &handler, std::string_view token)