aboutsummaryrefslogtreecommitdiff
path: root/src/server/game
diff options
context:
space:
mode:
authorTreeston <treeston.mmoc@gmail.com>2020-09-20 02:50:38 +0200
committerShauren <shauren.trinity@gmail.com>2022-02-27 20:08:41 +0100
commit3fd2eb126cbed36292fa5defc024c2b93e8d8671 (patch)
tree6068f6e874d7552fcf00a92ca75a85381323a038 /src/server/game
parent7a2c3af98831364988db25dd1bdd8ca10464c641 (diff)
[3.3.5] ChatCommands, the other half: chat command resolution refactor (PR #25463)
(cherry picked from commit 1eca51b417678b9a48b28552925d5694105f82bb)
Diffstat (limited to 'src/server/game')
-rw-r--r--src/server/game/Accounts/BattlenetAccountMgr.cpp10
-rw-r--r--src/server/game/Accounts/BattlenetAccountMgr.h8
-rw-r--r--src/server/game/Accounts/RBAC.h122
-rw-r--r--src/server/game/Chat/Chat.cpp385
-rw-r--r--src/server/game/Chat/Chat.h27
-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
-rw-r--r--src/server/game/Miscellaneous/Language.h14
-rw-r--r--src/server/game/Scripting/ScriptMgr.cpp21
-rw-r--r--src/server/game/Scripting/ScriptMgr.h7
-rw-r--r--src/server/game/World/World.cpp3
12 files changed, 691 insertions, 526 deletions
diff --git a/src/server/game/Accounts/BattlenetAccountMgr.cpp b/src/server/game/Accounts/BattlenetAccountMgr.cpp
index a726fdf5c7d..a204e99f337 100644
--- a/src/server/game/Accounts/BattlenetAccountMgr.cpp
+++ b/src/server/game/Accounts/BattlenetAccountMgr.cpp
@@ -90,7 +90,7 @@ bool Battlenet::AccountMgr::CheckPassword(uint32 accountId, std::string password
return LoginDatabase.Query(stmt) != nullptr;
}
-AccountOpResult Battlenet::AccountMgr::LinkWithGameAccount(std::string const& email, std::string const& gameAccountName)
+AccountOpResult Battlenet::AccountMgr::LinkWithGameAccount(std::string_view email, std::string_view gameAccountName)
{
uint32 bnetAccountId = GetId(email);
if (!bnetAccountId)
@@ -111,7 +111,7 @@ AccountOpResult Battlenet::AccountMgr::LinkWithGameAccount(std::string const& em
return AccountOpResult::AOR_OK;
}
-AccountOpResult Battlenet::AccountMgr::UnlinkGameAccount(std::string const& gameAccountName)
+AccountOpResult Battlenet::AccountMgr::UnlinkGameAccount(std::string_view gameAccountName)
{
uint32 gameAccountId = GameAccountMgr::GetId(gameAccountName);
if (!gameAccountId)
@@ -128,10 +128,10 @@ AccountOpResult Battlenet::AccountMgr::UnlinkGameAccount(std::string const& game
return AccountOpResult::AOR_OK;
}
-uint32 Battlenet::AccountMgr::GetId(std::string const& username)
+uint32 Battlenet::AccountMgr::GetId(std::string_view username)
{
LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_BNET_ACCOUNT_ID_BY_EMAIL);
- stmt->setString(0, username);
+ stmt->setStringView(0, username);
if (PreparedQueryResult result = LoginDatabase.Query(stmt))
return (*result)[0].GetUInt32();
@@ -172,7 +172,7 @@ uint8 Battlenet::AccountMgr::GetMaxIndex(uint32 accountId)
return 0;
}
-std::string Battlenet::AccountMgr::CalculateShaPassHash(std::string const& name, std::string const& password)
+std::string Battlenet::AccountMgr::CalculateShaPassHash(std::string_view name, std::string_view password)
{
Trinity::Crypto::SHA256 email;
email.UpdateData(name);
diff --git a/src/server/game/Accounts/BattlenetAccountMgr.h b/src/server/game/Accounts/BattlenetAccountMgr.h
index 8221896eb11..43c60c5b006 100644
--- a/src/server/game/Accounts/BattlenetAccountMgr.h
+++ b/src/server/game/Accounts/BattlenetAccountMgr.h
@@ -32,15 +32,15 @@ namespace Battlenet
TC_GAME_API AccountOpResult CreateBattlenetAccount(std::string email, std::string password, bool withGameAccount, std::string* gameAccountName);
TC_GAME_API AccountOpResult ChangePassword(uint32 accountId, std::string newPassword);
TC_GAME_API bool CheckPassword(uint32 accountId, std::string password);
- TC_GAME_API AccountOpResult LinkWithGameAccount(std::string const& email, std::string const& gameAccountName);
- TC_GAME_API AccountOpResult UnlinkGameAccount(std::string const& gameAccountName);
+ TC_GAME_API AccountOpResult LinkWithGameAccount(std::string_view email, std::string_view gameAccountName);
+ TC_GAME_API AccountOpResult UnlinkGameAccount(std::string_view gameAccountName);
- TC_GAME_API uint32 GetId(std::string const& username);
+ TC_GAME_API uint32 GetId(std::string_view username);
TC_GAME_API bool GetName(uint32 accountId, std::string& name);
TC_GAME_API uint32 GetIdByGameAccount(uint32 gameAccountId);
TC_GAME_API uint8 GetMaxIndex(uint32 accountId);
- TC_GAME_API std::string CalculateShaPassHash(std::string const& name, std::string const& password);
+ TC_GAME_API std::string CalculateShaPassHash(std::string_view name, std::string_view password);
}
}
diff --git a/src/server/game/Accounts/RBAC.h b/src/server/game/Accounts/RBAC.h
index 95f1df06e1b..408da2515dc 100644
--- a/src/server/game/Accounts/RBAC.h
+++ b/src/server/game/Accounts/RBAC.h
@@ -105,6 +105,12 @@ enum RBACPermissions
// Free space for core permissions (till 149)
// Roles (Permissions with delegated permissions) use 199 and descending
+
+ RBAC_ROLE_ADMINISTRATOR = 196,
+ RBAC_ROLE_GAMEMASTER = 197,
+ RBAC_ROLE_MODERATOR = 198,
+ RBAC_ROLE_PLAYER = 199,
+
RBAC_PERM_COMMAND_RBAC = 200,
RBAC_PERM_COMMAND_RBAC_ACC = 201,
RBAC_PERM_COMMAND_RBAC_ACC_PERM_LIST = 202,
@@ -117,7 +123,7 @@ enum RBACPermissions
RBAC_PERM_COMMAND_BNET_ACCOUNT_LOCK_COUNTRY = 209,
RBAC_PERM_COMMAND_BNET_ACCOUNT_LOCK_IP = 210,
RBAC_PERM_COMMAND_BNET_ACCOUNT_PASSWORD = 211,
- RBAC_PERM_COMMAND_BNET_ACCOUNT_SET = 212,
+ // 212 previously used, do not reuse
RBAC_PERM_COMMAND_BNET_ACCOUNT_SET_PASSWORD = 213,
RBAC_PERM_COMMAND_BNET_ACCOUNT_LINK = 214,
RBAC_PERM_COMMAND_BNET_ACCOUNT_UNLINK = 215,
@@ -135,41 +141,41 @@ enum RBACPermissions
RBAC_PERM_COMMAND_ACCOUNT_SET_ADDON = 227,
RBAC_PERM_COMMAND_ACCOUNT_SET_SECLEVEL = 228,
RBAC_PERM_COMMAND_ACCOUNT_SET_PASSWORD = 229,
- RBAC_PERM_COMMAND_ACHIEVEMENT = 230,
+ // 230 previously used, do not reuse
RBAC_PERM_COMMAND_ACHIEVEMENT_ADD = 231,
- RBAC_PERM_COMMAND_ARENA = 232,
+ // 232 previously used, do not reuse
RBAC_PERM_COMMAND_ARENA_CAPTAIN = 233,
RBAC_PERM_COMMAND_ARENA_CREATE = 234,
RBAC_PERM_COMMAND_ARENA_DISBAND = 235,
RBAC_PERM_COMMAND_ARENA_INFO = 236,
RBAC_PERM_COMMAND_ARENA_LOOKUP = 237,
RBAC_PERM_COMMAND_ARENA_RENAME = 238,
- RBAC_PERM_COMMAND_BAN = 239,
+ // 239 previously used, do not reuse
RBAC_PERM_COMMAND_BAN_ACCOUNT = 240,
RBAC_PERM_COMMAND_BAN_CHARACTER = 241,
RBAC_PERM_COMMAND_BAN_IP = 242,
RBAC_PERM_COMMAND_BAN_PLAYERACCOUNT = 243,
- RBAC_PERM_COMMAND_BANINFO = 244,
+ // 244 previously used, do not reuse
RBAC_PERM_COMMAND_BANINFO_ACCOUNT = 245,
RBAC_PERM_COMMAND_BANINFO_CHARACTER = 246,
RBAC_PERM_COMMAND_BANINFO_IP = 247,
- RBAC_PERM_COMMAND_BANLIST = 248,
+ // 248 previously used, do not reuse
RBAC_PERM_COMMAND_BANLIST_ACCOUNT = 249,
RBAC_PERM_COMMAND_BANLIST_CHARACTER = 250,
RBAC_PERM_COMMAND_BANLIST_IP = 251,
- RBAC_PERM_COMMAND_UNBAN = 252,
+ // 252 previously used, do not reuse
RBAC_PERM_COMMAND_UNBAN_ACCOUNT = 253,
RBAC_PERM_COMMAND_UNBAN_CHARACTER = 254,
RBAC_PERM_COMMAND_UNBAN_IP = 255,
RBAC_PERM_COMMAND_UNBAN_PLAYERACCOUNT = 256,
- RBAC_PERM_COMMAND_BF = 257,
+ // 257 previously used, do not reuse
RBAC_PERM_COMMAND_BF_START = 258,
RBAC_PERM_COMMAND_BF_STOP = 259,
RBAC_PERM_COMMAND_BF_SWITCH = 260,
RBAC_PERM_COMMAND_BF_TIMER = 261,
RBAC_PERM_COMMAND_BF_ENABLE = 262,
RBAC_PERM_COMMAND_ACCOUNT_EMAIL = 263,
- RBAC_PERM_COMMAND_ACCOUNT_SET_SEC = 264,
+ // 264 previously used, do not reuse
RBAC_PERM_COMMAND_ACCOUNT_SET_SEC_EMAIL = 265,
RBAC_PERM_COMMAND_ACCOUNT_SET_SEC_REGMAIL = 266,
RBAC_PERM_COMMAND_CAST = 267,
@@ -178,11 +184,11 @@ enum RBACPermissions
RBAC_PERM_COMMAND_CAST_SELF = 270,
RBAC_PERM_COMMAND_CAST_TARGET = 271,
RBAC_PERM_COMMAND_CAST_DEST = 272,
- RBAC_PERM_COMMAND_CHARACTER = 273,
+ // 273 previously used, do not reuse
RBAC_PERM_COMMAND_CHARACTER_CUSTOMIZE = 274,
RBAC_PERM_COMMAND_CHARACTER_CHANGEFACTION = 275,
RBAC_PERM_COMMAND_CHARACTER_CHANGERACE = 276,
- RBAC_PERM_COMMAND_CHARACTER_DELETED = 277,
+ // 277 previously used, do not reuse
RBAC_PERM_COMMAND_CHARACTER_DELETED_DELETE = 278,
RBAC_PERM_COMMAND_CHARACTER_DELETED_LIST = 279,
RBAC_PERM_COMMAND_CHARACTER_DELETED_RESTORE = 280,
@@ -193,10 +199,10 @@ enum RBACPermissions
RBAC_PERM_COMMAND_CHARACTER_REPUTATION = 285,
RBAC_PERM_COMMAND_CHARACTER_TITLES = 286,
RBAC_PERM_COMMAND_LEVELUP = 287,
- RBAC_PERM_COMMAND_PDUMP = 288,
+ // 288 previously used, do not reuse
RBAC_PERM_COMMAND_PDUMP_LOAD = 289,
RBAC_PERM_COMMAND_PDUMP_WRITE = 290,
- RBAC_PERM_COMMAND_CHEAT = 291,
+ // 291 previously used, do not reuse
RBAC_PERM_COMMAND_CHEAT_CASTTIME = 292,
RBAC_PERM_COMMAND_CHEAT_COOLDOWN = 293,
RBAC_PERM_COMMAND_CHEAT_EXPLORE = 294,
@@ -206,55 +212,13 @@ enum RBACPermissions
RBAC_PERM_COMMAND_CHEAT_TAXI = 298,
RBAC_PERM_COMMAND_CHEAT_WATERWALK = 299,
RBAC_PERM_COMMAND_DEBUG = 300,
- RBAC_PERM_COMMAND_DEBUG_ANIM = 301,
- RBAC_PERM_COMMAND_DEBUG_AREATRIGGERS = 302,
- RBAC_PERM_COMMAND_DEBUG_ARENA = 303,
- RBAC_PERM_COMMAND_DEBUG_BG = 304,
- RBAC_PERM_COMMAND_DEBUG_ENTERVEHICLE = 305,
- RBAC_PERM_COMMAND_DEBUG_GETITEMSTATE = 306,
- RBAC_PERM_COMMAND_DEBUG_GETITEMVALUE = 307, // DEPRECATED: DON'T REUSE
- RBAC_PERM_COMMAND_DEBUG_GETVALUE = 308, // DEPRECATED: DON'T REUSE
- RBAC_PERM_COMMAND_DEBUG_COMBAT = 309,
- RBAC_PERM_COMMAND_DEBUG_ITEMEXPIRE = 310,
- RBAC_PERM_COMMAND_DEBUG_LOOTRECIPIENT = 311,
- RBAC_PERM_COMMAND_DEBUG_LOS = 312,
- RBAC_PERM_COMMAND_DEBUG_MOD32VALUE = 313, // DEPRECATED: DON'T REUSE
- RBAC_PERM_COMMAND_DEBUG_MOVEFLAGS = 314,
- RBAC_PERM_COMMAND_DEBUG_PLAY = 315,
- RBAC_PERM_COMMAND_DEBUG_PLAY_CINEMATIC = 316,
- RBAC_PERM_COMMAND_DEBUG_PLAY_MOVIE = 317,
- RBAC_PERM_COMMAND_DEBUG_PLAY_SOUND = 318,
- RBAC_PERM_COMMAND_DEBUG_SEND = 319,
- RBAC_PERM_COMMAND_DEBUG_SEND_BUYERROR = 320,
- RBAC_PERM_COMMAND_DEBUG_SEND_CHANNELNOTIFY = 321,
- RBAC_PERM_COMMAND_DEBUG_SEND_CHATMESSAGE = 322,
- RBAC_PERM_COMMAND_DEBUG_SEND_EQUIPERROR = 323,
- RBAC_PERM_COMMAND_DEBUG_SEND_LARGEPACKET = 324,
- RBAC_PERM_COMMAND_DEBUG_SEND_OPCODE = 325,
- RBAC_PERM_COMMAND_DEBUG_SEND_QINVALIDMSG = 326,
- RBAC_PERM_COMMAND_DEBUG_SEND_QPARTYMSG = 327,
- RBAC_PERM_COMMAND_DEBUG_SEND_SELLERROR = 328,
- RBAC_PERM_COMMAND_DEBUG_SEND_SETPHASESHIFT = 329,
- RBAC_PERM_COMMAND_DEBUG_SEND_SPELLFAIL = 330,
- RBAC_PERM_COMMAND_DEBUG_SETAURASTATE = 331,
- RBAC_PERM_COMMAND_DEBUG_SETBIT = 332, // DEPRECATED: DON'T REUSE
- RBAC_PERM_COMMAND_DEBUG_SETITEMVALUE = 333, // DEPRECATED: DON'T REUSE
- RBAC_PERM_COMMAND_DEBUG_SETVALUE = 334, // DEPRECATED: DON'T REUSE
- RBAC_PERM_COMMAND_DEBUG_SETVID = 335,
- RBAC_PERM_COMMAND_DEBUG_SPAWNVEHICLE = 336,
- RBAC_PERM_COMMAND_DEBUG_THREAT = 337,
- RBAC_PERM_COMMAND_DEBUG_UPDATE = 338, // DEPRECATED: DON'T REUSE
- RBAC_PERM_COMMAND_DEBUG_WORLDSTATE = 339,
- RBAC_PERM_COMMAND_WPGPS = 340,
- RBAC_PERM_COMMAND_DESERTER = 341,
- RBAC_PERM_COMMAND_DESERTER_BG = 342,
+ // 301-342 previously used, do not reuse
RBAC_PERM_COMMAND_DESERTER_BG_ADD = 343,
RBAC_PERM_COMMAND_DESERTER_BG_REMOVE = 344,
- RBAC_PERM_COMMAND_DESERTER_INSTANCE = 345,
+ // 345 previously used, do not reuse
RBAC_PERM_COMMAND_DESERTER_INSTANCE_ADD = 346,
RBAC_PERM_COMMAND_DESERTER_INSTANCE_REMOVE = 347,
- RBAC_PERM_COMMAND_DISABLE = 348,
- RBAC_PERM_COMMAND_DISABLE_ADD = 349,
+ // 348-349 previously used, do not reuse
RBAC_PERM_COMMAND_DISABLE_ADD_CRITERIA = 350,
RBAC_PERM_COMMAND_DISABLE_ADD_BATTLEGROUND = 351,
RBAC_PERM_COMMAND_DISABLE_ADD_MAP = 352,
@@ -263,7 +227,7 @@ enum RBACPermissions
RBAC_PERM_COMMAND_DISABLE_ADD_QUEST = 355,
RBAC_PERM_COMMAND_DISABLE_ADD_SPELL = 356,
RBAC_PERM_COMMAND_DISABLE_ADD_VMAP = 357,
- RBAC_PERM_COMMAND_DISABLE_REMOVE = 358,
+ // 358 previously used, do not reuse
RBAC_PERM_COMMAND_DISABLE_REMOVE_CRITERIA = 359,
RBAC_PERM_COMMAND_DISABLE_REMOVE_BATTLEGROUND = 360,
RBAC_PERM_COMMAND_DISABLE_REMOVE_MAP = 361,
@@ -272,7 +236,7 @@ enum RBACPermissions
RBAC_PERM_COMMAND_DISABLE_REMOVE_QUEST = 364,
RBAC_PERM_COMMAND_DISABLE_REMOVE_SPELL = 365,
RBAC_PERM_COMMAND_DISABLE_REMOVE_VMAP = 366,
- RBAC_PERM_COMMAND_EVENT = 367,
+ RBAC_PERM_COMMAND_EVENT_INFO = 367,
RBAC_PERM_COMMAND_EVENT_ACTIVELIST = 368,
RBAC_PERM_COMMAND_EVENT_START = 369,
RBAC_PERM_COMMAND_EVENT_STOP = 370,
@@ -292,7 +256,7 @@ enum RBACPermissions
// = 384, // DEPRECATED: DON'T REUSE
// = 385, // DEPRECATED: DON'T REUSE
// = 386, // DEPRECATED: DON'T REUSE
- RBAC_PERM_COMMAND_GOBJECT = 387,
+ // 387 previously used, do not reuse
RBAC_PERM_COMMAND_GOBJECT_ACTIVATE = 388,
RBAC_PERM_COMMAND_GOBJECT_ADD = 389,
RBAC_PERM_COMMAND_GOBJECT_ADD_TEMP = 390,
@@ -300,12 +264,12 @@ enum RBACPermissions
RBAC_PERM_COMMAND_GOBJECT_INFO = 392,
RBAC_PERM_COMMAND_GOBJECT_MOVE = 393,
RBAC_PERM_COMMAND_GOBJECT_NEAR = 394,
- RBAC_PERM_COMMAND_GOBJECT_SET = 395,
+ // 395 previously used, do not reuse
RBAC_PERM_COMMAND_GOBJECT_SET_PHASE = 396,
RBAC_PERM_COMMAND_GOBJECT_SET_STATE = 397,
RBAC_PERM_COMMAND_GOBJECT_TARGET = 398,
RBAC_PERM_COMMAND_GOBJECT_TURN = 399,
- RBAC_PERM_COMMAND_DEBUG_TRANSPORT = 400,
+ // 400 previously used, do not reuse
RBAC_PERM_COMMAND_GUILD = 401,
RBAC_PERM_COMMAND_GUILD_CREATE = 402,
RBAC_PERM_COMMAND_GUILD_DELETE = 403,
@@ -323,7 +287,7 @@ enum RBACPermissions
RBAC_PERM_COMMAND_INSTANCE_STATS = 415,
RBAC_PERM_COMMAND_INSTANCE_SAVEDATA = 416,
RBAC_PERM_COMMAND_LEARN = 417,
- RBAC_PERM_COMMAND_LEARN_ALL = 418,
+ // 418 previously used, do not reuse
RBAC_PERM_COMMAND_LEARN_ALL_MY = 419,
RBAC_PERM_COMMAND_LEARN_ALL_MY_CLASS = 420,
RBAC_PERM_COMMAND_LEARN_ALL_MY_PETTALENTS = 421,
@@ -335,13 +299,13 @@ enum RBACPermissions
RBAC_PERM_COMMAND_LEARN_ALL_LANG = 427,
RBAC_PERM_COMMAND_LEARN_ALL_RECIPES = 428,
RBAC_PERM_COMMAND_UNLEARN = 429,
- RBAC_PERM_COMMAND_LFG = 430,
+ // 430 previously used, do not reuse
RBAC_PERM_COMMAND_LFG_PLAYER = 431,
RBAC_PERM_COMMAND_LFG_GROUP = 432,
RBAC_PERM_COMMAND_LFG_QUEUE = 433,
RBAC_PERM_COMMAND_LFG_CLEAN = 434,
RBAC_PERM_COMMAND_LFG_OPTIONS = 435,
- RBAC_PERM_COMMAND_LIST = 436,
+ // 436 previously used, do not reuse
RBAC_PERM_COMMAND_LIST_CREATURE = 437,
RBAC_PERM_COMMAND_LIST_ITEM = 438,
RBAC_PERM_COMMAND_LIST_OBJECT = 439,
@@ -475,7 +439,7 @@ enum RBACPermissions
RBAC_PERM_COMMAND_MODIFY_SPELL = 567,
RBAC_PERM_COMMAND_MODIFY_STANDSTATE = 568,
RBAC_PERM_COMMAND_MODIFY_TALENTPOINTS = 569,
- RBAC_PERM_COMMAND_NPC = 570,
+ // 570 previously used, do not reuse
RBAC_PERM_COMMAND_NPC_ADD = 571,
RBAC_PERM_COMMAND_NPC_ADD_FORMATION = 572,
RBAC_PERM_COMMAND_NPC_ADD_ITEM = 573,
@@ -610,7 +574,7 @@ enum RBACPermissions
RBAC_PERM_COMMAND_RELOAD_SPELL_THREATS = 702,
RBAC_PERM_COMMAND_RELOAD_SPELL_GROUP_STACK_RULES = 703,
RBAC_PERM_COMMAND_RELOAD_TRINITY_STRING = 704,
- // 705 unused
+ // 705 previously used, do not reuse
RBAC_PERM_COMMAND_RELOAD_WAYPOINT_SCRIPTS = 706,
RBAC_PERM_COMMAND_RELOAD_WAYPOINT_DATA = 707,
RBAC_PERM_COMMAND_RELOAD_VEHICLE_ACCESORY = 708,
@@ -681,9 +645,9 @@ enum RBACPermissions
RBAC_PERM_COMMAND_WP_RELOAD = 773,
RBAC_PERM_COMMAND_WP_SHOW = 774,
RBAC_PERM_COMMAND_MODIFY_CURRENCY = 775,
- RBAC_PERM_COMMAND_DEBUG_PHASE = 776,
+ // 776 previously used, do not reuse
RBAC_PERM_COMMAND_MAILBOX = 777,
- RBAC_PERM_COMMAND_AHBOT = 778,
+ // 778 previously used, do not reuse
RBAC_PERM_COMMAND_AHBOT_ITEMS = 779,
RBAC_PERM_COMMAND_AHBOT_ITEMS_GRAY = 780,
RBAC_PERM_COMMAND_AHBOT_ITEMS_WHITE = 781,
@@ -740,8 +704,7 @@ enum RBACPermissions
RBAC_PERM_COMMAND_TICKET_RESET_COMPLAINT = 832,
RBAC_PERM_COMMAND_TICKET_RESET_SUGGESTION = 833,
// = 834, // DEPRECATED: DON'T REUSE
- RBAC_PERM_COMMAND_DEBUG_LOADCELLS = 835,
- RBAC_PERM_COMMAND_DEBUG_BOUNDARY = 836,
+ // 835-836 previously used, do not reuse
RBAC_PERM_COMMAND_NPC_EVADE = 837,
RBAC_PERM_COMMAND_PET_LEVEL = 838,
RBAC_PERM_COMMAND_SERVER_SHUTDOWN_FORCE = 839,
@@ -757,10 +720,9 @@ enum RBACPermissions
RBAC_PERM_COMMAND_LIST_SCENES = 849,
RBAC_PERM_COMMAND_RELOAD_SCENE_TEMPLATE = 850,
RBAC_PERM_COMMAND_RELOAD_AREATRIGGER_TEMPLATE = 851,
- RBAC_PERM_COMMAND_DEBUG_DUMMY = 852,
+ // 852 previously used, do not reuse
RBAC_PERM_COMMAND_RELOAD_CONVERSATION_TEMPLATE = 853,
- RBAC_PERM_COMMAND_DEBUG_CONVERSATION = 854,
- RBAC_PERM_COMMAND_DEBUG_PLAY_MUSIC = 855,
+ // 854-855 previously used, do not reuse
RBAC_PERM_COMMAND_NPC_SPAWNGROUP = 856,
RBAC_PERM_COMMAND_NPC_DESPAWNGROUP = 857,
RBAC_PERM_COMMAND_GOBJECT_SPAWNGROUP = 858,
@@ -774,17 +736,15 @@ enum RBACPermissions
RBAC_PERM_COMMAND_LIST_SPAWNPOINTS = 866,
RBAC_PERM_COMMAND_RELOAD_QUEST_GREETING_LOCALE = 867, // reserved
RBAC_PERM_COMMAND_MODIFY_POWER = 868,
- RBAC_PERM_COMMAND_DEBUG_SEND_PLAYER_CHOICE = 869,
- RBAC_PERM_COMMAND_DEBUG_THREATINFO = 870,
- RBAC_PERM_COMMAND_DEBUG_INSTANCESPAWN = 871,
+ // 869 previously used, do not reuse
+ // 870-871 previously used, do not reuse
RBAC_PERM_COMMAND_SERVER_DEBUG = 872,
RBAC_PERM_COMMAND_RELOAD_CREATURE_MOVEMENT_OVERRIDE = 873,
- RBAC_PERM_COMMAND_DEBUG_ASAN = 874,
+ // 874 previously used, do not reuse
RBAC_PERM_COMMAND_LOOKUP_MAP_ID = 875,
RBAC_PERM_COMMAND_LOOKUP_ITEM_ID = 876,
RBAC_PERM_COMMAND_LOOKUP_QUEST_ID = 877,
- RBAC_PERM_COMMAND_DEBUG_QUESTRESET = 878,
- RBAC_PERM_COMMAND_DEBUG_POOLSTATUS = 879,
+ // 878-879 previously used, do not reuse
RBAC_PERM_COMMAND_PDUMP_COPY = 880,
RBAC_PERM_COMMAND_RELOAD_VEHICLE_TEMPLATE = 881,
RBAC_PERM_COMMAND_RELOAD_SPELL_SCRIPT_NAMES = 882,
diff --git a/src/server/game/Chat/Chat.cpp b/src/server/game/Chat/Chat.cpp
index 708178e9ab6..59487cde650 100644
--- a/src/server/game/Chat/Chat.cpp
+++ b/src/server/game/Chat/Chat.cpp
@@ -39,7 +39,7 @@
#include <boost/algorithm/string/replace.hpp>
#include <sstream>
-Player* ChatHandler::GetPlayer() { return m_session ? m_session->GetPlayer() : nullptr; }
+Player* ChatHandler::GetPlayer() const { return m_session ? m_session->GetPlayer() : nullptr; }
char* ChatHandler::LineFromMessage(char*& pos)
{
@@ -48,56 +48,11 @@ char* ChatHandler::LineFromMessage(char*& pos)
return start;
}
-// Lazy loading of the command table cache from commands and the
-// ScriptMgr should be thread safe since the player commands,
-// cli commands and ScriptMgr updates are all dispatched one after
-// one inside the world update loop.
-static Optional<std::vector<ChatCommand>> commandTableCache;
-
-std::vector<ChatCommand> const& ChatHandler::getCommandTable()
-{
- if (!commandTableCache)
- InitializeCommandTable();
-
- return *commandTableCache;
-}
-
-void ChatHandler::InitializeCommandTable()
-{
- // We need to initialize this at top since SetDataForCommandInTable
- // calls getCommandTable() recursively.
- commandTableCache = sScriptMgr->GetChatCommands();
-
- WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_SEL_COMMANDS);
- PreparedQueryResult result = WorldDatabase.Query(stmt);
- if (result)
- {
- do
- {
- Field* fields = result->Fetch();
- std::string name = fields[0].GetString();
-
- SetDataForCommandInTable(*commandTableCache, name.c_str(), fields[1].GetUInt16(), fields[2].GetString(), name);
- }
- while (result->NextRow());
- }
-}
-
-void ChatHandler::invalidateCommandTable()
-{
- commandTableCache.reset();
-}
-
char const* ChatHandler::GetTrinityString(uint32 entry) const
{
return m_session->GetTrinityString(entry);
}
-bool ChatHandler::isAvailable(ChatCommand const& cmd) const
-{
- return HasPermission(cmd.Permission);
-}
-
bool ChatHandler::HasPermission(uint32 permission) const
{
return m_session->HasPermission(permission);
@@ -158,31 +113,6 @@ bool ChatHandler::HasLowerSecurityAccount(WorldSession* target, uint32 target_ac
return false;
}
-bool ChatHandler::hasStringAbbr(const char* name, const char* part)
-{
- // non "" command
- if (*name)
- {
- // "" part from non-"" command
- if (!*part)
- return false;
-
- while (true)
- {
- if (!*part || *part == ' ')
- return true;
- else if (!*name)
- return false;
- else if (tolower(*name) != tolower(*part))
- return false;
- ++name; ++part;
- }
- }
- // allow with any for ""
-
- return true;
-}
-
void ChatHandler::SendSysMessage(std::string_view str, bool escapeCharacters)
{
std::string msg{ str };
@@ -232,171 +162,9 @@ void ChatHandler::SendSysMessage(uint32 entry)
SendSysMessage(GetTrinityString(entry));
}
-bool ChatHandler::ExecuteCommandInTable(std::vector<ChatCommand> const& table, const char* text, std::string const& fullcmd)
+bool ChatHandler::_ParseCommands(std::string_view text)
{
- char const* oldtext = text;
- std::string cmd = "";
-
- while (*text != ' ' && *text != '\0')
- {
- cmd += *text;
- ++text;
- }
-
- while (*text == ' ') ++text;
-
- for (uint32 i = 0; i < table.size(); ++i)
- {
- if (!hasStringAbbr(table[i].Name, cmd.c_str()))
- continue;
-
- bool match = false;
- if (strlen(table[i].Name) > cmd.length())
- {
- for (uint32 j = 0; j < table.size(); ++j)
- {
- if (!hasStringAbbr(table[j].Name, cmd.c_str()))
- continue;
-
- if (strcmp(table[j].Name, cmd.c_str()) == 0)
- {
- match = true;
- break;
- }
- }
- }
- if (match)
- continue;
-
- // select subcommand from child commands list
- if (!table[i].ChildCommands.empty())
- {
- if (!ExecuteCommandInTable(table[i].ChildCommands, text, fullcmd))
- {
- if (m_session && !m_session->HasPermission(rbac::RBAC_PERM_COMMANDS_NOTIFY_COMMAND_NOT_FOUND_ERROR))
- return false;
-
- if (text[0] != '\0')
- SendSysMessage(LANG_NO_SUBCMD);
- else
- SendSysMessage(LANG_CMD_SYNTAX);
-
- ShowHelpForCommand(table[i].ChildCommands, text);
- }
-
- return true;
- }
-
- // must be available and have handler
- if (!table[i].HasHandler() || !isAvailable(table[i]))
- continue;
-
- SetSentErrorMessage(false);
- // table[i].Name == "" is special case: send original command to handler
- if (table[i](this, table[i].Name[0] != '\0' ? text : oldtext))
- {
- if (!m_session) // ignore console
- return true;
-
- Player* player = m_session->GetPlayer();
- if (!AccountMgr::IsPlayerAccount(m_session->GetSecurity()))
- {
- ObjectGuid guid = player->GetTarget();
- uint32 areaId = player->GetAreaId();
- std::string areaName = "Unknown";
- std::string zoneName = "Unknown";
- if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(areaId))
- {
- LocaleConstant locale = GetSessionDbcLocale();
- areaName = area->AreaName[locale];
- if (AreaTableEntry const* zone = sAreaTableStore.LookupEntry(area->ParentAreaID))
- zoneName = zone->AreaName[locale];
- }
-
- sLog->outCommand(m_session->GetAccountId(), "Command: %s [Player: %s (%s) (Account: %u) X: %f Y: %f Z: %f Map: %u (%s) Area: %u (%s) Zone: %s Selected: %s (%s)]",
- fullcmd.c_str(), player->GetName().c_str(), player->GetGUID().ToString().c_str(),
- m_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() : "",
- guid.ToString().c_str());
- }
- }
- // some commands have custom error messages. Don't send the default one in these cases.
- else if (!HasSentErrorMessage())
- {
- if (!table[i].Help.empty())
- SendSysMessage(table[i].Help.c_str());
- else
- SendSysMessage(LANG_CMD_SYNTAX);
- SetSentErrorMessage(true);
- }
-
- return true;
- }
-
- return false;
-}
-
-bool ChatHandler::SetDataForCommandInTable(std::vector<ChatCommand>& table, char const* text, uint32 permission, std::string const& help, std::string const& fullcommand)
-{
- std::string cmd = "";
-
- while (*text != ' ' && *text != '\0')
- {
- cmd += *text;
- ++text;
- }
-
- while (*text == ' ') ++text;
-
- for (uint32 i = 0; i < table.size(); i++)
- {
- // for data fill use full explicit command names
- if (table[i].Name != cmd)
- continue;
-
- // select subcommand from child commands list (including "")
- if (!table[i].ChildCommands.empty())
- {
- if (SetDataForCommandInTable(table[i].ChildCommands, text, permission, help, fullcommand))
- return true;
- else if (*text)
- return false;
-
- // fail with "" subcommands, then use normal level up command instead
- }
- // expected subcommand by full name DB content
- else if (*text)
- {
- TC_LOG_ERROR("sql.sql", "Table `command` contains an unexpected subcommand '%s' in command '%s', skipped.", text, fullcommand.c_str());
- return false;
- }
-
- if (table[i].Permission != permission)
- TC_LOG_INFO("misc", "Table `command` overwrite for command '%s' default permission (%u) by %u", fullcommand.c_str(), table[i].Permission, permission);
-
- table[i].Permission = permission;
- table[i].Help = help;
- return true;
- }
-
- // in case "" command let process by caller
- if (!cmd.empty())
- {
- if (&table == &getCommandTable())
- TC_LOG_ERROR("sql.sql", "Table `command` contains a non-existing command '%s', skipped.", cmd.c_str());
- else
- TC_LOG_ERROR("sql.sql", "Table `command` contains a non-existing subcommand '%s' in command '%s', skipped.", cmd.c_str(), fullcommand.c_str());
- }
-
- return false;
-}
-
-bool ChatHandler::_ParseCommands(char const* text)
-{
- if (ExecuteCommandInTable(getCommandTable(), text, text))
+ if (Trinity::ChatCommands::TryExecuteCommand(*this, text))
return true;
// Pretend commands don't exist for regular players
@@ -404,126 +172,32 @@ bool ChatHandler::_ParseCommands(char const* text)
return false;
// Send error message for GMs
- SendSysMessage(LANG_NO_CMD);
+ PSendSysMessage(LANG_CMD_INVALID, STRING_VIEW_FMT_ARG(text));
SetSentErrorMessage(true);
return true;
}
-bool ChatHandler::ParseCommands(char const* text)
+bool ChatHandler::ParseCommands(std::string_view text)
{
- ASSERT(text);
- ASSERT(*text);
+ ASSERT(!text.empty());
- /// chat case (.command or !command format)
- if (text[0] != '!' && text[0] != '.')
+ // chat case (.command or !command format)
+ if ((text[0] != '!') && (text[0] != '.'))
return false;
- /// ignore single . and ! in line
- if (!text[1])
+ // ignore single . and ! in line
+ if (text.length() < 2)
return false;
- /// ignore messages staring from many dots.
- if (text[1] == '!' || text[1] == '.')
+ // ignore messages staring from many dots.
+ if (text[1] == text[0])
return false;
- return _ParseCommands(text+1);
-}
-
-bool ChatHandler::ShowHelpForSubCommands(std::vector<ChatCommand> const& table, char const* cmd, char const* subcmd)
-{
- std::string list;
- for (uint32 i = 0; i < table.size(); ++i)
- {
- // must be available (ignore handler existence to show command with possible available subcommands)
- if (!isAvailable(table[i]))
- continue;
-
- // for empty subcmd show all available
- if (*subcmd && !hasStringAbbr(table[i].Name, subcmd))
- continue;
-
- if (m_session)
- list += "\n ";
- else
- list += "\n\r ";
-
- list += table[i].Name;
-
- if (!table[i].ChildCommands.empty())
- list += " ...";
- }
-
- if (list.empty())
+ // ignore messages with separator after .
+ if (text[1] == Trinity::Impl::ChatCommands::COMMAND_DELIMITER)
return false;
- if (&table == &getCommandTable())
- {
- SendSysMessage(LANG_AVIABLE_CMD);
- PSendSysMessage("%s", list.c_str());
- }
- else
- PSendSysMessage(LANG_SUBCMDS_LIST, cmd, list.c_str());
-
- return true;
-}
-
-bool ChatHandler::ShowHelpForCommand(std::vector<ChatCommand> const& table, const char* cmd)
-{
- if (*cmd)
- {
- std::string subcmd;
- if (size_t n = std::string_view(cmd).find(' '); n != std::string_view::npos)
- subcmd.assign(cmd+n+1);
-
- for (uint32 i = 0; i < table.size(); ++i)
- {
- // must be available (ignore handler existence to show command with possible available subcommands)
- if (!isAvailable(table[i]))
- continue;
-
- if (!hasStringAbbr(table[i].Name, cmd))
- continue;
-
- // have subcommand
- if (!table[i].ChildCommands.empty() && !subcmd.empty())
- {
- if (ShowHelpForCommand(table[i].ChildCommands, subcmd.c_str()))
- return true;
- }
-
- if (!table[i].Help.empty())
- SendSysMessage(table[i].Help.c_str());
-
- if (!table[i].ChildCommands.empty())
- if (ShowHelpForSubCommands(table[i].ChildCommands, table[i].Name, subcmd.c_str()))
- return true;
-
- return !table[i].Help.empty();
- }
- }
- else
- {
- for (uint32 i = 0; i < table.size(); ++i)
- {
- // must be available (ignore handler existence to show command with possible available subcommands)
- if (!isAvailable(table[i]))
- continue;
-
- if (strlen(table[i].Name))
- continue;
-
- if (!table[i].Help.empty())
- SendSysMessage(table[i].Help.c_str());
-
- if (!table[i].ChildCommands.empty())
- if (ShowHelpForSubCommands(table[i].ChildCommands, "", ""))
- return true;
-
- return !table[i].Help.empty();
- }
- }
-
- return ShowHelpForSubCommands(table, "", cmd);
+ return _ParseCommands(text.substr(1));
}
Player* ChatHandler::getSelectedPlayer()
@@ -1005,25 +679,19 @@ char const* CliHandler::GetTrinityString(uint32 entry) const
return sObjectMgr->GetTrinityStringForDBCLocale(entry);
}
-bool CliHandler::isAvailable(ChatCommand const& cmd) const
-{
- // skip non-console commands in console case
- return cmd.AllowConsole;
-}
-
void CliHandler::SendSysMessage(std::string_view str, bool /*escapeCharacters*/)
{
m_print(m_callbackArg, str);
m_print(m_callbackArg, "\r\n");
}
-bool CliHandler::ParseCommands(char const* str)
+bool CliHandler::ParseCommands(std::string_view str)
{
- if (!str[0])
+ if (str.empty())
return false;
// Console allows using commands both with and without leading indicator
if (str[0] == '.' || str[0] == '!')
- ++str;
+ str = str.substr(1);
return _ParseCommands(str);
}
@@ -1093,14 +761,12 @@ LocaleConstant CliHandler::GetSessionDbLocaleIndex() const
std::string const AddonChannelCommandHandler::PREFIX = "TrinityCore";
-bool AddonChannelCommandHandler::ParseCommands(char const* str)
+bool AddonChannelCommandHandler::ParseCommands(std::string_view str)
{
- char opcode = str[0];
- if (!opcode) // str[0] is opcode
+ if (str.length() < 5)
return false;
- if (!str[1] || !str[2] || !str[3] || !str[4]) // str[1] through str[4] is 4-character command counter
- return false;
- echo = str+1;
+ char opcode = str[0];
+ echo = &str[1];
switch (opcode)
{
@@ -1109,10 +775,12 @@ bool AddonChannelCommandHandler::ParseCommands(char const* str)
return true;
case 'h': // h Issue human-readable command
case 'i': // i Issue command
+ {
if (!str[5])
return false;
humanReadable = (opcode == 'h');
- if (_ParseCommands(str + 5)) // actual command starts at str[5]
+ std::string_view cmd = str.substr(5);
+ if (_ParseCommands(cmd)) // actual command starts at str[5]
{
if (!hadAck)
SendAck();
@@ -1123,10 +791,11 @@ bool AddonChannelCommandHandler::ParseCommands(char const* str)
}
else
{
- SendSysMessage(LANG_NO_CMD);
+ PSendSysMessage(LANG_CMD_INVALID, STRING_VIEW_FMT_ARG(cmd));
SendFailed();
}
return true;
+ }
default:
return false;
}
diff --git a/src/server/game/Chat/Chat.h b/src/server/game/Chat/Chat.h
index edb5858ac7c..5af87f472e9 100644
--- a/src/server/game/Chat/Chat.h
+++ b/src/server/game/Chat/Chat.h
@@ -20,9 +20,7 @@
#include "ObjectGuid.h"
#include "StringFormat.h"
-#include <vector>
-class ChatCommand;
class ChatHandler;
class Creature;
class GameObject;
@@ -39,8 +37,10 @@ enum LocaleConstant : uint8;
class TC_GAME_API ChatHandler
{
public:
+ bool IsConsole() const { return (m_session == nullptr); }
WorldSession* GetSession() { return m_session; }
- Player* GetPlayer();
+ WorldSession const* GetSession() const { return m_session; }
+ Player* GetPlayer() const;
explicit ChatHandler(WorldSession* session) : m_session(session), sentErrorMessage(false) { }
virtual ~ChatHandler() { }
@@ -70,19 +70,12 @@ class TC_GAME_API ChatHandler
return Trinity::StringFormat(GetTrinityString(entry), std::forward<Args>(args)...);
}
- bool _ParseCommands(char const* text);
- virtual bool ParseCommands(char const* text);
-
- static std::vector<ChatCommand> const& getCommandTable();
- static void InitializeCommandTable();
- static void invalidateCommandTable();
+ bool _ParseCommands(std::string_view text);
+ virtual bool ParseCommands(std::string_view text);
void SendGlobalSysMessage(const char *str);
- bool hasStringAbbr(const char* name, const char* part);
-
// function with different implementation for chat/console
- virtual bool isAvailable(ChatCommand const& cmd) const;
virtual bool IsHumanReadable() const { return true; }
virtual bool HasPermission(uint32 permission) const;
virtual std::string GetNameLink() const;
@@ -119,13 +112,8 @@ class TC_GAME_API ChatHandler
Creature* GetCreatureFromPlayerMapByDbGuid(ObjectGuid::LowType lowguid);
bool HasSentErrorMessage() const { return sentErrorMessage; }
void SetSentErrorMessage(bool val){ sentErrorMessage = val; }
-
- bool ShowHelpForCommand(std::vector<ChatCommand> const& table, const char* cmd);
protected:
explicit ChatHandler() : m_session(nullptr), sentErrorMessage(false) { } // for CLI subclass
- static bool SetDataForCommandInTable(std::vector<ChatCommand>& table, const char* text, uint32 permission, std::string const& help, std::string const& fullcommand);
- bool ExecuteCommandInTable(std::vector<ChatCommand> const& table, const char* text, std::string const& fullcmd);
- bool ShowHelpForSubCommands(std::vector<ChatCommand> const& table, char const* cmd, char const* subcmd);
private:
WorldSession* m_session; // != nullptr for chat command call and nullptr for CLI command
@@ -142,10 +130,9 @@ class TC_GAME_API CliHandler : public ChatHandler
// overwrite functions
char const* GetTrinityString(uint32 entry) const override;
- bool isAvailable(ChatCommand const& cmd) const override;
bool HasPermission(uint32 /*permission*/) const override { return true; }
void SendSysMessage(std::string_view, bool escapeCharacters) override;
- bool ParseCommands(char const* str) override;
+ bool ParseCommands(std::string_view str) override;
std::string GetNameLink() const override;
bool needReportToTarget(Player* chr) const override;
LocaleConstant GetSessionDbcLocale() const override;
@@ -162,7 +149,7 @@ class TC_GAME_API AddonChannelCommandHandler : public ChatHandler
static std::string const PREFIX;
using ChatHandler::ChatHandler;
- bool ParseCommands(char const* str) override;
+ bool ParseCommands(std::string_view str) override;
void SendSysMessage(std::string_view, bool escapeCharacters) override;
using ChatHandler::SendSysMessage;
bool IsHumanReadable() const override { return humanReadable; }
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
diff --git a/src/server/game/Miscellaneous/Language.h b/src/server/game/Miscellaneous/Language.h
index 99c4342a3bb..2ff3bed0043 100644
--- a/src/server/game/Miscellaneous/Language.h
+++ b/src/server/game/Miscellaneous/Language.h
@@ -35,10 +35,10 @@ enum TrinityStrings
LANG_SYSTEMMESSAGE = 3,
LANG_EVENTMESSAGE = 4,
LANG_NO_HELP_CMD = 5,
- LANG_NO_CMD = 6,
- LANG_NO_SUBCMD = 7,
+ LANG_CMD_INVALID = 6,
+ LANG_SUBCMD_AMBIGUOUS = 7,
LANG_SUBCMDS_LIST = 8,
- LANG_AVIABLE_CMD = 9,
+ LANG_AVAILABLE_CMDS = 9,
LANG_CMD_SYNTAX = 10,
LANG_ACCOUNT_LEVEL = 11,
LANG_CONNECTED_USERS = 12,
@@ -232,10 +232,10 @@ enum TrinityStrings
LANG_2FA_SECRET_TOO_LONG = 188,
LANG_2FA_SECRET_INVALID = 189,
LANG_2FA_SECRET_SET_COMPLETE = 190,
- LANG_SUBCMDS_LIST_ENTRY = 191, // 3.3.5 RESERVED
- LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS = 192, // 3.3.5 RESERVED
- LANG_SUBCMD_INVALID = 193, // 3.3.5 RESERVED
- LANG_CMD_AMBIGUOUS = 194, // 3.3.5 RESERVED
+ LANG_SUBCMDS_LIST_ENTRY = 191,
+ LANG_SUBCMDS_LIST_ENTRY_ELLIPSIS = 192,
+ LANG_SUBCMD_INVALID = 193,
+ LANG_CMD_AMBIGUOUS = 194,
LANG_CMD_HELP_GENERIC = 195, // 3.3.5 RESERVED
LANG_CMD_NO_HELP_AVAILABLE = 196, // 3.3.5 RESERVED
// Room for more level 1 197-199 not used
diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp
index b50f5a21642..30eefe2fb4a 100644
--- a/src/server/game/Scripting/ScriptMgr.cpp
+++ b/src/server/game/Scripting/ScriptMgr.cpp
@@ -19,7 +19,6 @@
#include "AchievementMgr.h"
#include "AreaTrigger.h"
#include "AreaTriggerAI.h"
-#include "Chat.h"
#include "ChatCommand.h"
#include "Conversation.h"
#include "Creature.h"
@@ -1057,17 +1056,17 @@ class ScriptRegistrySwapHooks<CommandScript, Base>
public:
void BeforeReleaseContext(std::string const& /*context*/) final override
{
- ChatHandler::invalidateCommandTable();
+ Trinity::ChatCommands::InvalidateCommandMap();
}
void BeforeSwapContext(bool /*initialize*/) override
{
- ChatHandler::invalidateCommandTable();
+ Trinity::ChatCommands::InvalidateCommandMap();
}
void BeforeUnload() final override
{
- ChatHandler::invalidateCommandTable();
+ Trinity::ChatCommands::InvalidateCommandMap();
}
};
@@ -1753,22 +1752,16 @@ OutdoorPvP* ScriptMgr::CreateOutdoorPvP(uint32 scriptId)
return tmpscript->GetOutdoorPvP();
}
-std::vector<ChatCommand> ScriptMgr::GetChatCommands()
+Trinity::ChatCommands::ChatCommandTable ScriptMgr::GetChatCommands()
{
- std::vector<ChatCommand> table;
+ Trinity::ChatCommands::ChatCommandTable table;
FOR_SCRIPTS_RET(CommandScript, itr, end, table)
{
- std::vector<ChatCommand> cmds = itr->second->GetCommands();
- table.insert(table.end(), cmds.begin(), cmds.end());
+ Trinity::ChatCommands::ChatCommandTable cmds = itr->second->GetCommands();
+ std::move(cmds.begin(), cmds.end(), std::back_inserter(table));
}
- // Sort commands in alphabetical order
- std::sort(table.begin(), table.end(), [](ChatCommand const& a, ChatCommand const& b)
- {
- return strcmp(a.Name, b.Name) < 0;
- });
-
return table;
}
diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h
index 9d420542cae..36e0a2addfc 100644
--- a/src/server/game/Scripting/ScriptMgr.h
+++ b/src/server/game/Scripting/ScriptMgr.h
@@ -34,7 +34,6 @@ class Battlefield;
class Battleground;
class BattlegroundMap;
class Channel;
-class ChatCommand;
class Conversation;
class Creature;
class CreatureAI;
@@ -79,6 +78,8 @@ struct Position;
struct QuestObjective;
struct SceneTemplate;
+namespace Trinity::ChatCommands { struct ChatCommandBuilder; }
+
enum BattlegroundTypeId : uint32;
enum Difficulty : uint8;
enum DuelCompleteType : uint8;
@@ -569,7 +570,7 @@ class TC_GAME_API CommandScript : public ScriptObject
~CommandScript();
// Should return a pointer to a valid command table (ChatCommand array) to be used by ChatHandler.
- virtual std::vector<ChatCommand> GetCommands() const = 0;
+ virtual std::vector<Trinity::ChatCommands::ChatCommandBuilder> GetCommands() const = 0;
};
class TC_GAME_API WeatherScript : public ScriptObject, public UpdatableScript<Weather>
@@ -1149,7 +1150,7 @@ class TC_GAME_API ScriptMgr
public: /* CommandScript */
- std::vector<ChatCommand> GetChatCommands();
+ std::vector<Trinity::ChatCommands::ChatCommandBuilder> GetChatCommands();
public: /* WeatherScript */
diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp
index e9edf05026c..67bd521f23d 100644
--- a/src/server/game/World/World.cpp
+++ b/src/server/game/World/World.cpp
@@ -38,6 +38,7 @@
#include "CharacterDatabaseCleaner.h"
#include "CharacterTemplateDataStore.h"
#include "Chat.h"
+#include "ChatCommand.h"
#include "ChatPackets.h"
#include "Config.h"
#include "ConversationDataStore.h"
@@ -2314,7 +2315,7 @@ void World::SetInitialWorldSettings()
sObjectMgr->InitializeQueriesData(QUERY_DATA_ALL);
TC_LOG_INFO("server.loading", "Initialize commands...");
- ChatHandler::InitializeCommandTable();
+ Trinity::ChatCommands::LoadCommandMap();
///- Initialize game time and timers
TC_LOG_INFO("server.loading", "Initialize game time and timers");