TrinityCore
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 if (ChatCommandBuilder::InvokerEntry const* invokerEntry = std::get_if<ChatCommandBuilder::InvokerEntry>(&builder._data))
35 {
36 ASSERT(!_invoker, "Duplicate blank sub-command.");
37 _invoker = invokerEntry->_invoker;
38 if (invokerEntry->_help)
39 _help.emplace<TrinityStrings>(invokerEntry->_help);
40
41 _permission = invokerEntry->_permissions;
42 }
43 else
44 LoadCommandsIntoMap(this, _subCommands, std::get<ChatCommandBuilder::SubCommandEntry>(builder._data));
45}
46
48{
49 for (ChatCommandBuilder const& builder : commands)
50 {
51 if (builder._name.empty())
52 {
53 ASSERT(blank, "Empty name command at top level is not permitted.");
54 blank->LoadFromBuilder(builder);
55 }
56 else
57 {
58 std::vector<std::string_view> const tokens = Trinity::Tokenize(builder._name, COMMAND_DELIMITER, false);
59 ASSERT(!tokens.empty(), "Invalid command name '" STRING_VIEW_FMT "'.", STRING_VIEW_FMT_ARG(builder._name));
60 ChatSubCommandMap* subMap = &map;
61 for (size_t i = 0, n = (tokens.size() - 1); i < n; ++i)
62 subMap = &((*subMap)[tokens[i]]._subCommands);
63 ((*subMap)[tokens.back()]).LoadFromBuilder(builder);
64 }
65 }
66}
67
70{
71 if (COMMAND_MAP.empty())
73 return COMMAND_MAP;
74}
77{
79 LoadCommandsIntoMap(nullptr, COMMAND_MAP, sScriptMgr->GetChatCommands());
80
81 if (PreparedQueryResult result = WorldDatabase.Query(WorldDatabase.GetPreparedStatement(WORLD_SEL_COMMANDS)))
82 {
83 do
84 {
85 Field* fields = result->Fetch();
86 std::string_view const name = fields[0].GetStringView();
87 std::string_view const help = fields[1].GetStringView();
88
89 ChatCommandNode* cmd = nullptr;
91 for (std::string_view key : Trinity::Tokenize(name, COMMAND_DELIMITER, false))
92 {
93 auto it = map->find(key);
94 if (it != map->end())
95 {
96 cmd = &it->second;
97 map = &cmd->_subCommands;
98 }
99 else
100 {
101 TC_LOG_ERROR("sql.sql", "Table `command` contains data for non-existant command '{}'. Skipped.", name);
102 cmd = nullptr;
103 break;
104 }
105 }
106
107 if (!cmd)
108 continue;
109
110 if (std::holds_alternative<std::string>(cmd->_help))
111 TC_LOG_ERROR("sql.sql", "Table `command` contains duplicate data for command '{}'. Skipped.", name);
112
113 if (std::holds_alternative<std::monostate>(cmd->_help))
114 cmd->_help.emplace<std::string>(help);
115 else
116 TC_LOG_ERROR("sql.sql", "Table `command` contains legacy help text for command '{}', which uses `trinity_string`. Skipped.", name);
117 } while (result->NextRow());
118 }
119
120 for (auto& [name, cmd] : COMMAND_MAP)
121 cmd.ResolveNames(std::string(name));
122}
123
125{
126 if (_invoker && std::holds_alternative<std::monostate>(_help))
127 TC_LOG_WARN("sql.sql", "Table `command` is missing help text for command '{}'.", name);
128
129 _name = name;
130 for (auto& [subToken, cmd] : _subCommands)
131 {
132 std::string subName(name);
133 subName.push_back(COMMAND_DELIMITER);
134 subName.append(subToken);
135 cmd.ResolveNames(subName);
136 }
137}
138
139static void LogCommandUsage(WorldSession const& session, uint32 permission, std::string_view cmdStr)
140{
142 return;
143
144 if (sAccountMgr->GetRBACPermission(rbac::RBAC_ROLE_PLAYER)->GetLinkedPermissions().count(permission))
145 return;
146
147 Player* player = session.GetPlayer();
148 ObjectGuid targetGuid = player->GetTarget();
149 uint32 areaId = player->GetAreaId();
150 std::string areaName = "Unknown";
151 std::string zoneName = "Unknown";
152 if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(areaId))
153 {
154 LocaleConstant locale = session.GetSessionDbcLocale();
155 areaName = area->AreaName[locale];
156 if (area->GetFlags().HasFlag(AreaFlags::IsSubzone))
157 if (AreaTableEntry const* zone = sAreaTableStore.LookupEntry(area->ParentAreaID))
158 zoneName = zone->AreaName[locale];
159 }
160
161 sLog->OutCommand(session.GetAccountId(), "Command: {} [Player: {} ({}) (Account: {}) X: {} Y: {} Z: {} Map: {} ({}) Area: {} ({}) Zone: {} Selected: {} ({})]",
162 cmdStr, player->GetName(), player->GetGUID().ToString(),
163 session.GetAccountId(), player->GetPositionX(), player->GetPositionY(),
164 player->GetPositionZ(), player->GetMapId(),
165 player->FindMap() ? player->FindMap()->GetMapName() : "Unknown",
166 areaId, areaName, zoneName,
167 player->GetSelectedUnit() ? player->GetSelectedUnit()->GetName().c_str() : "",
168 targetGuid.ToString());
169}
170
172{
173 bool const hasInvoker = IsInvokerVisible(handler);
174 if (hasInvoker)
175 {
176 if (std::holds_alternative<TrinityStrings>(_help))
177 handler.SendSysMessage(std::get<TrinityStrings>(_help));
178 else if (std::holds_alternative<std::string>(_help))
179 handler.SendSysMessage(std::get<std::string>(_help));
180 else
181 {
184 }
185 }
186
187 bool header = false;
188 for (auto it = _subCommands.begin(); it != _subCommands.end(); ++it)
189 {
190 bool const subCommandHasSubCommand = it->second.HasVisibleSubCommands(handler);
191 if (!subCommandHasSubCommand && !it->second.IsInvokerVisible(handler))
192 continue;
193 if (!header)
194 {
195 if (!hasInvoker)
198 header = true;
199 }
200 handler.PSendSysMessage(subCommandHasSubCommand ? LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS : LANG_SUBCMDS_LIST_ENTRY, STRING_VIEW_FMT_ARG(it->second._name));
201 }
202}
203
205{
207 {
208 public:
209 FilteredCommandListIterator(ChatSubCommandMap const& map, ChatHandler const& handler, std::string_view token)
210 : _handler{ handler }, _token{ token }, _it{ map.lower_bound(token) }, _end{ map.end() }
211 {
212 _skip();
213 }
214
215 decltype(auto) operator*() const { return _it.operator*(); }
216 decltype(auto) operator->() const { return _it.operator->(); }
218 {
219 ++_it;
220 _skip();
221 return *this;
222 }
223 explicit operator bool() const { return (_it != _end); }
224 bool operator!() const { return !static_cast<bool>(*this); }
225
226 private:
227 void _skip()
228 {
229 if ((_it != _end) && !StringStartsWithI(_it->first, _token))
230 _it = _end;
231 while ((_it != _end) && !_it->second.IsVisible(_handler))
232 {
233 ++_it;
234 if ((_it != _end) && !StringStartsWithI(_it->first, _token))
235 _it = _end;
236 }
237 }
239 std::string_view const _token;
240 ChatSubCommandMap::const_iterator _it, _end;
241
242 };
243}
244
245/*static*/ bool Trinity::Impl::ChatCommands::ChatCommandNode::TryExecuteCommand(ChatHandler& handler, std::string_view cmdStr)
246{
247 ChatCommandNode const* cmd = nullptr;
248 ChatSubCommandMap const* map = &GetTopLevelMap();
249
250 while (!cmdStr.empty() && (cmdStr.front() == COMMAND_DELIMITER))
251 cmdStr.remove_prefix(1);
252 while (!cmdStr.empty() && (cmdStr.back() == COMMAND_DELIMITER))
253 cmdStr.remove_suffix(1);
254 std::string_view oldTail = cmdStr;
255 while (!oldTail.empty())
256 {
257 /* oldTail = token DELIMITER newTail */
258 auto [token, newTail] = tokenize(oldTail);
259 ASSERT(!token.empty());
260 FilteredCommandListIterator it1(*map, handler, token);
261 if (!it1)
262 break; /* no matching subcommands found */
263
264 if (!StringEqualI(it1->first, token))
265 { /* ok, so it1 points at a partially matching subcommand - let's see if there are others */
266 auto it2 = it1;
267 ++it2;
268
269 if (it2)
270 { /* there are multiple matching subcommands - print possibilities and return */
271 if (cmd)
273 else
275
276 handler.PSendSysMessage(it1->second.HasVisibleSubCommands(handler) ? LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS : LANG_SUBCMDS_LIST_ENTRY, STRING_VIEW_FMT_ARG(it1->first));
277 do
278 {
279 handler.PSendSysMessage(it2->second.HasVisibleSubCommands(handler) ? LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS : LANG_SUBCMDS_LIST_ENTRY, STRING_VIEW_FMT_ARG(it2->first));
280 } while (++it2);
281
282 return true;
283 }
284 }
285
286 /* now we matched exactly one subcommand, and it1 points to it; go down the rabbit hole */
287 cmd = &it1->second;
288 map = &cmd->_subCommands;
289
290 oldTail = newTail;
291 }
292
293 if (cmd)
294 { /* if we matched a command at some point, invoke it */
295 handler.SetSentErrorMessage(false);
296 if (cmd->IsInvokerVisible(handler) && cmd->_invoker(&handler, oldTail))
297 { /* invocation succeeded, log this */
298 if (!handler.IsConsole())
300 }
301 else if (!handler.HasSentErrorMessage())
302 { /* invocation failed, we should show usage */
303 cmd->SendCommandHelp(handler);
304 handler.SetSentErrorMessage(true);
305 }
306 return true;
307 }
308
309 return false;
310}
311
313{
314 ChatCommandNode const* cmd = nullptr;
315 ChatSubCommandMap const* map = &GetTopLevelMap();
316 for (std::string_view token : Trinity::Tokenize(cmdStr, COMMAND_DELIMITER, false))
317 {
318 FilteredCommandListIterator it1(*map, handler, token);
319 if (!it1)
320 { /* no matching subcommands found */
321 if (cmd)
322 {
323 cmd->SendCommandHelp(handler);
325 }
326 else
328 return;
329 }
330
331 if (!StringEqualI(it1->first, token))
332 { /* ok, so it1 points at a partially matching subcommand - let's see if there are others */
333 auto it2 = it1;
334 ++it2;
335
336 if (it2)
337 { /* there are multiple matching subcommands - print possibilities and return */
338 if (cmd)
340 else
342
343 handler.PSendSysMessage(it1->second.HasVisibleSubCommands(handler) ? LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS : LANG_SUBCMDS_LIST_ENTRY, STRING_VIEW_FMT_ARG(it1->first));
344 do
345 {
346 handler.PSendSysMessage(it2->second.HasVisibleSubCommands(handler) ? LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS : LANG_SUBCMDS_LIST_ENTRY, STRING_VIEW_FMT_ARG(it2->first));
347 } while (++it2);
348
349 return;
350 }
351 }
352
353 cmd = &it1->second;
354 map = &cmd->_subCommands;
355 }
356
357 if (cmd)
358 cmd->SendCommandHelp(handler);
359 else if (cmdStr.empty())
360 {
361 FilteredCommandListIterator it(*map, handler, "");
362 if (!it)
363 return;
365 do
366 {
367 handler.PSendSysMessage(it->second.HasVisibleSubCommands(handler) ? LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS : LANG_SUBCMDS_LIST_ENTRY, STRING_VIEW_FMT_ARG(it->second._name));
368 } while (++it);
369 }
370 else
372}
373
374/*static*/ std::vector<std::string> Trinity::Impl::ChatCommands::ChatCommandNode::GetAutoCompletionsFor(ChatHandler const& handler, std::string_view cmdStr)
375{
376 std::string path;
377 ChatCommandNode const* cmd = nullptr;
378 ChatSubCommandMap const* map = &GetTopLevelMap();
379
380 while (!cmdStr.empty() && (cmdStr.front() == COMMAND_DELIMITER))
381 cmdStr.remove_prefix(1);
382 while (!cmdStr.empty() && (cmdStr.back() == COMMAND_DELIMITER))
383 cmdStr.remove_suffix(1);
384 std::string_view oldTail = cmdStr;
385 while (!oldTail.empty())
386 {
387 /* oldTail = token DELIMITER newTail */
388 auto [token, newTail] = tokenize(oldTail);
389 ASSERT(!token.empty());
390 FilteredCommandListIterator it1(*map, handler, token);
391 if (!it1)
392 break; /* no matching subcommands found */
393
394 if (!StringEqualI(it1->first, token))
395 { /* ok, so it1 points at a partially matching subcommand - let's see if there are others */
396 auto it2 = it1;
397 ++it2;
398
399 if (it2)
400 { /* there are multiple matching subcommands - terminate here and show possibilities */
401 std::vector<std::string> vec;
402 auto possibility = ([prefix = std::string_view(path), suffix = std::string_view(newTail)](std::string_view match)
403 {
404 if (prefix.empty())
405 return Trinity::StringFormat("{}{}{}", match, COMMAND_DELIMITER, suffix);
406 else
407 return Trinity::StringFormat("{}{}{}{}{}", prefix, COMMAND_DELIMITER, match, COMMAND_DELIMITER, suffix);
408 });
409
410 vec.emplace_back(possibility(it1->first));
411
412 do vec.emplace_back(possibility(it2->first));
413 while (++it2);
414
415 return vec;
416 }
417 }
418
419 /* now we matched exactly one subcommand, and it1 points to it; go down the rabbit hole */
420 if (path.empty())
421 path.assign(it1->first);
422 else
423 path = Trinity::StringFormat("{}{}{}", path, COMMAND_DELIMITER, it1->first);
424
425 cmd = &it1->second;
426 map = &cmd->_subCommands;
427
428 oldTail = newTail;
429 }
430
431 if (!oldTail.empty())
432 { /* there is some trailing text, leave it as is */
433 if (cmd)
434 { /* if we matched a command at some point, auto-complete it */
435 return {
436 Trinity::StringFormat("{}{}{}", path, COMMAND_DELIMITER, oldTail)
437 };
438 }
439 else
440 return {};
441 }
442 else
443 { /* offer all subcommands */
444 auto possibility = ([prefix = std::string_view(path)](std::string_view match)
445 {
446 if (prefix.empty())
447 return std::string(match);
448 else
449 {
450 return Trinity::StringFormat("{}{}{}", prefix, COMMAND_DELIMITER, match);
451 }
452 });
453
454 std::vector<std::string> vec;
455 for (FilteredCommandListIterator it(*map, handler, ""); it; ++it)
456 vec.emplace_back(possibility(it->first));
457 return vec;
458 }
459}
460
462{
463 if (!_invoker)
464 return false;
465 if (who.IsConsole() && (_permission.AllowConsole == Trinity::ChatCommands::Console::No))
466 return false;
467 return who.HasPermission(_permission.RequiredPermission);
468}
469
471{
472 for (auto it = _subCommands.begin(); it != _subCommands.end(); ++it)
473 if (it->second.IsVisible(who))
474 return true;
475 return false;
476}
477
482std::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:98
static void LogCommandUsage(WorldSession const &session, uint32 permission, std::string_view cmdStr)
static ChatSubCommandMap COMMAND_MAP
Definition: ChatCommand.cpp:68
std::map< std::string_view, Trinity::Impl::ChatCommands::ChatCommandNode, StringCompareLessI_T > ChatSubCommandMap
Definition: ChatCommand.cpp:30
LocaleConstant
Definition: Common.h:48
DB2Storage< AreaTableEntry > sAreaTableStore("AreaTable.db2", &AreaTableLoadInfo::Instance)
std::shared_ptr< PreparedResultSet > PreparedQueryResult
DatabaseWorkerPool< WorldDatabaseConnection > WorldDatabase
Accessor to the world database.
Definition: DatabaseEnv.cpp:20
#define STRING_VIEW_FMT_ARG(str)
Definition: Define.h:135
#define STRING_VIEW_FMT
Definition: Define.h:134
uint32_t uint32
Definition: Define.h:142
#define ASSERT
Definition: Errors.h:68
TrinityStrings
Definition: Language.h:29
@ LANG_CMD_NO_HELP_AVAILABLE
Definition: Language.h:240
@ LANG_SUBCMD_INVALID
Definition: Language.h:237
@ LANG_SUBCMDS_LIST
Definition: Language.h:40
@ LANG_SUBCMDS_LIST_ENTRY
Definition: Language.h:235
@ LANG_AVAILABLE_CMDS
Definition: Language.h:41
@ LANG_CMD_HELP_GENERIC
Definition: Language.h:239
@ LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS
Definition: Language.h:236
@ LANG_CMD_AMBIGUOUS
Definition: Language.h:238
@ LANG_CMD_INVALID
Definition: Language.h:38
@ LANG_SUBCMD_AMBIGUOUS
Definition: Language.h:39
#define TC_LOG_WARN(filterType__,...)
Definition: Log.h:162
#define TC_LOG_ERROR(filterType__,...)
Definition: Log.h:165
#define sLog
Definition: Log.h:130
#define sScriptMgr
Definition: ScriptMgr.h:1418
bool StringEqualI(std::string_view a, std::string_view b)
Definition: Util.cpp:891
bool StringStartsWithI(std::string_view haystack, std::string_view needle)
Definition: Util.h:399
@ WORLD_SEL_COMMANDS
Definition: WorldDatabase.h:69
static bool IsPlayerAccount(uint32 gmlevel)
Definition: AccountMgr.cpp:431
virtual bool HasPermission(uint32 permission) const
Definition: Chat.cpp:53
WorldSession * GetSession()
Definition: Chat.h:42
bool HasSentErrorMessage() const
Definition: Chat.h:113
void PSendSysMessage(const char *fmt, Args &&... args)
Definition: Chat.h:57
void SetSentErrorMessage(bool val)
Definition: Chat.h:114
virtual void SendSysMessage(std::string_view str, bool escapeCharacters=false)
Definition: Chat.cpp:113
bool IsConsole() const
Definition: Chat.h:41
Class used to access individual fields of database query result.
Definition: Field.h:90
std::string_view GetStringView() const
Definition: Field.cpp:130
char const * GetMapName() const
Definition: Map.cpp:1854
std::string ToString() const
Definition: ObjectGuid.cpp:554
static ObjectGuid GetGUID(Object const *o)
Definition: Object.h:159
Unit * GetSelectedUnit() const
Definition: Player.cpp:24142
void SendCommandHelp(ChatHandler &handler) const
std::map< std::string_view, ChatCommandNode, StringCompareLessI_T > _subCommands
Definition: ChatCommand.h:209
std::variant< std::monostate, TrinityStrings, std::string > _help
Definition: ChatCommand.h:208
bool HasVisibleSubCommands(ChatHandler const &who) const
static void SendCommandHelpFor(ChatHandler &handler, std::string_view cmd)
void LoadFromBuilder(ChatCommandBuilder const &builder)
Definition: ChatCommand.cpp:32
static std::map< std::string_view, ChatCommandNode, StringCompareLessI_T > const & GetTopLevelMap()
Definition: ChatCommand.cpp:69
static void LoadCommandsIntoMap(ChatCommandNode *blank, std::map< std::string_view, ChatCommandNode, StringCompareLessI_T > &map, Trinity::ChatCommands::ChatCommandTable const &commands)
Definition: ChatCommand.cpp:47
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:1759
constexpr uint32 GetMapId() const
Definition: Position.h:201
Map * FindMap() const
Definition: Object.h:625
std::string const & GetName() const
Definition: Object.h:555
uint32 GetAreaId() const
Definition: Object.h:546
Player session in the World.
Definition: WorldSession.h:963
AccountTypes GetSecurity() const
Definition: WorldSession.h:999
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)
std::vector< ChatCommandBuilder > ChatCommandTable
Definition: ChatCommand.h:49
TC_GAME_API bool TryExecuteCommand(ChatHandler &handler, std::string_view cmd)
TC_GAME_API void InvalidateCommandMap()
TC_GAME_API void LoadCommandMap()
TokenizeResult tokenize(std::string_view args)
static constexpr char COMMAND_DELIMITER
TC_COMMON_API std::vector< std::string_view > Tokenize(std::string_view str, char sep, bool keepEmpty)
Definition: Util.cpp:56
std::string StringFormat(FormatString< Args... > fmt, Args &&... args)
Default TC string format function.
Definition: StringFormat.h:38
@ RBAC_ROLE_PLAYER
Definition: RBAC.h:112
constexpr float GetPositionX() const
Definition: Position.h:76
constexpr float GetPositionY() const
Definition: Position.h:77
constexpr float GetPositionZ() const
Definition: Position.h:78
std::variant< InvokerEntry, SubCommandEntry > _data
Definition: ChatCommand.h:267
FilteredCommandListIterator(ChatSubCommandMap const &map, ChatHandler const &handler, std::string_view token)