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