From ffc66fab677486ab810a14a6b5f3a10e90ade40f Mon Sep 17 00:00:00 2001 From: kimden <23140380+kimden@users.noreply.github.com> Date: Mon, 19 Jun 2023 17:19:49 +0300 Subject: [PATCH] Subcommands big update, detailed description below - Allow subcommands - now commands form a tree, and not just an array - Move definitions of commands to commands.xml, that means, only the code for non-trivial commands and a few permissions for commands are hardcoded; otherwise, whether a command exists or who can use it is decided by server host only - Rewrite /commands and /help according to new structure - Split several commands into a public version without arguments for checking the value of the corresponding parameter, and a private version with arguments for editing the value - Add a message when invoking not implemented commands mentioned in commands.xml - Allow reading bitwise OR of bitmask enum values from XML (currently ints cannot be read though) Notes: - If a command has subcommands, it can only be invoked without arguments - The only subcommand of another command can be specified as omit-name="true", then it means it can be found by its name, but its name should not be written when invoking command. This is done to allow using e.g. /troll and /troll 1 at the same time, invoking "looking" and "editing" commands for them (instead of /troll = 1). --- data/commands.xml | 520 ++++++-- src/network/protocols/command_manager.cpp | 1325 +++++++++++++-------- src/network/protocols/command_manager.hpp | 95 +- src/utils/enum_extended_reader.cpp | 39 + src/utils/enum_extended_reader.hpp | 43 + 5 files changed, 1385 insertions(+), 637 deletions(-) create mode 100644 src/utils/enum_extended_reader.cpp create mode 100644 src/utils/enum_extended_reader.hpp diff --git a/data/commands.xml b/data/commands.xml index f899d5f6a0..ec4af51213 100644 --- a/data/commands.xml +++ b/data/commands.xml @@ -1,432 +1,702 @@ + + usage="/config" + permissions-verbose="everyone" + description="Shows game mode and difficulty used right now." + permissions="UP_EVERYONE" + state-scope="SS_ALWAYS" + > + + + usage="/length" + permissions-verbose="everyone" + description="Contains functions related to length of the game. Without arguments, shows the current setting. Arguments can be used only with permissions, check /commands length and /help length (argument) for more details." + permissions="UP_EVERYONE" + state-scope="SS_ALWAYS" + > + + + + + usage="/direction" + permissions-verbose="everyone" + description="Prints the currently set direction of the game. Arguments can be used only with permissions, check /commands direction and /help direction = for more details." + permissions="UP_EVERYONE" + state-scope="SS_ALWAYS" + > + + + usage="/queue" + permissions-verbose="everyone" + description="Contains functions related to track queue. Without arguments, shows the current queue. Arguments can be used only with permissions, check /commands queue and /help queue (argument) for more details." + permissions="UP_EVERYONE" + state-scope="SS_ALWAYS" + > + + + + + + + + + + + + + + + usage="/allowstart" + permissions-verbose="everyone" + description="Shows if the game can start right now." + permissions="UP_EVERYONE" + > + + + usage="/shuffle" + permissions-verbose="everyone" + description="Shows if the Grand Prix grid is shuffled before each race, or it corresponds to the standings (0)." + permissions="UP_EVERYONE" + > + + + usage="/troll" + permissions-verbose="everyone" + description="Shows whether anti-troll system is enabled." + permissions="UP_EVERYONE" + state-scope="SS_ALWAYS" + > + + + usage="/hitmsg" + permissions-verbose="everyone" + description="Shows whether the messages about teammate hits are displayed." + permissions="UP_EVERYONE" + state-scope="SS_ALWAYS" + > + + + usage="/teamhit" + permissions-verbose="everyone" + description="Shows whether the teammate hits are punished ingame." + permissions="UP_EVERYONE" + state-scope="SS_ALWAYS" + > + + + usage="/scoring" + permissions-verbose="everyone" + description="Shows Grand Prix scoring used now." + permissions="UP_EVERYONE" + state-scope="SS_ALWAYS" + > + + + permissions-verbose="everyone; votable" + description="A test command. Deprecated. Try /commands test" + permissions="UP_EVERYONE | PE_VOTED"> + + + + usage="/slots" + permissions-verbose="everyone" + description="Shows the number of playable slots." + permissions="UP_EVERYONE" + state-scope="SS_LOBBY" + > + + + usage="/preserve" + permissions-verbose="everyone" + description="Shows which settings are preserved when all players leave. Possible settings: mode, elim, laps, queue, replay, direction." + permissions="UP_EVERYONE" + > + + diff --git a/src/network/protocols/command_manager.cpp b/src/network/protocols/command_manager.cpp index 20c5f21232..2ea4050723 100644 --- a/src/network/protocols/command_manager.cpp +++ b/src/network/protocols/command_manager.cpp @@ -71,13 +71,42 @@ #include #include #include - +#include + +// ======================================================================== +EnumExtendedReader CommandManager::mode_scope_reader({ + {"MS_DEFAULT", MS_DEFAULT}, + {"MS_SOCCER_TOURNAMENT", MS_SOCCER_TOURNAMENT} +}); +EnumExtendedReader CommandManager::state_scope_reader({ + {"SS_LOBBY", SS_LOBBY}, + {"SS_INGAME", SS_INGAME}, + {"SS_ALWAYS", SS_ALWAYS} +}); +EnumExtendedReader CommandManager::permission_reader({ + {"PE_NONE", PE_NONE}, + {"PE_SPECTATOR", PE_SPECTATOR}, + {"PE_USUAL", PE_USUAL}, + {"PE_CROWNED", PE_CROWNED}, + {"PE_SINGLE", PE_SINGLE}, + {"PE_HAMMER", PE_HAMMER}, + {"PE_CONSOLE", PE_CONSOLE}, + {"PE_VOTED_SPECTATOR", PE_VOTED_SPECTATOR}, + {"PE_VOTED_NORMAL", PE_VOTED_NORMAL}, + {"PE_VOTED", PE_VOTED}, + {"UP_CONSOLE", UP_CONSOLE}, + {"UP_HAMMER", UP_HAMMER}, + {"UP_SINGLE", UP_SINGLE}, + {"UP_CROWNED", UP_CROWNED}, + {"UP_NORMAL", UP_NORMAL}, + {"UP_EVERYONE", UP_EVERYONE} +}); // ======================================================================== CommandManager::FileResource::FileResource(std::string file_name, uint64_t interval) { - m_file_name = file_name; + m_file_name = std::move(file_name); m_interval = interval; m_contents = ""; m_last_invoked = 0; @@ -92,7 +121,7 @@ void CommandManager::FileResource::read() // idk what to do with absolute or relative paths const std::string& path = /*ServerConfig::getConfigDirectory() + "/" + */m_file_name; std::ifstream message(FileUtils::getPortableReadingPath(path)); - std::string answer = ""; + std::string answer; if (message.is_open()) { for (std::string line; std::getline(message, line); ) @@ -126,7 +155,7 @@ CommandManager::AuthResource::AuthResource(std::string secret, std::string serve } // AuthResource::AuthResource // ======================================================================== -std::string CommandManager::AuthResource::get(std::string username) +std::string CommandManager::AuthResource::get(const std::string& username) const { #ifdef ENABLE_CRYPTO_OPENSSL std::string header = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}"; @@ -149,6 +178,17 @@ std::string CommandManager::AuthResource::get(std::string username) } // AuthResource::get // ======================================================================== +CommandManager::Command::Command(std::string name, + void (CommandManager::*f)(Context& context), + int permissions, + int mode_scope, + int state_scope): + m_name(name), m_action(f), m_permissions(permissions), + m_mode_scope(mode_scope), m_state_scope(state_scope) +{ +} // Command::Command(5) +// ======================================================================== + void CommandManager::initCommandsInfo() { const std::string file_name = file_manager->getAsset("commands.xml"); @@ -158,186 +198,257 @@ void CommandManager::initCommandsInfo() root->get("version", &version); if (version != 1) Log::warn("CommandManager", "command.xml has version %d which is not supported", version); - for (unsigned int i = 0; i < num_nodes; i++) - { - const XMLNode *node = root->getNode(i); - std::string node_name = node->getName(); - // here the commands go - std::string name = ""; - std::string text = ""; // for text-command - std::string file = ""; // for file-command - uint64_t interval = 0; // for file-command - std::string usage = ""; - std::string permissions = ""; - std::string description = ""; - std::string aliases = ""; - std::string secret = ""; // for auth-command - std::string link_format = ""; // for auth-command - std::string server = ""; // for auth-command - // If enabled is not empty, command is added iff the server name is in enabled - // Otherwise it is added iff the server name is not in disabled - std::string enabled = ""; - std::string disabled = ""; - node->get("enabled", &enabled); - node->get("disabled", &disabled); - std::vector enabled_split = StringUtils::split(enabled, ' '); - std::vector disabled_split = StringUtils::split(disabled, ' '); - bool ok; - if (!enabled.empty()) - { - ok = false; - for (const std::string& s: enabled_split) - if (s == ServerConfig::m_server_uid) - ok = true; - } - else - { - ok = true; - for (const std::string& s: disabled_split) - if (s == ServerConfig::m_server_uid) - ok = false; - } - if (!ok) - continue; - node->get("name", &name); - node->get("usage", &usage); - node->get("permissions", &permissions); - node->get("description", &description); - node->get("aliases", &aliases); - std::vector aliases_split = StringUtils::split(aliases, ' '); - for (const std::string& alias_name: aliases_split) - m_aliases[name].push_back(alias_name); - - if (node_name == "command") - { - m_config_descriptions[name] = CommandDescription(usage, permissions, description); - } - else if (node_name == "text-command") + std::function command)> dfs = + [&](const XMLNode* current, const std::shared_ptr& command) { + for (unsigned int i = 0; i < current->getNumNodes(); i++) { - node->get("text", &text); - m_commands.emplace_back(name, &CommandManager::process_text, UP_EVERYONE, MS_DEFAULT); - addTextResponse(name, text); - m_config_descriptions[name] = CommandDescription(usage, permissions, description); - } - else if (node_name == "file-command") - { - node->get("file", &file); - node->get("interval", &interval); - m_commands.emplace_back(name, &CommandManager::process_file, UP_EVERYONE, MS_DEFAULT); - addFileResource(name, file, interval); - m_config_descriptions[name] = CommandDescription(usage, permissions, description); - } - else if (node_name == "auth-command") - { - node->get("secret", &secret); - node->get("server", &server); - node->get("link-format", &link_format); - m_commands.emplace_back(name, &CommandManager::process_auth, UP_EVERYONE, MS_DEFAULT); - addAuthResource(name, secret, server, link_format); - m_config_descriptions[name] = CommandDescription(usage, permissions, description); + const XMLNode *node = current->getNode(i); + std::string node_name = node->getName(); + // here the commands go + std::string name = ""; + std::string text = ""; // for text-command + std::string file = ""; // for file-command + uint64_t interval = 0; // for file-command + std::string usage = ""; + std::string permissions_s = "UP_EVERYONE"; + std::string mode_scope_s = "MS_DEFAULT"; + std::string state_scope_s = "SS_ALWAYS"; + bool omit_name = false; + int permissions; + int mode_scope; + int state_scope; + std::string permissions_str = ""; + std::string description = ""; + std::string aliases = ""; + std::string secret = ""; // for auth-command + std::string link_format = ""; // for auth-command + std::string server = ""; // for auth-command + // If enabled is not empty, command is added iff the server name is in enabled + // Otherwise it is added iff the server name is not in disabled + std::string enabled = ""; + std::string disabled = ""; + node->get("enabled", &enabled); + node->get("disabled", &disabled); + std::vector enabled_split = StringUtils::split(enabled, ' '); + std::vector disabled_split = StringUtils::split(disabled, ' '); + bool ok; + if (!enabled.empty()) + { + ok = false; + for (const std::string& s: enabled_split) + if (s == ServerConfig::m_server_uid) + ok = true; + } + else + { + ok = true; + for (const std::string& s: disabled_split) + if (s == ServerConfig::m_server_uid) + ok = false; + } + if (!ok) + continue; + + node->get("name", &name); + node->get("usage", &usage); + node->get("permissions", &permissions_s); + permissions = CommandManager::permission_reader.parse(permissions_s); + node->get("mode-scope", &mode_scope_s); + mode_scope = CommandManager::mode_scope_reader.parse(mode_scope_s); + node->get("state-scope", &state_scope_s); + state_scope = CommandManager::state_scope_reader.parse(state_scope_s); + node->get("permissions-verbose", &permissions_str); + node->get("description", &description); + node->get("omit-name", &omit_name); + node->get("aliases", &aliases); + std::vector aliases_split = StringUtils::split(aliases, ' '); + + std::shared_ptr c; + if (node_name == "command") + { + c = addChildCommand(command, name, &CommandManager::special, permissions, mode_scope, state_scope); + } + else if (node_name == "text-command") + { + c = addChildCommand(command, name, &CommandManager::process_text, permissions, mode_scope, state_scope); + node->get("text", &text); + addTextResponse(c->getFullName(), text); + } + else if (node_name == "file-command") + { + c = addChildCommand(command, name, &CommandManager::process_file, permissions, mode_scope, state_scope); + node->get("file", &file); + node->get("interval", &interval); + addFileResource(c->getFullName(), file, interval); + } + else if (node_name == "auth-command") + { + c = addChildCommand(command, name, &CommandManager::process_auth, permissions, mode_scope, state_scope); + node->get("secret", &secret); + node->get("server", &server); + node->get("link-format", &link_format); + addAuthResource(name, secret, server, link_format); + } + c->m_description = CommandDescription(usage, permissions_str, description); + m_all_commands.emplace_back(c); + m_full_name_to_command[c->getFullName()] = std::weak_ptr(c); + c->m_omit_name = omit_name; + command->m_stf_subcommand_names.add(name); + for (const std::string& alias_name: aliases_split) + { + command->m_stf_subcommand_names.add(alias_name, name); + command->m_name_to_subcommand[alias_name] = command->m_name_to_subcommand[name]; + } + dfs(node, c); } - } + }; + dfs(root, m_root_command); delete root; } // initCommandsInfo // ======================================================================== void CommandManager::initCommands() { - initCommandsInfo(); using CM = CommandManager; - auto kick_permissions = ((ServerConfig::m_kicks_allowed ? UP_CROWNED : UP_HAMMER) | PE_VOTED_NORMAL); - auto& v = m_commands; - - v.emplace_back("commands", &CM::process_commands, UP_EVERYONE); - v.emplace_back("replay", &CM::process_replay, UP_SINGLE, MS_DEFAULT, SS_LOBBY); - v.emplace_back("start", &CM::process_start, UP_NORMAL | PE_VOTED_NORMAL, MS_DEFAULT, SS_LOBBY); - if (ServerConfig::m_server_configurable) - v.emplace_back("config", &CM::process_config, UP_CROWNED | PE_VOTED_NORMAL, MS_DEFAULT, SS_LOBBY); - v.emplace_back("spectate", &CM::process_spectate, UP_EVERYONE); - v.emplace_back("addons", &CM::process_addons, UP_EVERYONE); - v.emplace_back("moreaddons", &CM::process_addons, UP_EVERYONE); - v.emplace_back("getaddons", &CM::process_addons, UP_EVERYONE); - v.emplace_back("checkaddon", &CM::process_checkaddon, UP_EVERYONE); - v.emplace_back("id", &CM::process_id, UP_EVERYONE); - v.emplace_back("listserveraddon", &CM::process_lsa, UP_EVERYONE); - v.emplace_back("playerhasaddon", &CM::process_pha, UP_EVERYONE); - v.emplace_back("kick", &CM::process_kick, kick_permissions); - v.emplace_back("kickban", &CM::process_kick, UP_HAMMER | PE_VOTED_NORMAL); - v.emplace_back("unban", &CM::process_unban, UP_HAMMER); - v.emplace_back("ban", &CM::process_ban, UP_HAMMER); - v.emplace_back("playeraddonscore", &CM::process_pas, UP_EVERYONE); - v.emplace_back("serverhasaddon", &CM::process_sha, UP_EVERYONE); - v.emplace_back("mute", &CM::process_mute, UP_EVERYONE); - v.emplace_back("unmute", &CM::process_unmute, UP_EVERYONE); - v.emplace_back("listmute", &CM::process_listmute, UP_EVERYONE); - v.emplace_back("description", &CM::process_text, UP_EVERYONE); - v.emplace_back("moreinfo", &CM::process_text, UP_EVERYONE); - v.emplace_back("gnu", &CM::process_gnu, UP_HAMMER | PE_VOTED_NORMAL, MS_DEFAULT, SS_LOBBY); - v.emplace_back("nognu", &CM::process_gnu, UP_HAMMER | PE_VOTED_NORMAL, MS_DEFAULT, SS_LOBBY); - v.emplace_back("tell", &CM::process_tell, UP_EVERYONE); - v.emplace_back("standings", &CM::process_standings, UP_EVERYONE); - v.emplace_back("teamchat", &CM::process_teamchat, UP_EVERYONE); - v.emplace_back("to", &CM::process_to, UP_EVERYONE); - v.emplace_back("public", &CM::process_public, UP_EVERYONE); - v.emplace_back("record", &CM::process_record, UP_EVERYONE); - v.emplace_back("power", &CM::process_power, UP_EVERYONE); - v.emplace_back("length", &CM::process_length, UP_SINGLE, MS_DEFAULT, SS_LOBBY); - v.emplace_back("direction", &CM::process_direction, UP_SINGLE, MS_DEFAULT, SS_LOBBY); - v.emplace_back("queue", &CM::process_queue, UP_SINGLE, MS_DEFAULT, SS_LOBBY); - v.emplace_back("allowstart", &CM::process_allowstart, UP_HAMMER); - v.emplace_back("shuffle", &CM::process_shuffle, UP_HAMMER); - v.emplace_back("timeout", &CM::process_timeout, UP_HAMMER); - v.emplace_back("team", &CM::process_team, UP_HAMMER); - v.emplace_back("swapteams", &CM::process_swapteams, UP_HAMMER); - v.emplace_back("resetteams", &CM::process_resetteams, UP_HAMMER); - v.emplace_back("randomteams", &CM::process_randomteams, UP_HAMMER); - v.emplace_back("resetgp", &CM::process_resetgp, UP_HAMMER, MS_DEFAULT, SS_LOBBY); - v.emplace_back("cat+", &CM::process_cat, UP_HAMMER); - v.emplace_back("cat-", &CM::process_cat, UP_HAMMER); - v.emplace_back("catshow", &CM::process_cat, UP_HAMMER); - v.emplace_back("troll", &CM::process_troll, UP_HAMMER, MS_DEFAULT, SS_LOBBY); - v.emplace_back("hitmsg", &CM::process_hitmsg, UP_HAMMER, MS_DEFAULT, SS_LOBBY); - v.emplace_back("teamhit", &CM::process_teamhit, UP_HAMMER, MS_DEFAULT, SS_LOBBY); - v.emplace_back("scoring", &CM::process_scoring, UP_HAMMER, MS_DEFAULT, SS_LOBBY); - v.emplace_back("version", &CM::process_text, UP_EVERYONE); - v.emplace_back("clear", &CM::process_text, UP_EVERYONE); - v.emplace_back("register", &CM::process_register, UP_EVERYONE); + auto& mp = m_full_name_to_command; + m_root_command = std::make_shared("", &CM::special); + + initCommandsInfo(); + + auto applyFunctionIfPossible = [&](std::string&& name, void (CommandManager::*f)(Context& context)) { + if (mp.count(name) == 0) + return; + std::shared_ptr command = mp[name].lock(); + if (!command) { + return; + } + command->changeFunction(f); + }; + // special permissions according to ServerConfig options + std::shared_ptr kick_command = mp["kick"].lock(); + if (kick_command) { + if (ServerConfig::m_kicks_allowed) + kick_command->m_permissions |= PE_CROWNED; + else + kick_command->m_permissions &= ~PE_CROWNED; + } + + applyFunctionIfPossible("commands", &CM::process_commands); + applyFunctionIfPossible("replay", &CM::process_replay); + applyFunctionIfPossible("start", &CM::process_start); + applyFunctionIfPossible("config", &CM::process_config); + applyFunctionIfPossible("config =", &CM::process_config_assign); + applyFunctionIfPossible("spectate", &CM::process_spectate); + applyFunctionIfPossible("addons", &CM::process_addons); + applyFunctionIfPossible("moreaddons", &CM::process_addons); + applyFunctionIfPossible("getaddons", &CM::process_addons); + applyFunctionIfPossible("checkaddon", &CM::process_checkaddon); + applyFunctionIfPossible("id", &CM::process_id); + applyFunctionIfPossible("listserveraddon", &CM::process_lsa); + applyFunctionIfPossible("playerhasaddon", &CM::process_pha); + applyFunctionIfPossible("kick", &CM::process_kick); // todo Actual permissions are (kick_permissions) + applyFunctionIfPossible("kickban", &CM::process_kick); + applyFunctionIfPossible("unban", &CM::process_unban); + applyFunctionIfPossible("ban", &CM::process_ban); + applyFunctionIfPossible("playeraddonscore", &CM::process_pas); + applyFunctionIfPossible("serverhasaddon", &CM::process_sha); + applyFunctionIfPossible("mute", &CM::process_mute); + applyFunctionIfPossible("unmute", &CM::process_unmute); + applyFunctionIfPossible("listmute", &CM::process_listmute); + applyFunctionIfPossible("description", &CM::process_text); + applyFunctionIfPossible("moreinfo", &CM::process_text); + applyFunctionIfPossible("gnu", &CM::process_gnu); + applyFunctionIfPossible("nognu", &CM::process_gnu); + applyFunctionIfPossible("tell", &CM::process_tell); + applyFunctionIfPossible("standings", &CM::process_standings); + applyFunctionIfPossible("teamchat", &CM::process_teamchat); + applyFunctionIfPossible("to", &CM::process_to); + applyFunctionIfPossible("public", &CM::process_public); + applyFunctionIfPossible("record", &CM::process_record); + applyFunctionIfPossible("power", &CM::process_power); + applyFunctionIfPossible("length", &CM::process_length); + applyFunctionIfPossible("length clear", &CM::process_length_clear); + applyFunctionIfPossible("length =", &CM::process_length_fixed); + applyFunctionIfPossible("length x", &CM::process_length_multi); + applyFunctionIfPossible("direction", &CM::process_direction); + applyFunctionIfPossible("direction =", &CM::process_direction_assign); + applyFunctionIfPossible("queue", &CM::process_queue); + applyFunctionIfPossible("queue push", &CM::process_queue_push); + applyFunctionIfPossible("queue push_back", &CM::process_queue_push); + applyFunctionIfPossible("queue push_front", &CM::process_queue_push); + // Temporary pf commands + applyFunctionIfPossible("queue pf", &CM::process_queue_pf); + applyFunctionIfPossible("queue pf_back", &CM::process_queue_pf); + applyFunctionIfPossible("queue pf_front", &CM::process_queue_pf); + // End of pf commands + applyFunctionIfPossible("queue pop", &CM::process_queue_pop); + applyFunctionIfPossible("queue pop_back", &CM::process_queue_pop); + applyFunctionIfPossible("queue pop_front", &CM::process_queue_pop); + applyFunctionIfPossible("queue clear", &CM::process_queue_clear); + applyFunctionIfPossible("queue shuffle", &CM::process_queue_shuffle); + applyFunctionIfPossible("allowstart", &CM::process_allowstart); + applyFunctionIfPossible("allowstart =", &CM::process_allowstart_assign); + applyFunctionIfPossible("shuffle", &CM::process_shuffle); + applyFunctionIfPossible("shuffle =", &CM::process_shuffle_assign); + applyFunctionIfPossible("timeout", &CM::process_timeout); + applyFunctionIfPossible("team", &CM::process_team); + applyFunctionIfPossible("swapteams", &CM::process_swapteams); + applyFunctionIfPossible("resetteams", &CM::process_resetteams); + applyFunctionIfPossible("randomteams", &CM::process_randomteams); + applyFunctionIfPossible("resetgp", &CM::process_resetgp); + applyFunctionIfPossible("cat+", &CM::process_cat); + applyFunctionIfPossible("cat-", &CM::process_cat); + applyFunctionIfPossible("catshow", &CM::process_cat); + applyFunctionIfPossible("troll", &CM::process_troll); + applyFunctionIfPossible("troll =", &CM::process_troll_assign); + applyFunctionIfPossible("hitmsg", &CM::process_hitmsg); + applyFunctionIfPossible("hitmsg =", &CM::process_hitmsg_assign); + applyFunctionIfPossible("teamhit", &CM::process_teamhit); + applyFunctionIfPossible("teamhit =", &CM::process_teamhit_assign); + applyFunctionIfPossible("scoring", &CM::process_scoring); + applyFunctionIfPossible("scoring =", &CM::process_scoring_assign); + applyFunctionIfPossible("version", &CM::process_text); + applyFunctionIfPossible("clear", &CM::process_text); + applyFunctionIfPossible("register", &CM::process_register); #ifdef ENABLE_WEB_SUPPORT - v.emplace_back("token", &CM::process_token, UP_EVERYONE); + applyFunctionIfPossible("token", &CM::process_token); #endif - v.emplace_back("muteall", &CM::process_muteall, UP_EVERYONE, MS_SOCCER_TOURNAMENT); - v.emplace_back("game", &CM::process_game, UP_HAMMER, MS_SOCCER_TOURNAMENT, SS_LOBBY); - v.emplace_back("role", &CM::process_role, UP_HAMMER, MS_SOCCER_TOURNAMENT); - v.emplace_back("stop", &CM::process_stop, UP_HAMMER, MS_SOCCER_TOURNAMENT, SS_INGAME); - v.emplace_back("go", &CM::process_go, UP_HAMMER, MS_SOCCER_TOURNAMENT, SS_INGAME); - v.emplace_back("play", &CM::process_go, UP_HAMMER, MS_SOCCER_TOURNAMENT, SS_INGAME); - v.emplace_back("resume", &CM::process_go, UP_HAMMER, MS_SOCCER_TOURNAMENT, SS_INGAME); - v.emplace_back("lobby", &CM::process_lobby, UP_HAMMER, MS_SOCCER_TOURNAMENT, SS_INGAME); - v.emplace_back("init", &CM::process_init, UP_HAMMER, MS_SOCCER_TOURNAMENT, SS_INGAME); - v.emplace_back("vote", &CM::special, UP_EVERYONE); - v.emplace_back("mimiz", &CM::process_mimiz, UP_EVERYONE); - v.emplace_back("test", &CM::process_test, UP_EVERYONE | PE_VOTED); - v.emplace_back("help", &CM::process_help, UP_EVERYONE); -// v.emplace_back("1", &CM::special, UP_EVERYONE, MS_DEFAULT); -// v.emplace_back("2", &CM::special, UP_EVERYONE, MS_DEFAULT); -// v.emplace_back("3", &CM::special, UP_EVERYONE, MS_DEFAULT); -// v.emplace_back("4", &CM::special, UP_EVERYONE, MS_DEFAULT); -// v.emplace_back("5", &CM::special, UP_EVERYONE, MS_DEFAULT); - if (!ServerConfig::m_only_host_riding) - v.emplace_back("slots", &CM::process_slots, UP_HAMMER | PE_VOTED_NORMAL, MS_DEFAULT, SS_LOBBY); - v.emplace_back("time", &CM::process_time, UP_EVERYONE); - v.emplace_back("result", &CM::process_result, UP_EVERYONE); - v.emplace_back("preserve", &CM::process_preserve, UP_HAMMER); - - v.emplace_back("addondownloadprogress", &CM::special, UP_EVERYONE); - v.emplace_back("stopaddondownload", &CM::special, UP_EVERYONE); - v.emplace_back("installaddon", &CM::special, UP_EVERYONE); - v.emplace_back("uninstalladdon", &CM::special, UP_EVERYONE); - v.emplace_back("music", &CM::special, UP_EVERYONE); - v.emplace_back("addonrevision", &CM::special, UP_EVERYONE); - v.emplace_back("liststkaddon", &CM::special, UP_EVERYONE); - v.emplace_back("listlocaladdon", &CM::special, UP_EVERYONE); + applyFunctionIfPossible("muteall", &CM::process_muteall); + applyFunctionIfPossible("game", &CM::process_game); + applyFunctionIfPossible("role", &CM::process_role); + applyFunctionIfPossible("stop", &CM::process_stop); + applyFunctionIfPossible("go", &CM::process_go); + applyFunctionIfPossible("play", &CM::process_go); + applyFunctionIfPossible("resume", &CM::process_go); + applyFunctionIfPossible("lobby", &CM::process_lobby); + applyFunctionIfPossible("init", &CM::process_init); + applyFunctionIfPossible("vote", &CM::special); + applyFunctionIfPossible("mimiz", &CM::process_mimiz); + applyFunctionIfPossible("test", &CM::process_test); + applyFunctionIfPossible("test test2", &CM::process_test); + applyFunctionIfPossible("test test3", &CM::process_test); + applyFunctionIfPossible("help", &CM::process_help); +// applyFunctionIfPossible("1", &CM::special); +// applyFunctionIfPossible("2", &CM::special); +// applyFunctionIfPossible("3", &CM::special); +// applyFunctionIfPossible("4", &CM::special); +// applyFunctionIfPossible("5", &CM::special); + applyFunctionIfPossible("slots", &CM::process_slots); + applyFunctionIfPossible("slots =", &CM::process_slots_assign); + applyFunctionIfPossible("time", &CM::process_time); + applyFunctionIfPossible("result", &CM::process_result); + applyFunctionIfPossible("preserve", &CM::process_preserve); + applyFunctionIfPossible("preserve =", &CM::process_preserve_assign); + + applyFunctionIfPossible("addondownloadprogress", &CM::special); + applyFunctionIfPossible("stopaddondownload", &CM::special); + applyFunctionIfPossible("installaddon", &CM::special); + applyFunctionIfPossible("uninstalladdon", &CM::special); + applyFunctionIfPossible("music", &CM::special); + applyFunctionIfPossible("addonrevision", &CM::special); + applyFunctionIfPossible("liststkaddon", &CM::special); + applyFunctionIfPossible("listlocaladdon", &CM::special); addTextResponse("description", StringUtils::wideToUtf8(m_lobby->getGameSetup()->readOrLoadFromFile ((std::string)ServerConfig::m_motd))); @@ -352,25 +463,6 @@ void CommandManager::initCommands() addTextResponse("version", version); addTextResponse("clear", std::string(30, '\n')); - for (Command& command: m_commands) { - m_stf_command_names.add(command.m_name); - command.m_description = m_config_descriptions[command.m_name]; - } - - std::sort(m_commands.begin(), m_commands.end(), [](const Command& a, const Command& b) -> bool { - return a.m_name < b.m_name; - }); - for (auto& command: m_commands) - m_name_to_command[command.m_name] = command; - for (auto& p: m_aliases) - { - for (const std::string& alias_name: p.second) - { - m_stf_command_names.add(alias_name, p.first); - m_name_to_command[alias_name] = m_name_to_command[p.first]; - } - } - // m_votables.emplace("replay", 1.0); m_votables.emplace("start", 0.81); m_votables.emplace("config", 0.6); @@ -426,6 +518,28 @@ CommandManager::CommandManager(ServerLobby* lobby): return; initCommands(); initAssets(); + + m_aux_mode_aliases = { + {"m0"}, + {"m1"}, + {"m2"}, + {"m3", "normal", "normal-race", "race"}, + {"m4", "tt", "time-trial", "trial"}, + {"m5"}, + {"m6", "soccer", "football"}, + {"m7", "ffa", "free-for-all", "free", "for", "all"}, + {"m8", "ctf", "capture-the-flag", "capture", "the", "flag"} + }; + m_aux_difficulty_aliases = { + {"d0", "novice", "easy"}, + {"d1", "intermediate", "medium"}, + {"d2", "expert", "hard"}, + {"d3", "supertux", "super", "best"} + }; + m_aux_goal_aliases = { + {"tl", "time-limit", "time", "minutes"}, + {"gl", "goal-limit", "goal", "goals"} + }; } // CommandManager // ======================================================================== @@ -443,7 +557,7 @@ void CommandManager::handleCommand(Event* event, std::shared_ptr peer) data.decodeString(&cmd); argv = StringUtils::splitQuoted(cmd, ' ', '"', '"', '\\'); - if (argv.size() == 0) + if (argv.empty()) return; CommandManager::restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); @@ -477,16 +591,20 @@ void CommandManager::handleCommand(Event* event, std::shared_ptr peer) !m_user_command_replacements[username][i].empty()) { cmd = m_user_command_replacements[username][i]; argv = StringUtils::splitQuoted(cmd, ' ', '"', '"', '\\'); - if (argv.size() == 0) + if (argv.empty()) return; CommandManager::restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); voting = m_user_saved_voting[username]; restored = true; break; } else { - std::string msg = "Pick one of " + - std::to_string(-1 + (int)m_user_command_replacements[username].size()) - + " options using /1, etc., or use /0, or type a different command"; + std::string msg; + if (m_user_command_replacements[username].empty()) + msg = "This command is for fixing typos. Try typing a different command"; + else + msg = "Pick one of " + + std::to_string(-1 + (int)m_user_command_replacements[username].size()) + + " options using /1, etc., or use /0, or type a different command"; m_lobby->sendStringToPeer(msg, peer); return; } @@ -496,31 +614,59 @@ void CommandManager::handleCommand(Event* event, std::shared_ptr peer) if (!restored) m_user_correct_arguments.erase(username); - if (hasTypo(peer, voting, argv, cmd, 0, m_stf_command_names, 3, false, false)) - return; + std::shared_ptr current_command = m_root_command; + std::shared_ptr executed_command; + for (int idx = 0; ; idx++) + { + bool one_omittable_subcommand = (current_command->m_subcommands.size() == 1 + && current_command->m_subcommands[0]->m_omit_name == true); - auto command_iterator = m_name_to_command.find(argv[0]); + std::shared_ptr command; + if (one_omittable_subcommand) + { + command = current_command->m_subcommands[0]; + } + else + { + if (hasTypo(peer, voting, argv, cmd, idx, current_command->m_stf_subcommand_names, 3, false, idx == 0)) + return; + auto command_iterator = current_command->m_name_to_subcommand.find(argv[idx]); + command = command_iterator->second.lock(); + } - const auto& command = command_iterator->second; + if (!command) + { + // todo change message + std::string msg = "There is no such command but there should be. Very strange. Please report it."; + m_lobby->sendStringToPeer(msg, peer); + return; + } + else if (!isAvailable(command)) + { + std::string msg = "You don't have permissions to " + action + " this command"; + m_lobby->sendStringToPeer(msg, peer); + return; + } + int mask = (permissions & command->m_permissions); + if (mask == 0) + { + std::string msg = "You don't have permissions to " + action + " this command"; + m_lobby->sendStringToPeer(msg, peer); + return; + } + int mask_without_voting = (mask & ~PE_VOTED); + if (mask != PE_NONE && mask_without_voting == PE_NONE) + voting = true; - if (!isAvailable(command)) - { - std::string msg = "You don't have permissions to " + action + " this command"; - m_lobby->sendStringToPeer(msg, peer); - return; - } - int mask = (permissions & command.m_permissions); - if (mask == 0) - { - std::string msg = "You don't have permissions to " + action + " this command"; - m_lobby->sendStringToPeer(msg, peer); - return; + if (one_omittable_subcommand) + --idx; + current_command = command; + if (idx + 1 == argv.size() || command->m_subcommands.empty()) { + executed_command = command; + execute(command, context); + break; + } } - int mask_without_voting = (mask & ~PE_VOTED); - if (mask != PE_NONE && mask_without_voting == PE_NONE) - voting = true; - - execute(command, context); while (!m_triggered_votables.empty()) { @@ -554,10 +700,10 @@ void CommandManager::handleCommand(Event* event, std::shared_ptr peer) std::string new_cmd = p.first + " " + p.second; auto new_argv = StringUtils::splitQuoted(new_cmd, ' ', '"', '"', '\\'); CommandManager::restoreCmdByArgv(new_cmd, new_argv, ' ', '"', '"', '\\'); - std::string msg = "Command \"/" + new_cmd + "\" has been successfully voted"; - m_lobby->sendStringToAllPeers(msg); + std::string msg2 = "Command \"/" + new_cmd + "\" has been successfully voted"; + m_lobby->sendStringToAllPeers(msg2); Context new_context(event, std::shared_ptr(nullptr), new_argv, new_cmd, UP_EVERYONE, false); - execute(command, new_context); + execute(executed_command, new_context); } } } @@ -587,10 +733,10 @@ int CommandManager::getCurrentStateScope() } // getCurrentStateScope // ======================================================================== -bool CommandManager::isAvailable(const Command& c) +bool CommandManager::isAvailable(std::shared_ptr c) { - return (getCurrentModeScope() & c.m_mode_scope) != 0 - && (getCurrentStateScope() & c.m_state_scope) != 0; + return (getCurrentModeScope() & c->m_mode_scope) != 0 + && (getCurrentStateScope() & c->m_state_scope) != 0; } // getCurrentModeScope // ======================================================================== @@ -602,11 +748,11 @@ void CommandManager::vote(Context& context, std::string category, std::string va return; std::string username = StringUtils::wideToUtf8( peer->getPlayerProfiles()[0]->getName()); - auto& votable = m_votables[argv[0]]; + auto& votable = m_votables[context.m_command->m_prefix_name]; bool neededCheck = votable.needsCheck(); votable.castVote(username, category, value); if (votable.needsCheck() && !neededCheck) - m_triggered_votables.push(argv[0]); + m_triggered_votables.push(context.m_command->m_prefix_name); } // vote // ======================================================================== @@ -626,7 +772,13 @@ void CommandManager::update() CommandManager::restoreCmdByArgv(new_cmd, new_argv, ' ', '"', '"', '\\'); std::string msg = "Command \"/" + new_cmd + "\" has been successfully voted"; m_lobby->sendStringToAllPeers(msg); - auto& command = m_name_to_command[new_argv[0]]; + // Happily the name of the votable coincides with the command full name + std::shared_ptr command = m_full_name_to_command[votable_pairs.first].lock(); + if (!command) + { + Log::error("CommandManager", "For some reason command \"%s\" was not found??", votable_pairs.first.c_str()); + continue; + } // We don't know the event though it is only needed in // ServerLobby::startSelection where it is nullptr when they vote Context new_context(nullptr, std::shared_ptr(nullptr), new_argv, new_cmd, UP_EVERYONE, false); @@ -639,20 +791,18 @@ void CommandManager::update() void CommandManager::error(Context& context) { - std::string command = context.m_argv[0]; - // Here we assume that the command argv[0] exists, - // as we intend to invoke error() from process_* functions - std::string msg = m_name_to_command[command].getUsage(); + std::string msg = context.m_command->getUsage(); if (msg.empty()) - msg = "An error occurred while invoking command \"" + context.m_argv[0] + "\"."; + msg = "An error occurred while invoking command \"" + context.m_command->getFullName() + "\"."; m_lobby->sendStringToPeer(msg, context.m_peer); } // error // ======================================================================== -void CommandManager::execute(const Command& command, Context& context) +void CommandManager::execute(std::shared_ptr command, Context& context) { m_current_argv = context.m_argv; - (this->*command.m_action)(context); + context.m_command = command; + (this->*(command->m_action))(context); m_current_argv = {}; } // execute // ======================================================================== @@ -660,32 +810,34 @@ void CommandManager::execute(const Command& command, Context& context) void CommandManager::process_help(Context& context) { auto& argv = context.m_argv; - if (argv.size() < 2) + std::shared_ptr command = m_root_command; + for (int i = 1; i < argv.size(); ++i) { + if (hasTypo(context.m_peer, context.m_voting, context.m_argv, context.m_cmd, i, command->m_stf_subcommand_names, 3, false, false)) + return; + auto ptr = command->m_name_to_subcommand[argv[i]].lock(); + if (ptr) + command = ptr; + else + break; + if (command->m_subcommands.empty()) + break; + } + if (command == m_root_command) { error(context); return; } - - if (hasTypo(context.m_peer, context.m_voting, context.m_argv, context.m_cmd, 1, m_stf_command_names, 3, false, false)) - return; - - std::string msg; - auto it = m_name_to_command.find(argv[1]); - if (it == m_name_to_command.end()) - msg = "Unknown command \"" + argv[1] + "\""; - else - msg = it->second.getHelp(); - + std::string msg = command->getHelp(); m_lobby->sendStringToPeer(msg, context.m_peer); -} // process_commands +} // process_help // ======================================================================== void CommandManager::process_text(Context& context) { std::string response; - auto it = m_text_response.find(context.m_argv[0]); + auto it = m_text_response.find(context.m_command->getFullName()); if (it == m_text_response.end()) - response = "Error: a text command " + context.m_argv[0] + response = "Error: a text command " + context.m_command->getFullName() + " is defined without text"; else response = it->second; @@ -696,10 +848,10 @@ void CommandManager::process_text(Context& context) void CommandManager::process_file(Context& context) { std::string response; - auto it = m_file_resources.find(context.m_argv[0]); + auto it = m_file_resources.find(context.m_command->getFullName()); if (it == m_file_resources.end()) response = "Error: file not found for a file command " - + context.m_argv[0]; + + context.m_command->getFullName(); else response = it->second.get(); m_lobby->sendStringToPeer(response, context.m_peer); @@ -709,10 +861,10 @@ void CommandManager::process_file(Context& context) void CommandManager::process_auth(Context& context) { std::string response; - auto it = m_auth_resources.find(context.m_argv[0]); + auto it = m_auth_resources.find(context.m_command->getFullName()); if (it == m_auth_resources.end()) response = "Error: auth method not found for a command " - + context.m_argv[0]; + + context.m_command->getFullName(); else { auto profile = context.m_peer->getPlayerProfiles()[0]; @@ -730,14 +882,63 @@ void CommandManager::process_auth(Context& context) void CommandManager::process_commands(Context& context) { - std::string result = "Available commands:"; - for (const Command& c: m_commands) + std::string result; + auto& argv = context.m_argv; + std::shared_ptr command = m_root_command; + bool valid_prefix = true; + for (int i = 1; i < argv.size(); ++i) { + if (hasTypo(context.m_peer, context.m_voting, context.m_argv, context.m_cmd, i, command->m_stf_subcommand_names, 3, false, false)) + return; + auto ptr = command->m_name_to_subcommand[argv[i]].lock(); + if (!ptr) + break; + if ((context.m_user_permissions & ptr->m_permissions) != 0 + && isAvailable(ptr)) + command = ptr; + else + { + valid_prefix = false; + break; + } + if (command->m_subcommands.empty()) + break; + } + if (!valid_prefix) { - if ((context.m_user_permissions & c.m_permissions) != 0 - && isAvailable(c)) - result += " " + c.m_name; + result = "There are no available commands with such prefix"; + m_lobby->sendStringToPeer(result, context.m_peer); + return; } - + result = (command == m_root_command ? "Available commands" + : "Available subcommands for /" + command->getFullName()); + result += ":"; + bool had_any_subcommands = false; + std::map res; + for (std::shared_ptr& subcommand: command->m_subcommands) + { + if ((context.m_user_permissions & subcommand->m_permissions) != 0 + && isAvailable(subcommand)) + { + bool subcommands_available = false; + for (auto& c: subcommand->m_subcommands) + { + if ((context.m_user_permissions & c->m_permissions) != 0 + && isAvailable(c)) + subcommands_available = true; + } + res[subcommand->m_name] = subcommands_available; + if (subcommands_available) + had_any_subcommands = true; + } + } + for (const auto& p: res) + { + result += " " + p.first; + if (p.second) + result += "*"; + } + if (had_any_subcommands) + result += "\n* has subcommands"; m_lobby->sendStringToPeer(result, context.m_peer); } // process_commands // ======================================================================== @@ -785,6 +986,36 @@ void CommandManager::process_start(Context& context) void CommandManager::process_config(Context& context) { + int difficulty = m_lobby->getDifficulty(); + int mode = m_lobby->getGameMode(); + bool goal_target = (m_lobby->m_game_setup->hasExtraSeverInfo() ? m_lobby->isSoccerGoalTarget() : false); +// m_aux_goal_aliases[goal_target ? 1 : 0][0] + std::string msg = "Current config: "; + auto get_first_if_exists = [&](std::vector& v) -> std::string { + if (v.size() < 2) + return v[0]; + return v[1]; + }; + msg += " "; + msg += get_first_if_exists(m_aux_mode_aliases[mode]); + msg += " "; + msg += get_first_if_exists(m_aux_difficulty_aliases[difficulty]); + msg += " "; + msg += get_first_if_exists(m_aux_goal_aliases[goal_target ? 1 : 0]); + if (!ServerConfig::m_server_configurable) + msg += " (not configurable)"; + m_lobby->sendStringToPeer(msg, context.m_peer); +} // process_config +// ======================================================================== + +void CommandManager::process_config_assign(Context& context) +{ + if (!ServerConfig::m_server_configurable) + { + std::string msg = "Server is not configurable, this command cannot be invoked."; + m_lobby->sendStringToPeer(msg, context.m_peer); + return; + } const auto& argv = context.m_argv; int difficulty = m_lobby->getDifficulty(); int mode = m_lobby->getGameMode(); @@ -793,51 +1024,30 @@ void CommandManager::process_config(Context& context) bool user_chose_mode = false; bool user_chose_target = false; // bool gp = false; - std::vector> mode_aliases = { - {"m0"}, - {"m1"}, - {"m2"}, - {"m3", "normal", "normal-race", "race"}, - {"m4", "tt", "time-trial", "trial"}, - {"m5"}, - {"m6", "soccer", "football"}, - {"m7", "ffa", "free-for-all", "free", "for", "all"}, - {"m8", "ctf", "capture-the-flag", "capture", "the", "flag"} - }; - std::vector> difficulty_aliases = { - {"d0", "novice", "easy"}, - {"d1", "intermediate", "medium"}, - {"d2", "expert", "hard"}, - {"d3", "supertux", "super", "best"} - }; - std::vector> goal_aliases = { - {"tl", "time-limit", "time", "minutes"}, - {"gl", "goal-limit", "goal", "goals"} - }; for (unsigned i = 1; i < argv.size(); i++) { - for (unsigned j = 0; j < mode_aliases.size(); ++j) { + for (unsigned j = 0; j < m_aux_mode_aliases.size(); ++j) { if (j <= 2 || j == 5) { // Switching to GP or modes 2, 5 is not supported yet continue; } - for (std::string& alias: mode_aliases[j]) { + for (std::string& alias: m_aux_mode_aliases[j]) { if (argv[i] == alias) { mode = j; user_chose_mode = true; } } } - for (unsigned j = 0; j < difficulty_aliases.size(); ++j) { - for (std::string& alias: difficulty_aliases[j]) { + for (unsigned j = 0; j < m_aux_difficulty_aliases.size(); ++j) { + for (std::string& alias: m_aux_difficulty_aliases[j]) { if (argv[i] == alias) { difficulty = j; user_chose_difficulty = true; } } } - for (unsigned j = 0; j < goal_aliases.size(); ++j) { - for (std::string& alias: goal_aliases[j]) { + for (unsigned j = 0; j < m_aux_goal_aliases.size(); ++j) { + for (std::string& alias: m_aux_goal_aliases[j]) { if (argv[i] == alias) { goal_target = (bool)j; user_chose_target = true; @@ -860,15 +1070,15 @@ void CommandManager::process_config(Context& context) // Definitely not the best format as there are extra words, // but I'll think how to resolve it if (user_chose_mode) - vote(context, "config mode", mode_aliases[mode][0]); + vote(context, "config mode", m_aux_mode_aliases[mode][0]); if (user_chose_difficulty) - vote(context, "config difficulty", difficulty_aliases[difficulty][0]); + vote(context, "config difficulty", m_aux_difficulty_aliases[difficulty][0]); if (user_chose_target) - vote(context, "config target", goal_aliases[goal_target ? 1 : 0][0]); + vote(context, "config target", m_aux_goal_aliases[goal_target ? 1 : 0][0]); return; } m_lobby->handleServerConfiguration(context.m_peer, difficulty, mode, goal_target); -} // process_config +} // process_config_assign // ======================================================================== void CommandManager::process_spectate(Context& context) @@ -1864,70 +2074,85 @@ void CommandManager::process_power(Context& context) m_lobby->updatePlayerList(); } // process_power // ======================================================================== - void CommandManager::process_length(Context& context) { - auto& argv = context.m_argv; std::string msg; - if (argv.size() < 2) - { - argv.push_back("check"); - } - if (argv[1] == "check") - { - if (m_lobby->m_default_lap_multiplier < 0 && m_lobby->m_fixed_lap < 0) - msg = "Game length is currently chosen by players"; - else if (m_lobby->m_default_lap_multiplier > 0) - msg = StringUtils::insertValues( + if (m_lobby->m_default_lap_multiplier < 0 && m_lobby->m_fixed_lap < 0) + msg = "Game length is currently chosen by players"; + else if (m_lobby->m_default_lap_multiplier > 0) + msg = StringUtils::insertValues( "Game length is %f x default", m_lobby->m_default_lap_multiplier); - else if (m_lobby->m_fixed_lap > 0) - msg = StringUtils::insertValues( + else if (m_lobby->m_fixed_lap > 0) + msg = StringUtils::insertValues( "Game length is %d", m_lobby->m_fixed_lap); - else - msg = StringUtils::insertValues( + else + msg = StringUtils::insertValues( "An error: game length is both %f x default and %d", m_lobby->m_default_lap_multiplier, m_lobby->m_fixed_lap); - m_lobby->sendStringToPeer(msg, context.m_peer); - return; - } - if (argv[1] == "clear") + m_lobby->sendStringToPeer(msg, context.m_peer); +} // process_length +// ======================================================================== +void CommandManager::process_length_multi(Context& context) +{ + auto& argv = context.m_argv; + double temp_double = -1.0; + if (argv.size() < 3 || + !StringUtils::parseString(argv[2], &temp_double)) { - m_lobby->m_default_lap_multiplier = -1.0; - m_lobby->m_fixed_lap = -1; - msg = "Game length will be chosen by players"; - m_lobby->sendStringToAllPeers(msg); + error(context); return; } - double temp_double = -1.0; - int temp_int = -1; - if (argv[1] == "x" && argv.size() >= 3 && - StringUtils::parseString(argv[2], &temp_double)) - { - m_lobby->m_default_lap_multiplier = std::max(0.0, temp_double); - m_lobby->m_fixed_lap = -1; - msg = StringUtils::insertValues( + m_lobby->m_default_lap_multiplier = std::max(0.0, temp_double); + m_lobby->m_fixed_lap = -1; + std::string msg = StringUtils::insertValues( "Game length is now %f x default", m_lobby->m_default_lap_multiplier); - m_lobby->sendStringToAllPeers(msg); - return; - } - if (argv[1] == "=" && argv.size() >= 3 && - StringUtils::parseString(argv[2], &temp_int)) + m_lobby->sendStringToAllPeers(msg); +} // process_length_multi +// ======================================================================== +void CommandManager::process_length_fixed(Context& context) +{ + auto& argv = context.m_argv; + int temp_int = -1; + if (argv.size() < 3 || + !StringUtils::parseString(argv[2], &temp_int)) { - m_lobby->m_fixed_lap = std::max(0, temp_int); - m_lobby->m_default_lap_multiplier = -1.0; - msg = StringUtils::insertValues( - "Game length is now %d", m_lobby->m_fixed_lap); - m_lobby->sendStringToAllPeers(msg); + error(context); return; } - - error(context); -} // process_length -// ======================================================= + m_lobby->m_fixed_lap = std::max(0, temp_int); + m_lobby->m_default_lap_multiplier = -1.0; + std::string msg = StringUtils::insertValues( + "Game length is now %d", m_lobby->m_fixed_lap); + m_lobby->sendStringToAllPeers(msg); +} // process_length_fixed +// ======================================================================== +void CommandManager::process_length_clear(Context& context) +{ + m_lobby->m_default_lap_multiplier = -1.0; + m_lobby->m_fixed_lap = -1; + std::string msg = "Game length will be chosen by players"; + m_lobby->sendStringToAllPeers(msg); +} // process_length_clear +// ======================================================================== void CommandManager::process_direction(Context& context) +{ + std::string msg = "Direction is "; + if (m_lobby->m_fixed_direction == -1) + { + msg += "chosen ingame"; + } else + { + msg += "set to "; + msg += (m_lobby->m_fixed_direction == 0 ? "forward" : "reverse"); + } + m_lobby->sendStringToAllPeers(msg); +} // process_direction +// ======================================================================== + +void CommandManager::process_direction_assign(Context& context) { auto& argv = context.m_argv; std::string msg; @@ -1958,121 +2183,138 @@ void CommandManager::process_direction(Context& context) msg += (temp_int == 0 ? "forward" : "reverse"); } m_lobby->sendStringToAllPeers(msg); -} // process_direction +} // process_direction_assign // ======================================================================== void CommandManager::process_queue(Context& context) { - // todo update help when everything is changed - std::string msg; + std::string msg = StringUtils::insertValues("Queue (size = %d):", + (int)m_lobby->m_tracks_queue.size()); + for (const TrackFilter& s: m_lobby->m_tracks_queue) { + msg += " " + s.toString(); + } + m_lobby->sendStringToPeer(msg, context.m_peer); +} // process_queue +// ======================================================================== + +void CommandManager::process_queue_push(Context& context) +{ auto& argv = context.m_argv; - if (argv.size() < 2) + + if (argv.size() < 3) { - argv.push_back("show"); + error(context); + return; } - if (argv[1] == "show") + bool to_front = (argv[1] == "push_front"); + if (argv[2] == "-") + argv[2] = getRandomMap(); + else if (argv[2] == "-addon") + argv[2] = getRandomAddonMap(); + if (hasTypo(context.m_peer, context.m_voting, context.m_argv, context.m_cmd, + 2, m_stf_all_maps, 3, false, false)) + return; + if (to_front) + m_lobby->m_tracks_queue.emplace_front(argv[2]); + else + m_lobby->m_tracks_queue.emplace_back(argv[2]); + std::string msg = "Pushed " + argv[2] + + " to the " + (to_front ? "front" : "back") + + " of queue, current queue size: " + + std::to_string(m_lobby->m_tracks_queue.size()); + m_lobby->sendStringToAllPeers(msg); + m_lobby->updatePlayerList(); +} // process_queue_push +// ======================================================================== + +void CommandManager::process_queue_pf(Context& context) +{ + auto& argv = context.m_argv; + + if (argv.size() < 3) { - msg = StringUtils::insertValues("Queue (size = %d):", (int)m_lobby->m_tracks_queue.size()); - for (const TrackFilter& s: m_lobby->m_tracks_queue) { - msg += " " + s.toString(); - } - m_lobby->sendStringToPeer(msg, context.m_peer); + error(context); return; } - if (argv[1] == "push" || argv[1] == "push_back" || argv[1] == "push_front") - { - if (argv.size() < 3) - { - error(context); - return; - } - bool to_front = (argv[1] == "push_front"); - if (argv[2] == "-") - argv[2] = getRandomMap(); - else if (argv[2] == "-addon") - argv[2] = getRandomAddonMap(); - if (hasTypo(context.m_peer, context.m_voting, context.m_argv, context.m_cmd, - 2, m_stf_all_maps, 3, false, false)) - return; - if (to_front) - m_lobby->m_tracks_queue.emplace_front(argv[2]); - else - m_lobby->m_tracks_queue.emplace_back(argv[2]); - std::string msg = "Pushed " + argv[2] - + " to the " + (to_front ? "front" : "back") - + " of queue, current queue size: " - + std::to_string(m_lobby->m_tracks_queue.size()); - m_lobby->sendStringToAllPeers(msg); - m_lobby->updatePlayerList(); + bool to_front = (argv[1] == "pf_front"); + std::string filter_text = argv[2]; + if (argv.size() > 3) { + CommandManager::restoreCmdByArgv(filter_text, argv, ' ', '"', '"', '\\', 2); } - else if (argv[1] == "pf" || argv[1] == "pf_back" || argv[1] == "pf_front") - { - if (argv.size() < 3) - { - error(context); - return; - } - bool to_front = (argv[1] == "pf_front"); - std::string filter_text = argv[2]; - if (argv.size() > 3) { - CommandManager::restoreCmdByArgv(filter_text, argv, ' ', '"', '"', '\\', 2); - } - if (to_front) - m_lobby->m_tracks_queue.emplace_front(filter_text); - else - m_lobby->m_tracks_queue.emplace_back(filter_text); - std::string msg = "Pushed { " + filter_text + " }" - + " to the " + (to_front ? "front" : "back") - + " of queue, current queue size: " - + std::to_string(m_lobby->m_tracks_queue.size()); - m_lobby->sendStringToAllPeers(msg); - m_lobby->updatePlayerList(); + if (to_front) + m_lobby->m_tracks_queue.emplace_front(filter_text); + else + m_lobby->m_tracks_queue.emplace_back(filter_text); + std::string msg = "Pushed { " + filter_text + " }" + + " to the " + (to_front ? "front" : "back") + + " of queue, current queue size: " + + std::to_string(m_lobby->m_tracks_queue.size()); + m_lobby->sendStringToAllPeers(msg); + m_lobby->updatePlayerList(); +} // process_queue_pf +// ======================================================================== + +void CommandManager::process_queue_pop(Context& context) +{ + std::string msg; + auto& argv = context.m_argv; + + bool from_back = (argv[1] == "pop_back"); + if (m_lobby->m_tracks_queue.empty()) { + msg = "Queue was empty before."; } - else if (argv[1] == "pop" || argv[1] == "pop_front" || argv[1] == "pop_back") + else { - msg = ""; - bool from_back = (argv[1] == "pop_back"); - if (m_lobby->m_tracks_queue.empty()) { - msg = "Queue was empty before."; - } + auto object = (from_back ? m_lobby->m_tracks_queue.back() : m_lobby->m_tracks_queue.front()); + msg = "Popped " + object.toString() + + " from the " + (from_back ? "back" : "front") + " of the queue,"; + if (from_back) + m_lobby->m_tracks_queue.pop_back(); else - { - auto object = (from_back ? m_lobby->m_tracks_queue.back() : m_lobby->m_tracks_queue.front()); - msg = "Popped " + object.toString() - + " from the " + (from_back ? "back" : "front") + " of the queue,"; - if (from_back) - m_lobby->m_tracks_queue.pop_back(); - else - m_lobby->m_tracks_queue.pop_front(); - msg += " current queue size: " - + std::to_string(m_lobby->m_tracks_queue.size()); - } - m_lobby->sendStringToAllPeers(msg); - m_lobby->updatePlayerList(); - } - else if (argv[1] == "clear") - { - msg = StringUtils::insertValues( - "Queue is now empty (previous size: %d)", - (int)m_lobby->m_tracks_queue.size()); - m_lobby->m_tracks_queue.clear(); - m_lobby->sendStringToAllPeers(msg); - m_lobby->updatePlayerList(); - } - else if (argv[1] == "shuffle") - { - std::random_device rd; - std::mt19937 g(rd()); - auto& queue = m_lobby->m_tracks_queue; - std::shuffle(queue.begin(), queue.end(), g); - msg = "Queue is now shuffled"; - m_lobby->sendStringToAllPeers(msg); - m_lobby->updatePlayerList(); + m_lobby->m_tracks_queue.pop_front(); + msg += " current queue size: " + + std::to_string(m_lobby->m_tracks_queue.size()); } -} // process_queue + m_lobby->sendStringToAllPeers(msg); + m_lobby->updatePlayerList(); +} // process_queue_pop +// ======================================================================== + +void CommandManager::process_queue_clear(Context& context) +{ + std::string msg = StringUtils::insertValues( + "Queue is now empty (previous size: %d)", + (int)m_lobby->m_tracks_queue.size()); + m_lobby->m_tracks_queue.clear(); + m_lobby->sendStringToAllPeers(msg); + m_lobby->updatePlayerList(); +} // process_queue_clear +// ======================================================================== + +void CommandManager::process_queue_shuffle(Context& context) +{ + std::random_device rd; + std::mt19937 g(rd()); + auto& queue = m_lobby->m_tracks_queue; + std::shuffle(queue.begin(), queue.end(), g); + std::string msg = "Queue is now shuffled"; + m_lobby->sendStringToAllPeers(msg); + m_lobby->updatePlayerList(); +} // process_queue_shuffle // ======================================================================== void CommandManager::process_allowstart(Context& context) +{ + std::string msg; + if (m_lobby->m_allowed_to_start) + msg = "Starting the game is allowed"; + else + msg = "Starting the game is forbidden"; + m_lobby->sendStringToPeer(msg, context.m_peer); +} // process_allowstart +// ======================================================================== + +void CommandManager::process_allowstart_assign(Context& context) { std::string msg; auto& argv = context.m_argv; @@ -2091,11 +2333,22 @@ void CommandManager::process_allowstart(Context& context) m_lobby->m_allowed_to_start = true; msg = "Now starting a game is allowed"; } - m_lobby->sendStringToPeer(msg, context.m_peer); -} // process_allowstart + m_lobby->sendStringToAllPeers(msg); +} // process_allowstart_assign // ======================================================================== void CommandManager::process_shuffle(Context& context) +{ + std::string msg; + if (m_lobby->m_shuffle_gp) + msg = "The GP grid is sorted by score"; + else + msg = "The GP grid is shuffled"; + m_lobby->sendStringToPeer(msg, context.m_peer); +} // process_shuffle +// ======================================================================== + +void CommandManager::process_shuffle_assign(Context& context) { std::string msg; auto& argv = context.m_argv; @@ -2112,8 +2365,8 @@ void CommandManager::process_shuffle(Context& context) m_lobby->m_shuffle_gp = true; msg = "Now the GP grid is shuffled"; } - m_lobby->sendStringToPeer(msg, context.m_peer); -} // process_shuffle + m_lobby->sendStringToAllPeers(msg); +} // process_shuffle_assign // ======================================================================== void CommandManager::process_timeout(Context& context) @@ -2364,6 +2617,17 @@ void CommandManager::process_cat(Context& context) // ======================================================================== void CommandManager::process_troll(Context& context) +{ + std::string msg; + if (m_lobby->m_troll_active) + msg = "Trolls will be kicked"; + else + msg = "Trolls can stay"; + m_lobby->sendStringToPeer(msg, context.m_peer); +} // process_troll +// ======================================================================== + +void CommandManager::process_troll_assign(Context& context) { std::string msg; auto& argv = context.m_argv; @@ -2380,11 +2644,22 @@ void CommandManager::process_troll(Context& context) m_lobby->m_troll_active = true; msg = "Trolls will be kicked"; } - m_lobby->sendStringToPeer(msg, context.m_peer); -} // process_troll + m_lobby->sendStringToAllPeers(msg); +} // process_troll_assign // ======================================================================== void CommandManager::process_hitmsg(Context& context) +{ + std::string msg; + if (m_lobby->m_show_teammate_hits) + msg = "Teammate hits are sent to all players"; + else + msg = "Teammate hits are not sent"; + m_lobby->sendStringToPeer(msg, context.m_peer); +} // process_hitmsg +// ======================================================================== + +void CommandManager::process_hitmsg_assign(Context& context) { std::string msg; auto& argv = context.m_argv; @@ -2402,10 +2677,21 @@ void CommandManager::process_hitmsg(Context& context) msg = "Teammate hits will be sent to all players"; } m_lobby->sendStringToAllPeers(msg); -} // process_hitmsg +} // process_hitmsg_assign // ======================================================================== void CommandManager::process_teamhit(Context& context) +{ + std::string msg; + if (m_lobby->m_teammate_hit_mode) + msg = "Teammate hits are punished"; + else + msg = "Teammate hits are not punished"; + m_lobby->sendStringToPeer(msg, context.m_peer); +} // process_teamhit +// ======================================================================== + +void CommandManager::process_teamhit_assign(Context& context) { std::string msg; auto& argv = context.m_argv; @@ -2425,24 +2711,26 @@ void CommandManager::process_teamhit(Context& context) msg = "Teammate hits are punished now"; } m_lobby->sendStringToAllPeers(msg); -} // process_teamhit +} // process_teamhit_assign // ======================================================================== void CommandManager::process_scoring(Context& context) +{ + std::string msg = "Current scoring is \"" + m_lobby->m_scoring_type; + for (int param: m_lobby->m_scoring_int_params) + msg += StringUtils::insertValues(" %d", param); + msg += "\""; + m_lobby->sendStringToPeer(msg, context.m_peer); +} // process_scoring +// ======================================================================== + +void CommandManager::process_scoring_assign(Context& context) { std::string msg; auto& argv = context.m_argv; std::string cmd2; CommandManager::restoreCmdByArgv(cmd2, argv, ' ', '"', '"', '\\', 1); - if (cmd2 == "") - { - msg = "Current scoring is \"" + m_lobby->m_scoring_type; - for (int param: m_lobby->m_scoring_int_params) - msg += StringUtils::insertValues(" %d", param); - msg += "\""; - m_lobby->sendStringToPeer(msg, context.m_peer); - } - else if (m_lobby->loadCustomScoring(cmd2)) + if (m_lobby->loadCustomScoring(cmd2)) { msg = "Scoring set to \"" + cmd2 + "\""; m_lobby->sendStringToAllPeers(msg); @@ -2452,7 +2740,7 @@ void CommandManager::process_scoring(Context& context) msg = "Scoring could not be parsed from \"" + cmd2 + "\""; m_lobby->sendStringToPeer(msg, context.m_peer); } -} // process_scoring +} // process_scoring_assign // ======================================================================== void CommandManager::process_register(Context& context) @@ -2909,11 +3197,22 @@ void CommandManager::process_mimiz(Context& context) void CommandManager::process_test(Context& context) { auto& argv = context.m_argv; - argv.resize(3, ""); + if (argv.size() == 1) + { + std::string msg = "/test is now deprecated. Use /test *2 [something] [something]"; + m_lobby->sendStringToPeer(msg, context.m_peer); + return; + } + argv.resize(4, ""); auto& peer = context.m_peer; + if (argv[2] == "no" && argv[3] == "u") + { + error(context); + return; + } if (context.m_voting) { - vote(context, "test " + argv[1], argv[2]); + vote(context, "test " + argv[1] + " " + argv[2], argv[3]); return; } std::string username = "Vote"; @@ -2922,24 +3221,31 @@ void CommandManager::process_test(Context& context) username = StringUtils::wideToUtf8( peer->getPlayerProfiles()[0]->getName()); } - std::string msg = username + ", " + argv[1] + ", " + argv[2]; + username = "{" + argv[1].substr(4) + "} " + username; + std::string msg = username + ", " + argv[2] + ", " + argv[3]; m_lobby->sendStringToAllPeers(msg); } // process_test // ======================================================================== void CommandManager::process_slots(Context& context) { - // todo allow non-hammers to invoke /slots without parameters - auto& argv = context.m_argv; - bool fail = false; - int number = 0; - if (argv.size() < 2) + int current = m_lobby->m_current_max_players_in_game.load(); + std::string msg = "Number of slots is currently " + std::to_string(current); + m_lobby->sendStringToPeer(msg, context.m_peer); +} // process_slots +// ======================================================================== + +void CommandManager::process_slots_assign(Context& context) +{ + if (ServerConfig::m_only_host_riding) { - int current = m_lobby->m_current_max_players_in_game.load(); - std::string msg = "Number of slots is currently " + std::to_string(current); + std::string msg = "Changing slots is not possible in the singleplayer mode"; m_lobby->sendStringToPeer(msg, context.m_peer); return; } + auto& argv = context.m_argv; + bool fail = false; + int number = 0; if (argv.size() < 2 || !StringUtils::parseString(argv[1], &number)) fail = true; else if (number <= 0 || number > ServerConfig::m_server_max_players) @@ -2958,7 +3264,7 @@ void CommandManager::process_slots(Context& context) m_lobby->updatePlayerList(); std::string msg = "Number of playable slots is now " + argv[1]; m_lobby->sendStringToAllPeers(msg); -} // process_slots +} // process_slots_assign // ======================================================================== void CommandManager::process_time(Context& context) @@ -2991,46 +3297,38 @@ void CommandManager::process_result(Context& context) // ======================================================================== void CommandManager::process_preserve(Context& context) +{ + std::string msg = "Preserved settings:"; + for (const std::string& str: m_lobby->m_preserve) + msg += " " + str; + m_lobby->sendStringToPeer(msg, context.m_peer); +} // process_preserve +// ======================================================================== + +void CommandManager::process_preserve_assign(Context& context) { auto& peer = context.m_peer; auto& argv = context.m_argv; std::string msg = ""; - if (1 >= argv.size()) + if (argv.size() != 3) { error(context); return; } - if (argv.size() == 2) + if (argv[2] == "0") { - if (argv[1] == "show") - { - msg = "Preserved settings:"; - for (const std::string& str: m_lobby->m_preserve) - msg += " " + str; - } - else - { - error(context); - return; - } + msg = StringUtils::insertValues( + "'%s' isn't preserved on server reset anymore", argv[1].c_str()); + m_lobby->m_preserve.erase(argv[1]); } else { - if (argv[2] == "0") - { - msg = StringUtils::insertValues( - "'%s' isn't preserved on server reset anymore", argv[1].c_str()); - m_lobby->m_preserve.erase(argv[1]); - } - else - { - msg = StringUtils::insertValues( - "'%s' is now preserved on server reset", argv[1].c_str()); - m_lobby->m_preserve.insert(argv[1]); - } + msg = StringUtils::insertValues( + "'%s' is now preserved on server reset", argv[1].c_str()); + m_lobby->m_preserve.insert(argv[1]); } - m_lobby->sendStringToPeer(msg, peer); -} // process_preserve + m_lobby->sendStringToAllPeers(msg); +} // process_preserve_assign // ======================================================================== void CommandManager::special(Context& context) @@ -3039,6 +3337,14 @@ void CommandManager::special(Context& context) // other future special functions that are never executed "as usual" // but need to be displayed in /commands output. So, in fact, this // function is only a placeholder and should be never executed. + Log::warn("CommandManager", "Command %s was invoked " + "but not implemented or unavailable for this server", + context.m_command->getFullName().c_str()); + std::string msg = "This command (%s) is not implemented, or " + "not available for this server. " + "If you believe that is a bug, please report it."; + msg = StringUtils::insertValues(msg, context.m_command->getFullName()); + m_lobby->sendStringToPeer(msg, context.m_peer); } // special // ======================================================================== @@ -3075,6 +3381,7 @@ std::string CommandManager::getRandomAddonMap() const return *it; } // getRandomAddonMap // ======================================================================== + void CommandManager::addUser(std::string& s) { m_users.insert(s); @@ -3128,7 +3435,8 @@ void CommandManager::restoreCmdByArgv(std::string& cmd, bool CommandManager::hasTypo(std::shared_ptr peer, bool voting, std::vector& argv, std::string& cmd, int idx, - SetTypoFixer& stf, int top, bool case_sensitive, bool allow_as_is) + SetTypoFixer& stf, int top, bool case_sensitive, bool allow_as_is, + bool dont_replace) { if (!peer.get()) // voted return false; @@ -3145,8 +3453,10 @@ bool CommandManager::hasTypo(std::shared_ptr peer, bool voting, m_lobby->sendStringToPeer(msg, peer); return true; } - if (closest_commands[0].second != 0 || (closest_commands[0].second == 0 - && closest_commands.size() > 1 && closest_commands[1].second == 0)) + bool no_zeros = closest_commands[0].second != 0; + bool at_least_two_zeros = closest_commands.size() > 1 && closest_commands[1].second == 0; + bool there_is_only_one = closest_commands.size() == 1; + if ((no_zeros || at_least_two_zeros) && !(there_is_only_one && !allow_as_is)) { m_user_command_replacements[username].clear(); m_user_correct_arguments[username] = idx + 1; @@ -3181,10 +3491,12 @@ bool CommandManager::hasTypo(std::shared_ptr peer, bool voting, return true; } - argv[idx] = closest_commands[0].first; // converts case or regex - CommandManager::restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); + if (!dont_replace) + { + argv[idx] = closest_commands[0].first; // converts case or regex + CommandManager::restoreCmdByArgv(cmd, argv, ' ', '"', '"', '\\'); + } return false; - } // hasTypo // ======================================================================== @@ -3202,4 +3514,29 @@ void CommandManager::onStartSelection() m_votables["slots"].resetAllVotes(); update(); } // onStartSelection +// ======================================================================== +std::shared_ptr CommandManager::addChildCommand(std::shared_ptr target, + std::string name, void (CommandManager::*f)(Context& context), + int permissions, int mode_scope, int state_scope) +{ + std::shared_ptr child = std::make_shared(name, f, + permissions, mode_scope, state_scope); + target->m_subcommands.push_back(child); + child->m_parent = std::weak_ptr(target); + if (target->m_prefix_name.empty()) + child->m_prefix_name = name; + else + child->m_prefix_name = target->m_prefix_name + " " + name; + target->m_name_to_subcommand[name] = std::weak_ptr(child); + return child; +} // addChildCommand +// ======================================================================== + +void CommandManager::Command::changePermissions(int permissions, + int mode_scope, int state_scope) +{ + m_permissions = permissions; + m_mode_scope = mode_scope; + m_state_scope = state_scope; +} // changePermissions // ======================================================================== \ No newline at end of file diff --git a/src/network/protocols/command_manager.hpp b/src/network/protocols/command_manager.hpp index 384214231c..676de990b5 100644 --- a/src/network/protocols/command_manager.hpp +++ b/src/network/protocols/command_manager.hpp @@ -39,6 +39,8 @@ #endif #include "network/protocols/command_voting.hpp" +#include "network/protocols/command_permissions.hpp" +#include "utils/enum_extended_reader.hpp" #include "utils/set_typo_fixer.hpp" class ServerLobby; @@ -70,9 +72,13 @@ class CommandManager AuthResource(std::string secret = "", std::string server = "", std::string link_format = ""); - std::string get(std::string username); + std::string get(const std::string& username) const; }; + static EnumExtendedReader permission_reader; + static EnumExtendedReader mode_scope_reader; + static EnumExtendedReader state_scope_reader; + enum ModeScope: int { MS_DEFAULT = 1, @@ -87,6 +93,8 @@ class CommandManager SS_ALWAYS = SS_LOBBY | SS_INGAME }; + struct Command; + struct Context { Event* m_event; @@ -97,6 +105,8 @@ class CommandManager std::string m_cmd; + std::shared_ptr m_command; + int m_user_permissions; bool m_voting; @@ -121,9 +131,9 @@ class CommandManager std::string description = ""): m_usage(usage), m_permissions(permissions), m_description(description) {} - std::string getUsage() { return "Usage: " + m_usage; } + std::string getUsage() const { return "Usage: " + m_usage; } - std::string getHelp() + std::string getHelp() const { return "Usage: " + m_usage + "\nAvailable to: " + m_permissions @@ -135,6 +145,8 @@ class CommandManager { std::string m_name; + std::string m_prefix_name; + int m_permissions; void (CommandManager::*m_action)(Context& context); @@ -143,28 +155,45 @@ class CommandManager int m_state_scope; + bool m_omit_name; + CommandDescription m_description; - Command() {} + std::weak_ptr m_parent; - Command(std::string name, - void (CommandManager::*f)(Context& context), int permissions, - int mode_scope = MS_DEFAULT, int state_scope = SS_ALWAYS): - m_name(name), m_permissions(permissions), m_action(f), - m_mode_scope(mode_scope), m_state_scope(state_scope) {} + std::map> m_name_to_subcommand; - std::string getUsage() { return m_description.getUsage(); } + std::vector> m_subcommands; - std::string getHelp() { return m_description.getHelp(); } + SetTypoFixer m_stf_subcommand_names; + + Command() {} + + Command(std::string name, + void (CommandManager::*f)(Context& context), + int permissions = UP_EVERYONE, + int mode_scope = MS_DEFAULT, int state_scope = SS_ALWAYS); + + void changeFunction(void (CommandManager::*f)(Context& context)) + { m_action = f; } + void changePermissions(int permissions = UP_EVERYONE, + int mode_scope = MS_DEFAULT, + int state_scope = SS_ALWAYS); + + std::string getUsage() const { return m_description.getUsage(); } + std::string getHelp() const { return m_description.getHelp(); } + std::string getFullName() const { return m_prefix_name; } }; private: ServerLobby* m_lobby; - std::vector m_commands; + std::vector> m_all_commands; - std::map m_name_to_command; + std::map> m_full_name_to_command; + + std::shared_ptr m_root_command; std::multiset m_users; @@ -188,8 +217,6 @@ class CommandManager std::map> m_aliases; - SetTypoFixer m_stf_command_names; - SetTypoFixer m_stf_present_users; SetTypoFixer m_stf_all_maps; @@ -198,6 +225,15 @@ class CommandManager std::vector m_current_argv; + // Auxiliary things, should be moved somewhere because they just help + // in commands but have nothing to do with CM itself + + std::vector> m_aux_mode_aliases; + std::vector> m_aux_difficulty_aliases; + std::vector> m_aux_goal_aliases; + + // End of auxiliary things + void initCommandsInfo(); void initCommands(); @@ -206,13 +242,13 @@ class CommandManager int getCurrentModeScope(); int getCurrentStateScope(); - bool isAvailable(const Command& c); + bool isAvailable(std::shared_ptr c); void vote(Context& context, std::string category, std::string value); void update(); void error(Context& context); - void execute(const Command& command, Context& context); + void execute(std::shared_ptr command, Context& context); void process_help(Context& context); void process_text(Context& context); @@ -222,6 +258,7 @@ class CommandManager void process_replay(Context& context); void process_start(Context& context); void process_config(Context& context); + void process_config_assign(Context& context); void process_spectate(Context& context); void process_addons(Context& context); void process_checkaddon(Context& context); @@ -245,10 +282,21 @@ class CommandManager void process_record(Context& context); void process_power(Context& context); void process_length(Context& context); + void process_length_multi(Context& context); + void process_length_fixed(Context& context); + void process_length_clear(Context& context); void process_direction(Context& context); + void process_direction_assign(Context& context); void process_queue(Context& context); + void process_queue_push(Context& context); + void process_queue_pf(Context& context); + void process_queue_pop(Context& context); + void process_queue_clear(Context& context); + void process_queue_shuffle(Context& context); void process_allowstart(Context& context); + void process_allowstart_assign(Context& context); void process_shuffle(Context& context); + void process_shuffle_assign(Context& context); void process_timeout(Context& context); void process_team(Context& context); void process_swapteams(Context& context); @@ -257,9 +305,13 @@ class CommandManager void process_resetgp(Context& context); void process_cat(Context& context); void process_troll(Context& context); + void process_troll_assign(Context& context); void process_hitmsg(Context& context); + void process_hitmsg_assign(Context& context); void process_teamhit(Context& context); + void process_teamhit_assign(Context& context); void process_scoring(Context& context); + void process_scoring_assign(Context& context); void process_register(Context& context); #ifdef ENABLE_WEB_SUPPORT void process_token(Context& context); @@ -275,9 +327,11 @@ class CommandManager void process_mimiz(Context& context); void process_test(Context& context); void process_slots(Context& context); + void process_slots_assign(Context& context); void process_time(Context& context); void process_result(Context& context); void process_preserve(Context& context); + void process_preserve_assign(Context& context); void special(Context& context); std::string getRandomMap() const; @@ -311,13 +365,18 @@ class CommandManager bool hasTypo(std::shared_ptr peer, bool voting, std::vector& argv, std::string& cmd, int idx, - SetTypoFixer& stf, int top, bool case_sensitive, bool allow_as_is); + SetTypoFixer& stf, int top, bool case_sensitive, bool allow_as_is, + bool dont_replace = false); void onResetServer(); void onStartSelection(); std::vector getCurrentArgv() { return m_current_argv; } + + std::shared_ptr addChildCommand(std::shared_ptr target, std::string name, + void (CommandManager::*f)(Context& context), int permissions = UP_EVERYONE, + int mode_scope = MS_DEFAULT, int state_scope = SS_ALWAYS); }; #endif // COMMAND_MANAGER_HPP \ No newline at end of file diff --git a/src/utils/enum_extended_reader.cpp b/src/utils/enum_extended_reader.cpp new file mode 100644 index 0000000000..62815e3167 --- /dev/null +++ b/src/utils/enum_extended_reader.cpp @@ -0,0 +1,39 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2021 kimden +// +// 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 3 +// 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, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include "utils/enum_extended_reader.hpp" +#include "utils/log.hpp" +#include "utils/string_utils.hpp" + +// ======================================================================== + +int EnumExtendedReader::parse(std::string& text) +{ + std::string no_whitespaces = StringUtils::removeWhitespaces(text); + std::vector items = StringUtils::split(no_whitespaces, '|'); + int value = 0; + for (const std::string& key: items) { + auto it = values.find(key); + if (it == values.end()) + Log::warn("EnumExtendedReader", "Key \"%s\" not found - ignored.", key.c_str()); + else + value |= it->second; + } + return value; +} // parse +// ======================================================================== \ No newline at end of file diff --git a/src/utils/enum_extended_reader.hpp b/src/utils/enum_extended_reader.hpp new file mode 100644 index 0000000000..8b82401653 --- /dev/null +++ b/src/utils/enum_extended_reader.hpp @@ -0,0 +1,43 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2021-2022 kimden +// +// 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 3 +// 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, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef ENUM_EXTENDED_READER_HPP +#define ENUM_EXTENDED_READER_HPP + +#include +#include + +// A lazy class that for string representations of enum values, +// allows to read expressions like "ENUM_1 | ENUM_2 | ENUM_3" +// as a number, so that the config is human readable. +// Names should not have spaces or vertical bars. +// Most likely, this class will be extended later for similar purposes, +// as for now it's simple and config may depend on other variables. + +class EnumExtendedReader { +private: + std::map values; +public: + EnumExtendedReader() {} + EnumExtendedReader(std::map&& values): values(values) {} + void add(std::string key, int value) { values[key] = value; } + void remove(std::string key) { values.erase(key); } + int parse(std::string& text); +}; + +#endif \ No newline at end of file